object-file: fix packfile flush during transaction commit
cover object-file: fix packfile flush during transaction commit
Greetings,
This short series fixes a bug I found related to committing an ODB transaction that contains both a loose object and "large" blob when also configured to batch fsync loose objects. The issue can be reproduced with the following:
git init
git config core.fsync loose-object
git config core.fsyncMethod batch
git config core.bigFileThreshold 5
echo foo >1-foo && echo foobar >2-foobar
git add 1-foo 2-foobar
and produces the following error:
error: unable to write file .git/objects/pack/pack-2b7c2470289822070687e8d64186093a710eaed3.pack: No such file or directory fatal: unable to rename temporary file to '.git/objects/pack/pack-2b7c2470289822070687e8d64186093a710eaed3.pack'
If a "large" blob packfile is written to the transaction temporary directory, it is unable to be flushed during transaction commit because the underlying transaction is migrated to the main ODB before the packfile is finalized. To avoid this, this series ensures any pending packfile in the transaction is flushed first.
Thanks,
-Justin
Justin Tobler (2):
object-file: lift ODB reprepare out of packfile flush
object-file: flush transaction packfile before migrating objects
object-file.c | 12 ++++++++---- t/t1050-large.sh | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-)
base-commit: 47ce80527c56f462cb97db4ca8125342204d3783
Signature · 1 line
2.55.0
PATCH 1/2 object-file: lift ODB reprepare out of packfile flush
When flushing a packfile via flush_packfile_transaction(), odb_reprepare() is invoked so the written packfile becomes visible in the current process. In a subsequent commit, repreparing the ODB is slightly deferred when committing a "files" ODB transaction.
Lift ODB reprepare out of flush_packfile_transaction() and instead require callers to explicitly invoke odb_reprepare() if required.
--- object-file.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-)
object-file.c +7 -3
@@ -857,8 +857,6 @@ static void flush_packfile_transaction(struct odb_transaction_files *transactionmemset (state,0 ,sizeof (*state));strbuf_release (&packname); -/* Make objects we just wrote available to ourselves */ -odb_reprepare (repo->objects); }/*
@@ -909,8 +907,10 @@ static int odb_transaction_files_write_object_stream(struct odb_transaction *bas* to zlib compression and is sufficient for this check. */ if (state->nr_written && pack_size_limit_cfg && - pack_size_limit_cfg < state->offset + stream->size) + pack_size_limit_cfg < state->offset + stream->size) {flush_packfile_transaction (transaction); +odb_reprepare (transaction->base.source->odb); + }CALLOC_ARRAY (idx,1 );prepare_packfile_transaction (transaction);
@@ -1260,6 +1260,7 @@ static int odb_transaction_files_commit(struct odb_transaction *base) {struct odb_transaction_files *transaction = container_of(base, struct odb_transaction_files, base); +int have_packfile = !!transaction->packfile.f;if (transaction->objdir) {struct strbuf temp_path =STRBUF_INIT ;
@@ -1293,6 +1294,9 @@ static int odb_transaction_files_commit(struct odb_transaction *base)flush_packfile_transaction (transaction); +if (have_packfile) +odb_reprepare (transaction->base.source->odb); +return 0 ; }
Signature · 1 line
2.55.0
Re: [PATCH 1/2] object-file: lift ODB reprepare out of packfile flush
Quoted 47 lines · Justin Tobler
When flushing a packfile via
flush_packfile_transaction(),odb_reprepare()is invoked so the written packfile becomes visible in the current process. In a subsequent commit, repreparing the ODB is slightly deferred when committing a "files" ODB transaction.Lift ODB reprepare out of
flush_packfile_transaction()and instead require callers to explicitly invokeodb_reprepare()if required.Signed-off-by Justin Tobler <[email protected]>--- object-file.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-)object-file.c +4
-3@@ -857,8 +857,6 @@ static void flush_packfile_transaction(struct odb_transaction_files *transactionmemset (state,0 ,sizeof (*state));strbuf_release (&packname); -/* Make objects we just wrote available to ourselves */ -odb_reprepare (repo->objects); }/* @@ -909,8 +907,10 @@ static int odb_transaction_files_write_object_stream(struct odb_transaction *bas* to zlib compression and is sufficient for this check. */ if (state->nr_written && pack_size_limit_cfg && - pack_size_limit_cfg < state->offset + stream->size) + pack_size_limit_cfg < state->offset + stream->size) {flush_packfile_transaction (transaction); +odb_reprepare (transaction->base.source->odb); + }CALLOC_ARRAY (idx,1 );prepare_packfile_transaction (transaction);@@ -1260,6 +1260,7 @@ static int odb_transaction_files_commit(struct odb_transaction *base) {struct odb_transaction_files *transaction = container_of(base, struct odb_transaction_files, base); +int have_packfile = !!transaction->packfile.f;if (transaction->objdir) {struct strbuf temp_path =STRBUF_INIT ;
object-file.c static int odb_transaction_files_commit(struct odb_transaction *base) · line 1296
Quoted 3 lines
object-file.c +0
-0@@ -1293,6 +1294,9 @@ static int odb_transaction_files_commit(struct odb_transaction *base)flush_packfile_transaction (transaction);
Earlier this would unconditionally call odb_reprepare() within, now we only call if needed. Which makes sense. Would it also make sense to only call flush_packfile_transaction(transaction) if we have a packfile?
Quoted 8 lines
object-file.c +3
-1+if (have_packfile) +odb_reprepare (transaction->base.source->odb); +return 0 ; } --2.55.0
Re: [PATCH 1/2] object-file: lift ODB reprepare out of packfile flush
Quoted 11 lines · Karthik Nayak
Quoted 10 lines · Justin Tobler
object-file.c +1
-0@@ -1260,6 +1260,7 @@ static int odb_transaction_files_commit(struct odb_transaction *base) {struct odb_transaction_files *transaction = container_of(base, struct odb_transaction_files, base); +int have_packfile = !!transaction->packfile.f;if (transaction->objdir) {struct strbuf temp_path =STRBUF_INIT ;@@ -1293,6 +1294,9 @@ static int odb_transaction_files_commit(struct odb_transaction *base)
Quoted 6 lines
Review static int odb_transaction_files_commit(struct odb_transaction *base) · line 1296
Quoted 2 lines
(quoted diff) +0
-0flush_packfile_transaction(transaction);Earlier this would unconditionally call
odb_reprepare()within, now we only call if needed. Which makes sense. Would it also make sense to only callflush_packfile_transaction(transaction)if we have a packfile?
flush_packfile_transaction() already returns early if there is nothing to do. We could make it more explicit here, but I think it is probably fine to leave it as-is.
-Justin
Re: [PATCH 1/2] object-file: lift ODB reprepare out of packfile flush
In reply to Justin ToblerQuoted 25 lines · Justin Tobler
object-file.c +4
-1@@ -909,8 +907,10 @@ static int odb_transaction_files_write_object_stream(struct odb_transaction *bas* to zlib compression and is sufficient for this check. */ if (state->nr_written && pack_size_limit_cfg && - pack_size_limit_cfg < state->offset + stream->size) + pack_size_limit_cfg < state->offset + stream->size) {flush_packfile_transaction (transaction); +odb_reprepare (transaction->base.source->odb); + }CALLOC_ARRAY (idx,1 );prepare_packfile_transaction (transaction);@@ -1260,6 +1260,7 @@ static int odb_transaction_files_commit(struct odb_transaction *base) {struct odb_transaction_files *transaction = container_of(base, struct odb_transaction_files, base); +int have_packfile = !!transaction->packfile.f;if (transaction->objdir) {struct strbuf temp_path =STRBUF_INIT ;
object-file.c static int odb_transaction_files_commit(struct odb_transaction *base) · line 1301
Quoted 8 lines
object-file.c +3
-0@@ -1293,6 +1294,9 @@ static int odb_transaction_files_commit(struct odb_transaction *base)flush_packfile_transaction (transaction); +if (have_packfile) +odb_reprepare (transaction->base.source->odb); +return 0 ; }
One thing that I'm curious about: we don't have any error checking for flushing the object directory at alll. So there is actually a change in behaviour here, where we now also reprepare in case flushing has failed. It probably doesn't matter much, but it does raise the question whether we may want to start checking for errors.
Patrick
Re: [PATCH 1/2] object-file: lift ODB reprepare out of packfile flush
Quoted 29 lines · Patrick Steinhardt
Quoted 28 lines · Justin Tobler
object-file.c +4
-1@@ -909,8 +907,10 @@ static int odb_transaction_files_write_object_stream(struct odb_transaction *bas* to zlib compression and is sufficient for this check. */ if (state->nr_written && pack_size_limit_cfg && - pack_size_limit_cfg < state->offset + stream->size) + pack_size_limit_cfg < state->offset + stream->size) {flush_packfile_transaction (transaction); +odb_reprepare (transaction->base.source->odb); + }CALLOC_ARRAY (idx,1 );prepare_packfile_transaction (transaction);@@ -1260,6 +1260,7 @@ static int odb_transaction_files_commit(struct odb_transaction *base) {struct odb_transaction_files *transaction = container_of(base, struct odb_transaction_files, base); +int have_packfile = !!transaction->packfile.f;if (transaction->objdir) {struct strbuf temp_path =STRBUF_INIT ;@@ -1293,6 +1294,9 @@ static int odb_transaction_files_commit(struct odb_transaction *base)flush_packfile_transaction (transaction);
Quoted 11 lines
object-file.c static int odb_transaction_files_commit(struct odb_transaction *base) · line 1301
Quoted 5 lines
object-file.c +3
-0+if (have_packfile) +odb_reprepare (transaction->base.source->odb); +return 0 ; }One thing that I'm curious about: we don't have any error checking for flushing the object directory at alll. So there is actually a change in behaviour here, where we now also reprepare in case flushing has failed. It probably doesn't matter much, but it does raise the question whether we may want to start checking for errors.
Regarding the behavior change, I'm not entirely sure I follow. flush_packfile_transaction() only returns early in the case where there is nothing to flush. In both of the above call sites, odb_reprepare() is only invoked in the same circumstance.
I do agree with the sentiment that error handling could be improve here as most errors are simply handled by die()'ing in place. I'll probably defer doing that as part of this series though.
Thanks,
-Justin
Re: [PATCH 1/2] object-file: lift ODB reprepare out of packfile flush
Quoted 42 lines · Justin Tobler
Quoted 29 lines · Patrick Steinhardt
Quoted 28 lines · Justin Tobler
object-file.c +4
-1@@ -909,8 +907,10 @@ static int odb_transaction_files_write_object_stream(struct odb_transaction *bas* to zlib compression and is sufficient for this check. */ if (state->nr_written && pack_size_limit_cfg && - pack_size_limit_cfg < state->offset + stream->size) + pack_size_limit_cfg < state->offset + stream->size) {flush_packfile_transaction (transaction); +odb_reprepare (transaction->base.source->odb); + }CALLOC_ARRAY (idx,1 );prepare_packfile_transaction (transaction);@@ -1260,6 +1260,7 @@ static int odb_transaction_files_commit(struct odb_transaction *base) {struct odb_transaction_files *transaction = container_of(base, struct odb_transaction_files, base); +int have_packfile = !!transaction->packfile.f;if (transaction->objdir) {struct strbuf temp_path =STRBUF_INIT ;@@ -1293,6 +1294,9 @@ static int odb_transaction_files_commit(struct odb_transaction *base)flush_packfile_transaction (transaction);Quoted 11 lines
object-file.c static int odb_transaction_files_commit(struct odb_transaction *base) · line 1301
Quoted 5 lines
object-file.c +3
-0+if (have_packfile) +odb_reprepare (transaction->base.source->odb); +return 0 ; }One thing that I'm curious about: we don't have any error checking for flushing the object directory at alll. So there is actually a change in behaviour here, where we now also reprepare in case flushing has failed. It probably doesn't matter much, but it does raise the question whether we may want to start checking for errors.
Quoted 4 lines
Regarding the behavior change, I'm not entirely sure I follow.
flush_packfile_transaction()only returns early in the case where there is nothing to flush. In both of the above call sites,odb_reprepare()is only invoked in the same circumstance.
There's a second early return when tmp_objdir_migrate() fails, and that early return causes us to not flush.
Patrick
Re: [PATCH 1/2] object-file: lift ODB reprepare out of packfile flush
Quoted 43 lines · Patrick Steinhardt
Quoted 42 lines · Justin Tobler
Quoted 40 lines · Patrick Steinhardt
Quoted 25 lines · Justin Tobler
object-file.c +4
-1@@ -909,8 +907,10 @@ static int odb_transaction_files_write_object_stream(struct odb_transaction *bas* to zlib compression and is sufficient for this check. */ if (state->nr_written && pack_size_limit_cfg && - pack_size_limit_cfg < state->offset + stream->size) + pack_size_limit_cfg < state->offset + stream->size) {flush_packfile_transaction (transaction); +odb_reprepare (transaction->base.source->odb); + }CALLOC_ARRAY (idx,1 );prepare_packfile_transaction (transaction);@@ -1260,6 +1260,7 @@ static int odb_transaction_files_commit(struct odb_transaction *base) {struct odb_transaction_files *transaction = container_of(base, struct odb_transaction_files, base); +int have_packfile = !!transaction->packfile.f;if (transaction->objdir) {struct strbuf temp_path =STRBUF_INIT ;object-file.c static int odb_transaction_files_commit(struct odb_transaction *base) · line 1301
Quoted 8 lines
object-file.c +3
-0@@ -1293,6 +1294,9 @@ static int odb_transaction_files_commit(struct odb_transaction *base)flush_packfile_transaction (transaction); +if (have_packfile) +odb_reprepare (transaction->base.source->odb); +return 0 ; }One thing that I'm curious about: we don't have any error checking for flushing the object directory at alll. So there is actually a change in behaviour here, where we now also reprepare in case flushing has failed. It probably doesn't matter much, but it does raise the question whether we may want to start checking for errors.
Quoted 7 lines
Quoted 4 lines
Regarding the behavior change, I'm not entirely sure I follow.
flush_packfile_transaction()only returns early in the case where there is nothing to flush. In both of the above call sites,odb_reprepare()is only invoked in the same circumstance.There's a second early return when
tmp_objdir_migrate()fails, and that early return causes us to not flush.
Oh, never mind. I think I've been confusing the fact that what you're changing is actually odb_transaction_files_commit() itself, and that early return of course still exists in there. So this looks good to me.
Patrick
PATCH 2/2 object-file: flush transaction packfile before migrating objects
In reply to Justin ToblerA "files" ODB transaction creates a temporary directory to stage newly written objects in when configured to batch fsync loose objects. Once the temporary directory is created, it is configured as the primary ODB and all object are written to it accordingly. This also includes packfiles containing blobs that exceed core.bigFileThreshold written via odb_transaction_files_write_object_stream().
If a "large" blob packfile is written to the ODB transaction temporary directory after other loose objects, the ODB transaction fails to commit as a result of the temporary directory being migrated prior to the packfile being flushed. Fix this bug by always flushing the packfile transaction before objects are migrated to the main ODB.
--- object-file.c | 4 ++-- t/t1050-large.sh | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-)
object-file.c +2 -2
@@ -1262,6 +1262,8 @@ static int odb_transaction_files_commit(struct odb_transaction *base) container_of(base, struct odb_transaction_files, base);int have_packfile = !!transaction->packfile.f; +flush_packfile_transaction (transaction); +if (transaction->objdir) {struct strbuf temp_path =STRBUF_INIT ;struct tempfile *temp;
@@ -1292,8 +1294,6 @@ static int odb_transaction_files_commit(struct odb_transaction *base) transaction->objdir =NULL ; } -flush_packfile_transaction (transaction); -if (have_packfile)odb_reprepare (transaction->base.source->odb);
t/t1050-large.sh +16 -0
@@ -87,6 +87,22 @@ test_expect_success 'add a large file or two' ' test $count = 1 ' +test_expect_success 'add large file with loose object in batch fsync' ' + test_when_finished "rm -rf batch" && + git init batch && + + git -C batch config core.bigFileThreshold 5 && + echo foo >batch/1-small && + echo foobar >batch/2-large && + + git -C batch -c core.fsync=loose-object -c core.fsyncMethod=batch \ + add 1-small 2-large && + + # Neither object may be left behind in a temporary location. + git -C batch cat-file -e :1-small && + git -C batch cat-file -e :2-large +' + test_expect_success 'checkout a large file' ' large1=$(git rev-parse :large1) && git update-index --add --cacheinfo 100644 $large1 another &&
Signature · 1 line
2.55.0
Re: [PATCH 2/2] object-file: flush transaction packfile before migrating objects
Quoted 7 lines · Justin Tobler
A "files" ODB transaction creates a temporary directory to stage newly written objects in when configured to batch fsync loose objects. Once the temporary directory is created, it is configured as the primary ODB and all object are written to it accordingly. This also includes packfiles containing blobs that exceed
core.bigFileThresholdwritten viaodb_transaction_files_write_object_stream().
Quoted 5 lines
If a "large" blob packfile is written to the ODB transaction temporary directory after other loose objects, the ODB transaction fails to commit as a result of the temporary directory being migrated prior to the packfile being flushed. Fix this bug by always flushing the packfile transaction before objects are migrated to the main ODB.
Okay this makes sense.
Quoted 34 lines
Signed-off-by Justin Tobler <[email protected]>--- object-file.c | 4 ++-- t/t1050-large.sh | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-)object-file.c +2
-2@@ -1262,6 +1262,8 @@ static int odb_transaction_files_commit(struct odb_transaction *base) container_of(base, struct odb_transaction_files, base);int have_packfile = !!transaction->packfile.f; +flush_packfile_transaction (transaction); +if (transaction->objdir) {struct strbuf temp_path =STRBUF_INIT ;struct tempfile *temp;@@ -1292,8 +1294,6 @@ static int odb_transaction_files_commit(struct odb_transaction *base) transaction->objdir =NULL ; } -flush_packfile_transaction (transaction); -if (have_packfile)odb_reprepare (transaction->base.source->odb);t/t1050-large.sh +0
-0
t/t1050-large.sh test_expect_success 'add a large file or two' ' · line 94
Quoted 8 lines
t/t1050-large.sh +5
-0@@ -87,6 +87,22 @@ test_expect_success 'add a large file or two' ' test $count = 1 ' +test_expect_success 'add large file with loose object in batch fsync' ' + test_when_finished "rm -rf batch" && + git init batch && + + git -C batch config core.bigFileThreshold 5 &&
Nit: we have test_config which automatically unsets after the test. Perhaps not really needed here, as we drop 'batch' anyways.
t/t1050-large.sh test_expect_success 'add a large file or two' '
Quoted 11 lines
t/t1050-large.sh +10
-0+ echo foo >batch/1-small && + echo foobar >batch/2-large && + + git -C batch -c core.fsync=loose-object -c core.fsyncMethod=batch \ + add 1-small 2-large && + + # Neither object may be left behind in a temporary location. + git -C batch cat-file -e :1-small && + git -C batch cat-file -e :2-large +'
Looks good.
Quoted 6 lines
t/t1050-large.sh +1
-0+ test_expect_success 'checkout a large file' ' large1=$(git rev-parse :large1) &&git update-index --add --cacheinfo 100644 $large1 another &&
--
2.55.0
Re: [PATCH 2/2] object-file: flush transaction packfile before migrating objects
Quoted 9 lines · Karthik Nayak
t/t1050-large.sh test_expect_success 'add a large file or two' ' · line 94
Quoted 5 lines · Justin Tobler
t/t1050-large.sh +5
-0+test_expect_success 'add large file with loose object in batch fsync' ' + test_when_finished "rm -rf batch" && + git init batch && + + git -C batch config core.bigFileThreshold 5 &&Nit: we have
test_configwhich automatically unsets after the test. Perhaps not really needed here, as we drop 'batch' anyways.
Ya, since we are deleting the repo here as part of this test, I don't think there is much need to cleanup the config right before deleting. I'll leave it as-is.
Quoted 5 lines
Quoted 5 lines
t/t1050-large.sh +5
-0+ echo foo >batch/1-small && + echo foobar >batch/2-large && + + git -C batch -c core.fsync=loose-object -c core.fsyncMethod=batch \ + add 1-small 2-large &&
Quoted 8 lines
t/t1050-large.sh test_expect_success 'add a large file or two' ' · line 104
Quoted 6 lines
t/t1050-large.sh +5
-0+ + # Neither object may be left behind in a temporary location. + git -C batch cat-file -e :1-small && + git -C batch cat-file -e :2-large +'Looks good.
Thanks for the review,
-Justin
Re: [PATCH 2/2] object-file: flush transaction packfile before migrating objects
In reply to Justin ToblerQuoted 14 lines · Justin Tobler
object-file.c +2
-0@@ -1262,6 +1262,8 @@ static int odb_transaction_files_commit(struct odb_transaction *base) container_of(base, struct odb_transaction_files, base);int have_packfile = !!transaction->packfile.f; +flush_packfile_transaction (transaction); +if (transaction->objdir) {struct strbuf temp_path =STRBUF_INIT ;struct tempfile *temp;
object-file.c static int odb_transaction_files_commit(struct odb_transaction *base) · line 1299
Quoted 8 lines
object-file.c +0
-2@@ -1292,8 +1294,6 @@ static int odb_transaction_files_commit(struct odb_transaction *base) transaction->objdir =NULL ; } -flush_packfile_transaction (transaction); -if (have_packfile)odb_reprepare (transaction->base.source->odb);
In the preceding commit you wrote:
In a subsequent commit, repreparing the ODB is slightly deferred when committing a "files" ODB transaction.
But that's not really true -- you don't delay repreparing the object database, but instead only flush earlier. The reprepare still happens at the same point in time.
t/t1050-large.sh test_expect_success 'add a large file or two' ' · line 92
Quoted 11 lines
t/t1050-large.sh +3
-0@@ -87,6 +87,22 @@ test_expect_success 'add a large file or two' ' test $count = 1 ' +test_expect_success 'add large file with loose object in batch fsync' ' + test_when_finished "rm -rf batch" && + git init batch &&
I feel like using a subshell might've helped here for readability. But, oh well, it saves us an extra process.
t/t1050-large.sh test_expect_success 'add a large file or two' ' · line 101
Quoted 8 lines
t/t1050-large.sh +8
-0+ git -C batch config core.bigFileThreshold 5 && + echo foo >batch/1-small && + echo foobar >batch/2-large && + + git -C batch -c core.fsync=loose-object -c core.fsyncMethod=batch \ + add 1-small 2-large && + + # Neither object may be left behind in a temporary location.
You don't really verify whether they are left behind, but rather verify that the can be read. Which is a bit of a different thing.
Sorry, feels like I'm in a nitpicky mood today :)
Thanks!
Patrick
Re: [PATCH 2/2] object-file: flush transaction packfile before migrating objects
Quoted 26 lines · Patrick Steinhardt
Quoted 14 lines · Justin Tobler
object-file.c +2
-0@@ -1262,6 +1262,8 @@ static int odb_transaction_files_commit(struct odb_transaction *base) container_of(base, struct odb_transaction_files, base);int have_packfile = !!transaction->packfile.f; +flush_packfile_transaction (transaction); +if (transaction->objdir) {struct strbuf temp_path =STRBUF_INIT ;struct tempfile *temp;object-file.c static int odb_transaction_files_commit(struct odb_transaction *base) · line 1299
Quoted 8 lines
object-file.c +0
-2@@ -1292,8 +1294,6 @@ static int odb_transaction_files_commit(struct odb_transaction *base) transaction->objdir =NULL ; } -flush_packfile_transaction (transaction); -if (have_packfile)odb_reprepare (transaction->base.source->odb);In the preceding commit you wrote:
Quoted 6 lines
In a subsequent commit, repreparing the ODB is slightly deferred when committing a "files" ODB transaction.
But that's not really true -- you don't delay repreparing the object database, but instead only flush earlier. The reprepare still happens at the same point in time.
That's fair. When I said "deferred" I really meant that odb_reprepare() was now happening after and outside of flush_packfile_transaction(), but logically it is really in the same place.
I will adapt the commit message accordingly.
Quoted 8 lines
Quoted 8 lines
t/t1050-large.sh +0
-0@@ -87,6 +87,22 @@ test_expect_success 'add a large file or two' ' test $count = 1 '
Quoted 6 lines
t/t1050-large.sh test_expect_success 'add a large file or two' ' · line 92
Quoted 3 lines
t/t1050-large.sh +3
-0+test_expect_success 'add large file with loose object in batch fsync' ' + test_when_finished "rm -rf batch" && + git init batch &&I feel like using a subshell might've helped here for readability. But, oh well, it saves us an extra process.
Ya, using a subshell is probably a bit easier on the eyes. Since I'm making some small changes anyways I'll go ahead and make this change too.
Quoted 11 lines
t/t1050-large.sh test_expect_success 'add a large file or two' ' · line 101
Quoted 8 lines
t/t1050-large.sh +8
-0+ git -C batch config core.bigFileThreshold 5 && + echo foo >batch/1-small && + echo foobar >batch/2-large && + + git -C batch -c core.fsync=loose-object -c core.fsyncMethod=batch \ + add 1-small 2-large && + + # Neither object may be left behind in a temporary location.You don't really verify whether they are left behind, but rather verify that the can be read. Which is a bit of a different thing.
That fair, I'm not sure this comment is really that useful anyways so I'll just go ahead and remove it in the next version.
Quoted 1 line
Sorry, feels like I'm in a nitpicky mood today :)
It is always welcome and appreciated! :)
Thanks,
-Justin
v2 cover object-file: fix packfile flush during transaction commit
In reply to Justin ToblerGreetings,
This short series fixes a bug I found related to committing an ODB transaction that contains both a loose object and "large" blob when also configured to batch fsync loose objects. The issue can be reproduced with the following:
git init
git config core.fsync loose-object
git config core.fsyncMethod batch
git config core.bigFileThreshold 5
echo foo >1-foo && echo foobar >2-foobar
git add 1-foo 2-foobar
and produces the following error:
error: unable to write file .git/objects/pack/pack-2b7c2470289822070687e8d64186093a710eaed3.pack: No such file or directory
fatal: unable to rename temporary file to '.git/objects/pack/pack-2b7c2470289822070687e8d64186093a710eaed3.pack'If a "large" blob packfile is written to the transaction temporary directory, it is unable to be flushed during transaction commit because the underlying transaction is migrated to the main ODB before the packfile is finalized. To avoid this, this series ensures any pending packfile in the transaction is flushed first.
Changes since V1:
- Updated a commit message of first patch.
- Improved test readability in second patch.
Thanks,
-Justin
Justin Tobler (2):
object-file: lift ODB reprepare out of packfile flush
object-file: flush transaction packfile before migrating objects
object-file.c | 12 ++++++++---- t/t1050-large.sh | 17 +++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-)
Range-diff against v1:
1: cf14416f22 ! 1: 6f74391ae8 object-file: lift ODB reprepare out of packfile flush
@@ Commit message
When flushing a packfile via flush_packfile_transaction(), odb_reprepare() is invoked so the written packfile becomes visible in
- the current process. In a subsequent commit, repreparing the ODB is
- slightly deferred when committing a "files" ODB transaction.
+ the current process. In a subsequent commit, flushing the packfile is
+ performed earlier when committing a "files" ODB transaction, but the ODB
+ reprepare needs to remain the last step.
Lift ODB reprepare out of flush_packfile_transaction() and instead require callers to explicitly invoke odb_reprepare() if required.
2: 18a1798d95 ! 2: ad2fa8ee3f object-file: flush transaction packfile before migrating objects
@@ t/t1050-large.sh: test_expect_success 'add a large file or two' '
+test_expect_success 'add large file with loose object in batch fsync' '
+ test_when_finished "rm -rf batch" &&
+ git init batch &&
++ (
++ cd batch &&
++ git config core.bigFileThreshold 5 &&
++ echo foo >1-small &&
++ echo foobar >2-large &&
+
-+ git -C batch config core.bigFileThreshold 5 &&
-+ echo foo >batch/1-small &&
-+ echo foobar >batch/2-large &&
++ git -c core.fsync=loose-object -c core.fsyncMethod=batch \
++ add 1-small 2-large &&
+
-+ git -C batch -c core.fsync=loose-object -c core.fsyncMethod=batch \
-+ add 1-small 2-large &&
-+
-+ # Neither object may be left behind in a temporary location.
-+ git -C batch cat-file -e :1-small &&
-+ git -C batch cat-file -e :2-large
++ git cat-file -e :1-small &&
++ git cat-file -e :2-large
++ )
+'
+
test_expect_success 'checkout a large file' 'base-commit: 47ce80527c56f462cb97db4ca8125342204d3783
Signature · 1 line
2.55.0.424.g13c7afec21
PATCH v2 1/2 object-file: lift ODB reprepare out of packfile flush
When flushing a packfile via flush_packfile_transaction(), odb_reprepare() is invoked so the written packfile becomes visible in the current process. In a subsequent commit, flushing the packfile is performed earlier when committing a "files" ODB transaction, but the ODB reprepare needs to remain the last step.
Lift ODB reprepare out of flush_packfile_transaction() and instead require callers to explicitly invoke odb_reprepare() if required.
--- object-file.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-)
object-file.c +7 -3
@@ -857,8 +857,6 @@ static void flush_packfile_transaction(struct odb_transaction_files *transactionmemset (state,0 ,sizeof (*state));strbuf_release (&packname); -/* Make objects we just wrote available to ourselves */ -odb_reprepare (repo->objects); }/*
@@ -909,8 +907,10 @@ static int odb_transaction_files_write_object_stream(struct odb_transaction *bas* to zlib compression and is sufficient for this check. */ if (state->nr_written && pack_size_limit_cfg && - pack_size_limit_cfg < state->offset + stream->size) + pack_size_limit_cfg < state->offset + stream->size) {flush_packfile_transaction (transaction); +odb_reprepare (transaction->base.source->odb); + }CALLOC_ARRAY (idx,1 );prepare_packfile_transaction (transaction);
@@ -1260,6 +1260,7 @@ static int odb_transaction_files_commit(struct odb_transaction *base) {struct odb_transaction_files *transaction = container_of(base, struct odb_transaction_files, base); +int have_packfile = !!transaction->packfile.f;if (transaction->objdir) {struct strbuf temp_path =STRBUF_INIT ;
@@ -1293,6 +1294,9 @@ static int odb_transaction_files_commit(struct odb_transaction *base)flush_packfile_transaction (transaction); +if (have_packfile) +odb_reprepare (transaction->base.source->odb); +return 0 ; }
Signature · 1 line
2.55.0.424.g13c7afec21
Re: [PATCH v2 1/2] object-file: lift ODB reprepare out of packfile flush
Quoted 5 lines · Justin Tobler
When flushing a packfile via
flush_packfile_transaction(),odb_reprepare()is invoked so the written packfile becomes visible in the current process. In a subsequent commit, flushing the packfile is performed earlier when committing a "files" ODB transaction, but the ODB reprepare needs to remain the last step.
Yup, this is more in line with what that second commit will do.
Patrick
PATCH v2 2/2 object-file: flush transaction packfile before migrating objects
In reply to Justin ToblerA "files" ODB transaction creates a temporary directory to stage newly written objects in when configured to batch fsync loose objects. Once the temporary directory is created, it is configured as the primary ODB and all object are written to it accordingly. This also includes packfiles containing blobs that exceed core.bigFileThreshold written via odb_transaction_files_write_object_stream().
If a "large" blob packfile is written to the ODB transaction temporary directory after other loose objects, the ODB transaction fails to commit as a result of the temporary directory being migrated prior to the packfile being flushed. Fix this bug by always flushing the packfile transaction before objects are migrated to the main ODB.
--- object-file.c | 4 ++-- t/t1050-large.sh | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-)
object-file.c +2 -2
@@ -1262,6 +1262,8 @@ static int odb_transaction_files_commit(struct odb_transaction *base) container_of(base, struct odb_transaction_files, base);int have_packfile = !!transaction->packfile.f; +flush_packfile_transaction (transaction); +if (transaction->objdir) {struct strbuf temp_path =STRBUF_INIT ;struct tempfile *temp;
@@ -1292,8 +1294,6 @@ static int odb_transaction_files_commit(struct odb_transaction *base) transaction->objdir =NULL ; } -flush_packfile_transaction (transaction); -if (have_packfile)odb_reprepare (transaction->base.source->odb);
t/t1050-large.sh +17 -0
@@ -87,6 +87,23 @@ test_expect_success 'add a large file or two' ' test $count = 1 ' +test_expect_success 'add large file with loose object in batch fsync' ' + test_when_finished "rm -rf batch" && + git init batch && + ( + cd batch && + git config core.bigFileThreshold 5 && + echo foo >1-small && + echo foobar >2-large && + + git -c core.fsync=loose-object -c core.fsyncMethod=batch \ + add 1-small 2-large && + + git cat-file -e :1-small && + git cat-file -e :2-large + ) +' + test_expect_success 'checkout a large file' ' large1=$(git rev-parse :large1) && git update-index --add --cacheinfo 100644 $large1 another &&
Signature · 1 line
2.55.0.424.g13c7afec21
Quoted 3 lines · Justin Tobler
Changes since V1:
- Updated a commit message of first patch.
- Improved test readability in second patch.
Thanks, this version looks good to me.
Patrick