Skip to content

Commit ab68c70

Browse files
committed
Merge branch 'ps/reftable-concurrent-writes'
Give timeout to the locking code to write to reftable. * ps/reftable-concurrent-writes: refs/reftable: reload locked stack when preparing transaction reftable/stack: allow locking of outdated stacks refs/reftable: introduce "reftable.lockTimeout"
2 parents 3857aae + 6241ce2 commit ab68c70

File tree

7 files changed

+189
-19
lines changed

7 files changed

+189
-19
lines changed

Documentation/config/reftable.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,11 @@ reftable.geometricFactor::
4646
By default, the geometric sequence uses a factor of 2, meaning that for any
4747
table, the next-biggest table must at least be twice as big. A maximum factor
4848
of 256 is supported.
49+
50+
reftable.lockTimeout::
51+
Whenever the reftable backend appends a new table to the stack, it has
52+
to lock the central "tables.list" file before updating it. This config
53+
controls how long the process will wait to acquire the lock in case
54+
another process has already acquired it. Value 0 means not to retry at
55+
all; -1 means to try indefinitely. Default is 100 (i.e., retry for
56+
100ms).

refs/reftable-backend.c

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,13 @@ static int reftable_be_config(const char *var, const char *value,
260260
if (factor > UINT8_MAX)
261261
die("reftable geometric factor cannot exceed %u", (unsigned)UINT8_MAX);
262262
opts->auto_compaction_factor = factor;
263+
} else if (!strcmp(var, "reftable.locktimeout")) {
264+
int64_t lock_timeout = git_config_int64(var, value, ctx->kvi);
265+
if (lock_timeout > LONG_MAX)
266+
die("reftable lock timeout cannot exceed %"PRIdMAX, (intmax_t)LONG_MAX);
267+
if (lock_timeout < 0 && lock_timeout != -1)
268+
die("reftable lock timeout does not support negative values other than -1");
269+
opts->lock_timeout_ms = lock_timeout;
263270
}
264271

