Skip to content

bpo-32373: Add socket.getblocking() method. #4926

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jan 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Doc/library/socket.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1098,6 +1098,16 @@ 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Victor: "getblocking() should be a simple as: return True if the timeout is equal to zero, False otherwise."

Yury: "That's exactly what is in the PR."

Hum, this doc doesn't mention the timeout but refers to a "blocking mode" which is not well defined for timeout > 0.

The socket module mentions 3 modes: blocking, non-blocking and "timeout mode":
https://docs.python.org/dev/library/socket.html#notes-on-socket-timeouts

Replace "if socket is in blocking mode" with "if the timeout is equal to zero", or replace "is in blocking mode" with "is in blocking or timeout mode"?

By the way, there is already a note: "At the operating system level, sockets in timeout mode are internally set in non-blocking mode."

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the docstring here should say "This is equivalent to gettimeout() == 0", just like the docstring for setblocking explains that it's equivalent to a certain settimeout call.


This is equivalent to checking ``socket.gettimeout() == 0``.

.. versionadded:: 3.7


.. method:: socket.gettimeout()

Return the timeout in seconds (float) associated with socket operations,
Expand Down
63 changes: 60 additions & 3 deletions Lib/test/test_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -4123,8 +4129,44 @@ def testSetBlocking(self):
# Testing whether set blocking works
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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may write an helper function which makes sure that timeout, blocking and SOCK_NONBLOCK flag are consistent.

Something like:

def check_timeout(sock, timeout):
  assert sock.gettimeout() == timeout
  assert sock.getblocking() == (timeout == 0.0)
  assert _is_fd_in_blocking_mode(sock) == (timeout != 0.0)

(I'm not sure of these assertions :-))


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()
Expand Down Expand Up @@ -4157,6 +4199,8 @@ def testInitNonBlocking(self):
self.serv.close()
self.serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM |
socket.SOCK_NONBLOCK)
self.assertFalse(self.serv.getblocking())
self.assertEqual(self.serv.gettimeout(), 0)
self.port = support.bind_port(self.serv)
self.serv.listen()
# actual testing
Expand Down Expand Up @@ -5234,11 +5278,24 @@ 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)
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there's any point in explaining this inside test_socket.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this test it makes sense, because I use fcntl to check the blocking mode of the underlying FD. Usually comments don't hurt in tests.

# "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)
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):
Expand All @@ -5248,15 +5305,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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add socket.getblocking() method.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like you modified settimeout() behaviour. It must be documented here and in settimeout() doc.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wrong, settimeout() is (no longer) modified by this PR.

My comment was for a previous version of the PR. The PR evolved too quickly, I was lost, sorry.

49 changes: 48 additions & 1 deletion Modules/socketmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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\
Expand Down Expand Up @@ -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\
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very minor thing: moving if it to next line increases docstrict readability imho.

is in non-blocking mode.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may add "This is equivalent to checking socket.gettimeout() == 0." here as well.


static int
socket_parse_timeout(_PyTime_t *timeout, PyObject *timeout_obj)
{
Expand Down Expand Up @@ -2587,7 +2609,30 @@ 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I'm sure that comment can be made much shorter :-)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you write your version?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure:

/* When a socket socket has a non-infinite timeout,
 * its underlying fd is internally set to non-blocking.
 * This table summarizes [etc.]
 */

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, that's too short :) But I'll trim down my version.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed an update.

like :meth:`recv` or :meth:`sendall` will block the execution of
the current thread until they are complete or aborted with a
`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.

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:

==================== ===================== ==============
`gettimeout()` `getblocking()` FD
==================== ===================== ==============
``None`` ``True`` blocking
``0.0`` ``False`` non-blocking
``> 0`` ``True`` non-blocking
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is internal C docs, it might be helpful to add a column to the table for s->sock_timeout values – IIUC the rows would be < 0, 0.0, and > 0 respectively, but it took some grovelling through the source to figure that out.

*/

if (internal_setblocking(s, block) == -1) {
return NULL;
}
Py_RETURN_NONE;
Expand Down Expand Up @@ -4607,6 +4652,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,
Expand Down