Skip to content

Commit 9d471b9

Browse files
pks-tgitster
authored andcommitted
reftable/merged: drain priority queue on reseek
In 5bf96e0 (reftable/generic: move seeking of records into the iterator, 2024-05-13) we have refactored the reftable codebase such that iterators can be initialized once and then re-seeked multiple times. This feature is used by 1869525 (refs/reftable: wire up support for exclude patterns, 2024-09-16) in order to skip records based on exclude patterns provided by the caller. The logic to re-seek the merged iterator is insufficient though because we don't drain the priority queue on a re-seek. This means that the queue may contain stale entries and thus reading the next record in the queue will return the wrong entry. While this is an obvious bug, it is harmless in the context of above exclude patterns: - If the queue contained stale entries that match the pattern then the caller would already know to filter out such refs. This is because our codebase is prepared to handle backends that don't have a way to efficiently implement exclude patterns. - If the queue contained stale entries that don't match the pattern we'd eventually filter out any duplicates. This is because the reftable code discards items with the same ref name and sorts any remaining entries properly. So things happen to work in this context regardless of the bug, and there is no other use case yet where we re-seek iterators. We're about to introduce a caching mechanism though where iterators are reused by the reftable backend, and that will expose the bug. Fix the issue by draining the priority queue when seeking and add a testcase that surfaces the issue. Signed-off-by: Patrick Steinhardt <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent eb22c1b commit 9d471b9

File tree

2 files changed

+75
-0
lines changed

2 files changed

+75
-0
lines changed

reftable/merged.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ static int merged_iter_seek(struct merged_iter *mi, struct reftable_record *want
6666
int err;
6767

6868
mi->advance_index = -1;
69+
while (!merged_iter_pqueue_is_empty(mi->pq))
70+
merged_iter_pqueue_remove(&mi->pq);
6971

7072
for (size_t i = 0; i < mi->subiters_len; i++) {
7173
err = iterator_seek(&mi->subiters[i].iter, want);

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

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,78 @@ static void t_merged_seek_multiple_times(void)
273273
reftable_free(sources);
274274
}
275275

276+
static void t_merged_seek_multiple_times_without_draining(void)
277+
{
278+
struct reftable_ref_record r1[] = {
279+
{
280+
.refname = (char *) "a",
281+
.update_index = 1,
282+
.value_type = REFTABLE_REF_VAL1,
283+
.value.val1 = { 1 },
284+
},
285+
{
286+
.refname = (char *) "c",
287+
.update_index = 1,
288+
.value_type = REFTABLE_REF_VAL1,
289+
.value.val1 = { 2 },
290+
}
291+
};
292+
struct reftable_ref_record r2[] = {
293+
{
294+
.refname = (char *) "b",
295+
.update_index = 2,
296+
.value_type = REFTABLE_REF_VAL1,
297+
.value.val1 = { 3 },
298+
},
299+
{
300+
.refname = (char *) "d",
301+
.update_index = 2,
302+
.value_type = REFTABLE_REF_VAL1,
303+
.value.val1 = { 4 },
304+
},
305+
};
306+
struct reftable_ref_record *refs[] = {
307+
r1, r2,
308+
};
309+
size_t sizes[] = {
310+
ARRAY_SIZE(r1), ARRAY_SIZE(r2),
311+
};
312+
struct reftable_buf bufs[] = {
313+
REFTABLE_BUF_INIT, REFTABLE_BUF_INIT,
314+
};
315+
struct reftable_block_source *sources = NULL;
316+
struct reftable_reader **readers = NULL;
317+
struct reftable_ref_record rec = { 0 };
318+
struct reftable_iterator it = { 0 };
319+
struct reftable_merged_table *mt;
320+
int err;
321+
322+
mt = merged_table_from_records(refs, &sources, &readers, sizes, bufs, 2);
323+
merged_table_init_iter(mt, &it, BLOCK_TYPE_REF);
324+
325+
err = reftable_iterator_seek_ref(&it, "b");
326+
check(!err);
327+
err = reftable_iterator_next_ref(&it, &rec);
328+
check(!err);
329+
err = reftable_ref_record_equal(&rec, &r2[0], REFTABLE_HASH_SIZE_SHA1);
330+
check(err == 1);
331+
332+
err = reftable_iterator_seek_ref(&it, "a");
333+
check(!err);
334+
err = reftable_iterator_next_ref(&it, &rec);
335+
check(!err);
336+
err = reftable_ref_record_equal(&rec, &r1[0], REFTABLE_HASH_SIZE_SHA1);
337+
check(err == 1);
338+
339+
for (size_t i = 0; i < ARRAY_SIZE(bufs); i++)
340+
reftable_buf_release(&bufs[i]);
341+
readers_destroy(readers, ARRAY_SIZE(refs));
342+
reftable_ref_record_release(&rec);
343+
reftable_iterator_destroy(&it);
344+
reftable_merged_table_free(mt);
345+
reftable_free(sources);
346+
}
347+
276348
static struct reftable_merged_table *
277349
merged_table_from_log_records(struct reftable_log_record **logs,
278350
struct reftable_block_source **source,
@@ -467,6 +539,7 @@ int cmd_main(int argc UNUSED, const char *argv[] UNUSED)
467539
TEST(t_merged_logs(), "merged table with multiple log updates for same ref");
468540
TEST(t_merged_refs(), "merged table with multiple updates to same ref");
469541
TEST(t_merged_seek_multiple_times(), "merged table can seek multiple times");
542+
TEST(t_merged_seek_multiple_times_without_draining(), "merged table can seek multiple times without draining");
470543
TEST(t_merged_single_record(), "ref occurring in only one record can be fetched");
471544

472545
return test_done();

0 commit comments

Comments
 (0)