Commits

Robert Brewer committed 9d2d4ba

Renamed restsrv -> process. Boring but practical.

  • Participants
  • Parent commits 8e7d23c

Comments (0)

Files changed (14)

File cherrypy/process/__init__.py

+"""Site container for an HTTP server.
+
+A Web Site Process Bus object is used to connect applications, servers,
+and frameworks with site-wide services such as daemonization, process
+reload, signal handling, drop privileges, PID file management, logging
+for all of these, and many more.
+
+The 'plugins' module defines a few abstract and concrete services for
+use with the bus. Some use tool-specific channels; see the documentation
+for each class.
+"""
+
+from cherrypy.restsrv.wspbus import bus
+from cherrypy.restsrv import plugins, servers

File cherrypy/process/plugins.py

+"""Site services for use with a Web Site Process Bus."""
+
+import os
+import re
+try:
+    set
+except NameError:
+    from sets import Set as set
+import signal as _signal
+import sys
+import time
+import threading
+
+
+class SimplePlugin(object):
+    """Plugin base class which auto-subscribes methods for known channels."""
+    
+    def __init__(self, bus):
+        self.bus = bus
+    
+    def subscribe(self):
+        """Register this object as a (multi-channel) listener on the bus."""
+        for channel in self.bus.listeners:
+            method = getattr(self, channel, None)
+            if method is not None:
+                self.bus.subscribe(channel, method)
+    
+    def unsubscribe(self):
+        """Unregister this object as a listener on the bus."""
+        for channel in self.bus.listeners:
+            method = getattr(self, channel, None)
+            if method is not None:
+                self.bus.unsubscribe(channel, method)
+
+
+
+class SignalHandler(object):
+    """Register bus channels (and listeners) for system signals.
+    
+    By default, instantiating this object subscribes the following signals
+    and listeners:
+    
+        TERM: bus.exit
+        HUP : bus.restart
+        USR1: bus.graceful
+    """
+    
+    # Map from signal numbers to names
+    signals = {}
+    for k, v in vars(_signal).items():
+        if k.startswith('SIG') and not k.startswith('SIG_'):
+            signals[v] = k
+    del k, v
+    
+    def __init__(self, bus):
+        self.bus = bus
+        # Set default handlers
+        self.handlers = {'SIGTERM': self.bus.exit,
+                         'SIGHUP': self.bus.restart,
+                         'SIGUSR1': self.bus.graceful,
+                         }
+    
+    def subscribe(self):
+        for sig, func in self.handlers.iteritems():
+            try:
+                self.set_handler(sig, func)
+            except ValueError:
+                pass
+    
+    def set_handler(self, signal, listener=None):
+        """Subscribe a handler for the given signal (number or name).
+        
+        If the optional 'listener' argument is provided, it will be
+        subscribed as a listener for the given signal's channel.
+        
+        If the given signal name or number is not available on the current
+        platform, ValueError is raised.
+        """
+        if isinstance(signal, basestring):
+            signum = getattr(_signal, signal, None)
+            if signum is None:
+                raise ValueError("No such signal: %r" % signal)
+            signame = signal
+        else:
+            try:
+                signame = self.signals[signal]
+            except KeyError:
+                raise ValueError("No such signal: %r" % signal)
+            signum = signal
+        
+        # Should we do something with existing signal handlers?
+        # cur = _signal.getsignal(signum)
+        _signal.signal(signum, self._handle_signal)
+        if listener is not None:
+            self.bus.log("Listening for %s." % signame)
+            self.bus.subscribe(signame, listener)
+    
+    def _handle_signal(self, signum=None, frame=None):
+        """Python signal handler (self.set_handler subscribes it for you)."""
+        signame = self.signals[signum]
+        self.bus.log("Caught signal %s." % signame)
+        self.bus.publish(signame)
+
+
+
+try:
+    import pwd, grp
+except ImportError:
+    pwd, grp = None, None
+
+
+class DropPrivileges(SimplePlugin):
+    """Drop privileges. uid/gid arguments not available on Windows.
+    
+    Special thanks to Gavin Baker: http://antonym.org/node/100.
+    """
+    
+    def __init__(self, bus, umask=None, uid=None, gid=None):
+        SimplePlugin.__init__(self, bus)
+        self.finalized = False
+        self.uid = uid
+        self.gid = gid
+        self.umask = umask
+    
+    def _get_uid(self):
+        return self._uid
+    def _set_uid(self, val):
+        if val is not None:
+            if pwd is None:
+                self.bus.log("pwd module not available; ignoring uid.")
+                val = None
+            elif isinstance(val, basestring):
+                val = pwd.getpwnam(val)[2]
+        self._uid = val
+    uid = property(_get_uid, _set_uid, doc="The uid under which to run.")
+    
+    def _get_gid(self):
+        return self._gid
+    def _set_gid(self, val):
+        if val is not None:
+            if grp is None:
+                self.bus.log("grp module not available; ignoring gid.")
+                val = None
+            elif isinstance(val, basestring):
+                val = grp.getgrnam(val)[2]
+        self._gid = val
+    gid = property(_get_gid, _set_gid, doc="The gid under which to run.")
+    
+    def _get_umask(self):
+        return self._umask
+    def _set_umask(self, val):
+        if val is not None:
+            try:
+                os.umask
+            except AttributeError:
+                self.bus.log("umask function not available; ignoring umask.")
+                val = None
+        self._umask = val
+    umask = property(_get_umask, _set_umask, doc="The umask under which to run.")
+    
+    def start(self):
+        # uid/gid
+        def current_ids():
+            """Return the current (uid, gid) if available."""
+            name, group = None, None
+            if pwd:
+                name = pwd.getpwuid(os.getuid())[0]
+            if grp:
+                group = grp.getgrgid(os.getgid())[0]
+            return name, group
+        
+        if self.finalized:
+            if not (self.uid is None and self.gid is None):
+                self.bus.log('Already running as uid: %r gid: %r' %
+                             current_ids())
+        else:
+            if self.uid is None and self.gid is None:
+                if pwd or grp:
+                    self.bus.log('uid/gid not set')
+            else:
+                self.bus.log('Started as uid: %r gid: %r' % current_ids())
+                if self.gid is not None:
+                    os.setgid(gid)
+                if self.uid is not None:
+                    os.setuid(uid)
+                self.bus.log('Running as uid: %r gid: %r' % current_ids())
+        
+        # umask
+        if self.finalized:
+            if self.umask is not None:
+                self.bus.log('umask already set to: %03o' % self.umask)
+        else:
+            if self.umask is None:
+                self.bus.log('umask not set')
+            else:
+                old_umask = os.umask(self.umask)
+                self.bus.log('umask old: %03o, new: %03o' %
+                             (old_umask, self.umask))
+        
+        self.finalized = True
+    start.priority = 75
+
+
+class Daemonizer(SimplePlugin):
+    """Daemonize the running script.
+    
+    Use this with a Web Site Process Bus via:
+        
+        Daemonizer(bus).subscribe()
+    
+    When this component finishes, the process is completely decoupled from
+    the parent environment. Please note that when this component is used,
+    the return code from the parent process will still be 0 if a startup
+    error occurs in the forked children. Errors in the initial daemonizing
+    process still return proper exit codes. Therefore, if you use this
+    plugin to daemonize, don't use the return code as an accurate indicator
+    of whether the process fully started. In fact, that return code only
+    indicates if the process succesfully finished the first fork.
+    """
+    
+    def __init__(self, bus, stdin='/dev/null', stdout='/dev/null',
+                 stderr='/dev/null'):
+        SimplePlugin.__init__(self, bus)
+        self.stdin = stdin
+        self.stdout = stdout
+        self.stderr = stderr
+        self.finalized = False
+    
+    def start(self):
+        if self.finalized:
+            self.bus.log('Already deamonized.')
+        
+        # forking has issues with threads:
+        # http://www.opengroup.org/onlinepubs/000095399/functions/fork.html
+        # "The general problem with making fork() work in a multi-threaded
+        #  world is what to do with all of the threads..."
+        # So we check for active threads:
+        if threading.activeCount() != 1:
+            self.bus.log('There are %r active threads. '
+                         'Daemonizing now may cause strange failures.' %
+                         threading.enumerate())
+        
+        # See http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
+        # (or http://www.faqs.org/faqs/unix-faq/programmer/faq/ section 1.7)
+        # and http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66012
+        
+        # Finish up with the current stdout/stderr
+        sys.stdout.flush()
+        sys.stderr.flush()
+        
+        # Do first fork.
+        try:
+            pid = os.fork()
+            if pid == 0:
+                # This is the child process. Continue.
+                pass
+            else:
+                # This is the first parent. Exit, now that we've forked.
+                self.bus.log('Forking once.')
+                os._exit(0)
+        except OSError, exc:
+            # Python raises OSError rather than returning negative numbers.
+            sys.exit("%s: fork #1 failed: (%d) %s\n"
+                     % (sys.argv[0], exc.errno, exc.strerror))
+        
+        os.setsid()
+        
+        # Do second fork
+        try:
+            pid = os.fork()
+            if pid > 0:
+                self.bus.log('Forking twice.')
+                os._exit(0) # Exit second parent
+        except OSError, exc:
+            sys.exit("%s: fork #2 failed: (%d) %s\n"
+                     % (sys.argv[0], exc.errno, exc.strerror))
+        
+        os.chdir("/")
+        os.umask(0)
+        
+        si = open(self.stdin, "r")
+        so = open(self.stdout, "a+")
+        se = open(self.stderr, "a+", 0)
+
+        # os.dup2(fd, fd2) will close fd2 if necessary,
+        # so we don't explicitly close stdin/out/err.
+        # See http://docs.python.org/lib/os-fd-ops.html
+        os.dup2(si.fileno(), sys.stdin.fileno())
+        os.dup2(so.fileno(), sys.stdout.fileno())
+        os.dup2(se.fileno(), sys.stderr.fileno())
+        
+        self.bus.log('Daemonized to PID: %s' % os.getpid())
+        self.finalized = True
+    start.priority = 65
+
+
+class PIDFile(SimplePlugin):
+    """Maintain a PID file via a WSPBus."""
+    
+    def __init__(self, bus, pidfile):
+        SimplePlugin.__init__(self, bus)
+        self.pidfile = pidfile
+        self.finalized = False
+    
+    def start(self):
+        pid = os.getpid()
+        if self.finalized:
+            self.bus.log('PID %r already written to %r.' % (pid, self.pidfile))
+        else:
+            open(self.pidfile, "wb").write(str(pid))
+            self.bus.log('PID %r written to %r.' % (pid, self.pidfile))
+            self.finalized = True
+    start.priority = 70
+    
+    def exit(self):
+        try:
+            os.remove(self.pidfile)
+            self.bus.log('PID file removed: %r.' % self.pidfile)
+        except (KeyboardInterrupt, SystemExit):
+            raise
+        except:
+            pass
+
+
+class PerpetualTimer(threading._Timer):
+    """A subclass of threading._Timer whose run() method repeats."""
+    
+    def run(self):
+        while True:
+            self.finished.wait(self.interval)
+            if self.finished.isSet():
+                return
+            self.function(*self.args, **self.kwargs)
+
+
+class Monitor(SimplePlugin):
+    """WSPBus listener to periodically run a callback in its own thread.
+    
+    bus: a Web Site Process Bus object.
+    callback: the function to call at intervals.
+    frequency: the time in seconds between callback runs.
+    """
+    
+    frequency = 60
+    
+    def __init__(self, bus, callback, frequency=60):
+        SimplePlugin.__init__(self, bus)
+        self.callback = callback
+        self.frequency = frequency
+        self.thread = None
+    
+    def start(self):
+        """Start our callback in its own perpetual timer thread."""
+        if self.frequency > 0:
+            threadname = "restsrv %s" % self.__class__.__name__
+            if self.thread is None:
+                self.thread = PerpetualTimer(self.frequency, self.callback)
+                self.thread.setName(threadname)
+                self.thread.start()
+                self.bus.log("Started thread %r." % threadname)
+            else:
+                self.bus.log("Thread %r already started." % threadname)
+    start.priority = 70
+    
+    def stop(self):
+        """Stop our callback's perpetual timer thread."""
+        if self.thread is None:
+            self.bus.log("No thread running for %s." % self.__class__.__name__)
+        else:
+            if self.thread is not threading.currentThread():
+                self.thread.cancel()
+                self.thread.join()
+                self.bus.log("Stopped thread %r." % self.thread.getName())
+            self.thread = None
+    
+    def graceful(self):
+        """Stop the callback's perpetual timer thread and restart it."""
+        self.stop()
+        self.start()
+
+
+class Autoreloader(Monitor):
+    """Monitor which re-executes the process when files change."""
+    
+    frequency = 1
+    match = '.*'
+    
+    def __init__(self, bus, frequency=1, match='.*'):
+        self.mtimes = {}
+        self.files = set()
+        self.match = match
+        Monitor.__init__(self, bus, self.run, frequency)
+    
+    def start(self):
+        """Start our own perpetual timer thread for self.run."""
+        if self.thread is None:
+            self.mtimes = {}
+        Monitor.start(self)
+    start.priority = 70 
+    
+    def run(self):
+        """Reload the process if registered files have been modified."""
+        sysfiles = set()
+        for k, m in sys.modules.items():
+            if re.match(self.match, k):
+                if hasattr(m, '__loader__'):
+                    if hasattr(m.__loader__, 'archive'):
+                        k = m.__loader__.archive
+                k = getattr(m, '__file__', None)
+                sysfiles.add(k)
+        
+        for filename in sysfiles | self.files:
+            if filename:
+                if filename.endswith('.pyc'):
+                    filename = filename[:-1]
+                
+                oldtime = self.mtimes.get(filename, 0)
+                if oldtime is None:
+                    # Module with no .py file. Skip it.
+                    continue
+                
+                try:
+                    mtime = os.stat(filename).st_mtime
+                except OSError:
+                    # Either a module with no .py file, or it's been deleted.
+                    mtime = None
+                
+                if filename not in self.mtimes:
+                    # If a module has no .py file, this will be None.
+                    self.mtimes[filename] = mtime
+                else:
+                    if mtime is None or mtime > oldtime:
+                        # The file has been deleted or modified.
+                        self.bus.log("Restarting because %s changed." % filename)
+                        self.thread.cancel()
+                        self.bus.log("Stopped thread %r." % self.thread.getName())
+                        self.bus.restart()
+                        return
+
+
+class ThreadManager(SimplePlugin):
+    """Manager for HTTP request threads.
+    
+    If you have control over thread creation and destruction, publish to
+    the 'acquire_thread' and 'release_thread' channels (for each thread).
+    This will register/unregister the current thread and publish to
+    'start_thread' and 'stop_thread' listeners in the bus as needed.
+    
+    If threads are created and destroyed by code you do not control
+    (e.g., Apache), then, at the beginning of every HTTP request,
+    publish to 'acquire_thread' only. You should not publish to
+    'release_thread' in this case, since you do not know whether
+    the thread will be re-used or not. The bus will call
+    'stop_thread' listeners for you when it stops.
+    """
+    
+    def __init__(self, bus):
+        self.threads = {}
+        SimplePlugin.__init__(self, bus)
+        self.bus.listeners.setdefault('acquire_thread', set())
+        self.bus.listeners.setdefault('release_thread', set())
+    
+    def acquire_thread(self):
+        """Run 'start_thread' listeners for the current thread.
+        
+        If the current thread has already been seen, any 'start_thread'
+        listeners will not be run again.
+        """
+        thread_ident = threading._get_ident()
+        if thread_ident not in self.threads:
+            # We can't just use _get_ident as the thread ID
+            # because some platforms reuse thread ID's.
+            i = len(self.threads) + 1
+            self.threads[thread_ident] = i
+            self.bus.publish('start_thread', i)
+    
+    def release_thread(self):
+        """Release the current thread and run 'stop_thread' listeners."""
+        thread_ident = threading._get_ident()
+        i = self.threads.pop(thread_ident, None)
+        if i is not None:
+            self.bus.publish('stop_thread', i)
+    
+    def stop(self):
+        """Release all threads and run all 'stop_thread' listeners."""
+        for thread_ident, i in self.threads.iteritems():
+            self.bus.publish('stop_thread', i)
+        self.threads.clear()
+    graceful = stop
+

