How do I create a single-execution Upstart job guaranteed to complete before two other jobs begin?

Solution 1:

I believe I have an answer to my own question that leverages CameronNemo's partial solution and Mark Russell's answer to a related but somewhat different question.

Two Upstart configuration files are required. The first is a job that starts as soon as the local filesystem is available, performs the desired file modifications as a pre-start script, and then sits idle in the running state forever:

# modify-files - Single-execution file modification job

start on local-filesystems

console log

pre-start script
  echo "$(date --rfc-3339=ns) $(hostname) $UPSTART_JOB"
  exec /path/to/your/script
end script

The second configuration file is an Upstart task that delays the start of all other jobs that might depend on the files we're trying to modify. It produces one instance of itself per dependent job:

# modify-files-wait - Helper task for modify-files

start on (starting jobA or jobB)
stop on (started modify-files or stopped modify-files)

instance $JOB

console log
normal exit 0 2
task

script
  echo "$(date --rfc-3339=ns) $(hostname) $UPSTART_JOB ($UPSTART_INSTANCE)"
  status modify-files | grep -q "start/running" && exit 0
  start modify-files || true
  sleep infinity
end script

Upstart will kill all instances of modify-files-wait once modify-files is idling in its running state. That normal exit line accounts for the possibility of being killed during its infinite sleep. We need the task line to block jobA and joB until the stopped state is reached. Whichever instance runs first will start modify-files, if it hasn't already been started.

Since modify-files never reaches its stopped state, it will never be run anew, regardless of jobA or jobB being restarted.

This solution seems to be working, but I welcome any criticisms or improvements.

Solution 2:

You can define a simple task job that start on event of your choice, run your script and at the end emit event to start the other two job.

For example:

# mainJob - 
#
# This service emit myEvent to run firstJob 
description "emit myEvent to run firstJob"
start on runlevel [2345]
task
console log
script
     echo "(startTask) $UPSTART_JOB -- $UPSTART_EVENTS" 
     exec /path/to/your/script
     initctl emit -n myEvent
end script

In order to do not modify upstart script of the other two jobs, you should override files that allow you to modify the way in which a job starts and stop by modifying the start on and stop on conditions.

Following my examples I created a simple firstJob.conf like this:

# firstJob - 
#
# This service print environment variable 
description "print environment variable"
start on runlevel [2345]
stop on runlevel [016]
task
console log
script
if [ "$RUNLEVEL" = "0" -o "$RUNLEVEL" = "1" -o "$RUNLEVEL" = "6" ]; then
     exec  echo "(stopTask) $UPSTART_JOB -- $UPSTART_EVENTS"  
else
     exec  echo "(startTask) $UPSTART_JOB -- $UPSTART_EVENTS" 
fi
end script

And then I override start on condition creating override file:

echo "start on myEvent" > /etc/init/firstJob.override

So firstJob will start on myEvent generated by mainJob and stop on runlevel [016]

I tested these jobs on lubuntu 12.04 and after reboot I found in /var/log/upstart/firstJob.log:

  (startTask) firstJob -- myEvent

You should check if "the other two jobs" need particular event condition to start and be sure that mainJob start on these events.