Rev 4734: Reduce the leaking http tests from ~200 to ~5. in file:///home/vila/src/bzr/bugs/392127-thread-leak/

Vincent Ladeuil v.ladeuil+lp at free.fr
Wed Oct 7 15:55:54 BST 2009


At file:///home/vila/src/bzr/bugs/392127-thread-leak/

------------------------------------------------------------
revno: 4734
revision-id: v.ladeuil+lp at free.fr-20091007145554-j0njl74ji5o5v6cx
parent: v.ladeuil+lp at free.fr-20091007133103-bagf11imwtcenmzy
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: 392127-thread-leak
timestamp: Wed 2009-10-07 16:55:54 +0200
message:
  Reduce the leaking http tests from ~200 to ~5.
  
  * bzrlib/tests/test_http.py:
  (WallRequestHandler._handle_one_request,
  PredefinedRequestHandler._handle_one_request): Be sure to use the
  proper wrapper to catch socket errors. This becomes more important
  when threaded servers are used and now properly shut down.
  
  * bzrlib/tests/http_server.py:
  (TestingHTTPRequestHandler.handle_one_request): Use a wrapper
  around the desired processing to share the socket errors handling.
  (TestingHTTPServerMixin.shutdown_client): Helper to
  (TestingThreadingHTTPServer): Collect clients and shut them down
  properly.
-------------- next part --------------
=== modified file 'bzrlib/tests/http_server.py'
--- a/bzrlib/tests/http_server.py	2009-10-07 13:31:03 +0000
+++ b/bzrlib/tests/http_server.py	2009-10-07 14:55:54 +0000
@@ -77,7 +77,7 @@
         connection early to avoid polluting the test results.
         """
         try:
-            SimpleHTTPServer.SimpleHTTPRequestHandler.handle_one_request(self)
+            self._handle_one_request()
         except socket.error, e:
             # Any socket error should close the connection, but some errors are
             # due to the client closing early and we don't want to pollute test
@@ -88,6 +88,9 @@
                                      errno.ECONNABORTED, errno.EBADF)):
                 raise
 
+    def _handle_one_request(self):
+        SimpleHTTPServer.SimpleHTTPRequestHandler.handle_one_request(self)
+
     _range_regexp = re.compile(r'^(?P<start>\d+)-(?P<end>\d+)$')
     _tail_regexp = re.compile(r'^-(?P<tail>\d+)$')
 
@@ -365,6 +368,28 @@
         self.is_shut_down.wait()
 
 
+    def shutdown_client(self, client_socket):
+        """Properly shutdown a client socket.
+
+        Under some circumstances (as in bug #383920), we need to force the
+        shutdown as python delays it until gc occur otherwise and the client
+        may hang.
+
+        This should be called only when no other thread is trying to use the
+        socket.
+        """
+        try:
+            # The request process has been completed, the thread is about to
+            # die, let's shutdown the socket if we can.
+            client_socket.shutdown(socket.SHUT_RDWR)
+        except (socket.error, select.error), e:
+            if e[0] in (errno.EBADF, errno.ENOTCONN):
+                # Right, the socket is already down
+                pass
+            else:
+                raise
+
+
 class TestingHTTPServer(TestingHTTPServerMixin, SocketServer.TCPServer):
 
     def __init__(self, server_address, request_handler_class,
@@ -393,23 +418,26 @@
         # process. This is prophylactic as we should not leave the threads
         # lying around.
         self.daemon_threads = True
+        # We collect the sockets used by the clients to we can close them when
+        # shutting down
+        self.clients = []
 
     def process_request_thread(self, request, client_address):
-        SocketServer.ThreadingTCPServer.process_request_thread(
-            self, request, client_address)
-        # Under some circumstances (as in bug #383920), we need to force the
-        # shutdown as python delays it until gc occur otherwise and the client
-        # may hang.
+        self.clients.append((request, threading.current_thread()))
         try:
-            # The request process has been completed, the thread is about to
-            # die, let's shutdown the socket if we can.
-            request.shutdown(socket.SHUT_RDWR)
-        except (socket.error, select.error), e:
-            if e[0] in (errno.EBADF, errno.ENOTCONN):
-                # Right, the socket is already down
-                pass
-            else:
-                raise
+            SocketServer.ThreadingTCPServer.process_request_thread(
+                self, request, client_address)
+        except Exception, e:
+            import pdb; pdb.set_trace()
+
+        self.shutdown_client(request)
+
+    def shutdown(self):
+        TestingHTTPServerMixin.shutdown(self)
+        # Let's close all our pending clients too
+        for c, t in self.clients:
+            self.shutdown_client(c)
+            t.join()
 
 
 class HttpServer(transport.Server):

=== modified file 'bzrlib/tests/test_http.py'
--- a/bzrlib/tests/test_http.py	2009-10-07 13:31:03 +0000
+++ b/bzrlib/tests/test_http.py	2009-10-07 14:55:54 +0000
@@ -610,7 +610,7 @@
 class WallRequestHandler(http_server.TestingHTTPRequestHandler):
     """Whatever request comes in, close the connection"""
 
-    def handle_one_request(self):
+    def _handle_one_request(self):
         """Handle a single HTTP request, by abruptly closing the connection"""
         self.close_connection = 1
 
@@ -1907,7 +1907,7 @@
     line.
     """
 
-    def handle_one_request(self):
+    def _handle_one_request(self):
         tcs = self.server.test_case_server
         requestline = self.rfile.readline()
         headers = self.MessageClass(self.rfile, 0)
@@ -2097,7 +2097,7 @@
 '''
         t = self.get_transport()
         # We must send a single line of body bytes, see
-        # PredefinedRequestHandler.handle_one_request
+        # PredefinedRequestHandler._handle_one_request
         code, f = t._post('abc def end-of-body\n')
         self.assertEqual('lalala whatever as long as itsssss\n', f.read())
         self.assertActivitiesMatch()



More information about the bazaar-commits mailing list