From: Ted Nyman <tnyman@openai.com>
To: git@vger.kernel.org
Cc: gitster@pobox.com, me@ttaylorr.com, peff@peff.net, ps@pks.im,
karthik.188@gmail.com, sandals@crustytoothpaste.net,
avarab@gmail.com
Subject: [PATCH v4 0/3] packfile URIs: support concurrent downloads
Date: Fri, 24 Jul 2026 01:14:22 -0700 [thread overview]
Message-ID: <cover.1784874850.git.tnyman@openai.com> (raw)
In-Reply-To: <cover.1784676106.git.tnyman@openai.com>
Packfile URI and dumb HTTP downloads stage packs at
objects/pack/pack-<hash>.pack.temp so an interrupted transfer can
resume. Opening that file in append mode forces every write to its
current end. Two Git processes fetching the same pack into one object
database can therefore append duplicate data and corrupt the pack.
The first patch separates the unrelated --index-pack-arg documentation
and error-message correction requested during review.
The second patch keeps the predictable staging name but removes append
mode. Each downloader seeks once to the current end, requests the
corresponding Range, and writes using its own descriptor offset. Since
the staging key must identify immutable pack contents, overlapping
responses write identical bytes at identical offsets. There is no need
for pwrite(2) or cross-process coordination, and resumption continues to
work for both packfile URI and ordinary dumb HTTP downloads.
A downloader can also find that the partial pack has completed and
request a range starting at EOF. Servers may respond with HTTP 416 in
that case. Treat the response as a completed download and let
index-pack validate the pack.
On MinGW, the non-append O_RDWR open grants FILE_SHARE_DELETE only for an
existing file. Create a missing staging file exclusively, close it, and
reopen it without O_CREAT so every retained descriptor permits another
downloader to unlink the path. Keep the open descriptor for index-pack;
it installs its own pack, so the shared staging file is only unlinked,
never renamed.
The third patch handles the related .keep race. When another process has
already created the keep file, index-pack reports "pack<TAB><hash>"
instead of "keep<TAB><hash>". Accept both successful forms and remove
only keep files created by the current process. Read only the prefix and
hash so any following fsck output remains available to fetch-pack.
The tests cover resumption, a completed partial returning 416,
overlapping 200 and 206 responses, unlinking the staging path while
index-pack holds its descriptor, and a pre-existing .keep file. The
unlink test does not require FIFOs, so it can exercise MinGW's sharing
behavior even though the concurrent-download tests are skipped there.
Changes since v3:
* Match HTTP 416 in trace output from both older and current libcurl.
* Add a timeout to the overlapping-download test server, notify FIFO
waiters on server failures, and track the actual server process for
cleanup.
* Wait for the second downloader first so an early failure cannot
leave the test server waiting for a request that will never arrive.
* No production code changes.
These changes avoid false failures with older libcurl and prevent a
failed downloader from leaving the test server running indefinitely.
The v3 discussion is at:
https://lore.kernel.org/git/cover.1784676106.git.tnyman@openai.com/
Ted Nyman (3):
http-fetch: correct --index-pack-arg documentation
http: avoid concurrent appends to partial packs
fetch-pack: accept "pack" output for packfile URIs
Documentation/git-http-fetch.adoc | 13 +-
fetch-pack.c | 33 ++--
http-fetch.c | 7 +-
http-push.c | 3 +-
http-walker.c | 3 +-
http.c | 56 ++++---
t/t5550-http-fetch-dumb.sh | 250 ++++++++++++++++++++++++++++++
t/t5702-protocol-v2.sh | 31 ++++
8 files changed, 350 insertions(+), 46 deletions(-)
Range-diff against v3:
1: a6a40b8046 = 1: a6a40b8046 http-fetch: correct --index-pack-arg documentation
2: 6c91054afc ! 2: 144c98cdfa http: avoid concurrent appends to partial packs
@@ t/t5550-http-fetch-dumb.sh: test_expect_success 'http-fetch --packfile' '
+ test_cmp expect second.out &&
+ test_grep "Range: bytes=64-" first.trace &&
+ test_grep "Range: bytes=[0-9]*-" second.trace &&
-+ test_grep "HTTP/[0-9.]* 416" second.trace &&
++ test_grep "416 Requested Range Not Satisfiable" second.trace &&
+ test_path_is_missing "$tmpfile" &&
+ git -C packfileclient-concurrent cat-file -e "$HASH"
+'
@@ t/t5550-http-fetch-dumb.sh: test_expect_success 'http-fetch --packfile' '
+ use IO::Socket::INET;
+
+ my ($packfile, $server_ready, $first_ready) = @ARGV;
++ my $completed = 0;
++ END {
++ if (!$completed) {
++ signal_ready($server_ready, "failed");
++ signal_ready($first_ready, "failed");
++ }
++ }
++
++ $SIG{ALRM} = sub { die "timed out serving concurrent pack requests\n" };
++ alarm 60;
++
+ open(my $in, "<:raw", $packfile) or die "open $packfile: $!";
+ my $pack = do { local $/; <$in> };
+ close($in) or die "close $packfile: $!";
@@ t/t5550-http-fetch-dumb.sh: test_expect_success 'http-fetch --packfile' '
+ write_all($second, substr($pack, $second_pos));
+ close($first) or die "close first response: $!";
+ close($second) or die "close second response: $!";
++ $completed = 1;
++ alarm 0;
+ EOF
+ {
-+ (
-+ if ! "$TRASH_DIRECTORY/slow-pack-server" "$pack" \
-+ "$TRASH_DIRECTORY/server-ready" \
-+ "$TRASH_DIRECTORY/first-ready"
-+ then
-+ echo failed >"$TRASH_DIRECTORY/server-ready" &&
-+ echo failed >"$TRASH_DIRECTORY/first-ready" &&
-+ exit 1
-+ fi
-+ ) >server.log 2>&1 &
++ "$TRASH_DIRECTORY/slow-pack-server" "$pack" \
++ "$TRASH_DIRECTORY/server-ready" \
++ "$TRASH_DIRECTORY/first-ready" >server.log 2>&1 &
+ server_pid=$!
+ } &&
+ test_when_finished "
@@ t/t5550-http-fetch-dumb.sh: test_expect_success 'http-fetch --packfile' '
+ kill $second_pid 2>/dev/null || :
+ wait $second_pid 2>/dev/null || :
+ " &&
-+ wait "$server_pid" &&
-+ wait "$first_pid" &&
+ wait "$second_pid" &&
++ wait "$first_pid" &&
++ wait "$server_pid" &&
+ test_grep "HTTP/[0-9.]* 200" overlap-first.trace &&
+ test_grep "Range: bytes=[1-9][0-9]*-" overlap-second.trace &&
+ test_grep "HTTP/[0-9.]* 206" overlap-second.trace &&
3: 1ee5d7e027 = 3: d9063deb60 fetch-pack: accept "pack" output for packfile URIs
base-commit: 5d2e7709234afea1b6ddb25cd4f60d3d5fb3c200
--
2.55.0.openai.131.g83a728de1eb6
next prev parent reply other threads:[~2026-07-24 8:14 UTC|newest]
Thread overview: 30+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-13 22:37 [PATCH 0/2] packfile URIs: support concurrent downloads Ted Nyman
2026-07-13 22:34 ` [PATCH 1/2] http: use unique tempfiles for packfile URI downloads Ted Nyman
2026-07-14 1:00 ` Junio C Hamano
2026-07-14 1:58 ` Ted Nyman
2026-07-14 4:07 ` Taylor Blau
2026-07-14 5:28 ` Jeff King
2026-07-14 18:10 ` Junio C Hamano
2026-07-14 18:31 ` Ted Nyman
2026-07-14 4:06 ` Taylor Blau
2026-07-14 5:44 ` Jeff King
2026-07-14 6:46 ` Jeff King
2026-07-13 22:34 ` [PATCH 2/2] fetch-pack: accept "pack" output for packfile URIs Ted Nyman
2026-07-14 7:12 ` Jeff King
2026-07-14 7:13 ` Jeff King
2026-07-14 18:38 ` Ted Nyman
2026-07-14 21:47 ` Jeff King
2026-07-14 4:13 ` [PATCH 0/2] packfile URIs: support concurrent downloads Taylor Blau
2026-07-20 22:33 ` [PATCH v2 " Ted Nyman
2026-07-20 22:33 ` [PATCH v2 1/2] http: avoid concurrent appends to partial packs Ted Nyman
2026-07-21 19:56 ` Junio C Hamano
2026-07-20 22:34 ` [PATCH v2 2/2] fetch-pack: accept "pack" output for packfile URIs Ted Nyman
2026-07-21 23:29 ` [PATCH v3 0/3] packfile URIs: support concurrent downloads Ted Nyman
2026-07-21 23:29 ` [PATCH v3 1/3] http-fetch: correct --index-pack-arg documentation Ted Nyman
2026-07-21 23:29 ` [PATCH v3 2/3] http: avoid concurrent appends to partial packs Ted Nyman
2026-07-21 23:29 ` [PATCH v3 3/3] fetch-pack: accept "pack" output for packfile URIs Ted Nyman
2026-07-24 4:43 ` [PATCH v3 0/3] packfile URIs: support concurrent downloads Junio C Hamano
2026-07-24 8:14 ` Ted Nyman [this message]
2026-07-24 8:14 ` [PATCH v4 1/3] http-fetch: correct --index-pack-arg documentation Ted Nyman
2026-07-24 8:14 ` [PATCH v4 2/3] http: avoid concurrent appends to partial packs Ted Nyman
2026-07-24 8:14 ` [PATCH v4 3/3] fetch-pack: accept "pack" output for packfile URIs Ted Nyman
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=cover.1784874850.git.tnyman@openai.com \
--to=tnyman@openai.com \
--cc=avarab@gmail.com \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=karthik.188@gmail.com \
--cc=me@ttaylorr.com \
--cc=peff@peff.net \
--cc=ps@pks.im \
--cc=sandals@crustytoothpaste.net \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.