def check_process(pidfile): """ Read pid file and check process status. Return (running, pid). """ # Check pid file try: handle = open(pidfile, 'r') except IOError as exc: if exc.errno == errno.ENOENT: # pid file disappeared return False, 0 raise try: pid = int(handle.read().strip(), 10) except (TypeError, ValueError) as exc: raise EnvironmentError("Invalid PID file '%s' (%s), won't start!" % (pidfile, exc)) finally: handle.close() # Check process try: os.kill(pid, 0) except EnvironmentError as exc: return False, pid else: return True, pid
def run(self): """ The main program skeleton. """ log_total = True try: try: # Preparation steps self.get_options() # Template method with the tool's main loop self.mainloop() except error.LoggableError, exc: if self.options.debug: raise # Log errors caused by invalid user input try: msg = str(exc) except UnicodeError: msg = unicode(exc, "UTF-8") self.LOG.error(msg) sys.exit(error.EX_SOFTWARE) except KeyboardInterrupt, exc: if self.options.debug: raise sys.stderr.write("\n\nAborted by CTRL-C!\n") sys.stderr.flush() # See https://www.cons.org/cracauer/sigint.html signal.signal(signal.SIGINT, signal.SIG_DFL) os.kill(os.getpid(), signal.SIGINT) sys.exit(error.EX_TEMPFAIL) # being paranoid
except IOError, exc: if exc.errno == errno.ENOENT: # pid file disappeared return False, 0 raise try: pid = int(handle.read().strip(), 10) except (TypeError, ValueError), exc: raise EnvironmentError("Invalid PID file '%s' (%s), won't start!" % (pidfile, exc)) finally: handle.close() # Check process try: os.kill(pid, 0) except EnvironmentError, exc: return False, pid else: return True, pid def guard(pidfile, guardfile=None): """ Raise an EnvironmentError when the "guardfile" doesn't exist, or the process with the ID found in "pidfile" is still active. """ # Check guard if guardfile and not os.path.exists(guardfile): raise EnvironmentError("Guard file '%s' not found, won't start!" % guardfile) if os.path.exists(pidfile):
def daemonize(pidfile=None, logfile=None, sync=True): """ Fork the process into the background. @param pidfile: Optional PID file path. @param sync: Wait for parent process to disappear? @param logfile: Optional name of stdin/stderr log file or stream. """ log = logging.getLogger("daemonize") ppid = os.getpid() try: pid = os.fork() if pid > 0: log.debug("Parent exiting (PID %d, CHILD %d)" % (ppid, pid)) sys.exit(0) except OSError as exc: log.critical("fork #1 failed (PID %d): (%d) %s\n" % (os.getpid(), exc.errno, exc.strerror)) sys.exit(1) ##os.chdir("/") ##os.umask(0022) os.setsid() try: pid = os.fork() if pid > 0: log.debug("Session leader exiting (PID %d, PPID %d, DEMON %d)" % (os.getpid(), ppid, pid)) sys.exit(0) except OSError as exc: log.critical("fork #2 failed (PID %d): (%d) %s\n" % (os.getpid(), exc.errno, exc.strerror)) sys.exit(1) if pidfile: _write_pidfile(pidfile) def sig_term(*dummy): "Handler for SIGTERM." sys.exit(0) stdin = open("/dev/null", "r") os.dup2(stdin.fileno(), sys.stdin.fileno()) signal.signal(signal.SIGTERM, sig_term) if logfile: try: logfile + "" except TypeError: if logfile.fileno() != sys.stdout.fileno(): os.dup2(logfile.fileno(), sys.stdout.fileno()) if logfile.fileno() != sys.stderr.fileno(): os.dup2(logfile.fileno(), sys.stderr.fileno()) else: log.debug("Redirecting stdout / stderr to %r" % logfile) loghandle = open(logfile, "a+") os.dup2(loghandle.fileno(), sys.stdout.fileno()) os.dup2(loghandle.fileno(), sys.stderr.fileno()) loghandle.close() if sync: # Wait for 5 seconds at most, in 10ms steps polling = 5, .01 for _ in range(int(polling[0] * 1 / polling[1])): try: os.kill(ppid, 0) except OSError: break else: time.sleep(polling[1]) log.debug("Process detached (PID %d)" % os.getpid())
def mainloop(self): """ The main loop. """ self._validate_config() config.engine.load_config() # Defaults for process control paths if not self.options.no_fork and not self.options.guard_file: self.options.guard_file = os.path.join(config.config_dir, "run/pyrotorque") if not self.options.pid_file: self.options.pid_file = os.path.join(config.config_dir, "run/pyrotorque.pid") # Process control if self.options.status or self.options.stop or self.options.restart: if self.options.pid_file and os.path.exists(self.options.pid_file): running, pid = osmagic.check_process(self.options.pid_file) else: running, pid = False, 0 if self.options.stop or self.options.restart: if running: os.kill(pid, signal.SIGTERM) # Wait for termination (max. 10 secs) for _ in range(100): running, _ = osmagic.check_process( self.options.pid_file) if not running: break time.sleep(.1) self.LOG.info("Process #%d stopped." % (pid)) elif pid: self.LOG.info("Process #%d NOT running anymore." % (pid)) else: self.LOG.info("No pid file '%s'" % (self.options.pid_file or "<N/A>")) else: self.LOG.info("Process #%d %s running." % (pid, "UP and" if running else "NOT")) if self.options.restart: if self.options.pid_file: running, pid = osmagic.check_process(self.options.pid_file) if running: self.return_code = error.EX_TEMPFAIL return else: self.return_code = error.EX_OK if running else error.EX_UNAVAILABLE return # Check for guard file and running daemon, abort if not OK try: osmagic.guard(self.options.pid_file, self.options.guard_file) except EnvironmentError as exc: self.LOG.debug(str(exc)) self.return_code = error.EX_TEMPFAIL return # Detach, if not disabled via option if not self.options.no_fork: # or getattr(sys.stdin, "isatty", lambda: False)(): osmagic.daemonize(pidfile=self.options.pid_file, logfile=logutil.get_logfile()) time.sleep(.05) # let things settle a little signal.signal(signal.SIGTERM, _raise_interrupt) # Set up services from apscheduler.scheduler import Scheduler self.sched = Scheduler(config.torque) self._init_wsgi_server() # Run services self.sched.start() try: self._add_jobs() # TODO: daemonize here, or before the scheduler starts? self._run_forever() finally: self.sched.shutdown() if self.wsgi_server: self.wsgi_server.task_dispatcher.shutdown() self.wsgi_server = None if self.options.pid_file: try: os.remove(self.options.pid_file) except EnvironmentError as exc: self.LOG.warn("Failed to remove pid file '%s' (%s)" % (self.options.pid_file, exc)) self.return_code = error.EX_IOERR
if exc.errno == errno.ENOENT: # pid file disappeared return False, 0 raise try: pid = int(handle.read().strip(), 10) except (TypeError, ValueError), exc: raise EnvironmentError("Invalid PID file '%s' (%s), won't start!" % (pidfile, exc)) finally: handle.close() # Check process try: os.kill(pid, 0) except EnvironmentError, exc: return False, pid else: return True, pid def guard(pidfile, guardfile=None): """ Raise an EnvironmentError when the "guardfile" doesn't exist, or the process with the ID found in "pidfile" is still active. """ # Check guard if guardfile and not os.path.exists(guardfile): raise EnvironmentError("Guard file '%s' not found, won't start!" % guardfile)