├── LICENSE ├── README.markdown ├── lib └── geventdaemon.py └── setup.py /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012, Antonin Amand 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # geventdaemon 2 | 3 | ## Introduction 4 | 5 | **geventdaemon** is a wrapper around python-daemon to help daemonizing gevent based processes. 6 | 7 | It delegates the signal handling to gevent instead of using python's 8 | and reinit the event loop after forks. 9 | 10 | ## Specific options 11 | 12 | `monkey`, None by default, it can be a *dict* or 13 | something that evaluate to *True*. 14 | If it is *True*, it patches all. (see `gevent.monkey.patch_all()`). 15 | If it is a *dict*, it pass the dict as keywords arguments to patch_all(). 16 | 17 | `monkey_greenlet_report`, False by default. It patches the 18 | gevent.Greenlet._report_error method to log the error using the root logger 19 | of python logging. 20 | 21 | `signal_map` receives a dict of signals, but handler is either a 22 | callable, a list of arguments [callable, arg1, arg2] or 23 | a string. 24 | callable without arguments will receive (signal, None) as arguments, 25 | meaning the `frame` parameter is always None. 26 | 27 | 28 | from geventdaemon import GeventDaemonContext 29 | def work() 30 | gevent.sleep(500) 31 | 32 | def stop(signal, frame): 33 | # frame is always None, it is there for compatibility with python-daemon 34 | raise SystemExit('terminated by signal %d' % int(signal)) 35 | 36 | context = GeventDaemonContext(monkey={'httplib': True}, 37 | signal_map={signal.SIGTERM: stop, 38 | signal.SIGINT: stop}) 39 | 40 | with context: 41 | # do some gevent work after fork. 42 | work() 43 | 44 | -------------------------------------------------------------------------------- /lib/geventdaemon.py: -------------------------------------------------------------------------------- 1 | import gevent.monkey 2 | import gevent.hub 3 | import daemon 4 | import signal 5 | 6 | 7 | class GeventDaemonContext(daemon.DaemonContext): 8 | """ DaemonContext for gevent. 9 | 10 | Receive same options as a DaemonContext (python-daemon), Except: 11 | 12 | `monkey`: None by default, does nothing. Else it can be a dict or 13 | something that evaluate to True. 14 | If it is True, it patches all. (gevent.monkey.patch_all()). 15 | If it is a dict, it pass the dict as keywords arguments to patch_all(). 16 | 17 | `signal_map`: receives a dict of signals, but handler is either a 18 | callable, a list of arguments [callable, arg1, arg2] or 19 | a string. 20 | callable without arguments will receive (signal, None) as arguments, 21 | meaning the `frame` parameter is always None. 22 | 23 | If the daemon context forks. It calls gevent.reinit(). 24 | """ 25 | 26 | def __init__(self, monkey_greenlet_report=True, 27 | monkey=True, gevent_hub=None, signal_map=None, **daemon_options): 28 | self.gevent_signal_map = signal_map 29 | self.monkey = monkey 30 | self.monkey_greenlet_report = monkey_greenlet_report 31 | self.gevent_hub = gevent_hub 32 | super(GeventDaemonContext, self).__init__( 33 | signal_map={}, **daemon_options) 34 | 35 | def open(self): 36 | super(GeventDaemonContext, self).open() 37 | # always reinit even when not forked when registering signals 38 | self._apply_monkey_patch() 39 | if self.gevent_hub is not None: 40 | # gevent 1.0 only 41 | gevent.get_hub(self.gevent_hub) 42 | gevent.reinit() 43 | self._setup_gevent_signals() 44 | 45 | def _apply_monkey_patch(self): 46 | if isinstance(self.monkey, dict): 47 | gevent.monkey.patch_all(**self.monkey) 48 | elif self.monkey: 49 | gevent.monkey.patch_all() 50 | 51 | if self.monkey_greenlet_report: 52 | import logging 53 | original_report = gevent.hub.Hub.print_exception 54 | 55 | def print_exception(self, context, type, value, tb): 56 | try: 57 | logging.error("Error in greenlet: %s" % str(context), 58 | exc_info=(type, value, tb)) 59 | finally: 60 | return original_report(self, context, type, value, tb) 61 | 62 | gevent.hub.Hub.print_exception = print_exception 63 | 64 | def _setup_gevent_signals(self): 65 | if self.gevent_signal_map is None: 66 | gevent.signal(signal.SIGTERM, self.terminate, signal.SIGTERM, None) 67 | return 68 | 69 | for sig, target in self.gevent_signal_map.items(): 70 | if target is None: 71 | raise ValueError( 72 | 'invalid handler argument for signal %s', str(sig)) 73 | tocall = target 74 | args = [sig, None] 75 | if isinstance(target, list): 76 | if not target: 77 | raise ValueError( 78 | 'handler list is empty for signal %s', str(sig)) 79 | tocall = target[0] 80 | args = target[1:] 81 | elif isinstance(target, basestring): 82 | assert not target.startswith('_') 83 | tocall = getattr(self, target) 84 | 85 | gevent.signal(sig, tocall, *args) 86 | 87 | 88 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup(name="geventdaemon", 4 | version="0.1dev", 5 | author="Antonin Amand", 6 | author_email="antonin.amand@gmail.com", 7 | description="gevent daemonizer", 8 | package_dir = {'':'lib'}, 9 | packages=find_packages('lib'), 10 | zip_safe=False, 11 | install_requires=[ 12 | 'gevent', 13 | 'python-daemon', 14 | ], 15 | ) 16 | 17 | --------------------------------------------------------------------------------