| 1 | # -*- test-case-name: twisted.test.test_application,twisted.test.test_twistd -*- |
|---|
| 2 | # Copyright (c) Twisted Matrix Laboratories. |
|---|
| 3 | # See LICENSE for details. |
|---|
| 4 | |
|---|
| 5 | import sys, os, pdb, getpass, traceback, signal |
|---|
| 6 | from operator import attrgetter |
|---|
| 7 | |
|---|
| 8 | from twisted.python import runtime, log, usage, failure, util, logfile |
|---|
| 9 | from twisted.python.versions import Version |
|---|
| 10 | from twisted.python.reflect import qual, namedAny |
|---|
| 11 | from twisted.python.deprecate import deprecated |
|---|
| 12 | from twisted.python.log import ILogObserver |
|---|
| 13 | from twisted.persisted import sob |
|---|
| 14 | from twisted.application import service, reactors |
|---|
| 15 | from twisted.internet import defer |
|---|
| 16 | from twisted import copyright, plugin |
|---|
| 17 | |
|---|
| 18 | # Expose the new implementation of installReactor at the old location. |
|---|
| 19 | from twisted.application.reactors import installReactor |
|---|
| 20 | from twisted.application.reactors import NoSuchReactor |
|---|
| 21 | |
|---|
| 22 | |
|---|
| 23 | |
|---|
| 24 | class _BasicProfiler(object): |
|---|
| 25 | """ |
|---|
| 26 | @ivar saveStats: if C{True}, save the stats information instead of the |
|---|
| 27 | human readable format |
|---|
| 28 | @type saveStats: C{bool} |
|---|
| 29 | |
|---|
| 30 | @ivar profileOutput: the name of the file use to print profile data. |
|---|
| 31 | @type profileOutput: C{str} |
|---|
| 32 | """ |
|---|
| 33 | |
|---|
| 34 | def __init__(self, profileOutput, saveStats): |
|---|
| 35 | self.profileOutput = profileOutput |
|---|
| 36 | self.saveStats = saveStats |
|---|
| 37 | |
|---|
| 38 | |
|---|
| 39 | def _reportImportError(self, module, e): |
|---|
| 40 | """ |
|---|
| 41 | Helper method to report an import error with a profile module. This |
|---|
| 42 | has to be explicit because some of these modules are removed by |
|---|
| 43 | distributions due to them being non-free. |
|---|
| 44 | """ |
|---|
| 45 | s = "Failed to import module %s: %s" % (module, e) |
|---|
| 46 | s += """ |
|---|
| 47 | This is most likely caused by your operating system not including |
|---|
| 48 | the module due to it being non-free. Either do not use the option |
|---|
| 49 | --profile, or install the module; your operating system vendor |
|---|
| 50 | may provide it in a separate package. |
|---|
| 51 | """ |
|---|
| 52 | raise SystemExit(s) |
|---|
| 53 | |
|---|
| 54 | |
|---|
| 55 | |
|---|
| 56 | class ProfileRunner(_BasicProfiler): |
|---|
| 57 | """ |
|---|
| 58 | Runner for the standard profile module. |
|---|
| 59 | """ |
|---|
| 60 | |
|---|
| 61 | def run(self, reactor): |
|---|
| 62 | """ |
|---|
| 63 | Run reactor under the standard profiler. |
|---|
| 64 | """ |
|---|
| 65 | try: |
|---|
| 66 | import profile |
|---|
| 67 | except ImportError, e: |
|---|
| 68 | self._reportImportError("profile", e) |
|---|
| 69 | |
|---|
| 70 | p = profile.Profile() |
|---|
| 71 | p.runcall(reactor.run) |
|---|
| 72 | if self.saveStats: |
|---|
| 73 | p.dump_stats(self.profileOutput) |
|---|
| 74 | else: |
|---|
| 75 | tmp, sys.stdout = sys.stdout, open(self.profileOutput, 'a') |
|---|
| 76 | try: |
|---|
| 77 | p.print_stats() |
|---|
| 78 | finally: |
|---|
| 79 | sys.stdout, tmp = tmp, sys.stdout |
|---|
| 80 | tmp.close() |
|---|
| 81 | |
|---|
| 82 | |
|---|
| 83 | |
|---|
| 84 | class HotshotRunner(_BasicProfiler): |
|---|
| 85 | """ |
|---|
| 86 | Runner for the hotshot profile module. |
|---|
| 87 | """ |
|---|
| 88 | |
|---|
| 89 | def run(self, reactor): |
|---|
| 90 | """ |
|---|
| 91 | Run reactor under the hotshot profiler. |
|---|
| 92 | """ |
|---|
| 93 | try: |
|---|
| 94 | import hotshot.stats |
|---|
| 95 | except (ImportError, SystemExit), e: |
|---|
| 96 | # Certain versions of Debian (and Debian derivatives) raise |
|---|
| 97 | # SystemExit when importing hotshot if the "non-free" profiler |
|---|
| 98 | # module is not installed. Someone eventually recognized this |
|---|
| 99 | # as a bug and changed the Debian packaged Python to raise |
|---|
| 100 | # ImportError instead. Handle both exception types here in |
|---|
| 101 | # order to support the versions of Debian which have this |
|---|
| 102 | # behavior. The bug report which prompted the introduction of |
|---|
| 103 | # this highly undesirable behavior should be available online at |
|---|
| 104 | # <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=334067>. |
|---|
| 105 | # There seems to be no corresponding bug report which resulted |
|---|
| 106 | # in the behavior being removed. -exarkun |
|---|
| 107 | self._reportImportError("hotshot", e) |
|---|
| 108 | |
|---|
| 109 | # this writes stats straight out |
|---|
| 110 | p = hotshot.Profile(self.profileOutput) |
|---|
| 111 | p.runcall(reactor.run) |
|---|
| 112 | if self.saveStats: |
|---|
| 113 | # stats are automatically written to file, nothing to do |
|---|
| 114 | return |
|---|
| 115 | else: |
|---|
| 116 | s = hotshot.stats.load(self.profileOutput) |
|---|
| 117 | s.strip_dirs() |
|---|
| 118 | s.sort_stats(-1) |
|---|
| 119 | if getattr(s, 'stream', None) is not None: |
|---|
| 120 | # Python 2.5 and above supports a stream attribute |
|---|
| 121 | s.stream = open(self.profileOutput, 'w') |
|---|
| 122 | s.print_stats() |
|---|
| 123 | s.stream.close() |
|---|
| 124 | else: |
|---|
| 125 | # But we have to use a trick for Python < 2.5 |
|---|
| 126 | tmp, sys.stdout = sys.stdout, open(self.profileOutput, 'w') |
|---|
| 127 | try: |
|---|
| 128 | s.print_stats() |
|---|
| 129 | finally: |
|---|
| 130 | sys.stdout, tmp = tmp, sys.stdout |
|---|
| 131 | tmp.close() |
|---|
| 132 | |
|---|
| 133 | |
|---|
| 134 | |
|---|
| 135 | class CProfileRunner(_BasicProfiler): |
|---|
| 136 | """ |
|---|
| 137 | Runner for the cProfile module. |
|---|
| 138 | """ |
|---|
| 139 | |
|---|
| 140 | def run(self, reactor): |
|---|
| 141 | """ |
|---|
| 142 | Run reactor under the cProfile profiler. |
|---|
| 143 | """ |
|---|
| 144 | try: |
|---|
| 145 | import cProfile, pstats |
|---|
| 146 | except ImportError, e: |
|---|
| 147 | self._reportImportError("cProfile", e) |
|---|
| 148 | |
|---|
| 149 | p = cProfile.Profile() |
|---|
| 150 | p.runcall(reactor.run) |
|---|
| 151 | if self.saveStats: |
|---|
| 152 | p.dump_stats(self.profileOutput) |
|---|
| 153 | else: |
|---|
| 154 | stream = open(self.profileOutput, 'w') |
|---|
| 155 | s = pstats.Stats(p, stream=stream) |
|---|
| 156 | s.strip_dirs() |
|---|
| 157 | s.sort_stats(-1) |
|---|
| 158 | s.print_stats() |
|---|
| 159 | stream.close() |
|---|
| 160 | |
|---|
| 161 | |
|---|
| 162 | |
|---|
| 163 | class AppProfiler(object): |
|---|
| 164 | """ |
|---|
| 165 | Class which selects a specific profile runner based on configuration |
|---|
| 166 | options. |
|---|
| 167 | |
|---|
| 168 | @ivar profiler: the name of the selected profiler. |
|---|
| 169 | @type profiler: C{str} |
|---|
| 170 | """ |
|---|
| 171 | profilers = {"profile": ProfileRunner, "hotshot": HotshotRunner, |
|---|
| 172 | "cprofile": CProfileRunner} |
|---|
| 173 | |
|---|
| 174 | def __init__(self, options): |
|---|
| 175 | saveStats = options.get("savestats", False) |
|---|
| 176 | profileOutput = options.get("profile", None) |
|---|
| 177 | self.profiler = options.get("profiler", "hotshot").lower() |
|---|
| 178 | if self.profiler in self.profilers: |
|---|
| 179 | profiler = self.profilers[self.profiler](profileOutput, saveStats) |
|---|
| 180 | self.run = profiler.run |
|---|
| 181 | else: |
|---|
| 182 | raise SystemExit("Unsupported profiler name: %s" % (self.profiler,)) |
|---|
| 183 | |
|---|
| 184 | |
|---|
| 185 | |
|---|
| 186 | class AppLogger(object): |
|---|
| 187 | """ |
|---|
| 188 | Class managing logging faciliy of the application. |
|---|
| 189 | |
|---|
| 190 | @ivar _logfilename: The name of the file to which to log, if other than the |
|---|
| 191 | default. |
|---|
| 192 | @type _logfilename: C{str} |
|---|
| 193 | |
|---|
| 194 | @ivar _observerFactory: Callable object that will create a log observer, or |
|---|
| 195 | None. |
|---|
| 196 | |
|---|
| 197 | @ivar _observer: log observer added at C{start} and removed at C{stop}. |
|---|
| 198 | @type _observer: C{callable} |
|---|
| 199 | """ |
|---|
| 200 | _observer = None |
|---|
| 201 | |
|---|
| 202 | def __init__(self, options): |
|---|
| 203 | self._logfilename = options.get("logfile", "") |
|---|
| 204 | self._observerFactory = options.get("logger") or None |
|---|
| 205 | |
|---|
| 206 | |
|---|
| 207 | def start(self, application): |
|---|
| 208 | """ |
|---|
| 209 | Initialize the logging system. |
|---|
| 210 | |
|---|
| 211 | If a customer logger was specified on the command line it will be |
|---|
| 212 | used. If not, and an L{ILogObserver} component has been set on |
|---|
| 213 | C{application}, then it will be used as the log observer. Otherwise a |
|---|
| 214 | log observer will be created based on the command-line options for |
|---|
| 215 | built-in loggers (e.g. C{--logfile}). |
|---|
| 216 | |
|---|
| 217 | @param application: The application on which to check for an |
|---|
| 218 | L{ILogObserver}. |
|---|
| 219 | """ |
|---|
| 220 | if self._observerFactory is not None: |
|---|
| 221 | observer = self._observerFactory() |
|---|
| 222 | else: |
|---|
| 223 | observer = application.getComponent(ILogObserver, None) |
|---|
| 224 | |
|---|
| 225 | if observer is None: |
|---|
| 226 | observer = self._getLogObserver() |
|---|
| 227 | self._observer = observer |
|---|
| 228 | log.startLoggingWithObserver(self._observer) |
|---|
| 229 | self._initialLog() |
|---|
| 230 | |
|---|
| 231 | |
|---|
| 232 | def _initialLog(self): |
|---|
| 233 | """ |
|---|
| 234 | Print twistd start log message. |
|---|
| 235 | """ |
|---|
| 236 | from twisted.internet import reactor |
|---|
| 237 | log.msg("twistd %s (%s %s) starting up." % (copyright.version, |
|---|
| 238 | sys.executable, |
|---|
| 239 | runtime.shortPythonVersion())) |
|---|
| 240 | log.msg('reactor class: %s.' % (qual(reactor.__class__),)) |
|---|
| 241 | |
|---|
| 242 | |
|---|
| 243 | def _getLogObserver(self): |
|---|
| 244 | """ |
|---|
| 245 | Create a log observer to be added to the logging system before running |
|---|
| 246 | this application. |
|---|
| 247 | """ |
|---|
| 248 | if self._logfilename == '-' or not self._logfilename: |
|---|
| 249 | logFile = sys.stdout |
|---|
| 250 | else: |
|---|
| 251 | logFile = logfile.LogFile.fromFullPath(self._logfilename) |
|---|
| 252 | return log.FileLogObserver(logFile).emit |
|---|
| 253 | |
|---|
| 254 | |
|---|
| 255 | def stop(self): |
|---|
| 256 | """ |
|---|
| 257 | Print twistd stop log message. |
|---|
| 258 | """ |
|---|
| 259 | log.msg("Server Shut Down.") |
|---|
| 260 | if self._observer is not None: |
|---|
| 261 | log.removeObserver(self._observer) |
|---|
| 262 | self._observer = None |
|---|
| 263 | |
|---|
| 264 | |
|---|
| 265 | |
|---|
| 266 | def fixPdb(): |
|---|
| 267 | def do_stop(self, arg): |
|---|
| 268 | self.clear_all_breaks() |
|---|
| 269 | self.set_continue() |
|---|
| 270 | from twisted.internet import reactor |
|---|
| 271 | reactor.callLater(0, reactor.stop) |
|---|
| 272 | return 1 |
|---|
| 273 | |
|---|
| 274 | |
|---|
| 275 | def help_stop(self): |
|---|
| 276 | print """stop - Continue execution, then cleanly shutdown the twisted reactor.""" |
|---|
| 277 | |
|---|
| 278 | |
|---|
| 279 | def set_quit(self): |
|---|
| 280 | os._exit(0) |
|---|
| 281 | |
|---|
| 282 | pdb.Pdb.set_quit = set_quit |
|---|
| 283 | pdb.Pdb.do_stop = do_stop |
|---|
| 284 | pdb.Pdb.help_stop = help_stop |
|---|
| 285 | |
|---|
| 286 | |
|---|
| 287 | |
|---|
| 288 | def runReactorWithLogging(config, oldstdout, oldstderr, profiler=None, reactor=None): |
|---|
| 289 | """ |
|---|
| 290 | Start the reactor, using profiling if specified by the configuration, and |
|---|
| 291 | log any error happening in the process. |
|---|
| 292 | |
|---|
| 293 | @param config: configuration of the twistd application. |
|---|
| 294 | @type config: L{ServerOptions} |
|---|
| 295 | |
|---|
| 296 | @param oldstdout: initial value of C{sys.stdout}. |
|---|
| 297 | @type oldstdout: C{file} |
|---|
| 298 | |
|---|
| 299 | @param oldstderr: initial value of C{sys.stderr}. |
|---|
| 300 | @type oldstderr: C{file} |
|---|
| 301 | |
|---|
| 302 | @param profiler: object used to run the reactor with profiling. |
|---|
| 303 | @type profiler: L{AppProfiler} |
|---|
| 304 | |
|---|
| 305 | @param reactor: The reactor to use. If C{None}, the global reactor will |
|---|
| 306 | be used. |
|---|
| 307 | """ |
|---|
| 308 | if reactor is None: |
|---|
| 309 | from twisted.internet import reactor |
|---|
| 310 | try: |
|---|
| 311 | if config['profile']: |
|---|
| 312 | if profiler is not None: |
|---|
| 313 | profiler.run(reactor) |
|---|
| 314 | elif config['debug']: |
|---|
| 315 | sys.stdout = oldstdout |
|---|
| 316 | sys.stderr = oldstderr |
|---|
| 317 | if runtime.platformType == 'posix': |
|---|
| 318 | signal.signal(signal.SIGUSR2, lambda *args: pdb.set_trace()) |
|---|
| 319 | signal.signal(signal.SIGINT, lambda *args: pdb.set_trace()) |
|---|
| 320 | fixPdb() |
|---|
| 321 | pdb.runcall(reactor.run) |
|---|
| 322 | else: |
|---|
| 323 | reactor.run() |
|---|
| 324 | except: |
|---|
| 325 | if config['nodaemon']: |
|---|
| 326 | file = oldstdout |
|---|
| 327 | else: |
|---|
| 328 | file = open("TWISTD-CRASH.log",'a') |
|---|
| 329 | traceback.print_exc(file=file) |
|---|
| 330 | file.flush() |
|---|
| 331 | |
|---|
| 332 | |
|---|
| 333 | |
|---|
| 334 | def getPassphrase(needed): |
|---|
| 335 | if needed: |
|---|
| 336 | return getpass.getpass('Passphrase: ') |
|---|
| 337 | else: |
|---|
| 338 | return None |
|---|
| 339 | |
|---|
| 340 | |
|---|
| 341 | |
|---|
| 342 | def getSavePassphrase(needed): |
|---|
| 343 | if needed: |
|---|
| 344 | passphrase = util.getPassword("Encryption passphrase: ") |
|---|
| 345 | else: |
|---|
| 346 | return None |
|---|
| 347 | |
|---|
| 348 | |
|---|
| 349 | |
|---|
| 350 | class ApplicationRunner(object): |
|---|
| 351 | """ |
|---|
| 352 | An object which helps running an application based on a config object. |
|---|
| 353 | |
|---|
| 354 | Subclass me and implement preApplication and postApplication |
|---|
| 355 | methods. postApplication generally will want to run the reactor |
|---|
| 356 | after starting the application. |
|---|
| 357 | |
|---|
| 358 | @ivar config: The config object, which provides a dict-like interface. |
|---|
| 359 | |
|---|
| 360 | @ivar application: Available in postApplication, but not |
|---|
| 361 | preApplication. This is the application object. |
|---|
| 362 | |
|---|
| 363 | @ivar profilerFactory: Factory for creating a profiler object, able to |
|---|
| 364 | profile the application if options are set accordingly. |
|---|
| 365 | |
|---|
| 366 | @ivar profiler: Instance provided by C{profilerFactory}. |
|---|
| 367 | |
|---|
| 368 | @ivar loggerFactory: Factory for creating object responsible for logging. |
|---|
| 369 | |
|---|
| 370 | @ivar logger: Instance provided by C{loggerFactory}. |
|---|
| 371 | """ |
|---|
| 372 | profilerFactory = AppProfiler |
|---|
| 373 | loggerFactory = AppLogger |
|---|
| 374 | |
|---|
| 375 | def __init__(self, config): |
|---|
| 376 | self.config = config |
|---|
| 377 | self.profiler = self.profilerFactory(config) |
|---|
| 378 | self.logger = self.loggerFactory(config) |
|---|
| 379 | |
|---|
| 380 | |
|---|
| 381 | def run(self): |
|---|
| 382 | """ |
|---|
| 383 | Run the application. |
|---|
| 384 | """ |
|---|
| 385 | self.preApplication() |
|---|
| 386 | self.application = self.createOrGetApplication() |
|---|
| 387 | |
|---|
| 388 | self.logger.start(self.application) |
|---|
| 389 | |
|---|
| 390 | self.postApplication() |
|---|
| 391 | self.logger.stop() |
|---|
| 392 | |
|---|
| 393 | |
|---|
| 394 | def startReactor(self, reactor, oldstdout, oldstderr): |
|---|
| 395 | """ |
|---|
| 396 | Run the reactor with the given configuration. Subclasses should |
|---|
| 397 | probably call this from C{postApplication}. |
|---|
| 398 | |
|---|
| 399 | @see: L{runReactorWithLogging} |
|---|
| 400 | """ |
|---|
| 401 | runReactorWithLogging( |
|---|
| 402 | self.config, oldstdout, oldstderr, self.profiler, reactor) |
|---|
| 403 | |
|---|
| 404 | |
|---|
| 405 | def preApplication(self): |
|---|
| 406 | """ |
|---|
| 407 | Override in subclass. |
|---|
| 408 | |
|---|
| 409 | This should set up any state necessary before loading and |
|---|
| 410 | running the Application. |
|---|
| 411 | """ |
|---|
| 412 | raise NotImplementedError() |
|---|
| 413 | |
|---|
| 414 | |
|---|
| 415 | def postApplication(self): |
|---|
| 416 | """ |
|---|
| 417 | Override in subclass. |
|---|
| 418 | |
|---|
| 419 | This will be called after the application has been loaded (so |
|---|
| 420 | the C{application} attribute will be set). Generally this |
|---|
| 421 | should start the application and run the reactor. |
|---|
| 422 | """ |
|---|
| 423 | raise NotImplementedError() |
|---|
| 424 | |
|---|
| 425 | |
|---|
| 426 | def createOrGetApplication(self): |
|---|
| 427 | """ |
|---|
| 428 | Create or load an Application based on the parameters found in the |
|---|
| 429 | given L{ServerOptions} instance. |
|---|
| 430 | |
|---|
| 431 | If a subcommand was used, the L{service.IServiceMaker} that it |
|---|
| 432 | represents will be used to construct a service to be added to |
|---|
| 433 | a newly-created Application. |
|---|
| 434 | |
|---|
| 435 | Otherwise, an application will be loaded based on parameters in |
|---|
| 436 | the config. |
|---|
| 437 | """ |
|---|
| 438 | if self.config.subCommand: |
|---|
| 439 | # If a subcommand was given, it's our responsibility to create |
|---|
| 440 | # the application, instead of load it from a file. |
|---|
| 441 | |
|---|
| 442 | # loadedPlugins is set up by the ServerOptions.subCommands |
|---|
| 443 | # property, which is iterated somewhere in the bowels of |
|---|
| 444 | # usage.Options. |
|---|
| 445 | plg = self.config.loadedPlugins[self.config.subCommand] |
|---|
| 446 | ser = plg.makeService(self.config.subOptions) |
|---|
| 447 | application = service.Application(plg.tapname) |
|---|
| 448 | ser.setServiceParent(application) |
|---|
| 449 | else: |
|---|
| 450 | passphrase = getPassphrase(self.config['encrypted']) |
|---|
| 451 | application = getApplication(self.config, passphrase) |
|---|
| 452 | return application |
|---|
| 453 | |
|---|
| 454 | |
|---|
| 455 | |
|---|
| 456 | def getApplication(config, passphrase): |
|---|
| 457 | s = [(config[t], t) |
|---|
| 458 | for t in ['python', 'source', 'file'] if config[t]][0] |
|---|
| 459 | filename, style = s[0], {'file':'pickle'}.get(s[1],s[1]) |
|---|
| 460 | try: |
|---|
| 461 | log.msg("Loading %s..." % filename) |
|---|
| 462 | application = service.loadApplication(filename, style, passphrase) |
|---|
| 463 | log.msg("Loaded.") |
|---|
| 464 | except Exception, e: |
|---|
| 465 | s = "Failed to load application: %s" % e |
|---|
| 466 | if isinstance(e, KeyError) and e.args[0] == "application": |
|---|
| 467 | s += """ |
|---|
| 468 | Could not find 'application' in the file. To use 'twistd -y', your .tac |
|---|
| 469 | file must create a suitable object (e.g., by calling service.Application()) |
|---|
| 470 | and store it in a variable named 'application'. twistd loads your .tac file |
|---|
| 471 | and scans the global variables for one of this name. |
|---|
| 472 | |
|---|
| 473 | Please read the 'Using Application' HOWTO for details. |
|---|
| 474 | """ |
|---|
| 475 | traceback.print_exc(file=log.logfile) |
|---|
| 476 | log.msg(s) |
|---|
| 477 | log.deferr() |
|---|
| 478 | sys.exit('\n' + s + '\n') |
|---|
| 479 | return application |
|---|
| 480 | |
|---|
| 481 | |
|---|
| 482 | |
|---|
| 483 | def _reactorAction(): |
|---|
| 484 | return usage.CompleteList([r.shortName for r in reactors.getReactorTypes()]) |
|---|
| 485 | |
|---|
| 486 | |
|---|
| 487 | class ReactorSelectionMixin: |
|---|
| 488 | """ |
|---|
| 489 | Provides options for selecting a reactor to install. |
|---|
| 490 | |
|---|
| 491 | If a reactor is installed, the short name which was used to locate it is |
|---|
| 492 | saved as the value for the C{"reactor"} key. |
|---|
| 493 | """ |
|---|
| 494 | compData = usage.Completions( |
|---|
| 495 | optActions={"reactor": _reactorAction}) |
|---|
| 496 | |
|---|
| 497 | messageOutput = sys.stdout |
|---|
| 498 | _getReactorTypes = staticmethod(reactors.getReactorTypes) |
|---|
| 499 | |
|---|
| 500 | |
|---|
| 501 | def opt_help_reactors(self): |
|---|
| 502 | """ |
|---|
| 503 | Display a list of possibly available reactor names. |
|---|
| 504 | """ |
|---|
| 505 | rcts = sorted(self._getReactorTypes(), key=attrgetter('shortName')) |
|---|
| 506 | for r in rcts: |
|---|
| 507 | self.messageOutput.write(' %-4s\t%s\n' % |
|---|
| 508 | (r.shortName, r.description)) |
|---|
| 509 | raise SystemExit(0) |
|---|
| 510 | |
|---|
| 511 | |
|---|
| 512 | def opt_reactor(self, shortName): |
|---|
| 513 | """ |
|---|
| 514 | Which reactor to use (see --help-reactors for a list of possibilities) |
|---|
| 515 | """ |
|---|
| 516 | # Actually actually actually install the reactor right at this very |
|---|
| 517 | # moment, before any other code (for example, a sub-command plugin) |
|---|
| 518 | # runs and accidentally imports and installs the default reactor. |
|---|
| 519 | # |
|---|
| 520 | # This could probably be improved somehow. |
|---|
| 521 | try: |
|---|
| 522 | installReactor(shortName) |
|---|
| 523 | except NoSuchReactor: |
|---|
| 524 | msg = ("The specified reactor does not exist: '%s'.\n" |
|---|
| 525 | "See the list of available reactors with " |
|---|
| 526 | "--help-reactors" % (shortName,)) |
|---|
| 527 | raise usage.UsageError(msg) |
|---|
| 528 | except Exception, e: |
|---|
| 529 | msg = ("The specified reactor cannot be used, failed with error: " |
|---|
| 530 | "%s.\nSee the list of available reactors with " |
|---|
| 531 | "--help-reactors" % (e,)) |
|---|
| 532 | raise usage.UsageError(msg) |
|---|
| 533 | else: |
|---|
| 534 | self["reactor"] = shortName |
|---|
| 535 | opt_r = opt_reactor |
|---|
| 536 | |
|---|
| 537 | |
|---|
| 538 | |
|---|
| 539 | |
|---|
| 540 | class ServerOptions(usage.Options, ReactorSelectionMixin): |
|---|
| 541 | |
|---|
| 542 | longdesc = ("twistd reads a twisted.application.service.Application out " |
|---|
| 543 | "of a file and runs it.") |
|---|
| 544 | |
|---|
| 545 | optFlags = [['savestats', None, |
|---|
| 546 | "save the Stats object rather than the text output of " |
|---|
| 547 | "the profiler."], |
|---|
| 548 | ['no_save','o', "do not save state on shutdown"], |
|---|
| 549 | ['encrypted', 'e', |
|---|
| 550 | "The specified tap/aos file is encrypted."]] |
|---|
| 551 | |
|---|
| 552 | optParameters = [['logfile','l', None, |
|---|
| 553 | "log to a specified file, - for stdout"], |
|---|
| 554 | ['logger', None, None, |
|---|
| 555 | "A fully-qualified name to a log observer factory to use " |
|---|
| 556 | "for the initial log observer. Takes precedence over " |
|---|
| 557 | "--logfile and --syslog (when available)."], |
|---|
| 558 | ['profile', 'p', None, |
|---|
| 559 | "Run in profile mode, dumping results to specified file"], |
|---|
| 560 | ['profiler', None, "hotshot", |
|---|
| 561 | "Name of the profiler to use (%s)." % |
|---|
| 562 | ", ".join(AppProfiler.profilers)], |
|---|
| 563 | ['file','f','twistd.tap', |
|---|
| 564 | "read the given .tap file"], |
|---|
| 565 | ['python','y', None, |
|---|
| 566 | "read an application from within a Python file " |
|---|
| 567 | "(implies -o)"], |
|---|
| 568 | ['source', 's', None, |
|---|
| 569 | "Read an application from a .tas file (AOT format)."], |
|---|
| 570 | ['rundir','d','.', |
|---|
| 571 | 'Change to a supplied directory before running']] |
|---|
| 572 | |
|---|
| 573 | compData = usage.Completions( |
|---|
| 574 | mutuallyExclusive=[("file", "python", "source")], |
|---|
| 575 | optActions={"file": usage.CompleteFiles("*.tap"), |
|---|
| 576 | "python": usage.CompleteFiles("*.(tac|py)"), |
|---|
| 577 | "source": usage.CompleteFiles("*.tas"), |
|---|
| 578 | "rundir": usage.CompleteDirs()} |
|---|
| 579 | ) |
|---|
| 580 | |
|---|
| 581 | _getPlugins = staticmethod(plugin.getPlugins) |
|---|
| 582 | |
|---|
| 583 | def __init__(self, *a, **kw): |
|---|
| 584 | self['debug'] = False |
|---|
| 585 | usage.Options.__init__(self, *a, **kw) |
|---|
| 586 | |
|---|
| 587 | |
|---|
| 588 | def opt_debug(self): |
|---|
| 589 | """ |
|---|
| 590 | Run the application in the Python Debugger (implies nodaemon), |
|---|
| 591 | sending SIGUSR2 will drop into debugger |
|---|
| 592 | """ |
|---|
| 593 | defer.setDebugging(True) |
|---|
| 594 | failure.startDebugMode() |
|---|
| 595 | self['debug'] = True |
|---|
| 596 | opt_b = opt_debug |
|---|
| 597 | |
|---|
| 598 | |
|---|
| 599 | def opt_spew(self): |
|---|
| 600 | """ |
|---|
| 601 | Print an insanely verbose log of everything that happens. |
|---|
| 602 | Useful when debugging freezes or locks in complex code.""" |
|---|
| 603 | sys.settrace(util.spewer) |
|---|
| 604 | try: |
|---|
| 605 | import threading |
|---|
| 606 | except ImportError: |
|---|
| 607 | return |
|---|
| 608 | threading.settrace(util.spewer) |
|---|
| 609 | |
|---|
| 610 | |
|---|
| 611 | def parseOptions(self, options=None): |
|---|
| 612 | if options is None: |
|---|
| 613 | options = sys.argv[1:] or ["--help"] |
|---|
| 614 | usage.Options.parseOptions(self, options) |
|---|
| 615 | |
|---|
| 616 | |
|---|
| 617 | def postOptions(self): |
|---|
| 618 | if self.subCommand or self['python']: |
|---|
| 619 | self['no_save'] = True |
|---|
| 620 | if self['logger'] is not None: |
|---|
| 621 | try: |
|---|
| 622 | self['logger'] = namedAny(self['logger']) |
|---|
| 623 | except Exception, e: |
|---|
| 624 | raise usage.UsageError("Logger '%s' could not be imported: %s" |
|---|
| 625 | % (self['logger'], e)) |
|---|
| 626 | |
|---|
| 627 | |
|---|
| 628 | def subCommands(self): |
|---|
| 629 | plugins = self._getPlugins(service.IServiceMaker) |
|---|
| 630 | self.loadedPlugins = {} |
|---|
| 631 | for plug in sorted(plugins, key=attrgetter('tapname')): |
|---|
| 632 | self.loadedPlugins[plug.tapname] = plug |
|---|
| 633 | yield (plug.tapname, |
|---|
| 634 | None, |
|---|
| 635 | # Avoid resolving the options attribute right away, in case |
|---|
| 636 | # it's a property with a non-trivial getter (eg, one which |
|---|
| 637 | # imports modules). |
|---|
| 638 | lambda plug=plug: plug.options(), |
|---|
| 639 | plug.description) |
|---|
| 640 | subCommands = property(subCommands) |
|---|
| 641 | |
|---|
| 642 | |
|---|
| 643 | |
|---|
| 644 | def run(runApp, ServerOptions): |
|---|
| 645 | config = ServerOptions() |
|---|
| 646 | try: |
|---|
| 647 | config.parseOptions() |
|---|
| 648 | except usage.error, ue: |
|---|
| 649 | print config |
|---|
| 650 | print "%s: %s" % (sys.argv[0], ue) |
|---|
| 651 | else: |
|---|
| 652 | runApp(config) |
|---|
| 653 | |
|---|
| 654 | |
|---|
| 655 | |
|---|
| 656 | def convertStyle(filein, typein, passphrase, fileout, typeout, encrypt): |
|---|
| 657 | application = service.loadApplication(filein, typein, passphrase) |
|---|
| 658 | sob.IPersistable(application).setStyle(typeout) |
|---|
| 659 | passphrase = getSavePassphrase(encrypt) |
|---|
| 660 | if passphrase: |
|---|
| 661 | fileout = None |
|---|
| 662 | sob.IPersistable(application).save(filename=fileout, passphrase=passphrase) |
|---|
| 663 | |
|---|
| 664 | |
|---|
| 665 | |
|---|
| 666 | def startApplication(application, save): |
|---|
| 667 | from twisted.internet import reactor |
|---|
| 668 | service.IService(application).startService() |
|---|
| 669 | if save: |
|---|
| 670 | p = sob.IPersistable(application) |
|---|
| 671 | reactor.addSystemEventTrigger('after', 'shutdown', p.save, 'shutdown') |
|---|
| 672 | reactor.addSystemEventTrigger('before', 'shutdown', |
|---|
| 673 | service.IService(application).stopService) |
|---|