Skip to content

Commit cdacb9b

Browse files
jeffhostetlerderrickstolee
authored andcommitted
gvfs-helper: better support for concurrent packfile fetches
Teach gvfs-helper to better support the concurrent fetching of the same packfile by multiple instances. If 2 instances of gvfs-helper did a POST and requested the same set of OIDs, they might receive the exact same packfile (same checksum SHA). Both processes would then race to install their copy of the .pack and .idx files into the ODB/pack directory. This is not a problem on Unix (because of filesystem semantics). On Windows, this can cause an EBUSY/EPERM problem for the loser while the winner is holding a handle to the target files. (The existing packfile code already handled simple the existence and/or replacement case.) The solution presented here is to silently let the loser claim victory IIF the .pack and .idx are already present in the ODB. (We can't check this in advance because we don't know the packfile SHA checksum until after we receive it and run index-pack.) We avoid using a per-packfile lockfile (or a single lockfile for the `vfs-` prefix) to avoid the usual issues with stale lockfiles. Signed-off-by: Jeff Hostetler <[email protected]>
1 parent 1103486 commit cdacb9b

File tree

2 files changed

+141
-2
lines changed

2 files changed

+141
-2
lines changed

gvfs-helper.c

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1871,12 +1871,36 @@ static void my_finalize_packfile(struct gh__request_params *params,
18711871
struct strbuf *final_path_idx,
18721872
struct strbuf *final_filename)
18731873
{
1874+
/*
1875+
* Install the .pack and .idx into the ODB pack directory.
1876+
*
1877+
* We might be racing with other instances of gvfs-helper if
1878+
* we, in parallel, both downloaded the exact same packfile
1879+
* (with the same checksum SHA) and try to install it at the
1880+
* same time. This might happen on Windows where the loser
1881+
* can get an EBUSY or EPERM trying to move/rename the
1882+
* tempfile into the pack dir, for example.
1883+
*
1884+
* So, we always install the .pack before the .idx for
1885+
* consistency. And only if *WE* created the .pack and .idx
1886+
* files, do we create the matching .keep (when requested).
1887+
*
1888+
* If we get an error and the target files already exist, we
1889+
* silently eat the error. Note that finalize_object_file()
1890+
* has already munged errno (and it has various creation
1891+
* strategies), so we don't bother looking at it.
1892+
*/
18741893
if (finalize_object_file(temp_path_pack->buf, final_path_pack->buf) ||
18751894
finalize_object_file(temp_path_idx->buf, final_path_idx->buf)) {
18761895
unlink(temp_path_pack->buf);
18771896
unlink(temp_path_idx->buf);
1878-
unlink(final_path_pack->buf);
1879-
unlink(final_path_idx->buf);
1897+
1898+
if (file_exists(final_path_pack->buf) &&
1899+
file_exists(final_path_idx->buf)) {
1900+
trace2_printf("%s: assuming ok for %s", TR2_CAT, final_path_pack->buf);
1901+
goto assume_ok;
1902+
}
1903+
18801904
strbuf_addf(&status->error_message,
18811905
"could not install packfile '%s'",
18821906
final_path_pack->buf);
@@ -1899,6 +1923,7 @@ static void my_finalize_packfile(struct gh__request_params *params,
18991923
strbuf_release(&keep);
19001924
}
19011925

1926+
assume_ok:
19021927
if (params->result_list) {
19031928
struct strbuf result_msg = STRBUF_INIT;
19041929

t/t5799-gvfs-helper.sh

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,10 @@ verify_objects_in_shared_cache () {
370370
return 0
371371
}
372372

373+
# gvfs-helper prints a "packfile <path>" message for each received
374+
# packfile to stdout. Verify that we received the expected number
375+
# of packfiles.
376+
#
373377
verify_received_packfile_count () {
374378
if test $# -eq 1
375379
then
@@ -412,6 +416,19 @@ verify_prefetch_keeps () {
412416
return 0
413417
}
414418

419+
# Verify that the number of vfs- packfile present in the shared-cache
420+
# matches our expectations.
421+
#
422+
verify_vfs_packfile_count () {
423+
count=$(( $(ls -1 "$SHARED_CACHE_T1"/pack/vfs-*.pack | wc -l) ))
424+
if test $count -ne $1
425+
then
426+
echo "verify_vfs_packfile_count: expected $1; actual $count"
427+
return 1
428+
fi
429+
return 0
430+
}
431+
415432
per_test_cleanup () {
416433
stop_gvfs_protocol_server
417434

@@ -1174,4 +1191,101 @@ test_expect_success 'integration: fully implicit: diff 2 commits' '
11741191
>OUT.output 2>OUT.stderr
11751192
'
11761193

1194+
#################################################################
1195+
# Duplicate packfile tests.
1196+
#
1197+
# If we request a fixed set of blobs, we should get a unique packfile
1198+
# of the form "vfs-<sha>.{pack,idx}". It we request that same set
1199+
# again, the server should create and send the exact same packfile.
1200+
# True webservers might build the custom packfile in random order,
1201+
# but our test webserver should give us consistent results.
1202+
#
1203+
# Verify that we can handle the duplicate pack and idx file properly.
1204+
#################################################################
1205+
1206+
test_expect_success 'duplicate: vfs- packfile' '
1207+
test_when_finished "per_test_cleanup" &&
1208+
start_gvfs_protocol_server &&
1209+
1210+
git -C "$REPO_T1" gvfs-helper \
1211+
--cache-server=disable \
1212+
--remote=origin \
1213+
--no-progress \
1214+
post \
1215+
<"$OIDS_BLOBS_FILE" >OUT.output 2>OUT.stderr &&
1216+
verify_received_packfile_count 1 &&
1217+
verify_vfs_packfile_count 1 &&
1218+
1219+
# Re-fetch the same packfile. We do not care if it replaces
1220+
# first one or if it silently fails to overwrite the existing
1221+
# one. We just confirm that afterwards we only have 1 packfile.
1222+
#
1223+
git -C "$REPO_T1" gvfs-helper \
1224+
--cache-server=disable \
1225+
--remote=origin \
1226+
--no-progress \
1227+
post \
1228+
<"$OIDS_BLOBS_FILE" >OUT.output 2>OUT.stderr &&
1229+
verify_received_packfile_count 1 &&
1230+
verify_vfs_packfile_count 1 &&
1231+
1232+
stop_gvfs_protocol_server
1233+
'
1234+
1235+
# Return the absolute pathname of the first received packfile.
1236+
#
1237+
first_received_packfile_pathname () {
1238+
fn=$(sed -n '/^packfile/p' <OUT.output | head -1 | sed -n 's/^packfile \(.*\)/\1/p')
1239+
echo "$SHARED_CACHE_T1"/pack/"$fn"
1240+
return 0
1241+
}
1242+
1243+
test_expect_success 'duplicate and busy: vfs- packfile' '
1244+
test_when_finished "per_test_cleanup" &&
1245+
start_gvfs_protocol_server &&
1246+
1247+
git -C "$REPO_T1" gvfs-helper \
1248+
--cache-server=disable \
1249+
--remote=origin \
1250+
--no-progress \
1251+
post \
1252+
<"$OIDS_BLOBS_FILE" \
1253+
>OUT.output \
1254+
2>OUT.stderr &&
1255+
verify_received_packfile_count 1 &&
1256+
verify_vfs_packfile_count 1 &&
1257+
1258+
# Re-fetch the same packfile, but hold the existing packfile
1259+
# open for writing on an obscure (and randomly-chosen) file
1260+
# descriptor.
1261+
#
1262+
# This should cause the replacement-install to fail (at least
1263+
# on Windows) with an EBUSY or EPERM or something.
1264+
#
1265+
# Verify that that error is eaten. We do not care if the
1266+
# replacement is retried or if gvfs-helper simply discards the
1267+
# second instance. We just confirm that afterwards we only
1268+
# have 1 packfile on disk and that the command "lies" and reports
1269+
# that it created the existing packfile. (We want the lie because
1270+
# in normal usage, gh-client has already built the packed-git list
1271+
# in memory and is using gvfs-helper to fetch missing objects;
1272+
# gh-client does not care who does the fetch, but it needs to
1273+
# update its packed-git list and restart the object lookup.)
1274+
#
1275+
PACK=$(first_received_packfile_pathname) &&
1276+
git -C "$REPO_T1" gvfs-helper \
1277+
--cache-server=disable \
1278+
--remote=origin \
1279+
--no-progress \
1280+
post \
1281+
<"$OIDS_BLOBS_FILE" \
1282+
>OUT.output \
1283+
2>OUT.stderr \
1284+
9>>"$PACK" &&
1285+
verify_received_packfile_count 1 &&
1286+
verify_vfs_packfile_count 1 &&
1287+
1288+
stop_gvfs_protocol_server
1289+
'
1290+
11771291
test_done

0 commit comments

Comments
 (0)