Please check the status of this specification in Launchpad before editing it. If it is Approved, contact the Assignee or another knowledgeable person before making changes.
Launchpad entry: https://launchpad.net/products/upstart/+spec/job-scripts
Summary
Upstart currently provides the option for two shell script tasks that may be run as part of the job's life cycle; one before it is started to prepare, and the other after is is stopped to cleanup. This specification proposes new names for these, allows them to be specified as paths to executables instead of shell scripts and describes an additional two tasks; one after it has started, and another before it is stopped.
Rationale
The current names of the existing tasks, "start" and "stop", are confusing to those coming to upstart from the existing init script system. They expect that these tasks need to actually start or kill the daemon themselves, whereas in upstart, that is handled by the init daemon itself. New names should reduce this confusion and make it obvious what they are for.
The primary process of the job has always been able to be specified as either a shell script or executable, however the "start" and "stop" tasks have only been able to be shell scripts. This is an artificial limitation with no real reason for existing, so will be removed to improve consistency.
While the two current tasks allow for preparing and cleaning up, they're not sufficient for performing such things as post-start checks, waiting for a daemon to be listening, instructing a daemon to save its state, wait for it to be safe to stop or even cancel the stop entirely. In order to that, seperate jobs needed to be written, which makes maintenance more difficult and is generally pointless; the proposed new tasks allow these things to be done inside the same job definition.
Use cases
- Once a daemon is started, it would be useful to have a script that connected to it to verify whether it is, in fact, running or not.
- A daemon may not be able to be stopped while there are connections to it, or it may wish to emit an event to inform others that it is going away.
Scope
This scope of this specification is limited to specifying the names and descriptions of the tasks, and how they are configured. The necessary underlying state changes are specified by JobStates.
Design
The start task will be renamed to pre-start, this better reflects that it happens before the job is actually started and is used for preparation.
pre-start script mkdir /var/run/daemon end scriptThe stop task will be renamed to post-stop, this better reflects that it happens after the job has stopped, and is used for cleanup.
post-stop script rm -rf /var/run/daemon end scriptIn the configuration file, either script or exec may follow the name of the task. It is an error to specify both.
pre-start exec /usr/lib/daemon/prepare.pl
A new post-start task will be introduced.
This may be specified as either a script or exec.
- It is run once the primary process has been started, and the pid located; alongside that process.
- The job is not considered fully started until this script finishes, so it may be used to hold up other jobs.
- The exit status of this task does not affect the goal of the job, if it terminates with an error, the job will still be running.
If the goal is changed to stop while this task is running (which may be by the task itself), the task will be terminated and the goal changed.
post-start script # wait for listen on port 80 while ! nc -q0 localhost 80 </dev/null >/dev/null 2>&1; do sleep 1; done end scriptA new pre-stop task will be introduced.
This may be specified as either a script or exec.
- It is run before the primary process is terminated, alongside that process.
- The job is not considered to be stopping until this script finishes, so it may be used to delay the termination and hold up other jobs.
- The exit status of this task does not affect the goal of the job, if it terminates with an error, the job will still stop.
If the goal is changed to start while this task is running (which may be by the task itself), the task will be terminated and the goal changed. The task should be careful to trap this and undo anything it does that disables the job.
pre-stop script # disable the queue, wait for it to become empty trap "fooctl enable" TERM fooctl disable while fooq >/dev/null; do sleep 1 done end script
Implementation
Code
Changes largely occur within init/job.h and init/job.c. A new structure will be introduced that defines either a command to execute or a script, this will be used for tasks and the primary process itself.
typedef struct task { int script; char *command; } Task;
When script is FALSE, the command member is executed directly; otherwise it is executed using a shell.
The existing members of the Job structure will be replaced with new members.
Task *process; Task *pre_start; Task *post_start; Task *pre_stop; Task *post_stop;
A new function, job_run_task will be introduced that is passed a task and calls either job_run_command or job_run_script with the details and stores the pid in the appropriate place. This function will be called by job_change_state where appropriate.
Because the post-start and pre-stop tasks can be run alongside the process, an additional pid will need to be tracked for them.
pid_t aux_pid;
This will need to be checked by job_find_by_pid, job_child_reaper will also need to be modified to check which process died and adjust its behaviour appropriately. job_kill_process will need to ensure that this process is also killed.
Data preservation and migration
These changes are not backwards compatible, many existing jobs will need changing. No migration plan is anticipated at this point in the development cycle.