243
243
raise NotImplementedError
246
class TestThread(cethread.CatchingExceptionThread):
246
class ThreadWithException(threading.Thread):
247
"""A catching exception thread.
249
If an exception occurs during the thread execution, it's caught and
250
re-raised when the thread is joined().
253
def __init__(self, *args, **kwargs):
254
# There are cases where the calling thread must wait, yet, if an
255
# exception occurs, the event should be set so the caller is not
256
# blocked. The main example is a calling thread that want to wait for
257
# the called thread to be in a given state before continuing.
259
event = kwargs.pop('event')
261
# If the caller didn't pass a specific event, create our own
262
event = threading.Event()
263
super(ThreadWithException, self).__init__(*args, **kwargs)
264
self.set_ready_event(event)
265
self.exception = None
266
self.ignored_exceptions = None # see set_ignored_exceptions
268
# compatibility thunk for python-2.4 and python-2.5...
269
if sys.version_info < (2, 6):
270
name = property(threading.Thread.getName, threading.Thread.setName)
272
def set_ready_event(self, event):
273
"""Set the ``ready`` event used to synchronize exception catching.
275
When the thread uses an event to synchronize itself with another thread
276
(setting it when the other thread can wake up from a ``wait`` call),
277
the event must be set after catching an exception or the other thread
280
Some threads require multiple events and should set the relevant one
285
def set_ignored_exceptions(self, ignored):
286
"""Declare which exceptions will be ignored.
288
:param ignored: Can be either:
289
- None: all exceptions will be raised,
290
- an exception class: the instances of this class will be ignored,
291
- a tuple of exception classes: the instances of any class of the
292
list will be ignored,
293
- a callable: that will be passed the exception object
294
and should return True if the exception should be ignored
297
self.ignored_exceptions = None
298
elif isinstance(ignored, (Exception, tuple)):
299
self.ignored_exceptions = lambda e: isinstance(e, ignored)
301
self.ignored_exceptions = ignored
304
"""Overrides Thread.run to capture any exception."""
308
super(ThreadWithException, self).run()
310
self.exception = sys.exc_info()
312
# Make sure the calling thread is released
248
316
def join(self, timeout=5):
249
"""Overrides to use a default timeout.
317
"""Overrides Thread.join to raise any exception caught.
320
Calling join(timeout=0) will raise the caught exception or return None
321
if the thread is still alive.
251
323
The default timeout is set to 5 and should expire only when a thread
252
324
serving a client connection is hung.
254
super(TestThread, self).join(timeout)
326
super(ThreadWithException, self).join(timeout)
327
if self.exception is not None:
328
exc_class, exc_value, exc_tb = self.exception
329
self.exception = None # The exception should be raised only once
330
if (self.ignored_exceptions is None
331
or not self.ignored_exceptions(exc_value)):
332
# Raise non ignored exceptions
333
raise exc_class, exc_value, exc_tb
255
334
if timeout and self.isAlive():
256
335
# The timeout expired without joining the thread, the thread is
257
336
# therefore stucked and that's a failure as far as the test is
437
523
"""Start a new thread to process the request."""
438
524
started = threading.Event()
439
525
stopped = threading.Event()
526
t = ThreadWithException(
442
528
name='%s -> %s' % (client_address, self.server_address),
443
529
target = self.process_request_thread,
444
530
args = (started, stopped, request, client_address))
445
531
# Update the client description
446
532
self.clients.pop()
447
533
self.clients.append((request, client_address, t))
448
# Propagate the exception handler since we must use the same one as
449
# TestingTCPServer for connections running in their own threads.
534
# Propagate the exception handler since we must use the same one for
535
# connections running in their own threads than TestingTCPServer.
450
536
t.set_ignored_exceptions(self.ignored_exceptions)