I have a schedule script running a job with:
schedule.every(3).seconds.do(jobCheckSmth)
while True:
schedule.run_pending()
time.sleep(1)
I wanted to use web interface to check on it's status instead of print() on the CLI with
run(host='localhost', port=80, debug=True)
But it blocks code execution so I have to Ctrl-C to break webserver loop to continue to run while loop
Bottle v0.12.18 server starting up (using WSGIRefServer())...
Listening on http://localhost:80/ Hit Ctrl-C to quit.
Here is an example that works really well for me using gevent to turn bottle into async. It's either this, or you have to run schedule in it's own thread outside of your bottle app. But honestly you should be doing this.
import gevent
from gevent import monkey,spawn as gspawn, sleep as gsleep, socket, signal_handler as sig
monkey.patch_all()
import signal
import arrow
from bottle import Bottle, static_file, get, post, request, response, template, redirect, hook, route, abort
from gevent.pywsgi import WSGIServer
from geventwebsocket.handler import WebSocketHandler
def start():
def start_thread():
set()
while 1:
try:
schedule.run_pending()
except:
logger.exception('Scheduler Exception')
gsleep(5) # This is the polling cycle for pending jobs
print('Scheduler Started...')
gspawn(start_thread)
def sample():
gspawn(jobCheckSmth)
def set():
# schedule.every(180).seconds.do(func)
schedule.every().day.at("00:00").do(sample)
logger.info('Started Schedule at {}'.format(arrow.now()))
#get('/')
def app():
return 'Hello World!'
if __name__ == '__main__':
scheduler.start()
botapp = bottle.app()
server = WSGIServer(("0.0.0.0", int(port)), botapp , handler_class=WebSocketHandler)
def shutdown():
print('Shutting down ...')
server.stop(timeout=60)
exit(signal.SIGTERM)
sig(signal.SIGTERM, shutdown)
sig(signal.SIGINT, shutdown)
server.serve_forever()
Related
I have python version 3.9, I am using the default development web server that is provided by the flask on windows OS (laptop). I need to run the app on Windows machine, and it should be able to start and stop the app on demand.
Here is what I tried so far. when I run the application either in VS code, I need to force stop the app, If running in the command windows I have to kill the python app from task manager.
With the following code changes I can exit the app by sending the SIGTERM to proess itself, as both the Flask, and simplehttpserver do not provide an API for exiting the process.
from flask import Flask, request, g, make_response
import queue
import time
import os
from threading import Thread
import signal
import ctypes
from multiprocessing import Process
import sys
OUTPUT_EMPTY_LIMIT = 3
DEFAULT_PORT = 9090
SHUTDOWN = False
EXIT_CHARS = "Qq\x11\x18" # 'Q', 'q', Ctrl-Q, ESC
print("Name = {}".format(__name__))
app = Flask(__name__)
def shutdown_app():
global SHUTDOWN
SHUTDOWN = True
#app.before_request
def before_request():
if SHUTDOWN:
return 'Shutting down...'
def run_app(port, host, debug, threaded):
app.run(port=port, host=host, debug=debug, threaded=threaded)
#app.route("/")
def app_help():
return "Hello, World from help!"
#app.route("/square/<int:number>")
def square(number):
return str(number**2)
# This doesnt kill the app as well.
#app.route("/stop")
def stop():
func = request.environ.get('werkzeug.server.shutdown')
if func is None:
raise RuntimeError('Not running with the werkzeug Server')
func()
return 'Server shutting down...'
exit(0)
if __name__ == "__main__":
port = 5000
host = '0.0.0.0'
debug = False
threaded = True
process = Process(target=run_app, args=(port, host, debug, threaded))
process.start()
while True:
ch = input("Enter 'Q' to stop server and exit: ")
if (ch == 'Q'):
break
shutdown_app()
process.terminate()
#process.kill()
#process.join()
print("Exiting the app")
# os.kill(os.getpid(), signal.SIGINT)
os._exit(0)
I am new to Python and have tried both google search and chatGPT to find a way to shutdown Flask App and the web server that is started on a windows operating system.
I have also tried the other answeres on the stack overflow 15562446
I also tried the link Shutdown The Simple Server
I've found while testing that werkzeug.server.shutdown does nothing, and it needs to be shutdown using sys.exit(0) on my windows machine.
In your code a value is returned before the exit() is run - therefore it does not shut down. One way to do what you want is using deferred callbacks.
from flask import after_this_request
#app.get('/stop')
def shutdown():
#after_this_request
def response_processor(response):
#response.call_on_close
def shutdown():
import sys
sys.exit("Stop API Called")
return response
return 'Server shutting down...',200
I am running WSGIServer using gevent but I also want to run a background worker.
from gevent import monkey
from gevent.pywsgi import WSGIServer
from flask import Flask, request,Response
monkey.patch_all()
from threading import Thread
api = Flask(__name__)
my_queue=[1,2,3]
#api.route('/add', methods=['GET'])
def add():
my_queue.append(request.args.get('item'))
return Response("Added!", 200)
def worker(id):
while True:
print(f"{id}-working-{my_queue}")
if __name__ == '__main__':
server = WSGIServer(('0.0.0.0', 5001), api)
server_thread = Thread(target=server.start)
worker1_thread = Thread(target=worker,args=(1,))
worker2_thread = Thread(target=worker,args=(2,))
server_thread.start()
worker1_thread.start()
worker2_thread.start()
If I am running it without monkey.patch_all(), my two worker works in parallel but the API will not respond.
If I use monkey.patch_all() only the first worker will work and the API will also not respond.
How can I get this to work properly?
Also, I am aware I need a lock for the queue but I don't know how to implement it.
I haven't found a way to set a handler to detect when a flask server is already running. Consider the following code snippet:
import flask
import requests
def on_start():
# send a request to the server, it's safe to do so
# because we know it's already running
r = requests.get("http://localhost:1234")
print(r.text) # hello world
app = flask.Flask(__name__)
#app.route("/")
def hello():
return "hello world"
app.run(port=1234, host="localhost", on_start=on_start)
The last line fails because on_start is not an argument of run, but hopefully you get the idea of what I'm trying to do. How can I do it?
What you can do is wrap the function that you want to kick off with before_first_request decorator as found here ==> http://flask.pocoo.org/docs/1.0/api/#flask.Flask.before_first_request
However, it won't get kicked off until someone makes a request to the server but you can do something like this:
import requests
import threading
import time
from flask import Flask
app = Flask(__name__)
#app.before_first_request
def activate_job():
def run_job():
while True:
print("Run recurring task")
time.sleep(3)
thread = threading.Thread(target=run_job)
thread.start()
#app.route("/")
def hello():
return "Hello World!"
def start_runner():
def start_loop():
not_started = True
while not_started:
print('In start loop')
try:
r = requests.get('http://127.0.0.1:5000/')
if r.status_code == 200:
print('Server started, quiting start_loop')
not_started = False
print(r.status_code)
except:
print('Server not yet started')
time.sleep(2)
print('Started runner')
thread = threading.Thread(target=start_loop)
thread.start()
if __name__ == "__main__":
start_runner()
app.run()
Details & Source via Google-fu: https://networklore.com/start-task-with-flask/
When starting a bottle webserver without a thread or a subprocess, there's no problem. To exit the bottle app -> CTRL + c.
In a thread, how can I programmatically stop the bottle web server ?
I didn't find a stop() method or something like that in the documentation. Is there a reason ?
For the default (WSGIRef) server, this is what I do (actually it is a cleaner approach of Vikram Pudi's suggestion):
from bottle import Bottle, ServerAdapter
class MyWSGIRefServer(ServerAdapter):
server = None
def run(self, handler):
from wsgiref.simple_server import make_server, WSGIRequestHandler
if self.quiet:
class QuietHandler(WSGIRequestHandler):
def log_request(*args, **kw): pass
self.options['handler_class'] = QuietHandler
self.server = make_server(self.host, self.port, handler, **self.options)
self.server.serve_forever()
def stop(self):
# self.server.server_close() <--- alternative but causes bad fd exception
self.server.shutdown()
app = Bottle()
#app.route('/')
def index():
return 'Hello world'
#app.route('/stop') # not working from here, it has to come from another thread
def stopit():
server.stop()
server = MyWSGIRefServer(port=80)
try:
app.run(server=server)
except:
print('Bye')
When I want to stop the bottle application, from another thread, I do the following:
server.stop()
I had trouble closing a bottle server from within a request as bottle seems to run requests in subprocesses.
I eventually found the solution was to do:
sys.stderr.close()
inside the request (that got passed up to the bottle server and axed it).
An updated version of mike's answer.
from bottlepy.bottle import WSGIRefServer, run
from threading import Thread
import time
class MyServer(WSGIRefServer):
def run(self, app): # pragma: no cover
from wsgiref.simple_server import WSGIRequestHandler, WSGIServer
from wsgiref.simple_server import make_server
import socket
class FixedHandler(WSGIRequestHandler):
def address_string(self): # Prevent reverse DNS lookups please.
return self.client_address[0]
def log_request(*args, **kw):
if not self.quiet:
return WSGIRequestHandler.log_request(*args, **kw)
handler_cls = self.options.get('handler_class', FixedHandler)
server_cls = self.options.get('server_class', WSGIServer)
if ':' in self.host: # Fix wsgiref for IPv6 addresses.
if getattr(server_cls, 'address_family') == socket.AF_INET:
class server_cls(server_cls):
address_family = socket.AF_INET6
srv = make_server(self.host, self.port, app, server_cls, handler_cls)
self.srv = srv ### THIS IS THE ONLY CHANGE TO THE ORIGINAL CLASS METHOD!
srv.serve_forever()
def shutdown(self): ### ADD SHUTDOWN METHOD.
self.srv.shutdown()
# self.server.server_close()
def begin():
run(server=server)
server = MyServer(host="localhost", port=8088)
Thread(target=begin).start()
time.sleep(2) # Shut down server after 2 seconds
server.shutdown()
The class WSGIRefServer is entirely copied with only 1 line added to the run() method is added. Also add a simple shutdown() method. Unfortunately, this is necessary because of the way bottle creates the run() method.
You can make your thread a daemon by setting the daemon property to True before calling start.
mythread = threading.Thread()
mythread.daemon = True
mythread.start()
A deamon thread will stop whenever the main thread that it is running in is killed or dies. The only problem is that you won't be able to make the thread run any code on exit and if the thread is in the process of doing something, it will be stopped immediately without being able to finish the method it is running.
There's no way in Python to actually explicitly stop a thread. If you want to have more control over being able to stop your server you should look into Python Processes from the multiprocesses module.
Since bottle doesn't provide a mechanism, it requires a hack. This is perhaps the cleanest one if you are using the default WSGI server:
In bottle's code the WSGI server is started with:
srv.serve_forever()
If you have started bottle in its own thread, you can stop it using:
srv.shutdown()
To access the srv variable in your code, you need to edit the bottle source code and make it global. After changing the bottle code, it would look like:
srv = None #make srv global
class WSGIRefServer(ServerAdapter):
def run(self, handler): # pragma: no cover
global srv #make srv global
...
Here's one option: provide custom server (same as default), that records itself:
import bottle
class WSGI(bottle.WSGIRefServer):
instances = []
def run(self, *args, **kw):
self.instances.append(self)
super(WSGI, self).run(*args, **kw)
# some other thread:
bottle.run(host=ip_address, port=12345, server=WSGI)
# control thread:
logging.warn("servers are %s", WSGI.instances)
This is exactly the same method than sepero's and mike's answer, but now much simpler with Bottle version 0.13+:
from bottle import W, run, route
from threading import Thread
import time
#route('/')
def index():
return 'Hello world'
def shutdown():
time.sleep(5)
server.srv.shutdown()
server = WSGIRefServer(port=80)
Thread(target=shutdown).start()
run(server=server)
Also related: https://github.com/bottlepy/bottle/issues/1229 and https://github.com/bottlepy/bottle/issues/1230.
Another example with a route http://localhost/stop to do the shutdown:
from bottle import WSGIRefServer, run, route
from threading import Thread
#route('/')
def index():
return 'Hello world'
#route('/stop')
def stopit():
Thread(target=shutdown).start()
def shutdown():
server.srv.shutdown()
server = WSGIRefServer(port=80)
run(server=server)
PS: it requires at least Bottle 0.13dev.
Console log of Bottle server tells us that the official way of shutting down the server is "Hit Ctrl-C":
Bottle v0.12.19 server starting up (using WSGIRefServer())...
Listening on http://localhost:8080/
Hit Ctrl-C to quit.
Why not simply follow it programmatically?
Hitting "Ctrl-C" is nothing but sending SIGINT to the process, and we can achieve it with just built-in modules:
Get current PID with os.getpid().
Kill the process with os.kill(). Remember passing SIGINT so it will be exactly same as "Hit Ctrl-C".
Wrap the 'kill' in another thread and start it after a few seconds so the client won't get error.
Here is the server code:
from bottle import route, run
import os
import signal
from threading import Thread
import time
#route('/hello')
def return_hello():
return 'Hello'
#route('/stop')
def handle_stop_request():
# Handle "stop server" request from client: start a new thread to stop the server
Thread(target=shutdown_server).start()
return ''
def shutdown_server():
time.sleep(2)
pid = os.getpid() # Get process ID of the current Python script
os.kill(pid, signal.SIGINT)
# Kill the current script process with SIGINT, which does same as "Ctrl-C"
run(host='localhost', port=8080)
Here is the client code:
import requests
def request_service(service_key):
url = f'http://127.0.0.1:8080/{service_key}'
response = requests.get(url)
content = response.content.decode('utf-8')
print(content)
request_service('hello')
request_service('stop')
Note that in function "handle_stop_request" we didn't stop the server immediately but rather started a thread then returned empty string. With this mechanism, when a client requests "http://127.0.0.1:8080/stop", it can get response (the empty string) normally. After that, the server will shutdown. If we otherwise shutdown the server in function "handle_stop_request", the server will close the connection before returning to the client, and hence the client will get "ConnectionError".
Server side output:
Bottle v0.12.19 server starting up (using WSGIRefServer())...
Listening on http://localhost:8080/
Hit Ctrl-C to quit.
127.0.0.1 - - [23/Nov/2021 11:18:08] "GET /hello HTTP/1.1" 200 5
127.0.0.1 - - [23/Nov/2021 11:18:08] "GET /stop HTTP/1.1" 200 0
Client side output:
Hello
The code was tested under Python 3.7 and Bottle 0.12.
I suppose that the bottle webserver runs forever until it terminates. There are no methonds like stop().
But you can make something like this:
from bottle import route, run
import threading, time, os, signal, sys, operator
class MyThread(threading.Thread):
def __init__(self, target, *args):
threading.Thread.__init__(self, target=target, args=args)
self.start()
class Watcher:
def __init__(self):
self.child = os.fork()
if self.child == 0:
return
else:
self.watch()
def watch(self):
try:
os.wait()
except KeyboardInterrupt:
print 'KeyBoardInterrupt'
self.kill()
sys.exit()
def kill(self):
try:
os.kill(self.child, signal.SIGKILL)
except OSError: pass
def background_process():
while 1:
print('background thread running')
time.sleep(1)
#route('/hello/:name')
def index(name='World'):
return '<b>Hello %s!</b>' % name
def main():
Watcher()
MyThread(background_process)
run(host='localhost', port=8080)
if __name__ == "__main__":
main()
Then you can use Watcher.kill() when you need to kill your server.
Here is the code of run() function of the bottle:
try:
app = app or default_app()
if isinstance(app, basestring):
app = load_app(app)
if not callable(app):
raise ValueError("Application is not callable: %r" % app)
for plugin in plugins or []:
app.install(plugin)
if server in server_names:
server = server_names.get(server)
if isinstance(server, basestring):
server = load(server)
if isinstance(server, type):
server = server(host=host, port=port, **kargs)
if not isinstance(server, ServerAdapter):
raise ValueError("Unknown or unsupported server: %r" % server)
server.quiet = server.quiet or quiet
if not server.quiet:
stderr("Bottle server starting up (using %s)...\n" % repr(server))
stderr("Listening on http://%s:%d/\n" % (server.host, server.port))
stderr("Hit Ctrl-C to quit.\n\n")
if reloader:
lockfile = os.environ.get('BOTTLE_LOCKFILE')
bgcheck = FileCheckerThread(lockfile, interval)
with bgcheck:
server.run(app)
if bgcheck.status == 'reload':
sys.exit(3)
else:
server.run(app)
except KeyboardInterrupt:
pass
except (SyntaxError, ImportError):
if not reloader: raise
if not getattr(server, 'quiet', False): print_exc()
sys.exit(3)
finally:
if not getattr(server, 'quiet', False): stderr('Shutdown...\n')
As you can see there are no other way to get off the run loop, except some exceptions.
The server.run function depends on the server you use, but there are no universal quit-method anyway.
This equally kludgy hack has the advantage that is doesn't have you copy-paste any code from bottle.py:
# The global server instance.
server = None
def setup_monkey_patch_for_server_shutdown():
"""Setup globals to steal access to the server reference.
This is required to initiate shutdown, unfortunately.
(Bottle could easily remedy that.)"""
# Save the original function.
from wsgiref.simple_server import make_server
# Create a decorator that will save the server upon start.
def stealing_make_server(*args, **kw):
global server
server = make_server(*args, **kw)
return server
# Patch up wsgiref itself with the decorated function.
import wsgiref.simple_server
wsgiref.simple_server.make_server = stealing_make_server
setup_monkey_patch_for_server_shutdown()
def shutdown():
"""Request for the server to shutdown."""
server.shutdown()
I've found this solution to be the easiest, but it does require that the "psutil" package is installed, to get the current process. It also requires the "signals" module, but that's part of the standard library.
#route('/shutdown')
def shutdown():
current_process = psutil.Process()
current_process.send_signal(signal.CTRL_C_EVENT)
return 'Shutting down the web server'
Hope that's of use to someone!
This question was top in my google search, so i will post my answer:
When the server is started with the Bottle() class, it has a method close() to stop the server. From the source code:
""" Close the application and all installed plugins. """
For example:
class Server:
def __init__(self, host, port):
self._host = host
self._port = port
self._app = Bottle()
def stop(self):
# close ws server
self._app.close()
def foo(self):
# More methods, routes...
Calling stop method will stop de server.
This question already has answers here:
Stopping a tornado application
(3 answers)
Closed 1 year ago.
I've been playing around a bit with the Tornado web server and have come to a point where I want to stop the web server (for example during unit testing). The following simple example exists on the Tornado web page:
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
application = tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
Once tornado.ioloop.IOLoop.instance().start() is called, it blocks the program (or current thread). Reading the source code for the IOLoop object gives this example in the documentation for the stop function:
To use asynchronous methods from otherwise-synchronous code (such as
unit tests), you can start and stop the event loop like this:
ioloop = IOLoop()
async_method(ioloop=ioloop, callback=ioloop.stop)
ioloop.start()
ioloop.start() will return after async_method has run its callback,
whether that callback was invoked before or after ioloop.start.
However, I have no idea how to integrate this into my program. I actually have a class that encapsulates the web server (having it's own start and stop functions), but as soon as I call start, the program (or tests) will of course block anyway.
I've tried to start the web server in another process (using the multiprocessing package). This is the class that is wrapping the web server:
class Server:
def __init__(self, port=8888):
self.application = tornado.web.Application([ (r"/", Handler) ])
def server_thread(application, port):
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(port)
tornado.ioloop.IOLoop.instance().start()
self.process = Process(target=server_thread,
args=(self.application, port,))
def start(self):
self.process.start()
def stop(self):
ioloop = tornado.ioloop.IOLoop.instance()
ioloop.add_callback(ioloop.stop)
However, stop does not seem to entirely stop the web server since it is still running in the next test, even with this test setup:
def setup_method(self, _function):
self.server = Server()
self.server.start()
time.sleep(0.5) # Wait for web server to start
def teardown_method(self, _function):
self.kstore.stop()
time.sleep(0.5)
How can I start and stop a Tornado web server from within a Python program?
I just ran into this and found this issue myself, and using info from this thread came up with the following. I simply took my working stand alone Tornado code (copied from all the examples) and moved the actual starting code into a function. I then called the function as a threading thread. My case different as the threading call was done from my existing code where I just imported the startTornado and stopTornado routines.
The suggestion above seemed to work great, so I figured I would supply the missing example code. I tested this code under Linux on a FC16 system (and fixed my initial type-o).
import tornado.ioloop, tornado.web
class Handler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
application = tornado.web.Application([ (r"/", Handler) ])
def startTornado():
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
def stopTornado():
tornado.ioloop.IOLoop.instance().stop()
if __name__ == "__main__":
import time, threading
threading.Thread(target=startTornado).start()
print "Your web server will self destruct in 2 minutes"
time.sleep(120)
stopTornado()
Hope this helps the next person.
Here is the solution how to stop Torando from another thread. Schildmeijer provided a good hint, but it took me a while to actually figure the final example that works.
Please see below:
import threading
import tornado.ioloop
import tornado.web
import time
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world!\n")
def start_tornado(*args, **kwargs):
application = tornado.web.Application([
(r"/", MainHandler),
])
application.listen(8888)
print "Starting Torando"
tornado.ioloop.IOLoop.instance().start()
print "Tornado finished"
def stop_tornado():
ioloop = tornado.ioloop.IOLoop.instance()
ioloop.add_callback(ioloop.stop)
print "Asked Tornado to exit"
def main():
t = threading.Thread(target=start_tornado)
t.start()
time.sleep(5)
stop_tornado()
t.join()
if __name__ == "__main__":
main()
In case you do no want to bother with threads, you could catch a keyboard interrupt signal :
try:
tornado.ioloop.IOLoop.instance().start()
# signal : CTRL + BREAK on windows or CTRL + C on linux
except KeyboardInterrupt:
tornado.ioloop.IOLoop.instance().stop()
There is a problem with Zaar Hai's solution, namely that it leaves the socket open. The reason I was looking for a solution to stop Tornado is I'm running unit tests against my app server and I needed a way to start/stop the server between tests to have a clear state (empty session, etc.). By leaving the socket open, the second test always ran into an Address already in use error. So I came up with the following:
import logging as log
from time import sleep
from threading import Thread
import tornado
from tornado.httpserver import HTTPServer
server = None
thread = None
def start_app():
def start():
global server
server = HTTPServer(create_app())
server.listen(TEST_PORT, TEST_HOST)
tornado.ioloop.IOLoop.instance().start()
global thread
thread = Thread(target=start)
thread.start()
# wait for the server to fully initialize
sleep(0.5)
def stop_app():
server.stop()
# silence StreamClosedError Tornado is throwing after it is stopped
log.getLogger().setLevel(log.FATAL)
ioloop = tornado.ioloop.IOLoop.instance()
ioloop.add_callback(ioloop.stop)
thread.join()
So the main idea here is to keep a reference to the HTTPServer instance and call its stop() method. And create_app() just returns an Application instance configured with handlers. Now you can use these methods in your unit tests like this:
class FoobarTest(unittest.TestCase):
def setUp(self):
start_app()
def tearDown(self):
stop_app()
def test_foobar(self):
# here the server is up and running, so you can make requests to it
pass
To stop the entire ioloop you simply invoke the ioloop.stop method when you have finished the unit test. (Remember that the only (documented) thread safe method is ioloop.add_callback, ie. if the unit tests is executed by another thread, you could wrap the stop invocation in a callback)
If its enough to stop the http web server you invoke the httpserver.stop() method
If you need this behavior for unit testing, take a look at tornado.testing.AsyncTestCase.
By default, a new IOLoop is constructed for each test and is available as self.io_loop. This IOLoop should be used in the construction of HTTP clients/servers, etc. If the code being tested requires a global IOLoop, subclasses should override get_new_ioloop to return it.
If you need to start and stop an IOLoop for some other purpose and can't call ioloop.stop() from a callback for some reason, a multi-threaded implementation is possible. To avoid race conditions, however, you need to synchronize access to the ioloop, which is actually impossible. Something like the following will result in deadlock:
Thread 1:
with lock:
ioloop.start()
Thread 2:
with lock:
ioloop.stop()
because thread 1 will never release the lock (start() is blocking) and thread 2 will wait till the lock is released to stop the ioloop.
The only way to do it is for thread 2 to call ioloop.add_callback(ioloop.stop), which will call stop() on thread 1 in the event loop's next iteration. Rest assured, ioloop.add_callback() is thread-safe.
Tornado's IOloop.instance() has trouble stopping from an external signal when run under multiprocessing.Process.
The only solution I came up with that works consistently, is by using Process.terminate():
import tornado.ioloop, tornado.web
import time
import multiprocessing
class Handler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
application = tornado.web.Application([ (r"/", Handler) ])
class TornadoStop(Exception):
pass
def stop():
raise TornadoStop
class worker(multiprocessing.Process):
def __init__(self):
multiprocessing.Process.__init__(self)
application.listen(8888)
self.ioloop = tornado.ioloop.IOLoop.instance()
def run(self):
self.ioloop.start()
def stop(self, timeout = 0):
self.ioloop.stop()
time.sleep(timeout)
self.terminate()
if __name__ == "__main__":
w = worker()
print 'starting server'
w.start()
t = 2
print 'waiting {} seconds before stopping'.format(t)
for i in range(t):
time.sleep(1)
print i
print 'stopping'
w.stop(1)
print 'stopped'
We want to use a multiprocessing.Process with a tornado.ioloop.IOLoop to work around the cPython GIL for performance and independency. To get access to the IOLoop we need to use Queue to pass the shutdown signal through.
Here is a minimalistic example:
class Server(BokehServer)
def start(self, signal=None):
logger.info('Starting server on http://localhost:%d'
% (self.port))
if signal is not None:
def shutdown():
if not signal.empty():
self.stop()
tornado.ioloop.PeriodicCallback(shutdown, 1000).start()
BokehServer.start(self)
self.ioloop.start()
def stop(self, *args, **kwargs): # args important for signals
logger.info('Stopping server...')
BokehServer.stop(self)
self.ioloop.stop()
The Process
import multiprocessing as mp
import signal
from server import Server # noqa
class ServerProcess(mp.Process):
def __init__(self, *args, **kwargs):
self.server = Server(*args, **kwargs)
self.shutdown_signal = _mp.Queue(1)
mp.Process.__init__(self)
signal.signal(signal.SIGTERM, self.server.stop)
signal.signal(signal.SIGINT, self.server.stop)
def run(self):
self.server.start(signal=self.shutdown_signal)
def stop(self):
self.shutdown_signal.put(True)
if __name__ == '__main__':
p = ServerProcess()
p.start()
Cheers!
Just add this before the start():
IOLoop.instance().add_timeout(10,IOLoop.instance().stop)
It will register the stop function as a callback in the loop and lauch it 10 second after the start