git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH] remote-curl: avoid hang if curl asks for more data after eof

From
JTJonathan Tan <jonathantanmy@google.com>
Date
Nov 13, 2023, 21:22 UTC
Message-ID
<20231113212243.1495815-1-jonathantanmy@google.com>
In-Reply-To
<CAGE_+C6DJMAO0bj5QHoKBBV3gMEMtZ-ajJ9ZnDGYq6eorr-oig@mail.gmail.com>
Jiří Hruška <jirka@fud.cz> writes:
Show 11 quoted lines
> It has been observed that under some circumstances, libcurl can call
> our `CURLOPT_READFUNCTION` callback `rpc_out()` again even after
> already getting a return value of zero (EOF) back once before.
> 
> This results in `rpc_read_from_out()` trying to read more from the
> child process pipe, because `rpc->flush_read_but_not_sent` is reset to
> false already the first time EOF is returned, and then the whole
> operation gets stuck - the child process is already trying to read
> a response back and will not write anything to the output pipe anymore,
> while the parent/remote process is now blocked waiting to read more too
> and never even finishes sending the request.

Ah, thanks for finding this bug. It sounds worthwhile to make Git more resilient in this situation.

I'll just make some preliminary comments.
> This commit addresses the bug with the following changes:
[snip]

This seems like a long list of changes, when from the description of the bug above, I would have assumed it sufficient to record somewhere when the CURLOPT_READFUNCTION callback returns zero, and then always return zero after that if the callback is ever re-invoked. If this is indeed not sufficient, we should describe why.

Also, if multiple changes are needed, please split them into several commits.

Show 24 quoted lines
> A trivial solution would be to just take the line which resets the flag
> 
>     /*
>      * The line length either does not need to be sent at
>      * all or has already been completely sent. Now we can
>      * return 0, indicating EOF, meaning that the flush has
>      * been fully sent.
>      */
> -   rpc->flush_read_but_not_sent = 0;
>     return 0;
> 
> from rpc_out() and reset it only in post_rpc(), before the next time a large
> request is being sent out and rpc_out() will go into play again:
> 
>     if (large_request) {
>         ...
>         rpc->initial_buffer = 1;
> +       rpc->flush_read_but_not_sent = 0;
>         curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, rpc_out);
> 
> This way the CURLOPT_READFUNCTION would be returning zeros at the end of the
> upload as long as needed, just like fread() at the end of a real file.
> 
> Hence, the bug could be fixed with just that two-lines change.

Ah, you describe the straightforward fix here. I haven't looked at this closely enough to see if this would work, though.

Show 11 quoted lines
> But while trying to figure out the above, I noticed a few things that prolonged
> the time I needed to understand what was going on, so I would like to propose
> some more changes to make the code perhaps a bit easier to read for the next
> person who comes to hack on it after me.
> 
> The description of the extra modifications is in the commit message. All of
> these changes are obviously optional and naturally subjective. I think that we
> can all agree on some points (less indentation = good), but naming is hard,
> and so is balance between unclear and too verbose, or when to split all
> non-functional changes to a separate commit. So let me know if there are things
> to do differently and I will gladly obey, it is your codebase after all.

Yes, please split non-functional changes into a separate commit (preferably one for each concern). I do envision reviewers saying "let's put patches X, Y, and Z in, but not patches A, B, and C", so splitting would make it easier to decide what's worthwhile to have.

> Which brings me to the next topic, testing.
> 
> Validating the fix would be trivial with a mocked libcurl, but turns out to be
> much harder with the integration-level test suite of this project.
[snip]
> But outside of that, and as far as the bundled test suite goes, I have failed to
> write a test that could validate this problem does not ever occur again.

Due to the nature of the bug and the fix, I do agree that it's hard to test and I would be OK with including the fix without associated tests.

 
Previous: Jiří HruškaNext: Junio C Hamano
Message 2 of 18 in “remote-curl: avoid hang if curl asks for more data after eof”
  1. remote-curl: avoid hang if curl asks for more data after eofJiří Hruška, Nov 13, 2023
  2. Jonathan TanNov 13, 2023
  3. Junio C HamanoNov 14, 2023
  4. Jiří HruškaNov 14, 2023
  5. Jiří HruškaNov 27, 2023
  6. Jiří HruškaNov 14, 2023
  7. 0/5 Avoid hang if curl needs eof twice + minor related improvementsJiří Hruška, Nov 15, 2023
  8. Jonathan TanNov 15, 2023
  9. Jiří HruškaNov 27, 2023
  10. Jonathan TanNov 27, 2023
  11. 2/5 remote-curl: improve readability of curl callbacksJiří Hruška, Nov 15, 2023
  12. 1/5 remote-curl: avoid hang if curl asks for more data after eofJiří Hruška, Nov 15, 2023
  13. Jonathan TanNov 15, 2023
  14. 3/5 remote-curl: simplify rpc_out() - remove superfluous ifsJiří Hruška, Nov 15, 2023
  15. 4/5 remote-curl: simplify rpc_out() - less nesting and renameJiří Hruška, Nov 15, 2023
  16. 5/5 http: reset CURLOPT_POSTFIELDSIZE_LARGE between requestsJiří Hruška, Nov 15, 2023
  17. Patrick SteinhardtNov 15, 2023
  18. Jiří HruškaNov 27, 2023

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.