def __init__(self, app): """ Set up the parameters of a new runner. The `app` argument must have the following attributes: * `stdin_path`, `stdout_path`, `stderr_path`: Filesystem paths to open and replace the existing `sys.stdin`, `sys.stdout`, `sys.stderr`. * `pidfile_path`: Absolute filesystem path to a file that will be used as the PID file for the daemon. If ``None``, no PID file will be used. * `pidfile_timeout`: Used as the default acquisition timeout value supplied to the runner's PID lock file. * `run`: Callable that will be invoked when the daemon is started. """ self.parse_args() self.app = app detach_process = None try: detach_process = app.detach_process except AttributeError: pass self.daemon_context = DaemonContext(detach_process=detach_process) if hasattr(app, 'stdin_path'): self.daemon_context.stdin = open(app.stdin_path, 'r') else: self.daemon_context.stdin = sys.stdin if hasattr(app, 'stdout_path'): self.daemon_context.stdout = open(app.stdout_path, 'w+') else: self.daemon_context.stdout = sys.stdout if hasattr(app, 'stderr_path'): self.daemon_context.stderr = open(app.stderr_path, 'w+', buffering=0) else: self.daemon_context.stderr = sys.stderr self.pidfile = None if app.pidfile_path is not None: self.pidfile = make_pidlockfile(app.pidfile_path, app.pidfile_timeout) self.daemon_context.pidfile = self.pidfile
def __init__(self, app): """ Set up the parameters of a new runner. The `app` argument must have the following attributes: * `stdin_path`, `stdout_path`, `stderr_path`: Filesystem paths to open and replace the existing `sys.stdin`, `sys.stdout`, `sys.stderr`. * `pidfile_path`: Absolute filesystem path to a file that will be used as the PID file for the daemon. If ``None``, no PID file will be used. * `pidfile_timeout`: Used as the default acquisition timeout value supplied to the runner's PID lock file. * `run`: Callable that will be invoked when the daemon is started. """ self.parse_args() self.app = app detach_process = None try: detach_process = app.detach_process except AttributeError: pass self.daemon_context = DaemonContext(detach_process=detach_process) if hasattr(app, 'stdin_path'): self.daemon_context.stdin = open(app.stdin_path, 'r') else: self.daemon_context.stdin = sys.stdin if hasattr(app, 'stdout_path'): self.daemon_context.stdout = open(app.stdout_path, 'w+') else: self.daemon_context.stdout = sys.stdout if hasattr(app, 'stderr_path'): self.daemon_context.stderr = open( app.stderr_path, 'w+', buffering=0) else: self.daemon_context.stderr = sys.stderr self.pidfile = None if app.pidfile_path is not None: self.pidfile = make_pidlockfile( app.pidfile_path, app.pidfile_timeout) self.daemon_context.pidfile = self.pidfile
class DaemonRunner(object): """ Controller for a callable running in a separate background process. The first command-line argument is the action to take: * 'start': Become a daemon and call `app.run()`. * 'stop': Exit the daemon process specified in the PID file. * 'restart': Stop, then start. """ start_message = "started with pid %(pid)d" def __init__(self, app): """ Set up the parameters of a new runner. The `app` argument must have the following attributes: * `stdin_path`, `stdout_path`, `stderr_path`: Filesystem paths to open and replace the existing `sys.stdin`, `sys.stdout`, `sys.stderr`. * `pidfile_path`: Absolute filesystem path to a file that will be used as the PID file for the daemon. If ``None``, no PID file will be used. * `pidfile_timeout`: Used as the default acquisition timeout value supplied to the runner's PID lock file. * `run`: Callable that will be invoked when the daemon is started. """ self.parse_args() self.app = app detach_process = None try: detach_process = app.detach_process except AttributeError: pass self.daemon_context = DaemonContext(detach_process=detach_process) if hasattr(app, 'stdin_path'): self.daemon_context.stdin = open(app.stdin_path, 'r') else: self.daemon_context.stdin = sys.stdin if hasattr(app, 'stdout_path'): self.daemon_context.stdout = open(app.stdout_path, 'w+') else: self.daemon_context.stdout = sys.stdout if hasattr(app, 'stderr_path'): self.daemon_context.stderr = open( app.stderr_path, 'w+', buffering=0) else: self.daemon_context.stderr = sys.stderr self.pidfile = None if app.pidfile_path is not None: self.pidfile = make_pidlockfile( app.pidfile_path, app.pidfile_timeout) self.daemon_context.pidfile = self.pidfile def _usage_exit(self, argv): """ Emit a usage message, then exit. """ progname = os.path.basename(argv[0]) usage_exit_code = 2 action_usage = "|".join(self.action_funcs.keys()) message = "usage: %(progname)s %(action_usage)s" % vars() emit_message(message) sys.exit(usage_exit_code) def parse_args(self, argv=None): """ Parse command-line arguments. """ if argv is None: argv = sys.argv min_args = 2 if len(argv) < min_args: self._usage_exit(argv) self.action = argv[1] if self.action not in self.action_funcs: self._usage_exit(argv) def _start(self): """ Open the daemon context and run the application. """ if is_pidfile_stale(self.pidfile): self.pidfile.break_lock() try: self.daemon_context.open() except pidlockfile.AlreadyLocked: pidfile_path = self.pidfile.path raise DaemonRunnerStartFailureError( "PID file %(pidfile_path)r already locked" % vars()) pid = os.getpid() message = self.start_message % vars() emit_message(message) self.app.run() def _terminate_daemon_process(self): """ Terminate the daemon process specified in the current PID file. """ pid = self.pidfile.read_pid() try: os.kill(pid, signal.SIGTERM) time.sleep(3) try: os.kill(pid, signal.SIGKILL) except OSError, exc: # It might already be dead. If we now get a no such # process error, ignore it. if 'No such process' not in str(exc): raise except OSError, exc: raise DaemonRunnerStopFailureError( "Failed to terminate %(pid)d: %(exc)s" % vars())
class DaemonRunner(object): """ Controller for a callable running in a separate background process. The first command-line argument is the action to take: * 'start': Become a daemon and call `app.run()`. * 'stop': Exit the daemon process specified in the PID file. * 'restart': Stop, then start. """ start_message = "started with pid %(pid)d" def __init__(self, app): """ Set up the parameters of a new runner. The `app` argument must have the following attributes: * `stdin_path`, `stdout_path`, `stderr_path`: Filesystem paths to open and replace the existing `sys.stdin`, `sys.stdout`, `sys.stderr`. * `pidfile_path`: Absolute filesystem path to a file that will be used as the PID file for the daemon. If ``None``, no PID file will be used. * `pidfile_timeout`: Used as the default acquisition timeout value supplied to the runner's PID lock file. * `run`: Callable that will be invoked when the daemon is started. """ self.parse_args() self.app = app detach_process = None try: detach_process = app.detach_process except AttributeError: pass self.daemon_context = DaemonContext(detach_process=detach_process) if hasattr(app, 'stdin_path'): self.daemon_context.stdin = open(app.stdin_path, 'r') else: self.daemon_context.stdin = sys.stdin if hasattr(app, 'stdout_path'): self.daemon_context.stdout = open(app.stdout_path, 'w+') else: self.daemon_context.stdout = sys.stdout if hasattr(app, 'stderr_path'): self.daemon_context.stderr = open(app.stderr_path, 'w+', buffering=0) else: self.daemon_context.stderr = sys.stderr self.pidfile = None if app.pidfile_path is not None: self.pidfile = make_pidlockfile(app.pidfile_path, app.pidfile_timeout) self.daemon_context.pidfile = self.pidfile def _usage_exit(self, argv): """ Emit a usage message, then exit. """ progname = os.path.basename(argv[0]) usage_exit_code = 2 action_usage = "|".join(self.action_funcs.keys()) message = "usage: %(progname)s %(action_usage)s" % vars() emit_message(message) sys.exit(usage_exit_code) def parse_args(self, argv=None): """ Parse command-line arguments. """ if argv is None: argv = sys.argv min_args = 2 if len(argv) < min_args: self._usage_exit(argv) self.action = argv[1] if self.action not in self.action_funcs: self._usage_exit(argv) def _start(self): """ Open the daemon context and run the application. """ if is_pidfile_stale(self.pidfile): self.pidfile.break_lock() try: self.daemon_context.open() except pidlockfile.AlreadyLocked: pidfile_path = self.pidfile.path raise DaemonRunnerStartFailureError( "PID file %(pidfile_path)r already locked" % vars()) pid = os.getpid() message = self.start_message % vars() emit_message(message) self.app.run() def _terminate_daemon_process(self): """ Terminate the daemon process specified in the current PID file. """ pid = self.pidfile.read_pid() try: os.kill(pid, signal.SIGTERM) time.sleep(3) try: os.kill(pid, signal.SIGKILL) except OSError, exc: # It might already be dead. If we now get a no such # process error, ignore it. if 'No such process' not in str(exc): raise except OSError, exc: raise DaemonRunnerStopFailureError( "Failed to terminate %(pid)d: %(exc)s" % vars())