BIND 10 trac2398, updated. 65962eeed622a6fc92b2e47ac881fbe7b54d3bf2 [2398] update doc and comments

BIND 10 source code commits bind10-changes at lists.isc.org
Fri Nov 23 15:03:27 UTC 2012


The branch, trac2398 has been updated
       via  65962eeed622a6fc92b2e47ac881fbe7b54d3bf2 (commit)
       via  553dfb31fc1ef98ae43e9d2929ba748c4ea14ca4 (commit)
       via  0ce073c9aab78d0136e7c2ba48731741b886ab4b (commit)
      from  8018033c5e0e31be028a3c08ec33234d794aa708 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 65962eeed622a6fc92b2e47ac881fbe7b54d3bf2
Author: Jelte Jansen <jelte at isc.org>
Date:   Fri Nov 23 16:03:07 2012 +0100

    [2398] update doc and comments

commit 553dfb31fc1ef98ae43e9d2929ba748c4ea14ca4
Author: Jelte Jansen <jelte at isc.org>
Date:   Fri Nov 23 15:38:36 2012 +0100

    [2398] Test and catch more socket errors

commit 0ce073c9aab78d0136e7c2ba48731741b886ab4b
Author: Jelte Jansen <jelte at isc.org>
Date:   Fri Nov 23 14:58:39 2012 +0100

    [2398] Introduce stop mechanism in msgqM
    
    Introduced a lowlevel mechanism into msgq to stop it (other than ye olde SIGTERM); a lowlevel message 'stop' will cause it to stop listening and shut down. Needed to add timeouts to poll() and control() for that, but chose them relatively high (their loop should simply reloop if they time out)
    
    This allows the threaded msgq testing code to be more reliable (and not having to daemonize and arbitrarily kill threads except in the case of major failures, in which case the test itself will fail)

-----------------------------------------------------------------------

Summary of changes:
 src/bin/msgq/msgq.py.in         |   58 +++++++++++++++++-------
 src/bin/msgq/tests/msgq_test.py |   92 ++++++++++++++++++++++-----------------
 2 files changed, 95 insertions(+), 55 deletions(-)

-----------------------------------------------------------------------
diff --git a/src/bin/msgq/msgq.py.in b/src/bin/msgq/msgq.py.in
index 903c722..303ea22 100755
--- a/src/bin/msgq/msgq.py.in
+++ b/src/bin/msgq/msgq.py.in
@@ -127,6 +127,7 @@ class MsgQ:
         self.subs = SubscriptionManager()
         self.lnames = {}
         self.sendbuffs = {}
+        self.running = False
 
     def setup_poller(self):
         """Set up the poll thing.  Internal function."""
@@ -238,6 +239,7 @@ class MsgQ:
         self.subs.unsubscribe_all(sock)
         lname = [ k for k, v in self.lnames.items() if v == sock ][0]
         del self.lnames[lname]
+        sock.shutdown(socket.SHUT_RDWR)
         sock.close()
         del self.sockets[fd]
         if fd in self.sendbuffs:
@@ -315,6 +317,8 @@ class MsgQ:
         elif cmd == 'ping':
             # Command for testing purposes
             self.process_command_ping(sock, routing, data)
+        elif cmd == 'stop':
+            self.stop()
         else:
             sys.stderr.write("[b10-msgq] Invalid command: %s\n" % cmd)
 
@@ -342,7 +346,9 @@ class MsgQ:
             sock.setblocking(0)
             return sock.send(data)
         except socket.error as e:
-            if e.errno == errno.EAGAIN or e.errno == errno.EWOULDBLOCK:
+            if e.errno in [ errno.EAGAIN,
+                            errno.EWOULDBLOCK,
+                            errno.EINTR ]:
                 return 0
             else:
                 raise e
@@ -359,11 +365,14 @@ class MsgQ:
             try:
                 amount_sent = self.__send_data(sock, msg)
             except socket.error as sockerr:
-                # in the case the other side seems gone, kill the socket
-                # and drop the send action
-                if sockerr.errno == errno.EPIPE:
-                    print("[b10-msgq] SIGPIPE on send, dropping message " +
-                          "and closing connection")
+                # in the case the other side seems gone, or unable to handle
+                # life, kill the socket and drop the send action
+                if sockerr.errno in [ errno.EPIPE,
+                                      errno.ECONNRESET,
+                                      errno.ENOBUFS
+                                    ]:
+                    print("[b10-msgq] " + errno.errorcode[sockerr.errno] +
+                          " on send, dropping message and closing connection")
                     self.kill_socket(fileno, sock)
                     return
                 else:
