From f98057668f633c255a93d653747949fa73073c06 Mon Sep 17 00:00:00 2001
From: Jim Fulton <jim@zope.com>
Date: Tue, 27 Apr 2010 20:23:12 +0000
Subject: [PATCH] On Mac OS X, clients that connected and disconnected quickly
 could cause a ZEO server to stop accepting connections, due to a failure to
 catch errors in the initial part of the connection process.

The failure to properly handle exceptions while accepting
connections is potentially problematic on other platforms.

Fixes: https://bugs.launchpad.net/zodb/+bug/135108
---
 src/CHANGES.txt          | 15 +++++++++++++++
 src/ZEO/tests/testZEO.py | 28 ++++++++++++++++++++++++++++
 src/ZEO/zrpc/server.py   | 23 +++++++++++++++++++++--
 3 files changed, 64 insertions(+), 2 deletions(-)

diff --git a/src/CHANGES.txt b/src/CHANGES.txt
index 0eb4919c..9756e30a 100644
--- a/src/CHANGES.txt
+++ b/src/CHANGES.txt
@@ -2,6 +2,21 @@
  Change History
 ================
 
+3.9.6 (unreleased)
+==================
+
+Bugs Fixed
+----------
+
+- On Mac OS X, clients that connected and disconnected quickly could
+  cause a ZEO server to stop accepting connections, due to a failure
+  to catch errors in the initial part of the connection process.
+
+  The failure to properly handle exceptions while accepting
+  connections is potentially problematic on other platforms.
+
+  Fixes: https://bugs.launchpad.net/zodb/+bug/135108
+
 3.9.5 (2010-04-23)
 ==================
 
diff --git a/src/ZEO/tests/testZEO.py b/src/ZEO/tests/testZEO.py
index 9daf82d3..11ff0c12 100644
--- a/src/ZEO/tests/testZEO.py
+++ b/src/ZEO/tests/testZEO.py
@@ -1227,6 +1227,34 @@ def runzeo_without_configfile():
     testing exit immediately
     """
 
+def quick_close_doesnt_kill_server():
+    r"""
+
+    Start a server:
+
+    >>> addr, _ = start_server()
+
+    Now connect and immediately disconnect. This caused the server to
+    die in the past:
+
+    >>> import socket, struct
+    >>> for i in range(5):
+    ...     s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    ...     s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
+    ...                  struct.pack('ii', 1, 0))
+    ...     s.connect(addr)
+    ...     s.close()
+
+    Now we should be able to connect as normal:
+
+    >>> db = ZEO.DB(addr)
+    >>> db.storage.is_connected()
+    True
+
+    >>> db.close()
+
+    """
+
 slow_test_classes = [
     BlobAdaptedFileStorageTests, BlobWritableCacheTests,
     DemoStorageTests, FileStorageTests, MappingStorageTests,
diff --git a/src/ZEO/zrpc/server.py b/src/ZEO/zrpc/server.py
index 3a29810b..25fc22fb 100644
--- a/src/ZEO/zrpc/server.py
+++ b/src/ZEO/zrpc/server.py
@@ -17,6 +17,7 @@ import types
 
 from ZEO.zrpc.connection import Connection
 from ZEO.zrpc.log import log
+import ZEO.zrpc.log
 import logging
 
 # Export the main asyncore loop
@@ -54,5 +55,23 @@ class Dispatcher(asyncore.dispatcher):
         except socket.error, msg:
             log("accepted failed: %s" % msg)
             return
-        c = self.factory(sock, addr)
-        log("connect from %s: %s" % (repr(addr), c))
+
+        # We could short-circuit the attempt below in some edge cases
+        # and avoid a log message by checking for addr being None.
+        # Unfortunately, our test for the code below,
+        # quick_close_doesnt_kill_server, causes addr to be None and
+        # we'd have to write a test for the non-None case, which is
+        # *even* harder to provoke. :/ So we'll leave things as they
+        # are for now.
+
+        # It might be better to check whether the socket has been
+        # closed, but I don't see a way to do that. :(
+
+        try:
+            c = self.factory(sock, addr)
+        except:
+            if sock.fileno() in asyncore.socket_map:
+                del asyncore.socket_map[sock.fileno()]
+            ZEO.zrpc.log.logger.exception("Error in handle_accept")
+        else:
+            log("connect from %s: %s" % (repr(addr), c))
-- 
2.30.9