~ubuntu-branches/debian/jessie/cherrypy3/jessie

« back to all changes in this revision

Viewing changes to cherrypy/process/plugins.py

  • Committer: Package Import Robot
  • Author(s): Gustavo Noronha Silva, JCF Ploemen, Stéphane Graber, Gustavo Noronha
  • Date: 2012-01-06 10:13:27 UTC
  • mfrom: (1.1.4) (7.1.2 sid)
  • Revision ID: package-import@ubuntu.com-20120106101327-smxnhguqs14ubl7e
Tags: 3.2.2-1
[ JCF Ploemen ]
* New upstream release (Closes: #571196).
* Bumped Standards-Version to 3.8.4 (no changes needed).
* Removing patch 02: no longer needed, incorporated upstream.
* Updating patch 00 to match release.
* Install cherryd man page via debian/manpages.
* debian/copyright:
  + Added notice for cherrypy/lib/httpauth.py.
  + Fixed years.
* debian/watch:
  + Don't hit on the -py3 release by blocking '-' from the version.
  + Mangle upstream version, inserting a tilde for beta/rc.

[ Stéphane Graber <stgraber@ubuntu.com> ]
 * Convert from python-support to dh_python2 (#654375)
  - debian/pyversions: Removed (no longer needed)
  - debian/rules
   + Replace call to dh_pysupport by dh_python2
   + Add --with=python2 to all dh calls
  - debian/control
   + Drop build-depends on python-support
   + Bump build-depends on python-all to >= 2.6.6-3~
   + Replace XS-Python-Version by X-Python-Version
   + Remove XB-Python-Version from binary package

[ Gustavo Noronha ]
* debian/control, debian/rules, debian/manpages:
 - use help2man to generate a manpage for cherryd at build time, since
  one is no longer shipped along with the source code
* debian/control:
- add python-nose to Build-Depends, since it's used during the
  documentation build for cross-reference generation

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
 
3
3
import os
4
4
import re
5
 
try:
6
 
    set
7
 
except NameError:
8
 
    from sets import Set as set
9
5
import signal as _signal
10
6
import sys
11
7
import time
12
8
import threading
13
9
 
 
10
from cherrypy._cpcompat import basestring, get_daemon, get_thread_ident, ntob, set
 
11
 
 
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.
 
17
#
 
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.
 
24
# See ticket #917.
 
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()
 
29
 
14
30
 
15
31
class SimplePlugin(object):
16
32
    """Plugin base class which auto-subscribes methods for known channels."""
17
33
    
 
34
    bus = None
 
35
    """A :class:`Bus <cherrypy.process.wspbus.Bus>`, usually cherrypy.engine."""
 
36
    
18
37
    def __init__(self, bus):
19
38
        self.bus = bus
20
39
    
39
58
class SignalHandler(object):
40
59
    """Register bus channels (and listeners) for system signals.
41
60
    
42
 
    By default, instantiating this object subscribes the following signals
43
 
    and listeners:
44
 
    
45
 
        TERM: bus.exit
46
 
        HUP : bus.restart
47
 
        USR1: bus.graceful
 
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::
 
64
    
 
65
        handlers = {'SIGTERM': self.bus.exit,
 
66
                    'SIGHUP': self.handle_SIGHUP,
 
67
                    'SIGUSR1': self.bus.graceful,
 
68
                   }
 
69
    
 
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.
 
76
    
 
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.
48
80
    """
49
81
    
50
 
    # Map from signal numbers to names
 
82
    handlers = {}
 
83
    """A map from signal names (e.g. 'SIGTERM') to handlers (e.g. bus.exit)."""
 
84
    
51
85
    signals = {}
 
86
    """A map from signal numbers to names."""
 
87
    
52
88
    for k, v in vars(_signal).items():
53
89
        if k.startswith('SIG') and not k.startswith('SIG_'):
54
90
            signals[v] = k
61
97
                         'SIGHUP': self.handle_SIGHUP,
62
98
                         'SIGUSR1': self.bus.graceful,
63
99
                         }
64
 
        
 
100
 
 
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
 
107
 
65
108
        self._previous_handlers = {}
66
109
    
 
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')
 
113
        self.bus.exit()
 
114
        
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():
69
118
            try:
70
119
                self.set_handler(sig, func)
71
120
            except ValueError:
72
121
                pass
73
122
    
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]
77
127
            
78
128
            if handler is None:
126
176
        self.bus.publish(signame)
127
177
    
128
178
    def handle_SIGHUP(self):
 
179
        """Restart if daemonized, else exit."""
129
180
        if os.isatty(sys.stdin.fileno()):
