* Improved Upstart job handling in switch-to-configuration. It no

longer compares the current configuration to the previous
  configuration, but instead compares the current Upstart state to the
  intended state.  Thus, if the switch script is interrupted, running
  nixos-rebuild again will resume starting/stopping Upstart jobs where
  the previous run left off.

  We determine if an Upstart job has changed by having the pre-start
  script of each Upstart job put a symlink to its .conf file in
  /var/run/upstart-jobs.  So if this symlink differs from the target
  of /etc/init/<job>.conf, then the job has changed.  This also
  prevents multiple restarts of dependent jobs.  E.g., if job B has
  "start on started A" and "stop on stopping A", then restarting A
  will cause B to be restarted, so B shouldn't B restarted a second
  time.

  We only start jobs that are not running if 1) they're tasks that
  have been previously run (like mountall); or 2) they're jobs that
  have a "start on" condition.  This seems a reasonable heuristic.

svn path=/nixos/trunk/; revision=33222
This commit is contained in:
Eelco Dolstra 2012-03-18 01:53:35 +00:00
parent 5a98d6d514
commit 3495a773f9
3 changed files with 94 additions and 68 deletions

View File

@ -109,6 +109,11 @@ in
mkdir -m 0755 -p /var/run/nix/current-load # for distributed builds mkdir -m 0755 -p /var/run/nix/current-load # for distributed builds
mkdir -m 0700 -p /var/run/nix/remote-stores mkdir -m 0700 -p /var/run/nix/remote-stores
# Directory holding symlinks to currently running Upstart
# jobs. Used to determine which jobs need to be restarted
# when switching to a new configuration.
mkdir -m 0700 -p /var/run/upstart-jobs
mkdir -m 0755 -p /var/log mkdir -m 0755 -p /var/log
mkdir -m 0755 -p /var/log/upstart mkdir -m 0755 -p /var/log/upstart

View File

