-
-
Notifications
You must be signed in to change notification settings - Fork 6.3k
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
Fix for HTTP/3: Windows build sends lots of empty UDP datagrams #9203
Conversation
@tatsuhiro-t hey, can you please test drive this workaround for me and compare the performance before and after this PR? |
I saw almost no difference in between. I also saw no difference when I tested HTTP/1.1 upload with 0 bytes send() commented out, so I might not have a good environment to test this thing. |
The test upload should be at least something around 100 MB to notice a difference. |
I actually used 100MiB file. |
Does this calling of getsockopt() on all the sockets risk slowing things down? It struck me that as we already have a "socket hash table" with all the sockets in use within the multi handle, it could easily be extended to have the socket type in there as well. It could then make it a hash table lookup instead, if this getsockopt() approach turns out unfavorable. |
I would think so, yes. But unfortunately, it is currently quite hard to test with the state of uploads using HTTP/3.
Good idea! Would you mind taking a look into this or pointing me into the right direction? Where would the hash table value be initialized and stored? |
The function |
becef60
to
7ef77d2
Compare
@bagder I attempted to implement the caching, but with the code committed in 7ef77d2 including debug fprintf statements, I always get Also I observed that For TFTP and pre-HTTP/3 uploads, I get |
@bagder @tatsuhiro-t I could really use your help and insight on this topic. Thanks in advance! |
7ef77d2
to
edf8ef9
Compare
Force-pushed with a new commit adding the socket to the hash table if it wasn't there yet: Lines 1170 to 1171 in edf8ef9
Let's see what CI will tell us if this is the right approach... |
CI failures are unrelated to the socket hash table. But @bagder , I still have doubts about adding sockets from extra_fds to that hash table belonging to a multi handle. Since we are not managing the extra_fds within libcurl, but the calling application does, those socket handles/numbers can change over time and eventually get re-used by a connection of a different type. So we have no way to invalidate our cache for these sockets and may never notice the changed socket's connection type. This means we probably should only cache the socket's connection type for our sockets, right? |
@tatsuhiro-t although this still contains debug code, can you review the impact of this PR again please? |
It works for me. No empty packets were observed. |
@tatsuhiro-t thanks a lot! @bagder does the logic in this PR make sense to you now? |
I am still not sure if using |
I share your concerns. I would change the extra fd part to call getsockopt directly. It is not ideal. I walked through it and there's a number of interlocked calls and a critical section lock to retrieve the socket information, but that may be true of send as well. You could use tvnow (which calls QueryPerformanceCounter) then make a bunch of sockets and call multi_wait 1000x to compare. The regular fd part if I recall the sockets should already have entries, and if they don't I can't readily tell if it's ok to create one without setting the rest of the entry as would be done in singlesocket. So, perhaps getsockopt should be added in singlesocket here and then multi_getsocktype return the type but not set it lazily |
In all my tests the entry wasn't existing for the socket in the regular fd part at the time multi_getsocktype was called. So I guess then I just probably just stay with always calling getsockopt without a caching layer. |
Interesting. That's not what I expected. In that case I'd wait for @bagder's input on this. |
The socket gets added to the sockhash on: Line 2879 in aa970c4
I have not analyzed why the sockets you check are not present in that hash. It seems curious to me. |
On Windows the internal state of FD_WRITE as returned from WSAEnumNetworkEvents is only reset after successful send(). Since multi_wait can be called multiple times without such a send() happening in between calls, we need to call send() ourselves to make sure we still get a fresh FD_WRITE signal. For TCP connections an empty send() does not actually send something on the wire, but for UDP an empty message packet is actually send on the wire, leading to hundreds of such. This commit makes the send()-workaround apply only to TCP.
Suggested-by: Jay Satiro
55c904b
to
a53f3a9
Compare
Thanks to both of you. I tried to incorporate your feedback, @jay. |
entry->socketype = st; | ||
return st; | ||
} | ||
#endif |
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 function could use a comment that it is only to be called on libcurl sockets and not the extra sockets, and also explain why it defaults to SOCK_STREAM when no entry is available or the socket type cannot be obtained. Conversely the second check sets the type to 0 when it cannot be obtained
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 this function can be called for extra sockets as they may overlap with libcurl sockets. We cannot assume that they are fully distinct or can we?
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 encourage you to test your caching method against getsockopt on 1000 sockets so we can see if it has any performance gain and is worth pursuing. For example
if(getsockopt(s, SOL_SOCKET, SO_TYPE, (char *)&st, &sl) != SOCKET_ERROR &&
st == SOCK_STREAM) {
send(s, NULL, 0, 0); /* reset FD_WRITE */
}
I would propose to beef up the Since this is not done in a day, I think we could merge this PR and then do the refactoring afterwards, eliminating |
Unfortunately, curl does not open the sockets in extra_fds. But yes, I hope to get to this again over the holidays period. Currently to busy at work, sorry. |
- Limit the 0-sized send procedure that is used to reset a SOCKET's FD_WRITE to TCP sockets only. Prior to this change the reset was used on UDP sockets as well, but unlike TCP sockets a 0-sized send actually sends out out a datagram. Assisted-by: Marc Hörsken Ref: curl#9203 Fixes curl#9086 Closes #xxxx
See #10430 for my take on this, which is calling getsockopt directly without caching, and then reset FD_WRITE only if TCP. It's unclear to me if extra user fd's that could possibly? be neither TCP nor UDP would need to reset FD_WRITE. |
- Limit the 0-sized send procedure that is used to reset a SOCKET's FD_WRITE to TCP sockets only. Prior to this change the reset was used on UDP sockets as well, but unlike TCP sockets a 0-sized send actually sends out out a datagram. Assisted-by: Marc Hörsken Ref: curl#9203 Fixes curl#9086 Closes #xxxx
- Limit the 0-sized send procedure that is used to reset a SOCKET's FD_WRITE to TCP sockets only. Prior to this change the reset was used on UDP sockets as well, but unlike TCP sockets a 0-sized send actually sends out a datagram. Assisted-by: Marc Hörsken Ref: curl#9203 Fixes curl#9086 Closes curl#10430
Attempt to #9086 by: fix sending empty UDP packets to reset FD_WRITE
On Windows the internal state of FD_WRITE as returned from
WSAEnumNetworkEvents is only reset after successful send().
Since multi_wait can be called multiple times without such
a send() happening in between calls, we need to call send()
ourselves to make sure we still get a fresh FD_WRITE signal.
For TCP connections an empty send() does not actually send
something on the wire, but for UDP an empty message packet
is actually send on the wire, leading to hundreds of such.
This commit makes the send()-workaround apply only to TCP.