Skip to content

Commit d857469

Browse files
pks-tgitster
authored andcommitted
reftable/reader: introduce refcounting
It was recently reported that concurrent reads and writes may cause the reftable backend to segfault. The root cause of this is that we do not properly keep track of reftable readers across reloads. Suppose that you have a reftable iterator and then decide to reload the stack while iterating through the iterator. When the stack has been rewritten since we have created the iterator, then we would end up discarding a subset of readers that may still be in use by the iterator. The consequence is that we now try to reference deallocated memory, which of course segfaults. One way to trigger this is in t5616, where some background maintenance jobs have been leaking from one test into another. This leads to stack traces like the following one: + git -c protocol.version=0 -C pc1 fetch --filter=blob:limit=29999 --refetch origin AddressSanitizer:DEADLYSIGNAL ================================================================= ==657994==ERROR: AddressSanitizer: SEGV on unknown address 0x7fa0f0ec6089 (pc 0x55f23e52ddf9 bp 0x7ffe7bfa1700 sp 0x7ffe7bfa1700 T0) ==657994==The signal is caused by a READ memory access. #0 0x55f23e52ddf9 in get_var_int reftable/record.c:29 #1 0x55f23e53295e in reftable_decode_keylen reftable/record.c:170 #2 0x55f23e532cc0 in reftable_decode_key reftable/record.c:194 #3 0x55f23e54e72e in block_iter_next reftable/block.c:398 #4 0x55f23e5573dc in table_iter_next_in_block reftable/reader.c:240 #5 0x55f23e5573dc in table_iter_next reftable/reader.c:355 #6 0x55f23e5573dc in table_iter_next reftable/reader.c:339 #7 0x55f23e551283 in merged_iter_advance_subiter reftable/merged.c:69 #8 0x55f23e55169e in merged_iter_next_entry reftable/merged.c:123 #9 0x55f23e55169e in merged_iter_next_void reftable/merged.c:172 #10 0x55f23e537625 in reftable_iterator_next_ref reftable/generic.c:175 #11 0x55f23e2cf9c6 in reftable_ref_iterator_advance refs/reftable-backend.c:464 #12 0x55f23e2d996e in ref_iterator_advance refs/iterator.c:13 #13 0x55f23e2d996e in do_for_each_ref_iterator refs/iterator.c:452 #14 0x55f23dca6767 in get_ref_map builtin/fetch.c:623 #15 0x55f23dca6767 in do_fetch builtin/fetch.c:1659 #16 0x55f23dca6767 in fetch_one builtin/fetch.c:2133 #17 0x55f23dca6767 in cmd_fetch builtin/fetch.c:2432 #18 0x55f23dba7764 in run_builtin git.c:484 #19 0x55f23dba7764 in handle_builtin git.c:741 #20 0x55f23dbab61e in run_argv git.c:805 #21 0x55f23dbab61e in cmd_main git.c:1000 #22 0x55f23dba4781 in main common-main.c:64 #23 0x7fa0f063fc89 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58 #24 0x7fa0f063fd44 in __libc_start_main_impl ../csu/libc-start.c:360 #25 0x55f23dba6ad0 in _start (git+0xadfad0) (BuildId: 803b2b7f59beb03d7849fb8294a8e2145dd4aa27) While it is somewhat awkward that the maintenance processes survive tests in the first place, it is totally expected that reftables should work alright with concurrent writers. Seemingly they don't. The only underlying resource that we need to care about in this context is the reftable reader, which is responsible for reading a single table from disk. These readers get discarded immediately (unless reused) when calling `reftable_stack_reload()`, which is wrong. We can only close them once we know that there are no iterators using them anymore. Prepare for a fix by converting the reftable readers to be refcounted. Reported-by: Jeff King <peff@peff.net> Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 4ac2fd9 commit d857469

File tree

8 files changed

+46
-25
lines changed

8 files changed

+46
-25
lines changed

reftable/reader.c

