Skip to content

Commit d7fcbe2

Browse files
pks-tgitster
authored andcommitted
object-file: retry linking file into place when occluding file vanishes
Prior to 0ad3d65 (object-file: fix race in object collision check, 2024-12-30), callers could expect that a successful return from `finalize_object_file()` means that either the file was moved into place, or the identical bytes were already present. If neither of those happens, we'd return an error. Since that commit, if the destination file disappears between our link(3p) call and the collision check, we'd return success without actually checking the contents, and without retrying the link. This solves the common case that the files were indeed the same, but it means that we may corrupt the repository if they weren't (this implies a hash collision, but the whole point of this function is protecting against hash collisions). We can't be pessimistic and assume they're different; that hurts the common case that the mentioned commit was trying to fix. But after seeing that the destination file went away, we can retry linking again. Adapt the code to do so when we see that the destination file has racily vanished. This should generally succeed as we have just observed that the destination file does not exist anymore, except in the very unlikely event that it gets recreated by another concurrent process again. Helped-by: Jeff King <peff@peff.net> Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent cfae50e commit d7fcbe2

File tree

1 file changed

+21
-4
lines changed

1 file changed

+21
-4
lines changed

object-file.c

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1974,6 +1974,8 @@ static void write_object_file_prepare_literally(const struct git_hash_algo *algo
19741974
hash_object_body(algo, &c, buf, len, oid, hdr, hdrlen);
19751975
}
19761976

1977+
#define CHECK_COLLISION_DEST_VANISHED -2
1978+
19771979
static int check_collision(const char *source, const char *dest)
19781980
{
19791981
char buf_source[4096], buf_dest[4096];
@@ -1990,6 +1992,8 @@ static int check_collision(const char *source, const char *dest)
19901992
if (fd_dest < 0) {
19911993
if (errno != ENOENT)
19921994
ret = error_errno(_("unable to open %s"), dest);
1995+
else
1996+
ret = CHECK_COLLISION_DEST_VANISHED;
19931997
goto out;
19941998
}
19951999

@@ -2037,8 +2041,11 @@ int finalize_object_file(const char *tmpfile, const char *filename)
20372041
int finalize_object_file_flags(const char *tmpfile, const char *filename,
20382042
enum finalize_object_file_flags flags)
20392043
{
2040-
struct stat st;
2041-
int ret = 0;
2044+
unsigned retries = 0;
2045+
int ret;
2046+
2047+
retry:
2048+
ret = 0;
20422049

20432050
if (object_creation_mode == OBJECT_CREATION_USES_RENAMES)
20442051
goto try_rename;
@@ -2059,6 +2066,8 @@ int finalize_object_file_flags(const char *tmpfile, const char *filename,
20592066
* left to unlink.
20602067
*/
20612068
if (ret && ret != EEXIST) {
2069+
struct stat st;
2070+
20622071
try_rename:
20632072
if (!stat(filename, &st))
20642073
ret = EEXIST;
@@ -2074,9 +2083,17 @@ int finalize_object_file_flags(const char *tmpfile, const char *filename,
20742083
errno = saved_errno;
20752084
return error_errno(_("unable to write file %s"), filename);
20762085
}
2077-
if (!(flags & FOF_SKIP_COLLISION_CHECK) &&
2078-
check_collision(tmpfile, filename))
2086+
if (!(flags & FOF_SKIP_COLLISION_CHECK)) {
2087+
ret = check_collision(tmpfile, filename);
2088+
if (ret == CHECK_COLLISION_DEST_VANISHED) {
2089+
if (retries++ > 5)
2090+
return error(_("unable to write repeatedly vanishing file %s"),
2091+
filename);
2092+
goto retry;
2093+
}
2094+
else if (ret)
20792095
return -1;
2096+
}
20802097
unlink_or_warn(tmpfile);
20812098
}
20822099

0 commit comments

Comments
 (0)