File cherrypy/process/restctl.sh

+#!/bin/sh
+#
+# Control script for the restsrv daemon
+
+ERROR=0
+PYTHON=python
+RESTD=restd.py
+PIDFILE=/var/log/restsrv/restd.pid
+
+# Determine if restsrv is already running.
+RUNNING=0
+if [ -f $PIDFILE ]; then
+    PID=`cat $PIDFILE`
+    if [ "x$PID" != "x" ]; then
+        if kill -0 $PID 2>/dev/null ; then
+            # a process using PID is running
+            if ps -p $PID -o args | grep restsrv > /dev/null 2>&1; then
+                # that process is restsrv itself
+                RUNNING=1
+            fi
+        fi
+    fi
+    if ["$RUNNING" = "0"]; then
+        STATUS="restsrv (stale pid file removed) not running"
+        rm -f $PIDFILE
+    fi
+else
+    STATUS="restsrv (no pid) not running"
+fi
+
+ARGS=""
+COMMAND=""
+for ARG in $@; do
+    case $ARG in
+    start|stop|restart|graceful|status)
+        COMMAND=$ARG
+        ;;
+    *)
+        ARGS="$ARGS $ARG"
+    esac
+    fi
+done
+
+case $COMMAND in
+start)
+    echo "Starting restd"
+    $RESTD $ARGS
+    ERROR=$?
+    ;;
+stop)
+    echo "Stopping restd"
+    kill -TERM `cat $PIDFILE`
+    ERROR=$?
+    ;;
+restart)
+    echo "Restarting restd"
+    kill -HUP `cat $PIDFILE`
+    ;;
+graceful)
+    echo "Gracefully restarting restd"
+    kill -USR1 `cat $PIDFILE`
+    ;;
+status)
+    echo $STATUS
+    ;;
+*)
+    $RESTD $ARGS
+    ERROR=$?
+esac
+
+exit $ERROR

