]> git.ipfire.org Git - thirdparty/git.git/commitdiff
packfile: explain ordering of how we look up auxiliary pack files
authorPatrick Steinhardt <ps@pks.im>
Wed, 28 May 2025 12:24:10 +0000 (14:24 +0200)
committerJunio C Hamano <gitster@pobox.com>
Wed, 28 May 2025 14:56:29 +0000 (07:56 -0700)
When adding a packfile to an object database we perform four syscalls:

  - Three calls to access(3p) are done to check for auxiliary data
    structures.

  - One call to stat(3p) is done to check for the ".pack" itself.

One curious bit is that we perform the access(3p) calls before checking
for the packfile itself, but if the packfile doesn't exist we discard
all results. The access(3p) calls are thus essentially wasted, so one
may be triggered to reorder those calls so that we can short-circuit the
other syscalls in case the packfile does not exist.

The order in which we look up files is quite important though to help
avoid races:

  - When installing a packfile we move auxiliary data structures into
    place before we install the ".idx" file.

  - When deleting a packfile we first delete the ".idx" and ".pack"
    files before deleting auxiliary data structures.

As such, to avoid any races with concurrently created or deleted packs
we need to make sure that we _first_ read auxiliary data structures
before we read the corresponding ".idx" or ".pack" file. Otherwise it
may easily happen that we return a populated but misclassified pack.

Add a comment to `add_packed_git()` to make future readers aware of this
ordering requirement.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
packfile.c

index d91016f1c7ff401a629def8971b3da819f998909..933036e26062f16771a73d7a1700f316534d286e 100644 (file)
@@ -737,6 +737,17 @@ struct packed_git *add_packed_git(struct repository *r, const char *path,
        p = alloc_packed_git(r, alloc);
        memcpy(p->pack_name, path, path_len);
 
+       /*
+        * Note that we have to check auxiliary data structures before we check
+        * for the ".pack" file to exist to avoid races with a packfile that is
+        * in the process of being deleted. The ".pack" file is unlinked before
+        * its auxiliary data structures, so we know that we either get a
+        * consistent snapshot of all data structures or that we'll fail to
+        * stat(3p) the packfile itself and thus return `NULL`.
+        *
+        * As such, we cannot bail out before the access(3p) calls in case the
+        * packfile doesn't exist without doing two stat(3p) calls for it.
+        */
        xsnprintf(p->pack_name + path_len, alloc - path_len, ".keep");
        if (!access(p->pack_name, F_OK))
                p->pack_keep = 1;