~landscape/zope3/ztk-1.1.3

« back to all changes in this revision

Viewing changes to src/twisted/application/app.py

  • Committer: Andreas Hasenack
  • Date: 2009-07-20 17:49:16 UTC
  • Revision ID: andreas@canonical.com-20090720174916-g2tn6qmietz2hn0u
Revert twisted removal, it breaks several dozen tests [trivial]

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.test.test_application -*-
 
2
#
 
3
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
 
4
# See LICENSE for details.
 
5
 
 
6
import sys, os, pdb, getpass, traceback, signal
 
7
 
 
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
 
13
 
 
14
# Expose the new implementation of installReactor at the old location.
 
15
from twisted.application.reactors import installReactor
 
16
 
 
17
 
 
18
def runWithProfiler(reactor, config):
 
19
    """Run reactor under standard profiler."""
 
20
    try:
 
21
        import profile
 
22
    except ImportError, e:
 
23
        s = "Failed to import module profile: %s" % e
 
24
        s += """
 
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.
 
29
"""
 
30
        traceback.print_exc(file=log.logfile)
 
31
        log.msg(s)
 
32
        log.deferr()
 
33
        sys.exit('\n' + s + '\n')
 
34
 
 
35
    p = profile.Profile()
 
36
    p.runcall(reactor.run)
 
37
    if config['savestats']:
 
38
        p.dump_stats(config['profile'])
 
39
    else:
 
40
        # XXX - omfg python sucks
 
41
        tmp, sys.stdout = sys.stdout, open(config['profile'], 'a')
 
42
        p.print_stats()
 
43
        sys.stdout, tmp = tmp, sys.stdout
 
44
        tmp.close()
 
45
 
 
46
def runWithHotshot(reactor, config):
 
47
    """Run reactor under hotshot profiler."""
 
48
    try:
 
49
        import hotshot.stats
 
50
    except ImportError, e:
 
51
        s = "Failed to import module hotshot: %s" % e
 
52
        s += """
 
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.
 
57
"""
 
58
        traceback.print_exc(file=log.logfile)
 
59
        log.msg(s)
 
60
        log.deferr()
 
61
        sys.exit('\n' + s + '\n')
 
62
 
 
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
 
68
        return
 
69
    else:
 
70
        s = hotshot.stats.load(config["profile"])
 
71
        s.strip_dirs()
 
72
        s.sort_stats(-1)
 
73
        tmp, sys.stdout = sys.stdout, open(config['profile'], 'w')
 
74
        s.print_stats()
 
75
        sys.stdout, tmp = tmp, sys.stdout
 
76
        tmp.close()
 
77
 
 
78
def fixPdb():
 
79
    def do_stop(self, arg):
 
80
        self.clear_all_breaks()
 
81
        self.set_continue()
 
82
        from twisted.internet import reactor
 
83
        reactor.callLater(0, reactor.stop)
 
84
        return 1
 
85
 
 
86
    def help_stop(self):
 
87
        print """stop - Continue execution, then cleanly shutdown the twisted reactor."""
 
88
    
 
89
    def set_quit(self):
 
90
        os._exit(0)
 
91
 
 
92
    pdb.Pdb.set_quit = set_quit
 
93
    pdb.Pdb.do_stop = do_stop
 
94
    pdb.Pdb.help_stop = help_stop
 
95
 
 
96
def runReactorWithLogging(config, oldstdout, oldstderr):
 
97
    from twisted.internet import reactor
 
98
    try:
 
99
        if config['profile']:
 
100
            if not config['nothotshot']:
 
101
                runWithHotshot(reactor, config)
 
102
            else:
 
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())
 
110
            fixPdb()
 
111
            pdb.runcall(reactor.run)
 
112
        else:
 
113
            reactor.run()
 
114
    except:
 
115
        if config['nodaemon']:
 
116
            file = oldstdout
 
117
        else:
 
118
            file = open("TWISTD-CRASH.log",'a')
 
119
        traceback.print_exc(file=file)
 
120
        file.flush()
 
121
 
 
122
 
 
123
def getPassphrase(needed):
 
124
    if needed:
 
125
        return getpass.getpass('Passphrase: ')
 
126
    else:
 
127
        return None
 
128
 
 
129
 
 
130
 
 
131
def getSavePassphrase(needed):
 
