Skip to content

Instantly share code, notes, and snippets.

@bdw
Last active June 18, 2019 23:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bdw/5161445 to your computer and use it in GitHub Desktop.
Save bdw/5161445 to your computer and use it in GitHub Desktop.
Genius - a roman daemon. This is a python context manager which tries to ensure it is the only process identified by a pidfile.
import os
import fcntl
import signal
import time
import contextlib
class Genius(object):
'''
A genius is basically a roman daemon. It differs from python-daemon
in the following ways:
- It replaces its predecessor (identified by the pidfile argument)
- It only really works properly as a context manager
- And it doesn't close files
'''
def __init__(self, pidfile):
self.filename = pidfile
def _open(self):
self._handler = signal.getsignal(signal.SIGTERM)
signal.signal(signal.SIGTERM, self.terminate)
try:
self.file = open(self.filename, 'r+')
except IOError as e: # typically, doesn't exist
self.file = open(self.filename, 'w+')
fcntl.flock(self.file, fcntl.LOCK_SH)
def _close(self):
if not self.file.closed:
fcntl.flock(self.file, fcntl.LOCK_UN)
self.file.close()
signal.signal(signal.SIGTERM, self._handler)
def __enter__(self):
self.detach()
self._open()
self.takeover()
return self
def __exit__(self, type, value, traceback):
self._close()
# catch the closing signal
def terminate(self, sigval, sigframe):
'''Catch SIGTERM and convert it into SystemExit.'''
quit(0)
@contextlib.contextmanager
def timeout(self, seconds):
signal.signal(signal.SIGALRM, lambda s,v: 1)
try:
signal.alarm(seconds)
yield
finally:
signal.alarm(0)
def takeover(self):
'''Take over control from the original daemon'''
try:
value = self.file.read()
os.kill(int(value), signal.SIGTERM)
except (ValueError, OSError, IOError) as e:
pass
try:
# now acquire a write lock
with self.timeout(5):
fcntl.flock(self.file, fcntl.LOCK_EX)
self.file.seek(0,0)
self.file.truncate()
self.file.write("%s\n" % (os.getpid()))
self.file.flush()
# I do not try to capture any errors,
# because there truly is no good way to handle them
finally:
# I must release the exclusive lock, though
fcntl.flock(self.file, fcntl.LOCK_SH)
def detach(self):
'''Detach from the starting process'''
try:
if os.fork():
quit(0)
os.setsid()
if os.fork():
quit(0)
except OSError as e:
print("Could not detach because", e)
quit(255)
with Genius('genius.pid'):
print("I CAN HAZ SLEEP")
time.sleep(60)
print("OH HAI")
@bdw
Copy link
Author

bdw commented Mar 22, 2013

Er zit een mogelijkheid tot deadlock in, omdat er meerdere processen in de beginfase een shared lock kunnen krijgen, en dan tegelijk het exclusieve lock proberen te verkrijgen. Omdat ze in de tussentijd het lot nooit loslaten zal geen van de processen dit slot verkrijgen.

De oplossing is met een timeout (http://stackoverflow.com/questions/5255220/fcntl-flock-how-to-implement-a-timeout) te wachten op het verkrijgen van het lock. Mocht er dan een deadlock ontstaan, dan kan het proces worden afgesloten.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment