From 3495a773f947105b88f375b525e3045b518443f9 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 18 Mar 2012 01:53:35 +0000 Subject: [PATCH] * 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/.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 --- .../system/activation/activation-script.nix | 5 + .../activation/switch-to-configuration.sh | 146 ++++++++++-------- modules/system/upstart/upstart.nix | 11 +- 3 files changed, 94 insertions(+), 68 deletions(-) diff --git a/modules/system/activation/activation-script.nix b/modules/system/activation/activation-script.nix index 9f10e84f5e9..924d6f680fc 100644 --- a/modules/system/activation/activation-script.nix +++ b/modules/system/activation/activation-script.nix @@ -109,6 +109,11 @@ in mkdir -m 0755 -p /var/run/nix/current-load # for distributed builds 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/upstart diff --git a/modules/system/activation/switch-to-configuration.sh b/modules/system/activation/switch-to-configuration.sh index c09fa89083b..07b24d23727 100644 --- a/modules/system/activation/switch-to-configuration.sh +++ b/modules/system/activation/switch-to-configuration.sh @@ -62,75 +62,95 @@ if [ "$action" = "switch" -o "$action" = "boot" ]; then fi # 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) - newVersion=$(cat @out@/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) - if test "$oldVersion" -ne "$newVersion"; then +if test "$oldVersion" -ne "$newVersion"; then cat < /dev/null | sed -e 's/.*process \([0-9]\+\)/\1/;t;d') - [ -n "$dbusPid" ] && kill -HUP "$dbusPid" - 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" diff --git a/modules/system/upstart/upstart.nix b/modules/system/upstart/upstart.nix index a9ed1d02000..c7203d5672b 100644 --- a/modules/system/upstart/upstart.nix +++ b/modules/system/upstart/upstart.nix @@ -42,13 +42,14 @@ let ${concatMapStrings (n: "env ${n}=\"${getAttr n env}\"\n") (attrNames env)} - ${optionalString (job.preStart != "") '' - pre-start script - exec >> ${log} 2>&1 + pre-start script + exec >> ${log} 2>&1 + ln -sfn "$(readlink -f "/etc/init/${job.name}.conf")" /var/run/upstart-jobs/${job.name} + ${optionalString (job.preStart != "") '' source ${jobHelpers} ${job.preStart} - end script - ''} + ''} + end script ${if job.script != "" && job.exec != "" then abort "Job ${job.name} has both a `script' and `exec' attribute."