+14-2
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,7 @@ int reftable_reader_new(struct reftable_reader **out,
621621
r->source = *source;
622622
r->name = xstrdup(name);
623623
r->hash_id = 0;
624+
r->refcount = 1;
624625

625626
err = block_source_read_block(source, &footer, r->size,
626627
footer_size(r->version));
@@ -645,10 +646,21 @@ int reftable_reader_new(struct reftable_reader **out,
645646
return err;
646647
}
647648

648-
void reftable_reader_free(struct reftable_reader *r)
649+
void reftable_reader_incref(struct reftable_reader *r)
650+
{
651+
if (!r->refcount)
652+
BUG("cannot increment ref counter of dead reader");
653+
r->refcount++;
654+
}
655+
656+
void reftable_reader_decref(struct reftable_reader *r)
649657
{
650658
if (!r)
651659
return;
660+
if (!r->refcount)
661+
BUG("cannot decrement ref counter of dead reader");
662+
if (--r->refcount)
663+
return;
652664
block_source_close(&r->source);
653665
FREE_AND_NULL(r->name);
654666
reftable_free(r);
@@ -812,7 +824,7 @@ int reftable_reader_print_blocks(const char *tablename)
812824
}
813825

814826
done:
815-
reftable_reader_free(r);
827+
reftable_reader_decref(r);
816828
table_iter_close(&ti);
817829
return err;
818830
}

reftable/reader.h

+2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ struct reftable_reader {
5050
struct reftable_reader_offsets ref_offsets;
5151
struct reftable_reader_offsets obj_offsets;
5252
struct reftable_reader_offsets log_offsets;
53+
54+
uint64_t refcount;
5355
};
5456

5557
const char *reader_name(struct reftable_reader *r);

reftable/readwrite_test.c

+9-9
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ static void test_log_write_read(void)
279279
/* cleanup. */
280280
strbuf_release(&buf);
281281
free_names(names);
282-
reftable_reader_free(reader);
282+
reftable_reader_decref(reader);
283283
}
284284

285285
static void test_log_zlib_corruption(void)
@@ -341,7 +341,7 @@ static void test_log_zlib_corruption(void)
341341
reftable_iterator_destroy(&it);
342342

343343
/* cleanup. */
344-
reftable_reader_free(reader);
344+
reftable_reader_decref(reader);
345345
strbuf_release(&buf);
346346
}
347347

@@ -383,7 +383,7 @@ static void test_table_read_write_sequential(void)
383383
EXPECT(j == N);
384384

385385
reftable_iterator_destroy(&it);
386-
reftable_reader_free(reader);
386+
reftable_reader_decref(reader);
387387
strbuf_release(&buf);
388388
free_names(names);
389389
}
@@ -431,7 +431,7 @@ static void test_table_read_api(void)
431431
}
432432
reftable_iterator_destroy(&it);
433433
reftable_free(names);
434-
reftable_reader_free(reader);
434+
reftable_reader_decref(reader);
435435
strbuf_release(&buf);
436436
}
437437

@@ -498,7 +498,7 @@ static void test_table_read_write_seek(int index, int hash_id)
498498
reftable_free(names[i]);
499499
}
500500
reftable_free(names);
501-
reftable_reader_free(reader);
501+
reftable_reader_decref(reader);
502502
}
503503

504504
static void test_table_read_write_seek_linear(void)
@@ -611,7 +611,7 @@ static void test_table_refs_for(int indexed)
611611
strbuf_release(&buf);
612612
free_names(want_names);
613613
reftable_iterator_destroy(&it);
614-
reftable_reader_free(reader);
614+
reftable_reader_decref(reader);
615615
}
616616

617617
static void test_table_refs_for_no_index(void)
@@ -657,7 +657,7 @@ static void test_write_empty_table(void)
657657
EXPECT(err > 0);
658658

659659
reftable_iterator_destroy(&it);
660-
reftable_reader_free(rd);
660+
reftable_reader_decref(rd);
661661
strbuf_release(&buf);
662662
}
663663

@@ -863,7 +863,7 @@ static void test_write_multiple_indices(void)
863863

864864
reftable_iterator_destroy(&it);
865865
reftable_writer_free(writer);
866-
reftable_reader_free(reader);
866+
reftable_reader_decref(reader);
867867
strbuf_release(&writer_buf);
868868
strbuf_release(&buf);
869869
}
@@ -919,7 +919,7 @@ static void test_write_multi_level_index(void)
919919

920920
reftable_iterator_destroy(&it);
921921
reftable_writer_free(writer);
922-
reftable_reader_free(reader);
922+
reftable_reader_decref(reader);
923923
strbuf_release(&writer_buf);
924924
strbuf_release(&buf);
925925
}

reftable/reftable-reader.h

+12-3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ struct reftable_reader;
3333
int reftable_reader_new(struct reftable_reader **pp,
3434
struct reftable_block_source *src, const char *name);
3535

