mirror of
https://github.com/git-for-windows/git.git
synced 2026-03-17 22:06:58 -05:00
fsmonitor--daemon: create token-based changed path cache
Teach fsmonitor--daemon to build lists of changed paths and associate them with a token-id. This will be used by the platform-specific backends to accumulate changed paths in response to filesystem events. The platform-specific event loops receive batches containing one or more changed paths. Their fs listener thread will accumulate them in a `fsmonitor_batch` (and without locking) and then "publish" them to associate them with the current token and to make them visible to the client worker threads. Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
This commit is contained in:
committed by
Johannes Schindelin
parent
6b4070e4ee
commit
3ffa3f6679
@@ -255,6 +255,120 @@ static struct fsmonitor_token_data *fsmonitor_new_token_data(void)
|
||||
return token;
|
||||
}
|
||||
|
||||
struct fsmonitor_batch {
|
||||
struct fsmonitor_batch *next;
|
||||
uint64_t batch_seq_nr;
|
||||
const char **interned_paths;
|
||||
size_t nr, alloc;
|
||||
time_t pinned_time;
|
||||
};
|
||||
|
||||
struct fsmonitor_batch *fsmonitor_batch__new(void)
|
||||
{
|
||||
struct fsmonitor_batch *batch = xcalloc(1, sizeof(*batch));
|
||||
|
||||
return batch;
|
||||
}
|
||||
|
||||
struct fsmonitor_batch *fsmonitor_batch__free(struct fsmonitor_batch *batch)
|
||||
{
|
||||
struct fsmonitor_batch *next;
|
||||
|
||||
if (!batch)
|
||||
return NULL;
|
||||
|
||||
next = batch->next;
|
||||
|
||||
/*
|
||||
* The actual strings within the array are interned, so we don't
|
||||
* own them.
|
||||
*/
|
||||
free(batch->interned_paths);
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
void fsmonitor_batch__add_path(struct fsmonitor_batch *batch,
|
||||
const char *path)
|
||||
{
|
||||
const char *interned_path = strintern(path);
|
||||
|
||||
trace_printf_key(&trace_fsmonitor, "event: %s", interned_path);
|
||||
|
||||
ALLOC_GROW(batch->interned_paths, batch->nr + 1, batch->alloc);
|
||||
batch->interned_paths[batch->nr++] = interned_path;
|
||||
}
|
||||
|
||||
static void fsmonitor_batch__combine(struct fsmonitor_batch *batch_dest,
|
||||
const struct fsmonitor_batch *batch_src)
|
||||
{
|
||||
/* assert state->main_lock */
|
||||
|
||||
size_t k;
|
||||
|
||||
ALLOC_GROW(batch_dest->interned_paths,
|
||||
batch_dest->nr + batch_src->nr + 1,
|
||||
batch_dest->alloc);
|
||||
|
||||
for (k = 0; k < batch_src->nr; k++)
|
||||
batch_dest->interned_paths[batch_dest->nr++] =
|
||||
batch_src->interned_paths[k];
|
||||
}
|
||||
|
||||
static void fsmonitor_free_token_data(struct fsmonitor_token_data *token)
|
||||
{
|
||||
struct fsmonitor_batch *p;
|
||||
|
||||
if (!token)
|
||||
return;
|
||||
|
||||
assert(token->client_ref_count == 0);
|
||||
|
||||
strbuf_release(&token->token_id);
|
||||
|
||||
for (p = token->batch_head; p; p = fsmonitor_batch__free(p))
|
||||
;
|
||||
|
||||
free(token);
|
||||
}
|
||||
|
||||
/*
|
||||
* Flush all of our cached data about the filesystem. Call this if we
|
||||
* lose sync with the filesystem and miss some notification events.
|
||||
*
|
||||
* [1] If we are missing events, then we no longer have a complete
|
||||
* history of the directory (relative to our current start token).
|
||||
* We should create a new token and start fresh (as if we just
|
||||
* booted up).
|
||||
*
|
||||
* If there are no readers of the the current token data series, we
|
||||
* can free it now. Otherwise, let the last reader free it. Either
|
||||
* way, the old token data series is no longer associated with our
|
||||
* state data.
|
||||
*/
|
||||
void fsmonitor_force_resync(struct fsmonitor_daemon_state *state)
|
||||
{
|
||||
struct fsmonitor_token_data *free_me = NULL;
|
||||
struct fsmonitor_token_data *new_one = NULL;
|
||||
|
||||
new_one = fsmonitor_new_token_data();
|
||||
|
||||
pthread_mutex_lock(&state->main_lock);
|
||||
|
||||
trace_printf_key(&trace_fsmonitor,
|
||||
"force resync [old '%s'][new '%s']",
|
||||
state->current_token_data->token_id.buf,
|
||||
new_one->token_id.buf);
|
||||
|
||||
if (state->current_token_data->client_ref_count == 0)
|
||||
free_me = state->current_token_data;
|
||||
state->current_token_data = new_one;
|
||||
|
||||
pthread_mutex_unlock(&state->main_lock);
|
||||
|
||||
fsmonitor_free_token_data(free_me);
|
||||
}
|
||||
|
||||
static ipc_server_application_cb handle_client;
|
||||
|
||||
static int handle_client(void *data, const char *command,
|
||||
@@ -355,6 +469,77 @@ enum fsmonitor_path_type fsmonitor_classify_path_absolute(
|
||||
return fsmonitor_classify_path_gitdir_relative(rel);
|
||||
}
|
||||
|
||||
/*
|
||||
* We try to combine small batches at the front of the batch-list to avoid
|
||||
* having a long list. This hopefully makes it a little easier when we want
|
||||
* to truncate and maintain the list. However, we don't want the paths array
|
||||
* to just keep growing and growing with realloc, so we insert an arbitrary
|
||||
* limit.
|
||||
*/
|
||||
#define MY_COMBINE_LIMIT (1024)
|
||||
|
||||
void fsmonitor_publish(struct fsmonitor_daemon_state *state,
|
||||
struct fsmonitor_batch *batch,
|
||||
const struct string_list *cookie_names)
|
||||
{
|
||||
if (!batch && !cookie_names->nr)
|
||||
return;
|
||||
|
||||
pthread_mutex_lock(&state->main_lock);
|
||||
|
||||
if (batch) {
|
||||
struct fsmonitor_batch *head;
|
||||
|
||||
head = state->current_token_data->batch_head;
|
||||
if (!head) {
|
||||
batch->batch_seq_nr = 0;
|
||||
batch->next = NULL;
|
||||
state->current_token_data->batch_head = batch;
|
||||
state->current_token_data->batch_tail = batch;
|
||||
} else if (head->pinned_time) {
|
||||
/*
|
||||
* We cannot alter the current batch list
|
||||
* because:
|
||||
*
|
||||
* [a] it is being transmitted to at least one
|
||||
* client and the handle_client() thread has a
|
||||
* ref-count, but not a lock on the batch list
|
||||
* starting with this item.
|
||||
*
|
||||
* [b] it has been transmitted in the past to
|
||||
* at least one client such that future
|
||||
* requests are relative to this head batch.
|
||||
*
|
||||
* So, we can only prepend a new batch onto
|
||||
* the front of the list.
|
||||
*/
|
||||
batch->batch_seq_nr = head->batch_seq_nr + 1;
|
||||
batch->next = head;
|
||||
state->current_token_data->batch_head = batch;
|
||||
} else if (head->nr + batch->nr > MY_COMBINE_LIMIT) {
|
||||
/*
|
||||
* The head batch in the list has never been
|
||||
* transmitted to a client, but folding the
|
||||
* contents of the new batch onto it would
|
||||
* exceed our arbitrary limit, so just prepend
|
||||
* the new batch onto the list.
|
||||
*/
|
||||
batch->batch_seq_nr = head->batch_seq_nr + 1;
|
||||
batch->next = head;
|
||||
state->current_token_data->batch_head = batch;
|
||||
} else {
|
||||
/*
|
||||
* We are free to append the paths in the given
|
||||
* batch onto the end of the current head batch.
|
||||
*/
|
||||
fsmonitor_batch__combine(head, batch);
|
||||
fsmonitor_batch__free(batch);
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&state->main_lock);
|
||||
}
|
||||
|
||||
static void *fsmonitor_fs_listen__thread_proc(void *_state)
|
||||
{
|
||||
struct fsmonitor_daemon_state *state = _state;
|
||||
@@ -369,6 +554,13 @@ static void *fsmonitor_fs_listen__thread_proc(void *_state)
|
||||
|
||||
fsmonitor_fs_listen__loop(state);
|
||||
|
||||
pthread_mutex_lock(&state->main_lock);
|
||||
if (state->current_token_data &&
|
||||
state->current_token_data->client_ref_count == 0)
|
||||
fsmonitor_free_token_data(state->current_token_data);
|
||||
state->current_token_data = NULL;
|
||||
pthread_mutex_unlock(&state->main_lock);
|
||||
|
||||
trace2_thread_exit();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,27 @@
|
||||
struct fsmonitor_batch;
|
||||
struct fsmonitor_token_data;
|
||||
|
||||
/*
|
||||
* Create a new batch of path(s). The returned batch is considered
|
||||
* private and not linked into the fsmonitor daemon state. The caller
|
||||
* should fill this batch with one or more paths and then publish it.
|
||||
*/
|
||||
struct fsmonitor_batch *fsmonitor_batch__new(void);
|
||||
|
||||
/*
|
||||
* Free this batch and return the value of the batch->next field.
|
||||
*/
|
||||
struct fsmonitor_batch *fsmonitor_batch__free(struct fsmonitor_batch *batch);
|
||||
|
||||
/*
|
||||
* Add this path to this batch of modified files.
|
||||
*
|
||||
* The batch should be private and NOT (yet) linked into the fsmonitor
|
||||
* daemon state and therefore not yet visible to worker threads and so
|
||||
* no locking is required.
|
||||
*/
|
||||
void fsmonitor_batch__add_path(struct fsmonitor_batch *batch, const char *path);
|
||||
|
||||
struct fsmonitor_daemon_backend_data; /* opaque platform-specific data */
|
||||
|
||||
struct fsmonitor_daemon_state {
|
||||
@@ -93,5 +114,24 @@ enum fsmonitor_path_type fsmonitor_classify_path_absolute(
|
||||
struct fsmonitor_daemon_state *state,
|
||||
const char *path);
|
||||
|
||||
/*
|
||||
* Prepend the this batch of path(s) onto the list of batches associated
|
||||
* with the current token. This makes the batch visible to worker threads.
|
||||
*
|
||||
* The caller no longer owns the batch and must not free it.
|
||||
*
|
||||
* Wake up the client threads waiting on these cookies.
|
||||
*/
|
||||
void fsmonitor_publish(struct fsmonitor_daemon_state *state,
|
||||
struct fsmonitor_batch *batch,
|
||||
const struct string_list *cookie_names);
|
||||
|
||||
/*
|
||||
* If the platform-specific layer loses sync with the filesystem,
|
||||
* it should call this to invalidate cached data and abort waiting
|
||||
* threads.
|
||||
*/
|
||||
void fsmonitor_force_resync(struct fsmonitor_daemon_state *state);
|
||||
|
||||
#endif /* HAVE_FSMONITOR_DAEMON_BACKEND */
|
||||
#endif /* FSMONITOR_DAEMON_H */
|
||||
|
||||
Reference in New Issue
Block a user