Skip to content

gh-123476: Add support for existing TCP_QUICKACK socket setting to Windows #123478

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 36 commits into from
Sep 5, 2024

Conversation

nkinnan
Copy link
Contributor

@nkinnan nkinnan commented Aug 29, 2024

On Windows, sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK, 1) throws AttributeError: module 'socket' has no attribute 'TCP_QUICKACK'

TCP_QUICKACK already exists and has support on the Linux side, this change just adds support when running on Windows as well.

On Windows, sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK, 1)
throws AttributeError: module 'socket' has no attribute 'TCP_QUICKACK'

TCP_QUICKACK already exists and has support on the Linux side, this
change just adds support when running on Windows as well.
@ghost
Copy link

ghost commented Aug 29, 2024

All commit authors signed the Contributor License Agreement.
CLA signed

@bedevere-app
Copy link

bedevere-app bot commented Aug 29, 2024

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

Co-authored-by: Kirill Podoprigora <[email protected]>
@nkinnan
Copy link
Contributor Author

nkinnan commented Aug 29, 2024

Update: disregard this comment, I found a good way to add the unit test.

I had to remove the (new) unit tests for QUICKACK:

  • They passed locally on windows
  • They failed in the github CI workflow
  • I believe this is because on the github servers, QUICKACK is set to true by default as a system-wide setting, I have seen this on other Linux based servers. Therefore, when the test asserts that QUICKACK is false (before setting true and asserting that as well) it is already true causing test failure.
  • This PR is windows specific, the change was not even being exercised in the CI workflow.
  • The test cases for QUICKACK Linux support didn't exist in the first place when I added Windows support, so I'm not removing anything that was already there.
  • Behavior has been manually verified.

For all of those reasons I believe it is reasonable to omit the unit tests from the PR.

Copy link
Member

@picnixz picnixz left a comment

Choose a reason for hiding this comment

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

This PR is windows specific, the change was not even being exercised in the CI workflow.

Actually, we have build bots for that so we can test it. So tests are required. You can make them conditioned on some information.

I'm surprised that you need a special casing for TCP_QUICKACK by the way and an additional structure field. Can't you just use get/setsockopt as with other constants?

