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

Re: [PATCH v3 2/3] t/lib-httpd: make http-429 first-request check atomic

From
Patrick Steinhardt <ps@pks.im>
Date
Aug 31, 2026, 07:18 UTC
Message-ID
<apUqs8N3EnTFngyQ@pks.im>
In-Reply-To
<8ed22c02a192e10ab46c7df61e92a3669faaf25a.1786583137.git.gitgitgadget@gmail.com>
On Thu, Aug 13, 2026 at 01:05:35AM +0000, Michael Montalbo via GitGitGadget wrote:
Show 31 quoted lines
> From: Michael Montalbo <mmontalbo@gmail.com>
> 
> http-429.sh returns 429 to the first request for an endpoint and
> forwards later ones to git-http-backend so the retry succeeds. It
> remembers that it has already answered 429 by checking for a shared
> state file with "test -f" and creating it with "touch".
> 
> That "check-and-set" is not atomic. Apache runs the CGI for several
> requests at once, so two of them can pass the "test -f" before either
> "touch"es the file, and both then answer as the first request. The
> retry flow is mostly sequential, so this has not been observed to fail,
> but the race is latent. Replace the check and the "touch" with a single
> atomic "mkdir", which fails if the directory already exists, so exactly
> one of the concurrent requests is rate-limited and the rest are
> forwarded.
> 
> The "permanent" mode needs one extra step, for correctness rather than
> tidiness. The marker means "429 already served, now forward", so it must
> never be visible to a request that must itself return 429. Since
> "permanent" returns 429 to every request, it must leave no marker. The
> original did not manage this. It ran the "touch" unconditionally and
> removed the file with "rm -f" in the "permanent" case, and that
> "create-then-remove" has the same racy window: a concurrent "permanent"
> request can see the marker before the "rm -f" and be wrongly forwarded.
> Skipping the "mkdir" entirely for "permanent" (the "!= permanent" guard)
> leaves no marker at all, so every "permanent" request rate-limits.
> 
> There is no regression test. The check and the set are adjacent commands
> with nothing in between to synchronize on, so the overlap cannot be
> forced deterministically, only reproduced by chance; the fix is
> preventive.

A lot of AI-fluff in this message that could have otherwise been much briefer, but okay.

Show 55 quoted lines
> diff --git a/t/lib-httpd/http-429.sh b/t/lib-httpd/http-429.sh
> index c97b16145b..904cdacbd0 100644
> --- a/t/lib-httpd/http-429.sh
> +++ b/t/lib-httpd/http-429.sh
> @@ -26,14 +26,24 @@ repo_path="${remaining#*/}"  # Get rest (repo path)
>  # The repo name is the first component before any "/"
>  repo_name="${repo_path%%/*}"
>  
> -# Use current directory (HTTPD_ROOT_PATH) for state file
> -# Create a safe filename from test_context, retry_after and repo_name
> -# This ensures all requests for the same test context share the same state file
> +# Store state in the current directory (HTTPD_ROOT_PATH). Build a safe name
> +# from test_context, retry_after, and repo_name, so that all requests for one
> +# test context share the same state.
>  safe_name=$(echo "${test_context}-${retry_after}-${repo_name}" | tr '/' '_' | tr -cd 'a-zA-Z0-9_-')
> -state_file="http-429-state-${safe_name}"
> +state="http-429-state-${safe_name}"
>  
> -# Check if this is the first call (no state file exists)
> -if test -f "$state_file"
> +# This endpoint returns 429 to the first request. It forwards every later
> +# request to git-http-backend, so the retry succeeds. Apache can run this CGI
> +# for several requests at the same time. A single atomic "mkdir" selects the
> +# first request, because only one "mkdir" succeeds. That request returns 429
> +# and leaves the directory as the "already rate-limited" marker. Every later
> +# "mkdir" fails, so the endpoint forwards those requests.
> +#
> +# "permanent" is the exception. It must return 429 to every request, so it
> +# skips the "mkdir" and records no state. A leftover directory would let a
> +# later "permanent" request find the marker. The endpoint would forward that
> +# request, which "permanent" must not allow.
> +if test "$retry_after" != permanent && ! mkdir "$state" 2>/dev/null
>  then
>  	# Already returned 429 once, forward to git-http-backend
>  	# Set PATH_INFO to just the repo path (without retry-after value)
> @@ -52,9 +62,6 @@ then
>  	exec "$GIT_EXEC_PATH/git-http-backend"
>  fi
>  
> -# Mark that we've returned 429
> -touch "$state_file"
> -
>  # Output HTTP 429 response
>  printf "Status: 429 Too Many Requests\r\n"
>  
> @@ -67,8 +74,7 @@ case "$retry_after" in
>  		printf "Retry-After: invalid-format-123abc\r\n"
>  		;;
>  	permanent)
> -		# Always return 429, don't set state file for success
> -		rm -f "$state_file"
> +		# Always return 429
>  		printf "Retry-After: 1\r\n"
>  		printf "Content-Type: text/plain\r\n"
>  		printf "\r\n"
The changes themselves look sensible.
Patrick
Previous: Michael Montalbo via GitGitGadgetNext: Junio C Hamano
Message 25 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.