From: "Michael Montalbo via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Patrick Steinhardt <ps@pks.im>,
Michael Montalbo <mmontalbo@gmail.com>,
Michael Montalbo <mmontalbo@gmail.com>
Subject: [PATCH v5 1/3] t/lib-httpd: fix apply-one-time-script race under concurrent requests
Date: Tue, 01 Sep 2026 15:53:01 +0000 [thread overview]
Message-ID: <e202142f1999a57d485cae0d50a1a7c1afa50763.1788277983.git.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2171.v5.git.1788277983.gitgitgadget@gmail.com>
From: Michael Montalbo <mmontalbo@gmail.com>
apply-one-time-script.sh is a test helper that executes a
"one-time-script" responsible for modifying the response normally
returned by git-http-backend. apply-one-time-script.sh should run
"one-time-script" once and return a modified response once. However,
sometimes a race between multiple concurrent requests causes
apply-one-time-script.sh to misbehave and return multiple modified
responses or an empty response that results in:
fatal: ... The requested URL returned error: 500
fatal: could not fetch <oid> from promisor remote
This can be seen in the flaky failure of t5616.47 on the macOS CI
runners.
Fix the logic that checks if "one-time-script" has returned its modified
response by chaining "rm one-time-script" with its execution. This
ensures a racing script does not also have the opportunity to execute
"one-time-script".
Add t/t5567-one-time-script.sh to verify the race is fixed. Implement a
stub "git-http-backend" that intentionally invokes a concurrent request,
and check that only one modified response is returned without error.
Signed-off-by: Michael Montalbo <mmontalbo@gmail.com>
---
t/lib-httpd/apply-one-time-script.sh | 38 +++++++----
t/meson.build | 1 +
t/t5567-one-time-script.sh | 96 ++++++++++++++++++++++++++++
3 files changed, 121 insertions(+), 14 deletions(-)
create mode 100755 t/t5567-one-time-script.sh
diff --git a/t/lib-httpd/apply-one-time-script.sh b/t/lib-httpd/apply-one-time-script.sh
index b1682944e2..eac21a3a8e 100644
--- a/t/lib-httpd/apply-one-time-script.sh
+++ b/t/lib-httpd/apply-one-time-script.sh
@@ -6,21 +6,31 @@
#
# This can be used to simulate the effects of the repository changing in
# between HTTP request-response pairs.
-if test -f one-time-script
-then
- LC_ALL=C
- export LC_ALL
+test -f one-time-script || exec "$GIT_EXEC_PATH/git-http-backend"
+
+LC_ALL=C
+export LC_ALL
- "$GIT_EXEC_PATH/git-http-backend" >out
- ./one-time-script out >out_modified
+out=out.$$
+modified=out-modified.$$
+"$GIT_EXEC_PATH/git-http-backend" >"$out"
- if cmp -s out out_modified
- then
- cat out
- else
- cat out_modified
- rm one-time-script
- fi
+# Since Apache can execute this script for multiple requests
+# concurrently, we chain "rm one-time-script" with the logic
+# for generating a modified response. If the "rm" ran separately,
+# a concurrent request could pass the "test -f" above and
+# erroneously result in multiple modified responses or an empty
+# body depending on the race state.
+#
+# We discard stderr for ./one-time-script since it is possible
+# ./one-time-script has been removed already, which is expected
+# sometimes. In this case, the unmodified response will be returned.
+if ./one-time-script "$out" 2>/dev/null >"$modified" &&
+ ! cmp -s "$out" "$modified" &&
+ rm one-time-script 2>/dev/null
+then
+ cat "$modified"
else
- "$GIT_EXEC_PATH/git-http-backend"
+ cat "$out"
fi
+rm -f "$out" "$modified"
diff --git a/t/meson.build b/t/meson.build
index a25f37d2f5..e4d0b6dc4e 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -716,6 +716,7 @@ integration_tests = [
't5564-http-proxy.sh',
't5565-push-multiple.sh',
't5566-push-group.sh',
+ 't5567-one-time-script.sh',
't5570-git-daemon.sh',
't5571-pre-push-hook.sh',
't5572-pull-submodule.sh',
diff --git a/t/t5567-one-time-script.sh b/t/t5567-one-time-script.sh
new file mode 100755
index 0000000000..a8429ef3c3
--- /dev/null
+++ b/t/t5567-one-time-script.sh
@@ -0,0 +1,96 @@
+#!/bin/sh
+
+test_description='apply-one-time-script CGI helper is safe under concurrent requests'
+
+. ./test-lib.sh
+
+HELPER="$TEST_DIRECTORY/lib-httpd/apply-one-time-script.sh"
+
+test_expect_success PIPE 'helper only serves one rewritten response for concurrent requests' '
+ mkdir workdir fakebin &&
+ ENTERED="$PWD/entered" &&
+ GATE="$PWD/gate" &&
+ export ENTERED GATE &&
+ mkfifo "$ENTERED" "$GATE" &&
+
+ # A stub git-http-backend that returns a response based on
+ # $ROLE. For $ROLE = modify, return the response string
+ # "packfile", which ends up being modified by the example
+ # one-time-script below.
+ #
+ # Otherwise, run the branch returning a response that
+ # should be passed through, and block until released
+ # by "read -r $GATE".
+ write_script fakebin/git-http-backend <<-\EOF &&
+ printf "Status: 200 OK\r\n"
+ printf "Content-Type: application/x-git-result\r\n"
+ printf "\r\n"
+ if test "$ROLE" = modify
+ then
+ printf "packfile\n"
+ else
+ echo entered >"$ENTERED"
+ read -r released <"$GATE"
+ printf "refs\n"
+ fi
+ EOF
+
+ # An example one-time-script for apply-one-time-script
+ # to execute. Checks for "packfile" in the response
+ # that will be returned, and replaces it with a
+ # modified response. Passes through responses without
+ # "packfile" in them.
+ write_script workdir/one-time-script <<-\EOF &&
+ if grep packfile "$1" >/dev/null
+ then
+ sed "/packfile/q" "$1" &&
+ printf "REPLACED\n"
+ else
+ cat "$1"
+ fi
+ EOF
+
+ GIT_EXEC_PATH="$PWD/fakebin" &&
+ export GIT_EXEC_PATH &&
+
+ # Ensure $GATE has a reader so the test does not block indefinitely if
+ # the helper is buggy and "echo released >&9" below does not unblock
+ # the unmodified response gate.
+ exec 9<>"$GATE" &&
+
+ # Launch the passthrough request in the background. Record its pid
+ # so it can be killed when the test finishes if, for some reason, the
+ # request stays blocked and would stall a test runner.
+ { (
+ cd workdir &&
+ ROLE=passthrough sh "$HELPER" >../passthrough.out 2>../passthrough.err
+ ) & } &&
+ passthrough_pid=$! &&
+ test_when_finished "kill $passthrough_pid 2>/dev/null || :" &&
+
+ # Wait until the passthrough request is "in-flight" and paused
+ # mid-response.
+ read -r entered <"$ENTERED" &&
+
+ # Launch the request for a modified response while the passthrough
+ # request is concurrently "in-flight" and paused.
+ (
+ cd workdir &&
+ ROLE=modify sh "$HELPER" >../modify.out 2>../modify.err
+ ) &&
+
+ # Unblock the passthrough request, allowing git-http-backend to
+ # complete its response.
+ echo released >&9 &&
+ { wait "$passthrough_pid" || :; } &&
+
+ test_must_be_empty passthrough.err &&
+ test_must_be_empty modify.err &&
+ test_grep "Status: 200 OK" passthrough.out &&
+ test_grep "Status: 200 OK" modify.out &&
+ test_grep REPLACED modify.out &&
+ test_grep ! REPLACED passthrough.out &&
+ test_grep refs passthrough.out
+'
+
+test_done
--
gitgitgadget
next prev parent reply other threads:[~2026-09-01 15:53 UTC|newest]
Thread overview: 43+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-08 2:59 [PATCH 0/3] t/lib-httpd: make CGI test helpers concurrency-safe Michael Montalbo via GitGitGadget
2026-07-08 2:59 ` [PATCH 1/3] t/lib-httpd: fix apply-one-time-script race under concurrent requests Michael Montalbo via GitGitGadget
2026-07-08 19:54 ` Junio C Hamano
2026-07-09 17:26 ` Michael Montalbo
2026-07-08 2:59 ` [PATCH 2/3] t/lib-httpd: make http-429 first-request check atomic Michael Montalbo via GitGitGadget
2026-07-08 19:58 ` Junio C Hamano
2026-07-08 20:02 ` Junio C Hamano
2026-07-09 18:10 ` Michael Montalbo
2026-07-08 2:59 ` [PATCH 3/3] t/README: document writing concurrency-safe helpers Michael Montalbo via GitGitGadget
2026-07-08 19:59 ` Junio C Hamano
2026-07-10 17:30 ` [PATCH v2 0/3] t/lib-httpd: make CGI test helpers concurrency-safe Michael Montalbo via GitGitGadget
2026-07-10 17:30 ` [PATCH v2 1/3] t/lib-httpd: fix apply-one-time-script race under concurrent requests Michael Montalbo via GitGitGadget
2026-08-04 8:03 ` Patrick Steinhardt
2026-08-07 16:29 ` Michael Montalbo
2026-07-10 17:30 ` [PATCH v2 2/3] t/lib-httpd: make http-429 first-request check atomic Michael Montalbo via GitGitGadget
2026-07-10 17:30 ` [PATCH v2 3/3] t/README: document writing concurrency-safe helpers Michael Montalbo via GitGitGadget
2026-08-04 8:03 ` Patrick Steinhardt
2026-08-07 16:51 ` Michael Montalbo
2026-08-10 6:06 ` Patrick Steinhardt
2026-08-02 3:02 ` [PATCH v2 0/3] t/lib-httpd: make CGI test helpers concurrency-safe Michael Montalbo
2026-08-03 21:55 ` Junio C Hamano
2026-08-13 1:05 ` [PATCH v3 " Michael Montalbo via GitGitGadget
2026-08-13 1:05 ` [PATCH v3 1/3] t/lib-httpd: fix apply-one-time-script race under concurrent requests Michael Montalbo via GitGitGadget
2026-08-13 1:05 ` [PATCH v3 2/3] t/lib-httpd: make http-429 first-request check atomic Michael Montalbo via GitGitGadget
2026-08-31 7:18 ` Patrick Steinhardt
2026-08-31 14:50 ` Junio C Hamano
2026-08-31 17:31 ` Michael Montalbo
2026-08-31 17:09 ` Michael Montalbo
2026-08-13 1:05 ` [PATCH v3 3/3] t/lib-httpd: document writing concurrency-safe CGI helpers Michael Montalbo via GitGitGadget
2026-08-31 7:18 ` Patrick Steinhardt
2026-08-26 19:59 ` [PATCH v3 0/3] t/lib-httpd: make CGI test helpers concurrency-safe Junio C Hamano
2026-08-31 7:18 ` Patrick Steinhardt
2026-09-01 0:27 ` [PATCH v4 " Michael Montalbo via GitGitGadget
2026-09-01 0:27 ` [PATCH v4 1/3] t/lib-httpd: fix apply-one-time-script race under concurrent requests Michael Montalbo via GitGitGadget
2026-09-01 0:27 ` [PATCH v4 2/3] t/lib-httpd: make http-429 first-request check atomic Michael Montalbo via GitGitGadget
2026-09-01 0:27 ` [PATCH v4 3/3] t/lib-httpd: document writing concurrency-safe CGI helpers Michael Montalbo via GitGitGadget
2026-09-01 11:17 ` Patrick Steinhardt
2026-09-01 14:28 ` Michael Montalbo
2026-09-01 15:53 ` [PATCH v5 0/3] t/lib-httpd: make CGI test helpers concurrency-safe Michael Montalbo via GitGitGadget
2026-09-01 15:53 ` Michael Montalbo via GitGitGadget [this message]
2026-09-01 15:53 ` [PATCH v5 2/3] t/lib-httpd: make http-429 first-request check atomic Michael Montalbo via GitGitGadget
2026-09-01 15:53 ` [PATCH v5 3/3] t/lib-httpd: document writing concurrency-safe CGI helpers Michael Montalbo via GitGitGadget
2026-09-03 5:29 ` [PATCH v5 0/3] t/lib-httpd: make CGI test helpers concurrency-safe Patrick Steinhardt
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=e202142f1999a57d485cae0d50a1a7c1afa50763.1788277983.git.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=git@vger.kernel.org \
--cc=mmontalbo@gmail.com \
--cc=ps@pks.im \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox