Skip to content

Commit b99b73a

Browse files
committed
fetch-pack: derive tunable worker defaults for URI packs
Packfile URI fetches default to one job, and parallel URI indexers force one thread even when pack.threads is configured. Use online CPUs to choose defaults while retaining the existing configuration controls. Default fetch.packfileUriJobs to the online CPU count capped at eight. Divide CPUs among the actual parallel jobs for each indexer, with a one-thread minimum and four-thread automatic maximum. Explicit positive pack.threads overrides that budget; explicit URI job values remain uncapped and values at or below one retain serial processing. Preserve no-ref-delta negotiation and verification, serial fallback, pack ownership and cancellation. Document that increasing both controls can increase CPU and memory use. Verification: the new default-concurrency regression fails on the old implementation and passes with the change. The developer build and all 102 t5702 protocol tests pass, including real HTTP coverage, explicit thread values, job overrides and no-ref-delta validation. All 70 applicable t5300 index-pack tests pass; two !PTHREADS cases do not apply to this threaded build. Chainlint, greplint and documentation style checks pass. Independent review found no introduced P0/P1/P2 defects.
1 parent 47c847c commit b99b73a

4 files changed

Lines changed: 204 additions & 9 deletions

File tree

Documentation/config/fetch.adoc

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,21 @@ config setting.
9696

9797
`fetch.packfileUriJobs`::
9898
Specifies the maximum number of packfile URI downloads and indexers
99-
to run at once. The default is 1, which preserves advertised URI
100-
order.
99+
to run at once. If unset, defaults to the number of online CPUs,
100+
capped at 8. Values of 1 or less preserve advertised URI order.
101+
An explicit value greater than 8 is permitted. The number of URI
102+
packs in the response also limits the number of jobs.
101103
+
102104
Values greater than 1 are used only when the server advertises and the
103105
client requests the `no-ref-delta` promise. Each URI pack is then checked
104106
with `index-pack --no-ref-delta` before it is accepted.
105107
Responses with one URI retain the serial path.
108+
+
109+
When `pack.threads` is unset or zero, each parallel URI indexer uses the
110+
number of online CPUs divided by the actual number of jobs, rounded down,
111+
with a minimum of 1 thread and a maximum of 4. A positive `pack.threads`
112+
value overrides this per-indexer default, including values greater than 4.
113+
Increasing both settings can increase CPU and memory use substantially.
106114

107115
`fetch.writeCommitGraph`::
108116
Set to true to write a commit-graph after every `git fetch` command

Documentation/config/pack.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ pack.threads::
8181
is however multiplied by the number of threads.
8282
Specifying 0 will cause Git to auto-detect the number of CPUs
8383
and set the number of threads accordingly.
84+
+
85+
This also sets the number of threads used to resolve deltas in
86+
linkgit:git-index-pack[1]. Parallel packfile URI indexers share the online
87+
CPUs when this is unset or zero; see `fetch.packfileUriJobs` for their
88+
automatic limits. A positive value applies to each indexer independently.
8489

8590
pack.indexVersion::
8691
Specify the default pack index version. Valid values are 1 for

fetch-pack.c

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "mergesort.h"
3838
#include "prio-queue.h"
3939
#include "promisor-remote.h"
40+
#include "thread-utils.h"
4041

