~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_server.py

  • Committer: Andrew Bennetts
  • Date: 2011-03-15 07:54:39 UTC
  • mfrom: (0.38.5 trunk)
  • mto: This revision was merged to the branch mainline in revision 5726.
  • Revision ID: andrew.bennetts@canonical.com-20110315075439-nzm293joz143cx0k
Merge bzr-changelog-merge plugin.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2010 Canonical Ltd
 
1
# Copyright (C) 2010, 2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
17
17
import errno
18
18
import socket
19
19
import SocketServer
20
 
import select
21
20
import sys
22
21
import threading
23
22
 
24
23
 
25
24
from bzrlib import (
 
25
    cethread,
26
26
    osutils,
27
27
    transport,
28
28
    urlutils,
243
243
        raise NotImplementedError
244
244
 
245
245
 
246
 
class ThreadWithException(threading.Thread):
247
 
    """A catching exception thread.
248
 
 
249
 
    If an exception occurs during the thread execution, it's caught and
250
 
    re-raised when the thread is joined().
251
 
    """
252
 
 
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.
258
 
        try:
259
 
            event = kwargs.pop('event')
260
 
        except KeyError:
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
267
 
 
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)
271
 
 
272
 
    def set_ready_event(self, event):
273
 
        """Set the ``ready`` event used to synchronize exception catching.
274
 
 
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
278
 
        will hang.
279
 
 
280
 
        Some threads require multiple events and should set the relevant one
281
 
        when appropriate.
282
 
        """
283
 
        self.ready = event
284
 
 
285
 
    def set_ignored_exceptions(self, ignored):
286
 
        """Declare which exceptions will be ignored.
287
 
 
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
295
 
        """
296
 
        if ignored is None:
297
 
            self.ignored_exceptions = None
298
 
        elif isinstance(ignored, (Exception, tuple)):
299
 
            self.ignored_exceptions = lambda e: isinstance(e, ignored)
300
 
        else:
301
 
            self.ignored_exceptions = ignored
302
 
 
303
 
    def run(self):
304
 
        """Overrides Thread.run to capture any exception."""
305
 
        self.ready.clear()
306
 
        try:
307
 
            try:
308
 
                super(ThreadWithException, self).run()
309
 
            except:
310
 
                self.exception = sys.exc_info()
311
 
        finally:
312
 
            # Make sure the calling thread is released
313
 
            self.ready.set()
314
 
 
 
246
class TestThread(cethread.CatchingExceptionThread):
315
247
 
316
248
    def join(self, timeout=5):
317
 
        """Overrides Thread.join to raise any exception caught.
318
 
 
319
 
 
320
 
        Calling join(timeout=0) will raise the caught exception or return None
321
 
        if the thread is still alive.
 
249
        """Overrides to use a default timeout.
322
250
 
323
251
        The default timeout is set to 5 and should expire only when a thread
324
252
        serving a client connection is hung.
325
253
        """
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
 
254
        super(TestThread, self).join(timeout)
334
255
        if timeout and self.isAlive():
335
256
            # The timeout expired without joining the thread, the thread is
336
257
            # therefore stucked and that's a failure as far as the test is
343
264
            sys.stderr.write('thread %s hung\n' % (self.name,))
344
265
            #raise AssertionError('thread %s hung' % (self.name,))
345
266
 
346
 
    def pending_exception(self):
347
 
        """Raise the caught exception.
348
 
 
349
 
        This does nothing if no exception occurred.
350
 
        """
351
 
        self.join(timeout=0)
352
 
 
353
267
 
354
268
class TestingTCPServerMixin:
355
269
    """Mixin to support running SocketServer.TCPServer in a thread.
523
437
        """Start a new thread to process the request."""
524
438
        started = threading.Event()
525
439
        stopped = threading.Event()
526
 
        t = ThreadWithException(
527
 
            event=stopped,
 
440
        t = TestThread(
 
441
            sync_event=stopped,
528
442
            name='%s -> %s' % (client_address, self.server_address),
529
443
            target = self.process_request_thread,
530
444
            args = (started, stopped, request, client_address))
531
445
        # Update the client description
532
446
        self.clients.pop()
533
447
        self.clients.append((request, client_address, t))
534
 
        # Propagate the exception handler since we must use the same one for
535
 
        # connections running in their own threads than TestingTCPServer.
 
448
        # Propagate the exception handler since we must use the same one as
 
449
        # TestingTCPServer for connections running in their own threads.
536
450
        t.set_ignored_exceptions(self.ignored_exceptions)
537
451
        t.start()
538
452
        started.wait()
590
504
 
591
505
    def start_server(self):
592
506
        self.server = self.create_server()
593
 
        self._server_thread = ThreadWithException(
594
 
            event=self.server.started,
 
507
        self._server_thread = TestThread(
 
508
            sync_event=self.server.started,
595
509
            target=self.run_server)
596
510
        self._server_thread.start()
597
511
        # Wait for the server thread to start (i.e release the lock)
607
521
        self._server_thread.pending_exception()
608
522
        # From now on, we'll use a different event to ensure the server can set
609
523
        # its exception
610
 
        self._server_thread.set_ready_event(self.server.stopped)
 
524
        self._server_thread.set_sync_event(self.server.stopped)
611
525
 
612
526
    def run_server(self):
613
527
        self.server.serve()
634
548
                # server thread, it may happen that it's not blocked or even
635
549
                # not started.
636
550
                pass
637
 
            # We start shutting down the client while the server itself is
 
551
            # We start shutting down the clients while the server itself is
638
552
            # shutting down.
639
553
            self.server.stop_client_connections()
640
554
            # Now we wait for the thread running self.server.serve() to finish
695
609
        server.SmartTCPServer.__init__(self, backing_transport,
696
610
                                       root_client_path)
697
611
    def serve(self):
698
 
        # FIXME: No test are exercising the hooks for the test server
699
 
        # -- vila 20100618
700
612
        self.run_server_started_hooks()
701
613
        try:
702
614
            TestingThreadingTCPServer.serve(self)
804
716
        """Get a backing transport from a server we are decorating."""
805
717
        url = 'readonly+' + backing_transport_server.get_url()
806
718
        return transport.get_transport(url)
807
 
 
808
 
 
809
 
 
810