CherryPy interferes with Twisted shutting down on Windows

2024/10/4 9:32:13

I've got an application that runs Twisted by starting the reactor with reactor.run() in my main thread after starting some other threads, including the CherryPy web server. Here's a program that shuts down cleanly when Ctrl+C is pressed on Linux but not on Windows:

from threading import Thread
from signal import signal, SIGINTimport cherrypyfrom twisted.internet import reactor
from twisted.web.client import getPagedef stop(signum, frame):cherrypy.engine.exit()reactor.callFromThread(reactor.stop)
signal(SIGINT, stop)class Root:@cherrypy.exposedef index(self):reactor.callFromThread(kickoff)return "Hello World!"cherrypy.server.socket_host = "0.0.0.0"
Thread(target=cherrypy.quickstart, args=[Root()]).start()def print_page(html):print(html)def kickoff():getPage("http://acpstats/account/login").addCallback(print_page)reactor.run()

I believe that CherryPy is the culprit here, because here's a different program that I wrote without CherryPy that does shutdown cleanly on both Linux and Windows when Ctrl+C is pressed:

from time import sleep
from threading import Thread
from signal import signal, SIGINTfrom twisted.internet import reactor
from twisted.web.client import getPagekeep_going = True
def stop(signum, frame):global keep_goingkeep_going = Falsereactor.callFromThread(reactor.stop)
signal(SIGINT, stop)def print_page(html):print(html)def kickoff():getPage("http://acpstats/account/login").addCallback(print_page)def periodic_downloader():while keep_going:reactor.callFromThread(kickoff)sleep(5)Thread(target=periodic_downloader).start()
reactor.run()

Does anyone have any idea what the problem is? Here's my conundrum:

  • On Linux everything works
  • On Windows, I can call functions from signal handlers using reactor.callFromThread when CherryPy is not running
  • When CherryPy is running, no function that I call using reactor.callFromThread from a signal handler will ever execute (I've verified that the signal handler itself does get called)

What can I do about this? How can I shut down Twisted on Windows from a signal handler while running CherryPy? Is this a bug, or have I simply missed some important part of the documentation for either of these two projects?

Answer

CherryPy handles signals by default when you call quickstart. In your case, you should probably just unroll quickstart, which is only a few lines, and pick and choose. Here's basically what quickstart does in trunk:

if config:cherrypy.config.update(config)tree.mount(root, script_name, config)if hasattr(engine, "signal_handler"):engine.signal_handler.subscribe()
if hasattr(engine, "console_control_handler"):engine.console_control_handler.subscribe()engine.start()
engine.block()

In your case, you don't need the signal handlers, so you can omit those. You also don't need to call engine.block if you're not starting CherryPy from the main thread. Engine.block() is just a way to make the main thread not terminate immediately, but instead wait around for process termination (this is so autoreload works reliably; some platforms have issues calling execv from any thread but the main thread).

If you remove the block() call, you don't even need the Thread() around quickstart. So, replace your line:

Thread(target=cherrypy.quickstart, args=[Root()]).start()

with:

cherrypy.tree.mount(Root())
cherrypy.engine.start()
https://en.xdnf.cn/q/70625.html

Related Q&A

From subprocess.Popen to multiprocessing

I got a function that invokes a process using subprocess.Popen in the following way:def func():...process = subprocess.Popen(substr, shell=True, stdout=subprocess.PIPE)timeout = {"value": Fal…

Assigning float as a dictionary key changes its precision (Python)

I have a list of floats (actually its a pandas Series object, if it changes anything) which looks like this:mySeries:... 22 16.0 23 14.0 24 12.0 25 10.0 26 3.1 ...(So elements…

Installing jpype in Mountain Lion

I am trying to install jpype in Mountain Lion. I followed all the steps suggested in this post: How to install JPype on OS X Lion to use with Neo4j?However, there is a glitch with Mountain Lion. I hav…

Most efficient way to index words in a document?

This came up in another question but I figured it is best to ask this as a separate question. Give a large list of sentences (order of 100 thousands):[ "This is sentence 1 as an example", &qu…

python libclang bindings on Windows fail to initialize a translation unit from sublime text

Short description: using libclang to autocomplete code does not work with python that comes bundled with Sublime Text 3.Details: A small verifiable example is in the repo on GithubIn essence, there is …

How to create a simple Gradient Descent algorithm

Im studying simple machine learning algorithms, beginning with a simple gradient descent, but Ive got some trouble trying to implement it in python. Here is the example Im trying to reproduce, Ive got …

login_required decorator on a class based view in django

I have a working class based view. But when adding @login_required I get the error:AttributeError: function object has no attribute as_viewSomething is happening to the ResultListView here:from django.…

Generic way to get primary key from declaratively defined instance in SQLAlchemy

Does SQLAlchemy offer a generic way to get the primary key from a declaratively defined instance, so that if:Base = declarative_base()class MyClass(Base):__tablename__ = mytablekey = Column(Integer, pr…

Add column after another column

How can I add a column after another column to a database using Alembic or SQLAlchemy? That would be equivalent to this SQL clause: ALTER TABLE foo CHANGE COLUMN bar bar COLUMN_DEFINITION_HERE AFTER …

Scrapy Images Downloading

My spider runs without displaying any errors but the images are not stored in the folder here are my scrapy files:Spider.py:import scrapy import re import os import urlparse from scrapy.spiders import …