From 396d7c3721bd0cd85b534ea5c0a8396f4e0c9912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20=27Necoro=27=20Neumann?= Date: Tue, 8 Feb 2011 19:27:00 +0100 Subject: [PATCH 01/15] First support for IPv6 and multiple interfaces --- radicale.py | 39 ++++++++++++++++++++++++++++++++++++--- radicale/__init__.py | 31 ++++++++++++++++++++++++------- 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/radicale.py b/radicale.py index 03f6a147..c5a9025c 100755 --- a/radicale.py +++ b/radicale.py @@ -38,6 +38,7 @@ arguments. import os import sys import optparse +import threading, signal import radicale @@ -97,8 +98,40 @@ if options.daemon: sys.exit() sys.stdout = sys.stderr = open(os.devnull, "w") +def exit (servers): + """Cleanly shutdown all servers. + + Might be called multiple times.""" + for s in servers: + s.shutdown() + # Launch calendar server server_class = radicale.HTTPSServer if options.ssl else radicale.HTTPServer -server = server_class( - (options.host, options.port), radicale.CalendarHTTPHandler) -server.serve_forever() +servers = [] +threads = [] + +for host in (x.strip() for x in options.host.split(',')): + try: + server = server_class( + (host, options.port), radicale.CalendarHTTPHandler) + servers.append(server) + + t = threading.Thread(target = server.serve_forever) + threads.append(t) + t.start() + except: + exit(servers) + raise + +# clean exit on SIGTERM +signal.signal(signal.SIGTERM, lambda *a: exit(servers)) + +try: + while threads: + threads[0].join(1) # try one second + if threading.active_count() <= len(threads): # one thread died + break +except KeyboardInterrupt: + pass +finally: + exit(servers) diff --git a/radicale/__init__.py b/radicale/__init__.py index ad943181..49443469 100644 --- a/radicale/__init__.py +++ b/radicale/__init__.py @@ -80,9 +80,24 @@ class HTTPServer(server.HTTPServer): # Maybe a Pylint bug, ``__init__`` calls ``server.HTTPServer.__init__`` # pylint: disable=W0231 - def __init__(self, address, handler): + def __init__(self, address, handler, bind_and_activate = True): """Create server.""" - server.HTTPServer.__init__(self, address, handler) + self.use_ipv6 = ':' in address[0] + + if self.use_ipv6: + self.address_family = socket.AF_INET6 + + # call superclass, but do NOT bind and activate, as we might change socketopts + server.HTTPServer.__init__(self, address, handler, bind_and_activate = False) + + if self.use_ipv6: + # only allow IPv6 connections to the IPv6 socket + self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1) + + if bind_and_activate: + self.server_bind() + self.server_activate() + self.acl = acl.load() # pylint: enable=W0231 @@ -91,22 +106,24 @@ class HTTPSServer(HTTPServer): """HTTPS server.""" PROTOCOL = "https" - def __init__(self, address, handler): + def __init__(self, address, handler, bind_and_activate = True): """Create server by wrapping HTTP socket in an SSL socket.""" # Fails with Python 2.5, import if needed # pylint: disable=F0401 import ssl # pylint: enable=F0401 - HTTPServer.__init__(self, address, handler) + HTTPServer.__init__(self, address, handler, False) self.socket = ssl.wrap_socket( - socket.socket(self.address_family, self.socket_type), + self.socket, # we can use this, it is not bound yet server_side=True, certfile=config.get("server", "certificate"), keyfile=config.get("server", "key"), ssl_version=ssl.PROTOCOL_SSLv23) - self.server_bind() - self.server_activate() + + if bind_and_activate: + self.server_bind() + self.server_activate() class CalendarHTTPHandler(server.BaseHTTPRequestHandler): From 723b2ffc29dfb73985073e7be30fd08caf25511d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20=27Necoro=27=20Neumann?= Date: Tue, 8 Feb 2011 19:29:30 +0100 Subject: [PATCH 02/15] Update config to reflect the change --- config | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config b/config index d04b3da8..04f47c89 100644 --- a/config +++ b/config @@ -7,6 +7,10 @@ [server] # CalDAV server hostname, empty for all hostnames +# if you want to bind to multiple interfaces, seperate them with a comma +# NOTE: IPv6 adresses are configured to only allow IPv6 connections +# hence for binding to all IPv4 and IPv6 interfaces: +# host = ::, 0.0.0.0 host = # CalDAV server port port = 5232 From 2848dbc1a19b781af893cfece06a62ee04e00f6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20=27Necoro=27=20Neumann?= Date: Tue, 8 Feb 2011 19:41:54 +0100 Subject: [PATCH 03/15] Fix comment --- radicale.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/radicale.py b/radicale.py index c5a9025c..222f060d 100755 --- a/radicale.py +++ b/radicale.py @@ -129,9 +129,11 @@ signal.signal(signal.SIGTERM, lambda *a: exit(servers)) try: while threads: threads[0].join(1) # try one second - if threading.active_count() <= len(threads): # one thread died + if threading.active_count() <= len(threads): + # at least one thread died -- exit all break except KeyboardInterrupt: + # no unwanted traceback :) pass finally: exit(servers) From 84b5c226bc5314cff5e3544b747bd25e4225ff16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20de=20Metz?= Date: Tue, 15 Feb 2011 22:57:06 +0100 Subject: [PATCH 04/15] Upage shebang. /usr/bin/env python allow you to use virtualenv for running radicale. --- radicale.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radicale.py b/radicale.py index 03f6a147..07293345 100755 --- a/radicale.py +++ b/radicale.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # -*- coding: utf-8 -*- # # This file is part of Radicale Server - Calendar Server From b19b4c881068be82fe3a529cdffa7233950d7f38 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Sat, 2 Apr 2011 11:37:42 +0200 Subject: [PATCH 05/15] 0.5 version --- NEWS | 5 +++-- TODO | 7 +------ radicale/__init__.py | 2 +- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/NEWS b/NEWS index fa927ff1..32370121 100644 --- a/NEWS +++ b/NEWS @@ -6,10 +6,11 @@ NEWS ------ -0.5 - *Not released yet* -======================== +0.5 - Historical Artifacts +========================== * Calendar depth +* iPhone support * MacOS and Windows support * HEAD requests management * htpasswd user from calendar path diff --git a/TODO b/TODO index f394b8ab..c695c0f4 100644 --- a/TODO +++ b/TODO @@ -6,12 +6,6 @@ TODO ------ -0.5 -=== - -* iCal and iPhone support - - 0.6 === @@ -19,6 +13,7 @@ * [IN PROGRESS] LDAP and databases auth support * CalDAV rights * Read-only access for foreign users +* Smart, verbose and configurable logs 1.0 diff --git a/radicale/__init__.py b/radicale/__init__.py index 905257b0..351597fd 100644 --- a/radicale/__init__.py +++ b/radicale/__init__.py @@ -49,7 +49,7 @@ except ImportError: from radicale import acl, config, ical, xmlutils -VERSION = "git" +VERSION = "0.5" def _check(request, function): """Check if user has sufficient rights for performing ``request``.""" From 4b737667e3e0dbd58977e5dc2125a25bf044fa95 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Sat, 2 Apr 2011 11:57:23 +0200 Subject: [PATCH 06/15] Version bump --- radicale/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radicale/__init__.py b/radicale/__init__.py index 351597fd..905257b0 100644 --- a/radicale/__init__.py +++ b/radicale/__init__.py @@ -49,7 +49,7 @@ except ImportError: from radicale import acl, config, ical, xmlutils -VERSION = "0.5" +VERSION = "git" def _check(request, function): """Check if user has sufficient rights for performing ``request``.""" From 9f723e5c7b62f6a87efd5aa2c109c8f4e1437a21 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Sat, 2 Apr 2011 12:03:33 +0200 Subject: [PATCH 07/15] Update package description --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 2d9b09cf..1dfba172 100755 --- a/setup.py +++ b/setup.py @@ -27,8 +27,8 @@ it requires few software dependances and is pre-configured to work out-of-the-box. The Radicale Project runs on most of the UNIX-like platforms (Linux, BSD, -MacOS X) and Windows. It is known to work with Evolution 2.30+, Lightning 0.9+ -and Sunbird 0.9+. It is free and open-source software, released under GPL +MacOS X) and Windows. It is known to work with Evolution, Lightning, iPhone +and Android clients. It is free and open-source software, released under GPL version 3. For further information, please visit the `Radicale Website From 9f19481a69e510a36e1a4d457b5cfe3a9fbc2a26 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Sat, 2 Apr 2011 12:06:08 +0200 Subject: [PATCH 08/15] Add support for Python 3.2 --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 1dfba172..fe745165 100755 --- a/setup.py +++ b/setup.py @@ -91,4 +91,5 @@ setup( "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.0", "Programming Language :: Python :: 3.1", + "Programming Language :: Python :: 3.2", "Topic :: Office/Business :: Groupware"]) From 9bab3cde5d4e1483341122a00c4883449dc18c06 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Sat, 2 Apr 2011 21:45:45 +0200 Subject: [PATCH 09/15] Clean IPv6 support --- config | 13 +++----- radicale.py | 75 +++++++++++++++++++------------------------- radicale/__init__.py | 18 +++++------ radicale/config.py | 3 +- 4 files changed, 48 insertions(+), 61 deletions(-) diff --git a/config b/config index 04f47c89..ae672afc 100644 --- a/config +++ b/config @@ -6,14 +6,11 @@ # The current values are the default ones [server] -# CalDAV server hostname, empty for all hostnames -# if you want to bind to multiple interfaces, seperate them with a comma -# NOTE: IPv6 adresses are configured to only allow IPv6 connections -# hence for binding to all IPv4 and IPv6 interfaces: -# host = ::, 0.0.0.0 -host = -# CalDAV server port -port = 5232 +# CalDAV server hostnames separated by a comma +# IPv4: address:port +# IPv6: [address]:port +# IPv6 adresses are configured to only allow IPv6 connections +hosts = [::]:5232, 0.0.0.0:5232 # Daemon flag daemon = False # SSL flag, enable HTTPS protocol diff --git a/radicale.py b/radicale.py index 5e183e79..79d32a98 100755 --- a/radicale.py +++ b/radicale.py @@ -38,7 +38,8 @@ arguments. import os import sys import optparse -import threading, signal +import signal +import threading import radicale @@ -56,13 +57,9 @@ parser.add_option( "-f", "--foreground", action="store_false", dest="daemon", help="launch in foreground (opposite of --daemon)") parser.add_option( - "-H", "--host", - default=radicale.config.get("server", "host"), - help="set server hostname") -parser.add_option( - "-p", "--port", type="int", - default=radicale.config.getint("server", "port"), - help="set server port") + "-H", "--hosts", + default=radicale.config.get("server", "hosts"), + help="set server hostnames") parser.add_option( "-s", "--ssl", action="store_true", default=radicale.config.getboolean("server", "ssl"), @@ -98,42 +95,36 @@ if options.daemon: sys.exit() sys.stdout = sys.stderr = open(os.devnull, "w") -def exit (servers): - """Cleanly shutdown all servers. - - Might be called multiple times.""" - for s in servers: - s.shutdown() - -# Launch calendar server -server_class = radicale.HTTPSServer if options.ssl else radicale.HTTPServer +# Launch calendar servers servers = [] -threads = [] +server_class = radicale.HTTPSServer if options.ssl else radicale.HTTPServer -for host in (x.strip() for x in options.host.split(',')): +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 = server_class( - (host, options.port), radicale.CalendarHTTPHandler) - servers.append(server) - - t = threading.Thread(target = server.serve_forever) - threads.append(t) - t.start() - except: - exit(servers) - raise + server.serve_forever() + except KeyboardInterrupt: + # No unwanted traceback + pass + finally: + exit() -# clean exit on SIGTERM -signal.signal(signal.SIGTERM, lambda *a: exit(servers)) +# Clean exit on SIGTERM +signal.signal(signal.SIGTERM, lambda *_: exit()) -try: - while threads: - threads[0].join(1) # try one second - if threading.active_count() <= len(threads): - # at least one thread died -- exit all - break -except KeyboardInterrupt: - # no unwanted traceback :) - pass -finally: - exit(servers) +for host in options.hosts.split(','): + address, port = host.strip().rsplit(':', 1) + address, port = address.strip('[] '), int(port) + servers.append(server_class((address, port), radicale.CalendarHTTPHandler)) + +for server in servers[:-1]: + # More servers to come, launch a new thread + threading.Thread(target=serve_forever, args=(server,)).start() + +# Last server, no more thread +serve_forever(servers[-1]) diff --git a/radicale/__init__.py b/radicale/__init__.py index 6e5865c3..1c43420f 100644 --- a/radicale/__init__.py +++ b/radicale/__init__.py @@ -85,18 +85,18 @@ class HTTPServer(server.HTTPServer): # Maybe a Pylint bug, ``__init__`` calls ``server.HTTPServer.__init__`` # pylint: disable=W0231 - def __init__(self, address, handler, bind_and_activate = True): + def __init__(self, address, handler, bind_and_activate=True): """Create server.""" - self.use_ipv6 = ':' in address[0] + ipv6 = ":" in address[0] - if self.use_ipv6: + if ipv6: self.address_family = socket.AF_INET6 - # call superclass, but do NOT bind and activate, as we might change socketopts - server.HTTPServer.__init__(self, address, handler, bind_and_activate = False) + # Do not bind and activate, as we might change socketopts + server.HTTPServer.__init__(self, address, handler, False) - if self.use_ipv6: - # only allow IPv6 connections to the IPv6 socket + if ipv6: + # Only allow IPv6 connections to the IPv6 socket self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1) if bind_and_activate: @@ -111,7 +111,7 @@ class HTTPSServer(HTTPServer): """HTTPS server.""" PROTOCOL = "https" - def __init__(self, address, handler, bind_and_activate = True): + def __init__(self, address, handler, bind_and_activate=True): """Create server by wrapping HTTP socket in an SSL socket.""" # Fails with Python 2.5, import if needed # pylint: disable=F0401 @@ -120,7 +120,7 @@ class HTTPSServer(HTTPServer): HTTPServer.__init__(self, address, handler, False) self.socket = ssl.wrap_socket( - self.socket, # we can use this, it is not bound yet + self.socket, server_side=True, certfile=config.get("server", "certificate"), keyfile=config.get("server", "key"), diff --git a/radicale/config.py b/radicale/config.py index 63929aec..8fe10272 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -39,8 +39,7 @@ except ImportError: # Default configuration INITIAL_CONFIG = { "server": { - "host": "", - "port": "5232", + "hosts": "[::]:5232, 0.0.0.0:5232", "daemon": "False", "ssl": "False", "certificate": "/etc/apache2/ssl/server.crt", From 48be062d382ac12655f02654141403cf61f7c691 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Sat, 2 Apr 2011 21:49:48 +0200 Subject: [PATCH 10/15] Disable IPv6 in default configuration --- config | 6 +++--- radicale/config.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config b/config index ae672afc..7907b4d6 100644 --- a/config +++ b/config @@ -7,10 +7,10 @@ [server] # CalDAV server hostnames separated by a comma -# IPv4: address:port -# IPv6: [address]:port +# IPv4 syntax: address:port +# IPv6 syntax: [address]:port # IPv6 adresses are configured to only allow IPv6 connections -hosts = [::]:5232, 0.0.0.0:5232 +hosts = 0.0.0.0:5232 # Daemon flag daemon = False # SSL flag, enable HTTPS protocol diff --git a/radicale/config.py b/radicale/config.py index 8fe10272..f313d695 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -39,7 +39,7 @@ except ImportError: # Default configuration INITIAL_CONFIG = { "server": { - "hosts": "[::]:5232, 0.0.0.0:5232", + "hosts": "0.0.0.0:5232", "daemon": "False", "ssl": "False", "certificate": "/etc/apache2/ssl/server.crt", From c0317c6817f3261a904376b6934d5e8deb69dda1 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Sat, 2 Apr 2011 22:09:43 +0200 Subject: [PATCH 11/15] Clean command-line options management --- radicale.py | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/radicale.py b/radicale.py index 79d32a98..bbc386f6 100755 --- a/radicale.py +++ b/radicale.py @@ -26,10 +26,9 @@ # pylint: disable-msg=W0406 """ -Radicale Server entry point. +Radicale CalDAV Server. -Launch the Radicale Server according to configuration and command-line -arguments. +Launch the server according to configuration and command-line options. """ @@ -44,11 +43,7 @@ import threading import radicale # Get command-line options -parser = optparse.OptionParser() -parser.add_option( - "-v", "--version", action="store_true", - default=False, - help="show version and exit") +parser = optparse.OptionParser(version=radicale.VERSION) parser.add_option( "-d", "--daemon", action="store_true", default=radicale.config.getboolean("server", "daemon"), @@ -59,7 +54,7 @@ parser.add_option( parser.add_option( "-H", "--hosts", default=radicale.config.get("server", "hosts"), - help="set server hostnames") + help="set server hostnames and ports") parser.add_option( "-s", "--ssl", action="store_true", default=radicale.config.getboolean("server", "ssl"), @@ -70,11 +65,11 @@ parser.add_option( parser.add_option( "-k", "--key", default=radicale.config.get("server", "key"), - help="private key file ") + help="set private key file") parser.add_option( "-c", "--certificate", default=radicale.config.get("server", "certificate"), - help="certificate file ") + help="set certificate file") options = parser.parse_args()[0] # Update Radicale configuration according to options @@ -84,11 +79,6 @@ for option in parser.option_list: value = getattr(options, key) radicale.config.set("server", key, value) -# Print version and exit if the option is given -if options.version: - print(radicale.VERSION) - sys.exit() - # Fork if Radicale is launched as daemon if options.daemon: if os.fork(): From 20d21ea01cfe74bb7cef96aed672dad22674f190 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Sat, 2 Apr 2011 22:34:04 +0200 Subject: [PATCH 12/15] Update TODO and NEWS files --- NEWS | 6 ++++++ TODO | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 32370121..7893995d 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,12 @@ NEWS ------ +0.6 - *Not released yet* +======================== + +* IPv6 support + + 0.5 - Historical Artifacts ========================== diff --git a/TODO b/TODO index c695c0f4..7ab5d0fd 100644 --- a/TODO +++ b/TODO @@ -11,9 +11,9 @@ * [IN PROGRESS] Group calendars * [IN PROGRESS] LDAP and databases auth support +* [IN PROGRESS] Smart, verbose and configurable logs * CalDAV rights * Read-only access for foreign users -* Smart, verbose and configurable logs 1.0 From 6c87df33641f4b51e2bb02789d74e689e96cd24d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20=27Necoro=27=20Neumann?= Date: Sun, 3 Apr 2011 12:18:43 +0200 Subject: [PATCH 13/15] Fix threading to avoid race-conditions and handle signals correctly. --- radicale.py | 64 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/radicale.py b/radicale.py index bbc386f6..8a9bd5a6 100755 --- a/radicale.py +++ b/radicale.py @@ -85,36 +85,54 @@ if options.daemon: sys.exit() sys.stdout = sys.stderr = open(os.devnull, "w") -# Launch calendar servers +# Create calendar servers servers = [] 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(','): address, port = host.strip().rsplit(':', 1) address, port = address.strip('[] '), int(port) + servers.append(server_class((address, port), radicale.CalendarHTTPHandler)) -for server in servers[:-1]: - # More servers to come, launch a new thread +# this event marks that the program should be shut down +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() -# Last server, no more thread -serve_forever(servers[-1]) +# mainloop: wait until all servers are exited +# 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() From 8d4eac5ea145dc6613479add7b906e7b1c29f63e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20=27Necoro=27=20Neumann?= Date: Sun, 3 Apr 2011 13:27:51 +0200 Subject: [PATCH 14/15] Don't rely on python internal implementation. Set a safe timeout value as we cannot assume, that the current implementation of busy-waiting won't be changed in the future. --- radicale.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radicale.py b/radicale.py index 8a9bd5a6..a4502395 100755 --- a/radicale.py +++ b/radicale.py @@ -122,7 +122,7 @@ try: # 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) + server_exited.wait(5.0) if server_exited.is_set(): break finally: From 7e572f44e4c80a6150495f4c391fbb4ee669bc7d Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Sat, 9 Apr 2011 21:46:46 +0200 Subject: [PATCH 15/15] Clean the multi-thread stuff --- radicale.py | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/radicale.py b/radicale.py index a4502395..507dff70 100755 --- a/radicale.py +++ b/radicale.py @@ -88,49 +88,41 @@ if options.daemon: # Create calendar servers servers = [] server_class = radicale.HTTPSServer if options.ssl else radicale.HTTPServer +shutdown_program = threading.Event() for host in options.hosts.split(','): address, port = host.strip().rsplit(':', 1) address, port = address.strip('[] '), int(port) - servers.append(server_class((address, port), radicale.CalendarHTTPHandler)) -# this event marks that the program should be shut down -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()) +signal.signal(signal.SIGTERM, lambda *_: shutdown_program.set()) +signal.signal(signal.SIGINT, lambda *_: shutdown_program.set()) def serve_forever(server): - """Serve a server forever, and mark the process for shut down if things go wrong.""" + """Serve a server forever, cleanly shutdown when things go wrong.""" try: server.serve_forever() finally: - server_exited.set() + shutdown_program.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 +# 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() -# mainloop: wait until all servers are exited -# we must do the busy-waiting here, as all ".join()"-calls -# completly block the thread, such that signals are not received +# Main loop: wait until all servers are exited try: + # We must do the busy-waiting here, as all ``.join()`` calls completly + # block the thread, such that signals are not received 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(5.0) - if server_exited.is_set(): + # 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: - # - # Cleanly shutdown server - # - - # ignore signals, s.t. they cannot interfere + # Ignore signals, so that they cannot interfere signal.signal(signal.SIGINT, signal.SIG_IGN) signal.signal(signal.SIGTERM, signal.SIG_IGN)