Integrated server without threads and busy waiting
This commit is contained in:
parent
3a4184d1ab
commit
bbe71c1ad1
@ -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
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user