132
    if needed:
 
133
        passphrase = util.getPassword("Encryption passphrase: ")
 
134
    else:
 
135
        return None
 
136
 
 
137
 
 
138
 
 
139
class ApplicationRunner(object):
 
140
    """
 
141
    An object which helps running an application based on a config object.
 
142
 
 
143
    Subclass me and implement preApplication and postApplication
 
144
    methods. postApplication generally will want to run the reactor
 
145
    after starting the application.
 
146
 
 
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.
 
150
    """
 
151
    def __init__(self, config):
 
152
        self.config = config
 
153
 
 
154
    def run(self):
 
155
        """Run the application."""
 
156
        self.preApplication()
 
157
        self.application = self.createOrGetApplication()
 
158
        self.postApplication()
 
159
 
 
160
 
 
161
    def preApplication(self):
 
162
        """
 
163
        Override in subclass.
 
164
 
 
165
        This should set up any state necessary before loading and
 
166
        running the Application.
 
167
        """
 
168
        raise NotImplementedError
 
169
 
 
170
 
 
171
    def postApplication(self):
 
172
        """
 
173
        Override in subclass.
 
174
 
 
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.
 
178
        """
 
179
        raise NotImplementedError
 
180
 
 
181
 
 
182
    def createOrGetApplication(self):
 
183
        """
 
184
        Create or load an Application based on the parameters found in the
 
185
        given L{ServerOptions} instance.
 
186
 
 
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.
 
190
 
 
191
        Otherwise, an application will be loaded based on parameters in
 
192
        the config.
 
193
        """
 
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.
 
197
 
 
198
            # loadedPlugins is set up by the ServerOptions.subCommands
 
199
            # property, which is iterated somewhere in the bowels of
 
200
            # usage.Options.
 
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)
 
205
        else:
 
206
            passphrase = getPassphrase(self.config['encrypted'])
 
207
            application = getApplication(self.config, passphrase)
 
208
        return application
 
209
 
 
210
 
 
211
 
 
212
def getApplication(config, passphrase):
 
213
    s = [(config[t], t)
 
214
           for t in ['python', 'xml', 'source', 'file'] if config[t]][0]
 
215
    filename, style = s[0], {'file':'pickle'}.get(s[1],s[1])
 
216
    try:
 
217
        log.msg("Loading %s..." % filename)
 
218
        application = service.loadApplication(filename, style, passphrase)
 
219
        log.msg("Loaded.")
 
220
    except Exception, e:
 
221
        s = "Failed to load application: %s" % e
 
222
        if isinstance(e, KeyError) and e.args[0] == "application":
 
223
            s += """
 
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.
 
228
 
 
229
Please read the 'Using Application' HOWTO for details.
 
230
"""
 
231
        traceback.print_exc(file=log.logfile)
 
232
        log.msg(s)
 
233
        log.deferr()
 
234
        sys.exit('\n' + s + '\n')
 
235
    return application
 
236
 
 
237
def reportProfile(report_profile, name):
 
238
    if not report_profile:
 
239
        return
 
240
    if name:
 
241
        from twisted.python.dxprofile import report
 
242
        log.msg("Sending DXP stats...")
 
243
        report(report_profile, name)
 
244
        log.msg("DXP stats sent.")
 
245
    else:
 
246
        log.err("--report-profile specified but application has no "
 
247
                "name (--appname unspecified)")
 
248
 
 
249
 
 
250
def _reactorZshAction():
 
251
    return "(%s)" % " ".join([r.shortName for r in reactors.getReactorTypes()])
 
252
 
 
253
class ReactorSelectionMixin:
 
254
    """
 
255
    Provides options for selecting a reactor to install.
 
256
    """
 
257
    zsh_actions = {"reactor" : _reactorZshAction}
 
258
    def opt_help_reactors(self):
 
259
        """
 
260
        Display a list of possibly available reactor names.
 
261
        """
 
262
        for r in reactors.getReactorTypes():
 
263
            print '    ', r.shortName, '\t', r.description
 
264
        raise SystemExit(0)
 
265
 
 
266
 
 
267
    def opt_reactor(self, shortName):
 
268
        """
 
269
        Which reactor to use (see --help-reactors for a list of possibilities)
 
270
        """
 
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.
 
274
        #
 
