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

Re: [PATCH 1/3] t/lib-httpd: fix apply-one-time-script race under concurrent requests

From
Junio C Hamano <gitster@pobox.com>
Date
Jul 8, 2026, 19:54 UTC
Message-ID
<xmqqpl0xtfyz.fsf@gitster.g>
In-Reply-To
<9f48aa6d6ddea681b700f689f0509c4b30a7007d.1783479584.git.gitgitgadget@gmail.com>
"Michael Montalbo via GitGitGadget" <gitgitgadget@gmail.com> writes:
Show 19 quoted lines
> From: Michael Montalbo <mmontalbo@gmail.com>
>
> apply-one-time-script.sh checks for the "one-time-script" marker, runs
> it, captures the git-http-backend response in the fixed-name files "out"
> and "out_modified", and removes the marker only after it has finished
> serving the modified response. Because the client receives the response
> body before that removal, it can start its next request while the marker
> still exists. Apache can then run this CGI for two requests at once: a
> partial fetch that receives a REF_DELTA against a missing promisor
> object lazily fetches that base while the first response is still in
> flight. The second request passes the marker check, the first request
> then removes the marker, and the second fails to exec the now-missing
> marker, emits no output, and the server answers HTTP 500:
>
>   fatal: ... The requested URL returned error: 500
>   fatal: could not fetch <oid> from promisor remote
>
> This has been seen as a flaky failure of t5616.47 on the macOS CI
> runners.
Thanks for this detailed write-up.  The analysis looks good.
Show 6 quoted lines
> Claim the marker atomically with a rename, and only once the one-time
> script has succeeded and actually changed the response; give the scratch
> files per-request names. A request that loses the rename, or whose
> script fails or leaves the response unchanged, serves the unmodified
> body and keeps the marker for a later request. No path emits an empty
> body, so the HTTP 500 no longer occurs.
Hmph.  
Show 16 quoted lines
> +#
> +# Apache can run this CGI for concurrent requests (for example a partial fetch
> +# that lazily fetches a missing object while the first response is still in
> +# flight), so the helper claims the marker atomically with a rename, and only
> +# once it has decided to modify the response. A request that loses the race
> +# finds the marker already gone and serves its response unchanged; no request
> +# is left emitting an empty body, which the server would report as HTTP 500.
> +# Scratch files are per-request ($$) so concurrent requests do not clobber each
> +# other.
> +
> +test -f one-time-script || exec "$GIT_EXEC_PATH/git-http-backend"
>  
> -	"$GIT_EXEC_PATH/git-http-backend" >out
> -	./one-time-script out >out_modified
> +LC_ALL=C
> +export LC_ALL

The original was somehow inconsistent in that it forced C locale only when one-time-script munged the output, and otherwise the backend was run in the original locale. I am not sure if that matters very much.

Show 12 quoted lines
> +out=out.$$
> +modified=out-modified.$$
> +"$GIT_EXEC_PATH/git-http-backend" >"$out"
> +
> +if ./one-time-script "$out" 2>/dev/null >"$modified" &&
> +   ! cmp -s "$out" "$modified" &&
> +   mv one-time-script one-time-script.$$ 2>/dev/null
> +then
> +	cat "$modified"
>  else
> +	cat "$out"
>  fi

We may run the one-time script, find that it modified the payload, and then another instance of us may start running before we can move the one-time script away, so the second request can see "ah, one-time-script is there, nobody has claimed it by renaming" and run it again, no? So this solution may shrink the race window but may not completely eliminate it, unless we have some coordination among ourselves, perhaps?

Ah, we assume running one-time-script itself multiple times is safe and does not cause issues. Our objective is to avoid returning modified output twice. So while the first instance of us successfully renames one-time-script to one-time-script.$$ and emits the modified result, even if the second instance raced and managed to run the script again, it will fail to rename with "mv", and discard the modified output, and instead show the unmodified output generated by the backend.

