-
-
Notifications
You must be signed in to change notification settings - Fork 6.4k
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
HTTP/2.0 multiplexing: broken POST request when re-adding GET request #2520
Comments
@bagder is this a beginner friendly issue? I am exploring the codebase now, if it's a low complexity issue then would like to work on it. |
Thanks for considering this @geekodour, but while I haven't yet investigated this issue very closely I think I can say that this is not a simple issue. It involves multiple HTTP/2 streams and something not being right in the data handling in (one of) them. To give you a better picture, let me describe roughly how I imagine I'd proceed to work on this bug:
|
My first attempt to reproduce this issue has failed. My test application (that only changed the two URLs in the source posted above) that downloads a 4KB file while doing the POST, managed to get it 5000 times without problems by the time I stopped it... I'm using libcurl from current git master. |
I'm also using git master now. And I've updated to Tomcat 9.0.7. What I've found out so far: I also changed the size of data for the GET request from a few bytes to ~4k , but I can still reproduce the issue. I'll investigate further and report back next week. |
The servlet I'm using for testing: |
I hope someone else can reproduce the issue. The problem is somewhere here: Lines 2793 to 2808 in f84139f
Curl_setup_transfer() is called for the GET request. As there is no postdata, the connections writesockfd is nuked. But the connection is still used for the POST request. But the writesockfd is -1 now, the read_callback() is not called. Please be patient, I won't be able to work on this for some days. |
I'm not convinced the problem is there. That part of the code sets up the request for this transfer. A transfer is pretty much a 1-1 mapping with a The function that controls what gets waited for, per transfer, is the |
Did a little bit more research. The problem might be solved somewhere else, but the problem can be seen there. Lines 2792 to 2809 in 1621aed
It calls this function to setup the transfer: Lines 1989 to 2017 in 1621aed
In setup_transfer() it sets conn->writesockfd (writesockfd of the connection !!!) to CURL_BAD_SOCK if writesockindex parameter is -1. So a GET requests always sets writesockfd of the connection (!!!) to CURL_BAD_SOCK. For testing, I changed in setup_transfer(): and With this changes, the read_callback() of the application is called. |
Your observation seems correct and it's curious this hasn't caused more problems. I think that's because So, while you've identified a problem correctly I unfortunately don't think that's the major one that causes your problems. My proposed fix for this issue (and I'll file a separate PR in a sec) looks likes this: diff --git a/lib/transfer.c b/lib/transfer.c
index 5c8eb31d3..db08a6b4d 100644
--- a/lib/transfer.c
+++ b/lib/transfer.c
@@ -2016,15 +2016,22 @@ Curl_setup_transfer(
data = conn->data;
k = &data->req;
DEBUGASSERT((sockindex <= 1) && (sockindex >= -1));
- /* now copy all input parameters */
- conn->sockfd = sockindex == -1 ?
+ if(conn->bits.multiplex) {
+ /* when multiplexing, the read/write sockets need to be the same! */
+ conn->sockfd = sockindex == -1 ?
+ conn->sock[writesockindex] : conn->sock[sockindex];
+ conn->writesockfd = conn->sockfd;
+ }
+ else {
+ conn->sockfd = sockindex == -1 ?
CURL_SOCKET_BAD : conn->sock[sockindex];
- conn->writesockfd = writesockindex == -1 ?
+ conn->writesockfd = writesockindex == -1 ?
CURL_SOCKET_BAD:conn->sock[writesockindex];
+ }
k->getheader = getheader;
k->size = size;
k->bytecountp = bytecountp;
k->writebytecountp = writecountp; |
Hm, no that was a bit too quick. Doesn't work properly... |
Curl_setup_transfer() can be called to setup a new individual transfer over a multiplexed connection so it shouldn't unset writesockfd. Bug: #2520
I polished it slightly and created #2549. But again: while I think this fixes a bug, I'm not convinced this is the fix for you. Let me know what you find out! |
Thank you. With #2549 I could investigate further. The FD sets for select() are created here: Lines 103 to 126 in d63bada
But nghttp2_session_want_write() returns FALSE, so we are not listening to the write socket. It returns FALSE because pq is empty. But why is pq empty? Does the new GET request do that? |
After startup:
After the GET has finished and is re-added:
After "perform GET", nghttp2_session_want_write() is always FALSE. Perform GET: |
Thanks for the added details. I think diff --git a/lib/http2.c b/lib/http2.c
index 770ebdab5..58f4c9920 100644
--- a/lib/http2.c
+++ b/lib/http2.c
@@ -106,10 +106,11 @@ static int http2_perform_getsock(const struct connectdata *conn,
number of
sockets */
int numsocks)
{
const struct http_conn *c = &conn->proto.httpc;
+ struct SingleRequest *k = &conn->data->req;
int bitmap = GETSOCK_BLANK;
(void)numsocks;
/* TODO We should check underlying socket state if it is SSL socket
because of renegotiation. */
@@ -117,11 +118,13 @@ static int http2_perform_getsock(const struct connectdata *conn,
/* in a HTTP/2 connection we can basically always get a frame so we should
always be ready for one */
bitmap |= GETSOCK_READSOCK(FIRSTSOCKET);
- if(nghttp2_session_want_write(c->h2))
+ /* we're still uploading or the HTTP/2 layer wants to send data */
+ if((k->keepon & KEEP_SEND|KEEP_SEND_PAUSE) == KEEP_SEND) ||
+ nghttp2_session_want_write(c->h2))
bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET);
return bitmap;
}
|
When there's an upload in progress, make sure to wait for the socket to become writable. Detected-by: steini2000 on github Bug: #2520
That patch now landed. Take it for a spin! |
With the 2 patches, the test case works now. |
Lovely, thanks for confirming! |
I did this
The problem appears when adding the GET handle.
When adding the GET handle again is delayed, the read_callback() is called until shortly after the curl_multi_add_handle().
I expected the following
read_callback() is still called continuously, data can be send continuously.
With HTTP/2.0 multiplexing disabled, everything works as expected.
curl/libcurl version
build from source a3f3853
curl 7.60.0-DEV (x86_64-pc-linux-gnu) libcurl/7.60.0-DEV OpenSSL/1.0.2g zlib/1.2.8 nghttp2/1.31.0
Release-Date: [unreleased]
Protocols: dict file ftp ftps gopher http https imap imaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp
Features: AsynchDNS Debug TrackMemory IPv6 Largefile NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets HTTPS-proxy
Also observed with curl 7.56 on ARM
operating system
Ubuntu 16.04
Servlet source and pcap on request.
http2-mpx.c.txt
http2-mpx.log.zip
The text was updated successfully, but these errors were encountered: