From e411fc1bdf37e801e7f9090cb893c37e136e868d Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Tue, 19 Dec 2017 09:23:53 -0500 Subject: [PATCH 1/7] bpo-32373: Add socket.getblocking() method. --- Doc/library/socket.rst | 9 +++++++ Lib/test/test_socket.py | 4 ++++ .../2017-12-19-09-23-46.bpo-32373.8qAkoW.rst | 1 + Modules/socketmodule.c | 24 +++++++++++++++++++ 4 files changed, 38 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2017-12-19-09-23-46.bpo-32373.8qAkoW.rst diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index 777710f16f264a..c16802dc95ece6 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -1098,6 +1098,15 @@ to sockets. to decode C structures encoded as byte strings). +.. method:: socket.getblocking() + + Return ``True`` if socket is in blocking mode, ``False`` if in + non-blocking. This reflects the last call to :meth:`setblocking` + or :meth:`settimeout`. + + .. versionadded:: 3.7 + + .. method:: socket.gettimeout() Return the timeout in seconds (float) associated with socket operations, diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index a971f921546f9c..54041ad3d764d1 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -4123,8 +4123,10 @@ def testSetBlocking(self): # Testing whether set blocking works self.serv.setblocking(True) self.assertIsNone(self.serv.gettimeout()) + self.assertTrue(self.serv.getblocking()) self.serv.setblocking(False) self.assertEqual(self.serv.gettimeout(), 0.0) + self.assertFalse(self.serv.getblocking()) start = time.time() try: self.serv.accept() @@ -5234,11 +5236,13 @@ def checkNonblock(self, s, nonblock=True, timeout=0.0): self.assertEqual(s.gettimeout(), timeout) self.assertTrue( fcntl.fcntl(s, fcntl.F_GETFL, os.O_NONBLOCK) & os.O_NONBLOCK) + self.assertFalse(s.getblocking()) else: self.assertEqual(s.type, socket.SOCK_STREAM) self.assertEqual(s.gettimeout(), None) self.assertFalse( fcntl.fcntl(s, fcntl.F_GETFL, os.O_NONBLOCK) & os.O_NONBLOCK) + self.assertTrue(s.getblocking()) @support.requires_linux_version(2, 6, 28) def test_SOCK_NONBLOCK(self): diff --git a/Misc/NEWS.d/next/Library/2017-12-19-09-23-46.bpo-32373.8qAkoW.rst b/Misc/NEWS.d/next/Library/2017-12-19-09-23-46.bpo-32373.8qAkoW.rst new file mode 100644 index 00000000000000..9772dda35a3013 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2017-12-19-09-23-46.bpo-32373.8qAkoW.rst @@ -0,0 +1 @@ +Add socket.getblocking() method. diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 30afe81d63525e..896da245524192 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -136,6 +136,7 @@ sendall(data[, flags]) -- send all data\n\ send(data[, flags]) -- send data, may not send all of it\n\ sendto(data[, flags], addr) -- send data to a given address\n\ setblocking(0 | 1) -- set or clear the blocking I/O flag\n\ +getblocking() -- return True if socket is blocking, False if non-blocking\n\ setsockopt(level, optname, value[, optlen]) -- set socket options\n\ settimeout(None | float) -- set or clear the timeout\n\ shutdown(how) -- shut down traffic in one or both directions\n\ @@ -2531,6 +2532,27 @@ Set the socket to blocking (flag is true) or non-blocking (false).\n\ setblocking(True) is equivalent to settimeout(None);\n\ setblocking(False) is equivalent to settimeout(0.0)."); +/* s.getblocking() method. + Returns True if socket is in blocking mode, + False if it is in non-blocking mode. +*/ +static PyObject * +sock_getblocking(PySocketSockObject *s) +{ + if (s->sock_timeout) { + Py_RETURN_TRUE; + } + else { + Py_RETURN_FALSE; + } +} + +PyDoc_STRVAR(getblocking_doc, +"getblocking()\n\ +\n\ +Returns True if socket is in blocking mode, or False if it\n\ +is in non-blocking mode."); + static int socket_parse_timeout(_PyTime_t *timeout, PyObject *timeout_obj) { @@ -4607,6 +4629,8 @@ static PyMethodDef sock_methods[] = { sendto_doc}, {"setblocking", (PyCFunction)sock_setblocking, METH_O, setblocking_doc}, + {"getblocking", (PyCFunction)sock_getblocking, METH_NOARGS, + getblocking_doc}, {"settimeout", (PyCFunction)sock_settimeout, METH_O, settimeout_doc}, {"gettimeout", (PyCFunction)sock_gettimeout, METH_NOARGS, From c737e293d43a8d3ef42436a6e02ba080948ad40a Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Tue, 19 Dec 2017 09:44:13 -0500 Subject: [PATCH 2/7] Address review. --- Doc/library/socket.rst | 3 +-- Lib/test/test_socket.py | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index c16802dc95ece6..8ec0fe572db9da 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -1101,8 +1101,7 @@ to sockets. .. method:: socket.getblocking() Return ``True`` if socket is in blocking mode, ``False`` if in - non-blocking. This reflects the last call to :meth:`setblocking` - or :meth:`settimeout`. + non-blocking. .. versionadded:: 3.7 diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 54041ad3d764d1..a0bc38427a09c1 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -4159,6 +4159,8 @@ def testInitNonBlocking(self): self.serv.close() self.serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM | socket.SOCK_NONBLOCK) + self.assertFalse(self.self.getblocking()) + self.assertEqual(self.self.gettimeout(), 0) self.port = support.bind_port(self.serv) self.serv.listen() # actual testing From 3e65a9057f232f25241562d08b473b16bc6b8398 Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Tue, 19 Dec 2017 10:21:17 -0500 Subject: [PATCH 3/7] Fix testInitNonBlocking test --- Lib/test/test_socket.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index a0bc38427a09c1..9c02eee5d82b48 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -4159,8 +4159,8 @@ def testInitNonBlocking(self): self.serv.close() self.serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM | socket.SOCK_NONBLOCK) - self.assertFalse(self.self.getblocking()) - self.assertEqual(self.self.gettimeout(), 0) + self.assertFalse(self.serv.getblocking()) + self.assertEqual(self.serv.gettimeout(), 0) self.port = support.bind_port(self.serv) self.serv.listen() # actual testing From 8487f580eb7b27e7d982c3ec62a93c2db367bc13 Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Tue, 19 Dec 2017 14:44:24 -0500 Subject: [PATCH 4/7] More setblocking/settimeout tests --- Lib/test/test_socket.py | 49 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 9c02eee5d82b48..7b2b3c5736b5db 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -98,6 +98,12 @@ def _have_socket_vsock(): ret = get_cid() is not None return ret + +def _is_fd_in_blocking_mode(sock): + return not bool( + fcntl.fcntl(sock, fcntl.F_GETFL, os.O_NONBLOCK) & os.O_NONBLOCK) + + HAVE_SOCKET_CAN = _have_socket_can() HAVE_SOCKET_CAN_ISOTP = _have_socket_can_isotp() @@ -4124,9 +4130,43 @@ def testSetBlocking(self): self.serv.setblocking(True) self.assertIsNone(self.serv.gettimeout()) self.assertTrue(self.serv.getblocking()) + if fcntl: + self.assertTrue(_is_fd_in_blocking_mode(self.serv)) + self.serv.setblocking(False) self.assertEqual(self.serv.gettimeout(), 0.0) self.assertFalse(self.serv.getblocking()) + if fcntl: + self.assertFalse(_is_fd_in_blocking_mode(self.serv)) + + self.serv.settimeout(None) + self.assertTrue(self.serv.getblocking()) + if fcntl: + self.assertTrue(_is_fd_in_blocking_mode(self.serv)) + + self.serv.settimeout(0) + self.assertFalse(self.serv.getblocking()) + self.assertEqual(self.serv.gettimeout(), 0) + if fcntl: + self.assertFalse(_is_fd_in_blocking_mode(self.serv)) + + self.serv.settimeout(10) + self.assertTrue(self.serv.getblocking()) + self.assertEqual(self.serv.gettimeout(), 10) + if fcntl: + # When a Python socket has a non-zero timeout, it's + # switched internally to a non-blocking mode. + # Later, sock.sendall(), sock.recv(), and other socket + # operations use a `select()` call and handle EWOULDBLOCK/EGAIN + # on all socket operations. That's how timeouts are + # enforced. + self.assertFalse(_is_fd_in_blocking_mode(self.serv)) + + self.serv.settimeout(0) + self.assertFalse(self.serv.getblocking()) + if fcntl: + self.assertFalse(_is_fd_in_blocking_mode(self.serv)) + start = time.time() try: self.serv.accept() @@ -5238,7 +5278,8 @@ def checkNonblock(self, s, nonblock=True, timeout=0.0): self.assertEqual(s.gettimeout(), timeout) self.assertTrue( fcntl.fcntl(s, fcntl.F_GETFL, os.O_NONBLOCK) & os.O_NONBLOCK) - self.assertFalse(s.getblocking()) + if timeout == 0: + self.assertFalse(s.getblocking()) else: self.assertEqual(s.type, socket.SOCK_STREAM) self.assertEqual(s.gettimeout(), None) @@ -5254,15 +5295,15 @@ def test_SOCK_NONBLOCK(self): socket.SOCK_STREAM | socket.SOCK_NONBLOCK) as s: self.checkNonblock(s) s.setblocking(1) - self.checkNonblock(s, False) + self.checkNonblock(s, nonblock=False) s.setblocking(0) self.checkNonblock(s) s.settimeout(None) - self.checkNonblock(s, False) + self.checkNonblock(s, nonblock=False) s.settimeout(2.0) self.checkNonblock(s, timeout=2.0) s.setblocking(1) - self.checkNonblock(s, False) + self.checkNonblock(s, nonblock=False) # defaulttimeout t = socket.getdefaulttimeout() socket.setdefaulttimeout(0.0) From 3a1898dbb347f4500757876133305dccf04ff4de Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Tue, 19 Dec 2017 18:37:48 -0500 Subject: [PATCH 5/7] Add docs/comments --- Doc/library/socket.rst | 33 +++++++++++++++++++++++++++++++++ Lib/test/test_socket.py | 10 ++++++++++ Modules/socketmodule.c | 37 ++++++++++++++++++++++++++++++++++++- 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index 8ec0fe572db9da..c8d2c8928eb14b 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -1103,6 +1103,39 @@ to sockets. Return ``True`` if socket is in blocking mode, ``False`` if in non-blocking. + Blocking mode for a Python socket object means that operations + like :meth:`recv` or :meth:`sendall` will block the execution of + the current thread until they are complete or aborted with a + :exc:`socket.timeout` or :exc:`socket.error` errors. + + Non-blocking mode means that socket operations will raise + a :exc:`BlockingIOError` if they cannot complete immediately. + + Internally, if a Python socket object has a positive timeout, its + underlying file descriptor (FD) is switched to a non-blocking mode. + Methods like :meth:`recv` for such sockets are implemented using a + :func:`select.select` call. + + If a Python socket has its timeout set to ``None``, the underlying + FD will be switched to a blocking mode. + + If a Python socket has ``0.0`` timeout, the underlying FD will + be in a non-blocking mode. + + When timeout is ``0.0`` or ``None``, methods like :meth:`recv` + are just thin wrappers over corresponding system calls. + + This table summarizes all states in which the socket object and + its underlying FD can be: + + ==================== ===================== ============== + :meth:`gettimeout` :meth:`getblocking` socket's FD + ==================== ===================== ============== + ``None`` ``True`` blocking + ``0.0`` ``False`` non-blocking + ``> 0`` ``True`` non-blocking + ==================== ===================== ============== + .. versionadded:: 3.7 diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 7b2b3c5736b5db..13b9b432f4e9a7 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -5279,7 +5279,17 @@ def checkNonblock(self, s, nonblock=True, timeout=0.0): self.assertTrue( fcntl.fcntl(s, fcntl.F_GETFL, os.O_NONBLOCK) & os.O_NONBLOCK) if timeout == 0: + # timeout == 0: means that getblocking() must be False. self.assertFalse(s.getblocking()) + else: + # If timeout > 0, the socket will be in a "blocking" mode + # from the standpoint of the Python API. For Python socket + # object, "blocking" means that operations like 'sock.recv()' + # will block. Internally, file descriptors for + # "blocking" Python sockets *with timeouts* are in a + # *non-blocking* mode, and 'sock.recv()' uses 'select()' + # and handles EWOULDBLOCK/EAGAIN to enforce the timeout. + self.assertTrue(s.getblocking()) else: self.assertEqual(s.type, socket.SOCK_STREAM) self.assertEqual(s.gettimeout(), None) diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 896da245524192..14e094edc00230 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -2609,7 +2609,42 @@ sock_settimeout(PySocketSockObject *s, PyObject *arg) return NULL; s->sock_timeout = timeout; - if (internal_setblocking(s, timeout < 0) == -1) { + + int block = timeout < 0; + /* Blocking mode for a Python socket object means that operations + like :meth:`recv` or :meth:`sendall` will block the execution of + the current thread until they are complete or aborted with a + :exc:`socket.timeout` or :exc:`socket.error` errors. + + Non-blocking mode means that socket operations will raise + a :exc:`BlockingIOError` if they cannot complete immediately. + + Internally, if a Python socket object has a positive timeout, its + underlying file descriptor (FD) is switched to a non-blocking mode. + Methods like :meth:`recv` for such sockets are implemented using a + :func:`select.select` call. + + If a Python socket has its timeout set to ``None``, the underlying + FD will be switched to a blocking mode. + + If a Python socket has ``0.0`` timeout, the underlying FD will + be in a non-blocking mode. + + When timeout is ``0.0`` or ``None``, methods like :meth:`recv` + are just thin wrappers over corresponding system calls. + + This table summarizes all states in which the socket object and + its underlying FD can be: + + ==================== ===================== ============== + :meth:`gettimeout` :meth:`getblocking` socket's FD + ==================== ===================== ============== + ``None`` ``True`` blocking + ``0.0`` ``False`` non-blocking + ``> 0`` ``True`` non-blocking + */ + + if (internal_setblocking(s, block) == -1) { return NULL; } Py_RETURN_NONE; From 240af3313c7ed5159aa84dd8723179f820d690e6 Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Tue, 19 Dec 2017 19:10:02 -0500 Subject: [PATCH 6/7] Trim down comments; remove low-level docs from socket.rst. --- Doc/library/socket.rst | 33 --------------------------------- Modules/socketmodule.c | 24 ++++++------------------ 2 files changed, 6 insertions(+), 51 deletions(-) diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index c8d2c8928eb14b..8ec0fe572db9da 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -1103,39 +1103,6 @@ to sockets. Return ``True`` if socket is in blocking mode, ``False`` if in non-blocking. - Blocking mode for a Python socket object means that operations - like :meth:`recv` or :meth:`sendall` will block the execution of - the current thread until they are complete or aborted with a - :exc:`socket.timeout` or :exc:`socket.error` errors. - - Non-blocking mode means that socket operations will raise - a :exc:`BlockingIOError` if they cannot complete immediately. - - Internally, if a Python socket object has a positive timeout, its - underlying file descriptor (FD) is switched to a non-blocking mode. - Methods like :meth:`recv` for such sockets are implemented using a - :func:`select.select` call. - - If a Python socket has its timeout set to ``None``, the underlying - FD will be switched to a blocking mode. - - If a Python socket has ``0.0`` timeout, the underlying FD will - be in a non-blocking mode. - - When timeout is ``0.0`` or ``None``, methods like :meth:`recv` - are just thin wrappers over corresponding system calls. - - This table summarizes all states in which the socket object and - its underlying FD can be: - - ==================== ===================== ============== - :meth:`gettimeout` :meth:`getblocking` socket's FD - ==================== ===================== ============== - ``None`` ``True`` blocking - ``0.0`` ``False`` non-blocking - ``> 0`` ``True`` non-blocking - ==================== ===================== ============== - .. versionadded:: 3.7 diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 14e094edc00230..127a2e6df93582 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -2614,30 +2614,18 @@ sock_settimeout(PySocketSockObject *s, PyObject *arg) /* Blocking mode for a Python socket object means that operations like :meth:`recv` or :meth:`sendall` will block the execution of the current thread until they are complete or aborted with a - :exc:`socket.timeout` or :exc:`socket.error` errors. + `socket.timeout` or `socket.error` errors. When timeout is `None`, + the underlying FD is in a blocking mode. When timeout is a positive + number, the FD is in a non-blocking mode, and socket ops are + implemented with a `select()` call. - Non-blocking mode means that socket operations will raise - a :exc:`BlockingIOError` if they cannot complete immediately. - - Internally, if a Python socket object has a positive timeout, its - underlying file descriptor (FD) is switched to a non-blocking mode. - Methods like :meth:`recv` for such sockets are implemented using a - :func:`select.select` call. - - If a Python socket has its timeout set to ``None``, the underlying - FD will be switched to a blocking mode. - - If a Python socket has ``0.0`` timeout, the underlying FD will - be in a non-blocking mode. - - When timeout is ``0.0`` or ``None``, methods like :meth:`recv` - are just thin wrappers over corresponding system calls. + When timeout is 0.0, the FD is in a non-blocking mode. This table summarizes all states in which the socket object and its underlying FD can be: ==================== ===================== ============== - :meth:`gettimeout` :meth:`getblocking` socket's FD + `gettimeout()` `getblocking()` FD ==================== ===================== ============== ``None`` ``True`` blocking ``0.0`` ``False`` non-blocking From 4fac6d39026647b0e384acda182cecde0f040bab Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Sun, 28 Jan 2018 15:13:35 -0500 Subject: [PATCH 7/7] Clarify docs --- Doc/library/socket.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index 8ec0fe572db9da..433ccbf9aa0671 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -1103,6 +1103,8 @@ to sockets. Return ``True`` if socket is in blocking mode, ``False`` if in non-blocking. + This is equivalent to checking ``socket.gettimeout() == 0``. + .. versionadded:: 3.7