8
from sets import Set as set
9
5
import signal as _signal
10
from cherrypy._cpcompat import basestring, get_daemon, get_thread_ident, ntob, set
12
# _module__file__base is used by Autoreload to make
13
# absolute any filenames retrieved from sys.modules which are not
14
# already absolute paths. This is to work around Python's quirk
15
# of importing the startup script and using a relative filename
16
# for it in sys.modules.
18
# Autoreload examines sys.modules afresh every time it runs. If an application
19
# changes the current directory by executing os.chdir(), then the next time
20
# Autoreload runs, it will not be able to find any filenames which are
21
# not absolute paths, because the current directory is not the same as when the
22
# module was first imported. Autoreload will then wrongly conclude the file has
23
# "changed", and initiate the shutdown/re-exec sequence.
25
# For this workaround to have a decent probability of success, this module
26
# needs to be imported as early as possible, before the app has much chance
27
# to change the working directory.
28
_module__file__base = os.getcwd()
15
31
class SimplePlugin(object):
16
32
"""Plugin base class which auto-subscribes methods for known channels."""
35
"""A :class:`Bus <cherrypy.process.wspbus.Bus>`, usually cherrypy.engine."""
18
37
def __init__(self, bus):
39
58
class SignalHandler(object):
40
59
"""Register bus channels (and listeners) for system signals.
42
By default, instantiating this object subscribes the following signals
61
You can modify what signals your application listens for, and what it does
62
when it receives signals, by modifying :attr:`SignalHandler.handlers`,
63
a dict of {signal name: callback} pairs. The default set is::
65
handlers = {'SIGTERM': self.bus.exit,
66
'SIGHUP': self.handle_SIGHUP,
67
'SIGUSR1': self.bus.graceful,
70
The :func:`SignalHandler.handle_SIGHUP`` method calls
71
:func:`bus.restart()<cherrypy.process.wspbus.Bus.restart>`
72
if the process is daemonized, but
73
:func:`bus.exit()<cherrypy.process.wspbus.Bus.exit>`
74
if the process is attached to a TTY. This is because Unix window
75
managers tend to send SIGHUP to terminal windows when the user closes them.
77
Feel free to add signals which are not available on every platform. The
78
:class:`SignalHandler` will ignore errors raised from attempting to register
79
handlers for unknown signals.
50
# Map from signal numbers to names
83
"""A map from signal names (e.g. 'SIGTERM') to handlers (e.g. bus.exit)."""
86
"""A map from signal numbers to names."""
52
88
for k, v in vars(_signal).items():
53
89
if k.startswith('SIG') and not k.startswith('SIG_'):
61
97
'SIGHUP': self.handle_SIGHUP,
62
98
'SIGUSR1': self.bus.graceful,
101
if sys.platform[:4] == 'java':
102
del self.handlers['SIGUSR1']
103
self.handlers['SIGUSR2'] = self.bus.graceful
104
self.bus.log("SIGUSR1 cannot be set on the JVM platform. "
105
"Using SIGUSR2 instead.")
106
self.handlers['SIGINT'] = self._jython_SIGINT_handler
65
108
self._previous_handlers = {}
110
def _jython_SIGINT_handler(self, signum=None, frame=None):
111
# See http://bugs.jython.org/issue1313
112
self.bus.log('Keyboard Interrupt: shutting down bus')
67
115
def subscribe(self):
68
for sig, func in self.handlers.iteritems():
116
"""Subscribe self.handlers to signals."""
117
for sig, func in self.handlers.items():
70
119
self.set_handler(sig, func)
71
120
except ValueError:
74
123
def unsubscribe(self):
75
for signum, handler in self._previous_handlers.iteritems():
124
"""Unsubscribe self.handlers from signals."""
125
for signum, handler in self._previous_handlers.items():
76
126
signame = self.signals[signum]
78
128
if handler is None:
363
424
class PerpetualTimer(threading._Timer):
364
"""A subclass of threading._Timer whose run() method repeats."""
425
"""A responsive subclass of threading._Timer whose run() method repeats.
427
Use this timer only when you really need a very interruptible timer;
428
this checks its 'finished' condition up to 20 times a second, which can
429
results in pretty high CPU usage
368
434
self.finished.wait(self.interval)
369
435
if self.finished.isSet():
371
self.function(*self.args, **self.kwargs)
438
self.function(*self.args, **self.kwargs)
440
self.bus.log("Error in perpetual timer thread function %r." %
441
self.function, level=40, traceback=True)
442
# Quit on first error to avoid massive logs.
446
class BackgroundTask(threading.Thread):
447
"""A subclass of threading.Thread whose run() method repeats.
449
Use this class for most repeating tasks. It uses time.sleep() to wait
450
for each interval, which isn't very responsive; that is, even if you call
451
self.cancel(), you'll have to wait until the sleep() call finishes before
452
the thread stops. To compensate, it defaults to being daemonic, which means
453
it won't delay stopping the whole process.
456
def __init__(self, interval, function, args=[], kwargs={}, bus=None):
457
threading.Thread.__init__(self)
458
self.interval = interval
459
self.function = function
471
time.sleep(self.interval)
475
self.function(*self.args, **self.kwargs)
478
self.bus.log("Error in background task thread function %r."
479
% self.function, level=40, traceback=True)
480
# Quit on first error to avoid massive logs.
483
def _set_daemon(self):
374
487
class Monitor(SimplePlugin):
375
"""WSPBus listener to periodically run a callback in its own thread.
488
"""WSPBus listener to periodically run a callback in its own thread."""
377
bus: a Web Site Process Bus object.
378
callback: the function to call at intervals.
379
frequency: the time in seconds between callback runs.
491
"""The function to call at intervals."""
384
def __init__(self, bus, callback, frequency=60):
494
"""The time in seconds between callback runs."""
497
"""A :class:`BackgroundTask<cherrypy.process.plugins.BackgroundTask>` thread."""
499
def __init__(self, bus, callback, frequency=60, name=None):
385
500
SimplePlugin.__init__(self, bus)
386
501
self.callback = callback
387
502
self.frequency = frequency
388
503
self.thread = None
391
"""Start our callback in its own perpetual timer thread."""
507
"""Start our callback in its own background thread."""
392
508
if self.frequency > 0:
393
threadname = self.__class__.__name__
509
threadname = self.name or self.__class__.__name__
394
510
if self.thread is None:
395
self.thread = PerpetualTimer(self.frequency, self.callback)
511
self.thread = BackgroundTask(self.frequency, self.callback,
396
513
self.thread.setName(threadname)
397
514
self.thread.start()
398
515
self.bus.log("Started monitor thread %r." % threadname)
401
518
start.priority = 70
404
"""Stop our callback's perpetual timer thread."""
521
"""Stop our callback's background task thread."""
405
522
if self.thread is None:
406
self.bus.log("No thread running for %s." % self.__class__.__name__)
523
self.bus.log("No thread running for %s." % self.name or self.__class__.__name__)
408
525
if self.thread is not threading.currentThread():
409
526
name = self.thread.getName()
410
527
self.thread.cancel()
528
if not get_daemon(self.thread):
529
self.bus.log("Joining %r" % name)
412
531
self.bus.log("Stopped thread %r." % name)
413
532
self.thread = None
415
534
def graceful(self):
416
"""Stop the callback's perpetual timer thread and restart it."""
535
"""Stop the callback's background task thread and restart it."""
421
540
class Autoreloader(Monitor):
422
"""Monitor which re-executes the process when files change."""
541
"""Monitor which re-executes the process when files change.
543
This :ref:`plugin<plugins>` restarts the process (via :func:`os.execv`)
544
if any of the files it monitors change (or is deleted). By default, the
545
autoreloader monitors all imported modules; you can add to the
546
set by adding to ``autoreload.files``::
548
cherrypy.engine.autoreload.files.add(myFile)
550
If there are imported files you do *not* wish to monitor, you can adjust the
551
``match`` attribute, a regular expression. For example, to stop monitoring
554
cherrypy.engine.autoreload.match = r'^(?!cherrypy).+'
556
Like all :class:`Monitor<cherrypy.process.plugins.Monitor>` plugins,
557
the autoreload plugin takes a ``frequency`` argument. The default is
558
1 second; that is, the autoreloader will examine files once each second.
562
"""The set of files to poll for modifications."""
565
"""The interval in seconds at which to poll for modified files."""
568
"""A regular expression by which to match filenames."""
427
570
def __init__(self, bus, frequency=1, match='.*'):
431
574
Monitor.__init__(self, bus, self.run, frequency)
434
"""Start our own perpetual timer thread for self.run."""
577
"""Start our own background task thread for self.run."""
435
578
if self.thread is None:
437
580
Monitor.start(self)
438
581
start.priority = 70
441
"""Reload the process if registered files have been modified."""
584
"""Return a Set of sys.modules filenames to monitor."""
443
586
for k, m in sys.modules.items():
444
587
if re.match(self.match, k):
445
if hasattr(m, '__loader__'):
446
if hasattr(m.__loader__, 'archive'):
447
k = m.__loader__.archive
448
k = getattr(m, '__file__', None)
451
for filename in sysfiles | self.files:
588
if hasattr(m, '__loader__') and hasattr(m.__loader__, 'archive'):
589
f = m.__loader__.archive
591
f = getattr(m, '__file__', None)
592
if f is not None and not os.path.isabs(f):
593
# ensure absolute paths so a os.chdir() in the app doesn't break me
594
f = os.path.normpath(os.path.join(_module__file__base, f))
599
"""Reload the process if registered files have been modified."""
600
for filename in self.sysfiles() | self.files:
453
602
if filename.endswith('.pyc'):
454
603
filename = filename[:-1]
493
642
'stop_thread' listeners for you when it stops.
646
"""A map of {thread ident: index number} pairs."""
496
648
def __init__(self, bus):
497
649
self.threads = {}
498
650
SimplePlugin.__init__(self, bus)
499
651
self.bus.listeners.setdefault('acquire_thread', set())
652
self.bus.listeners.setdefault('start_thread', set())
500
653
self.bus.listeners.setdefault('release_thread', set())
654
self.bus.listeners.setdefault('stop_thread', set())
502
656
def acquire_thread(self):
503
657
"""Run 'start_thread' listeners for the current thread.
505
659
If the current thread has already been seen, any 'start_thread'
506
660
listeners will not be run again.
508
thread_ident = threading._get_ident()
662
thread_ident = get_thread_ident()
509
663
if thread_ident not in self.threads:
510
# We can't just use _get_ident as the thread ID
664
# We can't just use get_ident as the thread ID
511
665
# because some platforms reuse thread ID's.
512
666
i = len(self.threads) + 1
513
667
self.threads[thread_ident] = i
516
670
def release_thread(self):
517
671
"""Release the current thread and run 'stop_thread' listeners."""
518
thread_ident = threading._get_ident()
672
thread_ident = get_thread_ident()
519
673
i = self.threads.pop(thread_ident, None)
520
674
if i is not None:
521
675
self.bus.publish('stop_thread', i)
524
678
"""Release all threads and run all 'stop_thread' listeners."""
525
for thread_ident, i in self.threads.iteritems():
679
for thread_ident, i in self.threads.items():
526
680
self.bus.publish('stop_thread', i)
527
681
self.threads.clear()