Skip to content

Commit 7215c5a

Browse files
committed
fsmonitor--daemon: implement 'run' command
Implement `run` command to try to begin listening for file system events. This version defines the thread structure with a single fsmonitor_fs_listen thread to watch for file system events and a simple IPC thread pool to watch for connection from Git clients over a well-known named pipe or Unix domain socket. This commit does not actually do anything yet because the platform backends are still just stubs. Signed-off-by: Jeff Hostetler <[email protected]>
1 parent 59bf746 commit 7215c5a

File tree

2 files changed

+243
-1
lines changed

2 files changed

+243
-1
lines changed

builtin/fsmonitor--daemon.c

Lines changed: 209 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,39 @@
33
#include "parse-options.h"
44
#include "fsmonitor.h"
55
#include "fsmonitor-ipc.h"
6+
#include "compat/fsmonitor/fsm-listen.h"
7+
#include "fsmonitor--daemon.h"
68
#include "simple-ipc.h"
79
#include "khash.h"
810

911
static const char * const builtin_fsmonitor__daemon_usage[] = {
12+
N_("git fsmonitor--daemon run [<options>]"),
1013
N_("git fsmonitor--daemon stop"),
1114
N_("git fsmonitor--daemon status"),
1215
NULL
1316
};
1417

1518
#ifdef HAVE_FSMONITOR_DAEMON_BACKEND
19+
/*
20+
* Global state loaded from config.
21+
*/
22+
#define FSMONITOR__IPC_THREADS "fsmonitor.ipcthreads"
23+
static int fsmonitor__ipc_threads = 8;
24+
25+
static int fsmonitor_config(const char *var, const char *value, void *cb)
26+
{
27+
if (!strcmp(var, FSMONITOR__IPC_THREADS)) {
28+
int i = git_config_int(var, value);
29+
if (i < 1)
30+
return error(_("value of '%s' out of range: %d"),
31+
FSMONITOR__IPC_THREADS, i);
32+
fsmonitor__ipc_threads = i;
33+
return 0;
34+
}
35+
36+
return git_default_config(var, value, cb);
37+
}
38+
1639
/*
1740
* Acting as a CLIENT.
1841
*
@@ -57,11 +80,190 @@ static int do_as_client__status(void)
5780
}
5881
}
5982

83+
static ipc_server_application_cb handle_client;
84+
85+
static int handle_client(void *data,
86+
const char *command, size_t command_len,
87+
ipc_server_reply_cb *reply,
88+
struct ipc_server_reply_data *reply_data)
89+
{
90+
/* struct fsmonitor_daemon_state *state = data; */
91+
int result;
92+
93+
/*
94+
* The Simple IPC API now supports {char*, len} arguments, but
95+
* FSMonitor always uses proper null-terminated strings, so
96+
* we can ignore the command_len argument. (Trust, but verify.)
97+
*/
98+
if (command_len != strlen(command))
99+
BUG("FSMonitor assumes text messages");
100+
101+
trace2_region_enter("fsmonitor", "handle_client", the_repository);
102+
trace2_data_string("fsmonitor", the_repository, "request", command);
103+
104+
result = 0; /* TODO Do something here. */
105+
106+
trace2_region_leave("fsmonitor", "handle_client", the_repository);
107+
108+
return result;
109+
}
110+
111+
static void *fsm_listen__thread_proc(void *_state)
112+
{
113+
struct fsmonitor_daemon_state *state = _state;
114+
115+
trace2_thread_start("fsm-listen");
116+
117+
trace_printf_key(&trace_fsmonitor, "Watching: worktree '%s'",
118+
state->path_worktree_watch.buf);
119+
if (state->nr_paths_watching > 1)
120+
trace_printf_key(&trace_fsmonitor, "Watching: gitdir '%s'",
121+
state->path_gitdir_watch.buf);
122+
123+
fsm_listen__loop(state);
124+
125+
trace2_thread_exit();
126+
return NULL;
127+
}
128+
129+
static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)
130+
{
131+
struct ipc_server_opts ipc_opts = {
132+
.nr_threads = fsmonitor__ipc_threads,
133+
134+
/*
135+
* We know that there are no other active threads yet,
136+
* so we can let the IPC layer temporarily chdir() if
137+
* it needs to when creating the server side of the
138+
* Unix domain socket.
139+
*/
140+
.uds_disallow_chdir = 0
141+
};
142+
143+
/*
144+
* Start the IPC thread pool before the we've started the file
145+
* system event listener thread so that we have the IPC handle
146+
* before we need it.
147+
*/
148+
if (ipc_server_run_async(&state->ipc_server_data,
149+
fsmonitor_ipc__get_path(), &ipc_opts,
150+
handle_client, state))
151+
return error(_("could not start IPC thread pool"));
152+
153+
/*
154+
* Start the fsmonitor listener thread to collect filesystem
155+
* events.
156+
*/
157+
if (pthread_create(&state->listener_thread, NULL,
158+
fsm_listen__thread_proc, state) < 0) {
159+
ipc_server_stop_async(state->ipc_server_data);
160+
ipc_server_await(state->ipc_server_data);
161+
162+
return error(_("could not start fsmonitor listener thread"));
163+
}
164+
165+
/*
166+
* The daemon is now fully functional in background threads.
167+
* Wait for the IPC thread pool to shutdown (whether by client
168+
* request or from filesystem activity).
169+
*/
170+
ipc_server_await(state->ipc_server_data);
171+
172+
/*
173+
* The fsmonitor listener thread may have received a shutdown
174+
* event from the IPC thread pool, but it doesn't hurt to tell
175+
* it again. And wait for it to shutdown.
176+
*/
177+
fsm_listen__stop_async(state);
178+
pthread_join(state->listener_thread, NULL);
179+
180+
return state->error_code;
181+
}
182+
183+
static int fsmonitor_run_daemon(void)
184+
{
185+
struct fsmonitor_daemon_state state;
186+
int err;
187+
188+
memset(&state, 0, sizeof(state));
189+
190+
pthread_mutex_init(&state.main_lock, NULL);
191+
state.error_code = 0;
192+
state.current_token_data = NULL;
193+
194+
/* Prepare to (recursively) watch the <worktree-root> directory. */
195+
strbuf_init(&state.path_worktree_watch, 0);
196+
strbuf_addstr(&state.path_worktree_watch, absolute_path(get_git_work_tree()));
197+
state.nr_paths_watching = 1;
198+
199+
/*
200+
* We create and delete cookie files somewhere inside the .git
201+
* directory to help us keep sync with the file system. If
202+
* ".git" is not a directory, then <gitdir> is not inside the
203+
* cone of <worktree-root>, so set up a second watch to watch
204+
* the <gitdir> so that we get events for the cookie files.
205+
*/
206+
strbuf_init(&state.path_gitdir_watch, 0);
207+
strbuf_addbuf(&state.path_gitdir_watch, &state.path_worktree_watch);
208+
strbuf_addstr(&state.path_gitdir_watch, "/.git");
209+
if (!is_directory(state.path_gitdir_watch.buf)) {
210+
strbuf_reset(&state.path_gitdir_watch);
211+
strbuf_addstr(&state.path_gitdir_watch, absolute_path(get_git_dir()));
212+
state.nr_paths_watching = 2;
213+
}
214+
215+
/*
216+
* Confirm that we can create platform-specific resources for the
217+
* filesystem listener before we bother starting all the threads.
218+
*/
219+
if (fsm_listen__ctor(&state)) {
220+
err = error(_("could not initialize listener thread"));
221+
goto done;
222+
}
223+
224+
err = fsmonitor_run_daemon_1(&state);
225+
226+
done:
227+
pthread_mutex_destroy(&state.main_lock);
228+
fsm_listen__dtor(&state);
229+
230+
ipc_server_free(state.ipc_server_data);
231+
232+
strbuf_release(&state.path_worktree_watch);
233+
strbuf_release(&state.path_gitdir_watch);
234+
235+
return err;
236+
}
237+
238+
static int try_to_run_foreground_daemon(void)
239+
{
240+
/*
241+
* Technically, we don't need to probe for an existing daemon
242+
* process, since we could just call `fsmonitor_run_daemon()`
243+
* and let it fail if the pipe/socket is busy.
244+
*
245+
* However, this method gives us a nicer error message for a
246+
* common error case.
247+
*/
248+
if (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING)
249+
die("fsmonitor--daemon is already running '%s'",
250+
the_repository->worktree);
251+
252+
printf(_("running fsmonitor-daemon in '%s'\n"),
253+
the_repository->worktree);
254+
fflush(stdout);
255+
256+
return !!fsmonitor_run_daemon();
257+
}
258+
60259
int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)
61260
{
62261
const char *subcmd;
63262

64263
struct option options[] = {
264+
OPT_INTEGER(0, "ipc-threads",
265+
&fsmonitor__ipc_threads,
266+
N_("use <n> ipc worker threads")),
65267
OPT_END()
66268
};
67269

@@ -71,14 +273,20 @@ int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)
71273
if (argc == 2 && !strcmp(argv[1], "-h"))
72274
usage_with_options(builtin_fsmonitor__daemon_usage, options);
73275