4142
static int transfer_unpack_limit = -1;
4243
static int fetch_unpack_limit = -1;
@@ -49,7 +50,7 @@ static int fetch_fsck_objects = -1;
4950
static int transfer_fsck_objects = -1;
5051
static int agent_supported;
5152
static int server_supports_filtering;
52-
static int fetch_packfile_uri_jobs = 1;
53+
static int fetch_packfile_uri_jobs;
5354
static struct shallow_lock shallow_lock;
5455
static const char *alternate_shallow_file;
5556
static struct strbuf fsck_msg_types = STRBUF_INIT;
@@ -1778,10 +1779,23 @@ static void fetch_packfile_uris_parallel(
17781779
struct packfile_uri_task *tasks;
17791780
struct pollfd *pollfds;
17801781
int precreate_keeps = index_pack_args_have_keep(index_pack_args);
1782+
int threads = 0;
17811783
size_t next = 0, running = 0;
17821784

17831785
if (task_nr > (size_t)fetch_packfile_uri_jobs)
17841786
task_nr = fetch_packfile_uri_jobs;
1787+
1788+
/* Share CPUs across active packs unless the caller chose a thread count. */
1789+
repo_config_get_int(the_repository, "pack.threads", &threads);
1790+
if (!threads) {
1791+
threads = online_cpus() / (int)task_nr;
1792+
if (threads < 1)
1793+
threads = 1;
1794+
else if (threads > 4)
1795+
threads = 4;
1796+
}
1797+
strvec_pushf(index_pack_args, "--threads=%d", threads);
1798+
17851799
CALLOC_ARRAY(tasks, task_nr);
17861800
CALLOC_ARRAY(pollfds, task_nr);
17871801

@@ -2036,12 +2050,6 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
20362050
fetch_packfile_uri_jobs > 1 &&
20372051
packfile_uris.nr > 1;
20382052
if (parallel_uri_indexing) {
2039-
/*
2040-
* Bound total indexer threads by the number of URI jobs. The
2041-
* URI packs are independent, so each indexer can stay single
2042-
* threaded while several indexers run at once.
2043-
*/
2044-
strvec_push(&index_pack_args, "--threads=1");
20452053
fetch_packfile_uris_parallel(&packfile_uris, &index_pack_args,
20462054
pack_lockfiles,
20472055
&fsck_options.gitmodules_found);
@@ -2166,6 +2174,9 @@ static int fetch_pack_config_cb(const char *var, const char *value,
21662174

21672175
static void fetch_pack_config(void)
21682176
{
2177+
fetch_packfile_uri_jobs = online_cpus();
2178+
if (fetch_packfile_uri_jobs > 8)
2179+
fetch_packfile_uri_jobs = 8;
21692180
repo_config_get_int(the_repository, "fetch.unpacklimit", &fetch_unpack_limit);
21702181
repo_config_get_int(the_repository, "transfer.unpacklimit", &transfer_unpack_limit);
21712182
repo_config_get_bool(the_repository, "repack.usedeltabaseoffset", &prefer_ofs_delta);

t/t5702-protocol-v2.sh

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1321,6 +1321,7 @@ test_expect_success 'no-ref-delta URI packs are indexed concurrently' '
13211321
GIT_TEST_SIDEBAND_ALL=1 \
13221322
git -c protocol.version=2 -c fetch.uriprotocols=http \
13231323
-c fetch.packfileUriJobs=2 \
1324+
-c pack.threads=1 \
13241325
clone "$HTTPD_URL/smart/http_parent" http_child-no-ref &&
13251326
13261327
test_grep "> no-ref-delta" no-ref-packet.trace &&
@@ -1332,6 +1333,176 @@ test_expect_success 'no-ref-delta URI packs are indexed concurrently' '
13321333
test_line_count = 3 no-ref-uri-indexers
13331334
'
13341335

1336+
test_expect_success 'setup packfile URI worker tuning' '
1337+
P="$HTTPD_DOCUMENT_ROOT_PATH/http_tuning" &&
1338+
git init "$P" &&
1339+
git -C "$P" config uploadpack.allowsidebandall true &&
1340+
git -C "$P" config uploadpack.allowNoRefDelta true &&
1341+
for i in $(test_seq 10)
1342+
do
1343+
echo "$i" >"$P/blob-$i" &&
1344+
git -C "$P" add "blob-$i" || return 1
1345+
done &&
1346+
git -C "$P" commit -m objects &&
1347+
for i in $(test_seq 10)
1348+
do
1349+
configure_exclusion "$P" "blob-$i" >/dev/null || return 1
1350+
done &&
1351+
uri_cpus=$(test-tool online-cpus)
1352+
'
1353+
1354+
clone_uri_tuning () {
1355+
local repository="$1" destination="$2" &&
1356+
shift 2 &&
1357+
GIT_TRACE2_EVENT="$TRASH_DIRECTORY/$destination.trace" \
1358+
GIT_TRACE_PACKET="$TRASH_DIRECTORY/$destination.packet" \
1359+
GIT_TEST_SIDEBAND_ALL=1 \
1360+
git -c protocol.version=2 -c fetch.uriprotocols=http "$@" \
1361+
clone "$HTTPD_URL/smart/$repository" "$destination" &&
1362+
git -C "$destination" fsck --no-reflogs
1363+
}
1364+
1365+
# Count outstanding URI children in their parent's Trace2 events. Indexers
1366+
# have their own child IDs, so pair each ID with its parent's session ID.
1367+
test_uri_fetch_jobs () {
1368+
awk -v expected="$2" '
1369+
function child_key( fields, sid, id) {
1370+
split($0, fields, "\"sid\":\"");
1371+
split(fields[2], sid, "\"");
1372+
split($0, fields, "\"child_id\":");
1373+
id = fields[2] + 0;
1374+
return sid[1] ":" id;
1375+
}
1376+
/"event":"child_start".*"http-fetch","--packfile=/ {
1377+
active[child_key()] = 1;
1378+
running++;
1379+
if (running > maximum)
1380+
maximum = running;
1381+
}
1382+
/"event":"child_exit"/ {
1383+
key = child_key();
1384+
if (active[key]) {
1385+
delete active[key];
1386+
running--;
1387+
}
1388+
}
1389+
END {
1390+
if (running != 0 || maximum != expected) {
1391+
printf "URI workers: maximum %d, expected %d, outstanding %d\n",
1392+
maximum, expected, running;
1393+
exit 1;
1394+
}
1395+
}
1396+
' "$1"
1397+
}
1398+
1399+
test_expect_success 'packfile URI defaults bound workers and indexer threads' '
1400+
jobs=$uri_cpus &&
1401+
if test "$jobs" -gt 8
1402+
then
1403+
jobs=8
1404+
fi &&
1405+
clone_uri_tuning http_tuning uri-default &&
1406+
test_uri_fetch_jobs uri-default.trace "$jobs" &&
1407+
if test "$jobs" -gt 1
1408+
then
1409+
threads=$((uri_cpus / jobs)) &&
1410+
if test "$threads" -gt 4
1411+
then
1412+
threads=4
1413+
fi &&
1414+
test_grep "> no-ref-delta" uri-default.packet &&
1415+
grep "\"event\":\"child_start\".*\"index-pack\".*--no-ref-delta.*--threads=$threads\"" \
1416+
uri-default.trace >uri-default.indexers &&
1417+
test_line_count = 10 uri-default.indexers
1418+
else
1419+
test_grep ! "> no-ref-delta" uri-default.packet
1420+
fi
1421+
'
1422+
1423+
for threads in 1 2 4 8
1424+
do
1425+
test_expect_success "parallel URI indexers honor pack.threads=$threads" '
1426+
clone_uri_tuning http_tuning uri-threads-$threads \
1427+
-c fetch.packfileUriJobs=2 -c pack.threads=$threads &&
1428+
test_uri_fetch_jobs uri-threads-$threads.trace 2 &&
1429+
grep "\"event\":\"child_start\".*\"index-pack\".*--no-ref-delta.*--threads=$threads\"" \
1430+
uri-threads-$threads.trace >uri-indexers &&
1431+
test_line_count = 10 uri-indexers
1432+
'
1433+
done
1434+
1435+
test_expect_success 'automatic URI threads use the actual worker count' '
1436+
# More requested jobs than URI packs must not dilute the CPU budget.
1437+
threads=$((uri_cpus / 3)) &&
1438+
if test "$threads" -lt 1
1439+
then
1440+
threads=1
1441+
elif test "$threads" -gt 4
1442+
then
1443+
threads=4
1444+
fi &&
1445+
clone_uri_tuning http_parent uri-auto-threads \
1446+
-c fetch.packfileUriJobs=100 -c pack.threads=0 &&
1447+
test_uri_fetch_jobs uri-auto-threads.trace 3 &&
1448+
grep "\"event\":\"child_start\".*\"index-pack\".*--no-ref-delta.*--threads=$threads\"" \
1449+
uri-auto-threads.trace >uri-indexers &&
1450+
test_line_count = 3 uri-indexers
1451+
'
1452+
1453+
test_expect_success 'explicit URI jobs can exceed the automatic limit' '
1454+
clone_uri_tuning http_tuning uri-many-jobs \
1455+
-c fetch.packfileUriJobs=100 -c pack.threads=1 &&
1456+
test_uri_fetch_jobs uri-many-jobs.trace 10
1457+
'
1458+
1459+
test_expect_success 'fetch honors packfile URI worker tuning' '
1460+
git init uri-fetch &&
1461+
GIT_TRACE2_EVENT="$TRASH_DIRECTORY/uri-fetch.trace" \
1462+
GIT_TEST_SIDEBAND_ALL=1 \
1463+
git -C uri-fetch -c protocol.version=2 -c fetch.uriprotocols=http \
1464+
-c fetch.packfileUriJobs=2 -c pack.threads=2 \
1465+
fetch "$HTTPD_URL/smart/http_tuning" &&
1466+
test_uri_fetch_jobs uri-fetch.trace 2 &&
1467+
grep "\"event\":\"child_start\".*\"index-pack\".*--no-ref-delta.*--threads=2\"" \
1468+
uri-fetch.trace >uri-indexers &&
1469+
test_line_count = 10 uri-indexers &&
1470+
git -C uri-fetch fsck --no-reflogs
1471+
'
1472+
1473+
for jobs in 0 1 -1
1474+
do
1475+
test_expect_success "fetch.packfileUriJobs=$jobs retains serial indexing" '
1476+
clone_uri_tuning http_tuning uri-serial-$jobs \
1477+
-c fetch.packfileUriJobs=$jobs -c pack.threads=2 &&
1478+
test_uri_fetch_jobs uri-serial-$jobs.trace 1 &&
1479+
test_grep ! "> no-ref-delta" uri-serial-$jobs.packet &&
1480+
test_grep ! -e "--threads=" uri-serial-$jobs.trace
1481+
'
1482+
done
1483+
1484+
test_expect_success 'parallel URI defaults require the server promise' '
1485+
test_config -C "$P" uploadpack.allowNoRefDelta false &&
1486+
clone_uri_tuning http_tuning uri-no-promise &&
1487+
test_uri_fetch_jobs uri-no-promise.trace 1 &&
1488+
test_grep ! "> no-ref-delta" uri-no-promise.packet &&
1489+
test_grep ! -e "--threads=" uri-no-promise.trace
1490+
'
1491+
1492+
test_expect_success 'single URI retains ordinary indexer thread selection' '
1493+
P="$HTTPD_DOCUMENT_ROOT_PATH/http_single_uri" &&
1494+
git init "$P" &&
1495+
git -C "$P" config uploadpack.allowsidebandall true &&
1496+
git -C "$P" config uploadpack.allowNoRefDelta true &&
1497+
test_commit -C "$P" one &&
1498+
configure_exclusion "$P" one.t >/dev/null &&
1499+
clone_uri_tuning http_single_uri uri-single \
1500+
-c fetch.packfileUriJobs=8 -c pack.threads=2 &&
1501+
test_uri_fetch_jobs uri-single.trace 1 &&
1502+
test_grep "> no-ref-delta" uri-single.packet &&
1503+
test_grep ! -e "--threads=" uri-single.trace
1504+
'
1505+
13351506
test_expect_success 'packfile URIs with fetch instead of clone' '
13361507
P="$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
13371508
rm -rf "$P" http_child log &&

0 commit comments

Comments
 (0)