refs/reftable: reload locked stack when preparing transaction

When starting a reftable transaction we lock all stacks we are about to modify. While it may happen that the stack is out-of-date at this point in time we don't really care: transactional updates encode the expected state of a certain reference, so all that we really want to verify is that the _current_ value matches that expected state. Pass `REFTABLE_STACK_NEW_ADDITION_RELOAD` when locking the stack such that an out-of-date stack will be reloaded after having been locked. This change is safe because all verifications of the expected state happen after this step anyway. Add a testcase that verifies that many writers are now able to write to the stack concurrently without failures and with a deterministic end result. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Sep 24, 2024 at 07:33 UTC 6241ce217046cc77d306bda06ad80ac1973b4932
2 files changed +33 -1
refs/reftable-backend.c
+2 -1
@@ -770,7 +770,8 @@ static int prepare_transaction_update(struct write_transaction_table_arg **out,
770 if (ret)
771 return ret;
772
773 - ret = reftable_stack_new_addition(&addition, stack, 0);
773 + ret = reftable_stack_new_addition(&addition, stack,
774 + REFTABLE_STACK_NEW_ADDITION_RELOAD);
775 if (ret) {
776 if (ret == REFTABLE_LOCK_ERROR)
777 strbuf_addstr(err, "cannot lock references");
t/t0610-reftable-basics.sh
+31
@@ -450,6 +450,37 @@ test_expect_success 'ref transaction: retry acquiring tables.list lock' '
450 )
451 '
452
453 +test_expect_success 'ref transaction: many concurrent writers' '
454 + test_when_finished "rm -rf repo" &&
455 + git init repo &&
456 + (
457 + cd repo &&
458 + # Set a high timeout such that a busy CI machine will not abort
459 + # early. 10 seconds should hopefully be ample of time to make
460 + # this non-flaky.
461 + git config set reftable.lockTimeout 10000 &&
462 + test_commit --no-tag initial &&
463 +
464 + head=$(git rev-parse HEAD) &&
465 + for i in $(test_seq 100)
466 + do
467 + printf "%s commit\trefs/heads/branch-%s\n" "$head" "$i" ||
468 + return 1
469 + done >expect &&
470 + printf "%s commit\trefs/heads/main\n" "$head" >>expect &&
471 +
472 + for i in $(test_seq 100)
473 + do
474 + { git update-ref refs/heads/branch-$i HEAD& } ||
475 + return 1
476 + done &&
477 +
478 + wait &&
479 + git for-each-ref --sort=v:refname >actual &&
480 + test_cmp expect actual
481 + )
482 +'
483 +
484 test_expect_success 'pack-refs: compacts tables' '
485 test_when_finished "rm -rf repo" &&
486 git init repo &&