Skip to content

Commit 87bf95d

Browse files
committed
fsmonitor--daemon: periodically truncate list of modified files
Teach fsmonitor--daemon to periodically truncate the list of modified files to save some memory. Clients will ask for the set of changes relative to a token that they found in the FSMN index extension in the index. (This token is like a point in time, but different). Clients will then update the index to contain the response token (so that subsequent commands will be relative to this new token). Therefore, the daemon can gradually truncate the in-memory list of changed paths as they become obsolete (older than the previous token). Since we may have multiple clients making concurrent requests with a skew of tokens and clients may be racing to the talk to the daemon, we lazily truncate the list. We introduce a 5 minute delay and truncate batches 5 minutes after they are considered obsolete. Signed-off-by: Jeff Hostetler <[email protected]>
1 parent b23d8be commit 87bf95d

File tree

1 file changed

+95
-0
lines changed

1 file changed

+95
-0
lines changed

builtin/fsmonitor--daemon.c

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,89 @@ static void fsmonitor_batch__combine(struct fsmonitor_batch *batch_dest,
300300
batch_src->interned_paths[k];
301301
}
302302

303+
/*
304+
* To keep the batch list from growing unbounded in response to filesystem
305+
* activity, we try to truncate old batches from the end of the list as
306+
* they become irrelevant.
307+
*
308+
* We assume that the .git/index will be updated with the most recent token
309+
* any time the index is updated. And future commands will only ask for
310+
* recent changes *since* that new token. So as tokens advance into the
311+
* future, older batch items will never be requested/needed. So we can
312+
* truncate them without loss of functionality.
313+
*
314+
* However, multiple commands may be talking to the daemon concurrently
315+
* or perform a slow command, so a little "token skew" is possible.
316+
* Therefore, we want this to be a little bit lazy and have a generous
317+
* delay.
318+
*
319+
* The current reader thread walked backwards in time from `token->batch_head`
320+
* back to `batch_marker` somewhere in the middle of the batch list.
321+
*
322+
* Let's walk backwards in time from that marker an arbitrary delay
323+
* and truncate the list there. Note that these timestamps are completely
324+
* artificial (based on when we pinned the batch item) and not on any
325+
* filesystem activity.
326+
*
327+
* Return the obsolete portion of the list after we have removed it from
328+
* the official list so that the caller can free it after leaving the lock.
329+
*/
330+
#define MY_TIME_DELAY_SECONDS (5 * 60) /* seconds */
331+
332+
static struct fsmonitor_batch *with_lock__truncate_old_batches(
333+
struct fsmonitor_daemon_state *state,
334+
const struct fsmonitor_batch *batch_marker)
335+
{
336+
/* assert current thread holding state->main_lock */
337+
338+
const struct fsmonitor_batch *batch;
339+
struct fsmonitor_batch *remainder;
340+
341+
if (!batch_marker)
342+
return NULL;
343+
344+
trace_printf_key(&trace_fsmonitor, "Truncate: mark (%"PRIu64",%"PRIu64")",
345+
batch_marker->batch_seq_nr,
346+
(uint64_t)batch_marker->pinned_time);
347+
348+
for (batch = batch_marker; batch; batch = batch->next) {
349+
time_t t;
350+
351+
if (!batch->pinned_time) /* an overflow batch */
352+
continue;
353+
354+
t = batch->pinned_time + MY_TIME_DELAY_SECONDS;
355+
if (t > batch_marker->pinned_time) /* too close to marker */
356+
continue;
357+
358+
goto truncate_past_here;
359+
}
360+
361+
return NULL;
362+
363+
truncate_past_here:
364+
state->current_token_data->batch_tail = (struct fsmonitor_batch *)batch;
365+
366+
remainder = ((struct fsmonitor_batch *)batch)->next;
367+
((struct fsmonitor_batch *)batch)->next = NULL;
368+
369+
return remainder;
370+
}
371+
372+
static void free_remainder(struct fsmonitor_batch *remainder)
373+
{
374+
struct fsmonitor_batch *p;
375+
376+
if (!remainder)
377+
return;
378+
379+
for (p = remainder; p; p = fsmonitor_batch__pop(p)) {
380+
trace_printf_key(&trace_fsmonitor,
381+
"Truncate: kill (%"PRIu64",%"PRIu64")",
382+
p->batch_seq_nr, (uint64_t)p->pinned_time);
383+
}
384+
}
385+
303386
static void fsmonitor_free_token_data(struct fsmonitor_token_data *token)
304387
{
305388
struct fsmonitor_batch *p;
@@ -416,6 +499,7 @@ static int do_handle_client(struct fsmonitor_daemon_state *state,
416499
const char *p;
417500
const struct fsmonitor_batch *batch_head;
418501
const struct fsmonitor_batch *batch;
502+
struct fsmonitor_batch *remainder = NULL;
419503
intmax_t count = 0, duplicates = 0;
420504
kh_str_t *shown;
421505
int hash_ret;
@@ -645,11 +729,22 @@ static int do_handle_client(struct fsmonitor_daemon_state *state,
645729
* that work.
646730
*/
647731
fsmonitor_free_token_data(token_data);
732+
} else if (batch) {
733+
/*
734+
* This batch is the first item in the list
735+
* that is older than the requested sequence
736+
* number and might be considered to be
737+
* obsolete. See if we can truncate the list
738+
* and save some memory.
739+
*/
740+
remainder = with_lock__truncate_old_batches(state, batch);
648741
}
649742
}
650743

651744
pthread_mutex_unlock(&state->main_lock);
652745

746+
free_remainder(remainder);
747+
653748
trace2_data_intmax("fsmonitor", the_repository, "response/length", total_response_len);
654749
trace2_data_intmax("fsmonitor", the_repository, "response/count/files", count);
655750
trace2_data_intmax("fsmonitor", the_repository, "response/count/duplicates", duplicates);

0 commit comments

Comments
 (0)