Submit Blog  RSS Feeds

Wednesday, April 4, 2012

Preventing python script from running multiple times (unix)

 Recently I was implementing many command line scripts that were scheduled in crontab. What a programmer has to know is that cron is a ruthless daemon and it doesn't care about your tasks... they are executed periodically according to a schedule - with no exceptions... even if the previous task is still running. In most cases this shouldn't be a problem, yet problems may occur when the following conditions are met:
  •  the average execution time of script s is greater then the defined crontab execution period.
  •  multiple instances of script s can't be executed simultaneously
 Critical sections are cool for solving similar problems, but processes spawned by cron don't share memory. A frequently used mechanism for solving such cases is file locking. Take a look at this python decorator (comments provided):

  1 import os
  2
  3 class SingleRun():
  4     class InstanceRunningException(Exception):
  5         pass
  6     def __init__(self, lock_file):
  7         #define the lock file name
  8         self.lock_file =  "/tmp/%s.pid" % lock_file
  9     def __call__(self, func):
 10         def f(*args, **kwargs):
 11             if os.path.exists(self.lock_file):
 12                 #get process id, if lock file exists
 13                 pid = open(self.lock_file, "rt").read()
 14                 if not os.path.exists("/proc/%s" % pid):
 15                     #if process is not alive remove the lock file
 16                     os.unlink(self.lock_file)
 17                 else:
 18                     #process is running
 19                     raise self.InstanceRunningException(pid)
 20             try:
 21                 #store process id
 22                 open(self.lock_file, "wt").write(str(os.getpid()))
 23                 #execute wrapped function
 24                 func(*args,**kwargs)
 25             finally:
 26                 if os.path.exists(self.lock_file):
 27                     os.unlink(self.lock_file)
 28         return f

So for example if we define the following functions:

  1 @SingleRun(lock_file="x")
  2 def a():
  3     pass
  4
  5 @SingleRun(lock_file="x")
  6 def b():
  7     pass
  8
  9 @SingleRun(lock_file="y")
 10 def c():
 11     pass


Only one function among a() and b(), may be executed at a time, since they share the same lock file.  Function c(), may be executed along with a() or b(), but only if no other c() function is executed (by other processes).

The presented solution is OS dependant (Unix).

No comments:

Post a Comment

free counters