[CalendarServer-changes] [9854] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Tue Sep 25 14:46:36 PDT 2012
Revision: 9854
http://trac.calendarserver.org//changeset/9854
Author: sagen at apple.com
Date: 2012-09-25 14:46:36 -0700 (Tue, 25 Sep 2012)
Log Message:
-----------
Graceful shutdown
Modified Paths:
--------------
CalendarServer/trunk/calendarserver/tap/caldav.py
CalendarServer/trunk/calendarserver/tap/test/test_caldav.py
CalendarServer/trunk/twext/internet/tcp.py
CalendarServer/trunk/twext/web2/channel/http.py
CalendarServer/trunk/twext/web2/metafd.py
CalendarServer/trunk/twext/web2/test/test_http.py
Modified: CalendarServer/trunk/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/caldav.py 2012-09-25 18:36:32 UTC (rev 9853)
+++ CalendarServer/trunk/calendarserver/tap/caldav.py 2012-09-25 21:46:36 UTC (rev 9854)
@@ -41,7 +41,7 @@
from twisted.python.logfile import LogFile
from twisted.python.usage import Options, UsageError
-from twisted.internet.defer import gatherResults, Deferred
+from twisted.internet.defer import gatherResults, Deferred, inlineCallbacks
from twisted.internet.process import ProcessExitedAlready
from twisted.internet.protocol import Protocol, Factory
@@ -244,10 +244,18 @@
MultiService.privilegedStartService(self)
self.logObserver.start()
+ @inlineCallbacks
def stopService(self):
- d = MultiService.stopService(self)
+ """
+ Wait for outstanding requests to finish
+ @return: a Deferred which fires when all outstanding requests are complete
+ """
+ connectionService = self.getServiceNamed("ConnectionService")
+ # Note: removeService() also calls stopService()
+ yield self.removeService(connectionService)
+ # At this point, all outstanding requests have been responded to
+ yield MultiService.stopService(self)
self.logObserver.stop()
- return d
class CalDAVOptions (Options, LoggingMixIn):
@@ -858,6 +866,12 @@
config.addPostUpdateHooks((updateFactory,))
+ # Bundle the various connection services within a single MultiService
+ # that can be stopped before the others for graceful shutdown.
+ connectionService = MultiService()
+ connectionService.setName("ConnectionService")
+ connectionService.setServiceParent(service)
+
if config.InheritFDs or config.InheritSSLFDs:
# Inherit sockets to call accept() on them individually.
@@ -873,13 +887,13 @@
contextFactory,
backlog=config.ListenBacklog,
inherit=True
- ).setServiceParent(service)
+ ).setServiceParent(connectionService)
for fdAsStr in config.InheritFDs:
MaxAcceptTCPServer(
int(fdAsStr), httpFactory,
backlog=config.ListenBacklog,
inherit=True
- ).setServiceParent(service)
+ ).setServiceParent(connectionService)
elif config.MetaFD:
# Inherit a single socket to receive accept()ed connections via
@@ -896,7 +910,7 @@
ReportingHTTPService(
requestFactory, int(config.MetaFD), contextFactory
- ).setServiceParent(service)
+ ).setServiceParent(connectionService)
else: # Not inheriting, therefore we open our own:
for bindAddress in self._allBindAddresses():
@@ -919,7 +933,7 @@
backlog=config.ListenBacklog,
inherit=False
)
- httpsService.setServiceParent(service)
+ httpsService.setServiceParent(connectionService)
for port in config.BindHTTPPorts:
MaxAcceptTCPServer(
@@ -927,7 +941,7 @@
interface=bindAddress,
backlog=config.ListenBacklog,
inherit=False
- ).setServiceParent(service)
+ ).setServiceParent(connectionService)
# Change log level back to what it was before
Modified: CalendarServer/trunk/calendarserver/tap/test/test_caldav.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/test/test_caldav.py 2012-09-25 18:36:32 UTC (rev 9853)
+++ CalendarServer/trunk/calendarserver/tap/test/test_caldav.py 2012-09-25 21:46:36 UTC (rev 9854)
@@ -542,7 +542,8 @@
Test that the Slave service has sub services with the
default TCP and SSL configuration
"""
- service = self.makeService()
+ # Note: the listeners are bundled within a MultiService named "ConnectionService"
+ service = self.makeService().getServiceNamed("ConnectionService")
expectedSubServices = dict((
(MaxAcceptTCPServer, self.config["HTTPPort"]),
@@ -568,7 +569,7 @@
Test that the configuration of the SSLServer reflect the config file's
SSL Private Key and SSL Certificate
"""
- service = self.makeService()
+ service = self.makeService().getServiceNamed("ConnectionService")
sslService = None
for s in service.services:
@@ -597,7 +598,7 @@
del self.config["SSLPort"]
self.writeConfig()
- service = self.makeService()
+ service = self.makeService().getServiceNamed("ConnectionService")
self.assertNotIn(
internet.SSLServer,
@@ -612,7 +613,7 @@
del self.config["HTTPPort"]
self.writeConfig()
- service = self.makeService()
+ service = self.makeService().getServiceNamed("ConnectionService")
self.assertNotIn(
internet.TCPServer,
@@ -626,7 +627,7 @@
self.config.BindAddresses = ["127.0.0.1"]
self.writeConfig()
- service = self.makeService()
+ service = self.makeService().getServiceNamed("ConnectionService")
for s in service.services:
if isinstance(s, (internet.TCPServer, internet.SSLServer)):
@@ -644,7 +645,7 @@
]
self.writeConfig()
- service = self.makeService()
+ service = self.makeService().getServiceNamed("ConnectionService")
tcpServers = []
sslServers = []
@@ -676,7 +677,7 @@
"""
self.config.ListenBacklog = 1024
self.writeConfig()
- service = self.makeService()
+ service = self.makeService().getServiceNamed("ConnectionService")
for s in service.services:
if isinstance(s, (internet.TCPServer, internet.SSLServer)):
Modified: CalendarServer/trunk/twext/internet/tcp.py
===================================================================
--- CalendarServer/trunk/twext/internet/tcp.py 2012-09-25 18:36:32 UTC (rev 9853)
+++ CalendarServer/trunk/twext/internet/tcp.py 2012-09-25 21:46:36 UTC (rev 9854)
@@ -114,7 +114,8 @@
def __init__(self, *args, **kwargs):
internet.TCPServer.__init__(self, *args, **kwargs)
- self.args[1].myServer = self
+ self.httpFactory = self.args[1]
+ self.httpFactory.myServer = self
self.inherit = self.kwargs.get("inherit", False)
self.backlog = self.kwargs.get("backlog", None)
self.interface = self.kwargs.get("interface", None)
@@ -131,6 +132,15 @@
self.myPort = port
return port
+ def stopService(self):
+ """
+ Wait for outstanding requests to finish
+ @return: a Deferred which fires when all outstanding requests are complete
+ """
+ internet.TCPServer.stopService(self)
+ return self.httpFactory.waitForCompletion()
+
+
class MaxAcceptSSLServer(internet.SSLServer):
"""
SSL server which will uses MaxAcceptSSLPorts (and optionally,
@@ -139,7 +149,8 @@
def __init__(self, *args, **kwargs):
internet.SSLServer.__init__(self, *args, **kwargs)
- self.args[1].myServer = self
+ self.httpFactory = self.args[1]
+ self.httpFactory.myServer = self
self.inherit = self.kwargs.get("inherit", False)
self.backlog = self.kwargs.get("backlog", None)
self.interface = self.kwargs.get("interface", None)
@@ -156,3 +167,12 @@
self.myPort = port
return port
+ def stopService(self):
+ """
+ Wait for outstanding requests to finish
+ @return: a Deferred which fires when all outstanding requests are complete
+ """
+ internet.SSLServer.stopService(self)
+ return self.httpFactory.waitForCompletion()
+
+
Modified: CalendarServer/trunk/twext/web2/channel/http.py
===================================================================
--- CalendarServer/trunk/twext/web2/channel/http.py 2012-09-25 18:36:32 UTC (rev 9853)
+++ CalendarServer/trunk/twext/web2/channel/http.py 2012-09-25 21:46:36 UTC (rev 9854)
@@ -32,6 +32,7 @@
from zope.interface import implements
from twisted.internet import interfaces, protocol, reactor
+from twisted.internet.defer import succeed, Deferred
from twisted.protocols import policies, basic
from twext.python.log import Logger
@@ -1019,6 +1020,7 @@
self.protocolArgs = kwargs
self.protocolArgs['requestFactory'] = requestFactory
self.connectedChannels = set()
+ self.deferred = None
def buildProtocol(self, addr):
@@ -1044,16 +1046,33 @@
"""
Remove a connected channel from the set of currently connected channels
and decrease the outstanding request count.
+ If someone is waiting for all the requests to be completed, self.deferred
+ will be non-None; fire that callback when the number of outstanding requests
+ hits zero.
"""
self.connectedChannels.remove(channel)
+ if self.deferred is not None:
+ if self.outstandingRequests == 0:
+ self.deferred.callback(None)
@property
def outstandingRequests(self):
return len(self.connectedChannels)
+ def waitForCompletion(self):
+ """
+ Return a Deferred that will fire when all outstanding requests have completed.
+ @return: A Deferred with a result of None
+ """
+ if self.outstandingRequests == 0:
+ return succeed(None)
+ self.deferred = Deferred()
+ return self.deferred
+
+
class HTTP503LoggingFactory (HTTPFactory):
"""
Factory for HTTP server which emits a 503 response when overloaded.
@@ -1201,7 +1220,7 @@
def removeConnectedChannel(self, channel):
"""
- Override L{HTTPFactory.addConnectedChannel} to resume listening on the
+ Override L{HTTPFactory.removeConnectedChannel} to resume listening on the
socket when there are too many outstanding channels.
"""
HTTPFactory.removeConnectedChannel(self, channel)
Modified: CalendarServer/trunk/twext/web2/metafd.py
===================================================================
--- CalendarServer/trunk/twext/web2/metafd.py 2012-09-25 18:36:32 UTC (rev 9853)
+++ CalendarServer/trunk/twext/web2/metafd.py 2012-09-25 21:46:36 UTC (rev 9854)
@@ -81,14 +81,17 @@
def stopService(self):
"""
Stop reading on the inherited port.
+ @return: a Deferred which fires after the last outstanding request is complete.
"""
Service.stopService(self)
# XXX stopping should really be destructive, because otherwise we will
# always leak a file descriptor; i.e. this shouldn't be restartable.
- # XXX this needs to return a Deferred.
self.reportingFactory.inheritedPort.stopReading()
+ # Let any outstanding requests finish
+ return self.reportingFactory.waitForCompletion()
+
def createTransport(self, skt, peer, data, protocol):
"""
Create a TCP transport, from a socket object passed by the parent.
Modified: CalendarServer/trunk/twext/web2/test/test_http.py
===================================================================
--- CalendarServer/trunk/twext/web2/test/test_http.py 2012-09-25 18:36:32 UTC (rev 9853)
+++ CalendarServer/trunk/twext/web2/test/test_http.py 2012-09-25 21:46:36 UTC (rev 9854)
@@ -14,7 +14,7 @@
from twisted.internet.defer import waitForDeferred, deferredGenerator
from twisted.protocols import loopback
from twisted.python import util, runtime
-from twext.web2.channel.http import SSLRedirectRequest
+from twext.web2.channel.http import SSLRedirectRequest, HTTPFactory
from twisted.internet.task import deferLater
class PreconditionTestCase(unittest.TestCase):
@@ -449,6 +449,45 @@
self.assertEquals(cxn.client.done, done)
+class GracefulShutdownTestCase(HTTPTests):
+
+ def _callback(self, result):
+ self.callbackFired = True
+
+ def testWaitForCompletionWithoutConnectedChannels(self):
+ """
+ waitForCompletion( ) should fire right away if no connected channels
+ """
+ self.callbackFired = False
+
+ factory = HTTPFactory(None)
+ factory.waitForCompletion().addCallback(self._callback)
+ self.assertTrue(self.callbackFired) # now!
+
+ def testWaitForCompletionWithConnectedChannels(self):
+ """
+ waitForCompletion( ) should only fire after all connected channels
+ have been removed
+ """
+ self.callbackFired = False
+
+ factory = HTTPFactory(None)
+ factory.addConnectedChannel("A")
+ factory.addConnectedChannel("B")
+ factory.addConnectedChannel("C")
+
+ factory.waitForCompletion().addCallback(self._callback)
+
+ factory.removeConnectedChannel("A")
+ self.assertFalse(self.callbackFired) # wait for it...
+
+ factory.removeConnectedChannel("B")
+ self.assertFalse(self.callbackFired) # wait for it...
+
+ factory.removeConnectedChannel("C")
+ self.assertTrue(self.callbackFired) # now!
+
+
class CoreHTTPTestCase(HTTPTests):
# Note: these tests compare the client output using string
# matching. It is acceptable for this to change and break
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120925/d0395115/attachment-0001.html>
More information about the calendarserver-changes
mailing list