Subjectively

dd if=/dev/random | kirk > blog

Subjectively header image 2

bash shell script to manage pid file creation/deletion

October 30th, 2009 · 1 Comment · Linux, OS X

Problem: You want your script to execute without overlapping another execution.
Solution: Make a flag file so you know that your script is already executing.
Extended problem: What if your script dies and leaves the flag file behind?
Extended solution: Check the process list to see if the old instance is still alive.
Ext. Ext. Problem: What if somebody kills my script?
Ext. Ext. Solution: Trap the abnormal exit and clean up properly.
Ext. Ext. Ext. Problem: What if someone else is invoking a script with exactly the same name as mine?
Ext. Ext. Ext. Solution: You’re screwed. Give your script a really long descriptive name so this won’t happen.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#===  FUNCTION  ================================================================
#          NAME:  pidfilename
#   DESCRIPTION:  create a predictable pid file name, put it in the right inode
#    PARAMETERS:  none
#       RETURNS:  path and filename
#===============================================================================
function pidfilename() {
  myfile=$(basename "$0" .sh)
  whoiam=$(whoami)
  mypidfile=/tmp/$myfile.pid
  [[ "$whoiam" == 'root' ]] && mypidfile=/var/run/$myfile.pid
  echo $mypidfile
}
 
 
#===  FUNCTION  ================================================================
#          NAME:  cleanup
#   DESCRIPTION:  post service processing (clean temp space,pid files)
#    PARAMETERS:  none
#       RETURNS:  none
#===============================================================================
function cleanup () {
  #Don't recurse in the exit trap
  trap - INT TERM EXIT
  #remove the pid file cleanly on exit
  [[ -f "$mypidfile" ]] && rm "$mypidfile"
  #add other post processing cleanup here
  exit
}
 
 
#===  FUNCTION  ================================================================
#          NAME:  isrunning
#   DESCRIPTION:  is any previous instance of this script already running
#    PARAMETERS:  pidfile location
#       RETURNS:  boolean 0|1
#===============================================================================
function isrunning() {
  pidfile="$1"
  [[ ! -f "$pidfile" ]] && return 1  #pid file is nonexistent
  procpid=$(<"$pidfile")
  [[ -z "$procpid" ]] && return 1  #pid file contains no pid
  # check process list for pid existence and is an instance of this script
  [[ ! $(ps -p $procpid | grep $(basename $0)) == "" ]] && value=0 || value=1
  return $value
}
 
#===  FUNCTION  ================================================================
#          NAME:  createpidfile
#   DESCRIPTION:  atomic creation of pid file with no race condition
#    PARAMETERS:  the pid to put in the file, the filename to use as a lock
#       RETURNS:  none
#===============================================================================
function createpidfile() {
  mypid=$1
  pidfile=$2
  #Close stderr, don't overwrite existing file, shove my pid in the lock file.
  $(exec 2>&-; set -o noclobber; echo "$mypid" > "$pidfile") 
  [[ ! -f "$pidfile" ]] && exit #Lock file creation failed
  procpid=$(<"$pidfile")
  [[ $mypid -ne $procpid ]] && {
    #I'm not the pid in the lock file
    # Is the process pid in the lockfile still running?
    isrunning "$pidfile" || {
      # No.  Kill the pidfile and relaunch ourselves properly.
      rm "$pidfile"
      $0 $@ &
    }
    exit
  }
}
 
mypidfile=$(pidfilename)
 
createpidfile $$ "$mypidfile"
#  I win!  set a trap for the lockfile on exit
trap 'cleanup' INT TERM EXIT
# Go ahead and do some processing 
sleep 10

I have been informed that there are a few corner cases that this script does not cover. For one, I may have write access to the pidfile, but not delete access. This would cause the second instance of the script to start a fork bomb. I can’t imagine a case where this would actually be true, so I’m not bothering to fix it. Second, this script forks itself in the background if the pid file is determined to be abandoned (i.e. power shut off). This might cause some calling script to return and proceed when the child is not finished. I’m not sure how to fix that without making the code extremely ugly, but when I think of something, I’ll stick it in here.

Tags:

One Comment so far ↓

Leave a Comment

You must log in to post a comment.