1
# -*- test-case-name: twisted.test.test_application,twisted.test.test_twistd -*-
2
# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
3
# See LICENSE for details.
5
import sys, os, pdb, getpass, traceback, signal, warnings
7
from twisted.python import runtime, log, usage, failure, util, logfile
8
from twisted.python.versions import Version
9
from twisted.python.reflect import qual
10
from twisted.python.deprecate import deprecated
11
from twisted.python.log import ILogObserver
12
from twisted.persisted import sob
13
from twisted.application import service, reactors
14
from twisted.internet import defer
15
from twisted import copyright
17
# Expose the new implementation of installReactor at the old location.
18
from twisted.application.reactors import installReactor
19
from twisted.application.reactors import NoSuchReactor
23
class _BasicProfiler(object):
25
@ivar saveStats: if C{True}, save the stats information instead of the
27
@type saveStats: C{bool}
29
@ivar profileOutput: the name of the file use to print profile data.
30
@type profileOutput: C{str}
33
def __init__(self, profileOutput, saveStats):
34
self.profileOutput = profileOutput
35
self.saveStats = saveStats
38
def _reportImportError(self, module, e):
40
Helper method to report an import error with a profile module. This
41
has to be explicit because some of these modules are removed by
42
distributions due to them being non-free.
44
s = "Failed to import module %s: %s" % (module, e)
46
This is most likely caused by your operating system not including
47
the module due to it being non-free. Either do not use the option
48
--profile, or install the module; your operating system vendor
49
may provide it in a separate package.
55
class ProfileRunner(_BasicProfiler):
57
Runner for the standard profile module.
60
def run(self, reactor):
62
Run reactor under the standard profiler.
66
except ImportError, e:
67
self._reportImportError("profile", e)
70
p.runcall(reactor.run)
72
p.dump_stats(self.profileOutput)
74
tmp, sys.stdout = sys.stdout, open(self.profileOutput, 'a')
78
sys.stdout, tmp = tmp, sys.stdout
83
class HotshotRunner(_BasicProfiler):
85
Runner for the hotshot profile module.
88
def run(self, reactor):
90
Run reactor under the hotshot profiler.
94
except (ImportError, SystemExit), e:
95
# Certain versions of Debian (and Debian derivatives) raise
96
# SystemExit when importing hotshot if the "non-free" profiler
97
# module is not installed. Someone eventually recognized this
98
# as a bug and changed the Debian packaged Python to raise
99
# ImportError instead. Handle both exception types here in
100
# order to support the versions of Debian which have this
101
# behavior. The bug report which prompted the introduction of
102
# this highly undesirable behavior should be available online at
103
# <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=334067>.
104
# There seems to be no corresponding bug report which resulted
105
# in the behavior being removed. -exarkun
106
self._reportImportError("hotshot", e)
108
# this writes stats straight out
109
p = hotshot.Profile(self.profileOutput)
110
p.runcall(reactor.run)
112
# stats are automatically written to file, nothing to do
115
s = hotshot.stats.load(self.profileOutput)
118
if getattr(s, 'stream', None) is not None:
119
# Python 2.5 and above supports a stream attribute
120
s.stream = open(self.profileOutput, 'w')
124
# But we have to use a trick for Python < 2.5
125
tmp, sys.stdout = sys.stdout, open(self.profileOutput, 'w')
129
sys.stdout, tmp = tmp, sys.stdout
134
class CProfileRunner(_BasicProfiler):
136
Runner for the cProfile module.
139
def run(self, reactor):
141
Run reactor under the cProfile profiler.
144
import cProfile, pstats
145
except ImportError, e:
146
self._reportImportError("cProfile", e)
148
p = cProfile.Profile()
149
p.runcall(reactor.run)
151
p.dump_stats(self.profileOutput)
153
stream = open(self.profileOutput, 'w')
154
s = pstats.Stats(p, stream=stream)
162
class AppProfiler(object):
164
Class which selects a specific profile runner based on configuration
167
@ivar profiler: the name of the selected profiler.
168
@type profiler: C{str}
170
profilers = {"profile": ProfileRunner, "hotshot": HotshotRunner,
171
"cprofile": CProfileRunner}
173
def __init__(self, options):
174
saveStats = options.get("savestats", False)
175
profileOutput = options.get("profile", None)
176
self.profiler = options.get("profiler", "hotshot").lower()
177
if options.get("nothotshot", False):
178
warnings.warn("The --nothotshot option is deprecated. Please "
179
"specify the profiler name using the --profiler "
180
"option", category=DeprecationWarning)
181
self.profiler = "profile"
182
if self.profiler in self.profilers:
183
profiler = self.profilers[self.profiler](profileOutput, saveStats)
184
self.run = profiler.run
186
raise SystemExit("Unsupported profiler name: %s" % (self.profiler,))
190
def runWithProfiler(reactor, config):
192
DEPRECATED in Twisted 8.0.
194
Run reactor under standard profiler.
196
warnings.warn("runWithProfiler is deprecated since Twisted 8.0. "
197
"Use ProfileRunner instead.", DeprecationWarning, 2)
198
item = AppProfiler(config)
199
return item.run(reactor)
203
def runWithHotshot(reactor, config):
205
DEPRECATED in Twisted 8.0.
207
Run reactor under hotshot profiler.
209
warnings.warn("runWithHotshot is deprecated since Twisted 8.0. "
210
"Use HotshotRunner instead.", DeprecationWarning, 2)
211
item = AppProfiler(config)
212
return item.run(reactor)
216
class AppLogger(object):
218
Class managing logging faciliy of the application.
220
@ivar _logfilename: The name of the file to which to log, if other than the
222
@type _logfilename: C{str}
224
@ivar _observer: log observer added at C{start} and removed at C{stop}.
225
@type _observer: C{callable}
229
def __init__(self, options):
230
self._logfilename = options.get("logfile", "")
233
def start(self, application):
235
Initialize the logging system.
237
If an L{ILogObserver} component has been set on C{application}, then
238
it will be used as the log observer. Otherwise a log observer will be
239
created based on the command-line options.
241
@param application: The application on which to check for an
244
observer = application.getComponent(ILogObserver, None)
247
observer = self._getLogObserver()
248
self._observer = observer
249
log.startLoggingWithObserver(self._observer)
253
def _initialLog(self):
255
Print twistd start log message.
257
from twisted.internet import reactor
258
log.msg("twistd %s (%s %s) starting up." % (copyright.version,
260
runtime.shortPythonVersion()))
261
log.msg('reactor class: %s.' % (qual(reactor.__class__),))
264
def _getLogObserver(self):
266
Create a log observer to be added to the logging system before running
269
if self._logfilename == '-' or not self._logfilename:
272
logFile = logfile.LogFile.fromFullPath(self._logfilename)
273
return log.FileLogObserver(logFile).emit
278
Print twistd stop log message.
280
log.msg("Server Shut Down.")
281
if self._observer is not None:
282
log.removeObserver(self._observer)
283
self._observer = None
288
def do_stop(self, arg):
289
self.clear_all_breaks()
291
from twisted.internet import reactor
292
reactor.callLater(0, reactor.stop)
296
print """stop - Continue execution, then cleanly shutdown the twisted reactor."""
301
pdb.Pdb.set_quit = set_quit
302
pdb.Pdb.do_stop = do_stop
303
pdb.Pdb.help_stop = help_stop
307
def runReactorWithLogging(config, oldstdout, oldstderr, profiler=None, reactor=None):
309
Start the reactor, using profiling if specified by the configuration, and
310
log any error happening in the process.
312
@param config: configuration of the twistd application.
313
@type config: L{ServerOptions}
315
@param oldstdout: initial value of C{sys.stdout}.
316
@type oldstdout: C{file}
318
@param oldstderr: initial value of C{sys.stderr}.
319
@type oldstderr: C{file}
321
@param profiler: object used to run the reactor with profiling.
322
@type profiler: L{AppProfiler}
324
@param reactor: The reactor to use. If C{None}, the global reactor will
328
from twisted.internet import reactor
330
if config['profile']:
331
if profiler is not None:
332
profiler.run(reactor)
334
# Backward compatible code
335
if not config['nothotshot']:
336
runWithHotshot(reactor, config)
338
runWithProfiler(reactor, config)
339
elif config['debug']:
340
sys.stdout = oldstdout
341
sys.stderr = oldstderr
342
if runtime.platformType == 'posix':
343
signal.signal(signal.SIGUSR2, lambda *args: pdb.set_trace())
344
signal.signal(signal.SIGINT, lambda *args: pdb.set_trace())
346
pdb.runcall(reactor.run)
350
if config['nodaemon']:
353
file = open("TWISTD-CRASH.log",'a')
354
traceback.print_exc(file=file)
359
def getPassphrase(needed):
361
return getpass.getpass('Passphrase: ')
367
def getSavePassphrase(needed):
369
passphrase = util.getPassword("Encryption passphrase: ")
375
class ApplicationRunner(object):
377
An object which helps running an application based on a config object.
379
Subclass me and implement preApplication and postApplication
380
methods. postApplication generally will want to run the reactor
381
after starting the application.
383
@ivar config: The config object, which provides a dict-like interface.
385
@ivar application: Available in postApplication, but not
386
preApplication. This is the application object.
388
@ivar profilerFactory: Factory for creating a profiler object, able to
389
profile the application if options are set accordingly.
391
@ivar profiler: Instance provided by C{profilerFactory}.
393
@ivar loggerFactory: Factory for creating object responsible for logging.
395
@ivar logger: Instance provided by C{loggerFactory}.
397
profilerFactory = AppProfiler
398
loggerFactory = AppLogger
400
def __init__(self, config):
402
self.profiler = self.profilerFactory(config)
403
self.logger = self.loggerFactory(config)
410
self.preApplication()
411
self.application = self.createOrGetApplication()
414
getLogObserverLegacy = getattr(self, 'getLogObserver', None)
415
if getLogObserverLegacy is not None:
416
warnings.warn("Specifying a log observer with getLogObserver is "
417
"deprecated. Please use a loggerFactory instead.",
418
category=DeprecationWarning)
419
self.startLogging(self.getLogObserver())
421
self.logger.start(self.application)
423
self.postApplication()
427
def startLogging(self, observer):
429
Initialize the logging system. DEPRECATED.
431
@param observer: The observer to add to the logging system.
433
log.startLoggingWithObserver(observer)
434
self.logger._initialLog()
437
def startReactor(self, reactor, oldstdout, oldstderr):
439
Run the reactor with the given configuration. Subclasses should
440
probably call this from C{postApplication}.
442
@see: L{runReactorWithLogging}
444
runReactorWithLogging(
445
self.config, oldstdout, oldstderr, self.profiler, reactor)
448
def preApplication(self):
450
Override in subclass.
452
This should set up any state necessary before loading and
453
running the Application.
455
raise NotImplementedError()
458
def postApplication(self):
460
Override in subclass.
462
This will be called after the application has been loaded (so
463
the C{application} attribute will be set). Generally this
464
should start the application and run the reactor.
466
raise NotImplementedError()
469
def createOrGetApplication(self):
471
Create or load an Application based on the parameters found in the
472
given L{ServerOptions} instance.
474
If a subcommand was used, the L{service.IServiceMaker} that it
475
represents will be used to construct a service to be added to
476
a newly-created Application.
478
Otherwise, an application will be loaded based on parameters in
481
if self.config.subCommand:
482
# If a subcommand was given, it's our responsibility to create
483
# the application, instead of load it from a file.
485
# loadedPlugins is set up by the ServerOptions.subCommands
486
# property, which is iterated somewhere in the bowels of
488
plg = self.config.loadedPlugins[self.config.subCommand]
489
ser = plg.makeService(self.config.subOptions)
490
application = service.Application(plg.tapname)
491
ser.setServiceParent(application)
493
passphrase = getPassphrase(self.config['encrypted'])
494
application = getApplication(self.config, passphrase)
499
def getApplication(config, passphrase):
501
for t in ['python', 'source', 'file'] if config[t]][0]
502
filename, style = s[0], {'file':'pickle'}.get(s[1],s[1])
504
log.msg("Loading %s..." % filename)
505
application = service.loadApplication(filename, style, passphrase)
508
s = "Failed to load application: %s" % e
509
if isinstance(e, KeyError) and e.args[0] == "application":
511
Could not find 'application' in the file. To use 'twistd -y', your .tac
512
file must create a suitable object (e.g., by calling service.Application())
513
and store it in a variable named 'application'. twistd loads your .tac file
514
and scans the global variables for one of this name.
516
Please read the 'Using Application' HOWTO for details.
518
traceback.print_exc(file=log.logfile)
521
sys.exit('\n' + s + '\n')
526
def reportProfile(report_profile, name):
528
DEPRECATED since Twisted 8.0. This does nothing.
530
warnings.warn("reportProfile is deprecated and a no-op since Twisted 8.0.",
531
category=DeprecationWarning)
535
def _reactorZshAction():
536
return "(%s)" % " ".join([r.shortName for r in reactors.getReactorTypes()])
538
class ReactorSelectionMixin:
540
Provides options for selecting a reactor to install.
542
zsh_actions = {"reactor" : _reactorZshAction}
543
messageOutput = sys.stdout
546
def opt_help_reactors(self):
548
Display a list of possibly available reactor names.
550
for r in reactors.getReactorTypes():
551
self.messageOutput.write(' %-4s\t%s\n' %
552
(r.shortName, r.description))
556
def opt_reactor(self, shortName):
558
Which reactor to use (see --help-reactors for a list of possibilities)
560
# Actually actually actually install the reactor right at this very
561
# moment, before any other code (for example, a sub-command plugin)
562
# runs and accidentally imports and installs the default reactor.
564
# This could probably be improved somehow.
566
installReactor(shortName)
567
except NoSuchReactor:
568
msg = ("The specified reactor does not exist: '%s'.\n"
569
"See the list of available reactors with "
570
"--help-reactors" % (shortName,))
571
raise usage.UsageError(msg)
573
msg = ("The specified reactor cannot be used, failed with error: "
574
"%s.\nSee the list of available reactors with "
575
"--help-reactors" % (e,))
576
raise usage.UsageError(msg)
582
class ServerOptions(usage.Options, ReactorSelectionMixin):
584
longdesc = ("twistd reads a twisted.application.service.Application out "
585
"of a file and runs it.")
587
optFlags = [['savestats', None,
588
"save the Stats object rather than the text output of "
590
['no_save','o', "do not save state on shutdown"],
592
"The specified tap/aos file is encrypted."],
594
"DEPRECATED. Don't use the hotshot profiler even if "
597
optParameters = [['logfile','l', None,
598
"log to a specified file, - for stdout"],
599
['profile', 'p', None,
600
"Run in profile mode, dumping results to specified file"],
601
['profiler', None, "hotshot",
602
"Name of the profiler to use (%s)." %
603
", ".join(AppProfiler.profilers)],
604
['file','f','twistd.tap',
605
"read the given .tap file"],
607
"read an application from within a Python file "
609
['source', 's', None,
610
"Read an application from a .tas file (AOT format)."],
612
'Change to a supplied directory before running'],
613
['report-profile', None, None,
614
'E-mail address to use when reporting dynamic execution '
615
'profiler stats. This should not be combined with '
616
'other profiling options. This will only take effect '
617
'if the application to be run has an application '
620
#zsh_altArgDescr = {"foo":"use this description for foo instead"}
621
#zsh_multiUse = ["foo", "bar"]
622
zsh_mutuallyExclusive = [("file", "python", "source")]
623
zsh_actions = {"file":'_files -g "*.tap"',
624
"python":'_files -g "*.(tac|py)"',
625
"source":'_files -g "*.tas"',
627
#zsh_actionDescr = {"logfile":"log file name", "random":"random seed"}
629
def __init__(self, *a, **kw):
630
self['debug'] = False
631
usage.Options.__init__(self, *a, **kw)
635
run the application in the Python Debugger (implies nodaemon),
636
sending SIGUSR2 will drop into debugger
638
defer.setDebugging(True)
639
failure.startDebugMode()
645
"""Print an insanely verbose log of everything that happens.
646
Useful when debugging freezes or locks in complex code."""
647
sys.settrace(util.spewer)
652
threading.settrace(util.spewer)
655
def opt_report_profile(self, value):
659
Manage --report-profile option, which does nothing currently.
661
warnings.warn("--report-profile option is deprecated and a no-op "
662
"since Twisted 8.0.", category=DeprecationWarning)
665
def parseOptions(self, options=None):
667
options = sys.argv[1:] or ["--help"]
668
usage.Options.parseOptions(self, options)
670
def postOptions(self):
671
if self.subCommand or self['python']:
672
self['no_save'] = True
674
def subCommands(self):
675
from twisted import plugin
676
plugins = plugin.getPlugins(service.IServiceMaker)
677
self.loadedPlugins = {}
679
self.loadedPlugins[plug.tapname] = plug
680
yield (plug.tapname, None, lambda: plug.options(), plug.description)
681
subCommands = property(subCommands)
685
def run(runApp, ServerOptions):
686
config = ServerOptions()
688
config.parseOptions()
689
except usage.error, ue:
691
print "%s: %s" % (sys.argv[0], ue)
698
AppLogger({})._initialLog()
699
initialLog = deprecated(Version("Twisted", 8, 2, 0))(initialLog)
703
def convertStyle(filein, typein, passphrase, fileout, typeout, encrypt):
704
application = service.loadApplication(filein, typein, passphrase)
705
sob.IPersistable(application).setStyle(typeout)
706
passphrase = getSavePassphrase(encrypt)
709
sob.IPersistable(application).save(filename=fileout, passphrase=passphrase)
711
def startApplication(application, save):
712
from twisted.internet import reactor
713
service.IService(application).startService()
715
p = sob.IPersistable(application)
716
reactor.addSystemEventTrigger('after', 'shutdown', p.save, 'shutdown')
717
reactor.addSystemEventTrigger('before', 'shutdown',
718
service.IService(application).stopService)
720
def getLogFile(logfilename):
722
Build a log file from the full path.
725
"app.getLogFile is deprecated. Use "
726
"twisted.python.logfile.LogFile.fromFullPath instead",
727
DeprecationWarning, stacklevel=2)
729
return logfile.LogFile.fromFullPath(logfilename)