74-
git_config(git_default_config, NULL);
276+
git_config(fsmonitor_config, NULL);
75277

76278
subcmd = argv[1];
77279
argv--;
78280
argc++;
79281

80282
argc = parse_options(argc, argv, prefix, options,
81283
builtin_fsmonitor__daemon_usage, 0);
284+
if (fsmonitor__ipc_threads < 1)
285+
die(_("invalid 'ipc-threads' value (%d)"),
286+
fsmonitor__ipc_threads);
287+
288+
if (!strcmp(subcmd, "run"))
289+
return !!try_to_run_foreground_daemon();
82290

83291
if (!strcmp(subcmd, "stop"))
84292
return !!do_as_client__send_stop();

fsmonitor--daemon.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#ifndef FSMONITOR_DAEMON_H
2+
#define FSMONITOR_DAEMON_H
3+
4+
#ifdef HAVE_FSMONITOR_DAEMON_BACKEND
5+
6+
#include "cache.h"
7+
#include "dir.h"
8+
#include "run-command.h"
9+
#include "simple-ipc.h"
10+
#include "thread-utils.h"
11+
12+
struct fsmonitor_batch;
13+
struct fsmonitor_token_data;
14+
15+
struct fsmonitor_daemon_backend_data; /* opaque platform-specific data */
16+
17+
struct fsmonitor_daemon_state {
18+
pthread_t listener_thread;
19+
pthread_mutex_t main_lock;
20+
21+
struct strbuf path_worktree_watch;
22+
struct strbuf path_gitdir_watch;
23+
int nr_paths_watching;
24+
25+
struct fsmonitor_token_data *current_token_data;
26+
27+
int error_code;
28+
struct fsmonitor_daemon_backend_data *backend_data;
29+
30+
struct ipc_server_data *ipc_server_data;
31+
};
32+
33+
#endif /* HAVE_FSMONITOR_DAEMON_BACKEND */
34+
#endif /* FSMONITOR_DAEMON_H */

0 commit comments

Comments
 (0)