--
Sean M. Collins
Core IT Pro, LLC
www.coreitpro.com
Killing a running server is usually done with the SIGINT
(KeyboardInterrupt) signal. If your server is running in the main
thread, you can kill it by calling thread.interrupt_main() from a
different thread. If you run the server in a side-thread, you have to
tell the server somehow to stop on its own. This depends on the server
adapter used and may require you to start the server yourself instead of
using a server adapter.
For some background, I'm only using Bottle in development mode with
the built-in WSGIServer. I have a single file application very similar
to the Hello World example. Before doing anything else, it starts a
background thread that needs to continue running until the server is
killed. The issue is that the Bottle server gets the KeyboardInterrupt
first, so my application never has a chance to kill the background
process and it just goes on forever until I manually kill it.
The problem is actually a combination of a bug and a malfeature in
Python. Fortunately, I found a simple recipe on ActiveState by Allen
Downey that solves it. Unfortunately, this only works on UNIX-like
systems (so not on Windows). In case it helps someone in the future,
here is a working Bottle example:
----
#!/usr/bin/env python
from bottle import route, run
import threading, time, os, signal, sys, operator
# Workaround for missed SIGINT in multithreaded programs
# from Allen Downey (released under the PSF license)
# http://code.activestate.com/recipes/496735-workaround-for-missed-sigint-in-multithreaded-prog/
class MyThread(threading.Thread):
"""this is a wrapper for threading.Thread that improves
the syntax for creating and starting threads.
"""
def __init__(self, target, *args):
threading.Thread.__init__(self, target=target, args=args)
self.start()
class Watcher:
"""this class solves two problems with multithreaded
programs in Python, (1) a signal might be delivered
to any thread (which is just a malfeature) and (2) if
the thread that gets the signal is waiting, the signal
is ignored (which is a bug).
The watcher is a concurrent process (not thread) that
waits for a signal and the process that contains the
threads. See Appendix A of The Little Book of Semaphores.
http://greenteapress.com/semaphores/
I have only tested this on Linux. I would expect it to
work on the Macintosh and not work on Windows.
"""
def __init__(self):
""" Creates a child thread, which returns. The parent
thread waits for a KeyboardInterrupt and then kills
the child thread.
"""
self.child = os.fork()
if self.child == 0:
return
else:
self.watch()
def watch(self):
try:
os.wait()
except KeyboardInterrupt:
# I put the capital B in KeyBoardInterrupt so I can
# tell when the Watcher gets the SIGINT
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()