File cherrypy/process/restd.py

+"""The restsrv daemon."""
+
+import getopt
+import sys
+
+from cherrypy import restsrv
+
+
+class _Optionset(object):
+    host = '0.0.0.0'
+    port = 80
+    protocol = 'HTTP/1.1'
+    scheme = 'http'
+options = _Optionset()
+
+shortopts = []
+longopts = [
+    # someday: 'cover', 'profile', 'validate', 'conquer',
+    'host=', 'port=', '1.0', 'ssl',
+    'project=', 'help',
+    ]
+
+
+def help():
+    """Print help for restd command-line options."""
+    
+    print """
+restd. Start the restsrv daemon.
+
+Usage:
+    restd [options]
+
+Options:
+  --host=<name or IP addr>: use a host other than the default (%s).
+  --port=<int>: use a port other than the default (%s)
+  --1.0: use HTTP/1.0 servers instead of default HTTP/1.1
+  --ssl: use HTTPS instead of default HTTP
+  --project=<module name>: import a module to set up the project
+  --help: print this usage message and exit
+""" % (options.host, options.port)
+
+
+def start(opts):
+    if '--project' in opts:
+        # delay import until after daemonize has a chance to run
+        def _import():
+            __import__(opts['--project'], {}, {}, [''])
+        _import.priority = 20
+        restsrv.bus.subscribe('start', _import)
+    
+    if 'win' not in sys.platform:
+        restsrv.bus.subscribe('start', restsrv.plugins.daemonize)
+    
+    restsrv.bus.start()
+    restsrv.bus.block()
+
+
+if __name__ == '__main__':
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], shortopts, longopts)
+    except getopt.GetoptError:
+        help()
+        sys.exit(2)
+    
+    if "--help" in opts:
+        help()
+        sys.exit(0)
+    
+    start(dict(opts))

File cherrypy/process/servers.py

+"""Adapt an HTTP server."""
+
+import socket
+import threading
+import time
+
+
+class ServerAdapter(object):
+    """Adapter for an HTTP server.
+    
+    If you need to start more than one HTTP server (to serve on multiple
+    ports, or protocols, etc.), you can manually register each one and then
+    start them all with bus.start:
+    
+        s1 = ServerAdapter(bus, MyWSGIServer(host='0.0.0.0', port=80))
+        s2 = ServerAdapter(bus, another.HTTPServer(host='127.0.0.1', SSL=True))
+        s1.subscribe()
+        s2.subscribe()
+        bus.start()
+    """
+    
+    def __init__(self, bus, httpserver=None, bind_addr=None):
+        self.bus = bus
+        self.httpserver = httpserver
+        self.bind_addr = bind_addr
+        self.interrupt = None
+        self.running = False
+    
+    def subscribe(self):
+        self.bus.subscribe('start', self.start)
+        self.bus.subscribe('stop', self.stop)
+    
+    def unsubscribe(self):
+        self.bus.unsubscribe('start', self.start)
+        self.bus.unsubscribe('stop', self.stop)
+    
+    def start(self):
+        """Start the HTTP server."""
+        if isinstance(self.bind_addr, tuple):
+            host, port = self.bind_addr
+            on_what = "%s:%s" % (host, port)
+        else:
+            on_what = "socket file: %s" % self.bind_addr
+        
+        if self.running:
+            self.bus.log("Already serving on %s" % on_what)
+            return
+        
+        self.interrupt = None
+        if not self.httpserver:
+            raise ValueError("No HTTP server has been created.")
+        
+        # Start the httpserver in a new thread.
+        if isinstance(self.bind_addr, tuple):
+            wait_for_free_port(*self.bind_addr)
+        
+        t = threading.Thread(target=self._start_http_thread)
+        t.setName("HTTPServer " + t.getName())
+        t.start()
+        
+        self.wait()
+        self.running = True
+        self.bus.log("Serving on %s" % on_what)
+    start.priority = 75
+    
+    def _start_http_thread(self):
+        """HTTP servers MUST be running in new threads, so that the
+        main thread persists to receive KeyboardInterrupt's. If an
+        exception is raised in the httpserver's thread then it's
+        trapped here, and the bus (and therefore our httpserver)
+        are shut down.
+        """
+        try:
+            self.httpserver.start()
+        except KeyboardInterrupt, exc:
+            self.bus.log("<Ctrl-C> hit: shutting down HTTP server")
+            self.interrupt = exc
+            self.bus.exit()
+        except SystemExit, exc:
+            self.bus.log("SystemExit raised: shutting down HTTP server")
+            self.interrupt = exc
+            self.bus.exit()
+            raise
+        except:
+            import sys
+            self.interrupt = sys.exc_info()[1]
+            self.bus.log("Error in HTTP server: shutting down",
+                         traceback=True)
+            self.bus.exit()
+            raise
+    
+    def wait(self):
+        """Wait until the HTTP server is ready to receive requests."""
+        while not getattr(self.httpserver, "ready", False):
+            if self.interrupt:
+                raise self.interrupt
+            time.sleep(.1)
+        
+        # Wait for port to be occupied
+        if isinstance(self.bind_addr, tuple):
+            host, port = self.bind_addr
+            wait_for_occupied_port(host, port)
+    
+    def stop(self):
+        """Stop the HTTP server."""
+        if self.running:
+            # stop() MUST block until the server is *truly* stopped.
+            self.httpserver.stop()
+            # Wait for the socket to be truly freed.
+            if isinstance(self.bind_addr, tuple):
+                wait_for_free_port(*self.bind_addr)
+            self.running = False
+            self.bus.log("HTTP Server %s shut down" % self.httpserver)
+        else:
+            self.bus.log("HTTP Server %s already shut down" % self.httpserver)
+    stop.priority = 25
+    
+    def restart(self):
+        """Restart the HTTP server."""
+        self.stop()
+        self.start()
+
+
+def client_host(server_host):
+    """Return the host on which a client can connect to the given listener."""
+    if server_host == '0.0.0.0':
+        # 0.0.0.0 is INADDR_ANY, which should answer on localhost.
+        return '127.0.0.1'
+    if server_host == '::':
+        # :: is IN6ADDR_ANY, which should answer on localhost.
+        return '::1'
+    return server_host
+
+def check_port(host, port):
+    """Raise an error if the given port is not free on the given host."""
+    if not host:
+        raise ValueError("Host values of '' or None are not allowed.")
+    host = client_host(host)
+    port = int(port)
+    
+    # AF_INET or AF_INET6 socket
+    # Get the correct address family for our host (allows IPv6 addresses)
+    for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
+                                  socket.SOCK_STREAM):
+        af, socktype, proto, canonname, sa = res
+        s = None
+        try:
+            s = socket.socket(af, socktype, proto)
+            # See http://groups.google.com/group/cherrypy-users/
+            #        browse_frm/thread/bbfe5eb39c904fe0
+            s.settimeout(1.0)
+            s.connect((host, port))
+            s.close()
+            raise IOError("Port %s is in use on %s; perhaps the previous "
+                          "httpserver did not shut down properly." %
+                          (repr(port), repr(host)))
+        except socket.error:
+            if s:
+                s.close()
+
+def wait_for_free_port(host, port):
+    """Wait for the specified port to become free (drop requests)."""
+    if not host:
+        raise ValueError("Host values of '' or None are not allowed.")
+    
+    for trial in xrange(50):
+        try:
+            check_port(host, port)
+        except IOError:
+            # Give the old server thread time to free the port.
+            time.sleep(.1)
+        else:
+            return
+    
+    raise IOError("Port %r not free on %r" % (port, host))
+
+def wait_for_occupied_port(host, port):
+    """Wait for the specified port to become active (receive requests)."""
+    if not host:
+        raise ValueError("Host values of '' or None are not allowed.")
+    
+    for trial in xrange(50):
+        try:
+            check_port(host, port)
+        except IOError:
+            return
+        else:
+            time.sleep(.1)
+    
+    raise IOError("Port %r not bound on %r" % (port, host))

