Fix threading to avoid race-conditions and handle signals correctly.

This commit is contained in:
René 'Necoro' Neumann 2011-04-03 12:18:43 +02:00
parent 20d21ea01c
commit 6c87df3364

View File

@ -85,36 +85,54 @@ if options.daemon:
sys.exit() sys.exit()
sys.stdout = sys.stderr = open(os.devnull, "w") sys.stdout = sys.stderr = open(os.devnull, "w")
# Launch calendar servers # Create calendar servers
servers = [] servers = []
server_class = radicale.HTTPSServer if options.ssl else radicale.HTTPServer server_class = radicale.HTTPSServer if options.ssl else radicale.HTTPServer
def exit():
"""Cleanly shutdown servers."""
while servers:
servers.pop().shutdown()
def serve_forever(server):
"""Serve a server forever with no traceback on keyboard interrupts."""
try:
server.serve_forever()
except KeyboardInterrupt:
# No unwanted traceback
pass
finally:
exit()
# Clean exit on SIGTERM
signal.signal(signal.SIGTERM, lambda *_: exit())
for host in options.hosts.split(','): for host in options.hosts.split(','):
address, port = host.strip().rsplit(':', 1) address, port = host.strip().rsplit(':', 1)
address, port = address.strip('[] '), int(port) address, port = address.strip('[] '), int(port)
servers.append(server_class((address, port), radicale.CalendarHTTPHandler)) servers.append(server_class((address, port), radicale.CalendarHTTPHandler))
for server in servers[:-1]: # this event marks that the program should be shut down
# More servers to come, launch a new thread server_exited = threading.Event()
# SIGTERM and SIGINT (aka KeyboardInterrupt) should just mark this for shutdown
signal.signal(signal.SIGTERM, lambda *_: server_exited.set())
signal.signal(signal.SIGINT, lambda *_: server_exited.set())
def serve_forever(server):
"""Serve a server forever, and mark the process for shut down if things go wrong."""
try:
server.serve_forever()
finally:
server_exited.set()
# start the servers in a different loop to avoid possible race-conditions,
# when a server exists but another server is added to the list at the same time
for server in servers:
threading.Thread(target=serve_forever, args=(server,)).start() threading.Thread(target=serve_forever, args=(server,)).start()
# Last server, no more thread # mainloop: wait until all servers are exited
serve_forever(servers[-1]) # we must do the busy-waiting here, as all ".join()"-calls
# completly block the thread, such that signals are not received
try:
while True:
# the number is irrelevant -- the only thing that matters, is that it is
# larger than 0.05
# this is due to python implementing its own busy-waiting logic
server_exited.wait(10.0)
if server_exited.is_set():
break
finally:
#
# Cleanly shutdown server
#
# ignore signals, s.t. they cannot interfere
signal.signal(signal.SIGINT, signal.SIG_IGN)
signal.signal(signal.SIGTERM, signal.SIG_IGN)
for server in servers:
server.shutdown()