Search code examples
python-3.xcherrypy

How to shut down CherryPy in no incoming connections for specified time?


I am using CherryPy to speak to an authentication server. The script runs fine if all the inputted information is fine. But if they make an mistake typing their ID the internal HTTP error screen fires ok, but the server keeps running and nothing else in the script will run until the CherryPy engine is closed so I have to manually kill the script. Is there some code I can put in the index along the lines of

if timer >10 and connections == 0:
    close cherrypy (< I have a method for this already)

Im mostly a data mangler, so not used to web servers. Googling shows lost of hits for closing CherryPy when there are too many connections but not when there have been no connections for a specified (short) time. I realise the point of a web server is usually to hang around waiting for connections, so this may be an odd case. All the same, any help welcome.


Solution

  • Interesting use case, you can use the CherryPy plugins infrastrcuture to do something like that, take a look at this ActivityMonitor plugin implementation, it shutdowns the server if is not handling anything and haven't seen any request in a specified amount of time (in this case 10 seconds).

    Maybe you have to adjust the logic on how to shut it down or do anything else in the _verify method.

    If you want to read a bit more about the publish/subscribe architecture take a look at the CherryPy Docs.

    import time
    import threading
    
    import cherrypy
    from cherrypy.process.plugins import Monitor
    
    
    class ActivityMonitor(Monitor):
    
        def __init__(self, bus, wait_time, monitor_time=None):
            """
            bus: cherrypy.engine
            wait_time: Seconds since last request that we consider to be active.
            monitor_time: Seconds that we'll wait before verifying the activity.
                          If is not defined, wait half the `wait_time`.
            """
            if monitor_time is None:
                # if monitor time is not defined, then verify half
                # the wait time since the last request
                monitor_time  = wait_time / 2
            super().__init__(
                bus, self._verify, monitor_time, self.__class__.__name__
            )
            # use a lock to make sure the thread that triggers the before_request
            # and after_request does not collide with the monitor method (_verify)
            self._active_request_lock = threading.Lock()
            self._active_requests = 0
            self._wait_time = wait_time
            self._last_request_ts = time.time()
    
        def _verify(self):
            # verify that we don't have any active requests and
            # shutdown the server in case we haven't seen any activity
            # since self._last_request_ts + self._wait_time
            with self._active_request_lock:
                if (not self._active_requests and
                    self._last_request_ts + self._wait_time < time.time()):
                    self.bus.exit() # shutdown the engine
    
        def before_request(self):
            with self._active_request_lock:
                self._active_requests += 1
    
        def after_request(self):
            with self._active_request_lock:
                self._active_requests -= 1
            # update the last time a request was served
            self._last_request_ts = time.time()
    
    
    class Root:
    
        @cherrypy.expose
        def index(self):
            return "Hello user: current time {:.0f}".format(time.time())
    
    
    def main():
        # here is how to use the plugin:
        ActivityMonitor(cherrypy.engine, wait_time=10, monitor_time=5).subscribe()
        cherrypy.quickstart(Root())
    
    
    if __name__ == '__main__':
        main()