~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_test_server.py

(gz) Fix random test failure due to race in
 test_server_crash_while_responding (Vincent Ladeuil)

Show diffs side-by-side

added added

removed removed

Lines of Context:
86
86
        for name in
87
87
        ('TestingTCPServer', 'TestingThreadingTCPServer')]
88
88
 
89
 
    # Set by load_tests()
90
 
    server_class = None
91
 
 
92
89
    def get_server(self, server_class=None, connection_handler_class=None):
93
90
        if server_class is not None:
94
91
            self.server_class = server_class
172
169
        self.assertRaises(CantConnect, server.stop_server)
173
170
 
174
171
    def test_server_crash_while_responding(self):
175
 
        sync = threading.Event()
176
 
        sync.clear()
 
172
        # We want to ensure the exception has been caught
 
173
        caught = threading.Event()
 
174
        caught.clear()
 
175
        # The thread that will serve the client, this needs to be an attribute
 
176
        # so the handler below can modify it when it's executed (it's
 
177
        # instantiated when the request is processed)
 
178
        self.connection_thread = None
 
179
 
177
180
        class FailToRespond(Exception):
178
181
            pass
179
182
 
180
183
        class FailingDuringResponseHandler(TCPConnectionHandler):
181
184
 
182
 
            def handle_connection(self):
183
 
                req = self.rfile.readline()
184
 
                threading.currentThread().set_sync_event(sync)
 
185
            def handle_connection(request):
 
186
                req = request.rfile.readline()
 
187
                # Capture the thread and make it use 'caught' so we can wait on
 
188
                # the even that will be set when the exception is caught. We
 
189
                # also capture the thread to know where to look.
 
190
                self.connection_thread = threading.currentThread()
 
191
                self.connection_thread.set_sync_event(caught)
185
192
                raise FailToRespond()
186
193
 
187
194
        server = self.get_server(
189
196
        client = self.get_client()
190
197
        client.connect((server.host, server.port))
191
198
        client.write('ping\n')
192
 
        sync.wait()
193
 
        self.assertRaises(FailToRespond, server.pending_exception)
 
199
        # Wait for the exception to be caught
 
200
        caught.wait()
 
201
        # Check that the connection thread did catch the exception,
 
202
        # http://pad.lv/869366 was wrongly checking the server thread which
 
203
        # works for TestingTCPServer where the connection is handled in the
 
204
        # same thread than the server one but is racy for
 
205
        # TestingThreadingTCPServer where the server thread may be in a
 
206
        # blocking accept() call (or not).
 
207
        try:
 
208
            self.connection_thread.pending_exception()
 
209
        except FailToRespond:
 
210
            # Great, the test succeeded
 
211
            pass
 
212
        else:
 
213
            # If the exception is not in the connection thread anymore, it's in
 
214
            # the server's one. 
 
215
            server.server.stopped.wait()
 
216
            # The exception is available now
 
217
            self.assertRaises(FailToRespond, server.pending_exception)
194
218
 
195
219
    def test_exception_swallowed_while_serving(self):
196
 
        sync = threading.Event()
197
 
        sync.clear()
 
220
        # We need to ensure the exception has been caught
 
221
        caught = threading.Event()
 
222
        caught.clear()
 
223
        # The thread that will serve the client, this needs to be an attribute
 
224
        # so the handler below can access it when it's executed (it's
 
225
        # instantiated when the request is processed)
 
226
        self.connection_thread = None
198
227
        class CantServe(Exception):
199
228
            pass
200
229
 
201
230
        class FailingWhileServingConnectionHandler(TCPConnectionHandler):
202
231
 
203
 
            def handle(self):
204
 
                # We want to sync with the thread that is serving the
205
 
                # connection.
206
 
                threading.currentThread().set_sync_event(sync)
 
232
            def handle(request):
 
233
                # Capture the thread and make it use 'caught' so we can wait on
 
234
                # the even that will be set when the exception is caught. We
 
235
                # also capture the thread to know where to look.
 
236
                self.connection_thread = threading.currentThread()
 
237
                self.connection_thread.set_sync_event(caught)
207
238
                raise CantServe()
208
239
 
209
240
        server = self.get_server(
213
244
        client = self.get_client()
214
245
        # Connect to the server so the exception is raised there
215
246
        client.connect((server.host, server.port))
216
 
        # Wait for the exception to propagate.
217
 
        sync.wait()
 
247
        # Wait for the exception to be caught
 
248
        caught.wait()
218
249
        # The connection wasn't served properly but the exception should have
219
 
        # been swallowed.
 
250
        # been swallowed (see test_server_crash_while_responding remark about
 
251
        # http://pad.lv/869366 explaining why we can't check the server thread
 
252
        # here). More precisely, the exception *has* been caught and captured
 
253
        # but it is cleared when joining the thread (or trying to acquire the
 
254
        # exception) and as such won't propagate to the server thread.
 
255
        self.connection_thread.pending_exception()
220
256
        server.pending_exception()