@ -62,75 +62,95 @@ if [ "$action" = "switch" -o "$action" = "boot" ]; then
fi fi
# Activate the new configuration. # Activate the new configuration.
if [ "$action" = "switch" -o "$action" = "test" ]; then if [ "$action" != switch -a "$action" != test ]; then exit 0; fi
oldVersion=$(cat /var/run/current-system/upstart-interface-version 2> /dev/null || echo 0) oldVersion=$(cat /var/run/current-system/upstart-interface-version 2> /dev/null || echo 0)
newVersion=$(cat @out@/upstart-interface-version 2> /dev/null || echo 0) newVersion=$(cat @out@/upstart-interface-version 2> /dev/null || echo 0)
if test "$oldVersion" -ne "$newVersion"; then if test "$oldVersion" -ne "$newVersion"; then
cat <<EOF cat <<EOF
Warning: the new NixOS configuration has an Upstart version that is Warning: the new NixOS configuration has an Upstart version that is
incompatible with the current version. The new configuration won't incompatible with the current version. The new configuration won't
take effect until you reboot the system. take effect until you reboot the system.
EOF EOF
exit 1 exit 1
fi
oldJobs=$(readlink -f /etc/static/init)
newJobs=$(readlink -f @out@/etc/init)
stopJob() {
local job=$1
initctl stop "$job" || true
}
# Stop all services that are not in the new Upstart
# configuration.
for job in $(cd $oldJobs && ls *.conf); do
job=$(basename $job .conf)
if ! test -e "$newJobs/$job.conf"; then
echo "stopping $job..."
stopJob $job
fi
done
# Activate the new configuration (i.e., update /etc, make
# accounts, and so on).
echo "activating the configuration..."
@out@/activate @out@
# Make Upstart reload its jobs.
initctl reload-configuration
# Start all new services and restart all changed services.
for job in $(cd $newJobs && ls *.conf); do
job=$(basename $job .conf)
# Hack: skip the shutdown and control-alt-delete jobs.
# Another hack: don't restart the X server (that would kill all the clients).
# And don't restart dbus, since that causes ConsoleKit to
# forget about current sessions.
# Idem for the emergeny-shell, because its `console owner'
# line screws up the X server.
# Idem for xendomains because we don't want to save/restore
# Xen domains unless we have to.
# TODO: Jobs should be able to declare that they should not be
# auto-restarted.
if echo "$job" | grep -q "^shutdown$\|^control-alt-delete$\|^xserver$\|^dbus$\|^disnix$\|^emergency-shell$\|^xendomains$\|^udevtrigger$\|^drbd-down$"; then continue; fi
if ! test -e "$oldJobs/$job.conf"; then
echo "starting $job..."
initctl start "$job" || true
elif test "$(readlink "$oldJobs/$job.conf")" != "$(readlink "$newJobs/$job.conf")"; then
echo "restarting $job..."
stopJob $job
initctl start "$job" || true
fi
done
# Signal dbus to reload its configuration.
dbusPid=$(initctl status dbus 2> /dev/null | sed -e 's/.*process \([0-9]\+\)/\1/;t;d')
[ -n "$dbusPid" ] && kill -HUP "$dbusPid"
fi fi
newJobs=$(readlink -f @out@/etc/init)
# Stop all currently running jobs that are not in the new Upstart
# configuration. (Here "running" means all jobs that are not in the
# stop/waiting state.)
for job in $(initctl list | sed -e '/ stop\/waiting/ d; /^[^a-z]/ d; s/^\([^ ]\+\).*/\1/' | sort); do
if ! [ -e "$newJobs/$job.conf" ] ; then
echo "stopping obsolete job $job..."
initctl stop "$job" || true
fi
done
# Activate the new configuration (i.e., update /etc, make accounts,
# and so on).
echo "activating the configuration..."
@out@/activate @out@
# Make Upstart reload its jobs.
initctl reload-configuration
# Allow Upstart jobs to react intelligently to a config change.
initctl emit config-changed
# Restart all running jobs that have changed. (Here "running" means
# all jobs that don't have a "stop" goal.) We use the symlinks in
# /var/run/upstart-jobs (created by each job's pre-start script) to
# determine if a job has changed.
for job in $(cd $newJobs && ls *.conf); do
job=$(basename $job .conf)
status=$(status "$job")
if ! [[ "$status" =~ start/ ]]; then continue; fi
if [ "$(readlink -f "$newJobs/$job.conf")" = "$(readlink -f "/var/run/upstart-jobs/$job")" ]; then continue; fi
# Hack: don't restart the X server (that would kill all the clients).
# And don't restart dbus, since that causes ConsoleKit to
# forget about current sessions.
# Idem for xendomains because we don't want to save/restore
# Xen domains unless we have to.
# TODO: Jobs should be able to declare that they should not be
# auto-restarted.
if echo "$job" | grep -q "^xserver$\|^dbus$\|^disnix$\|^xendomains$\|^udevtrigger$"; then
echo "not restarting changed service $job"
continue
fi
echo "restarting changed service $job..."
# Note: can't use "restart" here, since that only restarts the
# job's main process.
stop "$job" || true
start "$job" || true
done
# Start all jobs that are not running but should be. The "should be"
# criterion is tricky: the intended semantics is that we end up with
# the same jobs as after a reboot. If it's a task, restart it if it
# differs from the previous instance of the same task; if it wasn't
# previously run, don't run it. If it's a service, only start it if
# it has a "start on" condition.
for job in $(cd $newJobs && ls *.conf); do
job=$(basename $job .conf)
status=$(status "$job")
if ! [[ "$status" =~ stop/ ]]; then continue; fi
if grep -q '^task$' "$newJobs/$job.conf"; then
if [ ! -e "/var/run/upstart-jobs/$job" -o \
"$(readlink -f "$newJobs/$job.conf")" = "$(readlink -f "/var/run/upstart-jobs/$job")" ];
then continue; fi
echo "starting task $job..."
start "$job" || true
else
if ! grep -q "^start on" "$newJobs/$job.conf"; then continue; fi
echo "starting service $job..."
start "$job" || true
fi
done
# Signal dbus to reload its configuration.
dbusPid=$(initctl status dbus 2> /dev/null | sed -e 's/.*process \([0-9]\+\)/\1/;t;d')
[ -n "$dbusPid" ] && kill -HUP "$dbusPid"

View File

@ -42,13 +42,14 @@ let
${concatMapStrings (n: "env ${n}=\"${getAttr n env}\"\n") (attrNames env)} ${concatMapStrings (n: "env ${n}=\"${getAttr n env}\"\n") (attrNames env)}
${optionalString (job.preStart != "") '' pre-start script
pre-start script exec >> ${log} 2>&1
exec >> ${log} 2>&1 ln -sfn "$(readlink -f "/etc/init/${job.name}.conf")" /var/run/upstart-jobs/${job.name}
${optionalString (job.preStart != "") ''
source ${jobHelpers} source ${jobHelpers}
${job.preStart} ${job.preStart}
end script ''}
''} end script
${if job.script != "" && job.exec != "" then ${if job.script != "" && job.exec != "" then
abort "Job ${job.name} has both a `script' and `exec' attribute." abort "Job ${job.name} has both a `script' and `exec' attribute."