#ifdef MS_WINDOWS
if (optname == SIO_TCP_SET_ACK_FREQUENCY) {
int dummy;
res = WSAIoctl(s->sock_fd, SIO_TCP_SET_ACK_FREQUENCY, &flag,
Copy link
Member

@picnixz picnixz Aug 30, 2024

Choose a reason for hiding this comment

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

Should I understand that this constant cannot be used by setsockopt and that you need this special path?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, on windows platform, QUICKACK can only be set through WSAIoctl() and is not supported through setsockopt()

Copy link
Contributor Author

@nkinnan nkinnan Aug 30, 2024

Choose a reason for hiding this comment

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

Another quick note to help with review:

On Windows, the only values accepted for SIO_TCP_SET_ACK_FREQUENCY is 0 and 1, with 1 meaning "ack every 1 packets" aka TCP_QUICKACK is on, and 0 meaning "use default value" aka TCP_QUICKACK is off. So there's no need to translate the value set by the user. 1 is on, 0 is off, just like TCP_NODELAY and other socket flags. A convenient coincidence.

Copy link
Member

Choose a reason for hiding this comment

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

I see. I un-resolved this conversation so that future reviewers can see it (otherwise GH hides it).

Copy link
Contributor Author

@nkinnan nkinnan Sep 1, 2024

Choose a reason for hiding this comment

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

I'll throw another note in here for future reviewers then: there is no equivalent SIO_TCP_GET_ACK_FREQUENCY, so the value must be saved in the socket object. On Windows SIO_TCP_SET_ACK_FREQUENCY is always 0 for a new socket, and the socket->quickack variable is always initialized to 0, so the response value from socket.getsockopt for TCP_QUICKACK will always be correct on Windows even if not previously set.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

another explanation of behavior for future reviewers, please see this comment: #123478 (comment)

if statement brackets

Co-authored-by: Bénédikt Tran <[email protected]>
@nkinnan
Copy link
Contributor Author

nkinnan commented Aug 30, 2024

This PR is windows specific, the change was not even being exercised in the CI workflow.

Actually, we have build bots for that so we can test it. So tests are required. You can make them conditioned on some information.

edit: realized a better way, TC added,

I'll look for examples of how to do that in other tests so that I can re-add them. If you have any quick pointers that might help as I am unfamiliar with this codebase. I would note that QUICKACK isn't being tested at all right now, not even the existing support for Linux, but test cases are a good thing so I'll make the effort ;)

I'm surprised that you need a special casing for TCP_QUICKACK by the way and an additional structure field. Can't you just use get/setsockopt as with other constants?

Unfortunately not for this platform, please see the code review comment replies.

Copy link
Member

@zooba zooba left a comment

Choose a reason for hiding this comment

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

Change overall looks fine to me (apart from the apparent dead code), but I can only assume that this is the right way to enable TCP_QUICKACK. If someone more knowledgeable can confirm there are no unintended tricks or incompatibilities (that is, it does on Windows precisely what code written for POSIX will expect it to do), then it seems fine.

@nkinnan
Copy link
Contributor Author

nkinnan commented Sep 3, 2024

Change overall looks fine to me (apart from the apparent dead code),

edit: Realized what this meant after, will move that func.

but I can only assume that this is the right way to enable TCP_QUICKACK. If someone more knowledgeable can confirm there are no unintended tricks or incompatibilities (that is, it does on Windows precisely what code written for POSIX will expect it to do), then it seems fine.

Discussion between myself and picnixz on specific behavior: https://github.com/python/cpython/pull/123478/files#r1738786292

SIO_TCP_SET_ACK_FREQUENCY is not well documented on Windows but its behavior is confirmed, here is some discussion from folks at MS about adding this same support to the .NET runtime.

dotnet/runtime#798

triage: workaround exist through platform specific. Since there is no consistent cross-platform it seems like bad fit for general Socket API. We can revisit this in the future.

That "workaround exist" refers to this: dotnet/runtime#798 (comment)

Note that the value 12 passed for linux is the linux header defined TCP_QUICKACK value which was already implemented in cpython. Note that on Windows, IOControl is the .NET equivalent to WSAIoctl (what this PR calls).

And when they say "no consistent cross platform support" that might mean the lack of Mac support (fair enough) or the fact that the flag isn't persistent on Linux. Lack of Mac support: dotnet/runtime#798 (comment)

As to the lack of persistency on Linux, this flag was already exposed in cpython on Linux, my PR is only to add support for Windows which doesn't have that flaw. So that quirk of behavior on Linux is already there and "accounted for". It literally does the same thing, Linux just decides "maybe you changed your mind" and turs it off after every receive whereas windows respects your intent. Part of the reason they didn't add it to .NET is they didn't want to have to keep turning it back on when running on Linux if the user selected QUICKACK behavior. I suspect this "resetting flag" issue is also why many linux distros are forcing QUICKACK permanently on across the whole system nowadays so the flag doesn't even need to be set (over and over again) and because the alternative is unnecessary with modern networking systems and detrimental to latency sensitive applications.

@zooba
Copy link
Member

zooba commented Sep 3, 2024

Thanks, that .NET thread is convincing. Once you've moved out the asyncio changes, I'll merge.

formatting style oops

Co-authored-by: Steve Dower <[email protected]>
@vstinner
Copy link
Member

vstinner commented Sep 3, 2024

Maybe it makes sense to set TCP_QUICKACK flag by default on asyncio sockets, similar to what's done with TCP_NODELAY. But I would prefer to have a separated PR for that and have an asyncio expert to say if it makes sense or not.

@nkinnan
Copy link
Contributor Author

nkinnan commented Sep 3, 2024

Maybe it makes sense to set TCP_QUICKACK flag by default on asyncio sockets, similar to what's done with TCP_NODELAY. But I would prefer to have a separated PR for that and have an asyncio expert to say if it makes sense or not.

Agree on leaving it to an expert (my gut says yes) but if that ends up happening it may be worth looking into
net.inet.tcp.ack_strategy and net.inet.tcp.delayed_ack to add support for this flag on MacOS for consistency. I don't have access to an Apple platform to try it out though, and I believe those settings are system-wide on MacOS (I didn't investigate too deeply). Just a heads up.

@nkinnan
Copy link
Contributor Author

nkinnan commented Sep 3, 2024

Thanks, that .NET thread is convincing. Once you've moved out the asyncio changes, I'll merge.

Thank you. I believe I've moved them to the right place but am unfamiliar with cpython's test setup so please let me know if any further tweaks are required.

I want to quickly point out that I wasn't sure what version number to put into Doc/library/socket.rst so that may need changing before merge.

@zooba zooba added the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Sep 3, 2024
@bedevere-bot
Copy link

🤖 New build scheduled with the buildbot fleet by @zooba for commit 1bc9b7a 🤖

If you want to schedule another build, you need to add the 🔨 test-with-buildbots label again.

@bedevere-bot bedevere-bot removed the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Sep 3, 2024
@nkinnan
Copy link
Contributor Author

nkinnan commented Sep 4, 2024

There was an unrelated failure in the CI workflow, but pulling main seems to have fixed 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.

LGTM. I just have a remark/question on the test.

@vstinner vstinner merged commit b5aa271 into python:main Sep 5, 2024
37 checks passed
@vstinner
Copy link
Member

vstinner commented Sep 5, 2024

Merged, thanks for your contribution.

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.

6 participants