Merge lp:~vila/bzr/test-server-races into lp:bzr

Proposed by Vincent Ladeuil
Status: Merged
Approved by: Martin Packman
Approved revision: no longer in the source branch.
Merged at revision: 6337
Proposed branch: lp:~vila/bzr/test-server-races
Merge into: lp:bzr
Diff against target: 131 lines (+27/-26)
2 files modified
bzrlib/tests/test_server.py (+12/-8)
bzrlib/tests/test_test_server.py (+15/-18)
To merge this branch: bzr merge lp:~vila/bzr/test-server-races
Reviewer Review Type Date Requested Status
Martin Packman (community) Approve
Review via email: mp+84096@code.launchpad.net

Commit message

Properly synchronize connection thread start with test server main thread.

Description of the change

This is a followup to the fix for bug #869366 that was sitting on disk for
no good reasons.

It removes one race in the test server when a connection thread is started.

Under rare circumstances (described in bug #869366) an exception in the
connection thread was raised in the server thread too early leading to
semi-hackish tests.

The connection thread will now wait for the server thread to resume its
normal course before processing the conection.

I see no reason why this should address the other issues we currently have
but I may be wrong.

On the other hand, this is clearly better and remove one source of possible
confusion.

To post a comment you must log in.
Revision history for this message
Martin Packman (gz) wrote :

Okay, this all makes sense to me given the earlier merge proposal context.

review: Approve
Revision history for this message
Vincent Ladeuil (vila) wrote :

sent to pqm by email

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bzrlib/tests/test_server.py'
2--- bzrlib/tests/test_server.py 2011-10-10 13:59:22 +0000
3+++ bzrlib/tests/test_server.py 2011-12-01 12:49:45 +0000
4@@ -432,12 +432,15 @@
5 def get_request(self):
6 """Get the request and client address from the socket."""
7 sock, addr = TestingTCPServerMixin.get_request(self)
8- # The thread is not create yet, it will be updated in process_request
9+ # The thread is not created yet, it will be updated in process_request
10 self.clients.append((sock, addr, None))
11 return sock, addr
12
13- def process_request_thread(self, started, stopped, request, client_address):
14+ def process_request_thread(self, started, detached, stopped,
15+ request, client_address):
16 started.set()
17+ # We will be on our own once the server tells us we're detached
18+ detached.wait()
19 SocketServer.ThreadingTCPServer.process_request_thread(
20 self, request, client_address)
21 self.close_request(request)
22@@ -446,12 +449,13 @@
23 def process_request(self, request, client_address):
24 """Start a new thread to process the request."""
25 started = threading.Event()
26+ detached = threading.Event()
27 stopped = threading.Event()
28 t = TestThread(
29 sync_event=stopped,
30 name='%s -> %s' % (client_address, self.server_address),
31- target=self.process_request_thread,
32- args=(started, stopped, request, client_address))
33+ target = self.process_request_thread,
34+ args = (started, detached, stopped, request, client_address))
35 # Update the client description
36 self.clients.pop()
37 self.clients.append((request, client_address, t))
38@@ -460,12 +464,12 @@
39 t.set_ignored_exceptions(self.ignored_exceptions)
40 t.start()
41 started.wait()
42- if debug_threads():
43- sys.stderr.write('Client thread %s started\n' % (t.name,))
44 # If an exception occured during the thread start, it will get raised.
45- # In rare cases, an exception raised during the request processing may
46- # also get caught here (see http://pad.lv/869366)
47 t.pending_exception()
48+ if debug_threads():
49+ sys.stderr.write('Client thread %s started\n' % (t.name,))
50+ # Tell the thread, it's now on its own for exception handling.
51+ detached.set()
52
53 # The following methods are called by the main thread
54
55
56=== modified file 'bzrlib/tests/test_test_server.py'
57--- bzrlib/tests/test_test_server.py 2011-10-10 13:59:22 +0000
58+++ bzrlib/tests/test_test_server.py 2011-12-01 12:49:45 +0000
59@@ -212,10 +212,12 @@
60
61 class FailingDuringResponseHandler(TCPConnectionHandler):
62
63+ # We use 'request' instead of 'self' below because the test matters
64+ # more and we need a container to properly set connection_thread.
65 def handle_connection(request):
66 req = request.readline()
67 # Capture the thread and make it use 'caught' so we can wait on
68- # the even that will be set when the exception is caught. We
69+ # the event that will be set when the exception is caught. We
70 # also capture the thread to know where to look.
71 self.connection_thread = threading.currentThread()
72 self.connection_thread.set_sync_event(caught)
73@@ -232,20 +234,12 @@
74 # Check that the connection thread did catch the exception,
75 # http://pad.lv/869366 was wrongly checking the server thread which
76 # works for TestingTCPServer where the connection is handled in the
77- # same thread than the server one but is racy for
78- # TestingThreadingTCPServer where the server thread may be in a
79- # blocking accept() call (or not).
80- try:
81- self.connection_thread.pending_exception()
82- except FailToRespond:
83- # Great, the test succeeded
84- pass
85- else:
86- # If the exception is not in the connection thread anymore, it's in
87- # the server's one.
88- server.server.stopped.wait()
89- # The exception is available now
90- self.assertRaises(FailToRespond, server.pending_exception)
91+ # same thread than the server one but was racy for
92+ # TestingThreadingTCPServer. Since the connection thread detaches
93+ # itself before handling the request, we are guaranteed that the
94+ # exception won't leak into the server thread anymore.
95+ self.assertRaises(FailToRespond,
96+ self.connection_thread.pending_exception)
97
98 def test_exception_swallowed_while_serving(self):
99 # We need to ensure the exception has been caught
100@@ -260,9 +254,11 @@
101
102 class FailingWhileServingConnectionHandler(TCPConnectionHandler):
103
104+ # We use 'request' instead of 'self' below because the test matters
105+ # more and we need a container to properly set connection_thread.
106 def handle(request):
107 # Capture the thread and make it use 'caught' so we can wait on
108- # the even that will be set when the exception is caught. We
109+ # the event that will be set when the exception is caught. We
110 # also capture the thread to know where to look.
111 self.connection_thread = threading.currentThread()
112 self.connection_thread.set_sync_event(caught)
113@@ -270,6 +266,7 @@
114
115 server = self.get_server(
116 connection_handler_class=FailingWhileServingConnectionHandler)
117+ self.assertEquals(True, server.server.serving)
118 # Install the exception swallower
119 server.set_ignored_exceptions(CantServe)
120 client = self.get_client()
121@@ -284,8 +281,8 @@
122 # here). More precisely, the exception *has* been caught and captured
123 # but it is cleared when joining the thread (or trying to acquire the
124 # exception) and as such won't propagate to the server thread.
125- self.connection_thread.pending_exception()
126- server.pending_exception()
127+ self.assertIs(None, self.connection_thread.pending_exception())
128+ self.assertIs(None, server.pending_exception())
129
130 def test_handle_request_closes_if_it_doesnt_process(self):
131 server = self.get_server()