CherryPy interferes with Twisted shutting down on Windows - python

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, SIGINT
import cherrypy
from twisted.internet import reactor
from twisted.web.client import getPage
def stop(signum, frame):
cherrypy.engine.exit()
reactor.callFromThread(reactor.stop)
signal(SIGINT, stop)
class Root:
#cherrypy.expose
def 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, SIGINT
from twisted.internet import reactor
from twisted.web.client import getPage
keep_going = True
def stop(signum, frame):
global keep_going
keep_going = False
reactor.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?

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()

Related

Gracefully stopping greenlets in Windows service

Using pywin32 and gevent, I'm creating a Windows service that serves two functions:
It runs a web server for a simple web application (using bottle's gevent server adapter, which runs WSGIServer's serve_forever()).
It listens for incoming SIP calls (using a gevent-based SIP client) and runs some simple code to respond to calls.
I'd like the service to just keep the web server and SIP client running forever, but stop both immediately and gracefully if I try to stop the Windows service. This seems like it should be pretty simple.
What I'm currently doing to run the app is basically to run the web server and SIP client each in a greenlet, and run kill on both greenlets when I want to stop the app (simplified mockup):
from bottle import run
from mysipclient import SipClient
import gevent
def sip_listen():
client = SipClient()
try:
client.wait() # This method blocks on a gevent queue.get call
finally:
client.close() # This does some cleanup, like deregistering from the SIP server, that I really want to run when the service stops!
class App(object):
def start(self):
self.stop_event = gevent.event.Event()
self.server_greenlet = gevent.spawn(run, server='gevent', host='0.0.0.0', port=8080)
self.sip_greenlet = gevent.spawn(sip_listen)
gevent.joinall([self.server_greenlet, self.sip_greenlet])
def stop(self):
self.server_greenlet.kill()
self.sip_greenlet.kill()
if __name__ == "__main__":
app = App()
gevent.spawn_later(10, app.stop)
app.start()
If I run this from the command line, it works great: it starts the app with both greenlets working, then ten seconds later it shuts itself down, running the cleanup code for the SIP client and all.
Now, though, I try to make this into a Windows service, using pywin32's win32serviceutil:
import win32serviceutil
import win32service
import win32event
import servicemanager
from app import App
class TestService(win32serviceutil.ServiceFramework):
_svc_name_ = 'TestService'
_svc_display_name_ = 'TestService'
def __init__(self, args):
win32serviceutil.ServiceFramework.__init__(self, args)
self.service_obj = App()
def SvcStop(self):
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
self.service_obj.stop()
def SvcDoRun(self):
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STARTED, (self._svc_name_, ''))
self.service_obj.start()
if __name__ == '__main__':
if len(sys.argv) == 1:
servicemanager.Initialize()
servicemanager.PrepareToHostSingle(TestService)
servicemanager.StartServiceCtrlDispatcher()
else:
win32serviceutil.HandleCommandLine(TestService)
When I install this as a service and run it, I get an exception when trying to kill the greenlets: LoopExit: This operation would block forever. Then the service fails to stop, and I have to kill it manually. (I can avoid this by catching the exception, using an event instead of joining the greenlets and setting the event on stop - but this means the cleanup doesn't run at all.)
I'm pretty new to both gevent and working with Windows services, and Google hasn't been terribly helpful. I thought maybe the difference was about how with the command line version I was running stop in another greenlet, so I tried replacing self.service_obj.stop() in SvcStop with gevent.spawn(self.service_obj.stop).join(), but that way it doesn't even throw the exception and just hangs completely until I kill the process.
What's going on here? Am I doing something fundamentally wrong? How do I stop the greenlets gracefully on SvcStop?
You need to trap the signal event
from gevent import signal
def shutdown():
print('Shutting down ...')
server.stop(timeout=60)
exit(signal.SIGTERM)
gevent.signal(signal.SIGTERM, shutdown)
gevent.signal(signal.SIGQUIT, shutdown)
gevent.signal(signal.SIGINT, shutdown) #CTRL C
... start server code

Create process in tornado web server

I have a multiproccessing tornado web server and I want to create another process that will do some things in the background.
I have a server with to following code
start_background_process
app = Application([<someurls>])
server = HTTPServer(app)
server.bind(8888)
server.start(4) # Forks multiple sub-processes
IOLoop.current().start()
def start_background_process():
process = multiprocessing.Process(target=somefunc)
process.start()
and everything is working great.
However when I try to close the server (by crtl c or send signal)
I get AssertionError: can only join a child process
I understood the cause of this problem:
when I create a process with multiprocess a call for the process join method
is registered in "atexit" and because tornado does a simple fork all its childs also call the join method of the process I created and the can't since the process is their brother and not their son?
So how can I open a process normally in tornado?
"HTTPTserver start" uses os.fork to fork the 4 sub-processes as it can be seen in its source code.
If you want your method to be executed by all the 4 sub-processes, you have to call it after the processes have been forked.
Having that in mind your code can be changed to look as below:
import multiprocessing
import tornado.web
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
# A simple external handler as an example for completion
from handlers.index import IndexHandler
def method_on_sub_process():
print("Executing in sub-process")
def start_background_process():
process = multiprocessing.Process(target=method_on_sub_process)
process.start()
def main():
app = tornado.web.Application([(r"/", IndexHandler)])
server = HTTPServer(app)
server.bind(8888)
server.start(4)
start_background_process()
IOLoop.current().start()
if __name__ == "__main__":
main()
Furthermore to keep the behavior of your program clean during any keyboard interruption , surround the instantiation of the sever by a try...except clause as below:
def main():
try:
app = tornado.web.Application([(r"/", IndexHandler)])
server = HTTPServer(app)
server.bind(8888)
server.start(4)
start_background_process()
IOLoop.current().start()
except KeyboardInterrupt:
IOLoop.instance().stop()

How can I run a local server and open urls from the same python program?

I want to start a local server and then open a link with a browser from the same python program.
This is what I tried (a very naive and foolish attempt):
from subprocess import call
import webbrowser
call(["python", "-m", "SimpleHTTPServer"]) #This creates a server at port:8000
webbrowser.open_new_tab("some/url")
However, the program doesn't go to the second statement because the server is still running in the background. To open the browser, I need to exit the server which defeats the purpose of running the server.
Can anyone help me by suggesting a working solution?
You could start your web server in a daemon thread (a Python program exits if only daemon threads are left) and then make your requests from the main thread.
The only problem then is to synchronize your main thread to the server thread, since the HTTP-server will need some time to start up and won't handle any requests until this point. I am not aware of an easy and clean solution to do that, but you could (somewhat hackish) just pause your main thread for a number seconds (possibly shorter) and start making requests only after this. Another option would be to just send requests to the webserver from the very beginning and expect them to fail for some amount of time.
Here is a small sample script with a simple HTTP webserver that serves content from the local file system over TCP on localhost:8080 and a sample request, requesting a file foo.txt from the directory the webserver (and in this case also the script) was started in.
import sys
import requests
import threading
import time
from BaseHTTPServer import HTTPServer
from SimpleHTTPServer import SimpleHTTPRequestHandler
# set up the HTTP server and start it in a separate daemon thread
httpd = HTTPServer(('localhost', 8080), SimpleHTTPRequestHandler)
thread = threading.Thread(target=httpd.serve_forever)
thread.daemon = True
# if startup time is too long we might want to be able to quit the program
try:
thread.start()
except KeyboardInterrupt:
httpd.shutdown()
sys.exit(0)
# wait until the webserver finished starting up (maybe wait longer or shorter...)
time.sleep(5)
# start sending requests
r = requests.get('http://localhost:8080/foo.txt')
print r.status_code
# => 200 (hopefully...)

Handling stdin with tornado

How to listen for events that happen on stdin in Tornado loop?
In particular, in a tornado-system, I want to read from stdin, react on it, and terminate if stdin closes. At the same time, the Tornado web service is running on the same process.
While looking for this, the most similar I could find was handling streams of an externally spawned process. However, this is not what I want: I want to handle i/o stream of the current process, i.e. the one that has the web server.
Structurally, my server is pretty much hello-world tornado, so we can base the example off that. I just need to add an stdin handler.
You can use the add_handler method on the IOLoop instance to watch for events on stdin.
Here's a minimal working example:
from tornado.ioloop import IOLoop
from tornado.web import Application, RequestHandler
import sys
class MainHandler(RequestHandler):
def get(self):
self.finish("foo")
application = Application([
(r"/", MainHandler),
])
def on_stdin(fd, events):
content = fd.readline()
print "received: %s" % content
if __name__ == "__main__":
application.listen(8888)
IOLoop.instance().add_handler(sys.stdin, on_stdin, IOLoop.READ)
IOLoop.instance().start()

Threading/Multiprocessing in Python

I have the following code:
import SimpleHTTPServer
import SocketServer
def http_server():
PORT = 80
Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
httpd = SocketServer.TCPServer(("", PORT), Handler)
httpd.serve_forever()
The problem with this is that, because of httpd.serve_forever(), it hangs the rest of the program. I'm assuming I could use threading to run this on its own thread, so the rest of the program can execute independently of the server, but I'm not sure how to implement this.
Simplest way, straight from the docs:
from threading import Thread
t = Thread(target=http_server)
t.start()
Note that this thread will be difficult to kill as-is, KeyboardInterrupts do not propagate to random threads that you've start()ed. You may want to set daemon=True or have some more sophisticated method to shut it down.

Categories

Resources