< BACK

Bash tips, tricks and code snippets

MUTEX locking a script to prevent parallel or multiple execution

The following is my adaptation of this good implementation of MUTEX (mutual exclusion) locking in a bash script which I use as a template:

#!/usr/bin/env bash

NAME="[generation]"

# lock dirs/files
LOCKDIR="/tmp/"${0##*/}"_LCK"
PIDFILE="${LOCKDIR}/PID"

# exit codes and text for them
SUCCESS=0; ETXT[0]="SUCCESS"
GENERAL=1; ETXT[1]="GENERAL"
LOCKFAIL=2; ETXT[2]="LOCKFAIL"
RECVSIG=3; ETXT[3]="RECVSIG"

###
### start locking attempt
###

trap 'ECODE=$?; echo "$NAME Exit: ${ETXT[ECODE]}($ECODE)" >&2' 0
echo -n "$NAME Locking: " >&2

if mkdir "${LOCKDIR}" &>/dev/null; then

	# lock succeeded, install signal handlers before storing the PID just in case~
	# storing the PID fails
	trap 'ECODE=$?;
		echo "$NAME Removing lock. Exit: ${ETXT[ECODE]}($ECODE)" >&2
		rm -rf "${LOCKDIR}"' 0
	echo "$$" >"${PIDFILE}"
	# the following handler will exit the script on receiving these signals
	# the trap on "0" (EXIT) from above will be triggered by this trap's "exit" command!
	trap 'echo "$NAME Killed by a signal." >&2
		exit ${RECVSIG}' 1 2 3 15
	echo "success, installed signal handlers"

else
	# lock failed, now check if the other PID is alive
	OTHERPID="$(cat "${PIDFILE}")"

	# if cat wasn't able to read the file anymore, another instance probably is
	# about to remove the lock -- exit, we're *still* locked
	if [ $? != 0 ]; then
		echo "lock failed, PID ${OTHERPID} is active" >&2
		exit ${LOCKFAIL}
	fi

	if ! kill -0 $OTHERPID &>/dev/null; then
		# lock is stale, remove it and restart
		echo "removing stale lock of nonexistant PID ${OTHERPID}" >&2
		rm -rf "${LOCKDIR}"
		echo "$NAME restarting myself" >&2
		exec "$0" "$@"
	else
		# lock is valid and OTHERPID is active - exit, we're locked!
		echo "lock failed, PID ${OTHERPID} is active" >&2
		exit ${LOCKFAIL}
	fi
fi

###
### end locking attempt
###