130
181
            # not daemonized (may be foreground or background)
131
182
            self.bus.log("SIGHUP caught but not daemonized. Exiting.")
165
216
            elif isinstance(val, basestring):
166
217
                val = pwd.getpwnam(val)[2]
167
218
        self._uid = val
168
 
    uid = property(_get_uid, _set_uid, doc="The uid under which to run.")
 
219
    uid = property(_get_uid, _set_uid,
 
220
        doc="The uid under which to run. Availability: Unix.")
169
221
    
170
222
    def _get_gid(self):
171
223
        return self._gid
178
230
            elif isinstance(val, basestring):
179
231
                val = grp.getgrnam(val)[2]
180
232
        self._gid = val
181
 
    gid = property(_get_gid, _set_gid, doc="The gid under which to run.")
 
233
    gid = property(_get_gid, _set_gid,
 
234
        doc="The gid under which to run. Availability: Unix.")
182
235
    
183
236
    def _get_umask(self):
184
237
        return self._umask
191
244
                             level=30)
192
245
                val = None
193
246
        self._umask = val
194
 
    umask = property(_get_umask, _set_umask, doc="The umask under which to run.")
 
247
    umask = property(_get_umask, _set_umask,
 
248
        doc="""The default permission mode for newly created files and directories.
 
249
        
 
250
        Usually expressed in octal format, for example, ``0644``.
 
251
        Availability: Unix, Windows.
 
252
        """)
195
253
    
196
254
    def start(self):
197
255
        # uid/gid
216
274
                self.bus.log('Started as uid: %r gid: %r' % current_ids())
217
275
                if self.gid is not None:
218
276
                    os.setgid(self.gid)
 
277
                    os.setgroups([])
219
278
                if self.uid is not None:
220
279
                    os.setuid(self.uid)
221
280
                self.bus.log('Running as uid: %r gid: %r' % current_ids())
242
301
class Daemonizer(SimplePlugin):
243
302
    """Daemonize the running script.
244
303
    
245
 
    Use this with a Web Site Process Bus via:
246
 
        
 
304
    Use this with a Web Site Process Bus via::
 
305
    
247
306
        Daemonizer(bus).subscribe()
248
307
    
249
308
    When this component finishes, the process is completely decoupled from
296
355
                # This is the first parent. Exit, now that we've forked.
297
356
                self.bus.log('Forking once.')
298
357
                os._exit(0)
299
 
        except OSError, exc:
 
358
        except OSError:
300
359
            # Python raises OSError rather than returning negative numbers.
 
360
            exc = sys.exc_info()[1]
301
361
            sys.exit("%s: fork #1 failed: (%d) %s\n"
302
362
                     % (sys.argv[0], exc.errno, exc.strerror))
303
363
        
309
369
            if pid > 0:
310
370
                self.bus.log('Forking twice.')
311
371
                os._exit(0) # Exit second parent
312
 
        except OSError, exc:
 
372
        except OSError:
 
373
            exc = sys.exc_info()[1]
313
374
            sys.exit("%s: fork #2 failed: (%d) %s\n"
314
375
                     % (sys.argv[0], exc.errno, exc.strerror))
315
376
        
318
379
        
319
380
        si = open(self.stdin, "r")
320
381
        so = open(self.stdout, "a+")
321
 
        se = open(self.stderr, "a+", 0)
 
382
        se = open(self.stderr, "a+")
322
383
 
323
384
        # os.dup2(fd, fd2) will close fd2 if necessary,
324
385
        # so we don't explicitly close stdin/out/err.
345
406
        if self.finalized:
346
407
            self.bus.log('PID %r already written to %r.' % (pid, self.pidfile))
347
408
        else:
348
 
            open(self.pidfile, "wb").write(str(pid))
 
409
            open(self.pidfile, "wb").write(ntob("%s" % pid, 'utf8'))
349
410
            self.bus.log('PID %r written to %r.' % (pid, self.pidfile))
350
411
            self.finalized = True
351
412
    start.priority = 70
361
422
 
362
423
 
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.
 
426
    
 
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 
 
430
    """
365
431
    
366
432
    def run(self):
367
433
        while True:
368
434
            self.finished.wait(self.interval)
369
435
            if self.finished.isSet():
370
436
                return
371
 
            self.function(*self.args, **self.kwargs)
 
437
            try:
 
438
                self.function(*self.args, **self.kwargs)
 
439
            except Exception:
 
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.
 
443
                raise
 
444
 
 
445
 
 
446
class BackgroundTask(threading.Thread):
 
447
    """A subclass of threading.Thread whose run() method repeats.
 
448
    
 
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.
 
454
    """
 
