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