Why is CTRL-C not captured and signal_handler call

2019-01-29 00:11发布

问题:

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.

回答1:

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.