-
-
Notifications
You must be signed in to change notification settings - Fork 32.3k
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
Changes from all commits
e411fc1
c737e29
3e65a90
8487f58
3a1898d
240af33
4fac6d3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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() | ||
|
@@ -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)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
(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() | ||
|
@@ -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 | ||
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think there's any point in explaining this inside There was a problem hiding this comment. Choose a reason for hiding this commentThe 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): | ||
|
@@ -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) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Add socket.getblocking() method. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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\ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Very minor thing: moving |
||
is in non-blocking mode."); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You may add "This is equivalent to checking |
||
|
||
static int | ||
socket_parse_timeout(_PyTime_t *timeout, PyObject *timeout_obj) | ||
{ | ||
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I'm sure that comment can be made much shorter :-) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you write your version? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, that's too short :) But I'll trim down my version. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
*/ | ||
|
||
if (internal_setblocking(s, block) == -1) { | ||
return NULL; | ||
} | ||
Py_RETURN_NONE; | ||
|
@@ -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, | ||
|
There was a problem hiding this comment.
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."
There was a problem hiding this comment.
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 forsetblocking
explains that it's equivalent to a certainsettimeout
call.