Differences between revisions 1 and 21 (spanning 20 versions)
Revision 1 as of 2007-05-02 23:36:06
Size: 4984
Editor: redondos
Comment:
Revision 21 as of 2016-03-23 11:25:28
Size: 296
Editor: GregorySta
Comment:
Deletions are marked like this. Additions are marked like this.
Line 1: Line 1:
[[Anchor(faq45)]]
== How can I ensure that only one instance of a script is running at a time (mutual exclusion)? ==

We need some means of '''mutual exclusion'''. One easy way is to use a "lock": any number of processes can try to acquire the lock simultaneously, but only one of them will succeed.

How can we implement this using shell scripts? Some people suggest creating a lock file, and checking for its presence:

 {{{
 # locking example -- WRONG

 lockfile=/tmp/myscript.lock
 if [ -f "$lockfile" ]
 then # lock is already held
     echo >&2 "cannot acquire lock, giving up: $lockfile"
     exit 0
 else # nobody owns the lock
     > "$lockfile" # create the file
     #...continue script
 fi}}}

This example '''does not work''', because there is a time window between checking and creating the file. Assume two processes are running the code at the same time. Both check if the lockfile exists, and both get the result that it does not exist. Now both processes assume they have acquired the lock -- a disaster waiting to happen. We need an atomic check-and-create operation, and fortunately there is one: {{{mkdir}}}, the command to create a directory:

 {{{
 # locking example -- CORRECT

 lockdir=/tmp/myscript.lock
 if mkdir "$lockdir"
 then # directory did not exist, but was created successfully
     echo >&2 "successfully acquired lock: $lockdir"
     # continue script
 else
     echo >&2 "cannot acquire lock, giving up on $lockdir"
     exit 0
 fi}}}

The advantage over using a lock file is, that even when two processes call {{{mkdir}}} at the same time, only one process can succeed at most. This atomicity of check-and-create is ensured at the operating system kernel level.

Note that we cannot use "mkdir -p" to automatically create missing path components: "mkdir -p" does not return an error if the directory exists already, but that's the feature we rely upon to ensure mutual exclusion.

Now let's spice up this example by automatically removing the lock when the script finishes:

 {{{
 lockdir=/tmp/myscript.lock
 if mkdir "$lockdir"
 then
     echo >&2 "successfully acquired lock"
 
     # Remove lockdir when the script finishes, or when it receives a signal
     trap 'rm -rf "$lockdir"' 0 # remove directory when script finishes
     trap "exit 2" 1 2 3 15 # terminate script when receiving signal
 
     # Optionally create temporary files in this directory, because
     # they will be removed automatically:
     tmpfile=$lockdir/filelist
 
 else
     echo >&2 "cannot acquire lock, giving up on $lockdir"
     exit 0
 fi}}}

This example provides reliable mutual exclusion. There is still the disadvantage that a ''stale'' lock file could remain when the script is terminated with a signal not caught (or signal 9, SIGKILL), but it's a good step towards reliable mutual exclusion. An example that remedies this (contributed by Charles Duffy) follows:

 ''Are we sure this code's correct? There seems to be a discrepancy between the names LOCK_DEFAULT_NAME and DEFAULT_NAME; and it checks for processes in what looks to be a race condition; and it uses the Linux-specific /proc file system and the GNU-specific egrep -o to do so.... I don't trust it. It looks overly complex and fragile. And quite non-portable. -- GreyCat''

 {{{
 LOCK_DEFAULT_NAME=$0
 LOCK_HOSTNAME="$(hostname -f)"
 
 ## function to take the lock if free; will fail otherwise
 function grab-lock {
   local PROGRAMNAME="${1:-$DEFAULT_NAME}"
   local PID=${2:-$$}
   (
     umask 000;
     mkdir -p "/tmp/${PROGRAMNAME}-lock"
     mkdir "/tmp/${PROGRAMNAME}-lock/held" || return 1
     mkdir "/tmp/${PROGRAMNAME}-lock/held/${LOCK_HOSTNAME}--pid-${PID}" && return 0 || return 1
   ) 2>/dev/null
   return $?
 }
 
 ## function to nicely let go of the lock
 function release-lock {
   local PROGRAMNAME="${1:-$DEFAULT_NAME}"
   local PID=${2:-$$}
   (
     rmdir "/tmp/${PROGRAMNAME}-lock/held/${LOCK_HOSTNAME}--pid-${PID}" || true
     rmdir "/tmp/${PROGRAMNAME}-lock/held" && return 0 || return 1
   ) 2>/dev/null
   return $?
 }
 
 ## function to force anyone else off of the lock
 function break-lock {
   local PROGRAMNAME="${1:-$DEFAULT_NAME}"
   (
     [ -d "/tmp/${PROGRAMNAME}-lock/held" ] || return 0
     for DIR in "/tmp/${PROGRAMNAME}-lock/held/${LOCK_HOSTNAME}--pid-"* ; do
       OTHERPID="$(echo $DIR | egrep -o '[0-9]+$')"
       [ -d /proc/${OTHERPID} ] || rmdir $DIR
     done
     rmdir /tmp/${PROGRAMNAME}-lock/held && return 0 || return 1
   ) 2>/dev/null
   return $?
 }
 
 ## function to take the lock nicely, freeing it first if needed
 function get-lock {
   break-lock "$@" && grab-lock "$@"
 }
 }}}

Instead of using {{{mkdir}}} we could also have used the program to create a symbolic link, {{{ln -s}}}.

For more discussion on these issues, see ProcessManagement.
38 year-old Composer Maness from Bonaventure, really loves r/c planes, SB game Hacker and yoyo. Recalls what a striking place it had been having made a vacation to uKhahlamba / Drakensberg Park.<<BR>><<BR>>
<<BR>><<BR>>
my website ... [[http://w.tt/1pjSQHv|sb game hacker ios 9 no jailbreak]]

38 year-old Composer Maness from Bonaventure, really loves r/c planes, SB game Hacker and yoyo. Recalls what a striking place it had been having made a vacation to uKhahlamba / Drakensberg Park.



my website ... sb game hacker ios 9 no jailbreak

BashFAQ/045 (last edited 2024-01-28 01:21:49 by larryv)