OK. It is a bit tricky. It may help future readers if we said something about this in the proposed log message (i.e., we consider that it is perfectly fine to run one-time-script more than once; we only want to avoid letting the second invocation's output used).

Thanks.
Previous: Michael Montalbo via GitGitGadgetNext: Michael Montalbo
Message 3 of 43 in “t/lib-httpd: make CGI test helpers concurrency-safe”
  1. 0/3 t/lib-httpd: make CGI test helpers concurrency-safeMichael Montalbo via GitGitGadget, Jul 8, 2026
  2. 1/3 t/lib-httpd: fix apply-one-time-script race under concurrent requestsMichael Montalbo via GitGitGadget, Jul 8, 2026
  3. Junio C HamanoJul 8, 2026
  4. Michael MontalboJul 9, 2026
  5. 2/3 t/lib-httpd: make http-429 first-request check atomicMichael Montalbo via GitGitGadget, Jul 8, 2026
  6. Junio C HamanoJul 8, 2026
  7. Junio C HamanoJul 8, 2026
  8. Michael MontalboJul 9, 2026
  9. 3/3 t/README: document writing concurrency-safe helpersMichael Montalbo via GitGitGadget, Jul 8, 2026
  10. Junio C HamanoJul 8, 2026
  11. 0/3 t/lib-httpd: make CGI test helpers concurrency-safeMichael Montalbo via GitGitGadget, Jul 10, 2026
  12. 1/3 t/lib-httpd: fix apply-one-time-script race under concurrent requestsMichael Montalbo via GitGitGadget, Jul 10, 2026
  13. Patrick SteinhardtAug 4, 2026
  14. Michael MontalboAug 7, 2026
  15. 2/3 t/lib-httpd: make http-429 first-request check atomicMichael Montalbo via GitGitGadget, Jul 10, 2026
  16. 3/3 t/README: document writing concurrency-safe helpersMichael Montalbo via GitGitGadget, Jul 10, 2026
  17. Patrick SteinhardtAug 4, 2026
  18. Michael MontalboAug 7, 2026
  19. Patrick SteinhardtAug 10, 2026
  20. Michael MontalboAug 2, 2026
  21. Junio C HamanoAug 3, 2026
  22. 0/3 t/lib-httpd: make CGI test helpers concurrency-safeMichael Montalbo via GitGitGadget, Aug 13, 2026
  23. 1/3 t/lib-httpd: fix apply-one-time-script race under concurrent requestsMichael Montalbo via GitGitGadget, Aug 13, 2026
  24. 2/3 t/lib-httpd: make http-429 first-request check atomicMichael Montalbo via GitGitGadget, Aug 13, 2026
  25. Patrick SteinhardtAug 31, 2026
  26. Junio C HamanoAug 31, 2026
  27. Michael MontalboAug 31, 2026
  28. Michael MontalboAug 31, 2026
  29. 3/3 t/lib-httpd: document writing concurrency-safe CGI helpersMichael Montalbo via GitGitGadget, Aug 13, 2026
  30. Patrick SteinhardtAug 31, 2026
  31. Junio C HamanoAug 26, 2026
  32. Patrick SteinhardtAug 31, 2026
  33. 0/3 t/lib-httpd: make CGI test helpers concurrency-safeMichael Montalbo via GitGitGadget, Sep 1, 2026
  34. 1/3 t/lib-httpd: fix apply-one-time-script race under concurrent requestsMichael Montalbo via GitGitGadget, Sep 1, 2026
  35. 2/3 t/lib-httpd: make http-429 first-request check atomicMichael Montalbo via GitGitGadget, Sep 1, 2026
  36. 3/3 t/lib-httpd: document writing concurrency-safe CGI helpersMichael Montalbo via GitGitGadget, Sep 1, 2026
  37. Patrick SteinhardtSep 1, 2026
  38. Michael MontalboSep 1, 2026
  39. 0/3 t/lib-httpd: make CGI test helpers concurrency-safeMichael Montalbo via GitGitGadget, Sep 1, 2026
  40. 1/3 t/lib-httpd: fix apply-one-time-script race under concurrent requestsMichael Montalbo via GitGitGadget, Sep 1, 2026
  41. 2/3 t/lib-httpd: make http-429 first-request check atomicMichael Montalbo via GitGitGadget, Sep 1, 2026
  42. 3/3 t/lib-httpd: document writing concurrency-safe CGI helpersMichael Montalbo via GitGitGadget, Sep 1, 2026
  43. Patrick SteinhardtSep 3, 2026

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.