Browse Source

Try to clean up some issues with cherrypy startup and os._exit

This is hard and finicky to test, so there's unfortunately not a clear
way to get 100% test coverage in cherrypy_start.  However, that
function is only used in the test suite and the standalone
nilmdb-server script, not in production (which goes through wsgi and
skips all this cherrypy server stuff entirely).
tags/nilmdb-2.0.0
Jim Paris 4 years ago
parent
commit
1952f245c0
2 changed files with 34 additions and 13 deletions
  1. +23
    -13
      nilmdb/server/serverutil.py
  2. +11
    -0
      tests/test_nilmdb.py

+ 23
- 13
nilmdb/server/serverutil.py View File

@@ -6,6 +6,7 @@ import sys
import os
import decorator
import json
import functools

# Helper to parse parameters into booleans
def bool_param(s):
@@ -147,19 +148,25 @@ def cherrypy_start(blocking = False, event = False):
"""Start the CherryPy server, handling errors and signals
somewhat gracefully."""

# Cherrypy stupidly calls os._exit(70) when it can't bind the
# port. At least try to print a reasonable error and continue
# in this case, rather than just dying silently (as we would
# otherwise do in embedded mode)
real_exit = os._exit
def fake_exit(code): # pragma: no cover
if code == os.EX_SOFTWARE:
fprintf(sys.stderr, "error: CherryPy called os._exit!\n")
else:
# 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
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)
os._exit = fake_exit
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 the server
cherrypy.engine.start()
os._exit = real_exit

# Signal that the engine has started successfully
if event is not None:
@@ -169,10 +176,13 @@ def cherrypy_start(blocking = False, event = False):
try:
cherrypy.engine.wait(cherrypy.engine.states.EXITING,
interval = 0.1, channel = 'main')
except (KeyboardInterrupt, IOError): # pragma: no cover
except (KeyboardInterrupt, IOError): # pragma: no cover (hard to invoke
# from tests, standalone server
# isn't used in production)
cherrypy.engine.log('Keyboard Interrupt: shutting down bus')
cherrypy.engine.exit()
except SystemExit: # pragma: no cover
except SystemExit: # pragma: no cover (hard to invoke from tests;
# standalone server isn't used in production)
cherrypy.engine.log('SystemExit raised: shutting down bus')
cherrypy.engine.exit()
raise


+ 11
- 0
tests/test_nilmdb.py View File

@@ -14,6 +14,7 @@ from urllib.error import HTTPError
import io
import time
import requests
import socket

from nilmdb.utils import serializer_proxy

@@ -25,6 +26,16 @@ testdb = "tests/testdb"

from testutil.helpers import *

def setup_module():
# Make sure port is free
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))
except OSError:
raise AssertionError("port 32180 must be free for tests")
sock.close()

class Test00Nilmdb(object): # named 00 so it runs first
def test_NilmDB(self):
recursive_unlink(testdb)


Loading…
Cancel
Save