123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- # -*- test-case-name: twisted.application.runner.test.test_runner -*-
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
- """
- Twisted application runner.
- """
- from sys import stderr
- from signal import SIGTERM
- from os import kill
- from attr import attrib, attrs, Factory
- from twisted.logger import (
- globalLogBeginner, textFileLogObserver,
- FilteringLogObserver, LogLevelFilterPredicate,
- LogLevel, Logger,
- )
- from ._exit import exit, ExitStatus
- from ._pidfile import nonePIDFile, AlreadyRunningError, InvalidPIDFileError
- @attrs(frozen=True)
- class Runner(object):
- """
- Twisted application runner.
- @cvar _log: The logger attached to this class.
- @type _log: L{Logger}
- @ivar _reactor: The reactor to start and run the application in.
- @type _reactor: L{IReactorCore}
- @ivar _pidFile: The file to store the running process ID in.
- @type _pidFile: L{IPIDFile}
- @ivar _kill: Whether this runner should kill an existing running
- instance of the application.
- @type _kill: L{bool}
- @ivar _defaultLogLevel: The default log level to start the logging
- system with.
- @type _defaultLogLevel: L{constantly.NamedConstant} from L{LogLevel}
- @ivar _logFile: A file stream to write logging output to.
- @type _logFile: writable file-like object
- @ivar _fileLogObserverFactory: A factory for the file log observer to
- use when starting the logging system.
- @type _pidFile: callable that takes a single writable file-like object
- argument and returns a L{twisted.logger.FileLogObserver}
- @ivar _whenRunning: Hook to call after the reactor is running;
- this is where the application code that relies on the reactor gets
- called.
- @type _whenRunning: callable that takes the keyword arguments specified
- by C{whenRunningArguments}
- @ivar _whenRunningArguments: Keyword arguments to pass to
- C{whenRunning} when it is called.
- @type _whenRunningArguments: L{dict}
- @ivar _reactorExited: Hook to call after the reactor exits.
- @type _reactorExited: callable that takes the keyword arguments
- specified by C{reactorExitedArguments}
- @ivar _reactorExitedArguments: Keyword arguments to pass to
- C{reactorExited} when it is called.
- @type _reactorExitedArguments: L{dict}
- """
- _log = Logger()
- _reactor = attrib()
- _pidFile = attrib(default=nonePIDFile)
- _kill = attrib(default=False)
- _defaultLogLevel = attrib(default=LogLevel.info)
- _logFile = attrib(default=stderr)
- _fileLogObserverFactory = attrib(default=textFileLogObserver)
- _whenRunning = attrib(default=lambda **_: None)
- _whenRunningArguments = attrib(default=Factory(dict))
- _reactorExited = attrib(default=lambda **_: None)
- _reactorExitedArguments = attrib(default=Factory(dict))
- def run(self):
- """
- Run this command.
- """
- pidFile = self._pidFile
- self.killIfRequested()
- try:
- with pidFile:
- self.startLogging()
- self.startReactor()
- self.reactorExited()
- except AlreadyRunningError:
- exit(ExitStatus.EX_CONFIG, "Already running.")
- return # When testing, patched exit doesn't exit
- def killIfRequested(self):
- """
- If C{self._kill} is true, attempt to kill a running instance of the
- application.
- """
- pidFile = self._pidFile
- if self._kill:
- if pidFile is nonePIDFile:
- exit(ExitStatus.EX_USAGE, "No PID file specified.")
- return # When testing, patched exit doesn't exit
- try:
- pid = pidFile.read()
- except EnvironmentError:
- exit(ExitStatus.EX_IOERR, "Unable to read PID file.")
- return # When testing, patched exit doesn't exit
- except InvalidPIDFileError:
- exit(ExitStatus.EX_DATAERR, "Invalid PID file.")
- return # When testing, patched exit doesn't exit
- self.startLogging()
- self._log.info("Terminating process: {pid}", pid=pid)
- kill(pid, SIGTERM)
- exit(ExitStatus.EX_OK)
- return # When testing, patched exit doesn't exit
- def startLogging(self):
- """
- Start the L{twisted.logger} logging system.
- """
- logFile = self._logFile
- fileLogObserverFactory = self._fileLogObserverFactory
- fileLogObserver = fileLogObserverFactory(logFile)
- logLevelPredicate = LogLevelFilterPredicate(
- defaultLogLevel=self._defaultLogLevel
- )
- filteringObserver = FilteringLogObserver(
- fileLogObserver, [logLevelPredicate]
- )
- globalLogBeginner.beginLoggingTo([filteringObserver])
- def startReactor(self):
- """
- Register C{self._whenRunning} with the reactor so that it is called
- once the reactor is running, then start the reactor.
- """
- self._reactor.callWhenRunning(self.whenRunning)
- self._log.info("Starting reactor...")
- self._reactor.run()
- def whenRunning(self):
- """
- Call C{self._whenRunning} with C{self._whenRunningArguments}.
- @note: This method is called after the reactor starts running.
- """
- self._whenRunning(**self._whenRunningArguments)
- def reactorExited(self):
- """
- Call C{self._reactorExited} with C{self._reactorExitedArguments}.
- @note: This method is called after the reactor exits.
- """
- self._reactorExited(**self._reactorExitedArguments)
|