Mercurial > piecrust2
comparison piecrust/serving/wrappers.py @ 558:9ab005db2592
serve: Improve reloading and shutdown of the preview server.
See the huge comment block in the file for more infos.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Fri, 14 Aug 2015 21:26:51 -0700 |
parents | cc6f3dbe3048 |
children | 3ceeca7bb71c |
comparison
equal
deleted
inserted
replaced
557:703ea5d76f33 | 558:9ab005db2592 |
---|---|
1 import os | 1 import os |
2 import signal | |
2 import logging | 3 import logging |
3 import threading | 4 import threading |
4 import urllib.request | 5 import urllib.request |
5 | 6 |
6 | 7 |
25 app = _get_piecrust_server(root_dir, | 26 app = _get_piecrust_server(root_dir, |
26 debug=debug_piecrust, | 27 debug=debug_piecrust, |
27 sub_cache_dir=sub_cache_dir, | 28 sub_cache_dir=sub_cache_dir, |
28 run_sse_check=_run_sse_check) | 29 run_sse_check=_run_sse_check) |
29 | 30 |
30 # We need to run Werkzeug in a background thread because we may have some | 31 # We need to do a few things to get Werkzeug to properly shutdown or |
31 # SSE responses running. In theory we should be using a proper async | 32 # restart while SSE responses are running. This is because Werkzeug runs |
32 # server for this kind of stuff, but I'd rather avoid additional | 33 # them in background threads (because we tell it to), but those threads |
33 # dependencies on stuff that's not necessarily super portable. | 34 # are not marked as "daemon", so when the main thread tries to exit, it |
34 # Anyway we run the server in multi-threading mode, but the request | 35 # will wait on those SSE responses to end, which will pretty much never |
35 # threads are not set to `daemon` mode (and there's no way to set that | 36 # happen (except for a timeout or the user closing their browser). |
36 # flag without re-implementing `run_simple` apparently). So instead we | 37 # |
37 # run the server in a background thread so we keep the main thread to | 38 # In theory we should be using a proper async server for this kind of |
38 # ourselves here, which means we can trap `KeyboardInterrupt`, and set | 39 # stuff, but I'd rather avoid additional dependencies on stuff that's not |
39 # a global flag that will kill all the long-running SSE threads and make | 40 # necessarily super portable. |
40 # this whole thing exit cleanly and properly (hopefully). | 41 # |
41 def _inner(): | 42 # Anyway, we run the server as usual, but we intercept the `SIGINT` |
43 # signal for when the user presses `CTRL-C`. When that happens, we set a | |
44 # flag that will make all existing SSE loops return, which will make it | |
45 # possible for the main thread to end too. | |
46 # | |
47 # We also need to do a few thing for the "reloading" feature in Werkzeug, | |
48 # see the comment down there for more info. | |
49 def _shutdown_server(): | |
50 from piecrust.serving import procloop | |
51 procloop.server_shutdown = True | |
52 | |
53 def _shutdown_server_and_raise_sigint(): | |
54 if not use_reloader or os.environ.get('WERKZEUG_RUN_MAIN') == 'true': | |
55 # We only need to shutdown the SSE requests for the process | |
56 # that actually runs them. | |
57 print("") | |
58 print("Shutting server down...") | |
59 _shutdown_server() | |
60 raise KeyboardInterrupt() | |
61 | |
62 signal.signal(signal.SIGINT, | |
63 lambda *args: _shutdown_server_and_raise_sigint()) | |
64 | |
65 try: | |
42 run_simple(host, port, app, | 66 run_simple(host, port, app, |
43 threaded=True, | 67 threaded=True, |
44 use_debugger=use_debugger, | 68 use_debugger=use_debugger, |
45 use_reloader=use_reloader) | 69 use_reloader=use_reloader) |
46 | 70 except SystemExit: |
47 t = threading.Thread(name='WerkzeugServer', target=_inner) | 71 if os.environ.get('WERKZEUG_RUN_MAIN') == 'true': |
48 t.start() | 72 # When using the reloader, if code has changed, the child process |
49 try: | 73 # will use `sys.exit` to end and let the master process restart |
50 while t.is_alive(): | 74 # it... we need to shutdown the SSE requests otherwise it will |
51 t.join(0.5) | 75 # not exit. |
52 except KeyboardInterrupt: | 76 _shutdown_server() |
53 shutdown_url = 'http://%s:%s/__piecrust_debug/werkzeug_shutdown' % ( | 77 raise |
54 host, port) | |
55 logger.info("") | |
56 logger.info("Shutting down server...") | |
57 urllib.request.urlopen(shutdown_url) | |
58 finally: | |
59 logger.debug("Terminating push notifications...") | |
60 from piecrust.serving import procloop | |
61 procloop.server_shutdown = True | |
62 | 78 |
63 | 79 |
64 def run_gunicorn_server(root_dir, | 80 def run_gunicorn_server(root_dir, |
65 debug_piecrust=False, sub_cache_dir=None, | 81 debug_piecrust=False, sub_cache_dir=None, |
66 gunicorn_options=None): | 82 gunicorn_options=None): |