File cherrypy/process/win32.py

+"""Windows service for restsrv. Requires pywin32."""
+
+import os
+import thread
+import win32api
+import win32con
+import win32event
+import win32service
+import win32serviceutil
+
+from cherrypy.restsrv import wspbus, plugins
+
+
+class ConsoleCtrlHandler(plugins.SimplePlugin):
+    """A WSPBus plugin for handling Win32 console events (like Ctrl-C)."""
+    
+    def __init__(self, bus):
+        self.is_set = False
+        plugins.SimplePlugin.__init__(self, bus)
+    
+    def start(self):
+        if self.is_set:
+            return
+        
+        result = win32api.SetConsoleCtrlHandler(self.handle, 1)
+        if result == 0:
+            self.bus.log('Could not SetConsoleCtrlHandler (error %r)' %
+                         win32api.GetLastError())
+        else:
+            self.is_set = True
+    
+    def stop(self):
+        if not self.is_set:
+            return
+        
+        try:
+            result = win32api.SetConsoleCtrlHandler(self.handle, 0)
+        except ValueError:
+            # "ValueError: The object has not been registered"
+            result = 1
+        
+        if result == 0:
+            self.bus.log('Could not remove SetConsoleCtrlHandler (error %r)' %
+                         win32api.GetLastError())
+        else:
+            self.is_set = False
+    
+    def handle(self, event):
+        """Handle console control events (like Ctrl-C)."""
+        if event in (win32con.CTRL_C_EVENT, win32con.CTRL_LOGOFF_EVENT,
+                     win32con.CTRL_BREAK_EVENT, win32con.CTRL_SHUTDOWN_EVENT,
+                     win32con.CTRL_CLOSE_EVENT):
+            self.bus.log('Console event %s: shutting down bus' % event)
+            
+            # Remove self immediately so repeated Ctrl-C doesn't re-call it.
+            try:
+                self.stop()
+            except ValueError:
+                pass
+            
+            self.bus.exit()
+            # 'First to return True stops the calls'
+            return 1
+        return 0
+
+
+class Win32Bus(wspbus.Bus):
+    """A Web Site Process Bus implementation for Win32.
+    
+    Instead of using time.sleep for blocking, this bus uses native
+    win32event objects. It also responds to console events.
+    """
+    
+    def __init__(self):
+        self.events = {}
+        wspbus.Bus.__init__(self)
+    
+    def _get_state_event(self, state):
+        """Return a win32event for the given state (creating it if needed)."""
+        try:
+            return self.events[state]
+        except KeyError:
+            event = win32event.CreateEvent(None, 0, 0,
+                                           u"WSPBus %s Event (pid=%r)" %
+                                           (state.name, os.getpid()))
+            self.events[state] = event
+            return event
+    
+    def _get_state(self):
+        return self._state
+    def _set_state(self, value):
+        self._state = value
+        event = self._get_state_event(value)
+        win32event.PulseEvent(event)
+    state = property(_get_state, _set_state)
+    
+    def wait(self, state, interval=0.1):
+        """Wait for the given state, KeyboardInterrupt or SystemExit.
+        
+        Since this class uses native win32event objects, the interval
+        argument is ignored.
+        """
+        # Don't wait for an event that beat us to the punch ;)
+        if self.state != state:
+            event = self._get_state_event(state)
+            win32event.WaitForSingleObject(event, win32event.INFINITE)
+
+
+class _ControlCodes(dict):
+    """Control codes used to "signal" a service via ControlService.
+    
+    User-defined control codes are in the range 128-255. We generally use
+    the standard Python value for the Linux signal and add 128. Example:
+    
+        >>> signal.SIGUSR1
+        10
+        control_codes['graceful'] = 128 + 10
+    """
+    
+    def key_for(self, obj):
+        """For the given value, return its corresponding key."""
+        for key, val in self.iteritems():
+            if val is obj:
+                return key
+        raise ValueError("The given object could not be found: %r" % obj)
+
+control_codes = _ControlCodes({'graceful': 138})
+
+
+def signal_child(service, command):
+    if command == 'stop':
+        win32serviceutil.StopService(service)
+    elif command == 'restart':
+        win32serviceutil.RestartService(service)
+    else:
+        win32serviceutil.ControlService(service, control_codes[command])
+
+
+class PyWebService(win32serviceutil.ServiceFramework):
+    """Python Web Service."""
+    
+    _svc_name_ = "Python Web Service"
+    _svc_display_name_ = "Python Web Service"
+    _svc_deps_ = None        # sequence of service names on which this depends
+    _exe_name_ = "restsrv"
+    _exe_args_ = None        # Default to no arguments
+    
+    # Only exists on Windows 2000 or later, ignored on windows NT
+    _svc_description_ = "Python Web Service"
+    
+    def SvcDoRun(self):
+        from cherrypy import restsrv
+        restsrv.bus.start()
+        restsrv.bus.block()
+    
+    def SvcStop(self):
+        from cherrypy import restsrv
+        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
+        restsrv.bus.exit()
+    
+    def SvcOther(self, control):
+        restsrv.bus.publish(control_codes.key_for(control))
+
+
+if __name__ == '__main__':
+    win32serviceutil.HandleCommandLine(PyWebService)

File cherrypy/process/wspbus.py

