[Python-3000] socket GC worries

Bill Janssen janssen at parc.com
Tue Oct 30 20:49:21 CET 2007


> But if we remove SocketCloser, there's no need for the cyclic GC to be
> involved.  If the count (of the number of outstanding SocketIO
> instances pointing to this socket.socket) is just moved into the
> socket.socket object itself, there's no cyclic reference, and normal
> refcounting should work just fine.  I don't even think a __del__ method
> on socket.socket is necessary.

Here's a patch, for whenever you get back to this.  You can
ignore/remove the first hunk, which is about SSL.  I've tried all the
tests, and they work.  I've looked for leaks in test_socket and
test_ssl, no leaks.

Bill

Index: Lib/socket.py
===================================================================
--- Lib/socket.py	(revision 58714)
+++ Lib/socket.py	(working copy)
@@ -21,7 +21,6 @@
 htons(), htonl() -- convert 16, 32 bit int from host to network byte order
 inet_aton() -- convert IP addr string (123.45.67.89) to 32-bit packed format
 inet_ntoa() -- convert 32-bit packed format IP to string (123.45.67.89)
-ssl() -- secure socket layer support (only available if configured)
 socket.getdefaulttimeout() -- get the default timeout value
 socket.setdefaulttimeout() -- set the default timeout value
 create_connection() -- connects to an address, with an optional timeout
@@ -46,36 +45,6 @@
 import _socket
 from _socket import *
 
-try:
-    import _ssl
-    import ssl as _realssl
-except ImportError:
-    # no SSL support
-    pass
-else:
-    def ssl(sock, keyfile=None, certfile=None):
-        # we do an internal import here because the ssl
-        # module imports the socket module
-        warnings.warn("socket.ssl() is deprecated.  Use ssl.wrap_socket() instead.",
-                      DeprecationWarning, stacklevel=2)
-        return _realssl.sslwrap_simple(sock, keyfile, certfile)
-
-    # we need to import the same constants we used to...
-    from _ssl import SSLError as sslerror
-    from _ssl import \
-         RAND_add, \
-         RAND_egd, \
-         RAND_status, \
-         SSL_ERROR_ZERO_RETURN, \
-         SSL_ERROR_WANT_READ, \
-         SSL_ERROR_WANT_WRITE, \
-         SSL_ERROR_WANT_X509_LOOKUP, \
-         SSL_ERROR_SYSCALL, \
-         SSL_ERROR_SSL, \
-         SSL_ERROR_WANT_CONNECT, \
-         SSL_ERROR_EOF, \
-         SSL_ERROR_INVALID_ERROR_CODE
-
 import os, sys, io
 
 try:
@@ -119,49 +88,11 @@
         nfd = os.dup(fd)
         return socket(family, type, proto, fileno=nfd)
 
-class SocketCloser:
-
-    """Helper to manage socket close() logic for makefile().
-
-    The OS socket should not be closed until the socket and all
-    of its makefile-children are closed.  If the refcount is zero
-    when socket.close() is called, this is easy: Just close the
-    socket.  If the refcount is non-zero when socket.close() is
-    called, then the real close should not occur until the last
-    makefile-child is closed.
-    """
-
-    def __init__(self, sock):
-        self._sock = sock
-        self._makefile_refs = 0
-        # Test whether the socket is open.
-        try:
-            sock.fileno()
-            self._socket_open = True
-        except error:
-            self._socket_open = False
-
-    def socket_close(self):
-        self._socket_open = False
-        self.close()
-
-    def makefile_open(self):
-        self._makefile_refs += 1
-
-    def makefile_close(self):
-        self._makefile_refs -= 1
-        self.close()
-
-    def close(self):
-        if not (self._socket_open or self._makefile_refs):
-            self._sock._real_close()
-
-
 class socket(_socket.socket):
 
     """A subclass of _socket.socket adding the makefile() method."""
 
-    __slots__ = ["__weakref__", "_closer"]
+    __slots__ = ["__weakref__", "_io_refs", "_closed"]
     if not _can_dup_socket:
         __slots__.append("_base")
 
@@ -170,16 +101,17 @@
             _socket.socket.__init__(self, family, type, proto)
         else:
             _socket.socket.__init__(self, family, type, proto, fileno)
-        # Defer creating a SocketCloser until makefile() is actually called.
-        self._closer = None
+        self._io_refs = 0
+        self._closed = False
 
     def __repr__(self):
         """Wrap __repr__() to reveal the real class name."""
         s = _socket.socket.__repr__(self)
         if s.startswith("<socket object"):
-            s = "<%s.%s%s" % (self.__class__.__module__,
-                              self.__class__.__name__,
-                              s[7:])
+            s = "<%s.%s%s%s" % (self.__class__.__module__,
+                                self.__class__.__name__,
+                                (self._closed and " [closed] ") or "",
+                                s[7:])
         return s
 
     def accept(self):
@@ -196,6 +128,12 @@
             conn.close()
         return wrapper, addr
 
+    def decref_socketios(self):
+        if self._io_refs > 0:
+            self._io_refs -= 1
+        if self._closed:
+            self.close()
+
     def makefile(self, mode="r", buffering=None, *,
                  encoding=None, newline=None):
         """Return an I/O stream connected to the socket.
@@ -216,9 +154,8 @@
             rawmode += "r"
         if writing:
             rawmode += "w"
-        if self._closer is None:
-            self._closer = SocketCloser(self)
-        raw = SocketIO(self, rawmode, self._closer)
+        raw = SocketIO(self, rawmode)
+        self._io_refs += 1
         if buffering is None:
             buffering = -1
         if buffering < 0:
@@ -246,10 +183,9 @@
         return text
 
     def close(self):
-        if self._closer is None:
+        self._closed = True
+        if self._io_refs < 1:
             self._real_close()
-        else:
-            self._closer.socket_close()
 
     # _real_close calls close on the _socket.socket base class.
 
@@ -275,16 +211,14 @@
 
     # XXX More docs
 
-    def __init__(self, sock, mode, closer):
+    def __init__(self, sock, mode):
         if mode not in ("r", "w", "rw"):
             raise ValueError("invalid mode: %r" % mode)
         io.RawIOBase.__init__(self)
         self._sock = sock
         self._mode = mode
-        self._closer = closer
         self._reading = "r" in mode
         self._writing = "w" in mode
-        closer.makefile_open()
 
     def readinto(self, b):
         self._checkClosed()
@@ -308,10 +242,12 @@
     def close(self):
         if self.closed:
             return
-        self._closer.makefile_close()
         io.RawIOBase.close(self)
 
+    def __del__(self):
+        self._sock.decref_socketios()
 
+
 def getfqdn(name=''):
     """Get fully qualified domain name from name.
 


More information about the Python-3000 mailing list