@@ -396,11 +405,14 @@ class MsgQ:
         try:
             amount_sent = self.__send_data(sock, msg)
         except socket.error as sockerr:
-            # in the case the other side seems gone, kill the socket
-            # and drop the send action
-            if sockerr.errno == errno.EPIPE:
-                print("[b10-msgq] SIGPIPE on send, dropping message " +
-                      "and closing connection")
+            # in the case the other side seems gone, or unable to handle
+            # life, kill the socket and drop the send action
+            if sockerr.errno in [ errno.EPIPE,
+                                  errno.ECONNRESET,
+                                  errno.ENOBUFS
+                                ]:
+                print("[b10-msgq] " + errno.errorcode[sockerr.errno] +
+                      " on send, dropping message and closing connection")
                 self.kill_socket(fileno, sock)
                 return
             else:
@@ -469,6 +481,7 @@ class MsgQ:
 
     def run(self):
         """Process messages.  Forever.  Mostly."""
+        self.running = True
 
         if self.poller:
             self.run_poller()
@@ -476,9 +489,13 @@ class MsgQ:
             self.run_kqueue()
 
     def run_poller(self):
-        while True:
+        while self.running:
             try:
-                events = self.poller.poll()
+                # Poll with a timeout so that every once in a while,
+                # the loop checks for self.running.
+                # Timeout set to 2 seconds so as not to block too long,
+                # but also not cause too many loop cycles
+                events = self.poller.poll(2000)
             except select.error as err:
                 if err.args[0] == errno.EINTR:
                     events = []
@@ -491,12 +508,18 @@ class MsgQ:
                 else:
                     if event & select.POLLOUT:
                         self.__process_write(fd)
-                    if event & select.POLLIN:
+                    elif event & select.POLLIN:
                         self.process_socket(fd)
+                    else:
+                        print("[XX] UNKNOWN EVENT")
 
     def run_kqueue(self):
-        while True:
-            events = self.kqueue.control(None, 10)
+        while self.running:
+            # Check with a timeout so that every once in a while,
+            # the loop checks for self.running.
+            # Timeout set to 2 seconds so as not to block too long,
+            # but also not cause too many loop cycles
+            events = self.kqueue.control(None, 10, 2)
             if not events:
                 raise RuntimeError('serve: kqueue returned no events')
 
@@ -513,6 +536,9 @@ class MsgQ:
                         self.kill_socket(event.ident,
                                          self.sockets[event.ident])
 
+    def stop(self):
+        self.running = False
+
     def shutdown(self):
         """Stop the MsgQ master."""
         if self.verbose:
diff --git a/src/bin/msgq/tests/msgq_test.py b/src/bin/msgq/tests/msgq_test.py
index 95ef968..b6426b4 100644
--- a/src/bin/msgq/tests/msgq_test.py
+++ b/src/bin/msgq/tests/msgq_test.py
@@ -138,10 +138,9 @@ class BadSocket:
         """
         Parameters:
         real_socket: The actual socket to wrap
-        raise_on_send: integer. If send_exception is not None, it will be
-                                raised on this byte (i.e. 1 = on the first
-                                call to send(), 1 = on the 4th call to send)
-                                Note: if 0, send_exception will not be raised.
+        raise_on_send: integer. If higher than 0, and send_exception is
+                       not None, send_exception will be raised on the
+                       'raise_on_send'th call to send().
         send_exception: if not None, this exception will be raised
                         (if raise_on_send is not 0)
         """
@@ -180,16 +179,19 @@ class MsgQThread(threading.Thread):
     def __init__(self, msgq):
         threading.Thread.__init__(self)
         self.msgq_ = msgq
-        self.stop = False
         self.caught_exception = None
+        self.lock = threading.Lock()
 
     def run(self):
         try:
-            while not self.stop:
-                self.msgq_.run()
+            self.msgq_.run()
         except Exception as exc:
+            # Store the exception to make the test fail if necessary
             self.caught_exception = exc
 