+"""An implementation of the Web Site Process Bus.
+
+This module is completely standalone, depending only on the stdlib.
+
+Web Site Process Bus
+--------------------
+
+A Bus object is used to contain and manage site-wide behavior:
+daemonization, HTTP server start/stop, process reload, signal handling,
+drop privileges, PID file management, logging for all of these,
+and many more.
+
+In addition, a Bus object provides a place for each web framework
+to register code that runs in response to site-wide events (like
+process start and stop), or which controls or otherwise interacts with
+the site-wide components mentioned above. For example, a framework which
+uses file-based templates would add known template filenames to an
+autoreload component.
+
+Ideally, a Bus object will be flexible enough to be useful in a variety
+of invocation scenarios:
+
+ 1. The deployer starts a site from the command line via a framework-
+     neutral deployment script; applications from multiple frameworks
+     are mixed in a single site. Command-line arguments and configuration
+     files are used to define site-wide components such as the HTTP server,
+     WSGI component graph, autoreload behavior, signal handling, etc.
+ 2. The deployer starts a site via some other process, such as Apache;
+     applications from multiple frameworks are mixed in a single site.
+     Autoreload and signal handling (from Python at least) are disabled.
+ 3. The deployer starts a site via a framework-specific mechanism;
+     for example, when running tests, exploring tutorials, or deploying
+     single applications from a single framework. The framework controls
+     which site-wide components are enabled as it sees fit.
+
+The Bus object in this package uses topic-based publish-subscribe
+messaging to accomplish all this. A few topic channels are built in
+('start', 'stop', 'exit', and 'graceful'). Frameworks and site containers
+are free to define their own. If a message is sent to a channel that has
+not been defined or has no listeners, there is no effect.
+
+In general, there should only ever be a single Bus object per process.
+Frameworks and site containers share a single Bus object by publishing
+messages and subscribing listeners.
+
+The Bus object works as a finite state machine which models the current
+state of the process. Bus methods move it from one state to another;
+those methods then publish to subscribed listeners on the channel for
+the new state.
+
+                        O
+                        |
+                        V
+       STOPPING --> STOPPED --> EXITING -> X
+          A   A         |
+          |    \___     |
+          |        \    |
+          |         V   V
+        STARTED <-- STARTING
+
+"""
+
+import atexit
+import os
+try:
+    set
+except NameError:
+    from sets import Set as set
+import sys
+import threading
+import time
+import traceback as _traceback
+import warnings
+
+
+# Use a flag to indicate the state of the bus.
+class _StateEnum(object):
+    class State(object):
+        name = None
+        def __repr__(self):
+            return "states.%s" % self.name
+    
+    def __setattr__(self, key, value):
+        if isinstance(value, self.State):
+            value.name = key
+        object.__setattr__(self, key, value)
+states = _StateEnum()
+states.STOPPED = states.State()
+states.STARTING = states.State()
+states.STARTED = states.State()
+states.STOPPING = states.State()
+states.EXITING = states.State()
+
+
+class Bus(object):
+    """Process state-machine and messenger for HTTP site deployment.
+    
+    All listeners for a given channel are guaranteed to be called even
+    if others at the same channel fail. Each failure is logged, but
+    execution proceeds on to the next listener. The only way to stop all
+    processing from inside a listener is to raise SystemExit and stop the
+    whole server.
+    """
+    
+    states = states
+    state = states.STOPPED
+    execv = False
+    
+    def __init__(self):
+        self.execv = False
+        self.state = states.STOPPED
+        self.listeners = dict(
+            [(channel, set()) for channel
+             in ('start', 'stop', 'exit', 'graceful', 'log')])
+        self._priorities = {}
+    
+    def subscribe(self, channel, callback, priority=None):
+        """Add the given callback at the given channel (if not present)."""
+        if channel not in self.listeners:
+            self.listeners[channel] = set()
+        self.listeners[channel].add(callback)
+        
+        if priority is None:
+            priority = getattr(callback, 'priority', 50)
+        self._priorities[(channel, callback)] = priority
+    
+    def unsubscribe(self, channel, callback):
+        """Discard the given callback (if present)."""
+        listeners = self.listeners.get(channel)
+        if listeners and callback in listeners:
+            listeners.discard(callback)
+            del self._priorities[(channel, callback)]
+    
+    def publish(self, channel, *args, **kwargs):
+        """Return output of all subscribers for the given channel."""
+        if channel not in self.listeners:
+            return []
+        
+        exc = None
+        output = []
+        
+        items = [(self._priorities[(channel, listener)], listener)
+                 for listener in self.listeners[channel]]
+        items.sort()
+        for priority, listener in items:
+            try:
+                output.append(listener(*args, **kwargs))
+            except KeyboardInterrupt:
+                raise
+            except SystemExit, e:
+                # If we have previous errors ensure the exit code is non-zero
+                if exc and e.code == 0:
+                    e.code = 1
+                raise
+            except:
+                self.log("Error in %r listener %r" % (channel, listener),
+                         traceback=True)
+                exc = sys.exc_info()[1]
+        if exc:
+            raise
+        return output
+    
+    def _clean_exit(self):
+        """An atexit handler which asserts the Bus is not running."""
+        if self.state != states.EXITING:
+            warnings.warn(
+                "The main thread is exiting, but the Bus is in the %r state; "
+                "shutting it down automatically now. You must either call "
+                "bus.block() after start(), or call bus.exit() before the "
+                "main thread exits." % self.state, RuntimeWarning)
+            self.exit()
+    
+    def start(self):
+        """Start all services."""
+        atexit.register(self._clean_exit)
+        
+        self.state = states.STARTING
+        self.log('Bus STARTING')
+        try:
+            self.publish('start')
+            self.state = states.STARTED
+            self.log('Bus STARTED')
+        except (KeyboardInterrupt, SystemExit):
+            raise
+        except:
+            self.log("Shutting down due to error in start listener:\n%s" %
+                     _traceback.format_exc())
+            e_info = sys.exc_info()
+            try:
+                self.exit()
+            except:
+                # Any stop/exit errors will be logged inside publish().
+                pass
+            raise e_info[0], e_info[1], e_info[2]
+    
+    def exit(self):
+        """Stop all services and prepare to exit the process."""
+        self.stop()
+        
+        self.state = states.EXITING
+        self.log('Bus EXITING')
+        self.publish('exit')
+    
+    def restart(self):
+        """Restart the process (may close connections).
+        
+        This method does not restart the process from the calling thread;
+        instead, it stops the bus and asks the main thread to call execv.
+        """
+        self.execv = True
+        self.exit()
+    
+    def graceful(self):
+        """Advise all services to reload."""
+        self.log('Bus graceful')
+        self.publish('graceful')
+    
+    def block(self, interval=0.1):
+        """Wait for the EXITING state, KeyboardInterrupt or SystemExit."""
+        try:
+            self.wait(states.EXITING, interval=interval)
+        except (KeyboardInterrupt, IOError):
+            # The time.sleep call might raise
+            # "IOError: [Errno 4] Interrupted function call" on KBInt.
+            self.log('Keyboard Interrupt: shutting down bus')
+            self.exit()
+        except SystemExit:
+            self.log('SystemExit raised: shutting down bus')
+            self.exit()
+            raise
+        
+        # Waiting for ALL child threads to finish is necessary on OS X.
+        # See http://www.cherrypy.org/ticket/581.
+        # It's also good to let them all shut down before allowing
+        # the main thread to call atexit handlers.
+        # See http://www.cherrypy.org/ticket/751.
+        self.log("Waiting for child threads to terminate...")
+        for t in threading.enumerate():
+            if (t != threading.currentThread() and t.isAlive()
+                # Note that any dummy (external) threads are always daemonic.
+                and not t.isDaemon()):
+                t.join()
+        
+        if self.execv:
+            self._do_execv()
+    
+    def wait(self, state, interval=0.1):
+        """Wait for the given state."""
+        def _wait():
+            while self.state != state:
+                time.sleep(interval)
+        
+        # From http://psyco.sourceforge.net/psycoguide/bugs.html:
+        # "The compiled machine code does not include the regular polling
+        # done by Python, meaning that a KeyboardInterrupt will not be
+        # detected before execution comes back to the regular Python
+        # interpreter. Your program cannot be interrupted if caught
+        # into an infinite Psyco-compiled loop."
+        try:
+            sys.modules['psyco'].cannotcompile(_wait)
+        except (KeyError, AttributeError):
+            pass
+        
+        _wait()
+    
+    def _do_execv(self):
+        """Re-execute the current process.
+        
+        This must be called from the main thread, because certain platforms
+        (OS X) don't allow execv to be called in a child thread very well.
+        """
+        args = sys.argv[:]
+        self.log('Re-spawning %s' % ' '.join(args))
+        args.insert(0, sys.executable)
+        if sys.platform == 'win32':
+            args = ['"%s"' % arg for arg in args]
+        
+        os.execv(sys.executable, args)
+    
+    def stop(self):
+        """Stop all services."""
+        self.state = states.STOPPING
+        self.log('Bus STOPPING')
+        self.publish('stop')
+        self.state = states.STOPPED
+        self.log('Bus STOPPED')
+    
+    def start_with_callback(self, func, args=None, kwargs=None):
+        """Start 'func' in a new thread T, then start self (and return T)."""
+        if args is None:
+            args = ()
+        if kwargs is None:
+            kwargs = {}
+        args = (func,) + args
+        
+        def _callback(func, *a, **kw):
+            self.wait(states.STARTED)
+            func(*a, **kw)
+        t = threading.Thread(target=_callback, args=args, kwargs=kwargs)
+        t.setName('Bus Callback ' + t.getName())
+        t.start()
+        
+        self.start()
+        
+        return t
+    
+    def log(self, msg="", traceback=False):
+        """Log the given message. Append the last traceback if requested."""
+        if traceback:
+            exc = sys.exc_info()
+            msg += "\n" + "".join(_traceback.format_exception(*exc))
+        self.publish('log', msg)
+
+bus = Bus()

