How does one write an Upstart task that is guaranteed to run only once after each startup but is also guaranteed to run to completion before either one of at least two other jobs start running. I do not want to alter the Upstart init files for those two other jobs as they do not belong to me. Restarting either of those two other jobs should not cause the desired task to run again.

The situation is that the desired task must make some modifications to some files in the local filesystem that both of the other jobs will be needing.

I'm completely new to Upstart and finding it a steeper learning curve than I'd expected, so an education on why the solution works will be as valuable as the solution itself.


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

  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.

| improve this answer | |
  • Good! You found the right solution as described in Upstart manual – Lety Oct 10 '14 at 10:49
  • Somehow I managed to miss that recipe in the manual, but that was only half the solution. On another note, both that recipe and Mark Russell used normal exit 2 whereas I used normal exit 0 2. Given the exit 0 possibility, I thought the additional 0 necessary based on this part of the manual. Am I not understanding something here or is that recipe flawed? – froage Oct 10 '14 at 22:07
  • I guess that normal exit stanza adds exit status as normal exit as described in init manual. It should be the same give normal exit 2 or normal exit 0 2 – Lety Oct 12 '14 at 20:09

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]
console log
     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]
console log
if [ "$RUNLEVEL" = "0" -o "$RUNLEVEL" = "1" -o "$RUNLEVEL" = "6" ]; then
     exec  echo "(stopTask) $UPSTART_JOB -- $UPSTART_EVENTS"  
     exec  echo "(startTask) $UPSTART_JOB -- $UPSTART_EVENTS" 
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.

| improve this answer | |
  • Needing to duplicate the start conditions of the dependant jobs into mainJob's start conditions means that things may break in a non-obvious way if I am not made aware of changes by the owners of those other jobs. It's not obvious to me whether merging their conditions will always even be possible. The number of job configuration files also scales with the number of dependent jobs. – froage Oct 10 '14 at 8:23
  • Yes, endeed, your observation is correct, and it is the weak point of this solution – Lety Oct 10 '14 at 10:52
start on starting jobA or starting jobB

instance $JOB

pre-start exec /path/to/script

The starting bit prevents the jobs from moving on in their life cycle until this job completes.

The instance bit is so that both starting events (for jobA and jobB) are inhibited, not just one, as would be the case if you did not have an instance stanza.

THe use of pre-start exec/script (in place of a regular exec/script) is so that when the script / executed command finishes, the job will still be considered running, whereas with a traditional exec/script the job is considered to be stopped when the exec/script exits.

Using task would be precisely what makes the job run twice (e.g. if the jobs you have are restarted), so we are leaving it out.

| improve this answer | |
  • This solution doesn't quite work. It actually ensures that /path/to/script will be run twice, once for each instance, but it does seem to prevent more than two. A fix is not obvious to me. – froage Oct 8 '14 at 7:21
  • You can add some stuff to the beginning that creates a file in /run that says that this thing has already been done, then stops if there is a file already there. – CameronNemo Oct 10 '14 at 23:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.