Skip to content

Commit 9d64de8

Browse files
committed
store: Track the actual mmap size separately
After a trim, the snip file is truncated and the header's nr_snips_alloc is updated, but mremap is not called because shrinking the mapping is unnecessary. local_nr_snips_alloc is also updated to match. When the store subsequently grows, cs_file_resize and cs_ref both derive the old mapping size from cs_file_size(local_nr_snips_alloc), which by that point reflects the post-trim allocation rather than the extent of the live mapping. Introduce mapped_file_size to record the actual size of the current mmap. Update it in cs_remap and cs_init, and use it wherever the true mapped extent is required.
1 parent 1e48456 commit 9d64de8

3 files changed

Lines changed: 69 additions & 7 deletions

File tree

src/store.c

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ static int _must_use_ _nonnull_ cs_remap(struct clip_store *cs, size_t old_size,
9494
}
9595
cs->header = new_header;
9696
cs->snips = (struct cs_snip *)(cs->header + 1);
97+
cs->mapped_file_size = new_size;
9798
return 0;
9899
}
99100

@@ -162,7 +163,7 @@ struct ref_guard cs_ref(struct clip_store *cs) {
162163

163164
// If we shrank, no need to remap, since we'll just use the new bounds.
164165
if (cs->local_nr_snips_alloc < cs->header->nr_snips_alloc) {
165-
int remap_ret = cs_remap(cs, cs_file_size(cs->local_nr_snips_alloc),
166+
int remap_ret = cs_remap(cs, cs->mapped_file_size,
166167
cs_file_size(cs->header->nr_snips_alloc));
167168
if (remap_ret < 0) {
168169
guard.status = remap_ret;
@@ -194,9 +195,7 @@ void drop_cs_unref(struct ref_guard *guard) {
194195
*/
195196
int cs_destroy(struct clip_store *cs) {
196197
cs->ready = false;
197-
// Don't use the value from the header: if it's out of date, we haven't
198-
// done mremap() with the new size yet
199-
if (munmap(cs->header, cs_file_size(cs->local_nr_snips_alloc))) {
198+
if (munmap(cs->header, cs->mapped_file_size)) {
200199
return negative_errno();
201200
}
202201
return 0;
@@ -258,6 +257,7 @@ int cs_init(struct clip_store *cs, int snip_fd, int content_dir_fd) {
258257
cs->snips = (struct cs_snip *)(cs->header + 1);
259258
cs->local_nr_snips = cs->header->nr_snips;
260259
cs->local_nr_snips_alloc = cs->header->nr_snips_alloc;
260+
cs->mapped_file_size = file_size;
261261
cs->ready = true;
262262

263263
(void)guard; // Old clang will complain guard is unused, despite cleanup
@@ -308,10 +308,10 @@ static int _must_use_ _nonnull_ cs_file_resize(struct clip_store *cs,
308308
}
309309

310310
if (grow) {
311-
size_t old_size = cs_file_size(cs->header->nr_snips_alloc);
312-
int ret = cs_remap(cs, old_size, new_size);
311+
size_t old_file_size = cs_file_size(cs->header->nr_snips_alloc);
312+
int ret = cs_remap(cs, cs->mapped_file_size, new_size);
313313
if (ret < 0) {
314-
ftruncate(cs->snip_fd, (off_t)old_size);
314+
expect(ftruncate(cs->snip_fd, (off_t)old_file_size) == 0);
315315
return ret;
316316
}
317317
}

src/store.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ static_assert(sizeof(struct cs_snip) == sizeof(struct cs_header),
6363
* @refcount: The reference count for the fd flock
6464
* @local_nr_snips: Our last known header->nr_snips
6565
* @local_nr_snips_alloc: Our last known header->nr_snips_alloc
66+
* @mapped_file_size: The actual size of our current mmap() in bytes
6667
*/
6768
struct clip_store {
6869
/* FDs */
@@ -77,6 +78,7 @@ struct clip_store {
7778
size_t refcount;
7879
size_t local_nr_snips;
7980
size_t local_nr_snips_alloc;
81+
size_t mapped_file_size;
8082
bool ready;
8183
};
8284

tests/test_store.c

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,25 @@ static int create_test_content_dir_fd(void) {
112112
return dir_fd;
113113
}
114114

115+
static int count_test_snip_maps(void) {
116+
#ifdef __linux__
117+
_drop_(fclose) FILE *maps = fopen("/proc/self/maps", "r");
118+
assert(maps != NULL);
119+
120+
char line[4096];
121+
int count = 0;
122+
while (fgets(line, sizeof(line), maps) != NULL) {
123+
if (strstr(line, TEST_SNIP_FILE) != NULL) {
124+
count++;
125+
}
126+
}
127+
128+
return count;
129+
#else
130+
return -1;
131+
#endif
132+
}
133+
115134
/* Test callables */
116135
static enum cs_remove_action remove_if_five(uint64_t hash, const char *line,
117136
void *private) {
@@ -646,6 +665,46 @@ static bool test__synchronisation(void) {
646665
return true;
647666
}
648667

668+
static bool test__cs_trim__regrow_does_not_leak_mapping(void) {
669+
struct clip_store cs = setup_test();
670+
671+
bool added = true;
672+
for (int i = 0; i < 1500; i++) {
673+
char buf[32];
674+
snprintf(buf, sizeof(buf), "clip-%d", i);
675+
if (cs_add(&cs, buf, NULL, CS_DUPE_KEEP_ALL) != 0) {
676+
added = false;
677+
break;
678+
}
679+
}
680+
t_assert(added);
681+
682+
int maps_after_growth = count_test_snip_maps();
683+
if (maps_after_growth < 0) {
684+
drop_teardown_test(&cs);
685+
return true;
686+
}
687+
t_assert(maps_after_growth == 1);
688+
689+
t_assert(cs_trim(&cs, CS_ITER_NEWEST_FIRST, 0) == 0);
690+
t_assert(count_test_snip_maps() == 1);
691+
692+
t_assert(cs_add(&cs, "regrow", NULL, CS_DUPE_KEEP_ALL) == 0);
693+
t_assert(count_test_snip_maps() == 1);
694+
695+
int snip_fd = cs.snip_fd;
696+
int content_dir_fd = cs.content_dir_fd;
697+
t_assert(cs_destroy(&cs) == 0);
698+
t_assert(count_test_snip_maps() == 0);
699+
700+
close(snip_fd);
701+
shm_unlink(TEST_SNIP_FILE);
702+
close(content_dir_fd);
703+
remove_test_content_dir(TEST_CONTENT_DIR);
704+
705+
return true;
706+
}
707+
649708
static bool test__cs_add__dupe_keep_all(void) {
650709
_drop_(teardown_test) struct clip_store cs = setup_test();
651710

@@ -771,6 +830,7 @@ int main(void) {
771830
t_run(test__cs_replace__out_of_bounds);
772831
t_run(test__cs_replace__add_fail_keeps_old_entry);
773832
t_run(test__synchronisation);
833+
t_run(test__cs_trim__regrow_does_not_leak_mapping);
774834
t_run(test__cs_trim__no_remove_when_still_referenced);
775835
t_run(test__cs_snip__correct_nr_lines);
776836
t_run(test__first_line__empty);

0 commit comments

Comments
 (0)