Clean up how we handle cherrypy's calls of os._exit(70)
With this solution, we can catch it cleanly in the standalone nilmdb-server, and test the error paths in our normal test suite.
This commit is contained in:
parent
ca440a42bd
commit
ea67e45be9
|
@ -5,6 +5,7 @@ import argparse
|
|||
import os
|
||||
import socket
|
||||
import cherrypy
|
||||
import sys
|
||||
|
||||
def main():
|
||||
"""Main entry point for the 'nilmdb-server' command line script"""
|
||||
|
@ -61,27 +62,30 @@ def main():
|
|||
print("----")
|
||||
|
||||
# Run it
|
||||
if args.yappi:
|
||||
print("Running in yappi")
|
||||
try:
|
||||
import yappi
|
||||
yappi.start()
|
||||
try:
|
||||
if args.yappi:
|
||||
print("Running in yappi")
|
||||
try:
|
||||
import yappi
|
||||
yappi.start()
|
||||
server.start(blocking = True)
|
||||
finally:
|
||||
yappi.stop()
|
||||
stats = yappi.get_func_stats()
|
||||
stats.sort("ttot")
|
||||
stats.print_all()
|
||||
from IPython import embed
|
||||
embed(header = "Use the `yappi` or `stats` object to explore "
|
||||
"further, quit to exit")
|
||||
else:
|
||||
server.start(blocking = True)
|
||||
finally:
|
||||
yappi.stop()
|
||||
stats = yappi.get_func_stats()
|
||||
stats.sort("ttot")
|
||||
stats.print_all()
|
||||
from IPython import embed
|
||||
embed(header = "Use the `yappi` or `stats` object to explore "
|
||||
"further, quit to exit")
|
||||
else:
|
||||
server.start(blocking = True)
|
||||
|
||||
# Clean up
|
||||
if not args.quiet:
|
||||
print("Closing database")
|
||||
db.close()
|
||||
except nilmdb.server.serverutil.CherryPyExit as e:
|
||||
print("Exiting due to CherryPy error", file=sys.stderr)
|
||||
raise
|
||||
finally:
|
||||
if not args.quiet:
|
||||
print("Closing database")
|
||||
db.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
@ -143,28 +143,38 @@ def json_error_page(status, message, traceback, version,
|
|||
errordata[k] = v
|
||||
return json.dumps(errordata, separators=(',',':'))
|
||||
|
||||
# Start/stop CherryPy standalone server
|
||||
def cherrypy_start(blocking = False, event = False):
|
||||
"""Start the CherryPy server, handling errors and signals
|
||||
somewhat gracefully."""
|
||||
class CherryPyExit(SystemExit):
|
||||
pass
|
||||
|
||||
def cherrypy_patch_exit():
|
||||
# Cherrypy stupidly calls os._exit(70) when it can't bind the port
|
||||
# and exits. Replace os._exit with a version that at least prints
|
||||
# a message, since otherwise it's totally unclear what happened
|
||||
# and exits. Instead of that, raise a CherryPyExit (derived from
|
||||
# SystemExit). This exception may not make it back up to the caller
|
||||
# due to internal thread use in the CherryPy engine, but there should
|
||||
# be at least some indication that it happened.
|
||||
bus = cherrypy.process.wspbus.bus
|
||||
if "_patched_exit" in bus.__dict__:
|
||||
return
|
||||
bus._patched_exit = True
|
||||
|
||||
def patched_exit(orig):
|
||||
real_exit = os._exit
|
||||
def fake_exit(code): # pragma: no cover (hard to invoke from tests;
|
||||
# standalone server isn't used in production)
|
||||
print("Cherrypy called os._exit(%d)" % code, file=sys.stderr)
|
||||
real_exit(code)
|
||||
def fake_exit(code):
|
||||
raise CherryPyExit(code)
|
||||
os._exit = fake_exit
|
||||
try:
|
||||
orig()
|
||||
finally:
|
||||
os._exit = real_exit
|
||||
bus = cherrypy.process.wspbus.bus
|
||||
bus.exit = functools.partial(patched_exit, bus.exit)
|
||||
|
||||
# Start/stop CherryPy standalone server
|
||||
def cherrypy_start(blocking = False, event = False):
|
||||
"""Start the CherryPy server, handling errors and signals
|
||||
somewhat gracefully."""
|
||||
|
||||
cherrypy_patch_exit()
|
||||
|
||||
# Start the server
|
||||
cherrypy.engine.start()
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@ import io
|
|||
import os
|
||||
import sys
|
||||
import time
|
||||
import socket
|
||||
import cherrypy
|
||||
|
||||
import nilmdb.server
|
||||
from nilmdb.utils import timer, lock
|
||||
|
@ -95,7 +97,7 @@ class TestMisc(object):
|
|||
nilmdb.utils.diskusage.du("/dev/null/bogus")
|
||||
nilmdb.utils.diskusage.du("super-bogus-does-not-exist")
|
||||
|
||||
def tests_cors_allow(self):
|
||||
def test_cors_allow(self):
|
||||
# Just to get some test coverage; these code paths aren't actually
|
||||
# used in current code
|
||||
cpy = nilmdb.server.serverutil.cherrypy
|
||||
|
@ -110,3 +112,28 @@ class TestMisc(object):
|
|||
with assert_raises(cpy.HTTPError):
|
||||
nilmdb.server.serverutil.CORS_allow(methods=[])
|
||||
(cpy.request, cpy.response) = (req, resp)
|
||||
|
||||
def test_cherrypy_failure(self):
|
||||
# Test failure of cherrypy to start up because the port is
|
||||
# already in use. This also tests the functionality of
|
||||
# serverutil:cherrypy_patch_exit()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
try:
|
||||
sock.bind(("127.0.0.1", 32180))
|
||||
sock.listen(1)
|
||||
except OSError:
|
||||
raise AssertionError("port 32180 must be free for tests")
|
||||
|
||||
nilmdb.server.serverutil.cherrypy_patch_exit()
|
||||
cherrypy.config.update({
|
||||
'environment': 'embedded',
|
||||
'server.socket_host': '127.0.0.1',
|
||||
'server.socket_port': 32180,
|
||||
'engine.autoreload.on': False,
|
||||
})
|
||||
with assert_raises(Exception) as e:
|
||||
cherrypy.engine.start()
|
||||
in_("Address already in use", str(e.exception))
|
||||
|
||||
sock.close()
|
||||
|
|
Loading…
Reference in New Issue
Block a user