Loading...

Using a pidfile for control flow

I had to write recently some shell scripts that may be used for LSB like init.d services. It was a quite good experience and fresh up of my bash scripting skills.

One of the major requirements:

  • The application can only be started once. A second instance is not allowed.
  • To shutdown, we have to kill the process.

Here are my basic steps.

Define colors

I define some colours to illustrate the importance or status of some text.

COLOR_SUCCESS="\\033[1;32m"
COLOR_FAILURE="\\033[1;31m"
COLOR_WARNING="\\033[1;33m"
COLOR_NORMAL="\\033[0;39m"

Remember process id

By starting the application, we need to remember the process id (pid), that has be assigned. This pid will be stored in the file, named pidfile with the extension .pid. I recommend to use a variable for the location of the pidfile.

# place pid according to FHS in /var/run is not possible,
# since it is not started as root
pidfile="$SCRIPT_HOME/java_app.pid"

If the application is started as root, put the pid under /var/run. If the application is running as different user and not started by root, you have to use a custom location.

Write pidfile

In the start function, execute the program.

start() {
    echo -e "Start $name using classpath $CLASSPATH \n"
    ${JAVA_HOME} -Djava.library.path=bin  \
    -classpath $CLASSPATH  \
    net.cinhtau.Starter logback.xml > /dev/null 2> "${SCRIPT_HOME}/$tan.err" &
    # Generate the pidfile from here. If we instead made the forked process
    # generate it there will be a race condition between the pidfile writing
    # and a process possibly asking for status.
    echo $! > $pidfile
}

The special parameter $! expands to the process ID of the most recently executed background (asynchronous) command. This pid is stored to the pidfile. See Bash Beginners Guide for more information about special parameters.

Check if process is still alive

The status function is essential for checking if the application, or more correctly the process id is still alive.

status() {
    if [ -f "$pidfile" ]; then
        pid=`cat "$pidfile"`
        if [ -e "/proc/$pid" ] ; then
            # process by this pid is running.
            # It may not be our pid, but that's what you get with just pidfiles.
            return 0
        else
            return 2 # program is dead but pid file exists
        fi
    else
        return 3 # program is not running
    fi
}

If the pidfile exists, we look for the pid under /proc, the process pseudo filesystem.

Stop application based on pidfile

To stop the application, we have to kill the process. If the application is successfully killed, we have to remove the pidfile.

stop() {
    # Try a few times to kill TERM the program
    if status; then
        pid=`cat "$pidfile"`
        echo "Killing $name (pid $pid)"
        kill -HUP $pid
        # Wait for it to exit.
        for i in `seq 1 5`; do
            echo "Waiting $name (pid $pid) to die..."
            status || break
            sleep 1
        done
        if status ; then
            echo "$name stop failed; still running."
        else
            echo "$name stopped."
            rm $pidfile
        fi
    fi
}

Use pidfile for control flow

This pidfile can now be used for the control flow, e.g. don’t start the application a second time, if the application is already running.

echo_success() {
  echo -n -e $"[$COLOR_SUCCESS  OK  $COLOR_NORMAL]"
  echo -ne "\r"
  return 0
}
case "$1" in
    start)
        if [ -e $pidfile ] ; then
            pid=$(cat $pidfile)
            echo -n -e "Application Component is already running under $COLOR_WARNING $pid $COLOR_NORMAL \n"
        else
            start
        fi
    ;;
    stop)
        stop ;;
    force-stop) force_stop ;;
    status)
        status
        code=$?
        if [ $code -eq 0 ]; then
            echo_success && echo -e "\t $name is running, process `cat $pidfile`"
        else
            echo -e " \t $name is $COLOR_WARNING not running $COLOR_NORMAL"
        fi
        exit $code
    ;;
    restart)
        stop && start
    ;;
    *)
        echo "Usage: cinhtau.sh {start|stop|force-stop|status|restart}"
        exit 3
    ;;
esac
exit $?