File cherrypy/restsrv/__init__.py

-"""Site container for an HTTP server.
-
-A Web Site Process Bus object is used to connect applications, servers,
-and frameworks with site-wide services such as daemonization, process
-reload, signal handling, drop privileges, PID file management, logging
-for all of these, and many more.
-
-The 'plugins' module defines a few abstract and concrete services for
-use with the bus. Some use tool-specific channels; see the documentation
-for each class.
-"""
-
-from cherrypy.restsrv.wspbus import bus
-from cherrypy.restsrv import plugins, servers

File cherrypy/restsrv/plugins.py

-"""Site services for use with a Web Site Process Bus."""
-
-import os
-import re
-try:
-    set
-except NameError:
-    from sets import Set as set
-import signal as _signal
-import sys
-import time
-import threading
-
-
-class SimplePlugin(object):
-    """Plugin base class which auto-subscribes methods for known channels."""
-    
-    def __init__(self, bus):
-        self.bus = bus
-    
-    def subscribe(self):
-        """Register this object as a (multi-channel) listener on the bus."""
-        for channel in self.bus.listeners:
-            method = getattr(self, channel, None)
-            if method is not None:
-                self.bus.subscribe(channel, method)
-    
-    def unsubscribe(self):
-        """Unregister this object as a listener on the bus."""
-        for channel in self.bus.listeners:
-            method = getattr(self, channel, None)
-            if method is not None:
-                self.bus.unsubscribe(channel, method)
-
-
-
-class SignalHandler(object):
-    """Register bus channels (and listeners) for system signals.
-    
-    By default, instantiating this object subscribes the following signals
-    and listeners:
-    
-        TERM: bus.exit
-        HUP : bus.restart
-        USR1: bus.graceful
-    """
-    
-    # Map from signal numbers to names
-    signals = {}
-    for k, v in vars(_signal).items():
-        if k.startswith('SIG') and not k.startswith('SIG_'):
-            signals[v] = k
-    del k, v
-    
-    def __init__(self, bus):
-        self.bus = bus
-        # Set default handlers
-        self.handlers = {'SIGTERM': self.bus.exit,
-                         'SIGHUP': self.bus.restart,
-                         'SIGUSR1': self.bus.graceful,
-                         }
-    
-    def subscribe(self):
-        for sig, func in self.handlers.iteritems():
-            try:
-                self.set_handler(sig, func)
-            except ValueError:
-                pass
-    
-    def set_handler(self, signal, listener=None):
-        """Subscribe a handler for the given signal (number or name).
-        
-        If the optional 'listener' argument is provided, it will be
-        subscribed as a listener for the given signal's channel.
-        
-        If the given signal name or number is not available on the current
-        platform, ValueError is raised.
-        """
-        if isinstance(signal, basestring):
-            signum = getattr(_signal, signal, None)
-            if signum is None:
-                raise ValueError("No such signal: %r" % signal)
-            signame = signal
-        else:
-            try:
-                signame = self.signals[signal]
-            except KeyError:
-                raise ValueError("No such signal: %r" % signal)
-            signum = signal
-        
-        # Should we do something with existing signal handlers?
-        # cur = _signal.getsignal(signum)
-        _signal.signal(signum, self._handle_signal)
-        if listener is not None:
-            self.bus.log("Listening for %s." % signame)
-            self.bus.subscribe(signame, listener)
-    
-    def _handle_signal(self, signum=None, frame=None):
-        """Python signal handler (self.set_handler subscribes it for you)."""
-        signame = self.signals[signum]
-        self.bus.log("Caught signal %s." % signame)
-        self.bus.publish(signame)
-
-
-
-try:
-    import pwd, grp
-except ImportError:
-    pwd, grp = None, None
-
-
-class DropPrivileges(SimplePlugin):
-    """Drop privileges. uid/gid arguments not available on Windows.
-    
-    Special thanks to Gavin Baker: http://antonym.org/node/100.
-    """
-    
-    def __init__(self, bus, umask=None, uid=None, gid=None):
-        SimplePlugin.__init__(self, bus)
-        self.finalized = False
-        self.uid = uid
-        self.gid = gid
-        self.umask = umask
-    
-    def _get_uid(self):
-        return self._uid
-    def _set_uid(self, val):
-        if val is not None:
-            if pwd is None:
-                self.bus.log("pwd module not available; ignoring uid.")
-                val = None
-            elif isinstance(val, basestring):
-                val = pwd.getpwnam(val)[2]
-        self._uid = val
-    uid = property(_get_uid, _set_uid, doc="The uid under which to run.")
-    
-    def _get_gid(self):
-        return self._gid
-    def _set_gid(self, val):
-        if val is not None:
-            if grp is None:
-                self.bus.log("grp module not available; ignoring gid.")
-                val = None
-            elif isinstance(val, basestring):
-                val = grp.getgrnam(val)[2]
-        self._gid = val
-    gid = property(_get_gid, _set_gid, doc="The gid under which to run.")
-    
-    def _get_umask(self):
-        return self._umask
-    def _set_umask(self, val):
-        if val is not None:
-            try:
-                os.umask
-            except AttributeError:
-                self.bus.log("umask function not available; ignoring umask.")
-                val = None
-        self._umask = val
-    umask = property(_get_umask, _set_umask, doc="The umask under which to run.")
-    
-    def start(self):
-        # uid/gid
-        def current_ids():
-            """Return the current (uid, gid) if available."""
-            name, group = None, None
-            if pwd:
-                name = pwd.getpwuid(os.getuid())[0]
-            if grp:
-                group = grp.getgrgid(os.getgid())[0]
-            return name, group
-        
-        if self.finalized:
-            if not (self.uid is None and self.gid is None):
-                self.bus.log('Already running as uid: %r gid: %r' %
-                             current_ids())
-        else:
-            if self.uid is None and self.gid is None:
-                if pwd or grp:
-                    self.bus.log('uid/gid not set')
-            else:
-                self.bus.log('Started as uid: %r gid: %r' % current_ids())
-                if self.gid is not None:
-                    os.setgid(gid)
-                if self.uid is not None:
-                    os.setuid(uid)
-                self.bus.log('Running as uid: %r gid: %r' % current_ids())
-        
-        # umask
-        if self.finalized:
-            if self.umask is not None:
-                self.bus.log('umask already set to: %03o' % self.umask)
-        else:
-            if self.umask is None:
-                self.bus.log('umask not set')
-            else:
-                old_umask = os.umask(self.umask)
-                self.bus.log('umask old: %03o, new: %03o' %
-                             (old_umask, self.umask))
-        
-        self.finalized = True
-    start.priority = 75
-
-
-class Daemonizer(SimplePlugin):
-    """Daemonize the running script.
-    
-    Use this with a Web Site Process Bus via:
-        
-        Daemonizer(bus).subscribe()
-    
-    When this component finishes, the process is completely decoupled from
-    the parent environment. Please note that when this component is used,
-    the return code from the parent process will still be 0 if a startup
-    error occurs in the forked children. Errors in the initial daemonizing
-    process still return proper exit codes. Therefore, if you use this
-    plugin to daemonize, don't use the return code as an accurate indicator
-    of whether the process fully started. In fact, that return code only
-    indicates if the process succesfully finished the first fork.
-    """
-    
-    def __init__(self, bus, stdin='/dev/null', stdout='/dev/null',
-                 stderr='/dev/null'):
-        SimplePlugin.__init__(self, bus)
-        self.stdin = stdin
-        self.stdout = stdout
-        self.stderr = stderr
-        self.finalized = False
-    
-    def start(self):
-        if self.finalized:
-            self.bus.log('Already deamonized.')
-        
-        # forking has issues with threads:
-        # http://www.opengroup.org/onlinepubs/000095399/functions/fork.html
-        # "The general problem with making fork() work in a multi-threaded
-        #  world is what to do with all of the threads..."
-        # So we check for active threads:
-        if threading.activeCount() != 1:
-            self.bus.log('There are %r active threads. '
-                         'Daemonizing now may cause strange failures.' %
-                         threading.enumerate())
-        
-        # See http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
-        # (or http://www.faqs.org/faqs/unix-faq/programmer/faq/ section 1.7)
-        # and http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66012
-        
-        # Finish up with the current stdout/stderr
-        sys.stdout.flush()
-        sys.stderr.flush()
-        
-        # Do first fork.
-        try:
-            pid = os.fork()
-            if pid == 0:
-                # This is the child process. Continue.
-                pass
-            else:
-                # This is the first parent. Exit, now that we've forked.
-                self.bus.log('Forking once.')
-                os._exit(0)
-        except OSError, exc:
-            # Python raises OSError rather than returning negative numbers.
-            sys.exit("%s: fork #1 failed: (%d) %s\n"
-                     % (sys.argv[0], exc.errno, exc.strerror))
-        
-        os.setsid()
-        
-        # Do second fork
-        try:
-            pid = os.fork()
-            if pid > 0:
-                self.bus.log('Forking twice.')
-                os._exit(0) # Exit second parent
-        except OSError, exc:
-            sys.exit("%s: fork #2 failed: (%d) %s\n"
-                     % (sys.argv[0], exc.errno, exc.strerror))
-        
-        os.chdir("/")
-        os.umask(0)
-        
-        si = open(self.stdin, "r")
-        so = open(self.stdout, "a+")
-        se = open(self.stderr, "a+", 0)
-
-        # os.dup2(fd, fd2) will close fd2 if necessary,
-        # so we don't explicitly close stdin/out/err.
-        # See http://docs.python.org/lib/os-fd-ops.html
-        os.dup2(si.fileno(), sys.stdin.fileno())
-        os.dup2(so.fileno(), sys.stdout.fileno())
-        os.dup2(se.fileno(), sys.stderr.fileno())
-        
-        self.bus.log('Daemonized to PID: %s' % os.getpid())
-        self.finalized = True
-    start.priority = 65
-
-
-class PIDFile(SimplePlugin):
-    """Maintain a PID file via a WSPBus."""
-    
-    def __init__(self, bus, pidfile):
-        SimplePlugin.__init__(self, bus)
-        self.pidfile = pidfile
-        self.finalized = False
-    
-    def start(self):
-        pid = os.getpid()
-        if self.finalized:
-            self.bus.log('PID %r already written to %r.' % (pid, self.pidfile))
-        else:
-            open(self.pidfile, "wb").write(str(pid))
-            self.bus.log('PID %r written to %r.' % (pid, self.pidfile))
-            self.finalized = True
-    start.priority = 70
-    
-    def exit(self):
-        try:
-            os.remove(self.pidfile)
-            self.bus.log('PID file removed: %r.' % self.pidfile)
-        except (KeyboardInterrupt, SystemExit):
-            raise
-        except:
-            pass
-
-
-class PerpetualTimer(threading._Timer):
-    """A subclass of threading._Timer whose run() method repeats."""
-    
-    def run(self):
-        while True:
-            self.finished.wait(self.interval)
-            if self.finished.isSet():
-                return
-            self.function(*self.args, **self.kwargs)
-
-
-class Monitor(SimplePlugin):
-    """WSPBus listener to periodically run a callback in its own thread.
-    
-    bus: a Web Site Process Bus object.
-    callback: the function to call at intervals.
-    frequency: the time in seconds between callback runs.
-    """
-    
-    frequency = 60
-    
-    def __init__(self, bus, callback, frequency=60):
-        SimplePlugin.__init__(self, bus)
-        self.callback = callback
-        self.frequency = frequency
-        self.thread = None
-    
-    def start(self):
-        """Start our callback in its own perpetual timer thread."""
-        if self.frequency > 0:
-            threadname = "restsrv %s" % self.__class__.__name__
-            if self.thread is None:
-                self.thread = PerpetualTimer(self.frequency, self.callback)
-                self.thread.setName(threadname)
-                self.thread.start()
-                self.bus.log("Started thread %r." % threadname)
-            else:
-                self.bus.log("Thread %r already started." % threadname)
-    start.priority = 70
-    
-    def stop(self):
-        """Stop our callback's perpetual timer thread."""
-        if self.thread is None:
-            self.bus.log("No thread running for %s." % self.__class__.__name__)
-        else:
-            if self.thread is not threading.currentThread():
-                self.thread.cancel()
-                self.thread.join()
-                self.bus.log("Stopped thread %r." % self.thread.getName())
-            self.thread = None
-    
-    def graceful(self):
-        """Stop the callback's perpetual timer thread and restart it."""
-        self.stop()
-        self.start()
-
-
-class Autoreloader(Monitor):
-    """Monitor which re-executes the process when files change."""
-    
-    frequency = 1
-    match = '.*'
-    
-    def __init__(self, bus, frequency=1, match='.*'):
-        self.mtimes = {}
-        self.files = set()
-        self.match = match
-        Monitor.__init__(self, bus, self.run, frequency)
-    
-    def start(self):
-        """Start our own perpetual timer thread for self.run."""
-        if self.thread is None:
-            self.mtimes = {}
-        Monitor.start(self)
-    start.priority = 70 
-    
-    def run(self):
-        """Reload the process if registered files have been modified."""
-        sysfiles = set()
-        for k, m in sys.modules.items():
-            if re.match(self.match, k):
-                if hasattr(m, '__loader__'):
-                    if hasattr(m.__loader__, 'archive'):
-                        k = m.__loader__.archive
-                k = getattr(m, '__file__', None)
-                sysfiles.add(k)
-        
-        for filename in sysfiles | self.files:
-            if filename:
-                if filename.endswith('.pyc'):
-                    filename = filename[:-1]
-                
-                oldtime = self.mtimes.get(filename, 0)
-                if oldtime is None:
-                    # Module with no .py file. Skip it.
-                    continue
-                
-                try:
-                    mtime = os.stat(filename).st_mtime
-                except OSError:
-                    # Either a module with no .py file, or it's been deleted.
-                    mtime = None
-                
-                if filename not in self.mtimes:
-                    # If a module has no .py file, this will be None.
-                    self.mtimes[filename] = mtime
-                else:
-                    if mtime is None or mtime > oldtime:
-                        # The file has been deleted or modified.
-                        self.bus.log("Restarting because %s changed." % filename)
-                        self.thread.cancel()
-                        self.bus.log("Stopped thread %r." % self.thread.getName())
-                        self.bus.restart()
-                        return
-
-
-class ThreadManager(SimplePlugin):
-    """Manager for HTTP request threads.
-    
-    If you have control over thread creation and destruction, publish to
-    the 'acquire_thread' and 'release_thread' channels (for each thread).
-    This will register/unregister the current thread and publish to
-    'start_thread' and 'stop_thread' listeners in the bus as needed.
-    
-    If threads are created and destroyed by code you do not control
-    (e.g., Apache), then, at the beginning of every HTTP request,
-    publish to 'acquire_thread' only. You should not publish to
-    'release_thread' in this case, since you do not know whether
-    the thread will be re-used or not. The bus will call
-    'stop_thread' listeners for you when it stops.
-    """
-    
-    def __init__(self, bus):
-        self.threads = {}
-        SimplePlugin.__init__(self, bus)
-        self.bus.listeners.setdefault('acquire_thread', set())
-        self.bus.listeners.setdefault('release_thread', set())
-    
-    def acquire_thread(self):
-        """Run 'start_thread' listeners for the current thread.
-        
-        If the current thread has already been seen, any 'start_thread'
-        listeners will not be run again.
-        """
-        thread_ident = threading._get_ident()
-        if thread_ident not in self.threads:
-            # We can't just use _get_ident as the thread ID
-            # because some platforms reuse thread ID's.
-            i = len(self.threads) + 1
-            self.threads[thread_ident] = i
-            self.bus.publish('start_thread', i)
-    
-    def release_thread(self):
-        """Release the current thread and run 'stop_thread' listeners."""
-        thread_ident = threading._get_ident()
-        i = self.threads.pop(thread_ident, None)
-        if i is not None:
-            self.bus.publish('stop_thread', i)
-    
-    def stop(self):
-        """Release all threads and run all 'stop_thread' listeners."""
-        for thread_ident, i in self.threads.iteritems():
-            self.bus.publish('stop_thread', i)
-        self.threads.clear()
-    graceful = stop
-

