Comment 161 for bug 363695

Revision history for this message
hackerb9 (hackerb9) wrote :

Confirming that this is still a bug in Ubuntu 16.04.1 LTS (Xenial Xerus). Tested on a Pentium III Mobile 1GHz w/ 1GB ram. (Compaq Evo N600c).

Also, I note that this bug has been open for seven years and offer a workaround that solves the problem for me. Since the update-apt-xapian-index process is needlessly impolite even with nice and ionice, I wrote a script (called "stutter") that forces the process to pause repeatedly. (Technically, it uses SIGSTOP/SIGCONT with a 10% active duty cycle and 0.1s period).

It's a kludge, but it definitely works. As I'm posting this comment, I'm running update-apt-xapian-index in the background. Without my script, the streaming YouTube video I'm also playing in the background would have been unwatchably choppy instead of smooth.

To use my solution put the attached script into /usr/local/bin/stutter, make it executable, and update /etc/cron.weekly/apt-xapian-index to prepend stutter before the command:

==== /etc/cron.weekly/apt-xapian-index ====
#!/bin/sh

CMD=/usr/sbin/update-apt-xapian-index

# ionice should not be called in a virtual environment
# (similar to man-db cronjobs)
egrep -q '(envID|VxID):.*[1-9]' /proc/self/status || IONICE=/usr/bin/ionice

if [ -x "$IONICE" ]
then
    IONICE_ARGS="-c 3"
else
    IONICE=""
fi

# Check if we're on battery
if which on_ac_power >/dev/null 2>&1; then
    on_ac_power >/dev/null 2>&1
     ON_BATTERY=$?

    # Here we use "-eq 1" instead of "-ne 0" because
    # on_ac_power could also return 255, which means
    # it can't tell whether we are on AC or not. In
    # that case, run update-a-x-i nevertheless.
    [ "$ON_BATTERY" -eq 1 ] && exit 0
fi

# If we have B9's stutter utility installed, use it to
# prevent excessive interference with interactive users.
STUTTER=$(which stutter)

# Rebuild the index
if [ -x "$CMD" ]
then
    $STUTTER nice -n 19 $IONICE $IONICE_ARGS $CMD --quiet
fi

==== /usr/local/bin/stutter ====
#!/bin/bash

# stutter: Force a process to pause its work every once in a while.
# v1.0 (c) 2016 hackerb9 - GPLv3 or higher.
#
# Usage: stutter -p PID
# or: stutter command [arguments...]

# Even with the latest Linux (4.4, at the moment) it is possible for
# background processes to rudely prevent interactive use of a
# computer. This is true even with "nice -n 19 ionice -c 3 $CMD".
#
# [Yes, I'm looking at you, update-apt-xapian-index.]

# This kludge repeatedly sends STOP and CONT signals to a process to
# force it to give up the resource (CPU/disk/network) it is hogging.

# A reasonable default is to have the process active for 0.01s and
# sleep for 0.09s. (A 10% duty cycle with a period of 0.1s).
ontime=0.01
offtime=0.09

#verbose=true
debug() {
    if [ "$verbose" = "true" ]; then
 echo "$@" >&2
    fi
}

usage() {
    period=$(echo "$ontime + $offtime" | bc)
    duty=$(echo "$ontime * 100 / $period" | bc)

    cat <<EOF
stutter - Force a process to pause its work every once in a while
Usage: stutter [ -p <pid> | <cmd> ]

Current defaults
    on time: $ontime seconds
   off time: $offtime seconds
   (A $duty% duty cycle with a period of ${period}s).
EOF
}

cleanup() {
    if [ "$pid" ] && ps --pid $pid >/dev/null; then
 if [ "$processWasAlreadyRunning" ]; then
     if kill -CONT $pid 2>/dev/null; then
  debug "Detached from $pid"
     fi
 else
     debug "Killing $pid"
     kill $pid 2>/dev/null
 fi
    fi
    trap - EXIT
    exit
}

trap cleanup INT QUIT EXIT

if [ -z "$1" -o "$1" = "-h" -o "$1" = "--help" ]; then
    usage
    exit 1
fi

if [ "$1" = "-p" ]; then
    # Attach to existing process
    processWasAlreadyRunning=TRUE
    pid="$2"
    if [ ! "$pid" ]; then usage; exit 1; fi
    if kill -STOP $pid; then
 debug "Attaching to $pid. Hit ^C to detach."
    else
 echo "Error: Could not send STOP signal to process ID $pid." >&2
 exit 3
    fi
else
    # Run a new command
    "$@" &
    pid=$!
    if [ "$pid" ] && ps --pid $pid >/dev/null; then
 # Note: using ps to check if command was not found or already exited.
 debug "Launched command \"$*\" as PID $pid. Hit ^C to kill it."
    fi
fi

while :; do
    kill -STOP $pid 2>/dev/null || break
    sleep $offtime
    kill -CONT $pid 2>/dev/null || break
    sleep $ontime
done