275
        # This could probably be improved somehow.
 
276
        installReactor(shortName)
 
277
    opt_r = opt_reactor
 
278
 
 
279
 
 
280
 
 
281
 
 
282
class ServerOptions(usage.Options, ReactorSelectionMixin):
 
283
 
 
284
    optFlags = [['savestats', None,
 
285
                 "save the Stats object rather than the text output of "
 
286
                 "the profiler."],
 
287
                ['no_save','o',   "do not save state on shutdown"],
 
288
                ['encrypted', 'e',
 
289
                 "The specified tap/aos/xml file is encrypted."],
 
290
                ['nothotshot', None,
 
291
                 "Don't use the 'hotshot' profiler even if it's available."]]
 
292
 
 
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"],
 
299
                     ['python','y', None,
 
300
                      "read an application from within a Python file (implies -o)"],
 
301
                     ['xml', 'x', None,
 
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)."],
 
306
                     ['rundir','d','.',
 
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 '
 
313
                      'name.']]
 
314
 
 
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"',
 
322
                   "rundir":"_dirs"}
 
323
    #zsh_actionDescr = {"logfile":"log file name", "random":"random seed"}
 
324
 
 
325
    def __init__(self, *a, **kw):
 
326
        self['debug'] = False
 
327
        usage.Options.__init__(self, *a, **kw)
 
328
 
 
329
    def opt_debug(self):
 
330
        """
 
331
        run the application in the Python Debugger (implies nodaemon),
 
332
        sending SIGUSR2 will drop into debugger
 
333
        """
 
334
        defer.setDebugging(True)
 
335
        failure.startDebugMode()
 
336
        self['debug'] = True
 
337
    opt_b = opt_debug
 
338
 
 
339
 
 
340
    def opt_spew(self):
 
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)
 
344
        try:
 
345
            import threading
 
346
        except ImportError:
 
347
            return
 
348
        threading.settrace(util.spewer)
 
349
 
 
350
 
 
351
    def parseOptions(self, options=None):
 
352
        if options is None:
 
353
            options = sys.argv[1:] or ["--help"]
 
354
        usage.Options.parseOptions(self, options)
 
355
 
 
356
    def postOptions(self):
 
357
        if self.subCommand or self['python']:
 
358
            self['no_save'] = True
 
359
 
 
360
    def subCommands(self):
 
361
        from twisted import plugin
 
362
        plugins = plugin.getPlugins(service.IServiceMaker)
 
363
        self.loadedPlugins = {}
 
364
        for plug in plugins:
 
365
            self.loadedPlugins[plug.tapname] = plug
 
366
            yield (plug.tapname, None, lambda: plug.options(), plug.description)
 
367
    subCommands = property(subCommands)
 
368
 
 
369
 
 
370
 
 
371
def run(runApp, ServerOptions):
 
372
    config = ServerOptions()
 
373
    try:
 
374
        config.parseOptions()
 
375
    except usage.error, ue:
 
376
        print config
 
377
        print "%s: %s" % (sys.argv[0], ue)
 
378
    else:
 
379
        runApp(config)
 
380
 
 
381
 
 
382
def initialLog():
 
383
    from twisted.internet import reactor
 
384
    log.msg("twistd %s (%s %s) starting up" % (copyright.version,
 
385
                                               sys.executable,
 
386
                                               runtime.shortPythonVersion()))
 
387
    log.msg('reactor class: %s' % reactor.__class__)
 
388
 
 
389
 
 
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)
 
394
    if passphrase:
 
395
        fileout = None
 
396
    sob.IPersistable(application).save(filename=fileout, passphrase=passphrase)
 
397
 
 
398
def startApplication(application, save):
 
399
    from twisted.internet import reactor
 
400
    service.IService(application).startService()
 
401
    if save:
 
402
         p = sob.IPersistable(application)
 
403
         reactor.addSystemEventTrigger('after', 'shutdown', p.save, 'shutdown')
 
404
    reactor.addSystemEventTrigger('before', 'shutdown',
 
405
                                  service.IService(application).stopService)
 
406
 
 
407
def getLogFile(logfilename):
 
408
    logPath = os.path.abspath(logfilename)
 
409
    logFile = logfile.LogFile(os.path.basename(logPath),
 
410
                              os.path.dirname(logPath))
 
411
    return logFile