Skip to content

Commit 32f5baf

Browse files
jeffhostetlerderrickstolee
authored andcommitted
Merge pull request git-for-windows#229 from jeffhostetler/gvfs-helper-lock-pack-dir
gvfs-helper: better support for concurrent packfile fetches
2 parents d4097de + 0469f1d commit 32f5baf

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)