Skip to content

bpo-27456: Ensure TCP_NODELAY is set on linux #4231

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 2 commits into from
Dec 16, 2017
Merged

Conversation

1st1
Copy link
Member

@1st1 1st1 commented Nov 2, 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 copy/paste my unit test into your PR: https://github.com/python/cpython/pull/4233/files

You may also copy my NEWS entry. IMHO it's worth it to document this bugfix.

@@ -0,0 +1 @@
Ensure TCP_NODELAY is set on Linux
Copy link
Member

@Mariatta Mariatta Nov 2, 2017

Choose a reason for hiding this comment

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

Please end the sentence with a period. :)

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

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 didn't check that you replaced all sock with sock.type, but tests pass so it should be fine :-)

LGTM.

@vstinner
Copy link
Member

vstinner commented Nov 2, 2017

Oops, no, Travis CI failed.

@asvetlov
Copy link
Contributor

asvetlov commented Nov 3, 2017

I don't figure out why the failed test shouldn't go though fastpath

@@ -107,7 +107,7 @@ def _ipaddr_info(host, port, family, type, proto):
host is None:
return None

if type == socket.SOCK_STREAM:
if _is_stream_socket(type):
Copy link
Member

Choose a reason for hiding this comment

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

This change is ok. But please fix also "type == socket.SOCK_DGRAM" below, fix test_ipaddr_info(), and please also write a test for non-blocking DGRAM socket.

Copy link
Member

Choose a reason for hiding this comment

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

Ping @1st1

@bedevere-bot
Copy link

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

@1st1
Copy link
Member Author

1st1 commented Nov 6, 2017

I'll work on this later today.

# Linux's socket.type is a bitmask that can include extra info
# about socket, therefore we can't do simple
# `sock_type == socket.SOCK_STREAM`.
return (sock.type & socket.SOCK_STREAM) == socket.SOCK_STREAM
return (sock_type & socket.SOCK_STREAM) == socket.SOCK_STREAM

Choose a reason for hiding this comment

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

socket.SOCK_STREAM is not a bit, it's a value, but socket.SOCK_NONBLOCK is a bit. That's why it is weird and complicated. Values and socket creation flags are mixed. For example, this check will fail for sock_type == socket.SOCK_SEQPACKET:

>>> (socket.SOCK_SEQPACKET & socket.SOCK_STREAM) == socket.SOCK_STREAM
True

All possible values on Linux:

>>> list(socket.SocketKind)
[<SocketKind.SOCK_STREAM: 1>,
 <SocketKind.SOCK_DGRAM: 2>,
 <SocketKind.SOCK_RAW: 3>,
 <SocketKind.SOCK_RDM: 4>,
 <SocketKind.SOCK_SEQPACKET: 5>,
 <SocketKind.SOCK_NONBLOCK: 2048>,
 <SocketKind.SOCK_CLOEXEC: 524288>]

