Integrated server without threads and busy waiting

This commit is contained in:
Unrud 2016-01-27 07:17:38 +01:00
parent 3a4184d1ab
commit bbe71c1ad1

View File

@ -28,8 +28,9 @@ import atexit
import os import os
import sys import sys
import optparse import optparse
import select
import signal import signal
import threading import socket
from wsgiref.simple_server import make_server from wsgiref.simple_server import make_server
from . import ( from . import (
@ -137,69 +138,71 @@ def run():
atexit.register(cleanup) atexit.register(cleanup)
log.LOGGER.info("Starting Radicale") log.LOGGER.info("Starting Radicale")
log.LOGGER.debug(
"Base URL prefix: %s" % config.get("server", "base_prefix"))
# Create collection servers # Create collection servers
servers = [] servers = {}
server_class = ( server_class = (
HTTPSServer if config.getboolean("server", "ssl") else HTTPServer) HTTPSServer if config.getboolean("server", "ssl") else HTTPServer)
shutdown_program = threading.Event() shutdown_program = [False]
for host in config.get("server", "hosts").split(","): for host in config.get("server", "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 = make_server(address, port, Application(),
make_server(address, port, Application(), server_class, RequestHandler)
server_class, RequestHandler)) servers[server.socket] = server
# SIGTERM and SIGINT (aka KeyboardInterrupt) should just mark this for
# shutdown
signal.signal(signal.SIGTERM, lambda *_: shutdown_program.set())
signal.signal(signal.SIGINT, lambda *_: shutdown_program.set())
def serve_forever(server):
"""Serve a server forever, cleanly shutdown when things go wrong."""
try:
server.serve_forever()
finally:
shutdown_program.set()
log.LOGGER.debug(
"Base URL prefix: %s" % config.get("server", "base_prefix"))
# 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:
log.LOGGER.debug( log.LOGGER.debug(
"Listening to %s port %s" % ( "Listening to %s port %s" % (
server.server_name, server.server_port)) server.server_name, server.server_port))
if config.getboolean("server", "ssl"): if config.getboolean("server", "ssl"):
log.LOGGER.debug("Using SSL") log.LOGGER.debug("Using SSL")
threading.Thread(target=serve_forever, args=(server,)).start()
log.LOGGER.debug("Radicale server ready") # Create a socket pair to notify the select syscall of program shutdown
# This is not available in python < 3.5 on Windows
# Main loop: wait until all servers are exited if hasattr(socket, "socketpair"):
try: shutdown_program_socket_in, shutdown_program_socket_out = \
# We must do the busy-waiting here, as all ``.join()`` calls completly socket.socketpair()
# block the thread, such that signals are not received else:
while True: shutdown_program_socket_in, shutdown_program_socket_out = None, None
# The number is irrelevant, it only needs to be greater than 0.05
# due to python implementing its own busy-waiting logic
shutdown_program.wait(5.0)
if shutdown_program.is_set():
break
finally:
# Ignore signals, so that they cannot interfere
signal.signal(signal.SIGINT, signal.SIG_IGN)
signal.signal(signal.SIGTERM, signal.SIG_IGN)
# SIGTERM and SIGINT (aka KeyboardInterrupt) should just mark this for
# shutdown
def shutdown(*_):
if shutdown_program[0]:
# Ignore following signals
return
log.LOGGER.info("Stopping Radicale") log.LOGGER.info("Stopping Radicale")
shutdown_program[0] = True
if shutdown_program_socket_in:
shutdown_program_socket_in.sendall(b"goodbye")
signal.signal(signal.SIGTERM, shutdown)
signal.signal(signal.SIGINT, shutdown)
for server in servers: # Main loop: wait for requests on any of the servers or program shutdown
log.LOGGER.debug( sockets = list(servers.keys())
"Closing server listening to %s port %s" % ( if shutdown_program_socket_out:
server.server_name, server.server_port)) # Use socket pair to get notified of program shutdown
server.shutdown() sockets.append(shutdown_program_socket_out)
select_timeout = None
else:
# Fallback to busy waiting
select_timeout = 1.0
log.LOGGER.debug("Radicale server ready")
while not shutdown_program[0]:
try:
rlist, _, xlist = select.select(sockets, [], sockets,
select_timeout)
except (KeyboardInterrupt, select.error):
# SIGINT ist handled by signal handler above
rlist, _, xlist = [], [], []
if xlist:
raise RuntimeError("Unhandled socket error")
if rlist:
server = servers.get(rlist[0])
if server:
server.handle_request()
# pylint: enable=R0912,R0914 # pylint: enable=R0912,R0914