How can I avoid running a python script multiple times?

Grzegorz Adam Hankiewicz gradha at terra.es
Tue Jan 28 06:07:04 EST 2003


Hi.

Using the concurrency jargon, I'm trying to mark python scripts as
'critical processes' which are not allowed to be run more than one
at the same time (I'm constrained to unix environments, BTW). To
do so, and following the logic of a bash script I had, I've created
a function which is passed the real entry point of the script.

This wrapper function checks for a .lock file using sys.argv[0]
as a model, and if found, reads it's pid and compares it against
"ps ux".  The script below seems to work, but there are a lot of
broken things I don't know how to fix:

What about signals? Say, I recieve a kill signal, how would I handle
that to terminate the program and delete the .lock file?

What about race conditions and atomicity? If two processes are
started at the same time, maybe both won't detect the lock file
and one will overwrite the lock file of the other, which means
two processes running at the same time.

I have thought that maybe this could be solved with locking through
sockets: the script tries to open a specific socket on localhost, if
it's opened, the process runs, otherwise another one is being
run. Of course I am presuming that the socket would be closed if
the python interpreter died, but that's a supossition. Basically,
this is relaying on the OS to work out the race condition.

Anything else I might have forgotten? Is there a cleaner way to
detect a process than calling "ps"?

#!/usr/bin/env python

import os, sys

def still_running(pid_as_string):
   """Returns zero if the process is not running."""
   child_stdin, child_stdout_and_stderr = os.popen4(["ps", "ux"])
   child_stdin.close()
   # filter processes with the given pid
   lines = filter(lambda x: x.find(" %s " % pid_as_string) >= 0, child_stdout_and_stderr.readlines())
   child_stdout_and_stderr.close()
   # second test, filter with the current binary name
   lines = filter(lambda x: x.find(os.path.basename(sys.argv[0])) >= 0, lines)
   return len(lines)

   

def run_if_possible(entry_point):
   """Given a function, it is called if there's no previous process running"""
   permission = 0
   lock_path = "%s.lock" % os.path.realpath(sys.argv[0])
   if os.path.isfile(lock_path):
      file = open(lock_path, "rt")
      pid = file.readline() # it's a string! not an integer
      file.close()
      if still_running(pid):
         sys.stderr.write("Process '%s:%s' is still running\n" % (pid, sys.argv[0]))
      else:
         sys.stderr.write("Found stale lock '%s'\n" % lock_path)
         permission = 1
   else:
      permission = 1

   if permission:
      file = open(lock_path, "wt")
      file.write("%d" % os.getpid())
      file.close()

      try:
         entry_point()
      finally:
         try: os.unlink(lock_path)
         except IOError: pass



def hello():
   while 1: print "hello"

if __name__ == "__main__":
   run_if_possible(hello)





More information about the Python-list mailing list