1
# -*- test-case-name: twisted.test.test_application -*-
3
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
4
# See LICENSE for details.
6
import sys, os, pdb, getpass, traceback, signal
8
from twisted.python import runtime, log, usage, reflect, failure, util, logfile
9
from twisted.persisted import sob
10
from twisted.application import service, reactors
11
from twisted.internet import defer
12
from twisted import copyright
14
# Expose the new implementation of installReactor at the old location.
15
from twisted.application.reactors import installReactor
18
def runWithProfiler(reactor, config):
19
"""Run reactor under standard profiler."""
22
except ImportError, e:
23
s = "Failed to import module profile: %s" % e
25
This is most likely caused by your operating system not including
26
profile.py due to it being non-free. Either do not use the option
27
--profile, or install profile.py; your operating system vendor
28
may provide it in a separate package.
30
traceback.print_exc(file=log.logfile)
33
sys.exit('\n' + s + '\n')
36
p.runcall(reactor.run)
37
if config['savestats']:
38
p.dump_stats(config['profile'])
40
# XXX - omfg python sucks
41
tmp, sys.stdout = sys.stdout, open(config['profile'], 'a')
43
sys.stdout, tmp = tmp, sys.stdout
46
def runWithHotshot(reactor, config):
47
"""Run reactor under hotshot profiler."""
50
except ImportError, e:
51
s = "Failed to import module hotshot: %s" % e
53
This is most likely caused by your operating system not including
54
profile.py due to it being non-free. Either do not use the option
55
--profile, or install profile.py; your operating system vendor
56
may provide it in a separate package.
58
traceback.print_exc(file=log.logfile)
61
sys.exit('\n' + s + '\n')
63
# this writes stats straight out
64
p = hotshot.Profile(config["profile"])
65
p.runcall(reactor.run)
66
if config["savestats"]:
67
# stats are automatically written to file, nothing to do
70
s = hotshot.stats.load(config["profile"])
73
tmp, sys.stdout = sys.stdout, open(config['profile'], 'w')
75
sys.stdout, tmp = tmp, sys.stdout
79
def do_stop(self, arg):
80
self.clear_all_breaks()
82
from twisted.internet import reactor
83
reactor.callLater(0, reactor.stop)
87
print """stop - Continue execution, then cleanly shutdown the twisted reactor."""
92
pdb.Pdb.set_quit = set_quit
93
pdb.Pdb.do_stop = do_stop
94
pdb.Pdb.help_stop = help_stop
96
def runReactorWithLogging(config, oldstdout, oldstderr):
97
from twisted.internet import reactor
100
if not config['nothotshot']:
101
runWithHotshot(reactor, config)
103
runWithProfiler(reactor, config)
104
elif config['debug']:
105
sys.stdout = oldstdout
106
sys.stderr = oldstderr
107
if runtime.platformType == 'posix':
108
signal.signal(signal.SIGUSR2, lambda *args: pdb.set_trace())
109
signal.signal(signal.SIGINT, lambda *args: pdb.set_trace())
111
pdb.runcall(reactor.run)
115
if config['nodaemon']:
118
file = open("TWISTD-CRASH.log",'a')
119
traceback.print_exc(file=file)
123
def getPassphrase(needed):
125
return getpass.getpass('Passphrase: ')
131
def getSavePassphrase(needed):
133
passphrase = util.getPassword("Encryption passphrase: ")
139
class ApplicationRunner(object):
141
An object which helps running an application based on a config object.
143
Subclass me and implement preApplication and postApplication
144
methods. postApplication generally will want to run the reactor
145
after starting the application.
147
@ivar config: The config object, which provides a dict-like interface.
148
@ivar application: Available in postApplication, but not
149
preApplication. This is the application object.
151
def __init__(self, config):
155
"""Run the application."""
156
self.preApplication()
157
self.application = self.createOrGetApplication()
158
self.postApplication()
161
def preApplication(self):
163
Override in subclass.
165
This should set up any state necessary before loading and
166
running the Application.
168
raise NotImplementedError
171
def postApplication(self):
173
Override in subclass.
175
This will be called after the application has been loaded (so
176
the C{application} attribute will be set). Generally this
177
should start the application and run the reactor.
179
raise NotImplementedError
182
def createOrGetApplication(self):
184
Create or load an Application based on the parameters found in the
185
given L{ServerOptions} instance.
187
If a subcommand was used, the L{service.IServiceMaker} that it
188
represents will be used to construct a service to be added to
189
a newly-created Application.
191
Otherwise, an application will be loaded based on parameters in
194
if self.config.subCommand:
195
# If a subcommand was given, it's our responsibility to create
196
# the application, instead of load it from a file.
198
# loadedPlugins is set up by the ServerOptions.subCommands
199
# property, which is iterated somewhere in the bowels of
201
plg = self.config.loadedPlugins[self.config.subCommand]
202
ser = plg.makeService(self.config.subOptions)
203
application = service.Application(plg.tapname)
204
ser.setServiceParent(application)
206
passphrase = getPassphrase(self.config['encrypted'])
207
application = getApplication(self.config, passphrase)
212
def getApplication(config, passphrase):
214
for t in ['python', 'xml', 'source', 'file'] if config[t]][0]
215
filename, style = s[0], {'file':'pickle'}.get(s[1],s[1])
217
log.msg("Loading %s..." % filename)
218
application = service.loadApplication(filename, style, passphrase)
221
s = "Failed to load application: %s" % e
222
if isinstance(e, KeyError) and e.args[0] == "application":
224
Could not find 'application' in the file. To use 'twistd -y', your .tac
225
file must create a suitable object (e.g., by calling service.Application())
226
and store it in a variable named 'application'. twistd loads your .tac file
227
and scans the global variables for one of this name.
229
Please read the 'Using Application' HOWTO for details.
231
traceback.print_exc(file=log.logfile)
234
sys.exit('\n' + s + '\n')
237
def reportProfile(report_profile, name):
238
if not report_profile:
241
from twisted.python.dxprofile import report
242
log.msg("Sending DXP stats...")
243
report(report_profile, name)
244
log.msg("DXP stats sent.")
246
log.err("--report-profile specified but application has no "
247
"name (--appname unspecified)")
250
def _reactorZshAction():
251
return "(%s)" % " ".join([r.shortName for r in reactors.getReactorTypes()])
253
class ReactorSelectionMixin:
255
Provides options for selecting a reactor to install.
257
zsh_actions = {"reactor" : _reactorZshAction}
258
def opt_help_reactors(self):
260
Display a list of possibly available reactor names.
262
for r in reactors.getReactorTypes():
263
print ' ', r.shortName, '\t', r.description
267
def opt_reactor(self, shortName):
269
Which reactor to use (see --help-reactors for a list of possibilities)
271
# Actually actually actually install the reactor right at this very
272
# moment, before any other code (for example, a sub-command plugin)
273
# runs and accidentally imports and installs the default reactor.
275
# This could probably be improved somehow.
276
installReactor(shortName)
282
class ServerOptions(usage.Options, ReactorSelectionMixin):
284
optFlags = [['savestats', None,
285
"save the Stats object rather than the text output of "
287
['no_save','o', "do not save state on shutdown"],
289
"The specified tap/aos/xml file is encrypted."],
291
"Don't use the 'hotshot' profiler even if it's available."]]
293
optParameters = [['logfile','l', None,
294
"log to a specified file, - for stdout"],
295
['profile', 'p', None,
296
"Run in profile mode, dumping results to specified file"],
297
['file','f','twistd.tap',
298
"read the given .tap file"],
300
"read an application from within a Python file (implies -o)"],
302
"Read an application from a .tax file "
303
"(Marmalade format)."],
304
['source', 's', None,
305
"Read an application from a .tas file (AOT format)."],
307
'Change to a supplied directory before running'],
308
['report-profile', None, None,
309
'E-mail address to use when reporting dynamic execution '
310
'profiler stats. This should not be combined with '
311
'other profiling options. This will only take effect '
312
'if the application to be run has an application '
315
#zsh_altArgDescr = {"foo":"use this description for foo instead"}
316
#zsh_multiUse = ["foo", "bar"]
317
zsh_mutuallyExclusive = [("file", "python", "xml", "source")]
318
zsh_actions = {"file":'_files -g "*.tap"',
319
"python":'_files -g "*.(tac|py)"',
320
"xml":'_files -g "*.tax"',
321
"source":'_files -g "*.tas"',
323
#zsh_actionDescr = {"logfile":"log file name", "random":"random seed"}
325
def __init__(self, *a, **kw):
326
self['debug'] = False
327
usage.Options.__init__(self, *a, **kw)
331
run the application in the Python Debugger (implies nodaemon),
332
sending SIGUSR2 will drop into debugger
334
defer.setDebugging(True)
335
failure.startDebugMode()
341
"""Print an insanely verbose log of everything that happens.
342
Useful when debugging freezes or locks in complex code."""
343
sys.settrace(util.spewer)
348
threading.settrace(util.spewer)
351
def parseOptions(self, options=None):
353
options = sys.argv[1:] or ["--help"]
354
usage.Options.parseOptions(self, options)
356
def postOptions(self):
357
if self.subCommand or self['python']:
358
self['no_save'] = True
360
def subCommands(self):
361
from twisted import plugin
362
plugins = plugin.getPlugins(service.IServiceMaker)
363
self.loadedPlugins = {}
365
self.loadedPlugins[plug.tapname] = plug
366
yield (plug.tapname, None, lambda: plug.options(), plug.description)
367
subCommands = property(subCommands)
371
def run(runApp, ServerOptions):
372
config = ServerOptions()
374
config.parseOptions()
375
except usage.error, ue:
377
print "%s: %s" % (sys.argv[0], ue)
383
from twisted.internet import reactor
384
log.msg("twistd %s (%s %s) starting up" % (copyright.version,
386
runtime.shortPythonVersion()))
387
log.msg('reactor class: %s' % reactor.__class__)
390
def convertStyle(filein, typein, passphrase, fileout, typeout, encrypt):
391
application = service.loadApplication(filein, typein, passphrase)
392
sob.IPersistable(application).setStyle(typeout)
393
passphrase = getSavePassphrase(encrypt)
396
sob.IPersistable(application).save(filename=fileout, passphrase=passphrase)
398
def startApplication(application, save):
399
from twisted.internet import reactor
400
service.IService(application).startService()
402
p = sob.IPersistable(application)
403
reactor.addSystemEventTrigger('after', 'shutdown', p.save, 'shutdown')
404
reactor.addSystemEventTrigger('before', 'shutdown',
405
service.IService(application).stopService)
407
def getLogFile(logfilename):
408
logPath = os.path.abspath(logfilename)
409
logFile = logfile.LogFile(os.path.basename(logPath),
410
os.path.dirname(logPath))