add Linux_i686
This commit is contained in:
parent
75f9a2fcbc
commit
95cd9b11f2
1644 changed files with 564260 additions and 0 deletions
|
|
@ -0,0 +1,7 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
#
|
||||
"""
|
||||
Configuration objects for Twisted Applications
|
||||
"""
|
||||
|
|
@ -0,0 +1,664 @@
|
|||
# -*- test-case-name: twisted.test.test_application,twisted.test.test_twistd -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
import sys, os, pdb, getpass, traceback, signal
|
||||
from operator import attrgetter
|
||||
|
||||
from twisted.python import runtime, log, usage, failure, util, logfile
|
||||
from twisted.python.versions import Version
|
||||
from twisted.python.reflect import qual, namedAny
|
||||
from twisted.python.deprecate import deprecated
|
||||
from twisted.python.log import ILogObserver
|
||||
from twisted.persisted import sob
|
||||
from twisted.application import service, reactors
|
||||
from twisted.internet import defer
|
||||
from twisted import copyright, plugin
|
||||
|
||||
# Expose the new implementation of installReactor at the old location.
|
||||
from twisted.application.reactors import installReactor
|
||||
from twisted.application.reactors import NoSuchReactor
|
||||
|
||||
|
||||
|
||||
class _BasicProfiler(object):
|
||||
"""
|
||||
@ivar saveStats: if C{True}, save the stats information instead of the
|
||||
human readable format
|
||||
@type saveStats: C{bool}
|
||||
|
||||
@ivar profileOutput: the name of the file use to print profile data.
|
||||
@type profileOutput: C{str}
|
||||
"""
|
||||
|
||||
def __init__(self, profileOutput, saveStats):
|
||||
self.profileOutput = profileOutput
|
||||
self.saveStats = saveStats
|
||||
|
||||
|
||||
def _reportImportError(self, module, e):
|
||||
"""
|
||||
Helper method to report an import error with a profile module. This
|
||||
has to be explicit because some of these modules are removed by
|
||||
distributions due to them being non-free.
|
||||
"""
|
||||
s = "Failed to import module %s: %s" % (module, e)
|
||||
s += """
|
||||
This is most likely caused by your operating system not including
|
||||
the module due to it being non-free. Either do not use the option
|
||||
--profile, or install the module; your operating system vendor
|
||||
may provide it in a separate package.
|
||||
"""
|
||||
raise SystemExit(s)
|
||||
|
||||
|
||||
|
||||
class ProfileRunner(_BasicProfiler):
|
||||
"""
|
||||
Runner for the standard profile module.
|
||||
"""
|
||||
|
||||
def run(self, reactor):
|
||||
"""
|
||||
Run reactor under the standard profiler.
|
||||
"""
|
||||
try:
|
||||
import profile
|
||||
except ImportError, e:
|
||||
self._reportImportError("profile", e)
|
||||
|
||||
p = profile.Profile()
|
||||
p.runcall(reactor.run)
|
||||
if self.saveStats:
|
||||
p.dump_stats(self.profileOutput)
|
||||
else:
|
||||
tmp, sys.stdout = sys.stdout, open(self.profileOutput, 'a')
|
||||
try:
|
||||
p.print_stats()
|
||||
finally:
|
||||
sys.stdout, tmp = tmp, sys.stdout
|
||||
tmp.close()
|
||||
|
||||
|
||||
|
||||
class HotshotRunner(_BasicProfiler):
|
||||
"""
|
||||
Runner for the hotshot profile module.
|
||||
"""
|
||||
|
||||
def run(self, reactor):
|
||||
"""
|
||||
Run reactor under the hotshot profiler.
|
||||
"""
|
||||
try:
|
||||
import hotshot.stats
|
||||
except (ImportError, SystemExit), e:
|
||||
# Certain versions of Debian (and Debian derivatives) raise
|
||||
# SystemExit when importing hotshot if the "non-free" profiler
|
||||
# module is not installed. Someone eventually recognized this
|
||||
# as a bug and changed the Debian packaged Python to raise
|
||||
# ImportError instead. Handle both exception types here in
|
||||
# order to support the versions of Debian which have this
|
||||
# behavior. The bug report which prompted the introduction of
|
||||
# this highly undesirable behavior should be available online at
|
||||
# <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=334067>.
|
||||
# There seems to be no corresponding bug report which resulted
|
||||
# in the behavior being removed. -exarkun
|
||||
self._reportImportError("hotshot", e)
|
||||
|
||||
# this writes stats straight out
|
||||
p = hotshot.Profile(self.profileOutput)
|
||||
p.runcall(reactor.run)
|
||||
if self.saveStats:
|
||||
# stats are automatically written to file, nothing to do
|
||||
return
|
||||
else:
|
||||
s = hotshot.stats.load(self.profileOutput)
|
||||
s.strip_dirs()
|
||||
s.sort_stats(-1)
|
||||
s.stream = open(self.profileOutput, 'w')
|
||||
s.print_stats()
|
||||
s.stream.close()
|
||||
|
||||
|
||||
|
||||
class CProfileRunner(_BasicProfiler):
|
||||
"""
|
||||
Runner for the cProfile module.
|
||||
"""
|
||||
|
||||
def run(self, reactor):
|
||||
"""
|
||||
Run reactor under the cProfile profiler.
|
||||
"""
|
||||
try:
|
||||
import cProfile, pstats
|
||||
except ImportError, e:
|
||||
self._reportImportError("cProfile", e)
|
||||
|
||||
p = cProfile.Profile()
|
||||
p.runcall(reactor.run)
|
||||
if self.saveStats:
|
||||
p.dump_stats(self.profileOutput)
|
||||
else:
|
||||
stream = open(self.profileOutput, 'w')
|
||||
s = pstats.Stats(p, stream=stream)
|
||||
s.strip_dirs()
|
||||
s.sort_stats(-1)
|
||||
s.print_stats()
|
||||
stream.close()
|
||||
|
||||
|
||||
|
||||
class AppProfiler(object):
|
||||
"""
|
||||
Class which selects a specific profile runner based on configuration
|
||||
options.
|
||||
|
||||
@ivar profiler: the name of the selected profiler.
|
||||
@type profiler: C{str}
|
||||
"""
|
||||
profilers = {"profile": ProfileRunner, "hotshot": HotshotRunner,
|
||||
"cprofile": CProfileRunner}
|
||||
|
||||
def __init__(self, options):
|
||||
saveStats = options.get("savestats", False)
|
||||
profileOutput = options.get("profile", None)
|
||||
self.profiler = options.get("profiler", "hotshot").lower()
|
||||
if self.profiler in self.profilers:
|
||||
profiler = self.profilers[self.profiler](profileOutput, saveStats)
|
||||
self.run = profiler.run
|
||||
else:
|
||||
raise SystemExit("Unsupported profiler name: %s" % (self.profiler,))
|
||||
|
||||
|
||||
|
||||
class AppLogger(object):
|
||||
"""
|
||||
Class managing logging faciliy of the application.
|
||||
|
||||
@ivar _logfilename: The name of the file to which to log, if other than the
|
||||
default.
|
||||
@type _logfilename: C{str}
|
||||
|
||||
@ivar _observerFactory: Callable object that will create a log observer, or
|
||||
None.
|
||||
|
||||
@ivar _observer: log observer added at C{start} and removed at C{stop}.
|
||||
@type _observer: C{callable}
|
||||
"""
|
||||
_observer = None
|
||||
|
||||
def __init__(self, options):
|
||||
self._logfilename = options.get("logfile", "")
|
||||
self._observerFactory = options.get("logger") or None
|
||||
|
||||
|
||||
def start(self, application):
|
||||
"""
|
||||
Initialize the logging system.
|
||||
|
||||
If a customer logger was specified on the command line it will be
|
||||
used. If not, and an L{ILogObserver} component has been set on
|
||||
C{application}, then it will be used as the log observer. Otherwise a
|
||||
log observer will be created based on the command-line options for
|
||||
built-in loggers (e.g. C{--logfile}).
|
||||
|
||||
@param application: The application on which to check for an
|
||||
L{ILogObserver}.
|
||||
"""
|
||||
if self._observerFactory is not None:
|
||||
observer = self._observerFactory()
|
||||
else:
|
||||
observer = application.getComponent(ILogObserver, None)
|
||||
|
||||
if observer is None:
|
||||
observer = self._getLogObserver()
|
||||
self._observer = observer
|
||||
log.startLoggingWithObserver(self._observer)
|
||||
self._initialLog()
|
||||
|
||||
|
||||
def _initialLog(self):
|
||||
"""
|
||||
Print twistd start log message.
|
||||
"""
|
||||
from twisted.internet import reactor
|
||||
log.msg("twistd %s (%s %s) starting up." % (copyright.version,
|
||||
sys.executable,
|
||||
runtime.shortPythonVersion()))
|
||||
log.msg('reactor class: %s.' % (qual(reactor.__class__),))
|
||||
|
||||
|
||||
def _getLogObserver(self):
|
||||
"""
|
||||
Create a log observer to be added to the logging system before running
|
||||
this application.
|
||||
"""
|
||||
if self._logfilename == '-' or not self._logfilename:
|
||||
logFile = sys.stdout
|
||||
else:
|
||||
logFile = logfile.LogFile.fromFullPath(self._logfilename)
|
||||
return log.FileLogObserver(logFile).emit
|
||||
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Print twistd stop log message.
|
||||
"""
|
||||
log.msg("Server Shut Down.")
|
||||
if self._observer is not None:
|
||||
log.removeObserver(self._observer)
|
||||
self._observer = None
|
||||
|
||||
|
||||
|
||||
def fixPdb():
|
||||
def do_stop(self, arg):
|
||||
self.clear_all_breaks()
|
||||
self.set_continue()
|
||||
from twisted.internet import reactor
|
||||
reactor.callLater(0, reactor.stop)
|
||||
return 1
|
||||
|
||||
|
||||
def help_stop(self):
|
||||
print """stop - Continue execution, then cleanly shutdown the twisted reactor."""
|
||||
|
||||
|
||||
def set_quit(self):
|
||||
os._exit(0)
|
||||
|
||||
pdb.Pdb.set_quit = set_quit
|
||||
pdb.Pdb.do_stop = do_stop
|
||||
pdb.Pdb.help_stop = help_stop
|
||||
|
||||
|
||||
|
||||
def runReactorWithLogging(config, oldstdout, oldstderr, profiler=None, reactor=None):
|
||||
"""
|
||||
Start the reactor, using profiling if specified by the configuration, and
|
||||
log any error happening in the process.
|
||||
|
||||
@param config: configuration of the twistd application.
|
||||
@type config: L{ServerOptions}
|
||||
|
||||
@param oldstdout: initial value of C{sys.stdout}.
|
||||
@type oldstdout: C{file}
|
||||
|
||||
@param oldstderr: initial value of C{sys.stderr}.
|
||||
@type oldstderr: C{file}
|
||||
|
||||
@param profiler: object used to run the reactor with profiling.
|
||||
@type profiler: L{AppProfiler}
|
||||
|
||||
@param reactor: The reactor to use. If C{None}, the global reactor will
|
||||
be used.
|
||||
"""
|
||||
if reactor is None:
|
||||
from twisted.internet import reactor
|
||||
try:
|
||||
if config['profile']:
|
||||
if profiler is not None:
|
||||
profiler.run(reactor)
|
||||
elif config['debug']:
|
||||
sys.stdout = oldstdout
|
||||
sys.stderr = oldstderr
|
||||
if runtime.platformType == 'posix':
|
||||
signal.signal(signal.SIGUSR2, lambda *args: pdb.set_trace())
|
||||
signal.signal(signal.SIGINT, lambda *args: pdb.set_trace())
|
||||
fixPdb()
|
||||
pdb.runcall(reactor.run)
|
||||
else:
|
||||
reactor.run()
|
||||
except:
|
||||
if config['nodaemon']:
|
||||
file = oldstdout
|
||||
else:
|
||||
file = open("TWISTD-CRASH.log",'a')
|
||||
traceback.print_exc(file=file)
|
||||
file.flush()
|
||||
|
||||
|
||||
|
||||
def getPassphrase(needed):
|
||||
if needed:
|
||||
return getpass.getpass('Passphrase: ')
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
|
||||
def getSavePassphrase(needed):
|
||||
if needed:
|
||||
passphrase = util.getPassword("Encryption passphrase: ")
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
|
||||
class ApplicationRunner(object):
|
||||
"""
|
||||
An object which helps running an application based on a config object.
|
||||
|
||||
Subclass me and implement preApplication and postApplication
|
||||
methods. postApplication generally will want to run the reactor
|
||||
after starting the application.
|
||||
|
||||
@ivar config: The config object, which provides a dict-like interface.
|
||||
|
||||
@ivar application: Available in postApplication, but not
|
||||
preApplication. This is the application object.
|
||||
|
||||
@ivar profilerFactory: Factory for creating a profiler object, able to
|
||||
profile the application if options are set accordingly.
|
||||
|
||||
@ivar profiler: Instance provided by C{profilerFactory}.
|
||||
|
||||
@ivar loggerFactory: Factory for creating object responsible for logging.
|
||||
|
||||
@ivar logger: Instance provided by C{loggerFactory}.
|
||||
"""
|
||||
profilerFactory = AppProfiler
|
||||
loggerFactory = AppLogger
|
||||
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.profiler = self.profilerFactory(config)
|
||||
self.logger = self.loggerFactory(config)
|
||||
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Run the application.
|
||||
"""
|
||||
self.preApplication()
|
||||
self.application = self.createOrGetApplication()
|
||||
|
||||
self.logger.start(self.application)
|
||||
|
||||
self.postApplication()
|
||||
self.logger.stop()
|
||||
|
||||
|
||||
def startReactor(self, reactor, oldstdout, oldstderr):
|
||||
"""
|
||||
Run the reactor with the given configuration. Subclasses should
|
||||
probably call this from C{postApplication}.
|
||||
|
||||
@see: L{runReactorWithLogging}
|
||||
"""
|
||||
runReactorWithLogging(
|
||||
self.config, oldstdout, oldstderr, self.profiler, reactor)
|
||||
|
||||
|
||||
def preApplication(self):
|
||||
"""
|
||||
Override in subclass.
|
||||
|
||||
This should set up any state necessary before loading and
|
||||
running the Application.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def postApplication(self):
|
||||
"""
|
||||
Override in subclass.
|
||||
|
||||
This will be called after the application has been loaded (so
|
||||
the C{application} attribute will be set). Generally this
|
||||
should start the application and run the reactor.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def createOrGetApplication(self):
|
||||
"""
|
||||
Create or load an Application based on the parameters found in the
|
||||
given L{ServerOptions} instance.
|
||||
|
||||
If a subcommand was used, the L{service.IServiceMaker} that it
|
||||
represents will be used to construct a service to be added to
|
||||
a newly-created Application.
|
||||
|
||||
Otherwise, an application will be loaded based on parameters in
|
||||
the config.
|
||||
"""
|
||||
if self.config.subCommand:
|
||||
# If a subcommand was given, it's our responsibility to create
|
||||
# the application, instead of load it from a file.
|
||||
|
||||
# loadedPlugins is set up by the ServerOptions.subCommands
|
||||
# property, which is iterated somewhere in the bowels of
|
||||
# usage.Options.
|
||||
plg = self.config.loadedPlugins[self.config.subCommand]
|
||||
ser = plg.makeService(self.config.subOptions)
|
||||
application = service.Application(plg.tapname)
|
||||
ser.setServiceParent(application)
|
||||
else:
|
||||
passphrase = getPassphrase(self.config['encrypted'])
|
||||
application = getApplication(self.config, passphrase)
|
||||
return application
|
||||
|
||||
|
||||
|
||||
def getApplication(config, passphrase):
|
||||
s = [(config[t], t)
|
||||
for t in ['python', 'source', 'file'] if config[t]][0]
|
||||
filename, style = s[0], {'file':'pickle'}.get(s[1],s[1])
|
||||
try:
|
||||
log.msg("Loading %s..." % filename)
|
||||
application = service.loadApplication(filename, style, passphrase)
|
||||
log.msg("Loaded.")
|
||||
except Exception, e:
|
||||
s = "Failed to load application: %s" % e
|
||||
if isinstance(e, KeyError) and e.args[0] == "application":
|
||||
s += """
|
||||
Could not find 'application' in the file. To use 'twistd -y', your .tac
|
||||
file must create a suitable object (e.g., by calling service.Application())
|
||||
and store it in a variable named 'application'. twistd loads your .tac file
|
||||
and scans the global variables for one of this name.
|
||||
|
||||
Please read the 'Using Application' HOWTO for details.
|
||||
"""
|
||||
traceback.print_exc(file=log.logfile)
|
||||
log.msg(s)
|
||||
log.deferr()
|
||||
sys.exit('\n' + s + '\n')
|
||||
return application
|
||||
|
||||
|
||||
|
||||
def _reactorAction():
|
||||
return usage.CompleteList([r.shortName for r in reactors.getReactorTypes()])
|
||||
|
||||
|
||||
class ReactorSelectionMixin:
|
||||
"""
|
||||
Provides options for selecting a reactor to install.
|
||||
|
||||
If a reactor is installed, the short name which was used to locate it is
|
||||
saved as the value for the C{"reactor"} key.
|
||||
"""
|
||||
compData = usage.Completions(
|
||||
optActions={"reactor": _reactorAction})
|
||||
|
||||
messageOutput = sys.stdout
|
||||
_getReactorTypes = staticmethod(reactors.getReactorTypes)
|
||||
|
||||
|
||||
def opt_help_reactors(self):
|
||||
"""
|
||||
Display a list of possibly available reactor names.
|
||||
"""
|
||||
rcts = sorted(self._getReactorTypes(), key=attrgetter('shortName'))
|
||||
for r in rcts:
|
||||
self.messageOutput.write(' %-4s\t%s\n' %
|
||||
(r.shortName, r.description))
|
||||
raise SystemExit(0)
|
||||
|
||||
|
||||
def opt_reactor(self, shortName):
|
||||
"""
|
||||
Which reactor to use (see --help-reactors for a list of possibilities)
|
||||
"""
|
||||
# Actually actually actually install the reactor right at this very
|
||||
# moment, before any other code (for example, a sub-command plugin)
|
||||
# runs and accidentally imports and installs the default reactor.
|
||||
#
|
||||
# This could probably be improved somehow.
|
||||
try:
|
||||
installReactor(shortName)
|
||||
except NoSuchReactor:
|
||||
msg = ("The specified reactor does not exist: '%s'.\n"
|
||||
"See the list of available reactors with "
|
||||
"--help-reactors" % (shortName,))
|
||||
raise usage.UsageError(msg)
|
||||
except Exception, e:
|
||||
msg = ("The specified reactor cannot be used, failed with error: "
|
||||
"%s.\nSee the list of available reactors with "
|
||||
"--help-reactors" % (e,))
|
||||
raise usage.UsageError(msg)
|
||||
else:
|
||||
self["reactor"] = shortName
|
||||
opt_r = opt_reactor
|
||||
|
||||
|
||||
|
||||
|
||||
class ServerOptions(usage.Options, ReactorSelectionMixin):
|
||||
|
||||
longdesc = ("twistd reads a twisted.application.service.Application out "
|
||||
"of a file and runs it.")
|
||||
|
||||
optFlags = [['savestats', None,
|
||||
"save the Stats object rather than the text output of "
|
||||
"the profiler."],
|
||||
['no_save','o', "do not save state on shutdown"],
|
||||
['encrypted', 'e',
|
||||
"The specified tap/aos file is encrypted."]]
|
||||
|
||||
optParameters = [['logfile','l', None,
|
||||
"log to a specified file, - for stdout"],
|
||||
['logger', None, None,
|
||||
"A fully-qualified name to a log observer factory to use "
|
||||
"for the initial log observer. Takes precedence over "
|
||||
"--logfile and --syslog (when available)."],
|
||||
['profile', 'p', None,
|
||||
"Run in profile mode, dumping results to specified file"],
|
||||
['profiler', None, "hotshot",
|
||||
"Name of the profiler to use (%s)." %
|
||||
", ".join(AppProfiler.profilers)],
|
||||
['file','f','twistd.tap',
|
||||
"read the given .tap file"],
|
||||
['python','y', None,
|
||||
"read an application from within a Python file "
|
||||
"(implies -o)"],
|
||||
['source', 's', None,
|
||||
"Read an application from a .tas file (AOT format)."],
|
||||
['rundir','d','.',
|
||||
'Change to a supplied directory before running']]
|
||||
|
||||
compData = usage.Completions(
|
||||
mutuallyExclusive=[("file", "python", "source")],
|
||||
optActions={"file": usage.CompleteFiles("*.tap"),
|
||||
"python": usage.CompleteFiles("*.(tac|py)"),
|
||||
"source": usage.CompleteFiles("*.tas"),
|
||||
"rundir": usage.CompleteDirs()}
|
||||
)
|
||||
|
||||
_getPlugins = staticmethod(plugin.getPlugins)
|
||||
|
||||
def __init__(self, *a, **kw):
|
||||
self['debug'] = False
|
||||
usage.Options.__init__(self, *a, **kw)
|
||||
|
||||
|
||||
def opt_debug(self):
|
||||
"""
|
||||
Run the application in the Python Debugger (implies nodaemon),
|
||||
sending SIGUSR2 will drop into debugger
|
||||
"""
|
||||
defer.setDebugging(True)
|
||||
failure.startDebugMode()
|
||||
self['debug'] = True
|
||||
opt_b = opt_debug
|
||||
|
||||
|
||||
def opt_spew(self):
|
||||
"""
|
||||
Print an insanely verbose log of everything that happens.
|
||||
Useful when debugging freezes or locks in complex code."""
|
||||
sys.settrace(util.spewer)
|
||||
try:
|
||||
import threading
|
||||
except ImportError:
|
||||
return
|
||||
threading.settrace(util.spewer)
|
||||
|
||||
|
||||
def parseOptions(self, options=None):
|
||||
if options is None:
|
||||
options = sys.argv[1:] or ["--help"]
|
||||
usage.Options.parseOptions(self, options)
|
||||
|
||||
|
||||
def postOptions(self):
|
||||
if self.subCommand or self['python']:
|
||||
self['no_save'] = True
|
||||
if self['logger'] is not None:
|
||||
try:
|
||||
self['logger'] = namedAny(self['logger'])
|
||||
except Exception, e:
|
||||
raise usage.UsageError("Logger '%s' could not be imported: %s"
|
||||
% (self['logger'], e))
|
||||
|
||||
|
||||
def subCommands(self):
|
||||
plugins = self._getPlugins(service.IServiceMaker)
|
||||
self.loadedPlugins = {}
|
||||
for plug in sorted(plugins, key=attrgetter('tapname')):
|
||||
self.loadedPlugins[plug.tapname] = plug
|
||||
yield (plug.tapname,
|
||||
None,
|
||||
# Avoid resolving the options attribute right away, in case
|
||||
# it's a property with a non-trivial getter (eg, one which
|
||||
# imports modules).
|
||||
lambda plug=plug: plug.options(),
|
||||
plug.description)
|
||||
subCommands = property(subCommands)
|
||||
|
||||
|
||||
|
||||
def run(runApp, ServerOptions):
|
||||
config = ServerOptions()
|
||||
try:
|
||||
config.parseOptions()
|
||||
except usage.error, ue:
|
||||
print config
|
||||
print "%s: %s" % (sys.argv[0], ue)
|
||||
else:
|
||||
runApp(config)
|
||||
|
||||
|
||||
|
||||
def convertStyle(filein, typein, passphrase, fileout, typeout, encrypt):
|
||||
application = service.loadApplication(filein, typein, passphrase)
|
||||
sob.IPersistable(application).setStyle(typeout)
|
||||
passphrase = getSavePassphrase(encrypt)
|
||||
if passphrase:
|
||||
fileout = None
|
||||
sob.IPersistable(application).save(filename=fileout, passphrase=passphrase)
|
||||
|
||||
|
||||
|
||||
def startApplication(application, save):
|
||||
from twisted.internet import reactor
|
||||
service.IService(application).startService()
|
||||
if save:
|
||||
p = sob.IPersistable(application)
|
||||
reactor.addSystemEventTrigger('after', 'shutdown', p.save, 'shutdown')
|
||||
reactor.addSystemEventTrigger('before', 'shutdown',
|
||||
service.IService(application).stopService)
|
||||
|
||||
|
|
@ -0,0 +1,405 @@
|
|||
# -*- test-case-name: twisted.application.test.test_internet,twisted.test.test_application,twisted.test.test_cooperator -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Reactor-based Services
|
||||
|
||||
Here are services to run clients, servers and periodic services using
|
||||
the reactor.
|
||||
|
||||
If you want to run a server service, L{StreamServerEndpointService} defines a
|
||||
service that can wrap an arbitrary L{IStreamServerEndpoint
|
||||
<twisted.internet.interfaces.IStreamServerEndpoint>}
|
||||
as an L{IService}. See also L{twisted.application.strports.service} for
|
||||
constructing one of these directly from a descriptive string.
|
||||
|
||||
Additionally, this module (dynamically) defines various Service subclasses that
|
||||
let you represent clients and servers in a Service hierarchy. Endpoints APIs
|
||||
should be preferred for stream server services, but since those APIs do not yet
|
||||
exist for clients or datagram services, many of these are still useful.
|
||||
|
||||
They are as follows::
|
||||
|
||||
TCPServer, TCPClient,
|
||||
UNIXServer, UNIXClient,
|
||||
SSLServer, SSLClient,
|
||||
UDPServer,
|
||||
UNIXDatagramServer, UNIXDatagramClient,
|
||||
MulticastServer
|
||||
|
||||
These classes take arbitrary arguments in their constructors and pass
|
||||
them straight on to their respective reactor.listenXXX or
|
||||
reactor.connectXXX calls.
|
||||
|
||||
For example, the following service starts a web server on port 8080:
|
||||
C{TCPServer(8080, server.Site(r))}. See the documentation for the
|
||||
reactor.listen/connect* methods for more information.
|
||||
"""
|
||||
|
||||
from twisted.python import log
|
||||
from twisted.python.deprecate import deprecatedModuleAttribute
|
||||
from twisted.python.versions import Version
|
||||
from twisted.application import service
|
||||
from twisted.internet import task
|
||||
|
||||
from twisted.internet.defer import CancelledError
|
||||
|
||||
|
||||
def _maybeGlobalReactor(maybeReactor):
|
||||
"""
|
||||
@return: the argument, or the global reactor if the argument is C{None}.
|
||||
"""
|
||||
if maybeReactor is None:
|
||||
from twisted.internet import reactor
|
||||
return reactor
|
||||
else:
|
||||
return maybeReactor
|
||||
|
||||
|
||||
class _VolatileDataService(service.Service):
|
||||
|
||||
volatile = []
|
||||
|
||||
def __getstate__(self):
|
||||
d = service.Service.__getstate__(self)
|
||||
for attr in self.volatile:
|
||||
if attr in d:
|
||||
del d[attr]
|
||||
return d
|
||||
|
||||
|
||||
|
||||
class _AbstractServer(_VolatileDataService):
|
||||
"""
|
||||
@cvar volatile: list of attribute to remove from pickling.
|
||||
@type volatile: C{list}
|
||||
|
||||
@ivar method: the type of method to call on the reactor, one of B{TCP},
|
||||
B{UDP}, B{SSL} or B{UNIX}.
|
||||
@type method: C{str}
|
||||
|
||||
@ivar reactor: the current running reactor.
|
||||
@type reactor: a provider of C{IReactorTCP}, C{IReactorUDP},
|
||||
C{IReactorSSL} or C{IReactorUnix}.
|
||||
|
||||
@ivar _port: instance of port set when the service is started.
|
||||
@type _port: a provider of L{twisted.internet.interfaces.IListeningPort}.
|
||||
"""
|
||||
|
||||
volatile = ['_port']
|
||||
method = None
|
||||
reactor = None
|
||||
|
||||
_port = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.args = args
|
||||
if 'reactor' in kwargs:
|
||||
self.reactor = kwargs.pop("reactor")
|
||||
self.kwargs = kwargs
|
||||
|
||||
|
||||
def privilegedStartService(self):
|
||||
service.Service.privilegedStartService(self)
|
||||
self._port = self._getPort()
|
||||
|
||||
|
||||
def startService(self):
|
||||
service.Service.startService(self)
|
||||
if self._port is None:
|
||||
self._port = self._getPort()
|
||||
|
||||
|
||||
def stopService(self):
|
||||
service.Service.stopService(self)
|
||||
# TODO: if startup failed, should shutdown skip stopListening?
|
||||
# _port won't exist
|
||||
if self._port is not None:
|
||||
d = self._port.stopListening()
|
||||
del self._port
|
||||
return d
|
||||
|
||||
|
||||
def _getPort(self):
|
||||
"""
|
||||
Wrapper around the appropriate listen method of the reactor.
|
||||
|
||||
@return: the port object returned by the listen method.
|
||||
@rtype: an object providing
|
||||
L{twisted.internet.interfaces.IListeningPort}.
|
||||
"""
|
||||
return getattr(_maybeGlobalReactor(self.reactor),
|
||||
'listen%s' % (self.method,))(*self.args, **self.kwargs)
|
||||
|
||||
|
||||
|
||||
class _AbstractClient(_VolatileDataService):
|
||||
"""
|
||||
@cvar volatile: list of attribute to remove from pickling.
|
||||
@type volatile: C{list}
|
||||
|
||||
@ivar method: the type of method to call on the reactor, one of B{TCP},
|
||||
B{UDP}, B{SSL} or B{UNIX}.
|
||||
@type method: C{str}
|
||||
|
||||
@ivar reactor: the current running reactor.
|
||||
@type reactor: a provider of C{IReactorTCP}, C{IReactorUDP},
|
||||
C{IReactorSSL} or C{IReactorUnix}.
|
||||
|
||||
@ivar _connection: instance of connection set when the service is started.
|
||||
@type _connection: a provider of L{twisted.internet.interfaces.IConnector}.
|
||||
"""
|
||||
volatile = ['_connection']
|
||||
method = None
|
||||
reactor = None
|
||||
|
||||
_connection = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.args = args
|
||||
if 'reactor' in kwargs:
|
||||
self.reactor = kwargs.pop("reactor")
|
||||
self.kwargs = kwargs
|
||||
|
||||
|
||||
def startService(self):
|
||||
service.Service.startService(self)
|
||||
self._connection = self._getConnection()
|
||||
|
||||
|
||||
def stopService(self):
|
||||
service.Service.stopService(self)
|
||||
if self._connection is not None:
|
||||
self._connection.disconnect()
|
||||
del self._connection
|
||||
|
||||
|
||||
def _getConnection(self):
|
||||
"""
|
||||
Wrapper around the appropriate connect method of the reactor.
|
||||
|
||||
@return: the port object returned by the connect method.
|
||||
@rtype: an object providing L{twisted.internet.interfaces.IConnector}.
|
||||
"""
|
||||
return getattr(_maybeGlobalReactor(self.reactor),
|
||||
'connect%s' % (self.method,))(*self.args, **self.kwargs)
|
||||
|
||||
|
||||
|
||||
_doc={
|
||||
'Client':
|
||||
"""Connect to %(tran)s
|
||||
|
||||
Call reactor.connect%(tran)s when the service starts, with the
|
||||
arguments given to the constructor.
|
||||
""",
|
||||
'Server':
|
||||
"""Serve %(tran)s clients
|
||||
|
||||
Call reactor.listen%(tran)s when the service starts, with the
|
||||
arguments given to the constructor. When the service stops,
|
||||
stop listening. See twisted.internet.interfaces for documentation
|
||||
on arguments to the reactor method.
|
||||
""",
|
||||
}
|
||||
|
||||
import types
|
||||
for tran in 'TCP UNIX SSL UDP UNIXDatagram Multicast'.split():
|
||||
for side in 'Server Client'.split():
|
||||
if tran == "Multicast" and side == "Client":
|
||||
continue
|
||||
base = globals()['_Abstract'+side]
|
||||
doc = _doc[side] % vars()
|
||||
klass = types.ClassType(tran+side, (base,),
|
||||
{'method': tran, '__doc__': doc})
|
||||
globals()[tran+side] = klass
|
||||
|
||||
|
||||
|
||||
deprecatedModuleAttribute(
|
||||
Version("Twisted", 13, 1, 0),
|
||||
"It relies upon IReactorUDP.connectUDP "
|
||||
"which was removed in Twisted 10. "
|
||||
"Use twisted.application.internet.UDPServer instead.",
|
||||
"twisted.application.internet", "UDPClient")
|
||||
|
||||
|
||||
|
||||
class TimerService(_VolatileDataService):
|
||||
"""
|
||||
Service to periodically call a function
|
||||
|
||||
Every C{step} seconds call the given function with the given arguments.
|
||||
The service starts the calls when it starts, and cancels them
|
||||
when it stops.
|
||||
|
||||
@ivar clock: Source of time. This defaults to L{None} which is
|
||||
causes L{twisted.internet.reactor} to be used.
|
||||
Feel free to set this to something else, but it probably ought to be
|
||||
set *before* calling L{startService}.
|
||||
@type clock: L{IReactorTime<twisted.internet.interfaces.IReactorTime>}
|
||||
|
||||
@ivar call: Function and arguments to call periodically.
|
||||
@type call: L{tuple} of C{(callable, args, kwargs)}
|
||||
"""
|
||||
|
||||
volatile = ['_loop', '_loopFinished']
|
||||
|
||||
def __init__(self, step, callable, *args, **kwargs):
|
||||
"""
|
||||
@param step: The number of seconds between calls.
|
||||
@type step: L{float}
|
||||
|
||||
@param callable: Function to call
|
||||
@type callable: L{callable}
|
||||
|
||||
@param args: Positional arguments to pass to function
|
||||
@param kwargs: Keyword arguments to pass to function
|
||||
"""
|
||||
self.step = step
|
||||
self.call = (callable, args, kwargs)
|
||||
self.clock = None
|
||||
|
||||
def startService(self):
|
||||
service.Service.startService(self)
|
||||
callable, args, kwargs = self.call
|
||||
# we have to make a new LoopingCall each time we're started, because
|
||||
# an active LoopingCall remains active when serialized. If
|
||||
# LoopingCall were a _VolatileDataService, we wouldn't need to do
|
||||
# this.
|
||||
self._loop = task.LoopingCall(callable, *args, **kwargs)
|
||||
self._loop.clock = _maybeGlobalReactor(self.clock)
|
||||
self._loopFinished = self._loop.start(self.step, now=True)
|
||||
self._loopFinished.addErrback(self._failed)
|
||||
|
||||
def _failed(self, why):
|
||||
# make a note that the LoopingCall is no longer looping, so we don't
|
||||
# try to shut it down a second time in stopService. I think this
|
||||
# should be in LoopingCall. -warner
|
||||
self._loop.running = False
|
||||
log.err(why)
|
||||
|
||||
def stopService(self):
|
||||
"""
|
||||
Stop the service.
|
||||
|
||||
@rtype: L{Deferred<defer.Deferred>}
|
||||
@return: a L{Deferred<defer.Deferred>} which is fired when the
|
||||
currently running call (if any) is finished.
|
||||
"""
|
||||
if self._loop.running:
|
||||
self._loop.stop()
|
||||
self._loopFinished.addCallback(lambda _:
|
||||
service.Service.stopService(self))
|
||||
return self._loopFinished
|
||||
|
||||
|
||||
|
||||
class CooperatorService(service.Service):
|
||||
"""
|
||||
Simple L{service.IService} which starts and stops a L{twisted.internet.task.Cooperator}.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.coop = task.Cooperator(started=False)
|
||||
|
||||
|
||||
def coiterate(self, iterator):
|
||||
return self.coop.coiterate(iterator)
|
||||
|
||||
|
||||
def startService(self):
|
||||
self.coop.start()
|
||||
|
||||
|
||||
def stopService(self):
|
||||
self.coop.stop()
|
||||
|
||||
|
||||
|
||||
class StreamServerEndpointService(service.Service, object):
|
||||
"""
|
||||
A L{StreamServerEndpointService} is an L{IService} which runs a server on a
|
||||
listening port described by an L{IStreamServerEndpoint
|
||||
<twisted.internet.interfaces.IStreamServerEndpoint>}.
|
||||
|
||||
@ivar factory: A server factory which will be used to listen on the
|
||||
endpoint.
|
||||
|
||||
@ivar endpoint: An L{IStreamServerEndpoint
|
||||
<twisted.internet.interfaces.IStreamServerEndpoint>} provider
|
||||
which will be used to listen when the service starts.
|
||||
|
||||
@ivar _waitingForPort: a Deferred, if C{listen} has yet been invoked on the
|
||||
endpoint, otherwise None.
|
||||
|
||||
@ivar _raiseSynchronously: Defines error-handling behavior for the case
|
||||
where C{listen(...)} raises an exception before C{startService} or
|
||||
C{privilegedStartService} have completed.
|
||||
|
||||
@type _raiseSynchronously: C{bool}
|
||||
|
||||
@since: 10.2
|
||||
"""
|
||||
|
||||
_raiseSynchronously = None
|
||||
|
||||
def __init__(self, endpoint, factory):
|
||||
self.endpoint = endpoint
|
||||
self.factory = factory
|
||||
self._waitingForPort = None
|
||||
|
||||
|
||||
def privilegedStartService(self):
|
||||
"""
|
||||
Start listening on the endpoint.
|
||||
"""
|
||||
service.Service.privilegedStartService(self)
|
||||
self._waitingForPort = self.endpoint.listen(self.factory)
|
||||
raisedNow = []
|
||||
def handleIt(err):
|
||||
if self._raiseSynchronously:
|
||||
raisedNow.append(err)
|
||||
elif not err.check(CancelledError):
|
||||
log.err(err)
|
||||
self._waitingForPort.addErrback(handleIt)
|
||||
if raisedNow:
|
||||
raisedNow[0].raiseException()
|
||||
|
||||
|
||||
def startService(self):
|
||||
"""
|
||||
Start listening on the endpoint, unless L{privilegedStartService} got
|
||||
around to it already.
|
||||
"""
|
||||
service.Service.startService(self)
|
||||
if self._waitingForPort is None:
|
||||
self.privilegedStartService()
|
||||
|
||||
|
||||
def stopService(self):
|
||||
"""
|
||||
Stop listening on the port if it is already listening, otherwise,
|
||||
cancel the attempt to listen.
|
||||
|
||||
@return: a L{Deferred<twisted.internet.defer.Deferred>} which fires
|
||||
with C{None} when the port has stopped listening.
|
||||
"""
|
||||
self._waitingForPort.cancel()
|
||||
def stopIt(port):
|
||||
if port is not None:
|
||||
return port.stopListening()
|
||||
d = self._waitingForPort.addCallback(stopIt)
|
||||
def stop(passthrough):
|
||||
self.running = False
|
||||
return passthrough
|
||||
d.addBoth(stop)
|
||||
return d
|
||||
|
||||
|
||||
|
||||
__all__ = (['TimerService', 'CooperatorService', 'MulticastServer',
|
||||
'StreamServerEndpointService'] +
|
||||
[tran+side
|
||||
for tran in 'TCP UNIX SSL UDP UNIXDatagram'.split()
|
||||
for side in 'Server Client'.split()])
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
# -*- test-case-name: twisted.test.test_application -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Plugin-based system for enumerating available reactors and installing one of
|
||||
them.
|
||||
"""
|
||||
|
||||
from zope.interface import Interface, Attribute, implements
|
||||
|
||||
from twisted.plugin import IPlugin, getPlugins
|
||||
from twisted.python.reflect import namedAny
|
||||
|
||||
|
||||
class IReactorInstaller(Interface):
|
||||
"""
|
||||
Definition of a reactor which can probably be installed.
|
||||
"""
|
||||
shortName = Attribute("""
|
||||
A brief string giving the user-facing name of this reactor.
|
||||
""")
|
||||
|
||||
description = Attribute("""
|
||||
A longer string giving a user-facing description of this reactor.
|
||||
""")
|
||||
|
||||
def install():
|
||||
"""
|
||||
Install this reactor.
|
||||
"""
|
||||
|
||||
# TODO - A method which provides a best-guess as to whether this reactor
|
||||
# can actually be used in the execution environment.
|
||||
|
||||
|
||||
|
||||
class NoSuchReactor(KeyError):
|
||||
"""
|
||||
Raised when an attempt is made to install a reactor which cannot be found.
|
||||
"""
|
||||
|
||||
|
||||
class Reactor(object):
|
||||
"""
|
||||
@ivar moduleName: The fully-qualified Python name of the module of which
|
||||
the install callable is an attribute.
|
||||
"""
|
||||
implements(IPlugin, IReactorInstaller)
|
||||
|
||||
|
||||
def __init__(self, shortName, moduleName, description):
|
||||
self.shortName = shortName
|
||||
self.moduleName = moduleName
|
||||
self.description = description
|
||||
|
||||
|
||||
def install(self):
|
||||
namedAny(self.moduleName).install()
|
||||
|
||||
|
||||
|
||||
def getReactorTypes():
|
||||
"""
|
||||
Return an iterator of L{IReactorInstaller} plugins.
|
||||
"""
|
||||
return getPlugins(IReactorInstaller)
|
||||
|
||||
|
||||
|
||||
def installReactor(shortName):
|
||||
"""
|
||||
Install the reactor with the given C{shortName} attribute.
|
||||
|
||||
@raise NoSuchReactor: If no reactor is found with a matching C{shortName}.
|
||||
|
||||
@raise: anything that the specified reactor can raise when installed.
|
||||
"""
|
||||
for installer in getReactorTypes():
|
||||
if installer.shortName == shortName:
|
||||
installer.install()
|
||||
from twisted.internet import reactor
|
||||
return reactor
|
||||
raise NoSuchReactor(shortName)
|
||||
|
||||
|
|
@ -0,0 +1,413 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Service architecture for Twisted.
|
||||
|
||||
Services are arranged in a hierarchy. At the leafs of the hierarchy,
|
||||
the services which actually interact with the outside world are started.
|
||||
Services can be named or anonymous -- usually, they will be named if
|
||||
there is need to access them through the hierarchy (from a parent or
|
||||
a sibling).
|
||||
|
||||
Maintainer: Moshe Zadka
|
||||
"""
|
||||
|
||||
from zope.interface import implements, Interface, Attribute
|
||||
|
||||
from twisted.python.reflect import namedAny
|
||||
from twisted.python import components
|
||||
from twisted.internet import defer
|
||||
from twisted.persisted import sob
|
||||
from twisted.plugin import IPlugin
|
||||
|
||||
|
||||
class IServiceMaker(Interface):
|
||||
"""
|
||||
An object which can be used to construct services in a flexible
|
||||
way.
|
||||
|
||||
This interface should most often be implemented along with
|
||||
L{twisted.plugin.IPlugin}, and will most often be used by the
|
||||
'twistd' command.
|
||||
"""
|
||||
tapname = Attribute(
|
||||
"A short string naming this Twisted plugin, for example 'web' or "
|
||||
"'pencil'. This name will be used as the subcommand of 'twistd'.")
|
||||
|
||||
description = Attribute(
|
||||
"A brief summary of the features provided by this "
|
||||
"Twisted application plugin.")
|
||||
|
||||
options = Attribute(
|
||||
"A C{twisted.python.usage.Options} subclass defining the "
|
||||
"configuration options for this application.")
|
||||
|
||||
|
||||
def makeService(options):
|
||||
"""
|
||||
Create and return an object providing
|
||||
L{twisted.application.service.IService}.
|
||||
|
||||
@param options: A mapping (typically a C{dict} or
|
||||
L{twisted.python.usage.Options} instance) of configuration
|
||||
options to desired configuration values.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class ServiceMaker(object):
|
||||
"""
|
||||
Utility class to simplify the definition of L{IServiceMaker} plugins.
|
||||
"""
|
||||
implements(IPlugin, IServiceMaker)
|
||||
|
||||
def __init__(self, name, module, description, tapname):
|
||||
self.name = name
|
||||
self.module = module
|
||||
self.description = description
|
||||
self.tapname = tapname
|
||||
|
||||
|
||||
def options():
|
||||
def get(self):
|
||||
return namedAny(self.module).Options
|
||||
return get,
|
||||
options = property(*options())
|
||||
|
||||
|
||||
def makeService():
|
||||
def get(self):
|
||||
return namedAny(self.module).makeService
|
||||
return get,
|
||||
makeService = property(*makeService())
|
||||
|
||||
|
||||
|
||||
class IService(Interface):
|
||||
"""
|
||||
A service.
|
||||
|
||||
Run start-up and shut-down code at the appropriate times.
|
||||
|
||||
@type name: C{string}
|
||||
@ivar name: The name of the service (or None)
|
||||
@type running: C{boolean}
|
||||
@ivar running: Whether the service is running.
|
||||
"""
|
||||
|
||||
def setName(name):
|
||||
"""
|
||||
Set the name of the service.
|
||||
|
||||
@type name: C{str}
|
||||
@raise RuntimeError: Raised if the service already has a parent.
|
||||
"""
|
||||
|
||||
def setServiceParent(parent):
|
||||
"""
|
||||
Set the parent of the service. This method is responsible for setting
|
||||
the C{parent} attribute on this service (the child service).
|
||||
|
||||
@type parent: L{IServiceCollection}
|
||||
@raise RuntimeError: Raised if the service already has a parent
|
||||
or if the service has a name and the parent already has a child
|
||||
by that name.
|
||||
"""
|
||||
|
||||
def disownServiceParent():
|
||||
"""
|
||||
Use this API to remove an L{IService} from an L{IServiceCollection}.
|
||||
|
||||
This method is used symmetrically with L{setServiceParent} in that it
|
||||
sets the C{parent} attribute on the child.
|
||||
|
||||
@rtype: L{Deferred<defer.Deferred>}
|
||||
@return: a L{Deferred<defer.Deferred>} which is triggered when the
|
||||
service has finished shutting down. If shutting down is immediate,
|
||||
a value can be returned (usually, C{None}).
|
||||
"""
|
||||
|
||||
def startService():
|
||||
"""
|
||||
Start the service.
|
||||
"""
|
||||
|
||||
def stopService():
|
||||
"""
|
||||
Stop the service.
|
||||
|
||||
@rtype: L{Deferred<defer.Deferred>}
|
||||
@return: a L{Deferred<defer.Deferred>} which is triggered when the
|
||||
service has finished shutting down. If shutting down is immediate,
|
||||
a value can be returned (usually, C{None}).
|
||||
"""
|
||||
|
||||
def privilegedStartService():
|
||||
"""
|
||||
Do preparation work for starting the service.
|
||||
|
||||
Here things which should be done before changing directory,
|
||||
root or shedding privileges are done.
|
||||
"""
|
||||
|
||||
|
||||
class Service:
|
||||
"""
|
||||
Base class for services.
|
||||
|
||||
Most services should inherit from this class. It handles the
|
||||
book-keeping reponsibilities of starting and stopping, as well
|
||||
as not serializing this book-keeping information.
|
||||
"""
|
||||
|
||||
implements(IService)
|
||||
|
||||
running = 0
|
||||
name = None
|
||||
parent = None
|
||||
|
||||
def __getstate__(self):
|
||||
dict = self.__dict__.copy()
|
||||
if "running" in dict:
|
||||
del dict['running']
|
||||
return dict
|
||||
|
||||
def setName(self, name):
|
||||
if self.parent is not None:
|
||||
raise RuntimeError("cannot change name when parent exists")
|
||||
self.name = name
|
||||
|
||||
def setServiceParent(self, parent):
|
||||
if self.parent is not None:
|
||||
self.disownServiceParent()
|
||||
parent = IServiceCollection(parent, parent)
|
||||
self.parent = parent
|
||||
self.parent.addService(self)
|
||||
|
||||
def disownServiceParent(self):
|
||||
d = self.parent.removeService(self)
|
||||
self.parent = None
|
||||
return d
|
||||
|
||||
def privilegedStartService(self):
|
||||
pass
|
||||
|
||||
def startService(self):
|
||||
self.running = 1
|
||||
|
||||
def stopService(self):
|
||||
self.running = 0
|
||||
|
||||
|
||||
|
||||
class IServiceCollection(Interface):
|
||||
"""
|
||||
Collection of services.
|
||||
|
||||
Contain several services, and manage their start-up/shut-down.
|
||||
Services can be accessed by name if they have a name, and it
|
||||
is always possible to iterate over them.
|
||||
"""
|
||||
|
||||
def getServiceNamed(name):
|
||||
"""
|
||||
Get the child service with a given name.
|
||||
|
||||
@type name: C{str}
|
||||
@rtype: L{IService}
|
||||
@raise KeyError: Raised if the service has no child with the
|
||||
given name.
|
||||
"""
|
||||
|
||||
def __iter__():
|
||||
"""
|
||||
Get an iterator over all child services.
|
||||
"""
|
||||
|
||||
def addService(service):
|
||||
"""
|
||||
Add a child service.
|
||||
|
||||
Only implementations of L{IService.setServiceParent} should use this
|
||||
method.
|
||||
|
||||
@type service: L{IService}
|
||||
@raise RuntimeError: Raised if the service has a child with
|
||||
the given name.
|
||||
"""
|
||||
|
||||
def removeService(service):
|
||||
"""
|
||||
Remove a child service.
|
||||
|
||||
Only implementations of L{IService.disownServiceParent} should
|
||||
use this method.
|
||||
|
||||
@type service: L{IService}
|
||||
@raise ValueError: Raised if the given service is not a child.
|
||||
@rtype: L{Deferred<defer.Deferred>}
|
||||
@return: a L{Deferred<defer.Deferred>} which is triggered when the
|
||||
service has finished shutting down. If shutting down is immediate,
|
||||
a value can be returned (usually, C{None}).
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class MultiService(Service):
|
||||
"""
|
||||
Straightforward Service Container.
|
||||
|
||||
Hold a collection of services, and manage them in a simplistic
|
||||
way. No service will wait for another, but this object itself
|
||||
will not finish shutting down until all of its child services
|
||||
will finish.
|
||||
"""
|
||||
|
||||
implements(IServiceCollection)
|
||||
|
||||
def __init__(self):
|
||||
self.services = []
|
||||
self.namedServices = {}
|
||||
self.parent = None
|
||||
|
||||
def privilegedStartService(self):
|
||||
Service.privilegedStartService(self)
|
||||
for service in self:
|
||||
service.privilegedStartService()
|
||||
|
||||
def startService(self):
|
||||
Service.startService(self)
|
||||
for service in self:
|
||||
service.startService()
|
||||
|
||||
def stopService(self):
|
||||
Service.stopService(self)
|
||||
l = []
|
||||
services = list(self)
|
||||
services.reverse()
|
||||
for service in services:
|
||||
l.append(defer.maybeDeferred(service.stopService))
|
||||
return defer.DeferredList(l)
|
||||
|
||||
def getServiceNamed(self, name):
|
||||
return self.namedServices[name]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.services)
|
||||
|
||||
def addService(self, service):
|
||||
if service.name is not None:
|
||||
if service.name in self.namedServices:
|
||||
raise RuntimeError("cannot have two services with same name"
|
||||
" '%s'" % service.name)
|
||||
self.namedServices[service.name] = service
|
||||
self.services.append(service)
|
||||
if self.running:
|
||||
# It may be too late for that, but we will do our best
|
||||
service.privilegedStartService()
|
||||
service.startService()
|
||||
|
||||
def removeService(self, service):
|
||||
if service.name:
|
||||
del self.namedServices[service.name]
|
||||
self.services.remove(service)
|
||||
if self.running:
|
||||
# Returning this so as not to lose information from the
|
||||
# MultiService.stopService deferred.
|
||||
return service.stopService()
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
|
||||
class IProcess(Interface):
|
||||
"""
|
||||
Process running parameters.
|
||||
|
||||
Represents parameters for how processes should be run.
|
||||
"""
|
||||
processName = Attribute(
|
||||
"""
|
||||
A C{str} giving the name the process should have in ps (or C{None}
|
||||
to leave the name alone).
|
||||
""")
|
||||
|
||||
uid = Attribute(
|
||||
"""
|
||||
An C{int} giving the user id as which the process should run (or
|
||||
C{None} to leave the UID alone).
|
||||
""")
|
||||
|
||||
gid = Attribute(
|
||||
"""
|
||||
An C{int} giving the group id as which the process should run (or
|
||||
C{None} to leave the GID alone).
|
||||
""")
|
||||
|
||||
|
||||
|
||||
class Process:
|
||||
"""
|
||||
Process running parameters.
|
||||
|
||||
Sets up uid/gid in the constructor, and has a default
|
||||
of C{None} as C{processName}.
|
||||
"""
|
||||
implements(IProcess)
|
||||
processName = None
|
||||
|
||||
def __init__(self, uid=None, gid=None):
|
||||
"""
|
||||
Set uid and gid.
|
||||
|
||||
@param uid: The user ID as whom to execute the process. If
|
||||
this is C{None}, no attempt will be made to change the UID.
|
||||
|
||||
@param gid: The group ID as whom to execute the process. If
|
||||
this is C{None}, no attempt will be made to change the GID.
|
||||
"""
|
||||
self.uid = uid
|
||||
self.gid = gid
|
||||
|
||||
|
||||
def Application(name, uid=None, gid=None):
|
||||
"""
|
||||
Return a compound class.
|
||||
|
||||
Return an object supporting the L{IService}, L{IServiceCollection},
|
||||
L{IProcess} and L{sob.IPersistable} interfaces, with the given
|
||||
parameters. Always access the return value by explicit casting to
|
||||
one of the interfaces.
|
||||
"""
|
||||
ret = components.Componentized()
|
||||
for comp in (MultiService(), sob.Persistent(ret, name), Process(uid, gid)):
|
||||
ret.addComponent(comp, ignoreClass=1)
|
||||
IService(ret).setName(name)
|
||||
return ret
|
||||
|
||||
|
||||
|
||||
def loadApplication(filename, kind, passphrase=None):
|
||||
"""
|
||||
Load Application from a given file.
|
||||
|
||||
The serialization format it was saved in should be given as
|
||||
C{kind}, and is one of C{pickle}, C{source}, C{xml} or C{python}. If
|
||||
C{passphrase} is given, the application was encrypted with the
|
||||
given passphrase.
|
||||
|
||||
@type filename: C{str}
|
||||
@type kind: C{str}
|
||||
@type passphrase: C{str}
|
||||
"""
|
||||
if kind == 'python':
|
||||
application = sob.loadValueFromFile(filename, 'application', passphrase)
|
||||
else:
|
||||
application = sob.load(filename, kind, passphrase)
|
||||
return application
|
||||
|
||||
|
||||
__all__ = ['IServiceMaker', 'IService', 'Service',
|
||||
'IServiceCollection', 'MultiService',
|
||||
'IProcess', 'Process', 'Application', 'loadApplication']
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
# -*- test-case-name: twisted.test.test_strports -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Construct listening port services from a simple string description.
|
||||
|
||||
@see: L{twisted.internet.endpoints.serverFromString}
|
||||
@see: L{twisted.internet.endpoints.clientFromString}
|
||||
"""
|
||||
|
||||
import warnings
|
||||
|
||||
from twisted.internet import endpoints
|
||||
from twisted.python.deprecate import deprecatedModuleAttribute
|
||||
from twisted.python.versions import Version
|
||||
from twisted.application.internet import StreamServerEndpointService
|
||||
|
||||
|
||||
|
||||
def parse(description, factory, default='tcp'):
|
||||
"""
|
||||
This function is deprecated as of Twisted 10.2.
|
||||
|
||||
@see: L{twisted.internet.endpoints.server}
|
||||
"""
|
||||
return endpoints._parseServer(description, factory, default)
|
||||
|
||||
deprecatedModuleAttribute(
|
||||
Version("Twisted", 10, 2, 0),
|
||||
"in favor of twisted.internet.endpoints.serverFromString",
|
||||
__name__, "parse")
|
||||
|
||||
|
||||
|
||||
_DEFAULT = object()
|
||||
|
||||
def service(description, factory, default=_DEFAULT, reactor=None):
|
||||
"""
|
||||
Return the service corresponding to a description.
|
||||
|
||||
@param description: The description of the listening port, in the syntax
|
||||
described by L{twisted.internet.endpoints.server}.
|
||||
|
||||
@type description: C{str}
|
||||
|
||||
@param factory: The protocol factory which will build protocols for
|
||||
connections to this service.
|
||||
|
||||
@type factory: L{twisted.internet.interfaces.IProtocolFactory}
|
||||
|
||||
@type default: C{str} or C{None}
|
||||
|
||||
@param default: Do not use this parameter. It has been deprecated since
|
||||
Twisted 10.2.0.
|
||||
|
||||
@rtype: C{twisted.application.service.IService}
|
||||
|
||||
@return: the service corresponding to a description of a reliable
|
||||
stream server.
|
||||
|
||||
@see: L{twisted.internet.endpoints.serverFromString}
|
||||
"""
|
||||
if reactor is None:
|
||||
from twisted.internet import reactor
|
||||
if default is _DEFAULT:
|
||||
default = None
|
||||
else:
|
||||
message = "The 'default' parameter was deprecated in Twisted 10.2.0."
|
||||
if default is not None:
|
||||
message += (
|
||||
" Use qualified endpoint descriptions; for example, "
|
||||
"'tcp:%s'." % (description,))
|
||||
warnings.warn(
|
||||
message=message, category=DeprecationWarning, stacklevel=2)
|
||||
svc = StreamServerEndpointService(
|
||||
endpoints._serverFromStringLegacy(reactor, description, default),
|
||||
factory)
|
||||
svc._raiseSynchronously = True
|
||||
return svc
|
||||
|
||||
|
||||
|
||||
def listen(description, factory, default=None):
|
||||
"""Listen on a port corresponding to a description
|
||||
|
||||
@type description: C{str}
|
||||
@type factory: L{twisted.internet.interfaces.IProtocolFactory}
|
||||
@type default: C{str} or C{None}
|
||||
@rtype: C{twisted.internet.interfaces.IListeningPort}
|
||||
@return: the port corresponding to a description of a reliable
|
||||
virtual circuit server.
|
||||
|
||||
See the documentation of the C{parse} function for description
|
||||
of the semantics of the arguments.
|
||||
"""
|
||||
from twisted.internet import reactor
|
||||
name, args, kw = parse(description, factory, default)
|
||||
return getattr(reactor, 'listen'+name)(*args, **kw)
|
||||
|
||||
|
||||
|
||||
__all__ = ['parse', 'service', 'listen']
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.internet.application}.
|
||||
"""
|
||||
|
|
@ -0,0 +1,403 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for (new code in) L{twisted.application.internet}.
|
||||
"""
|
||||
|
||||
import pickle
|
||||
|
||||
from zope.interface import implements
|
||||
from zope.interface.verify import verifyClass
|
||||
|
||||
from twisted.internet.protocol import Factory
|
||||
from twisted.trial.unittest import TestCase
|
||||
from twisted.application import internet
|
||||
from twisted.application.internet import (
|
||||
StreamServerEndpointService, TimerService)
|
||||
from twisted.internet.interfaces import IStreamServerEndpoint, IListeningPort
|
||||
from twisted.internet.defer import Deferred, CancelledError
|
||||
from twisted.internet import task
|
||||
from twisted.python.failure import Failure
|
||||
|
||||
|
||||
def fakeTargetFunction():
|
||||
"""
|
||||
A fake target function for testing TimerService which does nothing.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class FakeServer(object):
|
||||
"""
|
||||
In-memory implementation of L{IStreamServerEndpoint}.
|
||||
|
||||
@ivar result: The L{Deferred} resulting from the call to C{listen}, after
|
||||
C{listen} has been called.
|
||||
|
||||
@ivar factory: The factory passed to C{listen}.
|
||||
|
||||
@ivar cancelException: The exception to errback C{self.result} when it is
|
||||
cancelled.
|
||||
|
||||
@ivar port: The L{IListeningPort} which C{listen}'s L{Deferred} will fire
|
||||
with.
|
||||
|
||||
@ivar listenAttempts: The number of times C{listen} has been invoked.
|
||||
|
||||
@ivar failImmediately: If set, the exception to fail the L{Deferred}
|
||||
returned from C{listen} before it is returned.
|
||||
"""
|
||||
|
||||
implements(IStreamServerEndpoint)
|
||||
|
||||
result = None
|
||||
factory = None
|
||||
failImmediately = None
|
||||
cancelException = CancelledError()
|
||||
listenAttempts = 0
|
||||
|
||||
def __init__(self):
|
||||
self.port = FakePort()
|
||||
|
||||
|
||||
def listen(self, factory):
|
||||
"""
|
||||
Return a Deferred and store it for future use. (Implementation of
|
||||
L{IStreamServerEndpoint}).
|
||||
"""
|
||||
self.listenAttempts += 1
|
||||
self.factory = factory
|
||||
self.result = Deferred(
|
||||
canceller=lambda d: d.errback(self.cancelException))
|
||||
if self.failImmediately is not None:
|
||||
self.result.errback(self.failImmediately)
|
||||
return self.result
|
||||
|
||||
|
||||
def startedListening(self):
|
||||
"""
|
||||
Test code should invoke this method after causing C{listen} to be
|
||||
invoked in order to fire the L{Deferred} previously returned from
|
||||
C{listen}.
|
||||
"""
|
||||
self.result.callback(self.port)
|
||||
|
||||
|
||||
def stoppedListening(self):
|
||||
"""
|
||||
Test code should invoke this method after causing C{stopListening} to
|
||||
be invoked on the port fired from the L{Deferred} returned from
|
||||
C{listen} in order to cause the L{Deferred} returned from
|
||||
C{stopListening} to fire.
|
||||
"""
|
||||
self.port.deferred.callback(None)
|
||||
|
||||
verifyClass(IStreamServerEndpoint, FakeServer)
|
||||
|
||||
|
||||
|
||||
class FakePort(object):
|
||||
"""
|
||||
Fake L{IListeningPort} implementation.
|
||||
|
||||
@ivar deferred: The L{Deferred} returned by C{stopListening}.
|
||||
"""
|
||||
|
||||
implements(IListeningPort)
|
||||
|
||||
deferred = None
|
||||
|
||||
def stopListening(self):
|
||||
self.deferred = Deferred()
|
||||
return self.deferred
|
||||
|
||||
verifyClass(IStreamServerEndpoint, FakeServer)
|
||||
|
||||
|
||||
|
||||
class TestEndpointService(TestCase):
|
||||
"""
|
||||
Tests for L{twisted.application.internet}.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Construct a stub server, a stub factory, and a
|
||||
L{StreamServerEndpointService} to test.
|
||||
"""
|
||||
self.fakeServer = FakeServer()
|
||||
self.factory = Factory()
|
||||
self.svc = StreamServerEndpointService(self.fakeServer, self.factory)
|
||||
|
||||
|
||||
def test_privilegedStartService(self):
|
||||
"""
|
||||
L{StreamServerEndpointService.privilegedStartService} calls its
|
||||
endpoint's C{listen} method with its factory.
|
||||
"""
|
||||
self.svc.privilegedStartService()
|
||||
self.assertIdentical(self.factory, self.fakeServer.factory)
|
||||
|
||||
|
||||
def test_synchronousRaiseRaisesSynchronously(self, thunk=None):
|
||||
"""
|
||||
L{StreamServerEndpointService.startService} should raise synchronously
|
||||
if the L{Deferred} returned by its wrapped
|
||||
L{IStreamServerEndpoint.listen} has already fired with an errback and
|
||||
the L{StreamServerEndpointService}'s C{_raiseSynchronously} flag has
|
||||
been set. This feature is necessary to preserve compatibility with old
|
||||
behavior of L{twisted.internet.strports.service}, which is to return a
|
||||
service which synchronously raises an exception from C{startService}
|
||||
(so that, among other things, twistd will not start running). However,
|
||||
since L{IStreamServerEndpoint.listen} may fail asynchronously, it is
|
||||
a bad idea to rely on this behavior.
|
||||
"""
|
||||
self.fakeServer.failImmediately = ZeroDivisionError()
|
||||
self.svc._raiseSynchronously = True
|
||||
self.assertRaises(ZeroDivisionError, thunk or self.svc.startService)
|
||||
|
||||
|
||||
def test_synchronousRaisePrivileged(self):
|
||||
"""
|
||||
L{StreamServerEndpointService.privilegedStartService} should behave the
|
||||
same as C{startService} with respect to
|
||||
L{TestEndpointService.test_synchronousRaiseRaisesSynchronously}.
|
||||
"""
|
||||
self.test_synchronousRaiseRaisesSynchronously(
|
||||
self.svc.privilegedStartService)
|
||||
|
||||
|
||||
def test_failReportsError(self):
|
||||
"""
|
||||
L{StreamServerEndpointService.startService} and
|
||||
L{StreamServerEndpointService.privilegedStartService} should both log
|
||||
an exception when the L{Deferred} returned from their wrapped
|
||||
L{IStreamServerEndpoint.listen} fails.
|
||||
"""
|
||||
self.svc.startService()
|
||||
self.fakeServer.result.errback(ZeroDivisionError())
|
||||
logged = self.flushLoggedErrors(ZeroDivisionError)
|
||||
self.assertEqual(len(logged), 1)
|
||||
|
||||
|
||||
def test_synchronousFailReportsError(self):
|
||||
"""
|
||||
Without the C{_raiseSynchronously} compatibility flag, failing
|
||||
immediately has the same behavior as failing later; it logs the error.
|
||||
"""
|
||||
self.fakeServer.failImmediately = ZeroDivisionError()
|
||||
self.svc.startService()
|
||||
logged = self.flushLoggedErrors(ZeroDivisionError)
|
||||
self.assertEqual(len(logged), 1)
|
||||
|
||||
|
||||
def test_startServiceUnstarted(self):
|
||||
"""
|
||||
L{StreamServerEndpointService.startService} sets the C{running} flag,
|
||||
and calls its endpoint's C{listen} method with its factory, if it
|
||||
has not yet been started.
|
||||
"""
|
||||
self.svc.startService()
|
||||
self.assertIdentical(self.factory, self.fakeServer.factory)
|
||||
self.assertEqual(self.svc.running, True)
|
||||
|
||||
|
||||
def test_startServiceStarted(self):
|
||||
"""
|
||||
L{StreamServerEndpointService.startService} sets the C{running} flag,
|
||||
but nothing else, if the service has already been started.
|
||||
"""
|
||||
self.test_privilegedStartService()
|
||||
self.svc.startService()
|
||||
self.assertEqual(self.fakeServer.listenAttempts, 1)
|
||||
self.assertEqual(self.svc.running, True)
|
||||
|
||||
|
||||
def test_stopService(self):
|
||||
"""
|
||||
L{StreamServerEndpointService.stopService} calls C{stopListening} on
|
||||
the L{IListeningPort} returned from its endpoint, returns the
|
||||
C{Deferred} from stopService, and sets C{running} to C{False}.
|
||||
"""
|
||||
self.svc.privilegedStartService()
|
||||
self.fakeServer.startedListening()
|
||||
# Ensure running gets set to true
|
||||
self.svc.startService()
|
||||
result = self.svc.stopService()
|
||||
l = []
|
||||
result.addCallback(l.append)
|
||||
self.assertEqual(len(l), 0)
|
||||
self.fakeServer.stoppedListening()
|
||||
self.assertEqual(len(l), 1)
|
||||
self.assertFalse(self.svc.running)
|
||||
|
||||
|
||||
def test_stopServiceBeforeStartFinished(self):
|
||||
"""
|
||||
L{StreamServerEndpointService.stopService} cancels the L{Deferred}
|
||||
returned by C{listen} if it has not yet fired. No error will be logged
|
||||
about the cancellation of the listen attempt.
|
||||
"""
|
||||
self.svc.privilegedStartService()
|
||||
result = self.svc.stopService()
|
||||
l = []
|
||||
result.addBoth(l.append)
|
||||
self.assertEqual(l, [None])
|
||||
self.assertEqual(self.flushLoggedErrors(CancelledError), [])
|
||||
|
||||
|
||||
def test_stopServiceCancelStartError(self):
|
||||
"""
|
||||
L{StreamServerEndpointService.stopService} cancels the L{Deferred}
|
||||
returned by C{listen} if it has not fired yet. An error will be logged
|
||||
if the resulting exception is not L{CancelledError}.
|
||||
"""
|
||||
self.fakeServer.cancelException = ZeroDivisionError()
|
||||
self.svc.privilegedStartService()
|
||||
result = self.svc.stopService()
|
||||
l = []
|
||||
result.addCallback(l.append)
|
||||
self.assertEqual(l, [None])
|
||||
stoppingErrors = self.flushLoggedErrors(ZeroDivisionError)
|
||||
self.assertEqual(len(stoppingErrors), 1)
|
||||
|
||||
|
||||
|
||||
class TestTimerService(TestCase):
|
||||
"""
|
||||
Tests for L{twisted.application.internet.TimerService}.
|
||||
|
||||
@type timer: L{TimerService}
|
||||
@ivar timer: service to test
|
||||
|
||||
@type clock: L{task.Clock}
|
||||
@ivar clock: source of time
|
||||
|
||||
@type deferred: L{Deferred}
|
||||
@ivar deferred: deferred returned by L{TestTimerService.call}.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.timer = TimerService(2, self.call)
|
||||
self.clock = self.timer.clock = task.Clock()
|
||||
self.deferred = Deferred()
|
||||
|
||||
|
||||
def call(self):
|
||||
"""
|
||||
Function called by L{TimerService} being tested.
|
||||
|
||||
@returns: C{self.deferred}
|
||||
@rtype: L{Deferred}
|
||||
"""
|
||||
return self.deferred
|
||||
|
||||
|
||||
def test_startService(self):
|
||||
"""
|
||||
When L{TimerService.startService} is called, it marks itself
|
||||
as running, creates a L{task.LoopingCall} and starts it.
|
||||
"""
|
||||
self.timer.startService()
|
||||
self.assertTrue(self.timer.running, "Service is started")
|
||||
self.assertIsInstance(self.timer._loop, task.LoopingCall)
|
||||
self.assertIdentical(self.clock, self.timer._loop.clock)
|
||||
self.assertTrue(self.timer._loop.running, "LoopingCall is started")
|
||||
|
||||
|
||||
def test_startServiceRunsCallImmediately(self):
|
||||
"""
|
||||
When L{TimerService.startService} is called, it calls the function
|
||||
immediately.
|
||||
"""
|
||||
result = []
|
||||
self.timer.call = (result.append, (None,), {})
|
||||
self.timer.startService()
|
||||
self.assertEqual([None], result)
|
||||
|
||||
|
||||
def test_startServiceUsesGlobalReactor(self):
|
||||
"""
|
||||
L{TimerService.startService} uses L{internet._maybeGlobalReactor} to
|
||||
choose the reactor to pass to L{task.LoopingCall}
|
||||
uses the global reactor.
|
||||
"""
|
||||
otherClock = task.Clock()
|
||||
def getOtherClock(maybeReactor):
|
||||
return otherClock
|
||||
self.patch(internet, "_maybeGlobalReactor", getOtherClock)
|
||||
self.timer.startService()
|
||||
self.assertIdentical(otherClock, self.timer._loop.clock)
|
||||
|
||||
|
||||
def test_stopServiceWaits(self):
|
||||
"""
|
||||
When L{TimerService.stopService} is called while a call is in progress.
|
||||
the L{Deferred} returned doesn't fire until after the call finishes.
|
||||
"""
|
||||
self.timer.startService()
|
||||
d = self.timer.stopService()
|
||||
self.assertNoResult(d)
|
||||
self.assertEqual(True, self.timer.running)
|
||||
self.deferred.callback(object())
|
||||
self.assertIdentical(self.successResultOf(d), None)
|
||||
|
||||
|
||||
def test_stopServiceImmediately(self):
|
||||
"""
|
||||
When L{TimerService.stopService} is called while a call isn't in progress.
|
||||
the L{Deferred} returned has already been fired.
|
||||
"""
|
||||
self.timer.startService()
|
||||
self.deferred.callback(object())
|
||||
d = self.timer.stopService()
|
||||
self.assertIdentical(self.successResultOf(d), None)
|
||||
|
||||
|
||||
def test_failedCallLogsError(self):
|
||||
"""
|
||||
When function passed to L{TimerService} returns a deferred that errbacks,
|
||||
the exception is logged, and L{TimerService.stopService} doesn't raise an error.
|
||||
"""
|
||||
self.timer.startService()
|
||||
self.deferred.errback(Failure(ZeroDivisionError()))
|
||||
errors = self.flushLoggedErrors(ZeroDivisionError)
|
||||
self.assertEqual(1, len(errors))
|
||||
d = self.timer.stopService()
|
||||
self.assertIdentical(self.successResultOf(d), None)
|
||||
|
||||
|
||||
def test_pickleTimerServiceNotPickleLoop(self):
|
||||
"""
|
||||
When pickling L{internet.TimerService}, it won't pickle
|
||||
L{internet.TimerService._loop}.
|
||||
"""
|
||||
# We need a pickleable callable to test pickling TimerService. So we
|
||||
# can't use self.timer
|
||||
timer = TimerService(1, fakeTargetFunction)
|
||||
timer.startService()
|
||||
dumpedTimer = pickle.dumps(timer)
|
||||
timer.stopService()
|
||||
loadedTimer = pickle.loads(dumpedTimer)
|
||||
nothing = object()
|
||||
value = getattr(loadedTimer, "_loop", nothing)
|
||||
self.assertIdentical(nothing, value)
|
||||
|
||||
|
||||
def test_pickleTimerServiceNotPickleLoopFinished(self):
|
||||
"""
|
||||
When pickling L{internet.TimerService}, it won't pickle
|
||||
L{internet.TimerService._loopFinished}.
|
||||
"""
|
||||
# We need a pickleable callable to test pickling TimerService. So we
|
||||
# can't use self.timer
|
||||
timer = TimerService(1, fakeTargetFunction)
|
||||
timer.startService()
|
||||
dumpedTimer = pickle.dumps(timer)
|
||||
timer.stopService()
|
||||
loadedTimer = pickle.loads(dumpedTimer)
|
||||
nothing = object()
|
||||
value = getattr(loadedTimer, "_loopFinished", nothing)
|
||||
self.assertIdentical(nothing, value)
|
||||
Loading…
Add table
Add a link
Reference in a new issue