_runner.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. # -*- test-case-name: twisted.application.runner.test.test_runner -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. Twisted application runner.
  6. """
  7. from sys import stderr
  8. from signal import SIGTERM
  9. from os import kill
  10. from twisted.logger import (
  11. globalLogBeginner, textFileLogObserver,
  12. FilteringLogObserver, LogLevelFilterPredicate,
  13. LogLevel, Logger,
  14. )
  15. from twisted.internet import default as defaultReactor
  16. from ._exit import exit, ExitStatus
  17. from ._pidfile import nonePIDFile, AlreadyRunningError, InvalidPIDFileError
  18. class Runner(object):
  19. """
  20. Twisted application runner.
  21. """
  22. log = Logger()
  23. def __init__(
  24. self,
  25. reactor=None,
  26. pidFile=nonePIDFile, kill=False,
  27. defaultLogLevel=LogLevel.info,
  28. logFile=stderr, fileLogObserverFactory=textFileLogObserver,
  29. whenRunning=lambda **_: None, whenRunningArguments={},
  30. reactorExited=lambda **_: None, reactorExitedArguments={},
  31. ):
  32. """
  33. @param reactor: The reactor to start and run the application in.
  34. @type reactor: L{IReactorCore}
  35. @param pidFile: The file to store the running process ID in.
  36. @type pidFile: L{IPIDFile}
  37. @param kill: Whether this runner should kill an existing running
  38. instance of the application.
  39. @type kill: L{bool}
  40. @param defaultLogLevel: The default log level to start the logging
  41. system with.
  42. @type defaultLogLevel: L{constantly.NamedConstant} from L{LogLevel}
  43. @param logFile: A file stream to write logging output to.
  44. @type logFile: writable file-like object
  45. @param fileLogObserverFactory: A factory for the file log observer to
  46. use when starting the logging system.
  47. @type pidFile: callable that takes a single writable file-like object
  48. argument and returns a L{twisted.logger.FileLogObserver}
  49. @param whenRunning: Hook to call after the reactor is running;
  50. this is where the application code that relies on the reactor gets
  51. called.
  52. @type whenRunning: callable that takes the keyword arguments specified
  53. by C{whenRunningArguments}
  54. @param whenRunningArguments: Keyword arguments to pass to
  55. C{whenRunning} when it is called.
  56. @type whenRunningArguments: L{dict}
  57. @param reactorExited: Hook to call after the reactor exits.
  58. @type reactorExited: callable that takes the keyword arguments
  59. specified by C{reactorExitedArguments}
  60. @param reactorExitedArguments: Keyword arguments to pass to
  61. C{reactorExited} when it is called.
  62. @type reactorExitedArguments: L{dict}
  63. """
  64. self.reactor = reactor
  65. self.pidFile = pidFile
  66. self.kill = kill
  67. self.defaultLogLevel = defaultLogLevel
  68. self.logFile = logFile
  69. self.fileLogObserverFactory = fileLogObserverFactory
  70. self.whenRunningHook = whenRunning
  71. self.whenRunningArguments = whenRunningArguments
  72. self.reactorExitedHook = reactorExited
  73. self.reactorExitedArguments = reactorExitedArguments
  74. def run(self):
  75. """
  76. Run this command.
  77. """
  78. pidFile = self.pidFile
  79. self.killIfRequested()
  80. try:
  81. with pidFile:
  82. self.startLogging()
  83. self.startReactor()
  84. self.reactorExited()
  85. except AlreadyRunningError:
  86. exit(ExitStatus.EX_CONFIG, "Already running.")
  87. return # When testing, patched exit doesn't exit
  88. def killIfRequested(self):
  89. """
  90. If C{self.kill} is true, attempt to kill a running instance of the
  91. application.
  92. """
  93. pidFile = self.pidFile
  94. if self.kill:
  95. if pidFile is nonePIDFile:
  96. exit(ExitStatus.EX_USAGE, "No PID file specified.")
  97. return # When testing, patched exit doesn't exit
  98. try:
  99. pid = pidFile.read()
  100. except EnvironmentError:
  101. exit(ExitStatus.EX_IOERR, "Unable to read PID file.")
  102. return # When testing, patched exit doesn't exit
  103. except InvalidPIDFileError:
  104. exit(ExitStatus.EX_DATAERR, "Invalid PID file.")
  105. return # When testing, patched exit doesn't exit
  106. self.startLogging()
  107. self.log.info("Terminating process: {pid}", pid=pid)
  108. kill(pid, SIGTERM)
  109. exit(ExitStatus.EX_OK)
  110. return # When testing, patched exit doesn't exit
  111. def startLogging(self):
  112. """
  113. Start the L{twisted.logger} logging system.
  114. """
  115. logFile = self.logFile
  116. fileLogObserverFactory = self.fileLogObserverFactory
  117. fileLogObserver = fileLogObserverFactory(logFile)
  118. logLevelPredicate = LogLevelFilterPredicate(
  119. defaultLogLevel=self.defaultLogLevel
  120. )
  121. filteringObserver = FilteringLogObserver(
  122. fileLogObserver, [logLevelPredicate]
  123. )
  124. globalLogBeginner.beginLoggingTo([filteringObserver])
  125. def startReactor(self):
  126. """
  127. If C{self.reactor} is L{None}, install the default reactor and set
  128. C{self.reactor} to the default reactor.
  129. Register C{self.whenRunning} with the reactor so that it is called once
  130. the reactor is running, then start the reactor.
  131. """
  132. if self.reactor is None:
  133. defaultReactor.install()
  134. from twisted.internet import reactor
  135. self.reactor = reactor
  136. else:
  137. reactor = self.reactor
  138. reactor.callWhenRunning(self.whenRunning)
  139. self.log.info("Starting reactor...")
  140. reactor.run()
  141. def whenRunning(self):
  142. """
  143. Call C{self.whenRunning}.
  144. @note: This method is called after the reactor starts running.
  145. """
  146. self.whenRunningHook(**self.whenRunningArguments)
  147. def reactorExited(self):
  148. """
  149. Call C{self.reactorExited}.
  150. @note: This method is called after the reactor exits.
  151. """
  152. self.reactorExitedHook(**self.reactorExitedArguments)