Skip to content
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

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

Merged
merged 7 commits into from
Jan 28, 2018
Merged

Conversation

1st1
Copy link
Member

@1st1 1st1 commented Dec 19, 2017

@1st1 1st1 requested review from vstinner and asvetlov December 19, 2017 14:24
@1st1 1st1 changed the title Add socket.getblocking() method. bpo-32373: Add socket.getblocking() method. Dec 19, 2017
Copy link
Member

@vstinner vstinner left a comment

Choose a reason for hiding this comment

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

Please add self.assertFalse(self.serv.getblocking()) in testInitNonBlocking(), or write a new test to ensure that socket created with SOCK_NONBLOCK is non-blocking: getblocking() is false.


Return ``True`` if socket is in blocking mode, ``False`` if in
non-blocking. This reflects the last call to :meth:`setblocking`
or :meth:`settimeout`.
Copy link
Member

Choose a reason for hiding this comment

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

"This reflects the last call to :meth:setblocking or :meth:settimeout."

What abou the following case?

>>> import socket; socket.socket(type=socket.SOCK_STREAM | socket.SOCK_NONBLOCK).getblocking()
False

I suggest to remove the second sentence.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure

@1st1
Copy link
Member Author

1st1 commented Dec 19, 2017

Please add self.assertFalse(self.serv.getblocking()) in testInitNonBlocking(), or write a new test to ensure that socket created with SOCK_NONBLOCK is non-blocking: getblocking() is false.

There're extensive tests for that, see the updated NonblockConstantTest. But I've added it to testInitNonBlocking just in case.

vstinner
vstinner previously approved these changes Dec 19, 2017
Copy link
Member

@vstinner vstinner left a comment

Choose a reason for hiding this comment

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

LGTM. You may document the new change in Doc/whatsnew/3.7.rst.

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.

@@ -2583,7 +2605,7 @@ sock_settimeout(PySocketSockObject *s, PyObject *arg)
return NULL;

s->sock_timeout = timeout;
if (internal_setblocking(s, timeout < 0) == -1) {
if (internal_setblocking(s, timeout != 0) == -1) {
Copy link
Member

Choose a reason for hiding this comment

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

Hum, wait, what is this change? Timeouts > 0 now set the socket is blocking mode?

Would you mind to document this subtle behaviour change in settimeout() documentation?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. I'm still working on it. Turns out sendfile currently tests test sendfile on non-blocking sockets, but at the same time the documentation says that it's not supported for non-blocking sockets at all. It's a mess.

@1st1 1st1 force-pushed the getblocking branch 3 times, most recently from 3b8074e to 5f1cf87 Compare December 19, 2017 23:14
@1st1
Copy link
Member Author

1st1 commented Dec 19, 2017

@vstinner Everything is back to normal, the patch is simple again. Please read the documentation update in adcc91f.

# 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.

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.

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.
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 wary of documenting internals here. It could be quite confusing to the reader, and doesn't seem to provide any useful information.

Copy link
Member Author

@1st1 1st1 Dec 19, 2017

Choose a reason for hiding this comment

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

Yeah, but my confusion with this thing came from me trying to check if the FD is blocking/nonblocking with fcntl.fcntl(sock, fcntl.F_GETFL, os.O_NONBLOCK) & os.O_NONBLOCK, and from there it's was quite a painful experience to discover how socket objects really work in Python (probably because I was debugging a failing sendfile test after some changes, but whatever).

I agree that all this information might overwhelm an average Python user who just wants to read the documentation. On the other hand, socket module is usually used by more or less advanced Python users.

That said, I'd be OK to keep this detailed documentation piece in socketmodule.c if everyone thinks it's too much to have it here.

Copy link
Member

Choose a reason for hiding this comment

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

I think it would be best located in socketmodule.c indeed. Not sure where.

Copy link
Member Author

Choose a reason for hiding this comment

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

Let's keep it in sock_settimeout(), as it's the central place that triggers blocking/non-blocking modes.

@vstinner vstinner dismissed their stale review December 19, 2017 23:51

I remove my vote since Yury rewrote his PR after I approved it

Copy link
Member

@vstinner vstinner left a comment

Choose a reason for hiding this comment

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

I don't think that getblocking() doc is the right place to explain in depth the behaviour of all timeouts and blocking/non-blocking modes.

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

Maybe the long description of blocking/non-blocking should even be a separated PR?

@bedevere-bot
Copy link

When you're done making the requested changes, leave the comment: I have made the requested changes; please review again.

@@ -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.

@1st1
Copy link
Member Author

1st1 commented Dec 20, 2017

I remove my vote since Yury rewrote his PR after I approved it

The PR is in the exact same shape as it was the moment you approved it + few comments. I've pushed a few commits experimenting with different approaches, and later removed those experimental commits.

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

That's exactly what is in the PR.

@@ -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.

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.

.. 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.

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 :-))

==================== ===================== ==============
``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.

@1st1
Copy link
Member Author

1st1 commented Jan 28, 2018

@vstinner Do you have any objections here? I'd like to merge this to 3.7 and I don't see any open questions regarding this PR. Reorganizing tests to make them prettier is fine, but we can surely do it later.

Copy link
Member

@vstinner vstinner left a comment

Choose a reason for hiding this comment

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

LGTM.

If I recall correctly, I voted -1 on a previous because setblocking() was modified. It's not more the case. Moreover, the new doc is also better now.

"getblocking()\n\
\n\
Returns True if socket is in blocking mode, or False if it\n\
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.

@1st1 1st1 merged commit f11b460 into python:master Jan 28, 2018
@bedevere-bot
Copy link

@1st1: Please replace # with GH- in the commit message next time. Thanks!

@1st1
Copy link
Member Author

1st1 commented Jan 28, 2018

Thanks, Victor!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants