Previously, I wrote a version using only the lock file, but it was unpopular (?), So I made a version using the pid file. It's easy to use because it's defined as a decorator. Just do the following:
@singleprocess
def main();
pass
The operation is as follows.
The pid file is specified to be placed under / tmp, and security is not considered. In order to use it in production, it may be necessary to change the getSignature algorithm and the location of the pid file depending on the application.
def singleprocess(func):
def basenameWithoutExt(path):
return os.path.splitext(os.path.basename(path))[0]
def getSignature(pid):
processDirectory = '/proc/%d/' % (pid,)
if os.path.isdir(processDirectory):
args = open(processDirectory + 'cmdline').read().split("\x00")
return [os.path.basename(args[0]), os.path.basename(args[1])]
return None
@contextmanager
def filelockingcontext(path):
open(path, "a").close() #ensure file to exist for locking
with open(path) as lockFd:
fcntl.flock(lockFd, fcntl.LOCK_EX)
yield
fcntl.flock(lockFd, fcntl.LOCK_UN)
def wrapper(*args, **kwargs):
pidFilePath = tempfile.gettempdir() + '/' + basenameWithoutExt(sys.argv[0]) + '.pid'
with filelockingcontext(pidFilePath):
runningPid = ""
with open(pidFilePath) as readFd:
runningPid = readFd.read()
if runningPid != "" and getSignature(int(runningPid)) == getSignature(os.getpid()) is not None:
print("process already exists", file=sys.stderr)
exit()
with open(pidFilePath, "w") as writeFd:
writeFd.write(str(os.getpid()))
try:
func(*args, **kwargs)
finally:
with filelockingcontext(pidFilePath):
os.remove(pidFilePath)
return wrapper
Recommended Posts