I have the following standard implementation of capturing Ctrl+C:
def signal_handler(signal, frame):
status = server.stop()
print("[{source}] Server Status: {status}".format(source=__name__.upper(),
status=status))
print("Exiting ...")
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
On server.start()
I am starting a threaded instance of CherryPy. I created the thread thinking that maybe since CherryPy is running, the main thread is not seeing the Ctrl+C. This did not seem to have any affect but posting the code as I have it now:
__main__:
server.start()
server:
def start(self):
# self.engine references cherrypy.engine
self.__cherry_thread = threading.Thread(target=self.engine.start)
self.status['running'] = True
self.status['start_time'] = get_timestamp()
self.__cherry_thread.start()
def stop(self):
self.status['running'] = False
self.status['stop_time'] = get_timestamp()
self.engine.exit()
self.__thread_event.set()
return self.status
When I press Ctrl+C the application does not stop. I have placed a breakpoint in the signal_handler
above and it is never hit.
It's not quite clear what you want to achieve in the end, but it looks like you miss important point of CherryPy design.
CherryPy state and component orchestration is built around the message bus. To you as a developer it's also an abstraction from OS-specific signalling. So if you want to have a thread, it is a good idea to wrap in into CherryPy plugin which will adhere to state of the server.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import threading
import time
import logging
import cherrypy
from cherrypy.process.plugins import SimplePlugin
config = {
'global' : {
'server.socket_host' : '127.0.0.1',
'server.socket_port' : 8080,
'server.thread_pool' : 8
}
}
class ExamplePlugin(SimplePlugin):
_thread = None
_running = None
_sleep = None
def __init__(self, bus, sleep = 2):
SimplePlugin.__init__(self, bus)
self._sleep = sleep
def start(self):
'''Called when the engine starts'''
self.bus.log('Setting up example plugin')
# You can listen for a message published in request handler or
# elsewhere. Usually it's putting some into the queue and waiting
# for queue entry in the thread.
self.bus.subscribe('do-something', self._do)
self._running = True
if not self._thread:
self._thread = threading.Thread(target = self._target)
self._thread.start()
# Make sure plugin priority matches your design e.g. when starting a
# thread and using Daemonizer which forks and has priority of 65, you
# need to start after the fork as default priority is 50
# see https://groups.google.com/forum/#!topic/cherrypy-users/1fmDXaeCrsA
start.priority = 70
def stop(self):
'''Called when the engine stops'''
self.bus.log('Freeing up example plugin')
self.bus.unsubscribe('do-something', self._do)
self._running = False
if self._thread:
self._thread.join()
self._thread = None
def exit(self):
'''Called when the engine exits'''
self.unsubscribe()
def _target(self):
while self._running:
try:
self.bus.log('some periodic routine')
time.sleep(self._sleep)
except:
self.bus.log('Error in example plugin', level = logging.ERROR, traceback = True)
def _do(self, arg):
self.bus.log('handling the message: {0}'.format(arg))
class App:
@cherrypy.expose
def index(self):
cherrypy.engine.publish('do-something', 'foo')
return 'Look in the terminal or log'
if __name__ == '__main__':
ExamplePlugin(cherrypy.engine).subscribe()
cherrypy.quickstart(App(), '/', config)
UPDATE
More explicitly about handling SIGINT signal. Here's the FSM diagram from the first link.
O
|
V
STOPPING --> STOPPED --> EXITING -> X
A A |
| \___ |
| \ |
| V V
STARTED <-- STARTING
Your interest is either STOPPING
or EXITING
as both relate to handling SIGINT. The difference is that STOPPING
may occur several times e.g. when the server is daemonised SIGHUP makes it restart. So you can just put your termination routine in ExamplePlugin.exit
.