Last active
June 18, 2019 23:35
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.