@@ -42,7 +42,7 @@ def _test_selector_event(selector, fd, event):
if hasattr(socket, 'TCP_NODELAY'):
def _set_nodelay(sock):
if (sock.family in {socket.AF_INET, socket.AF_INET6} and
sock.type == socket.SOCK_STREAM and
base_events._is_stream_socket(sock.type) and
Copy link
Member

Choose a reason for hiding this comment

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

At this point, how about switching to EAFP (Easier to Ask Forgiveness than Permission)?

try:
    sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
except OSError:
    pass

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, that's what I'm planning to do here. There appears to be no reliable and future-proof way of checking socket type on Linux, so EAFP is the best option. The "not so good" option is to do a dance like this:

if sock.type & ~(SOCK_NONBLOCK | SOCK_CLOEXEC) == SOCK_STREAM:

Copy link
Member

Choose a reason for hiding this comment

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

I prefer to filter flags rather than always calling setsockopt() but ignore errors. For best performance, I prefer to avoid useless syscalls.

sock.type & ~(SOCK_NONBLOCK | SOCK_CLOEXEC)

Maybe we should add an attribute to socket.socket which returns the type without SOCK_NONBLOCK and SOCK_CLOEXEC bits.

Choose a reason for hiding this comment

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

Another option is to add SOCK_TYPE_MASK, maybe even as socket.SOCK_TYPE_MASK, as Linux does (https://github.com/torvalds/linux/blob/v4.13/include/linux/net.h#L77).

I think that

sock.type & ~(SOCK_NONBLOCK | SOCK_CLOEXEC)

is not as future-proof as

sock.type & SOCK_TYPE_MASK

Shouldn't BSD/Linux devs support backward-compatibility/conventions after introducing SOCK_CLOEXEC and SOCK_NONBLOCK flags? Maybe using SOCK_TYPE_MASK will be safe enough?

Copy link
Member Author

Choose a reason for hiding this comment

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

I like SOCK_TYPE_MASK idea. At least we can do this on Linux.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll do some experiments/more digging and update this PR.

Copy link
Member

Choose a reason for hiding this comment

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

For best performance, I prefer to avoid useless syscalls.

At the cost of maintainability, for a syscall that's called once in a socket's lifetime...

Copy link
Member

Choose a reason for hiding this comment

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

About being "future-proof": SOCK_NONBLOCK and SOCK_CLOEXEC flags are now old and well known. But I agree that it's annoying to have to care of them :-(

Copy link
Member

Choose a reason for hiding this comment

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

>>> def f(sock):
...:    try:
...:        sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
...:    except OSError:
...:        pass
...:    
>>> sock = socket.socket(socket.AF_INET)
>>> %timeit f(sock)
500 ns ± 18.3 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
>>> sock = socket.socket(socket.AF_UNIX)
>>> %timeit f(sock)
1.66 µs ± 6.82 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

@vstinner
Copy link
Member

While the idea of a new socket.socket attribute getting type without flags and SOCK_TYPE_MASK idea are nice, I suggest to keep this PR simple as the initially proposed fix, and open a new issue on bugs.python.org to propose to enhance the socket module.

@asvetlov
Copy link
Contributor

asvetlov commented Dec 13, 2017

ping

@1st1
Copy link
Member Author

1st1 commented Dec 14, 2017

I have made the requested changes; please review again

@bedevere-bot
Copy link

Thanks for making the requested changes!

@vstinner: please review the changes made to this pull request.

@1st1
Copy link
Member Author

1st1 commented Dec 14, 2017

@vstinner I think this should now be ready to be merged.

# `sock_type == socket.SOCK_STREAM`, see
# https://github.com/torvalds/linux/blob/v4.13/include/linux/net.h#L77
# for more details.
return (sock_type & 0xF) == socket.SOCK_STREAM
Copy link
Member

Choose a reason for hiding this comment

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

Would it be possible to add a private constant to the socket module, rather rhan using a private constant? If available, even use the Linux constant.

Copy link
Member

Choose a reason for hiding this comment

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

This constant can be ~0 if SOCK_NONBLOCK is missing to prevent the if.

Copy link
Member Author

Choose a reason for hiding this comment

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

Would it be possible to add a private constant to the socket module, rather rhan using a private constant?

Maybe, but I'd like to do that in a separate PR to the socket module first. I'd like to merge this PR as is.

Copy link
Member Author

Choose a reason for hiding this comment

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

IOW -- in my opinion this PR should be merged as is and backported to 3.6. I'll work on a new PR to expose SOCK_TYPE_MASK in socket and we'll update asyncio to use that.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm ok with merging, backporting to 3.6 and polishing 3.7 at the end.

@bedevere-bot
Copy link

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

@1st1
Copy link
Member Author

1st1 commented Dec 16, 2017

A more generic fix is in progress: https://bugs.python.org/issue32331

Yes, I'll merge this in now, and when that issue is resolved, I'll update the code for 3.7.

@1st1 1st1 merged commit e796b2f into python:master Dec 16, 2017
@miss-islington
Copy link
Contributor

Thanks @1st1 for the PR 🌮🎉.. I'm working now to backport this PR to: 3.6.
🐍🍒⛏🤖

@miss-islington
Copy link
Contributor

Sorry, @1st1, I could not cleanly backport this to 3.6 due to a conflict.
Please backport using cherry_picker on command line.
cherry_picker e796b2fe26f220107ac50667de6cc86c82b465e3 3.6

1st1 added a commit to 1st1/cpython that referenced this pull request Dec 16, 2017
@1st1
Copy link
Member Author

1st1 commented Dec 16, 2017

Backport PR: #4898

1st1 added a commit that referenced this pull request Dec 16, 2017
@bedevere-bot
Copy link

GH-4898 is a backport of this pull request to the 3.6 branch.

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.

9 participants