Commits

Daniel Holth committed 7a251ec

add support for reloading servers in addition to applications.

  • Participants
  • Parent commits 71babb2

Comments (0)

Files changed (2)

File reloadwsgi.py

 import paste.deploy
 import paste.reloader
 
+POLL_INTERVAL = 1   # check for changes every n seconds.
+SPINUP_TIME = 10    # application must start within this time.
+
 class Monitor(paste.reloader.Monitor):
-    def __init__(self, poll_interval=1, tx=None, rx=None):
-        paste.reloader.Monitor.__init__(self, poll_interval)
+    def __init__(self, tx=None, rx=None):
+        paste.reloader.Monitor.__init__(self, POLL_INTERVAL)
         self.state = 'RUN'
         self.tx = tx
         self.rx = rx
                 self.state = 'STANDBY'
                 # inform code change
                 self.tx.put({'pid':os.getpid(), 'status':'changed'})
-                self.rx.wait(10)
+                self.rx.wait(SPINUP_TIME)
                 if self.rx.is_set():
                     return
                 self.state = 'RUN'
                 self.module_mtimes = {}
-            time.sleep(self.poll_interval) 
+            time.sleep(self.poll_interval)
+
+def configure_logging(uri):
+    """Configure logging from the PasteDeploy .ini found at uri"""
+    config_file = uri
+    if config_file.startswith('config:'):
+        config_file = config_file.split(':', 1)[1]
+    parser = ConfigParser.ConfigParser()
+    parser.read([config_file])
+    if parser.has_section('loggers'):
+        logging.config.fileConfig(config_file)
 
 def serve(server, uri, tx, rx):
     try:
-        # configure logging
-        config_file = uri
-        if config_file.startswith('config:'):
-            config_file = config_file.split(':', 1)[1]
-        parser = ConfigParser.ConfigParser()
-        parser.read([config_file])
-        if parser.has_section('loggers'):
-            logging.config.fileConfig(config_file)
+        configure_logging(uri)
 
         # load wsgi application
         app = paste.deploy.loadapp(uri)
     except KeyboardInterrupt:
         pass
 
-def reloadwsgi(uri, host='localhost', port=8080):
-    server = make_server(host, port, None)
+def serve_from_server(server_name, uri, tx, rx):
+    """Load named server from PasteDeploy .ini file and run.
+
+    This will only work if multiple servers do not require access to
+    an exclusive resource like a normal socket. In other words, this
+    will not work with most WSGI servers.
     
-    # tx, rx from the subprocess' perspective.
+    Intended for use with mongrel2_wsgi."""
+
+    try:
+        configure_logging(uri)
+
+        # load named server and default application:
+        app = paste.deploy.loadapp(uri)
+        server = paste.deploy.loadserver(uri, name=server_name)
+
+        tx.put({'pid':os.getpid(), 'status':'loaded'})
+
+        def go():
+            server(app)
+
+        t = threading.Thread(target=go)
+        t.setDaemon(True)
+        t.start()
+
+        monitor = Monitor(tx=tx, rx=rx)
+        monitor.periodic_reload()
+
+    except KeyboardInterrupt:
+        pass
+
+def reloadwsgi(uri, host='localhost', port=8080, server_name=None):
+    # tx, rx from the subprocess' perspective.   
     tx = Queue()
 
+    if not server_name:
+        server = make_server(host, port, None)
+        target = serve
+    else:
+        server = server_name
+        target = serve_from_server
+
     def spinup():
         rx = Event()
-        worker = Process(target=serve, args=(server, uri, tx, rx))
+        worker = Process(target=target, args=(server, uri, tx, rx))
         worker.rx = rx
         worker.start()
         return worker
     usage = """Usage: %prog [options] config.ini
 Robust automatic reloading for WSGI development."""
     parser = optparse.OptionParser(usage)
+    parser.add_option("-s", "--s", default=None, dest="server_name",
+            help="Load named server from [config.ini] instead of binding to a host and port.")
     parser.add_option("-H", "--host", dest="hostname",
             default="localhost", type="string",
             help="Listen on hostname/address instead of localhost")
     (options, args) = parser.parse_args()
     if len(args) != 1:
         parser.error("Must specify exactly one Paste Deploy .ini file.")
+    server_name = options.server_name
     host = options.hostname
     port = options.port
     config = os.path.abspath(args[0])
-    reloadwsgi('config:%s' % config, host=host, port=port)
+    reloadwsgi('config:%s' % config, host=host, port=port,
+            server_name=server_name)
 
 def app_factory(global_config, **local_conf):
+    """For testing."""
     import wsgiref.simple_server
     return wsgiref.simple_server.demo_app
 
 if sys.version_info[:2] < (2, 6):
     install_requires += ['multiprocessing']
 
-version = '0.2'
+version = '0.3'
 
 setup(name='ReloadWSGI',
       version=version,
       description="Robust WSGI auto-reloading for development.",
       long_description="""\
 
+Replacement for 'paster serve --reload config.ini'.
+
 Reload a WSGI application on source change. Keep the old code alive
 when the change has syntax errors. Never close the socket, never refuse
 a connection.
 
-Replacement for 'paster serve --reload config.ini'.
+As of version 0.3, ReloadWSGI also supports reloading a server specified
+in the config file. This is appropriate for wsgi servers such as
+mongrel2_wsgi which are able to support two concurrent instances
+without stepping on each other's network connection. Once ReloadWSGI
+confirms the second server can load without throwing e.g. a syntax error,
+the original server quits and Mongrel2's automatic load balancing
+sends requests to the newer instance.
 
 
 PID 4197 notifies us of a change in quux.py ::