455
    
 
456
    def __init__(self, interval, function, args=[], kwargs={}, bus=None):
 
457
        threading.Thread.__init__(self)
 
458
        self.interval = interval
 
459
        self.function = function
 
460
        self.args = args
 
461
        self.kwargs = kwargs
 
462
        self.running = False
 
463
        self.bus = bus
 
464
    
 
465
    def cancel(self):
 
466
        self.running = False
 
467
    
 
468
    def run(self):
 
469
        self.running = True
 
470
        while self.running:
 
471
            time.sleep(self.interval)
 
472
            if not self.running:
 
473
                return
 
474
            try:
 
475
                self.function(*self.args, **self.kwargs)
 
476
            except Exception:
 
477
                if self.bus:
 
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.
 
481
                raise
 
482
    
 
483
    def _set_daemon(self):
 
484
        return True
372
485
 
373
486
 
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."""
376
489
    
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.
380
 
    """
 
490
    callback = None
 
491
    """The function to call at intervals."""
381
492
    
382
493
    frequency = 60
383
 
    
384
 
    def __init__(self, bus, callback, frequency=60):
 
494
    """The time in seconds between callback runs."""
 
495
    
 
496
    thread = None
 
497
    """A :class:`BackgroundTask<cherrypy.process.plugins.BackgroundTask>` thread."""
 
498
    
 
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
 
504
        self.name = name
389
505
    
390
506
    def start(self):
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,
 
512
                                             bus = self.bus)
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
402
519
    
403
520
    def stop(self):
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__)
407
524
        else:
408
525
            if self.thread is not threading.currentThread():
409
526
                name = self.thread.getName()
410
527
                self.thread.cancel()
411
 
                self.thread.join()
 
528
                if not get_daemon(self.thread):
 
529
                    self.bus.log("Joining %r" % name)
 
530
                    self.thread.join()
412
531
                self.bus.log("Stopped thread %r." % name)
413
532
            self.thread = None
414
533
    
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."""
417
536
        self.stop()
418
537
        self.start()
419
538
 
420
539
 
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.
 
542
    
 
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``::
 
547
    
 
548
        cherrypy.engine.autoreload.files.add(myFile)
 
549
    
 
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
 
552
    cherrypy itself::
 
553
    
 
554
        cherrypy.engine.autoreload.match = r'^(?!cherrypy).+'
 
555
    
 
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.
 
559
    """
 
560
    
 
561
    files = None
 
562
    """The set of files to poll for modifications."""
423
563
    
424
564
    frequency = 1
 
565
    """The interval in seconds at which to poll for modified files."""
 
566
    
425
567
    match = '.*'
 
568
    """A regular expression by which to match filenames."""
426
569
    
427
570
    def __init__(self, bus, frequency=1, match='.*'):
428
571
        self.mtimes = {}
431
574
        Monitor.__init__(self, bus, self.run, frequency)
432
575
    
433
576
    def start(self):
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:
436
579
            self.mtimes = {}
437
580
        Monitor.start(self)
438
581
    start.priority = 70 
439
582
    
440
 
    def run(self):
441
 
        """Reload the process if registered files have been modified."""
442
 
        sysfiles = set()
 
583
    def sysfiles(self):
 
584
        """Return a Set of sys.modules filenames to monitor."""
 
585
        files = set()
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)
449
 
                sysfiles.add(k)
450
 
        
451
 
        for filename in sysfiles | self.files:
 
588
                if hasattr(m, '__loader__') and hasattr(m.__loader__, 'archive'):
 
589
                    f = m.__loader__.archive
 
590
                else:
 
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))
 
595
                files.add(f)
 
596
        return files
 
597
    
 
598
    def run(self):
 
599
        """Reload the process if registered files have been modified."""
 
600
        for filename in self.sysfiles() | self.files:
452
601
            if filename:
453
602
                if filename.endswith('.pyc'):
454
603
                    filename = filename[:-1]
493
642
    'stop_thread' listeners for you when it stops.
494
643
    """
495
644
    
 
645
    threads = None
 
646
    """A map of {thread ident: index number} pairs."""
 
647
    
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())
501
 
    
 
654
        self.bus.listeners.setdefault('stop_thread', set())
 
655
 
502
656
    def acquire_thread(self):
503
657
        """Run 'start_thread' listeners for the current thread.
504
658
        
505
659
        If the current thread has already been seen, any 'start_thread'
506
660
        listeners will not be run again.
507
661
        """
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
515
669
    
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)
522
676
    
523
677
    def stop(self):
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()
528
682
    graceful = stop