File cherrypy/restsrv/restctl.sh

-#!/bin/sh
-#
-# Control script for the restsrv daemon
-
-ERROR=0
-PYTHON=python
-RESTD=restd.py
-PIDFILE=/var/log/restsrv/restd.pid
-
-# Determine if restsrv is already running.
-RUNNING=0
-if [ -f $PIDFILE ]; then
-    PID=`cat $PIDFILE`
-    if [ "x$PID" != "x" ]; then
-        if kill -0 $PID 2>/dev/null ; then
-            # a process using PID is running
-            if ps -p $PID -o args | grep restsrv > /dev/null 2>&1; then
-                # that process is restsrv itself
-                RUNNING=1
-            fi
-        fi
-    fi
-    if ["$RUNNING" = "0"]; then
-        STATUS="restsrv (stale pid file removed) not running"
-        rm -f $PIDFILE
-    fi
-else
-    STATUS="restsrv (no pid) not running"
-fi
-
-ARGS=""
-COMMAND=""
-for ARG in $@; do
-    case $ARG in
-    start|stop|restart|graceful|status)
-        COMMAND=$ARG
-        ;;
-    *)
-        ARGS="$ARGS $ARG"
-    esac
-    fi
-done
-
-case $COMMAND in
-start)
-    echo "Starting restd"
-    $RESTD $ARGS
-    ERROR=$?
-    ;;
-stop)
-    echo "Stopping restd"
-    kill -TERM `cat $PIDFILE`
-    ERROR=$?
-    ;;
-restart)
-    echo "Restarting restd"
-    kill -HUP `cat $PIDFILE`
-    ;;
-graceful)
-    echo "Gracefully restarting restd"
-    kill -USR1 `cat $PIDFILE`
-    ;;
-status)
-    echo $STATUS
-    ;;
-*)
-    $RESTD $ARGS
-    ERROR=$?
-esac
-
-exit $ERROR

