[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