36+
/*
37+
* Manage the reference count of the reftable reader. A newly initialized
38+
* reader starts with a refcount of 1 and will be deleted once the refcount has
39+
* reached 0.
40+
*
41+
* This is required because readers may have longer lifetimes than the stack
42+
* they belong to. The stack may for example be reloaded while the old tables
43+
* are still being accessed by an iterator.
44+
*/
45+
void reftable_reader_incref(struct reftable_reader *reader);
46+
void reftable_reader_decref(struct reftable_reader *reader);
47+
3648
/* Initialize a reftable iterator for reading refs. */
3749
void reftable_reader_init_ref_iterator(struct reftable_reader *r,
3850
struct reftable_iterator *it);
@@ -44,9 +56,6 @@ void reftable_reader_init_log_iterator(struct reftable_reader *r,
4456
/* returns the hash ID used in this table. */
4557
uint32_t reftable_reader_hash_id(struct reftable_reader *r);
4658

47-
/* closes and deallocates a reader. */
48-
void reftable_reader_free(struct reftable_reader *);
49-
5059
/* return an iterator for the refs pointing to `oid`. */
5160
int reftable_reader_refs_for(struct reftable_reader *r,
5261
struct reftable_iterator *it, uint8_t *oid);

reftable/stack.c

+4-4
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ void reftable_stack_destroy(struct reftable_stack *st)
186186
if (names && !has_name(names, name)) {
187187
stack_filename(&filename, st, name);
188188
}
189-
reftable_reader_free(st->readers[i]);
189+
reftable_reader_decref(st->readers[i]);
190190

191191
if (filename.len) {
192192
/* On Windows, can only unlink after closing. */
@@ -290,7 +290,7 @@ static int reftable_stack_reload_once(struct reftable_stack *st,
290290
const char *name = reader_name(cur[i]);
291291
stack_filename(&table_path, st, name);
292292

293-
reftable_reader_free(cur[i]);
293+
reftable_reader_decref(cur[i]);
294294

295295
/* On Windows, can only unlink after closing. */
296296
unlink(table_path.buf);
@@ -299,7 +299,7 @@ static int reftable_stack_reload_once(struct reftable_stack *st,
299299

300300
done:
301301
for (i = 0; i < new_readers_len; i++)
302-
reftable_reader_free(new_readers[i]);
302+
reftable_reader_decref(new_readers[i]);
303303
reftable_free(new_readers);
304304
reftable_free(cur);
305305
strbuf_release(&table_path);
@@ -1534,7 +1534,7 @@ static void remove_maybe_stale_table(struct reftable_stack *st, uint64_t max,
15341534
goto done;
15351535

15361536
update_idx = reftable_reader_max_update_index(rd);
1537-
reftable_reader_free(rd);
1537+
reftable_reader_decref(rd);
15381538

15391539
if (update_idx <= max) {
15401540
unlink(table_path.buf);

reftable/stack_test.c

+2-4
Original file line numberDiff line numberDiff line change
@@ -1036,10 +1036,8 @@ static void test_reftable_stack_compaction_concurrent(void)
10361036
static void unclean_stack_close(struct reftable_stack *st)
10371037
{
10381038
/* break abstraction boundary to simulate unclean shutdown. */
1039-
int i = 0;
1040-
for (; i < st->readers_len; i++) {
1041-
reftable_reader_free(st->readers[i]);
1042-
}
1039+
for (size_t i = 0; i < st->readers_len; i++)
1040+
reftable_reader_decref(st->readers[i]);
10431041
st->readers_len = 0;
10441042
FREE_AND_NULL(st->readers);
10451043
}

t/helper/test-reftable.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ static int dump_reftable(const char *tablename)
152152

153153
done:
154154
reftable_merged_table_free(mt);
155-
reftable_reader_free(r);
155+
reftable_reader_decref(r);
156156
return err;
157157
}
158158

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ merged_table_from_records(struct reftable_ref_record **refs,
115115
static void readers_destroy(struct reftable_reader **readers, const size_t n)
116116
{
117117
for (size_t i = 0; i < n; i++)
118-
reftable_reader_free(readers[i]);
118+
reftable_reader_decref(readers[i]);
119119
reftable_free(readers);
120120
}
121121

@@ -437,7 +437,7 @@ static void t_default_write_opts(void)
437437
err = reftable_merged_table_new(&merged, &rd, 1, GIT_SHA1_FORMAT_ID);
438438
check(!err);
439439

440-
reftable_reader_free(rd);
440+
reftable_reader_decref(rd);
441441
reftable_merged_table_free(merged);
442442
strbuf_release(&buf);
443443
}

0 commit comments

Comments
 (0)