+    def stop(self):
+        self.msgq_.stop()
+
 
 class SendNonblock(unittest.TestCase):
     """
@@ -352,7 +354,6 @@ class SendNonblock(unittest.TestCase):
         # Run it in a thread
         msgq_thread = MsgQThread(msgq)
         # If we're done, just kill it
-        msgq_thread.daemon = True
         msgq_thread.start()
 
         if expect_arrive:
@@ -361,12 +362,36 @@ class SendNonblock(unittest.TestCase):
             self.assertEqual(env, recv_env)
             self.assertEqual(msg, recv_msg)
 
-        # Give it a chance to stop, if it doesn't, no problem, it'll
-        # die when the program does
-        msgq_thread.join(0.2)
+            # expect_arrive also suggests everything should
+            # still be working, so a stop command should also
+            # be processed correctly
+            msg = msgq.preparemsg({"type" : "stop"})
+            read.sendall(msg)
+        else:
+            # OK, then bluntly call stop itself
+            # First give it a chance to handle any remaining events.
+            # 1 second arbitrarily chosen to hopefully be long enough
+            # yet not bog down the tests too much.
+            msgq_thread.join(1.0)
+            # If it didn't crash, stop it now.
+            msgq.stop()
+
+        # Wait for thread to stop if it hasn't already.
+        # Put in a (long) timeout; the thread *should* stop, but if it
+        # does not, we don't want the test to hang forever
+        msgq_thread.join(60)
+        # Fail the test if it didn't stop
+        self.assertFalse(msgq_thread.isAlive(), "Thread did not stop")
 
         # Check the exception from the thread, if any
-        self.assertEqual(expect_send_exception, msgq_thread.caught_exception)
+        # First, if we didn't expect it; reraise it (to make test fail and
+        # show the stacktrace for debugging)
+        if expect_send_exception is None:
+            if msgq_thread.caught_exception is not None:
+                raise msgq_thread.caught_exception
+        else:
+            # If we *did* expect it, fail it there was none
+            self.assertIsNotNone(msgq_thread.caught_exception)
 
     def do_send_with_send_error(self, raise_on_send, send_exception,
                                 expect_answer=True,
@@ -384,48 +409,37 @@ class SendNonblock(unittest.TestCase):
                                send_exception is raised by BadSocket.
         """
         (write, read) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
-        # prevent the test from hanging if something goes wrong
-        read.settimeout(0.2)
-        write.settimeout(0.2)
         badwrite = BadSocket(write, raise_on_send, send_exception)
         self.do_send(badwrite, read, expect_answer, expect_send_exception)
         write.close()
         read.close()
 
-    def test_send_raise_eagain(self):
-        """
-        Test whether msgq survices an EAGAIN socket error when sending.
-        Two tests are done: one where EAGAIN is raised on the 3rd octet,
-                            and one on the 23rd.
-        """
-        sockerr = socket.error
-        sockerr.errno = errno.EAGAIN
-        self.do_send_with_send_error(3, sockerr)
-        self.do_send_with_send_error(23, sockerr)
-
-    def test_send_raise_ewouldblock(self):
+    def test_send_raise_recoverable(self):
         """
-        Test whether msgq survices an EWOULDBLOCK socket error when sending.
-        Two tests are done: one where EWOULDBLOCK is raised on the 3rd octet,
+        Test whether msgq survices a recoverable socket errors when sending.
+        Two tests are done: one where the error is raised on the 3rd octet,
                             and one on the 23rd.
         """
         sockerr = socket.error
-        sockerr.errno = errno.EWOULDBLOCK
-        self.do_send_with_send_error(3, sockerr)
-        self.do_send_with_send_error(23, sockerr)
+        for err in [ errno.EAGAIN, errno.EWOULDBLOCK, errno.EINTR ]:
+            sockerr.errno = err
+            self.do_send_with_send_error(3, sockerr)
+            self.do_send_with_send_error(23, sockerr)
 
-    def test_send_raise_pipe(self):
+    def test_send_raise_nonrecoverable(self):
         """
-        Test whether msgq survices an EPIPE socket error when sending.
-        Two tests are done: one where EPIPE is raised on the 3rd octet,
+        Test whether msgq survives socket errors that are nonrecoverable
+        (for said socket that is, i.e. EPIPE etc).
+        Two tests are done: one where the error is raised on the 3rd octet,
                             and one on the 23rd.
         """
         sockerr = socket.error
-        sockerr.errno = errno.EPIPE
-        self.do_send_with_send_error(3, sockerr, False)
-        self.do_send_with_send_error(23, sockerr, False)
+        for err in [ errno.EPIPE, errno.ENOBUFS, errno.ECONNRESET ]:
+            sockerr.errno = err
+            self.do_send_with_send_error(3, sockerr, False)
+            self.do_send_with_send_error(23, sockerr, False)
 
-    def test_send_raise_exception(self):
+    def otest_send_raise_crash(self):
         """
         Test whether msgq does NOT survive on a general exception.
         Note, perhaps it should; but we'd have to first discuss and decide



More information about the bind10-changes mailing list