Commit ed147335 authored by Jason Madden's avatar Jason Madden

Refactor _socket2/_socket3 to move more truly common methods into SocketMixin.

parent 8b0432b4
......@@ -112,6 +112,10 @@ jobs:
- name: Enable emulation
run: |
docker run --rm --privileged hypriot/qemu-register
# This one was seen in pyca/bcrypt. What's the difference?
# (Other than this one not working.)
#run: |
# docker run --rm --privileged multiarch/qemu-user-static:register --reset
- name: Build and test gevent
# Skip the bulk of the tests, running them emulated on arm takes
# forever. Likewise, don't build or even configure c-ares; in the emulated platform
......@@ -275,7 +279,7 @@ jobs:
python -c 'import gevent.ares; print(gevent.ares)'
ccache -s
- name: Lint (Python 3.9)
if: ${{ matrix.python-version == 3.9 }}
if: matrix.python-version == 3.9 && startsWith(runner.os, 'Linux')
# We only need to do this on one version, and it should be Python 3, because
# pylint has stopped updating for Python 2.
# We do this here rather than a separate job to avoid the compilation overhead.
......
......@@ -2,5 +2,10 @@
:mod:`gevent._socket2` -- Python 2 socket module
==================================================
.. caution:: Some of the docstrings are automatically generated and
may be correct for Python 3 but not Python 2. Please see
the standard library documentation.
.. automodule:: gevent._socket2
:members:
:inherited-members:
......@@ -4,3 +4,4 @@
.. automodule:: gevent._socket3
:members:
:inherited-members:
......@@ -52,6 +52,8 @@ Python 2 and Python 3, respectively.
standard library sockets never have.
.. toctree::
Python 3 interface <gevent._socket3>
......
......@@ -2,3 +2,6 @@ Remove the ``__dict__`` attribute from `gevent.socket.socket` objects. The
standard library socket do not have a ``__dict__``.
Noticed by Carson Ip.
As part of this refactoring, share more common socket code between Python 2
and Python 3.
......@@ -12,7 +12,6 @@ import sys
from gevent import _socketcommon
from gevent._util import copy_globals
from gevent._compat import PYPY
from gevent.timeout import Timeout
copy_globals(_socketcommon, globals(),
names_to_ignore=_socketcommon.__py3_imports__ + _socketcommon.__extensions__,
......@@ -25,17 +24,10 @@ __imports__ = [i for i in _socketcommon.__imports__ if i not in _socketcommon.__
__dns__ = _socketcommon.__dns__
try:
_fileobject = __socket__._fileobject
_socketmethods = __socket__._socketmethods
except AttributeError:
# Allow this module to be imported under Python 3
# for building the docs
_fileobject = object
_socketmethods = ('bind', 'connect', 'connect_ex',
'fileno', 'listen', 'getpeername',
'getsockname', 'getsockopt',
'setsockopt', 'sendall',
'setblocking', 'settimeout',
'gettimeout', 'shutdown')
else:
# Python 2 doesn't natively support with statements on _fileobject;
# but it substantially eases our test cases if we can do the same with on both Py3
......@@ -74,8 +66,6 @@ else:
super(_fileobject, self).close()
from gevent._greenlet_primitives import get_memory as _get_memory
class _closedsocket(object):
__slots__ = ()
......@@ -100,11 +90,8 @@ class _closedsocket(object):
__getattr__ = _dummy
timeout_default = object()
gtype = type
from gevent._hub_primitives import wait_on_socket as _wait_on_socket
_Base = _socketcommon.SocketMixin
class socket(_Base):
......@@ -189,17 +176,6 @@ class socket(_Base):
result += ' timeout=' + str(self.timeout)
return result
def _get_ref(self):
return self._read_event.ref or self._write_event.ref
def _set_ref(self, value):
self._read_event.ref = value
self._write_event.ref = value
ref = property(_get_ref, _set_ref)
_wait = _wait_on_socket
def accept(self):
while 1:
try:
......@@ -255,46 +231,6 @@ class socket(_Base):
def closed(self):
return isinstance(self._sock, _closedsocket)
def connect(self, address):
"""
Connect to *address*.
.. versionchanged:: 20.6.0
If the host part of the address includes an IPv6 scope ID,
it will be used instead of ignored, if the platform supplies
:func:`socket.inet_pton`.
"""
if self.timeout == 0.0:
return self._sock.connect(address)
address = _socketcommon._resolve_addr(self._sock, address)
timer = Timeout._start_new_or_dummy(self.timeout, timeout('timed out'))
try:
while 1:
err = self._sock.getsockopt(SOL_SOCKET, SO_ERROR)
if err:
raise error(err, strerror(err))
result = self._sock.connect_ex(address)
if not result or result == EISCONN:
break
if (result in (EWOULDBLOCK, EINPROGRESS, EALREADY)) or (result == EINVAL and is_windows):
self._wait(self._write_event)
else:
raise error(result, strerror(result))
finally:
timer.close()
def connect_ex(self, address):
try:
return self.connect(address) or 0
except timeout:
return EAGAIN
except error as ex:
if type(ex) is error: # pylint:disable=unidiomatic-typecheck
return ex.args[0]
raise # gaierror is not silenced by connect_ex
def dup(self):
"""dup() -> socket object
......@@ -321,123 +257,10 @@ class socket(_Base):
self._sock._drop()
return fobj
def recv(self, *args):
while 1:
try:
return self._sock.recv(*args)
except error as ex:
if ex.args[0] != EWOULDBLOCK or self.timeout == 0.0:
raise
# QQQ without clearing exc_info test__refcount.test_clean_exit fails
sys.exc_clear()
self._wait(self._read_event)
def recvfrom(self, *args):
while 1:
try:
return self._sock.recvfrom(*args)
except error as ex:
if ex.args[0] != EWOULDBLOCK or self.timeout == 0.0:
raise
sys.exc_clear()
self._wait(self._read_event)
def recvfrom_into(self, *args):
while 1:
try:
return self._sock.recvfrom_into(*args)
except error as ex:
if ex.args[0] != EWOULDBLOCK or self.timeout == 0.0:
raise
sys.exc_clear()
self._wait(self._read_event)
def recv_into(self, *args):
while 1:
try:
return self._sock.recv_into(*args)
except error as ex:
if ex.args[0] != EWOULDBLOCK or self.timeout == 0.0:
raise
sys.exc_clear()
self._wait(self._read_event)
def send(self, data, flags=0, timeout=timeout_default):
if timeout is timeout_default:
timeout = self.timeout
try:
return self._sock.send(data, flags)
except error as ex:
if ex.args[0] not in _socketcommon.GSENDAGAIN or timeout == 0.0:
raise
sys.exc_clear()
self._wait(self._write_event)
try:
return self._sock.send(data, flags)
except error as ex2:
if ex2.args[0] == EWOULDBLOCK:
return 0
raise
def sendall(self, data, flags=0):
if isinstance(data, unicode):
data = data.encode()
# this sendall is also reused by gevent.ssl.SSLSocket subclass,
# so it should not call self._sock methods directly
data_memory = _get_memory(data)
return _socketcommon._sendall(self, data_memory, flags)
def sendto(self, *args):
try:
return self._sock.sendto(*args)
except error as ex:
if ex.args[0] != EWOULDBLOCK or self.timeout == 0.0:
raise
sys.exc_clear()
self._wait(self._write_event)
try:
return self._sock.sendto(*args)
except error as ex2:
if ex2.args[0] == EWOULDBLOCK:
return 0
raise
def setblocking(self, flag):
if flag:
self.timeout = None
else:
self.timeout = 0.0
def shutdown(self, how):
if how == 0: # SHUT_RD
self.hub.cancel_wait(self._read_event, cancel_wait_ex)
elif how == 1: # SHUT_WR
self.hub.cancel_wait(self._write_event, cancel_wait_ex)
else:
self.hub.cancel_wait(self._read_event, cancel_wait_ex)
self.hub.cancel_wait(self._write_event, cancel_wait_ex)
self._sock.shutdown(how)
family = property(lambda self: self._sock.family)
type = property(lambda self: self._sock.type)
proto = property(lambda self: self._sock.proto)
def fileno(self):
return self._sock.fileno()
def getsockname(self):
return self._sock.getsockname()
def getpeername(self):
return self._sock.getpeername()
# delegate the functions that we haven't implemented to the real socket object
_s = "def %s(self, *args): return self._sock.%s(*args)\n\n"
_m = None
for _m in set(_socketmethods) - set(locals()) - {'settimeout', 'gettimeout'}:
exec(_s % (_m, _m,))
del _m, _s
return _Base.sendall(self, data, flags)
if PYPY:
......
......@@ -14,7 +14,6 @@ import sys
from gevent import _socketcommon
from gevent._util import copy_globals
from gevent._compat import PYPY
from gevent.timeout import Timeout
import _socket
from os import dup
......@@ -23,13 +22,6 @@ copy_globals(_socketcommon, globals(),
names_to_ignore=_socketcommon.__extensions__,
dunder_names_to_keep=())
try:
from errno import EHOSTUNREACH
from errno import ECONNREFUSED
except ImportError:
EHOSTUNREACH = -1
ECONNREFUSED = -1
__socket__ = _socketcommon.__socket__
__implements__ = _socketcommon._implements
......@@ -40,9 +32,6 @@ __dns__ = _socketcommon.__dns__
SocketIO = __socket__.SocketIO # pylint:disable=no-member
from gevent._greenlet_primitives import get_memory as _get_memory
timeout_default = object()
class _closedsocket(object):
__slots__ = ('family', 'type', 'proto', 'orig_fileno', 'description')
......@@ -96,7 +85,6 @@ class _wrefsocket(_socket.socket):
timeout = property(lambda s: s.gettimeout(),
lambda s, nv: s.settimeout(nv))
from gevent._hub_primitives import wait_on_socket as _wait_on_socket
class socket(_socketcommon.SocketMixin):
"""
......@@ -168,16 +156,6 @@ class socket(_socketcommon.SocketMixin):
return self._sock.type & ~_socket.SOCK_NONBLOCK # pylint:disable=no-member
return self._sock.type
def getblocking(self):
"""
Returns whether the socket will approximate blocking
behaviour.
.. versionadded:: 1.3a2
Added in Python 3.7.
"""
return self.timeout != 0.0
def __enter__(self):
return self
......@@ -212,17 +190,6 @@ class socket(_socketcommon.SocketMixin):
def __getstate__(self):
raise TypeError("Cannot serialize socket object")
def _get_ref(self):
return self._read_event.ref or self._write_event.ref
def _set_ref(self, value):
self._read_event.ref = value
self._write_event.ref = value
ref = property(_get_ref, _set_ref)
_wait = _wait_on_socket
def dup(self):
"""dup() -> socket object
......@@ -392,71 +359,6 @@ class socket(_socketcommon.SocketMixin):
self._detach_socket('detached')
return sock.detach()
def connect(self, address):
"""
Connect to *address*.
.. versionchanged:: 20.6.0
If the host part of the address includes an IPv6 scope ID,
it will be used instead of ignored, if the platform supplies
:func:`socket.inet_pton`.
"""
if self.timeout == 0.0:
return _socket.socket.connect(self._sock, address)
address = _socketcommon._resolve_addr(self._sock, address)
with Timeout._start_new_or_dummy(self.timeout, timeout("timed out")):
while True:
err = self.getsockopt(SOL_SOCKET, SO_ERROR)
if err:
raise error(err, strerror(err))
result = _socket.socket.connect_ex(self._sock, address)
if not result or result == EISCONN:
break
if (result in (EWOULDBLOCK, EINPROGRESS, EALREADY)) or (result == EINVAL and is_windows):
self._wait(self._write_event)
else:
if (isinstance(address, tuple)
and address[0] == 'fe80::1'
and result == EHOSTUNREACH):
# On Python 3.7 on mac, we see EHOSTUNREACH
# returned for this link-local address, but it really is
# supposed to be ECONNREFUSED according to the standard library
# tests (test_socket.NetworkConnectionNoServer.test_create_connection)
# (On previous versions, that code passed the '127.0.0.1' IPv4 address, so
# ipv6 link locals were never a factor; 3.7 passes 'localhost'.)
# It is something of a mystery how the stdlib socket code doesn't
# produce EHOSTUNREACH---I (JAM) can't see how socketmodule.c would avoid
# that. The normal connect just calls connect_ex much like we do.
result = ECONNREFUSED
raise error(result, strerror(result))
def connect_ex(self, address):
try:
return self.connect(address) or 0
except timeout:
return EAGAIN
except gaierror: # pylint:disable=try-except-raise
# gaierror/overflowerror/typerror is not silenced by connect_ex;
# gaierror extends OSError (aka error) so catch it first
raise
except error as ex:
# error is now OSError and it has various subclasses.
# Only those that apply to actually connecting are silenced by
# connect_ex.
if ex.errno:
return ex.errno
raise # pragma: no cover
def recv(self, *args):
while True:
try:
return self._sock.recv(*args)
except error as ex:
if ex.args[0] != EWOULDBLOCK or self.timeout == 0.0:
raise
self._wait(self._read_event)
if hasattr(_socket.socket, 'recvmsg'):
# Only on Unix; PyPy 3.5 5.10.0 provides sendmsg and recvmsg, but not
# recvmsg_into (at least on os x)
......@@ -485,72 +387,6 @@ class socket(_socketcommon.SocketMixin):
raise
self._wait(self._read_event)
def recvfrom(self, *args):
while True:
try:
return self._sock.recvfrom(*args)
except error as ex:
if ex.args[0] != EWOULDBLOCK or self.timeout == 0.0:
raise
self._wait(self._read_event)
def recvfrom_into(self, *args):
while True:
try:
return self._sock.recvfrom_into(*args)
except error as ex:
if ex.args[0] != EWOULDBLOCK or self.timeout == 0.0:
raise
self._wait(self._read_event)
def recv_into(self, *args):
while True:
try:
return self._sock.recv_into(*args)
except error as ex:
if ex.args[0] != EWOULDBLOCK or self.timeout == 0.0:
raise
self._wait(self._read_event)
def send(self, data, flags=0, timeout=timeout_default):
if timeout is timeout_default:
timeout = self.timeout
try:
return self._sock.send(data, flags)
except error as ex:
if ex.args[0] not in _socketcommon.GSENDAGAIN or timeout == 0.0:
raise
self._wait(self._write_event)
try:
return _socket.socket.send(self._sock, data, flags)
except error as ex2:
if ex2.args[0] == EWOULDBLOCK:
return 0
raise
def sendall(self, data, flags=0):
# XXX Now that we run on PyPy3, see the notes in _socket2.py's sendall()
# and implement that here if needed.
# PyPy3 is not optimized for performance yet, and is known to be slower than
# PyPy2, so it's possibly premature to do this. However, there is a 3.5 test case that
# possibly exposes this in a severe way.
data_memory = _get_memory(data)
return _socketcommon._sendall(self, data_memory, flags)
def sendto(self, *args):
try:
return self._sock.sendto(*args)
except error as ex:
if ex.args[0] != EWOULDBLOCK or self.timeout == 0.0:
raise
self._wait(self._write_event)
try:
return self._sock.sendto(*args)
except error as ex2:
if ex2.args[0] == EWOULDBLOCK:
return 0
raise
if hasattr(_socket.socket, 'sendmsg'):
# Only on Unix
def sendmsg(self, buffers, ancdata=(), flags=0, address=None):
......@@ -572,26 +408,6 @@ class socket(_socketcommon.SocketMixin):
return 0
raise
def setblocking(self, flag):
# Beginning in 3.6.0b3 this is supposed to raise
# if the file descriptor is closed, but the test for it
# involves closing the fileno directly. Since we
# don't touch the fileno here, it doesn't make sense for
# us.
if flag:
self.timeout = None
else:
self.timeout = 0.0
def shutdown(self, how):
if how == 0: # SHUT_RD
self.hub.cancel_wait(self._read_event, cancel_wait_ex)
elif how == 1: # SHUT_WR
self.hub.cancel_wait(self._write_event, cancel_wait_ex)
else:
self.hub.cancel_wait(self._read_event, cancel_wait_ex)
self.hub.cancel_wait(self._write_event, cancel_wait_ex)
self._sock.shutdown(how)
# sendfile: new in 3.5. But there's no real reason to not
# support it everywhere. Note that we can't use os.sendfile()
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment