Skip to content

Commit 9818142

Browse files
authored
bpo-32331: Fix socket.type when SOCK_NONBLOCK is available (#4877)
1 parent 6efcb6d commit 9818142

File tree

7 files changed

+87
-24
lines changed

7 files changed

+87
-24
lines changed

Doc/library/socket.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,20 @@ The following functions all create :ref:`socket objects <socket-objects>`.
482482
.. versionchanged:: 3.7
483483
The CAN_ISOTP protocol was added.
484484

485+
.. versionchanged:: 3.7
486+
When :const:`SOCK_NONBLOCK` or :const:`SOCK_CLOEXEC`
487+
bit flags are applied to *type* they are cleared, and
488+
:attr:`socket.type` will not reflect them. They are still passed
489+
to the underlying system `socket()` call. Therefore::
490+
491+
sock = socket.socket(
492+
socket.AF_INET,
493+
socket.SOCK_STREAM | socket.SOCK_NONBLOCK)
494+
495+
will still create a non-blocking socket on OSes that support
496+
``SOCK_NONBLOCK``, but ``sock.type`` will be set to
497+
``socket.SOCK_STREAM``.
498+
485499
.. function:: socketpair([family[, type[, proto]]])
486500

487501
Build a pair of connected socket objects using the given address family, socket
@@ -1417,6 +1431,10 @@ to sockets.
14171431

14181432
* ``sock.setblocking(False)`` is equivalent to ``sock.settimeout(0.0)``
14191433

1434+
.. versionchanged:: 3.7
1435+
The method no longer applies :const:`SOCK_NONBLOCK` flag on
1436+
:attr:`socket.type`.
1437+
14201438

14211439
.. method:: socket.settimeout(value)
14221440

@@ -1429,6 +1447,10 @@ to sockets.
14291447

14301448
For further information, please consult the :ref:`notes on socket timeouts <socket-timeouts>`.
14311449

1450+
.. versionchanged:: 3.7
1451+
The method no longer toggles :const:`SOCK_NONBLOCK` flag on
1452+
:attr:`socket.type`.
1453+
14321454

14331455
.. method:: socket.setsockopt(level, optname, value: int)
14341456
.. method:: socket.setsockopt(level, optname, value: buffer)

Doc/whatsnew/3.7.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -892,6 +892,13 @@ Changes in the Python API
892892
recent to be more consistent with :mod:`traceback`.
893893
(Contributed by Jesse Bakker in :issue:`32121`.)
894894

895+
* On OSes that support :const:`socket.SOCK_NONBLOCK` or
896+
:const:`socket.SOCK_CLOEXEC` bit flags, the
897+
:attr:`socket.type <socket.socket.type>` no longer has them applied.
898+
Therefore, checks like ``if sock.type == socket.SOCK_STREAM``
899+
work as expected on all platforms.
900+
(Contributed by Yury Selivanov in :issue:`32331`.)
901+
895902
.. _Unicode Technical Standard #18: https://unicode.org/reports/tr18/
896903

897904
* On Windows the default for the *close_fds* argument of

Lib/socket.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -203,11 +203,7 @@ def accept(self):
203203
For IP sockets, the address info is a pair (hostaddr, port).
204204
"""
205205
fd, addr = self._accept()
206-
# If our type has the SOCK_NONBLOCK flag, we shouldn't pass it onto the
207-
# new socket. We do not currently allow passing SOCK_NONBLOCK to
208-
# accept4, so the returned socket is always blocking.
209-
type = self.type & ~globals().get("SOCK_NONBLOCK", 0)
210-
sock = socket(self.family, type, self.proto, fileno=fd)
206+
sock = socket(self.family, self.type, self.proto, fileno=fd)
211207
# Issue #7995: if no default timeout is set and the listening
212208
# socket had a (non-zero) timeout, force the new socket in blocking
213209
# mode to override platform-specific socket flags inheritance.

Lib/test/test_asyncore.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -726,14 +726,10 @@ def test_connection_attributes(self):
726726
def test_create_socket(self):
727727
s = asyncore.dispatcher()
728728
s.create_socket(self.family)
729+
self.assertEqual(s.socket.type, socket.SOCK_STREAM)
729730
self.assertEqual(s.socket.family, self.family)
730-
SOCK_NONBLOCK = getattr(socket, 'SOCK_NONBLOCK', 0)
731-
sock_type = socket.SOCK_STREAM | SOCK_NONBLOCK
732-
if hasattr(socket, 'SOCK_CLOEXEC'):
733-
self.assertIn(s.socket.type,
734-
(sock_type | socket.SOCK_CLOEXEC, sock_type))
735-
else:
736-
self.assertEqual(s.socket.type, sock_type)
731+
self.assertEqual(s.socket.gettimeout(), 0)
732+
self.assertFalse(s.socket.get_inheritable())
737733

738734
def test_bind(self):
739735
if HAS_UNIX_SOCKETS and self.family == socket.AF_UNIX:

Lib/test/test_socket.py

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1577,6 +1577,22 @@ def test_str_for_enums(self):
15771577
self.assertEqual(str(s.family), 'AddressFamily.AF_INET')
15781578
self.assertEqual(str(s.type), 'SocketKind.SOCK_STREAM')
15791579

1580+
def test_socket_consistent_sock_type(self):
1581+
SOCK_NONBLOCK = getattr(socket, 'SOCK_NONBLOCK', 0)
1582+
SOCK_CLOEXEC = getattr(socket, 'SOCK_CLOEXEC', 0)
1583+
sock_type = socket.SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC
1584+
1585+
with socket.socket(socket.AF_INET, sock_type) as s:
1586+
self.assertEqual(s.type, socket.SOCK_STREAM)
1587+
s.settimeout(1)
1588+
self.assertEqual(s.type, socket.SOCK_STREAM)
1589+
s.settimeout(0)
1590+
self.assertEqual(s.type, socket.SOCK_STREAM)
1591+
s.setblocking(True)
1592+
self.assertEqual(s.type, socket.SOCK_STREAM)
1593+
s.setblocking(False)
1594+
self.assertEqual(s.type, socket.SOCK_STREAM)
1595+
15801596
@unittest.skipIf(os.name == 'nt', 'Will not work on Windows')
15811597
def test_uknown_socket_family_repr(self):
15821598
# Test that when created with a family that's not one of the known
@@ -1589,9 +1605,18 @@ def test_uknown_socket_family_repr(self):
15891605
# On Windows this trick won't work, so the test is skipped.
15901606
fd, path = tempfile.mkstemp()
15911607
self.addCleanup(os.unlink, path)
1592-
with socket.socket(family=42424, type=13331, fileno=fd) as s:
1593-
self.assertEqual(s.family, 42424)
1594-
self.assertEqual(s.type, 13331)
1608+
unknown_family = max(socket.AddressFamily.__members__.values()) + 1
1609+
1610+
unknown_type = max(
1611+
kind
1612+
for name, kind in socket.SocketKind.__members__.items()
1613+
if name not in {'SOCK_NONBLOCK', 'SOCK_CLOEXEC'}
1614+
) + 1
1615+
1616+
with socket.socket(
1617+
family=unknown_family, type=unknown_type, fileno=fd) as s:
1618+
self.assertEqual(s.family, unknown_family)
1619+
self.assertEqual(s.type, unknown_type)
15951620

15961621
@unittest.skipUnless(hasattr(os, 'sendfile'), 'test needs os.sendfile()')
15971622
def test__sendfile_use_sendfile(self):
@@ -5084,7 +5109,7 @@ class InheritanceTest(unittest.TestCase):
50845109
def test_SOCK_CLOEXEC(self):
50855110
with socket.socket(socket.AF_INET,
50865111
socket.SOCK_STREAM | socket.SOCK_CLOEXEC) as s:
5087-
self.assertTrue(s.type & socket.SOCK_CLOEXEC)
5112+
self.assertEqual(s.type, socket.SOCK_STREAM)
50885113
self.assertFalse(s.get_inheritable())
50895114

50905115
def test_default_inheritable(self):
@@ -5149,11 +5174,15 @@ def test_socketpair(self):
51495174
class NonblockConstantTest(unittest.TestCase):
51505175
def checkNonblock(self, s, nonblock=True, timeout=0.0):
51515176
if nonblock:
5152-
self.assertTrue(s.type & socket.SOCK_NONBLOCK)
5177+
self.assertEqual(s.type, socket.SOCK_STREAM)
51535178
self.assertEqual(s.gettimeout(), timeout)
5179+
self.assertTrue(
5180+
fcntl.fcntl(s, fcntl.F_GETFL, os.O_NONBLOCK) & os.O_NONBLOCK)
51545181
else:
5155-
self.assertFalse(s.type & socket.SOCK_NONBLOCK)
5182+
self.assertEqual(s.type, socket.SOCK_STREAM)
51565183
self.assertEqual(s.gettimeout(), None)
5184+
self.assertFalse(
5185+
fcntl.fcntl(s, fcntl.F_GETFL, os.O_NONBLOCK) & os.O_NONBLOCK)
51575186

51585187
@support.requires_linux_version(2, 6, 28)
51595188
def test_SOCK_NONBLOCK(self):
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix socket.settimeout() and socket.setblocking() to keep socket.type
2+
as is. Fix socket.socket() constructor to reset any bit flags applied to
3+
socket's type. This change only affects OSes that have SOCK_NONBLOCK
4+
and/or SOCK_CLOEXEC.

Modules/socketmodule.c

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -582,12 +582,6 @@ internal_setblocking(PySocketSockObject *s, int block)
582582
&& !((defined(HAVE_SYS_IOCTL_H) && defined(FIONBIO)))
583583
int delay_flag, new_delay_flag;
584584
#endif
585-
#ifdef SOCK_NONBLOCK
586-
if (block)
587-
s->sock_type &= (~SOCK_NONBLOCK);
588-
else
589-
s->sock_type |= SOCK_NONBLOCK;
590-
#endif
591585

592586
Py_BEGIN_ALLOW_THREADS
593587
#ifndef MS_WINDOWS
@@ -876,7 +870,22 @@ init_sockobject(PySocketSockObject *s,
876870
{
877871
s->sock_fd = fd;
878872
s->sock_family = family;
873+
879874
s->sock_type = type;
875+
876+
/* It's possible to pass SOCK_NONBLOCK and SOCK_CLOEXEC bit flags
877+
on some OSes as part of socket.type. We want to reset them here,
878+
to make socket.type be set to the same value on all platforms.
879+
Otherwise, simple code like 'if sock.type == SOCK_STREAM' is
880+
not portable.
881+
*/
882+
#ifdef SOCK_NONBLOCK
883+
s->sock_type = s->sock_type & ~SOCK_NONBLOCK;
884+
#endif
885+
#ifdef SOCK_CLOEXEC
886+
s->sock_type = s->sock_type & ~SOCK_CLOEXEC;
887+
#endif
888+
880889
s->sock_proto = proto;
881890

882891
s->errorhandler = &set_error;

0 commit comments

Comments
 (0)