Skip to content

Commit a6aca32

Browse files
pks-tdscho
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 cec3ebb commit a6aca32

File tree

1 file changed

+21
-4
lines changed

1 file changed

+21
-4
lines changed

Diff for: object-file.c

+21-4
Original file line numberDiff line numberDiff line change
@@ -1970,6 +1970,8 @@ static void write_object_file_prepare_literally(const struct git_hash_algo *algo
19701970
hash_object_body(algo, &c, buf, len, oid, hdr, hdrlen);
19711971
}
19721972

1973+
#define CHECK_COLLISION_DEST_VANISHED -2
1974+
19731975
static int check_collision(const char *source, const char *dest)
19741976
{
19751977
char buf_source[4096], buf_dest[4096];
@@ -1986,6 +1988,8 @@ static int check_collision(const char *source, const char *dest)
19861988
if (fd_dest < 0) {
19871989
if (errno != ENOENT)
19881990
ret = error_errno(_("unable to open %s"), dest);
1991+
else
1992+
ret = CHECK_COLLISION_DEST_VANISHED;
19891993
goto out;
19901994
}
19911995

@@ -2033,8 +2037,11 @@ int finalize_object_file(const char *tmpfile, const char *filename)
20332037
int finalize_object_file_flags(const char *tmpfile, const char *filename,
20342038
enum finalize_object_file_flags flags)
20352039
{
2036-
struct stat st;
2037-
int ret = 0;
2040+
unsigned retries = 0;
2041+
int ret;
2042+
2043+
retry:
2044+
ret = 0;
20382045

20392046
if (object_creation_mode == OBJECT_CREATION_USES_RENAMES)
20402047
goto try_rename;
@@ -2055,6 +2062,8 @@ int finalize_object_file_flags(const char *tmpfile, const char *filename,
20552062
* left to unlink.
20562063
*/
20572064
if (ret && ret != EEXIST) {
2065+
struct stat st;
2066+
20582067
try_rename:
20592068
if (!stat(filename, &st))
20602069
ret = EEXIST;
@@ -2070,9 +2079,17 @@ int finalize_object_file_flags(const char *tmpfile, const char *filename,
20702079
errno = saved_errno;
20712080
return error_errno(_("unable to write file %s"), filename);
20722081
}
2073-
if (!(flags & FOF_SKIP_COLLISION_CHECK) &&
2074-
check_collision(tmpfile, filename))
2082+
if (!(flags & FOF_SKIP_COLLISION_CHECK)) {
2083+
ret = check_collision(tmpfile, filename);
2084+
if (ret == CHECK_COLLISION_DEST_VANISHED) {
2085+
if (retries++ > 5)
2086+
return error(_("unable to write repeatedly vanishing file %s"),
2087+
filename);
2088+
goto retry;
2089+
}
2090+
else if (ret)
20752091
return -1;
2092+
}
20762093
unlink_or_warn(tmpfile);
20772094
}
20782095

0 commit comments

Comments
 (0)