When we iterate over our packfiles in the fsck code, we do so twice. The first time, we count the number of objects in all of the packs together and later on, we iterate a second time, processing each pack and verifying its integrity.
This would normally work fine, but if we have two packs and we're processing the second, the verification process will open the pack to read from it, which will place it at the beginning of the most recently used list. Since this same list is used for iteration, the pack we most recently processed before this will then be behind the current pack in the linked list, so when we next process the list, we will go back to the first pack again and then loop forever. This also makes our progress indicator loop up to many thousands of percent, which is not only nonsensical, but a clear indication that something has gone wrong.
Solve this by skipping our MRU updates when we're iterating over packfiles, which avoids the reordering that causes problems.
Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net> --- I realize that t1050 may seem like a bizarre place to put this test. However, I was debugging my sha256-interop branch and why the final test calling `git fsck` was failing, so I placed a `git fsck` earlier in the test to double-check and discovered the problem. Since we already have a natural testcase here, I thought I'd just place the test where we already know it will trigger the problem.
packfile.h | 16 ++++++++++++++-- t/t1050-large.sh | 4 ++++ 2 files changed, 18 insertions(+), 2 deletions(-)
Show changes to 2 files +18 −2
packfile.h, t/t1050-large.sh
diff --git a/packfile.h b/packfile.h index acc5c55ad5..086d98c1a0 100644 --- a/packfile.h +++ b/packfile.h @@ -183,6 +183,7 @@ struct packfile_list_entry *packfile_store_get_packs(struct packfile_store *stor struct repo_for_each_pack_data { struct odb_source *source; struct packfile_list_entry *entry; + struct repository *repo; }; static inline struct repo_for_each_pack_data repo_for_eack_pack_data_init(struct repository *repo) @@ -191,8 +192,13 @@ static inline struct repo_for_each_pack_data repo_for_eack_pack_data_init(struct odb_prepare_alternates(repo->objects); + data.repo = repo; + for (struct odb_source *source = repo->objects->sources; source; source = source->next) { - struct packfile_list_entry *entry = packfile_store_get_packs(source->packfiles); + struct packfile_list_entry *entry; + + source->packfiles->skip_mru_updates = true; + entry = packfile_store_get_packs(source->packfiles); if (!entry) continue; data.source = source; @@ -212,7 +218,10 @@ static inline void repo_for_each_pack_data_next(struct repo_for_each_pack_data * return; for (source = data->source->next; source; source = source->next) { - struct packfile_list_entry *entry = packfile_store_get_packs(source->packfiles); + struct packfile_list_entry *entry; + + source->packfiles->skip_mru_updates = true; + entry = packfile_store_get_packs(source->packfiles); if (!entry) continue; data->source = source; @@ -220,6 +229,9 @@ static inline void repo_for_each_pack_data_next(struct repo_for_each_pack_data * return; } + for (struct odb_source *source = data->repo->objects->sources; source; source = source->next) + source->packfiles->skip_mru_updates = false; + data->source = NULL; data->entry = NULL; } diff --git a/t/t1050-large.sh b/t/t1050-large.sh index 5be273611a..75e75e627c 100755 --- a/t/t1050-large.sh +++ b/t/t1050-large.sh @@ -160,6 +160,10 @@ test_expect_success 'hash-object' ' git hash-object large1 ' +test_expect_success 'fsck does not loop forever' ' + git fsck +' + test_expect_success 'cat-file a large file' ' git cat-file blob :large1 >/dev/null '