1

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.

IAbstract
  • 19,551
  • 15
  • 98
  • 146
  • Where do you run the code to set up the signal handler? – chepner Mar 24 '15 at 16:37
  • The signal_handler set-up is in the same source (at top) file as the server. So when main imports server, all of the set up should be done, right? – IAbstract Mar 24 '15 at 16:57
  • Signals are not propagated to child threads. – roippi Mar 24 '15 at 17:15
  • @roippi: Even if I remove `__cherry_thread` and call `engine.start()` directly, the `Ctrl+C` is not captured. I understand that CherryPy may be running on a child thread, but how do I get the main-thread to recognize `SIGINT`? – IAbstract Mar 24 '15 at 19:23

1 Answers1

3

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.

saaj
  • 23,253
  • 3
  • 104
  • 105
  • Ok, this is great information on how to properly implement CherryPy with a custom configuration. But that wasn't my question (although this answer does give me useful information in the rest of my implementation). With this custom configuration, where do I implement capturing SIGINT and shutting down? – IAbstract Mar 25 '15 at 15:19
  • @IAbstract I don't think that every question has to be answered literally because there is usually a better path to suggest. Here, for instance, my answer is to point you and future readers that you don't normally need to handle OS signals yourself. CherryPy does it anyway, so you can just follow server's state. It is easy with subclassing `SimplePlugin`, as it automatically subscribes to `start`, `stop`, `exit`, `graceful`. I also added common plugin's pattern and caveat for it be more useful and clear. I've updated my answer with SIGINT related states and suggestion, take a look. – saaj Mar 26 '15 at 09:21
  • We can agree to disagree on whether an answer needs to be literal. I am still fairly new to Python and have just begun to dig into CherryPy on my own. I have not fully understood the architecture and how I can extend CherryPy. That said, I am still not sure how/where to put a sigint_handler with this example. :/ Are you telling me that CherryPy already has a sigint_handler? – IAbstract Mar 26 '15 at 11:14
  • @IAbstract 1) For a beginner it's a good idea also to provide details of what you want to achieve in the end, like the purpose of your thread 2) Python handles Ctrl+C natively, and it raises [`KeyboardInterrupt`](https://docs.python.org/2/library/exceptions.html#exceptions.KeyboardInterrupt) ([details](http://stackoverflow.com/questions/1112343/how-do-i-capture-sigint-in-python)) 3) Of course CherryPy handles `KeyboardInterrupt` (SIGINT) and also SIGTERM, SIGHUP and SIGUSR1. So I repeat, put your termination code into `ExamplePlugin.exit` and let CherryPy do signal handling for you. – saaj Mar 26 '15 at 11:35