File cherrypy/restsrv/restd.py

-"""The restsrv daemon."""
-
-import getopt
-import sys
-
-from cherrypy import restsrv
-
-
-class _Optionset(object):
-    host = '0.0.0.0'
-    port = 80
-    protocol = 'HTTP/1.1'
-    scheme = 'http'
-options = _Optionset()
-
-shortopts = []
-longopts = [
-    # someday: 'cover', 'profile', 'validate', 'conquer',
-    'host=', 'port=', '1.0', 'ssl',
-    'project=', 'help',
-    ]
-
-
-def help():
-    """Print help for restd command-line options."""
-    
-    print """
-restd. Start the restsrv daemon.
-
-Usage:
-    restd [options]
-
-Options:
-  --host=<name or IP addr>: use a host other than the default (%s).
-  --port=<int>: use a port other than the default (%s)
-  --1.0: use HTTP/1.0 servers instead of default HTTP/1.1
-  --ssl: use HTTPS instead of default HTTP
-  --project=<module name>: import a module to set up the project
-  --help: print this usage message and exit
-""" % (options.host, options.port)
-
-
-def start(opts):
-    if '--project' in opts:
-        # delay import until after daemonize has a chance to run
-        def _import():
-            __import__(opts['--project'], {}, {}, [''])
-        _import.priority = 20
-        restsrv.bus.subscribe('start', _import)
-    
-    if 'win' not in sys.platform:
-        restsrv.bus.subscribe('start', restsrv.plugins.daemonize)
-    
-    restsrv.bus.start()
-    restsrv.bus.block()
-
-
-if __name__ == '__main__':
-    try:
-        opts, args = getopt.getopt(sys.argv[1:], shortopts, longopts)
-    except getopt.GetoptError:
-        help()
-        sys.exit(2)
-    
-    if "--help" in opts:
-        help()
-        sys.exit(0)
-    
-    start(dict(opts))

File cherrypy/restsrv/servers.py

-"""Adapt an HTTP server."""
-
-import socket
-import threading
-import time
-
-
-class ServerAdapter(object):
-    """Adapter for an HTTP server.
-    
-    If you need to start more than one HTTP server (to serve on multiple
-    ports, or protocols, etc.), you can manually register each one and then
-    start them all with bus.start:
-    
-        s1 = ServerAdapter(bus, MyWSGIServer(host='0.0.0.0', port=80))
-        s2 = ServerAdapter(bus, another.HTTPServer(host='127.0.0.1', SSL=True))
-        s1.subscribe()
-        s2.subscribe()
-        bus.start()
-    """
-    
-    def __init__(self, bus, httpserver=None, bind_addr=None):
-        self.bus = bus
-        self.httpserver = httpserver
-        self.bind_addr = bind_addr
-        self.interrupt = None
-        self.running = False
-    
-    def subscribe(self):
-        self.bus.subscribe('start', self.start)
-        self.bus.subscribe('stop', self.stop)
-    
-    def unsubscribe(self):
-        self.bus.unsubscribe('start', self.start)
-        self.bus.unsubscribe('stop', self.stop)
-    
-    def start(self):
-        """Start the HTTP server."""
-        if isinstance(self.bind_addr, tuple):
-            host, port = self.bind_addr
-            on_what = "%s:%s" % (host, port)
-        else:
-            on_what = "socket file: %s" % self.bind_addr
-        
-        if self.running:
-            self.bus.log("Already serving on %s" % on_what)
-            return
-        
-        self.interrupt = None
-        if not self.httpserver:
-            raise ValueError("No HTTP server has been created.")
-        
-        # Start the httpserver in a new thread.
-        if isinstance(self.bind_addr, tuple):
-            wait_for_free_port(*self.bind_addr)
-        
-        t = threading.Thread(target=self._start_http_thread)
-        t.setName("HTTPServer " + t.getName())
-        t.start()
-        
-        self.wait()
-        self.running = True
-        self.bus.log("Serving on %s" % on_what)
-    start.priority = 75
-    
-    def _start_http_thread(self):
-        """HTTP servers MUST be running in new threads, so that the
-        main thread persists to receive KeyboardInterrupt's. If an
-        exception is raised in the httpserver's thread then it's
-        trapped here, and the bus (and therefore our httpserver)
-        are shut down.
-        """
-        try:
-            self.httpserver.start()
-        except KeyboardInterrupt, exc:
-            self.bus.log("<Ctrl-C> hit: shutting down HTTP server")
-            self.interrupt = exc
-            self.bus.exit()
-        except SystemExit, exc:
-            self.bus.log("SystemExit raised: shutting down HTTP server")
-            self.interrupt = exc
-            self.bus.exit()
-            raise
-        except:
-            import sys
-            self.interrupt = sys.exc_info()[1]
-            self.bus.log("Error in HTTP server: shutting down",
-                         traceback=True)
-            self.bus.exit()
-            raise
-    
-    def wait(self):
-        """Wait until the HTTP server is ready to receive requests."""
-        while not getattr(self.httpserver, "ready", False):
-            if self.interrupt:
-                raise self.interrupt
-            time.sleep(.1)