Search code examples
pythonmultithreadingwebservercherrypy

Modify CherryPy's shutdown procedure


So I have a cherry py server which wraps a state machine. Behind the scenes this state machine does all the heavy lifting of mananging processes and threads and caching, and it is thread safe. So on start up of my cherry py I attach it to the app and the requests call into app.state_machine.methods.

In pseudo code I do:

from flask_app import app
import cherrypy

#configure app

app.state_machine = State_Machine.start()
try:
    cherrypy.tree.graft(app, "/")
    cherrypy.server.unsubscribe()

    server = cherrypy._cpserver.Server()
    server.socket_host="0.0.0.0"
    server.socket_port = 5001
    server.thread_pool = 10
    server.subscribe()

    cherrypy.engine.start()
    cherrypy.engine.block()
except KeyboardInterrupt:
    logging.getLogger(__name__).warn("Recieved keyboard interrupt")
except Exception:
    logging.getLogger(__name__).exception("Unknown exception from app")
finally:
    logging.getLogger(__name__).info("Entering Finally Block")
    state_machine.shutdown()

The intent here is that a key board interrupt should propagate out the app and calls state_machine.shutdown, which works with, e.g. the flask development server.

However, cherrypy swallows the KeyBoard interrupt and waits for its child threads to shutdown, since they contain references to app.state_machine, they will deadlock indefinitely until app.state_machine.shutdown() is called.

So how can I modify the shutdown procedure of cherrypy such that it will call shut down correctly?


Solution

  • As suggested by web ninja, the answer was to use the plugin.

    from cherrypy.process import wspbus, plugins
    
    class StateMachinePlugin(plugins.SimplePlugin) :
    
        def __init__(self, bus, state_machine):
            plugins.SimplePlugin.__init__(self, bus)
            self.state_manager = state_machine
    
        def start(self):
            self.bus.log("Starting state_machine")
            self.state_machine.run()
    
        def stop(self):
            self.bus.log("Shutting down state_machine")
            self.state_machine.shutdown()
            self.bus.log("Successfully shut down state_machine")
    

    Then just do :

    from flask_app import app
    import cherrypy
    
    #configure app
    
    app.state_machine = State_Machine.start()
    try:
        cherrypy.tree.graft(app, "/")
        cherrypy.server.unsubscribe()
    
        server = cherrypy._cpserver.Server()
        server.socket_host="0.0.0.0"
        server.socket_port = 5001
        server.thread_pool = 10
        server.subscribe()
    
     ###########################Added This line####################
     StateMachinePlugin(cherrypy.engine, app.state_machine).subscribe()
     #####################End Additions##########################
    
        cherrypy.engine.start()
        cherrypy.engine.block()
    except KeyboardInterrupt:
         logging.getLogger(__name__).warn("Recieved keyboard interrupt")
    except Exception:
         logging.getLogger(__name__).exception("Unknown exception from app")
    finally:
        logging.getLogger(__name__).info("Entering Finally Block")
        state_machine.shutdown()