265272
return 0;
@@ -286,6 +293,7 @@ static struct ref_store *reftable_be_init(struct repository *repo,
286293
refs->write_options.default_permissions = calc_shared_perm(0666 & ~mask);
287294
refs->write_options.disable_auto_compact =
288295
!git_env_bool("GIT_TEST_REFTABLE_AUTOCOMPACTION", 1);
296+
refs->write_options.lock_timeout_ms = 100;
289297

290298
git_config(reftable_be_config, &refs->write_options);
291299

@@ -893,7 +901,8 @@ static int prepare_transaction_update(struct write_transaction_table_arg **out,
893901
if (ret)
894902
return ret;
895903

896-
ret = reftable_stack_new_addition(&addition, stack);
904+
ret = reftable_stack_new_addition(&addition, stack,
905+
REFTABLE_STACK_NEW_ADDITION_RELOAD);
897906
if (ret) {
898907
if (ret == REFTABLE_LOCK_ERROR)
899908
strbuf_addstr(err, "cannot lock references");
@@ -2330,7 +2339,7 @@ static int reftable_be_reflog_expire(struct ref_store *ref_store,
23302339
if (ret < 0)
23312340
goto done;
23322341

2333-
ret = reftable_stack_new_addition(&add, stack);
2342+
ret = reftable_stack_new_addition(&add, stack, 0);
23342343
if (ret < 0)
23352344
goto done;
23362345

reftable/reftable-stack.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,21 @@ uint64_t reftable_stack_next_update_index(struct reftable_stack *st);
3737
/* holds a transaction to add tables at the top of a stack. */
3838
struct reftable_addition;
3939

40+
enum {
41+
/*
42+
* Reload the stack when the stack is out-of-date after locking it.
43+
*/
44+
REFTABLE_STACK_NEW_ADDITION_RELOAD = (1 << 0),
45+
};
46+
4047
/*
4148
* returns a new transaction to add reftables to the given stack. As a side
42-
* effect, the ref database is locked.
49+
* effect, the ref database is locked. Accepts REFTABLE_STACK_NEW_ADDITION_*
50+
* flags.
4351
*/
4452
int reftable_stack_new_addition(struct reftable_addition **dest,
45-
struct reftable_stack *st);
53+
struct reftable_stack *st,
54+
unsigned int flags);
4655

4756
/* Adds a reftable to transaction. */
4857
int reftable_addition_add(struct reftable_addition *add,

reftable/reftable-writer.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,17 @@ struct reftable_write_options {
5151
* tables to compact. Defaults to 2 if unset.
5252
*/
5353
uint8_t auto_compaction_factor;
54+
55+
/*
56+
* The number of milliseconds to wait when trying to lock "tables.list".
57+
* Note that this does not apply to locking individual tables, as these
58+
* should only ever be locked when already holding the "tables.list"
59+
* lock.
60+
*
61+
* Passing 0 will fail immediately when the file is locked, passing a
62+
* negative value will cause us to block indefinitely.
63+
*/
64+
long lock_timeout_ms;
5465
};
5566

5667
/* reftable_block_stats holds statistics for a single block type */

reftable/stack.c

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -596,15 +596,18 @@ struct reftable_addition {
596596
#define REFTABLE_ADDITION_INIT {0}
597597

598598
static int reftable_stack_init_addition(struct reftable_addition *add,
599-
struct reftable_stack *st)
599+
struct reftable_stack *st,
600+
unsigned int flags)
600601
{
601602
struct strbuf lock_file_name = STRBUF_INIT;
602603
int err;
603604

604605
add->stack = st;
605606

606-
err = hold_lock_file_for_update(&add->tables_list_lock, st->list_file,
607-
LOCK_NO_DEREF);
607+
err = hold_lock_file_for_update_timeout(&add->tables_list_lock,
608+
st->list_file,
609+
LOCK_NO_DEREF,
610+
st->opts.lock_timeout_ms);
608611
if (err < 0) {
609612
if (errno == EEXIST) {
610613
err = REFTABLE_LOCK_ERROR;
@@ -624,16 +627,20 @@ static int reftable_stack_init_addition(struct reftable_addition *add,
624627
err = stack_uptodate(st);
625628
if (err < 0)
626629
goto done;
630+
if (err > 0 && flags & REFTABLE_STACK_NEW_ADDITION_RELOAD) {
631+
err = reftable_stack_reload_maybe_reuse(add->stack, 1);
632+
if (err)
633+
goto done;
634+
}
627635
if (err > 0) {
628636
err = REFTABLE_OUTDATED_ERROR;
629637
goto done;
630638
}
631639

632640
add->next_update_index = reftable_stack_next_update_index(st);
633641
done:
634-
if (err) {
642+
if (err)
635643
reftable_addition_close(add);
636-
}
637644
strbuf_release(&lock_file_name);
638645
return err;
639646
}
@@ -737,13 +744,14 @@ int reftable_addition_commit(struct reftable_addition *add)
737744
}
738745

739746
int reftable_stack_new_addition(struct reftable_addition **dest,
740-
struct reftable_stack *st)
747+
struct reftable_stack *st,
748+
unsigned int flags)
741749
{
742750
int err = 0;
743751
struct reftable_addition empty = REFTABLE_ADDITION_INIT;
744752
REFTABLE_CALLOC_ARRAY(*dest, 1);
745753
**dest = empty;
746-
err = reftable_stack_init_addition(*dest, st);
754+
err = reftable_stack_init_addition(*dest, st, flags);
747755
if (err) {
748756
reftable_free(*dest);
749757
*dest = NULL;
@@ -757,7 +765,7 @@ static int stack_try_add(struct reftable_stack *st,
757765
void *arg)
758766
{
759767
struct reftable_addition add = REFTABLE_ADDITION_INIT;
760-
int err = reftable_stack_init_addition(&add, st);
768+
int err = reftable_stack_init_addition(&add, st, 0);
761769
if (err < 0)
762770
goto done;
763771

@@ -1056,8 +1064,10 @@ static int stack_compact_range(struct reftable_stack *st,
10561064
* Hold the lock so that we can read "tables.list" and lock all tables
10571065
* which are part of the user-specified range.
10581066
*/
1059-
err = hold_lock_file_for_update(&tables_list_lock, st->list_file,
1060-
LOCK_NO_DEREF);
1067+
err = hold_lock_file_for_update_timeout(&tables_list_lock,
1068+
st->list_file,
1069+
LOCK_NO_DEREF,
1070+
st->opts.lock_timeout_ms);
10611071
if (err < 0) {
10621072
if (errno == EEXIST)
10631073
err = REFTABLE_LOCK_ERROR;
@@ -1156,8 +1166,10 @@ static int stack_compact_range(struct reftable_stack *st,
11561166
* "tables.list". We'll then replace the compacted range of tables with
11571167
* the new table.
11581168
*/
1159-
err = hold_lock_file_for_update(&tables_list_lock, st->list_file,
1160-
LOCK_NO_DEREF);
1169+
err = hold_lock_file_for_update_timeout(&tables_list_lock,
1170+
st->list_file,
1171+
LOCK_NO_DEREF,
1172+
st->opts.lock_timeout_ms);
11611173
if (err < 0) {
11621174
if (errno == EEXIST)
11631175
err = REFTABLE_LOCK_ERROR;
@@ -1602,7 +1614,7 @@ static int reftable_stack_clean_locked(struct reftable_stack *st)
16021614
int reftable_stack_clean(struct reftable_stack *st)
16031615
{
16041616
struct reftable_addition *add = NULL;
1605-
int err = reftable_stack_new_addition(&add, st);
1617+
int err = reftable_stack_new_addition(&add, st, 0);
16061618
if (err < 0) {
16071619
goto done;
16081620
}

t/t0610-reftable-basics.sh

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,64 @@ test_expect_success 'ref transaction: fails gracefully when auto compaction fail
423423
)
424424
'
425425

426+
test_expect_success 'ref transaction: timeout acquiring tables.list lock' '
427+
test_when_finished "rm -rf repo" &&
428+
git init repo &&
429+
(
430+
cd repo &&
431+
test_commit initial &&
432+
>.git/reftable/tables.list.lock &&
433+
test_must_fail git update-ref refs/heads/branch HEAD 2>err &&
434+
test_grep "cannot lock references" err
435+
)
436+
'
437+
438+
test_expect_success 'ref transaction: retry acquiring tables.list lock' '
439+
test_when_finished "rm -rf repo" &&
440+
git init repo &&
441+
(
442+
cd repo &&
443+
test_commit initial &&
444+
LOCK=.git/reftable/tables.list.lock &&
445+
>$LOCK &&
446+
{
447+
( sleep 1 && rm -f $LOCK ) &
448+
} &&
449+
git -c reftable.lockTimeout=5000 update-ref refs/heads/branch HEAD
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+
426484
test_expect_success 'pack-refs: compacts tables' '
427485
test_when_finished "rm -rf repo" &&
428486
git init repo &&

t/unit-tests/t-reftable-stack.c

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ static void t_reftable_stack_transaction_api(void)
267267

268268
reftable_addition_destroy(add);
269269

270-
err = reftable_stack_new_addition(&add, st);
270+
err = reftable_stack_new_addition(&add, st, 0);
271271
check(!err);
272272

273273
err = reftable_addition_add(add, write_test_ref, &ref);
@@ -288,6 +288,68 @@ static void t_reftable_stack_transaction_api(void)
288288
clear_dir(dir);
289289
}
290290

291+
static void t_reftable_stack_transaction_with_reload(void)
292+
{
293+
char *dir = get_tmp_dir(__LINE__);
294+
struct reftable_stack *st1 = NULL, *st2 = NULL;
295+
int err;
296+
struct reftable_addition *add = NULL;
297+
struct reftable_ref_record refs[2] = {
298+
{
299+
.refname = (char *) "refs/heads/a",
300+
.update_index = 1,
301+
.value_type = REFTABLE_REF_VAL1,
302+
.value.val1 = { '1' },
303+
},
304+
{
305+
.refname = (char *) "refs/heads/b",
306+
.update_index = 2,
307+
.value_type = REFTABLE_REF_VAL1,
308+
.value.val1 = { '1' },
309+
},
310+
};
311+
struct reftable_ref_record ref = { 0 };
312+
313+
err = reftable_new_stack(&st1, dir, NULL);
314+
check(!err);
315+
err = reftable_new_stack(&st2, dir, NULL);
316+
check(!err);
317+
318+
err = reftable_stack_new_addition(&add, st1, 0);
319+
check(!err);
320+
err = reftable_addition_add(add, write_test_ref, &refs[0]);
321+
check(!err);
322+
err = reftable_addition_commit(add);
323+
check(!err);
324+
reftable_addition_destroy(add);
325+
326+
/*
327+
* The second stack is now outdated, which we should notice. We do not
328+
* create the addition and lock the stack by default, but allow the
329+
* reload to happen when REFTABLE_STACK_NEW_ADDITION_RELOAD is set.
330+
*/
331+
err = reftable_stack_new_addition(&add, st2, 0);
332+
check_int(err, ==, REFTABLE_OUTDATED_ERROR);
333+
err = reftable_stack_new_addition(&add, st2, REFTABLE_STACK_NEW_ADDITION_RELOAD);
334+
check(!err);
335+
err = reftable_addition_add(add, write_test_ref, &refs[1]);
336+
check(!err);
337+
err = reftable_addition_commit(add);
338+
check(!err);
339+
reftable_addition_destroy(add);
340+
341+
for (size_t i = 0; i < ARRAY_SIZE(refs); i++) {
342+
err = reftable_stack_read_ref(st2, refs[i].refname, &ref);
343+
check(!err);
344+
check(reftable_ref_record_equal(&refs[i], &ref, GIT_SHA1_RAWSZ));
345+
}
346+
347+
reftable_ref_record_release(&ref);
348+
reftable_stack_destroy(st1);
349+
reftable_stack_destroy(st2);
350+
clear_dir(dir);
351+
}
352+
291353
static void t_reftable_stack_transaction_api_performs_auto_compaction(void)
292354
{
293355
char *dir = get_tmp_dir(__LINE__);
@@ -318,7 +380,7 @@ static void t_reftable_stack_transaction_api_performs_auto_compaction(void)
318380
*/
319381
st->opts.disable_auto_compact = i != n;
320382

321-
err = reftable_stack_new_addition(&add, st);
383+
err = reftable_stack_new_addition(&add, st, 0);
322384
check(!err);
323385

324386
err = reftable_addition_add(add, write_test_ref, &ref);
@@ -1313,6 +1375,7 @@ int cmd_main(int argc UNUSED, const char *argv[] UNUSED)
13131375
TEST(t_reftable_stack_reload_with_missing_table(), "stack iteration with garbage tables");
13141376
TEST(t_reftable_stack_tombstone(), "'tombstone' refs in stack");
13151377
TEST(t_reftable_stack_transaction_api(), "update transaction to stack");
1378+
TEST(t_reftable_stack_transaction_with_reload(), "transaction with reload");
13161379
TEST(t_reftable_stack_transaction_api_performs_auto_compaction(), "update transaction triggers auto-compaction");
13171380
TEST(t_reftable_stack_update_index_check(), "update transactions with equal update indices");
13181381
TEST(t_reftable_stack_uptodate(), "stack must be reloaded before ref update");

0 commit comments

Comments
 (0)