diff piecrust/serving/wrappers.py @ 552:9612cfc6455a

serve: Rewrite of the Server-Sent Event code for build notifications. At the moment the server monitors the asset directories, and notifies the browser when an asset has changed and has been re-processed. * Fix issues around long-running requests/threads which mess up the ability to shutdown the server correctly with `CTRL-C` (see comments in code). * Move the notification queue to each SSE producer, to support having multiple pages open in a browser. * Add JS/CSS for showing quick notifications about re-processed assets. * Add support for hot-reloading CSS and pictures that have been re-processed.
author Ludovic Chabant <ludovic@chabant.com>
date Sat, 08 Aug 2015 16:12:04 -0700
parents fa3ee8a8ee2d
children cc6f3dbe3048
line wrap: on
line diff
--- a/piecrust/serving/wrappers.py	Sat Aug 08 15:55:24 2015 -0700
+++ b/piecrust/serving/wrappers.py	Sat Aug 08 16:12:04 2015 -0700
@@ -1,6 +1,11 @@
 import os
+import logging
+import threading
+import urllib.request
 from piecrust.serving.server import Server
-from piecrust.serving.procloop import _sse_abort
+
+
+logger = logging.getLogger(__name__)
 
 
 def run_werkzeug_server(root_dir, host, port,
@@ -22,13 +27,39 @@
                                debug=debug_piecrust,
                                sub_cache_dir=sub_cache_dir,
                                run_sse_check=_run_sse_check)
-    try:
+
+    # We need to run Werkzeug in a background thread because we may have some
+    # SSE responses running. In theory we should be using a proper async
+    # server for this kind of stuff, but I'd rather avoid additional
+    # dependencies on stuff that's not necessarily super portable.
+    # Anyway we run the server in multi-threading mode, but the request
+    # threads are not set to `daemon` mode (and there's no way to set that
+    # flag without re-implementing `run_simple` apparently). So instead we
+    # run the server in a background thread so we keep the main thread to
+    # ourselves here, which means we can trap `KeyboardInterrupt`, and set
+    # a global flag that will kill all the long-running SSE threads and make
+    # this whole thing exit cleanly and properly (hopefully).
+    def _inner():
         run_simple(host, port, app,
                    threaded=True,
                    use_debugger=use_debugger,
                    use_reloader=use_reloader)
+
+    t = threading.Thread(name='WerkzeugServer', target=_inner)
+    t.start()
+    try:
+        while t.is_alive():
+            t.join(0.5)
+    except KeyboardInterrupt:
+        shutdown_url = 'http://%s:%s/__piecrust_debug/werkzeug_shutdown' % (
+                host, port)
+        logger.info("")
+        logger.info("Shutting down server...")
+        urllib.request.urlopen(shutdown_url)
     finally:
-        _sse_abort.set()
+        logger.debug("Terminating push notifications...")
+        from piecrust.serving import procloop
+        procloop.server_shutdown = True
 
 
 def run_gunicorn_server(root_dir,