Skip to contentSkip to conversation

Builtin FSMonitor Feature

RFC PATCH Superseded Jeff Hostetler via GitGitGadget 11 participants 237 messages Apr 1, 2021 – Jul 27, 2021 Page 3 of 3

Subsystems (2)
lore
Share & tools
lore
#222 lore raw

PATCH v3 31/34 fsmonitor: force update index after large responses

In reply to Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Set the FSMONITOR_CHANGED bit on istate->cache_changed when FSMonitor returns a very large repsonse to ensure that the index is written to disk.

Normally, when the FSMonitor response includes a tracked file, the index is always updated. Similarly, the index might be updated when the response alters the untracked-cache (when enabled). However, in cases where neither of those cause the index to be considered changed, the FSMonitor response is wasted. Subsequent Git commands will make requests with the same token and receive the same response.

If that response is very large, performance may suffer. It would be more efficient to force update the index now (and the token in the index extension) in order to reduce the size of the response received by future commands.

This was observed on Windows after a large checkout. On Windows, the kernel emits events for the files that are changed as they are changed. However, it might delay events for the containing directories until the system is more idle (or someone scans the directory (so it seems)). The first status following a checkout would get the list of files. The subsequent status commands would get the list of directories as the events trickled out. But they would never catch up because the token was not advanced because the index wasn't updated.

This list of directories caused wt_status_collect_untracked() to unnecessarily spend time actually scanning them during each command.

---
 fsmonitor.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 49 insertions(+), 1 deletion(-)
fsmonitor.c +49 -1
index f53791c8674..eee653f9337 100644
@@ -236,6 +236,45 @@ static void fsmonitor_refresh_callback(struct index_state *istate, char *name)
 	untracked_cache_invalidate_path(istate, name, 0);
 }
 
+/*
+ * The number of pathnames that we need to receive from FSMonitor
+ * before we force the index to be updated.
+ *
+ * Note that any pathname within the set of received paths MAY cause
+ * cache-entry or istate flag bits to be updated and thus cause the
+ * index to be updated on disk.
+ *
+ * However, the response may contain many paths (such as ignored
+ * paths) that will not update any flag bits.  And thus not force the
+ * index to be updated.  (This is fine and normal.)  It also means
+ * that the token will not be updated in the FSMonitor index
+ * extension.  So the next Git command will find the same token in the
+ * index, make the same token-relative request, and receive the same
+ * response (plus any newly changed paths).  If this response is large
+ * (and continues to grow), performance could be impacted.
+ *
+ * For example, if the user runs a build and it writes 100K object
+ * files but doesn't modify any source files, the index would not need
+ * to be updated.  The FSMonitor response (after the build and
+ * relative to a pre-build token) might be 5MB.  Each subsequent Git
+ * command will receive that same 100K/5MB response until something
+ * causes the index to be updated.  And `refresh_fsmonitor()` will
+ * have to iterate over those 100K paths each time.
+ *
+ * Performance could be improved if we optionally force update the
+ * index after a very large response and get an updated token into
+ * the FSMonitor index extension.  This should allow subsequent
+ * commands to get smaller and more current responses.
+ *
+ * The value chosen here does not need to be precise.  The index
+ * will be updated automatically the first time the user touches
+ * a tracked file and causes a command like `git status` to
+ * update an mtime to be updated and/or set a flag bit.
+ *
+ * NEEDSWORK: Does this need to be a config value?
+ */
+static int fsmonitor_force_update_threshold = 100;
+
 void refresh_fsmonitor(struct index_state *istate)
 {
 	struct strbuf query_result = STRBUF_INIT;
@@ -379,19 +418,28 @@ apply_results:
 		 *
 		 * This updates both the cache-entries and the untracked-cache.
 		 */
+		int count = 0;
+
 		buf = query_result.buf;
 		for (i = bol; i < query_result.len; i++) {
 			if (buf[i] != '\0')
 				continue;
 			fsmonitor_refresh_callback(istate, buf + bol);
 			bol = i + 1;
+			count++;
 		}
-		if (bol < query_result.len)
+		if (bol < query_result.len) {
 			fsmonitor_refresh_callback(istate, buf + bol);
+			count++;
+		}
 
 		/* Now mark the untracked cache for fsmonitor usage */
 		if (istate->untracked)
 			istate->untracked->use_fsmonitor = 1;
+
+		if (count > fsmonitor_force_update_threshold)
+			istate->cache_changed |= FSMONITOR_CHANGED;
+
 	} else {
 		/*
 		 * We received a trivial response, so invalidate everything.
Signature · 1 line
gitgitgadget
#224 lore raw

PATCH v3 33/34 fsmonitor: handle shortname for .git

In reply to Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

On Windows, teach FSMonitor to recognize the shortname of ".git" as an alias for ".git".

Sometimes we receive FS events using the shortname, such as when a CMD shell runs "RENAME GIT~1 FOO" or "RMDIR GIT~1". The FS notification arrives using whatever combination of long and shortnames used by the other process. (Shortnames do seem to be case normalized, however.)

NEEDSWORK: This only addresses the case of removing or renaming the ".git" directory using the shortname alias, so that the daemon properly shuts down. I'm leaving it a task for later to handle the general case of shortnames and report them to the fsmonitor client process. This would include tracked and untracked paths that just happen to have a shortname alias.

---
 compat/fsmonitor/fsmonitor-fs-listen-win32.c | 192 +++++++++++++++----
 t/t7527-builtin-fsmonitor.sh                 |  65 +++++++
 2 files changed, 217 insertions(+), 40 deletions(-)
compat/fsmonitor/fsmonitor-fs-listen-win32.c +152 -40
index d707d47a0d7..f2ea5940790 100644
@@ -48,6 +48,8 @@ struct fsmonitor_daemon_backend_data
 #define LISTENER_HAVE_DATA_WORKTREE 1
 #define LISTENER_HAVE_DATA_GITDIR 2
 	int nr_listener_handles;
+
+	struct strbuf dot_git_shortname;
 };
 
 /*
@@ -250,6 +252,62 @@ static void cancel_rdcw_watch(struct one_watch *watch)
 	watch->is_active = FALSE;
 }
 
+/*
+ * Process a single relative pathname event.
+ * Return 1 if we should shutdown.
+ */
+static int process_1_worktree_event(
+	FILE_NOTIFY_INFORMATION *info,
+	struct string_list *cookie_list,
+	struct fsmonitor_batch **batch,
+	const struct strbuf *path,
+	enum fsmonitor_path_type t)
+{
+	const char *slash;
+
+	switch (t) {
+	case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:
+		/* special case cookie files within .git */
+
+		/* Use just the filename of the cookie file. */
+		slash = find_last_dir_sep(path->buf);
+		string_list_append(cookie_list,
+				   slash ? slash + 1 : path->buf);
+		break;
+
+	case IS_INSIDE_DOT_GIT:
+		/* ignore everything inside of "<worktree>/.git/" */
+		break;
+
+	case IS_DOT_GIT:
+		/* "<worktree>/.git" was deleted (or renamed away) */
+		if ((info->Action == FILE_ACTION_REMOVED) ||
+		    (info->Action == FILE_ACTION_RENAMED_OLD_NAME)) {
+			trace2_data_string("fsmonitor", NULL,
+					   "fsm-listen/dotgit",
+					   "removed");
+			return 1;
+		}
+		break;
+
+	case IS_WORKDIR_PATH:
+		/* queue normal pathname */
+		if (!*batch)
+			*batch = fsmonitor_batch__new();
+		fsmonitor_batch__add_path(*batch, path->buf);
+		break;
+
+	case IS_GITDIR:
+	case IS_INSIDE_GITDIR:
+	case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:
+	default:
+		BUG("unexpected path classification '%d' for '%s'",
+		    t, path->buf);
+	}
+
+	return 0;
+}
+
 /*
  * Process filesystem events that happen anywhere (recursively) under the
  * <worktree> root directory.  For a normal working directory, this includes
@@ -294,7 +352,6 @@ static int process_worktree_events(struct fsmonitor_daemon_state *state)
 	 */
 	for (;;) {
 		FILE_NOTIFY_INFORMATION *info = (void *)p;
-		const char *slash;
 		enum fsmonitor_path_type t;
 
 		strbuf_reset(&path);
@@ -303,45 +360,45 @@ static int process_worktree_events(struct fsmonitor_daemon_state *state)
 
 		t = fsmonitor_classify_path_workdir_relative(path.buf);
 
-		switch (t) {
-		case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:
-			/* special case cookie files within .git */
-
-			/* Use just the filename of the cookie file. */
-			slash = find_last_dir_sep(path.buf);
-			string_list_append(&cookie_list,
-					   slash ? slash + 1 : path.buf);
-			break;
-
-		case IS_INSIDE_DOT_GIT:
-			/* ignore everything inside of "<worktree>/.git/" */
-			break;
-
-		case IS_DOT_GIT:
-			/* "<worktree>/.git" was deleted (or renamed away) */
-			if ((info->Action == FILE_ACTION_REMOVED) ||
-			    (info->Action == FILE_ACTION_RENAMED_OLD_NAME)) {
-				trace2_data_string("fsmonitor", NULL,
-						   "fsm-listen/dotgit",
-						   "removed");
-				goto force_shutdown;
-			}
-			break;
-
-		case IS_WORKDIR_PATH:
-			/* queue normal pathname */
-			if (!batch)
-				batch = fsmonitor_batch__new();
-			fsmonitor_batch__add_path(batch, path.buf);
-			break;
-
-		case IS_GITDIR:
-		case IS_INSIDE_GITDIR:
-		case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:
-		default:
-			BUG("unexpected path classification '%d' for '%s'",
-			    t, path.buf);
-		}
+		if (process_1_worktree_event(info, &cookie_list, &batch,
+					     &path, t))
+			goto force_shutdown;
+
+		/*
+		 * NEEDSWORK: If `path` contains a shortname (that is,
+		 * if any component within it is a shortname), we
+		 * should expand it to a longname (See
+		 * `GetLongPathNameW()`) and re-normalize, classify,
+		 * and process it because our client is probably
+		 * expecting "normal" paths.
+		 *
+		 * HOWEVER, if our process has called `chdir()` to get
+		 * us out of the root of the worktree (so that the
+		 * root directory is not busy), then we have to be
+		 * careful to convert the paths in the INFO array
+		 * (which are relative to the directory of the RDCW
+		 * watch and not the CWD) into absolute paths before
+		 * calling GetLongPathNameW() and then convert the
+		 * computed value back to a RDCW-relative pathname
+		 * (which is what we and the client expect).
+		 *
+		 * FOR NOW, just handle case (1) exactly so that we
+		 * shutdown properly when ".git" is deleted via the
+		 * shortname alias.
+		 *
+		 * We might see case (2) events for cookie files, but
+		 * we can ignore them.
+		 *
+		 * FOR LATER, handle case (3) where the worktree
+		 * events contain shortnames.  We should convert
+		 * them to longnames to avoid confusing the client.
+		 */
+		if (data->dot_git_shortname.len &&
+		    !strcmp(path.buf, data->dot_git_shortname.buf) &&
+		    process_1_worktree_event(info, &cookie_list, &batch,
+					     &data->dot_git_shortname,
+					     IS_DOT_GIT))
+			goto force_shutdown;
 
 skip_this_path:
 		if (!info->NextEntryOffset)
@@ -415,6 +472,14 @@ static int process_gitdir_events(struct fsmonitor_daemon_state *state)
 			    t, path.buf);
 		}
 
+		/*
+		 * WRT shortnames, this external gitdir will not see
+		 * case (1) nor case (3) events.
+		 *
+		 * We might see case (2) events for cookie files, but
+		 * we can ignore them.
+		 */
+
 skip_this_path:
 		if (!info->NextEntryOffset)
 			break;
@@ -493,6 +558,7 @@ clean_shutdown:
 int fsmonitor_fs_listen__ctor(struct fsmonitor_daemon_state *state)
 {
 	struct fsmonitor_daemon_backend_data *data;
+	char shortname[16]; /* a padded 8.3 buffer */
 
 	CALLOC_ARRAY(data, 1);
 
@@ -523,6 +589,52 @@ int fsmonitor_fs_listen__ctor(struct fsmonitor_daemon_state *state)
 		data->nr_listener_handles++;
 	}
 
+	/*
+	 * NEEDSWORK: Properly handle 8.3 shortnames.  RDCW events can
+	 * contain a shortname (if another application uses a
+	 * shortname in a system call).  We care about aliasing and
+	 * the use of shortnames for:
+	 *
+	 * (1) ".git",
+	 *     -- if an external process deletes ".git" using "GIT~1",
+	 *        we need to catch that and shutdown.
+	 *
+	 * (2) our cookie files,
+	 *     -- if an external process deletes one of our cookie
+	 *        files using a shortname, we will get a shortname
+	 *        event for it.  However, we should have already
+	 *        gotten a longname event for it when we created the
+	 *        cookie, so we can safely discard the shortname
+	 *        events for cookie files.
+	 *
+	 * (3) the spelling of modified files that we report to clients.
+	 *     -- we need to report the longname to the client because
+	 *        that is what they are expecting.  Presumably, the
+	 *        client is going to lookup the paths that we report
+	 *        in their index and untracked-cache, so we should
+	 *        normalize the data for them.  (Technically, they
+	 *        could adapt, so we could relax this maybe.)
+	 *
+	 * FOR NOW, while our CWD is at the root of the worktree we
+	 * can easily get the spelling of the shortname of ".git" (if
+	 * the volume has shortnames enabled).  For most worktrees
+	 * this value will be "GIT~1", but we don't want to assume
+	 * that.
+	 *
+	 * Capture this so that we can handle (1).
+	 *
+	 * We leave (3) for a future effort.
+	 */
+	strbuf_init(&data->dot_git_shortname, 0);
+	GetShortPathNameA(".git", shortname, sizeof(shortname));
+	if (!strcmp(".git", shortname))
+		trace_printf_key(&trace_fsmonitor, "No shortname for '.git'");
+	else {
+		trace_printf_key(&trace_fsmonitor,
+				 "Shortname of '.git' is '%s'", shortname);
+		strbuf_addstr(&data->dot_git_shortname, shortname);
+	}
+
 	state->backend_data = data;
 	return 0;
 
t/t7527-builtin-fsmonitor.sh +65 -0

71 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#225 lore raw

PATCH v3 34/34 t7527: test FS event reporing on MacOS WRT case and Unicode

In reply to Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Confirm that MacOS FS events are reported with a normalized spelling.

APFS (and/or HFS+) is case-insensitive. This means that case-independent lookups ( [ -d .git ] and [ -d .GIT ] ) should both succeed. But that doesn't tell us how FS events are reported if we try "rm -rf .git" versus "rm -rf .GIT". Are the events reported using the on-disk spelling of the pathname or in the spelling used by the command.

NEEDSWORK: I was only able to test case. It would be nice to add tests that use different Unicode spellings/normalizations and understand the differences between APFS and HFS+ in this area. We should confirm that the spelling of the workdir paths that the daemon sends to clients are always properly normalized.

---
 t/t7527-builtin-fsmonitor.sh | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)
t/t7527-builtin-fsmonitor.sh +30 -0
index b166b4a0a31..d2ff1bf6c49 100755
@@ -178,6 +178,36 @@ test_expect_success MINGW,SHORTNAMES 'implicit daemon stop (rename GIT~2)' '
 	test_must_fail git -C test_implicit_1s2 fsmonitor--daemon status
 '
 
+# Confirm that MacOS hides all of the Unicode normalization and/or
+# case folding from the FS events.  That is, are the pathnames in the
+# FS events reported using the spelling on the disk or in the spelling
+# used by the other process.
+#
+# Note that we assume that the filesystem is set to case insensitive.
+#
+# NEEDSWORK: APFS handles Unicode and Unicode normalization
+# differently than HFS+.  I only have an APFS partition, so
+# more testing here would be helpful.
+#
+
+# Rename .git using alternate spelling and confirm that the daemon
+# sees the event using the correct spelling and shutdown.
+test_expect_success UTF8_NFD_TO_NFC 'MacOS event spelling (rename .GIT)' '
+	test_when_finished "stop_daemon_delete_repo test_apfs" &&
+
+	git init test_apfs &&
+	start_daemon test_apfs &&
+
+	[ -d test_apfs/.git ] &&
+	[ -d test_apfs/.GIT ] &&
+
+	mv test_apfs/.GIT test_apfs/.FOO &&
+	sleep 1 &&
+	mv test_apfs/.FOO test_apfs/.git &&
+
+	test_must_fail git -C test_apfs fsmonitor--daemon status
+'
+
 test_expect_success 'cannot start multiple daemons' '
 	test_when_finished "stop_daemon_delete_repo test_multiple" &&
 
Signature · 1 line
gitgitgadget
#136 lore raw

Re: [PATCH v3 06/34] fsmonitor: config settings are repository-specific

In reply to Jeff Hostetler via GitGitGadget

On Thu, Jul 01 2021, Jeff Hostetler via GitGitGadget wrote:

In a reference to a discussion[1] about an earlier version of this patch you said:

I'm going to ignore all of the thread responses to this patch dealing with how we acquire config settings and macros and etc. Those issues are completely independent of FSMonitor (which is already way too big).

Since then the changes to repo-settings.c have become a lot larger, so let's take a look...

1. https://lore.kernel.org/git/[email protected]/
2. https://lore.kernel.org/git/[email protected]/

Quoted 11 lines
repo-settings.c +3 -0
index 0cfe8b787db..faf197ff60a 100644
@@ -5,10 +5,42 @@
 
 #define UPDATE_DEFAULT_BOOL(s,v) do { if (s == -1) { s = v; } } while(0)
 
+/*
+ * Return 1 if the repo/workdir is incompatible with FSMonitor.
+ */

repo-settings.c line 20

Quoted 10 lines
repo-settings.c +10 -0
+static int is_repo_incompatible_with_fsmonitor(struct repository *r)
+{
+	const char *const_strval;
+
+	/*
+	 * Bare repositories don't have a working directory and
+	 * therefore, nothing to watch.
+	 */
+	if (!r->worktree)
+		return 1;

Looking ahead in this series you end up using FSMONITOR_MODE_INCOMPATIBLE in two places in the codebase. In builtin/update-index.c to throw a "repository is incompatible with fsmonitor" error.

Can't that case just be replaced with setup_work_tree()? Other sub-modes of update-index already die implicitly on that, e.g.:

	$ git update-index test
	fatal: this operation must be run in a work tree

The other case is:

+ prepare_repo_settings(the_repository);
+ if (!the_repository->worktree)
+ return error(_("fsmonitor-daemon does not support bare repos '%s'"),
+ xgetcwd());
+ if (the_repository->settings.fsmonitor_mode == FSMONITOR_MODE_INCOMPATIBLE)
+ return error(_("fsmonitor-daemon is incompatible with this repo '%s'"),
+ the_repository->worktree);

I.e. we just checked the_repository->worktree, but it's not that, but....

Quoted 4 lines
repo-settings.c +4 -0
+
+	/*
+	 * GVFS (aka VFS for Git) is incompatible with FSMonitor.
+	 *

repo-settings.c line 34

Quoted 10 lines
repo-settings.c +10 -0
+	 * Granted, core Git does not know anything about GVFS and
+	 * we shouldn't make assumptions about a downstream feature,
+	 * but users can install both versions.  And this can lead
+	 * to incorrect results from core Git commands.  So, without
+	 * bringing in any of the GVFS code, do a simple config test
+	 * for a published config setting.  (We do not look at the
+	 * various *_TEST_* environment variables.)
+	 */
+	if (!repo_config_get_value(r, "core.virtualfilesystem", &const_strval))
+		return 1;

I'm skeptical of us hardcoding a third-party software config variable. Can't GitVFS handle this somehow on its end?

But just in terms of implementation it seems the end result of that is to emit a very confusing error to the user. Sinc we already checked for bare repos we run into this and instead of sayingwhen we should really say "hey, maybe disable your core.virtualFileSystem setting", we say "your repo is incompatible".

repo-settings.c line 43

Quoted 9 lines
repo-settings.c +5 -0
+
+	return 0;
+}
+
 void prepare_repo_settings(struct repository *r)
 {
 	int value;
 	char *strval;
+	const char *const_strval;

Can be declared in the "else" below.

repo-settings.c void prepare_repo_settings(struct repository *r) · line 62

Quoted 9 lines
repo-settings.c +2 -0
 	if (r->settings.initialized)
 		return;
@@ -26,6 +58,22 @@ void prepare_repo_settings(struct repository *r)
 	UPDATE_DEFAULT_BOOL(r->settings.commit_graph_read_changed_paths, 1);
 	UPDATE_DEFAULT_BOOL(r->settings.gc_write_commit_graph, 1);
 
+	r->settings.fsmonitor_hook_path = NULL;
+	r->settings.fsmonitor_mode = FSMONITOR_MODE_DISABLED;

With the memset earlier (b.t.w. I've got a patch to fix all this bizarre behavior in repo-settings.c, but have been waiting on this series we implicitly set it to FSMONITOR_MODE_UNSET (-1) with the memset, but then never use that ever.

Your code in update-index.c then for a check against "FSMONITOR_MODE_DISABLED" says "core.useBuiltinFSMonitor is unset;".

repo-settings.c void prepare_repo_settings(struct repository *r) · line 64

Quoted 2 lines
repo-settings.c +2 -0
+	if (is_repo_incompatible_with_fsmonitor(r))
+		r->settings.fsmonitor_mode = FSMONITOR_MODE_INCOMPATIBLE;

Style: should have {} braces on all arms.

repo-settings.c void prepare_repo_settings(struct repository *r) · line 67

Quoted 3 lines
repo-settings.c +3 -0
+	else if (!repo_config_get_bool(r, "core.usebuiltinfsmonitor", &value)
+		   && value)
+		r->settings.fsmonitor_mode = FSMONITOR_MODE_IPC;

Here you're conflating false with whether the variable is set at all. I guess that works out here since if it's false we want to fall through to...

repo-settings.c void prepare_repo_settings(struct repository *r) · line 68

Quoted 1 line
repo-settings.c +1 -0
+	else {

...ignoring it and looing at core.fsmonitor instead.

repo-settings.c void prepare_repo_settings(struct repository *r) · line 70

Quoted 2 lines
repo-settings.c +2 -0
+		if (repo_config_get_pathname(r, "core.fsmonitor", &const_strval))
+			const_strval = getenv("GIT_TEST_FSMONITOR");

If it's not set we pay attention to GIT_TEST_FSMONITOR, so the behavior from the old git_config_get_fsmonitor(). So even if the env variable is set we want to take the config variable over it, correct?

repo-settings.c void prepare_repo_settings(struct repository *r) · line 72

Quoted 2 lines
repo-settings.c +2 -0
+		if (const_strval && *const_strval) {
+			r->settings.fsmonitor_hook_path = strdup(const_strval);

We had a strbuf_detach()'d string in the case of repo_config_get_pathname(), but here we strdup() it again in case we were in the getenv() codepath. This code probably leaks memory now anyway, but perhaps it's better to split up the two so we make it easier to deal with who owns/frees what in the future.

#227 lore raw
In reply to Jeff Hostetler via GitGitGadget
Quoted 10 lines · Jeff Hostetler via GitGitGadget

Here is V3 of my patch series to add a builtin FSMonitor daemon to Git. I rebased this series onto v2.32.0.

V3 addresses most of the previous review comments and things we've learned from our experimental testing of V2. (A version of V2 was shipped as an experimental feature in the v2.32.0-based releases of Git for Windows and VFS for Git.)

There are still a few items that I need to address, but that list is getting very short.

...

Quoted 2 lines

fsmonitor-fs-listen-win32: stub in backend for Windows
fsmonitor-fs-listen-macos: stub in backend for MacOS

I left some light comments on the repo-settings.c part of this to follow up from a previous round.

Any other testing of it is stalled by there being no linux backend for it as part of this series. I see from spelunking repos that Johannes had a WIP compat/fsmonitor/linux.c which looks like it could/should mostly work, but the API names all changed since then, and after a short try I gave up on trying to rebase it.

I'd really prefer for git not to have features that place free platforms at a disadvantage against proprietary platforms if it can be avoided, and in this case the lack of a Linux backend also means much less widespread testing of the feature among the development community / CI.

#228 lore raw
Quoted 14 lines · Ævar Arnfjörð Bjarmason
Quoted 10 lines · Jeff Hostetler via GitGitGadget

Here is V3 of my patch series to add a builtin FSMonitor daemon to Git. I rebased this series onto v2.32.0.

V3 addresses most of the previous review comments and things we've learned from our experimental testing of V2. (A version of V2 was shipped as an experimental feature in the v2.32.0-based releases of Git for Windows and VFS for Git.)

There are still a few items that I need to address, but that list is getting very short.

Quoted 6 lines

...

Quoted 2 lines

fsmonitor-fs-listen-win32: stub in backend for Windows
fsmonitor-fs-listen-macos: stub in backend for MacOS

I left some light comments on the repo-settings.c part of this to follow up from a previous round.

Thanks.

Quoted 6 lines

Any other testing of it is stalled by there being no linux backend for it as part of this series. I see from spelunking repos that Johannes had a WIP compat/fsmonitor/linux.c which looks like it could/should mostly work, but the API names all changed since then, and after a short try I gave up on trying to rebase it.

The early Linux version was dropped because inotify does not give recursive coverage -- only the requested directory. Using inotify requires adding a watch to each subdirectory (recursively) in the worktree. There's a system limit on the maximum number of watched directories (defaults to 8K IIRC) and that limit is system-wide.

Since the whole point was to support large very large repos, using inotify was a non-starter, so I removed the Linux version from our patch series. For example, the first repo I tried it on (outside of the test suite) had 25K subdirectories.

I'm told there is a new fanotify API in recent Linux kernels that is a better fit for what we need, but we haven't had time to investigate it yet.

Quoted 6 lines

I'd really prefer for git not to have features that place free platforms at a disadvantage against proprietary platforms if it can be avoided, and in this case the lack of a Linux backend also means much less widespread testing of the feature among the development community / CI.

This feature is always going to have platform-specific components, so the lack of one platform or another should not stop us from discussing it for the platforms that can be supported.

And given the size and complexity of the platform-specific code, we should not assume that "just test it on Linux" is sufficient. Yes, there are some common/shared routines/data structures in the daemon, but hard/tricky parts are in the platform layer.

Jeff

#197 lore raw

Re: [PATCH v3 23/34] t/helper/test-touch: add helper to touch a series of files

In reply to Jeff Hostetler via GitGitGadget
Quoted 9 lines · Jeff Hostetler via GitGitGadget
t/helper/test-touch.c new file +3 -0
new file mode 100644
index 00000000000..e9b3b754f1f
@@ -0,0 +1,126 @@
+/*
+ * test-touch.c: variation on /usr/bin/touch to speed up tests
+ * with a large number of files (primarily on Windows where child

t/helper/test-touch.c line 13

Quoted 10 lines
t/helper/test-touch.c new file +10 -0
+ * process are very, very expensive).
+ */
+
+#include "test-tool.h"
+#include "cache.h"
+#include "parse-options.h"
+
+char *seq_pattern;
+int seq_start = 1;
+int seq_count = 1;

With this in, "make sparse" dies like this:

SP t/helper/test-touch.c
t/helper/test-touch.c:11:6: error: symbol 'seq_pattern' was not declared. Should it be static? t/helper/test-touch.c:12:5: error: symbol 'seq_start' was not declared. Should it be static? t/helper/test-touch.c:13:5: error: symbol 'seq_count' was not declared. Should it be static?

#229 lore raw
In reply to Jeff Hostetler
Quoted 35 lines · Jeff Hostetler
Quoted 10 lines · Ævar Arnfjörð Bjarmason
Quoted 8 lines · Jeff Hostetler via GitGitGadget

Here is V3 of my patch series to add a builtin FSMonitor daemon to Git. I rebased this series onto v2.32.0.

V3 addresses most of the previous review comments and things we've learned from our experimental testing of V2. (A version of V2 was shipped as an experimental feature in the v2.32.0-based releases of Git for Windows and VFS for Git.)

Quoted 8 lines
Quoted 2 lines

There are still a few items that I need to address, but that list is getting very short.

...

Quoted 2 lines

fsmonitor-fs-listen-win32: stub in backend for Windows
fsmonitor-fs-listen-macos: stub in backend for MacOS

I left some light comments on the repo-settings.c part of this to follow
up from a previous round.

Thanks.

Quoted 6 lines

Any other testing of it is stalled by there being no linux backend for
it as part of this series. I see from spelunking repos that Johannes had a WIP compat/fsmonitor/linux.c which looks like it could/should mostly work, but the API names all changed since then, and after a short try I gave up on trying to rebase it.

The early Linux version was dropped because inotify does not give recursive coverage -- only the requested directory. Using inotify requires adding a watch to each subdirectory (recursively) in the worktree. There's a system limit on the maximum number of watched directories (defaults to 8K IIRC) and that limit is system-wide.

Quoted 8 lines

Since the whole point was to support large very large repos, using inotify was a non-starter, so I removed the Linux version from our patch series. For example, the first repo I tried it on (outside of the test suite) had 25K subdirectories.

I'm told there is a new fanotify API in recent Linux kernels that is a better fit for what we need, but we haven't had time to investigate it yet.

That default limit is a bit annoying, but I don't see how it's a blocker in any way.

You simply adjust the limit. E.g. I deployed and tested the hook version of inotify (using watchman) in a sizable development environment, and written my own code using the API. This was all before fanotify(7) existed. IIRC I set most of the limits to 2^24 or 2^20. I've used it with really large Git repos, including with David Turner's 2015-04-03-1M-git for testing (git ls-files | wc -l on that is around a quarter-million).

If you have a humongous repository and don't have root on your own machine you're out of luck. But I think most people who'd use this are either using their own laptop, or e.g. in a corporate setting where administrators would tweak the sysctl limits given the performance advantages (as I did).

Once you adjust the limits Linux deals with large trees just fine, it just has overly conservative limits for most things in sysctl. The API is a bit annoying, your event loop needs to run around and add watches.

AFAICT you need Linux 5.1 for fanotify(7) to be useful, e.g. Debian stable, RHEL etc. aren't using anything that new. So having an inotify backend as well as possibly a fanotify one would be very useful.

And linux.git's docs suggest that the default limit was bumped from 8192 to 1M in v5.10, a really recent kernel, so if you've got that you've also got fanotify.

In any case, even if Linux's inotify limit was something hardcoded and impossible to change you could still use such an API to test & debug the basics of this feature on that platform.

Quoted 10 lines
Quoted 6 lines

I'd really prefer for git not to have features that place free platforms
at a disadvantage against proprietary platforms if it can be avoided, and in this case the lack of a Linux backend also means much less widespread testing of the feature among the development community / CI.

This feature is always going to have platform-specific components, so the lack of one platform or another should not stop us from discussing it for the platforms that can be supported.

(I think per the above that's s/can be/are/)

Quoted 4 lines

And given the size and complexity of the platform-specific code, we should not assume that "just test it on Linux" is sufficient. Yes, there are some common/shared routines/data structures in the daemon, but hard/tricky parts are in the platform layer.

I think we're talking past each other a bit here. I'm not saying that you can get full or meaningful testing for it on Windows if you test it on Linux, or the other way around.

Of course there's platform-specific stuff, although there's also a lot of non-platform-specific stuff, so even a very basic implementation of inotify would make reviwing this easier / give access to more reviewers.

I'm saying that I prefer that Git as a free software project not be in the situation of saying the best use-case for a given size/shape of repo is to use Git in combination with proprietary operating systems over freely licensed ones.

IOW what the FSF has a policy for GNU projects. Now, I think the FSF probably goes too far in that (famously removing rather obscure font rendering features from Emacs on OSX), but "manage lots of data" is a core feature of git.

#168 lore raw

Re: [PATCH v3 14/34] fsmonitor--daemon: implement 'start' command

In reply to Jeff Hostetler via GitGitGadget
Quoted 68 lines · Jeff Hostetler via GitGitGadget
builtin/fsmonitor--daemon.c +68 -0
+#ifdef GIT_WINDOWS_NATIVE
+/*
+ * Create a background process to run the daemon.  It should be completely
+ * disassociated from the terminal.
+ *
+ * Conceptually like `daemonize()` but different because Windows does not
+ * have `fork(2)`.  Spawn a normal Windows child process but without the
+ * limitations of `start_command()` and `finish_command()`.
+ *
+ * The child process execs the "git fsmonitor--daemon run" command.
+ *
+ * The current process returns so that the caller can wait for the child
+ * to startup before exiting.
+ */
+static int spawn_background_fsmonitor_daemon(pid_t *pid)
+{
+	char git_exe[MAX_PATH];
+	struct strvec args = STRVEC_INIT;
+	int in, out;
+
+	GetModuleFileNameA(NULL, git_exe, MAX_PATH);
+
+	in = open("/dev/null", O_RDONLY);
+	out = open("/dev/null", O_WRONLY);
+
+	strvec_push(&args, git_exe);
+	strvec_push(&args, "fsmonitor--daemon");
+	strvec_push(&args, "run");
+	strvec_pushf(&args, "--ipc-threads=%d", fsmonitor__ipc_threads);
+
+	*pid = mingw_spawnvpe(args.v[0], args.v, NULL, NULL, in, out, out);
+	close(in);
+	close(out);
+
+	strvec_clear(&args);
+
+	if (*pid < 0)
+		return error(_("could not spawn fsmonitor--daemon in the background"));
+
+	return 0;
+}
+#else
+/*
+ * Create a background process to run the daemon.  It should be completely
+ * disassociated from the terminal.
+ *
+ * This is adapted from `daemonize()`.  Use `fork()` to directly
+ * create and run the daemon in the child process.
+ *
+ * The fork-child can just call the run code; it does not need to exec
+ * it.
+ *
+ * The fork-parent returns the child PID so that we can wait for the
+ * child to startup before exiting.
+ */
+static int spawn_background_fsmonitor_daemon(pid_t *pid)
+{
+	*pid = fork();
+
+	switch (*pid) {
+	case 0:
+		if (setsid() == -1)
+			error_errno(_("setsid failed"));
+		close(0);
+		close(1);
+		close(2);
+		sanitize_stdfds();
+

builtin/fsmonitor--daemon.c static int try_to_run_foreground_daemon(void) · line 349

Quoted 10 lines
builtin/fsmonitor--daemon.c +10 -0
+		return !!fsmonitor_run_daemon();
+
+	case -1:
+		return error_errno(_("could not spawn fsmonitor--daemon in the background"));
+
+	default:
+		return 0;
+	}
+}
+#endif

The spawn_background_fsmonitor_daemon() function here is almost the same as daemonize(). I wonder if this & the Windows-specific one you have here can't be refactored into an API from what's now in setup.c.

Then we could make builtin/gc.c and daemon.c use that, so Windows could have background GC, and we'd have a more battle-tested central codepath for this tricky bit.

It seems to me like the only limitations on it are to have this return slightly more general things (e.g. not set its own errors, return structured data), and maybe some callback for what to do in the child/parent.

Quoted 59 lines
builtin/fsmonitor--daemon.c +59 -0
+/*
+ * This is adapted from `wait_or_whine()`.  Watch the child process and
+ * let it get started and begin listening for requests on the socket
+ * before reporting our success.
+ */
+static int wait_for_background_startup(pid_t pid_child)
+{
+	int status;
+	pid_t pid_seen;
+	enum ipc_active_state s;
+	time_t time_limit, now;
+
+	time(&time_limit);
+	time_limit += fsmonitor__start_timeout_sec;
+
+	for (;;) {
+		pid_seen = waitpid(pid_child, &status, WNOHANG);
+
+		if (pid_seen == -1)
+			return error_errno(_("waitpid failed"));
+		else if (pid_seen == 0) {
+			/*
+			 * The child is still running (this should be
+			 * the normal case).  Try to connect to it on
+			 * the socket and see if it is ready for
+			 * business.
+			 *
+			 * If there is another daemon already running,
+			 * our child will fail to start (possibly
+			 * after a timeout on the lock), but we don't
+			 * care (who responds) if the socket is live.
+			 */
+			s = fsmonitor_ipc__get_state();
+			if (s == IPC_STATE__LISTENING)
+				return 0;
+
+			time(&now);
+			if (now > time_limit)
+				return error(_("fsmonitor--daemon not online yet"));
+		} else if (pid_seen == pid_child) {
+			/*
+			 * The new child daemon process shutdown while
+			 * it was starting up, so it is not listening
+			 * on the socket.
+			 *
+			 * Try to ping the socket in the odd chance
+			 * that another daemon started (or was already
+			 * running) while our child was starting.
+			 *
+			 * Again, we don't care who services the socket.
+			 */
+			s = fsmonitor_ipc__get_state();
+			if (s == IPC_STATE__LISTENING)
+				return 0;
+
+			/*
+			 * We don't care about the WEXITSTATUS() nor
+			 * any of the WIF*(status) values because
+			 * `cmd_fsmonitor__daemon()` does the `!!result`

builtin/fsmonitor--daemon.c static int try_to_run_foreground_daemon(void) · line 419

Quoted 10 lines
builtin/fsmonitor--daemon.c +10 -0
+			 * trick on all function return values.
+			 *
+			 * So it is sufficient to just report the
+			 * early shutdown as an error.
+			 */
+			return error(_("fsmonitor--daemon failed to start"));
+		} else
+			return error(_("waitpid is confused"));
+	}
+}

Ditto this. could we extend the wait_or_whine() function (or some extended version thereof) to do what you need with callbacks?

It seems the main difference is just being able to pass down a flag for waitpid(), and the loop needing to check EINTR or not depending on whether WNOHANG is passed.

For e.g. the "We don't care about the WEXITSTATUS()" you'd get that behavior with an adjusted wait_or_whine(). Wouldn't it be better to report what exit status it exits with e.g. if the top-level process is signalled? We do so in trace2 for other things we spawn...

#125 lore raw

Re: [PATCH v3 02/34] fsmonitor--daemon: man page

In reply to Jeff Hostetler via GitGitGadget
Quoted 81 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Create a manual page describing the git fsmonitor--daemon feature.

---
 Documentation/git-fsmonitor--daemon.txt | 75 +++++++++++++++++++++++++
 1 file changed, 75 insertions(+)
 create mode 100644 Documentation/git-fsmonitor--daemon.txt
Documentation/git-fsmonitor--daemon.txt new file +65 -0
new file mode 100644
index 00000000000..154e7684daa
@@ -0,0 +1,75 @@
+git-fsmonitor--daemon(1)
+========================
+
+NAME
+----
+git-fsmonitor--daemon - A Built-in File System Monitor
+
+SYNOPSIS
+--------
+[verse]
+'git fsmonitor--daemon' start
+'git fsmonitor--daemon' run
+'git fsmonitor--daemon' stop
+'git fsmonitor--daemon' status
+
+DESCRIPTION
+-----------
+
+A daemon to watch the working directory for file and directory
+changes using platform-specific file system notification facilities.
+
+This daemon communicates directly with commands like `git status`
+using the link:technical/api-simple-ipc.html[simple IPC] interface
+instead of the slower linkgit:githooks[5] interface.
+
+This daemon is built into Git so that no third-party tools are
+required.
+
+OPTIONS
+-------
+
+start::
+	Starts a daemon in the background.
+
+run::
+	Runs a daemon in the foreground.
+
+stop::
+	Stops the daemon running in the current working
+	directory, if present.
+
+status::
+	Exits with zero status if a daemon is watching the
+	current working directory.
+
+REMARKS
+-------
+
+This daemon is a long running process used to watch a single working
+directory and maintain a list of the recently changed files and
+directories.  Performance of commands such as `git status` can be
+increased if they just ask for a summary of changes to the working
+directory and can avoid scanning the disk.
+
+When `core.useBuiltinFSMonitor` is set to `true` (see
+linkgit:git-config[1]) commands, such as `git status`, will ask the
+daemon for changes and automatically start it (if necessary).
+
+For more information see the "File System Monitor" section in
+linkgit:git-update-index[1].
+
+CAVEATS
+-------
+
+The fsmonitor daemon does not currently know about submodules and does

Documentation/git-fsmonitor--daemon.txt line 75

Quoted 10 lines
Documentation/git-fsmonitor--daemon.txt new file +10 -0
+not know to filter out file system events that happen within a
+submodule.  If fsmonitor daemon is watching a super repo and a file is
+modified within the working directory of a submodule, it will report
+the change (as happening against the super repo).  However, the client
+will properly ignore these extra events, so performance may be affected
+but it will not cause an incorrect result.
+
+GIT
+---
+Part of the linkgit:git[1] suite

Later in the series we incrementally add features to the daemon, so this is describing a state that doesn't exist yet at this point.

I think it would be better to start with a stup here and add documentation as we add features, e.g. the patch tha adds "start" should add that to the synopsis + options etc.

See the outstanding ab/config-based-hooks-base for a small example of that.

#132 lore raw

Re: [PATCH v3 03/34] fsmonitor--daemon: update fsmonitor documentation

In reply to Jeff Hostetler via GitGitGadget
Quoted 131 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Update references to core.fsmonitor and core.fsmonitorHookVersion and pointers to Watchman to mention the new built-in fsmonitor--daemon.

---
 Documentation/config/core.txt      | 56 ++++++++++++++++++++++--------
 Documentation/git-update-index.txt | 27 +++++++-------
 Documentation/githooks.txt         |  3 +-
 3 files changed, 59 insertions(+), 27 deletions(-)
Documentation/config/core.txt +42 -14
index c04f62a54a1..4f6e519bc02 100644
@@ -62,22 +62,50 @@ core.protectNTFS::
 	Defaults to `true` on Windows, and `false` elsewhere.
 
 core.fsmonitor::
-	If set, the value of this variable is used as a command which
-	will identify all files that may have changed since the
-	requested date/time. This information is used to speed up git by
-	avoiding unnecessary processing of files that have not changed.
-	See the "fsmonitor-watchman" section of linkgit:githooks[5].
+	If set, this variable contains the pathname of the "fsmonitor"
+	hook command.
++
+This hook command is used to identify all files that may have changed
+since the requested date/time. This information is used to speed up
+git by avoiding unnecessary scanning of files that have not changed.
++
+See the "fsmonitor-watchman" section of linkgit:githooks[5].
++
+Note: The value of this config setting is ignored if the
+built-in file system monitor is enabled (see `core.useBuiltinFSMonitor`).
 
 core.fsmonitorHookVersion::
-	Sets the version of hook that is to be used when calling fsmonitor.
-	There are currently versions 1 and 2. When this is not set,
-	version 2 will be tried first and if it fails then version 1
-	will be tried. Version 1 uses a timestamp as input to determine
-	which files have changes since that time but some monitors
-	like watchman have race conditions when used with a timestamp.
-	Version 2 uses an opaque string so that the monitor can return
-	something that can be used to determine what files have changed
-	without race conditions.
+	Sets the protocol version to be used when invoking the
+	"fsmonitor" hook.
++
+There are currently versions 1 and 2. When this is not set,
+version 2 will be tried first and if it fails then version 1
+will be tried. Version 1 uses a timestamp as input to determine
+which files have changes since that time but some monitors
+like Watchman have race conditions when used with a timestamp.
+Version 2 uses an opaque string so that the monitor can return
+something that can be used to determine what files have changed
+without race conditions.
++
+Note: The value of this config setting is ignored if the
+built-in file system monitor is enabled (see `core.useBuiltinFSMonitor`).
+
+core.useBuiltinFSMonitor::
+	If set to true, enable the built-in file system monitor
+	daemon for this working directory (linkgit:git-fsmonitor--daemon[1]).
++
+Like hook-based file system monitors, the built-in file system monitor
+can speed up Git commands that need to refresh the Git index
+(e.g. `git status`) in a working directory with many files.  The
+built-in monitor eliminates the need to install and maintain an
+external third-party tool.
++
+The built-in file system monitor is currently available only on a
+limited set of supported platforms.  Currently, this includes Windows
+and MacOS.
++
+Note: if this config setting is set to `true`, the values of
+`core.fsmonitor` and `core.fsmonitorHookVersion` are ignored.
 
 core.trustctime::
 	If false, the ctime differences between the index and the
Documentation/git-update-index.txt +15 -12
index 2853f168d97..c7c31b3fcf9 100644
@@ -498,7 +498,9 @@ FILE SYSTEM MONITOR
 This feature is intended to speed up git operations for repos that have
 large working directories.
 
-It enables git to work together with a file system monitor (see the
+It enables git to work together with a file system monitor (see
+linkgit:git-fsmonitor--daemon[1]
+and the
 "fsmonitor-watchman" section of linkgit:githooks[5]) that can
 inform it as to what files have been modified. This enables git to avoid
 having to lstat() every file to find modified files.
@@ -508,17 +510,18 @@ performance by avoiding the cost of scanning the entire working directory
 looking for new files.
 
 If you want to enable (or disable) this feature, it is easier to use
-the `core.fsmonitor` configuration variable (see
-linkgit:git-config[1]) than using the `--fsmonitor` option to
-`git update-index` in each repository, especially if you want to do so
-across all repositories you use, because you can set the configuration
-variable in your `$HOME/.gitconfig` just once and have it affect all
-repositories you touch.
-
-When the `core.fsmonitor` configuration variable is changed, the
-file system monitor is added to or removed from the index the next time
-a command reads the index. When `--[no-]fsmonitor` are used, the file
-system monitor is immediately added to or removed from the index.
+the `core.fsmonitor` or `core.useBuiltinFSMonitor` configuration
+variable (see linkgit:git-config[1]) than using the `--fsmonitor`
+option to `git update-index` in each repository, especially if you
+want to do so across all repositories you use, because you can set the
+configuration variable in your `$HOME/.gitconfig` just once and have
+it affect all repositories you touch.
+
+When the `core.fsmonitor` or `core.useBuiltinFSMonitor` configuration
+variable is changed, the file system monitor is added to or removed
+from the index the next time a command reads the index. When
+`--[no-]fsmonitor` are used, the file system monitor is immediately
+added to or removed from the index.
 
 CONFIGURATION
 -------------
Documentation/githooks.txt +0 -0
index b51959ff941..b7d5e926f7b 100644

Documentation/githooks.txt fsmonitor-watchman · line 600

Quoted 9 lines
Documentation/githooks.txt +2 -1
@@ -593,7 +593,8 @@ fsmonitor-watchman
 
 This hook is invoked when the configuration option `core.fsmonitor` is
 set to `.git/hooks/fsmonitor-watchman` or `.git/hooks/fsmonitor-watchmanv2`
-depending on the version of the hook to use.
+depending on the version of the hook to use, unless overridden via
+`core.useBuiltinFSMonitor` (see linkgit:git-config[1]).
 
 Version 1 takes two arguments, a version (1) and the time in elapsed
 nanoseconds since midnight, January 1, 1970.

Ditto the comment on 02/34, mostly docs for things that don't exist until in later patches.

#140 lore raw

Re: [PATCH v3 08/34] fsmonitor--daemon: add a built-in fsmonitor daemon

In reply to Jeff Hostetler via GitGitGadget

On Thu, Jul 01 2021, Jeff Hostetler via GitGitGadget wrote:

A general comment on this series (including previous patches). We've usually tried to bend over backwards in git's codebase not to have big ifdef blocks, so we compile most code the same everywhere. We waste a bit of object code, but that's fine.

See 9c897c5c2ad (pack-objects: remove #ifdef NO_PTHREADS, 2018-11-03) for a good exmaple of bad code being turned to good.

E.g. in this case:

Quoted 31 lines
builtin/fsmonitor--daemon.c new file +31 -0
+#ifdef HAVE_FSMONITOR_DAEMON_BACKEND
+
+int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)
+{
+	const char *subcmd;
+
+	struct option options[] = {
+		OPT_END()
+	};
+
+	if (argc < 2)
+		usage_with_options(builtin_fsmonitor__daemon_usage, options);
+
+	if (argc == 2 && !strcmp(argv[1], "-h"))
+		usage_with_options(builtin_fsmonitor__daemon_usage, options);
+
+	git_config(git_default_config, NULL);
+
+	subcmd = argv[1];
+	argv--;
+	argc++;
+
+	argc = parse_options(argc, argv, prefix, options,
+			     builtin_fsmonitor__daemon_usage, 0);
+
+	die(_("Unhandled subcommand '%s'"), subcmd);
+}
+
+#else
+int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)
+{

builtin/fsmonitor--daemon.c line 53

Quoted 10 lines
builtin/fsmonitor--daemon.c new file +10 -0
+	struct option options[] = {
+		OPT_END()
+	};
+
+	if (argc == 2 && !strcmp(argv[1], "-h"))
+		usage_with_options(builtin_fsmonitor__daemon_usage, options);
+
+	die(_("fsmonitor--daemon not supported on this platform"));
+}
+#endif

This whole thing could really just be a -DHAVE_FSMONITOR_DAEMON_BACKEND=1 or -DHAVE_FSMONITOR_DAEMON_BACKEND=0 somewhere (depending), and then somewhere in the middle of the first function:

	if (!HAVE_FSMONITOR_DAEMON_BACKEND)
	    	die(_("fsmonitor--daemon not supported on this platform"));
#144 lore raw

Re: [PATCH v3 10/34] t/helper/fsmonitor-client: create IPC client to talk to FSMonitor Daemon

In reply to Jeff Hostetler via GitGitGadget
Quoted 37 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Create an IPC client to send query and flush commands to the daemon.

---
 Makefile                         |   1 +
 t/helper/test-fsmonitor-client.c | 121 +++++++++++++++++++++++++++++++
 t/helper/test-tool.c             |   1 +
 t/helper/test-tool.h             |   1 +
 4 files changed, 124 insertions(+)
 create mode 100644 t/helper/test-fsmonitor-client.c
Makefile +1 -0
index 8fe1e42a435..c45caacf2c3 100644
@@ -709,6 +709,7 @@ TEST_BUILTINS_OBJS += test-dump-split-index.o
 TEST_BUILTINS_OBJS += test-dump-untracked-cache.o
 TEST_BUILTINS_OBJS += test-example-decorate.o
 TEST_BUILTINS_OBJS += test-fast-rebase.o
+TEST_BUILTINS_OBJS += test-fsmonitor-client.o
 TEST_BUILTINS_OBJS += test-genrandom.o
 TEST_BUILTINS_OBJS += test-genzeros.o
 TEST_BUILTINS_OBJS += test-hash-speed.o
t/helper/test-fsmonitor-client.c new file +6 -0
new file mode 100644
index 00000000000..f7a5b3a32fa
@@ -0,0 +1,121 @@
+/*
+ * test-fsmonitor-client.c: client code to send commands/requests to
+ * a `git fsmonitor--daemon` daemon.
+ */
+
+#include "test-tool.h"

t/helper/test-fsmonitor-client.c line 16

Quoted 10 lines
t/helper/test-fsmonitor-client.c new file +10 -0
+#include "cache.h"
+#include "parse-options.h"
+#include "fsmonitor-ipc.h"
+
+#ifndef HAVE_FSMONITOR_DAEMON_BACKEND
+int cmd__fsmonitor_client(int argc, const char **argv)
+{
+	die("fsmonitor--daemon not available on this platform");
+}
+#else

Re my earlier comments on excessive ifdefs: In this case don't we just want to not compile test-fsmonitor-client at all unless HAVE_FSMONITOR_DAEMON_BACKEND is true.

You'll get the same error as though you ran "helper/test-tool does-not-exist", but the tests check for the prerequisite earlier anyway, so why get this far on an unsupported platform for a pure test helper?

#146 lore raw

Re: [PATCH v3 11/34] fsmonitor-fs-listen-win32: stub in backend for Windows

In reply to Jeff Hostetler via GitGitGadget
Quoted 44 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Stub in empty backend for fsmonitor--daemon on Windows.

---
 Makefile                                     | 13 ++++++
 compat/fsmonitor/fsmonitor-fs-listen-win32.c | 21 +++++++++
 compat/fsmonitor/fsmonitor-fs-listen.h       | 49 ++++++++++++++++++++
 config.mak.uname                             |  2 +
 contrib/buildsystems/CMakeLists.txt          |  5 ++
 5 files changed, 90 insertions(+)
 create mode 100644 compat/fsmonitor/fsmonitor-fs-listen-win32.c
 create mode 100644 compat/fsmonitor/fsmonitor-fs-listen.h
Makefile +10 -0
index c45caacf2c3..a2a6e1f20f6 100644
@@ -467,6 +467,11 @@ all::
 # directory, and the JSON compilation database 'compile_commands.json' will be
 # created at the root of the repository.
 #
+# If your platform supports a built-in fsmonitor backend, set
+# FSMONITOR_DAEMON_BACKEND to the "<name>" of the corresponding
+# `compat/fsmonitor/fsmonitor-fs-listen-<name>.c` that implements the
+# `fsmonitor_fs_listen__*()` routines.
+#
 # Define DEVELOPER to enable more compiler warnings. Compiler version
 # and family are auto detected, but could be overridden by defining
 # COMPILER_FEATURES (see config.mak.dev). You can still set
@@ -1929,6 +1934,11 @@ ifdef NEED_ACCESS_ROOT_HANDLER
 	COMPAT_OBJS += compat/access.o
 endif
 
+ifdef FSMONITOR_DAEMON_BACKEND
+	COMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND
+	COMPAT_OBJS += compat/fsmonitor/fsmonitor-fs-listen-$(FSMONITOR_DAEMON_BACKEND).o
+endif
+
 ifeq ($(TCLTK_PATH),)
 NO_TCLTK = NoThanks
 endif

Makefile GIT-BUILD-OPTIONS: FORCE · line 2808

Quoted 6 lines
Makefile +3 -0
@@ -2793,6 +2803,9 @@ GIT-BUILD-OPTIONS: FORCE
 	@echo PAGER_ENV=\''$(subst ','\'',$(subst ','\'',$(PAGER_ENV)))'\' >>$@+
 	@echo DC_SHA1=\''$(subst ','\'',$(subst ','\'',$(DC_SHA1)))'\' >>$@+
 	@echo X=\'$(X)\' >>$@+
+ifdef FSMONITOR_DAEMON_BACKEND
+	@echo FSMONITOR_DAEMON_BACKEND=\''$(subst ','\'',$(subst ','\'',$(FSMONITOR_DAEMON_BACKEND)))'\' >>$@+
+endif

Why put this in an ifdef?

In 342e9ef2d9e (Introduce a performance testing framework, 2012-02-17) we started doing that for some perf/test options (which b.t.w., I don't really see the reason for, maybe it's some subtlety in how test-lib.sh picks those up).

But for all the other compile-time stuff we don't ifdef it, we just define it, and then you get an empty value or not.

This would AFAICT be the first build-time-for-the-C-program option we ifdef for writing a line to GIT-BUILD-OPTIONS.

#157 lore raw

Re: [PATCH v3 12/34] fsmonitor-fs-listen-macos: stub in backend for MacOS

In reply to Jeff Hostetler via GitGitGadget
Quoted 44 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Stub in empty implementation of fsmonitor--daemon
backend for MacOS.

---
 compat/fsmonitor/fsmonitor-fs-listen-macos.c | 20 ++++++++++++++++++++
 config.mak.uname                             |  2 ++
 contrib/buildsystems/CMakeLists.txt          |  3 +++
 3 files changed, 25 insertions(+)
 create mode 100644 compat/fsmonitor/fsmonitor-fs-listen-macos.c
compat/fsmonitor/fsmonitor-fs-listen-macos.c new file +20 -0
new file mode 100644
index 00000000000..b91058d1c4f
@@ -0,0 +1,20 @@
+#include "cache.h"
+#include "fsmonitor.h"
+#include "fsmonitor-fs-listen.h"
+
+int fsmonitor_fs_listen__ctor(struct fsmonitor_daemon_state *state)
+{
+	return -1;
+}
+
+void fsmonitor_fs_listen__dtor(struct fsmonitor_daemon_state *state)
+{
+}
+
+void fsmonitor_fs_listen__stop_async(struct fsmonitor_daemon_state *state)
+{
+}
+
+void fsmonitor_fs_listen__loop(struct fsmonitor_daemon_state *state)
+{
+}
config.mak.uname +0 -0
index fcd88b60b14..394355463e1 100644

config.mak.uname ifeq ($(uname_S),Darwin) · line 150

Quoted 4 lines
config.mak.uname +1 -0
@@ -147,6 +147,8 @@ ifeq ($(uname_S),Darwin)
 			MSGFMT = /usr/local/opt/gettext/bin/msgfmt
 		endif
 	endif
+	FSMONITOR_DAEMON_BACKEND = macos

A rather trivial point, but can't we pick one of "macos" or "darwin" (I'd think going with the existing uname is better) and name the file after the uname (or lower-case thereof)?

Makes these make rules more consistent too, we could just set this to "YesPlease" here, and then lower case the uname for the file compilation/include.

#175 lore raw

Re: [PATCH v3 15/34] fsmonitor: do not try to operate on bare repos

In reply to Jeff Hostetler via GitGitGadget
Quoted 17 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Bare repos do not have a working directory, so there is no directory for the daemon to register a watch upon. And therefore there are no files within the directory for it to actually watch.

---
 builtin/fsmonitor--daemon.c |  8 ++++++++
 t/t7519-status-fsmonitor.sh | 16 ++++++++++++++++
 2 files changed, 24 insertions(+)
builtin/fsmonitor--daemon.c +0 -0
index 7fcf960652f..d6161ad95a5 100644

builtin/fsmonitor--daemon.c int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix) · line 499

Quoted 10 lines
builtin/fsmonitor--daemon.c +7 -0
@@ -490,6 +490,14 @@ int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)
 		die(_("invalid 'ipc-threads' value (%d)"),
 		    fsmonitor__ipc_threads);
 
+	prepare_repo_settings(the_repository);
+	if (!the_repository->worktree)
+		return error(_("fsmonitor-daemon does not support bare repos '%s'"),
+			     xgetcwd());
+	if (the_repository->settings.fsmonitor_mode == FSMONITOR_MODE_INCOMPATIBLE)
+		return error(_("fsmonitor-daemon is incompatible with this repo '%s'"),
+			     the_repository->worktree);

I commented on another patch that that second condition seems like it's only hit under core.virtualfilesystem=true....

builtin/fsmonitor--daemon.c int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)

Quoted 6 lines
builtin/fsmonitor--daemon.c +6 -0
+test_expect_success FSMONITOR_DAEMON 'try running fsmonitor-daemon in bare repo' '
+	test_when_finished "rm -rf ./bare-clone" &&
+	git clone --bare . ./bare-clone &&
+	test_must_fail git -C ./bare-clone fsmonitor--daemon run 2>actual &&
+	grep "fsmonitor-daemon does not support bare repos" actual
+'

Isn't just:

git init --bare bare.git
test_must_fail git -C bare.git [...]

Enough, or does the repository need content to get to that error.

builtin/fsmonitor--daemon.c

Quoted 9 lines
builtin/fsmonitor--daemon.c +9 -0
+test_expect_success FSMONITOR_DAEMON 'try running fsmonitor-daemon in virtual repo' '
+	test_when_finished "rm -rf ./fake-virtual-clone" &&
+	git clone . ./fake-virtual-clone &&
+	test_must_fail git -C ./fake-virtual-clone \
+			   -c core.virtualfilesystem=true \
+			   fsmonitor--daemon run 2>actual &&
+	grep "fsmonitor-daemon is incompatible with this repo" actual
+'
+

Ditto.

#178 lore raw

Re: [PATCH v3 17/34] fsmonitor--daemon: define token-ids

In reply to Jeff Hostetler via GitGitGadget
Quoted 6 lines · Jeff Hostetler via GitGitGadget
builtin/fsmonitor--daemon.c +6 -0
+	if (!test_env_value) {
+		struct timeval tv;
+		struct tm tm;
+		time_t secs;
+
+		gettimeofday(&tv, NULL);

builtin/fsmonitor--daemon.c static int do_as_client__status(void) · line 202

Quoted 10 lines
builtin/fsmonitor--daemon.c +10 -0
+		secs = tv.tv_sec;
+		gmtime_r(&secs, &tm);
+
+		strbuf_addf(&token->token_id,
+			    "%"PRIu64".%d.%4d%02d%02dT%02d%02d%02d.%06ldZ",
+			    flush_count++,
+			    getpid(),
+			    tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
+			    tm.tm_hour, tm.tm_min, tm.tm_sec,
+			    (long)tv.tv_usec);

Just bikeshedding, but can we have tokens that mostly sort numeric-wise by time order? So time at the start, not the flush_count/getpid.

Maybe I'm missing something, but couldn't we just re-use the trace2 SID
+ a more trivial trailer? It would have the nice property that you could find the trace2 SID whenever you looked at such a token (could e.g. split them by "/" too), and add the tv_usec, flush_count+whatever else is needed to make it unique after the "/", no?

#183 lore raw

Re: [PATCH v3 19/34] fsmonitor-fs-listen-win32: implement FSMonitor backend on Windows

In reply to Jeff Hostetler via GitGitGadget
Quoted 10 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Teach the win32 backend to register a watch on the working tree root directory (recursively). Also watch the <gitdir> if it is not inside the working tree. And to collect path change notifications into batches and publish.

---
 compat/fsmonitor/fsmonitor-fs-listen-win32.c | 530 +++++++++++++++++++

<bikeshed mode> Spying on the early history of this (looking for the Linux backend) I saw that at some point we had just compat/fsmonitor/linux.c, and presumably some of compat/fsmonitor/{windows,win32,macos,darwin}.c.

At some point those filenames became much much longer.

I've noticed you tend to prefer really long file and function names, e.g. your borrowed daemonize() became spawn_background_fsmonitor_daemon(), I think aiming for shorter filenames & function names helps, e.g. these long names widen diffstats, and many people who hack on the code stick religiously to 80 character width terminals.

#200 lore raw

Re: [PATCH v3 24/34] t/perf/p7519: speed up test using "test-tool touch"

In reply to Jeff Hostetler via GitGitGadget
Quoted 40 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Change p7519 to use a single "test-tool touch" command to update the mtime on a series of (thousands) files instead of invoking thousands of commands to update a single file.

This is primarily for Windows where process creation is so very slow and reduces the test run time by minutes.

---
 t/perf/p7519-fsmonitor.sh | 14 ++++++--------
 1 file changed, 6 insertions(+), 8 deletions(-)
t/perf/p7519-fsmonitor.sh +5 -6
index 5eb5044a103..f74e6014a0a 100755
@@ -119,10 +119,11 @@ test_expect_success "one time repo setup" '
 	fi &&
 
 	mkdir 1_file 10_files 100_files 1000_files 10000_files &&
-	for i in $(test_seq 1 10); do touch 10_files/$i; done &&
-	for i in $(test_seq 1 100); do touch 100_files/$i; done &&
-	for i in $(test_seq 1 1000); do touch 1000_files/$i; done &&
-	for i in $(test_seq 1 10000); do touch 10000_files/$i; done &&
+	test-tool touch sequence --pattern="10_files/%d" --start=1 --count=10 &&
+	test-tool touch sequence --pattern="100_files/%d" --start=1 --count=100 &&
+	test-tool touch sequence --pattern="1000_files/%d" --start=1 --count=1000 &&
+	test-tool touch sequence --pattern="10000_files/%d" --start=1 --count=10000 &&
+
 	git add 1_file 10_files 100_files 1000_files 10000_files &&
 	git commit -qm "Add files" &&
 
@@ -200,15 +201,12 @@ test_fsmonitor_suite() {
 	# Update the mtimes on upto 100k files to make status think
 	# that they are dirty.  For simplicity, omit any files with
 	# LFs (i.e. anything that ls-files thinks it needs to dquote).
-	# Then fully backslash-quote the paths to capture any
-	# whitespace so that they pass thru xargs properly.

t/perf/p7519-fsmonitor.sh test_fsmonitor_suite() { · line 211

Quoted 10 lines
t/perf/p7519-fsmonitor.sh +1 -2
 	#
 	test_perf_w_drop_caches "status (dirty) ($DESC)" '
 		git ls-files | \
 			head -100000 | \
 			grep -v \" | \
-			sed '\''s/\(.\)/\\\1/g'\'' | \
-			xargs test-tool chmtime -300 &&
+			test-tool touch stdin &&
 		git status
 	'

Did you try to replace this with some variant of:

    test_seq 1 10000 | xargs touch

Which (depending on your xargs version) would invoke "touch" commands with however many argv items it thinks you can handle.

#213 lore raw

Re: [PATCH v3 25/34] t/perf: avoid copying builtin fsmonitor files into test repo

In reply to Jeff Hostetler via GitGitGadget
Quoted 4 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Do not try to copy a fsmonitor--daemon socket from the current development directory into the test trash directory.

Okey, the */fsmonitor--daemon* rule covers that...

Quoted 7 lines

When we run the perf suite without an explicit source repo set, we copy of the current $GIT_DIR into the test trash directory. Unix domain sockets cannot be copied in that manner, so the test setup fails.

Additionally, omit any other fsmonitor--daemon temp files inside the $GIT_DIR directory.

So is the "any other" also matched by that rule? Not knowing the files part of this is just phrasing, would be less confusing (if that's true, and you didn't just forget to add a match for them) as:

The */fsmonitor--daemon* glob will also match temporary files the daemon creates, but that's OK. We'd like to ignore these too.

#216 lore raw

Re: [PATCH v3 27/34] t7527: create test for fsmonitor--daemon

In reply to Jeff Hostetler via GitGitGadget
Quoted 6 lines · Jeff Hostetler via GitGitGadget
t/t7527-builtin-fsmonitor.sh new file +6 -0
+	grep \"event\":\"start\".*\"fsmonitor--daemon\" .git/trace_implicit_1
+'
+
+test_expect_success 'status implicitly starts daemon' '
+	test_when_finished redundant_stop_daemon &&
+

t/t7527-builtin-fsmonitor.sh line 198

Quoted 10 lines
t/t7527-builtin-fsmonitor.sh new file +10 -0
+	test_must_fail git fsmonitor--daemon status &&
+
+	GIT_TRACE2_EVENT="$(pwd)/.git/trace_implicit_2" \
+		git status >actual &&
+
+	git fsmonitor--daemon status &&
+	test_might_fail git fsmonitor--daemon stop &&
+
+	grep \"event\":\"start\".*\"fsmonitor--daemon\" .git/trace_implicit_2
+'

Seems like this and test_region could eventually be factored into some common function that would be more flexible about grabbing trace2 data.

#219 lore raw

Re: [PATCH v3 29/34] fsmonitor--daemon: use a cookie file to sync with file system

In reply to Jeff Hostetler via GitGitGadget
Quoted 10 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Teach fsmonitor--daemon client threads to create a cookie file inside the .git directory and then wait until FS events for the cookie are observed by the FS listener thread.

This helps address the racy nature of file system events by blocking the client response until the kernel has drained any event backlog.

Quoted 8 lines

This is especially important on MacOS where kernel events are only issued with a limited frequency. See the latency argument of FSeventStreamCreate(). The kernel only signals every latency seconds, but does not guarantee that the kernel queue is completely drained, so we may have to wait more than one interval. If we increase the frequency, the system is more likely to drop events. We avoid these issues by having each client thread create a unique cookie file and then wait until it is seen in the event stream.

Is this a guaranteed property of any API fsmonitor might need to work with (linux, darwin, Windows) that if I perform a bunch of FS operations on my working tree, that if I finish up by touching this cookie file that that'll happen last?

I'd think that wouldn't be the case, i.e. on POSIX filesystems unless you run around fsyncing both files and directories you're not guaranteed that they're on disk, and even then the kernel might decide to sync your cookie earlier, won't it?

E.g. on Linux you can even have cross-FS watches, and mix & match different FS types. I'd expect to get events in whatever implementation-defined order the VFS layer + FS decided to sync them to disk in & get to firing off an event for me.

Or do these APIs all guarantee that a linear view of the world is presented to the API consumer?

Quoted 26 lines
---
 builtin/fsmonitor--daemon.c | 228 +++++++++++++++++++++++++++++++++++-
 fsmonitor--daemon.h         |   5 +
 2 files changed, 232 insertions(+), 1 deletion(-)
builtin/fsmonitor--daemon.c +10 -0
index 8249420ba18..25f18f2726b 100644
@@ -94,6 +94,149 @@ static int do_as_client__status(void)
 	}
 }
 
+enum fsmonitor_cookie_item_result {
+	FCIR_ERROR = -1, /* could not create cookie file ? */
+	FCIR_INIT = 0,
+	FCIR_SEEN,
+	FCIR_ABORT,
+};
+
+struct fsmonitor_cookie_item {
+	struct hashmap_entry entry;
+	const char *name;

builtin/fsmonitor--daemon.c static int do_as_client__status(void) · line 116

Quoted 10 lines
builtin/fsmonitor--daemon.c +10 -0
+	enum fsmonitor_cookie_item_result result;
+};
+
+static int cookies_cmp(const void *data, const struct hashmap_entry *he1,
+		     const struct hashmap_entry *he2, const void *keydata)
+{
+	const struct fsmonitor_cookie_item *a =
+		container_of(he1, const struct fsmonitor_cookie_item, entry);
+	const struct fsmonitor_cookie_item *b =
+		container_of(he2, const struct fsmonitor_cookie_item, entry);

Re earlier comments about verbose names, these are just enums in a builtin/*.c file, so a name like "cookie_item" is fine, and then the whole thing might even fit on one line.. :)

Quoted 28 lines

[...]

builtin/fsmonitor--daemon.c +27 -0
+	/*
+	 * We will write filesystem syncing cookie files into
+	 * <gitdir>/<fsmonitor-dir>/<cookie-dir>/<pid>-<seq>.
+	 *
+	 * The extra layers of subdirectories here keep us from
+	 * changing the mtime on ".git/" or ".git/foo/" when we create
+	 * or delete cookie files.
+	 *
+	 * There have been problems with some IDEs that do a
+	 * non-recursive watch of the ".git/" directory and run a
+	 * series of commands any time something happens.
+	 *
+	 * For example, if we place our cookie files directly in
+	 * ".git/" or ".git/foo/" then a `git status` (or similar
+	 * command) from the IDE will cause a cookie file to be
+	 * created in one of those dirs.  This causes the mtime of
+	 * those dirs to change.  This triggers the IDE's watch
+	 * notification.  This triggers the IDE to run those commands
+	 * again.  And the process repeats and the machine never goes
+	 * idle.
+	 *
+	 * Adding the extra layers of subdirectories prevents the
+	 * mtime of ".git/" and ".git/foo" from changing when a
+	 * cookie file is created.
+	 */
+	strbuf_init(&state.path_cookie_prefix, 0);
+	strbuf_addbuf(&state.path_cookie_prefix, &state.path_gitdir_watch);

builtin/fsmonitor--daemon.c static int fsmonitor_run_daemon(void) · line 1259

Quoted 10 lines
builtin/fsmonitor--daemon.c +10 -0
+
+	strbuf_addch(&state.path_cookie_prefix, '/');
+	strbuf_addstr(&state.path_cookie_prefix, FSMONITOR_DIR);
+	mkdir(state.path_cookie_prefix.buf, 0777);
+
+	strbuf_addch(&state.path_cookie_prefix, '/');
+	strbuf_addstr(&state.path_cookie_prefix, FSMONITOR_COOKIE_DIR);
+	mkdir(state.path_cookie_prefix.buf, 0777);
+
+	strbuf_addch(&state.path_cookie_prefix, '/');

So, on some stupid IDEs (would be nice to have specifics in the commit message, which ones/versions?) this avoids causing infinite activity, but on slightly more industrious stupid IDEs that would do their own recursive watch we'll have the same problem?

Perhaps we should just consider creating it at the top-level and those IDEs will eventually sort out their bugs, sooner than later if this feature ships...

builtin/fsmonitor--daemon.c done: · line 1285

Quoted 5 lines
builtin/fsmonitor--daemon.c +5 -0
+	strbuf_release(&state.path_cookie_prefix);
+
+	/*
+	 * NEEDSWORK: Consider "rm -rf <gitdir>/<fsmonitor-dir>"
+	 */

That would also make this trivial, presumably it's a "needswork" since you have this recursive structure, but if it's at the top-level we already did the unlink() above, so NEEDSWORK solved then?

#226 lore raw

Re: [PATCH v3 34/34] t7527: test FS event reporing on MacOS WRT case and Unicode

In reply to Jeff Hostetler via GitGitGadget

t/t7527-builtin-fsmonitor.sh test_expect_success MINGW,SHORTNAMES 'implicit daemon stop (rename GIT~2)' ' · line 202

Quoted 2 lines · Jeff Hostetler via GitGitGadget
t/t7527-builtin-fsmonitor.sh +2 -0
+	[ -d test_apfs/.git ] &&
+	[ -d test_apfs/.GIT ] &&

Better as "test_path_is_dir".

#230 lore raw
In reply to Ævar Arnfjörð Bjarmason
Quoted 72 lines · Ævar Arnfjörð Bjarmason
Quoted 35 lines · Jeff Hostetler
Quoted 10 lines · Ævar Arnfjörð Bjarmason
Quoted 8 lines · Jeff Hostetler via GitGitGadget

Here is V3 of my patch series to add a builtin FSMonitor daemon to Git. I rebased this series onto v2.32.0.

V3 addresses most of the previous review comments and things we've learned from our experimental testing of V2. (A version of V2 was shipped as an experimental feature in the v2.32.0-based releases of Git for Windows and VFS for Git.)

Quoted 8 lines
Quoted 2 lines

There are still a few items that I need to address, but that list is getting very short.

...

Quoted 2 lines

fsmonitor-fs-listen-win32: stub in backend for Windows
fsmonitor-fs-listen-macos: stub in backend for MacOS

I left some light comments on the repo-settings.c part of this to follow
up from a previous round.

Thanks.

Quoted 6 lines

Any other testing of it is stalled by there being no linux backend for
it as part of this series. I see from spelunking repos that Johannes had a WIP compat/fsmonitor/linux.c which looks like it could/should mostly work, but the API names all changed since then, and after a short try I gave up on trying to rebase it.

The early Linux version was dropped because inotify does not give recursive coverage -- only the requested directory. Using inotify requires adding a watch to each subdirectory (recursively) in the worktree. There's a system limit on the maximum number of watched directories (defaults to 8K IIRC) and that limit is system-wide.

Quoted 8 lines

Since the whole point was to support large very large repos, using inotify was a non-starter, so I removed the Linux version from our patch series. For example, the first repo I tried it on (outside of the test suite) had 25K subdirectories.

I'm told there is a new fanotify API in recent Linux kernels that is a better fit for what we need, but we haven't had time to investigate it yet.

That default limit is a bit annoying, but I don't see how it's a blocker in any way.

You simply adjust the limit. E.g. I deployed and tested the hook version of inotify (using watchman) in a sizable development environment, and written my own code using the API. This was all before fanotify(7) existed. IIRC I set most of the limits to 2^24 or 2^20. I've used it with really large Git repos, including with David Turner's 2015-04-03-1M-git for testing (git ls-files | wc -l on that is around a quarter-million).

If you have a humongous repository and don't have root on your own machine you're out of luck. But I think most people who'd use this are either using their own laptop, or e.g. in a corporate setting where administrators would tweak the sysctl limits given the performance advantages (as I did).

Once you adjust the limits Linux deals with large trees just fine, it just has overly conservative limits for most things in sysctl. The API is a bit annoying, your event loop needs to run around and add watches.

AFAICT you need Linux 5.1 for fanotify(7) to be useful, e.g. Debian stable, RHEL etc. aren't using anything that new. So having an inotify backend as well as possibly a fanotify one would be very useful.

Quoted 7 lines

And linux.git's docs suggest that the default limit was bumped from 8192 to 1M in v5.10, a really recent kernel, so if you've got that you've also got fanotify.

In any case, even if Linux's inotify limit was something hardcoded and impossible to change you could still use such an API to test & debug the basics of this feature on that platform.

Good points. If the inotify limits can be increased into the millions then we can revisit supporting it. I do worry about possible race conditions as we have to scan the worktree and add/delete a watch for each directory, but we don't need to worry about that right now.

I do want to have Linux support eventually, but I was saving it for a later effort (and/or looking for volunteers). My IPC-based builtin daemon complements the existing hook-based fsmonitor support that Ben Peart and Kevin Willford added a few years ago. That model (and PERL hook script and Watchman integration) work fine for Linux, so the advantages of a builtin monitor aren't as compelling.

For example, on Linux, hook process creation is fast, PERL is fast, and it is easy to just apt-get a tool like Watchman. But on Windows, process creation is slow, NTFS is slow, PERL is available as part of the Git-for-Windows distribution, and installing third-party tools like Watchman onto a collection of enterprise users' machines is a chore, so it made sense of us to pick platforms where the existing hook model has issues and add other platforms later.

Besides, this patch series is already at 34 commits. And some of them are quite large. Adding another platform would just make it even larger.

Right now I'm more interested in the larger question of whether we WANT to have a builtin fsmonitor and do we like the overall design of what I have here? Picking up a new platform, whether it is Linux, or AIX, or Solaris, or Nonstop, or whatever, should nicely fit into one platform-specific file in compat/fsmonitor and not take that long.

Quoted 23 lines
Quoted 11 lines
Quoted 7 lines

I'd really prefer for git not to have features that place free platforms
at a disadvantage against proprietary platforms if it can be avoided, and in this case the lack of a Linux backend also means much less widespread testing of the feature among the development community / CI.

This feature is always going to have platform-specific components, so the lack of one platform or another should not stop us from discussing it for the platforms that can be supported.

(I think per the above that's s/can be/are/)

Quoted 4 lines

And given the size and complexity of the platform-specific code, we should not assume that "just test it on Linux" is sufficient. Yes, there are some common/shared routines/data structures in the daemon, but hard/tricky parts are in the platform layer.

I think we're talking past each other a bit here. I'm not saying that you can get full or meaningful testing for it on Windows if you test it on Linux, or the other way around.

Quoted 8 lines

Of course there's platform-specific stuff, although there's also a lot of non-platform-specific stuff, so even a very basic implementation of inotify would make reviwing this easier / give access to more reviewers.

I'm saying that I prefer that Git as a free software project not be in the situation of saying the best use-case for a given size/shape of repo is to use Git in combination with proprietary operating systems over freely licensed ones.

I wouldn't worry about that. Even without Watchman integration, Linux runs things so much faster than anything else it's not funny.

If anything, we need things like fsmonitor on Windows to help keep up with Linux.

Quoted 6 lines

IOW what the FSF has a policy for GNU projects. Now, I think the FSF probably goes too far in that (famously removing rather obscure font rendering features from Emacs on OSX), but "manage lots of data" is a core feature of git.

Jeff

#232 lore raw
In reply to Ævar Arnfjörð Bjarmason

Hi Ævar,

Quoted 9 lines · Ævar Arnfjörð Bjarmason
Quoted 7 lines · Jeff Hostetler
Quoted 5 lines · Ævar Arnfjörð Bjarmason

Any other testing of it is stalled by there being no linux backend for it as part of this series. I see from spelunking repos that Johannes had a WIP compat/fsmonitor/linux.c which looks like it could/should mostly work, but the API names all changed since then, and after a short try I gave up on trying to rebase it.

I am a bit surprised that you gave up so easily, it cannot be that hard if you use git rebase -i smartly.

But I think there is an even bigger obstacle lurking than just the challenge of rebasing that experimental backend.

Quoted 11 lines
Quoted 11 lines

The early Linux version was dropped because inotify does not give recursive coverage -- only the requested directory. Using inotify requires adding a watch to each subdirectory (recursively) in the worktree. There's a system limit on the maximum number of watched directories (defaults to 8K IIRC) and that limit is system-wide.

Since the whole point was to support large very large repos, using inotify was a non-starter, so I removed the Linux version from our patch series. For example, the first repo I tried it on (outside of the test suite) had 25K subdirectories.

Quoted 6 lines
Quoted 3 lines

I'm told there is a new fanotify API in recent Linux kernels that is a better fit for what we need, but we haven't had time to investigate it yet.

That default limit is a bit annoying, but I don't see how it's a blocker in any way.

Let me help you to see it.

So let's assume that you start FSMonitor-enabled Git, with the default values. What is going to happen if you have any decently-sized worktree? You run out of handles. What then? Throw your hands in the air? Stop working? Report incorrect results?

Those are real design challenges, and together with the race problems Jeff mentioned, they pose a much bigger obstacle than the rebasing you mentioned above.

Quoted 8 lines

You simply adjust the limit. E.g. I deployed and tested the hook version of inotify (using watchman) in a sizable development environment, and written my own code using the API. This was all before fanotify(7) existed. IIRC I set most of the limits to 2^24 or 2^20. I've used it with really large Git repos, including with David Turner's 2015-04-03-1M-git for testing (git ls-files | wc -l on that is around a quarter-million).

Quoted 5 lines

If you have a humongous repository and don't have root on your own machine you're out of luck. But I think most people who'd use this are either using their own laptop, or e.g. in a corporate setting where administrators would tweak the sysctl limits given the performance advantages (as I did).

This conjecture that most people who'd use this are using their own laptop or have a corporate setting where administrators would tweak the sysctl limits according to engineers' wishes strikes me as totally made up from thin air, nothing else.

In other words, I find it an incredibly unconvincing argument.

I prefer not to address the rest of your mail, as I found it not only a lengthy tangent (basically trying to talk Jeff into adding Linux support in what could have been a much shorter mail), but actively distracting from the already long patch series Jeff presented. It is so long, in fact, that we had to put in an exemption in GitGitGadget because it is already longer than a usually-unreasonable 30 patches. Also, at this point, insisting on Linux support (in so many words) is unhelpful.

Let me summarize why I think this is unhelpful: In Git, it is our tradition to develop incrementally, for better or worse. Jeff's effort brought us to a point where we already have Windows and macOS support, i.e. support for the most prevalent development platforms (see e.g.
https://insights.stackoverflow.com/survey/2020#technology-developers-primary-operating-systems).
We already established multiple obstacles for Linux support, therefore demanding Linux support to be included Right Now would increase the patch series even further, making it even less reviewable, being even less incremental, hold up the current known-to-work-well state, force Jeff to work on something he probably cannot work on right now, and therefore delaying the entire effort even further.

Ciao,
Johannes

#169 lore raw

Re: [PATCH v3 14/34] fsmonitor--daemon: implement 'start' command

In reply to Ævar Arnfjörð Bjarmason

Hi Ævar,

Quoted 75 lines · Ævar Arnfjörð Bjarmason
Quoted 73 lines · Jeff Hostetler via GitGitGadget
builtin/fsmonitor--daemon.c +73 -0
+#ifdef GIT_WINDOWS_NATIVE
+/*
+ * Create a background process to run the daemon.  It should be completely
+ * disassociated from the terminal.
+ *
+ * Conceptually like `daemonize()` but different because Windows does not
+ * have `fork(2)`.  Spawn a normal Windows child process but without the
+ * limitations of `start_command()` and `finish_command()`.
+ *
+ * The child process execs the "git fsmonitor--daemon run" command.
+ *
+ * The current process returns so that the caller can wait for the child
+ * to startup before exiting.
+ */
+static int spawn_background_fsmonitor_daemon(pid_t *pid)
+{
+	char git_exe[MAX_PATH];
+	struct strvec args = STRVEC_INIT;
+	int in, out;
+
+	GetModuleFileNameA(NULL, git_exe, MAX_PATH);
+
+	in = open("/dev/null", O_RDONLY);
+	out = open("/dev/null", O_WRONLY);
+
+	strvec_push(&args, git_exe);
+	strvec_push(&args, "fsmonitor--daemon");
+	strvec_push(&args, "run");
+	strvec_pushf(&args, "--ipc-threads=%d", fsmonitor__ipc_threads);
+
+	*pid = mingw_spawnvpe(args.v[0], args.v, NULL, NULL, in, out, out);
+	close(in);
+	close(out);
+
+	strvec_clear(&args);
+
+	if (*pid < 0)
+		return error(_("could not spawn fsmonitor--daemon in the background"));
+
+	return 0;
+}
+#else
+/*
+ * Create a background process to run the daemon.  It should be completely
+ * disassociated from the terminal.
+ *
+ * This is adapted from `daemonize()`.  Use `fork()` to directly
+ * create and run the daemon in the child process.
+ *
+ * The fork-child can just call the run code; it does not need to exec
+ * it.
+ *
+ * The fork-parent returns the child PID so that we can wait for the
+ * child to startup before exiting.
+ */
+static int spawn_background_fsmonitor_daemon(pid_t *pid)
+{
+	*pid = fork();
+
+	switch (*pid) {
+	case 0:
+		if (setsid() == -1)
+			error_errno(_("setsid failed"));
+		close(0);
+		close(1);
+		close(2);
+		sanitize_stdfds();
+
+		return !!fsmonitor_run_daemon();
+
+	case -1:
+		return error_errno(_("could not spawn fsmonitor--daemon in the background"));
+
Quoted 8 lines

builtin/fsmonitor--daemon.c static int try_to_run_foreground_daemon(void) · line 349

Quoted 5 lines
builtin/fsmonitor--daemon.c +5 -0
+	default:
+		return 0;
+	}
+}
+#endif

The spawn_background_fsmonitor_daemon() function here is almost the same as daemonize().

Yes, the code comment above that function even says that it was adapted from daemonize().

And above that, of course, is a _completely different_ implementation that works on Windows (you will notice that this is in stark contrast of Windows, where the daemonize() function will simply fail with ENOSYS).

Quoted 2 lines

I wonder if this & the Windows-specific one you have here can't be refactored into an API from what's now in setup.c.

Given that there is no fork() on Windows (which has been the subject of many a message to this mailing list), I think the answer to this question of yours is a resounding "No".

Quoted 3 lines

Then we could make builtin/gc.c and daemon.c use that, so Windows could have background GC, and we'd have a more battle-tested central codepath for this tricky bit.

Please. Not _more_ sidetracks.

The issue of getting git gc --auto to daemonize on Windows is a rather complicated one. I won't bore this list with the details, but link to
https://github.com/git-for-windows/git/issues/2221#issuecomment-542589590
(a ~950 word analysis of the problem).

Quoted 4 lines

It seems to me like the only limitations on it are to have this return slightly more general things (e.g. not set its own errors, return structured data), and maybe some callback for what to do in the child/parent.

And one version doesn't die(). Nor does it call exit(0) in the parent process. But it calls fsmonitor_listen() in the child process. And if you wanted to refactor daemonize() to do all that, it would have to be renamed (because it does no longer _necessarily_ daemonize), and it would have to have a gentle flag, and it would somehow have to indicate in its return value whether 0 means that the parent process returned successfully or the client process. And soon we'll end up with a function that is both longer and more unreadable than what we have right now.

Quoted 74 lines
Quoted 60 lines
builtin/fsmonitor--daemon.c +59 -0
+/*
+ * This is adapted from `wait_or_whine()`.  Watch the child process and
+ * let it get started and begin listening for requests on the socket
+ * before reporting our success.
+ */
+static int wait_for_background_startup(pid_t pid_child)
+{
+	int status;
+	pid_t pid_seen;
+	enum ipc_active_state s;
+	time_t time_limit, now;
+
+	time(&time_limit);
+	time_limit += fsmonitor__start_timeout_sec;
+
+	for (;;) {
+		pid_seen = waitpid(pid_child, &status, WNOHANG);
+
+		if (pid_seen == -1)
+			return error_errno(_("waitpid failed"));
+		else if (pid_seen == 0) {
+			/*
+			 * The child is still running (this should be
+			 * the normal case).  Try to connect to it on
+			 * the socket and see if it is ready for
+			 * business.
+			 *
+			 * If there is another daemon already running,
+			 * our child will fail to start (possibly
+			 * after a timeout on the lock), but we don't
+			 * care (who responds) if the socket is live.
+			 */
+			s = fsmonitor_ipc__get_state();
+			if (s == IPC_STATE__LISTENING)
+				return 0;
+
+			time(&now);
+			if (now > time_limit)
+				return error(_("fsmonitor--daemon not online yet"));
+		} else if (pid_seen == pid_child) {
+			/*
+			 * The new child daemon process shutdown while
+			 * it was starting up, so it is not listening
+			 * on the socket.
+			 *
+			 * Try to ping the socket in the odd chance
+			 * that another daemon started (or was already
+			 * running) while our child was starting.
+			 *
+			 * Again, we don't care who services the socket.
+			 */
+			s = fsmonitor_ipc__get_state();
+			if (s == IPC_STATE__LISTENING)
+				return 0;
+
+			/*
+			 * We don't care about the WEXITSTATUS() nor
+			 * any of the WIF*(status) values because
+			 * `cmd_fsmonitor__daemon()` does the `!!result`

builtin/fsmonitor--daemon.c static int try_to_run_foreground_daemon(void) · line 419

Quoted 10 lines
builtin/fsmonitor--daemon.c +10 -0
+			 * trick on all function return values.
+			 *
+			 * So it is sufficient to just report the
+			 * early shutdown as an error.
+			 */
+			return error(_("fsmonitor--daemon failed to start"));
+		} else
+			return error(_("waitpid is confused"));
+	}
+}

Ditto this. could we extend the wait_or_whine() function (or some extended version thereof) to do what you need with callbacks?

Quoted 3 lines

It seems the main difference is just being able to pass down a flag for waitpid(), and the loop needing to check EINTR or not depending on whether WNOHANG is passed.

Given that over half of wait_or_whine() is concerned with signals, which the wait_for_background_startup() function is not at all concerned with, I see another main difference.

Quoted 4 lines

For e.g. the "We don't care about the WEXITSTATUS()" you'd get that behavior with an adjusted wait_or_whine(). Wouldn't it be better to report what exit status it exits with e.g. if the top-level process is signalled? We do so in trace2 for other things we spawn...

The wait_or_whine() function also adjusts atexit() behavior, which we would not want here.

Therefore, just like the suggestion about daemonize() above, it appears to me as if the suggested refactoring would make the code dramatically more complex and less readable.

In other words, it would be a refactoring for refactoring's sake. Definitely not something I would suggest.

Ciao,
Johannes

#126 lore raw

Re: [PATCH v3 02/34] fsmonitor--daemon: man page

In reply to Ævar Arnfjörð Bjarmason

Hi Ævar,

Quoted 97 lines · Ævar Arnfjörð Bjarmason
Quoted 81 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Create a manual page describing the git fsmonitor--daemon feature.

---
 Documentation/git-fsmonitor--daemon.txt | 75 +++++++++++++++++++++++++
 1 file changed, 75 insertions(+)
 create mode 100644 Documentation/git-fsmonitor--daemon.txt
Documentation/git-fsmonitor--daemon.txt new file +65 -0
new file mode 100644
index 00000000000..154e7684daa
@@ -0,0 +1,75 @@
+git-fsmonitor--daemon(1)
+========================
+
+NAME
+----
+git-fsmonitor--daemon - A Built-in File System Monitor
+
+SYNOPSIS
+--------
+[verse]
+'git fsmonitor--daemon' start
+'git fsmonitor--daemon' run
+'git fsmonitor--daemon' stop
+'git fsmonitor--daemon' status
+
+DESCRIPTION
+-----------
+
+A daemon to watch the working directory for file and directory
+changes using platform-specific file system notification facilities.
+
+This daemon communicates directly with commands like `git status`
+using the link:technical/api-simple-ipc.html[simple IPC] interface
+instead of the slower linkgit:githooks[5] interface.
+
+This daemon is built into Git so that no third-party tools are
+required.
+
+OPTIONS
+-------
+
+start::
+	Starts a daemon in the background.
+
+run::
+	Runs a daemon in the foreground.
+
+stop::
+	Stops the daemon running in the current working
+	directory, if present.
+
+status::
+	Exits with zero status if a daemon is watching the
+	current working directory.
+
+REMARKS
+-------
+
+This daemon is a long running process used to watch a single working
+directory and maintain a list of the recently changed files and
+directories.  Performance of commands such as `git status` can be
+increased if they just ask for a summary of changes to the working
+directory and can avoid scanning the disk.
+
+When `core.useBuiltinFSMonitor` is set to `true` (see
+linkgit:git-config[1]) commands, such as `git status`, will ask the
+daemon for changes and automatically start it (if necessary).
+
+For more information see the "File System Monitor" section in
+linkgit:git-update-index[1].
+
+CAVEATS
+-------
+
+The fsmonitor daemon does not currently know about submodules and does

Documentation/git-fsmonitor--daemon.txt line 75

Quoted 10 lines
Documentation/git-fsmonitor--daemon.txt new file +10 -0
+not know to filter out file system events that happen within a
+submodule.  If fsmonitor daemon is watching a super repo and a file is
+modified within the working directory of a submodule, it will report
+the change (as happening against the super repo).  However, the client
+will properly ignore these extra events, so performance may be affected
+but it will not cause an incorrect result.
+
+GIT
+---
+Part of the linkgit:git[1] suite

Later in the series we incrementally add features to the daemon, so this is describing a state that doesn't exist yet at this point.

Quoted 3 lines

I think it would be better to start with a stup here and add documentation as we add features, e.g. the patch tha adds "start" should add that to the synopsis + options etc.

Incidentally, I had structured the patch series that way until Jeff took over from me last year. And it was definitely not more reviewable because it was not clear from the get-go what I intended this command to do.

Therefore, I object to your suggestion.

Ciao,
Johannes

P.S.: I will not address your other reviews in this patch series, mostly because I am technically off from work this week.

#233 lore raw
In reply to Johannes Schindelin
Quoted 26 lines · Johannes Schindelin
Quoted 3 lines · Ævar Arnfjörð Bjarmason
Quoted 1 line · Jeff Hostetler

On 7/1/21 1:40 PM, Ævar Arnfjörð Bjarmason wrote:

[...]

Quoted 11 lines
Quoted 11 lines

The early Linux version was dropped because inotify does not give recursive coverage -- only the requested directory. Using inotify requires adding a watch to each subdirectory (recursively) in the worktree. There's a system limit on the maximum number of watched directories (defaults to 8K IIRC) and that limit is system-wide.

Since the whole point was to support large very large repos, using inotify was a non-starter, so I removed the Linux version from our patch series. For example, the first repo I tried it on (outside of the test suite) had 25K subdirectories.

Quoted 6 lines
Quoted 3 lines

I'm told there is a new fanotify API in recent Linux kernels that is a better fit for what we need, but we haven't had time to investigate it yet.

That default limit is a bit annoying, but I don't see how it's a blocker in any way.

Let me help you to see it.

Quoted 8 lines

So let's assume that you start FSMonitor-enabled Git, with the default values. What is going to happen if you have any decently-sized worktree? You run out of handles. What then? Throw your hands in the air? Stop working? Report incorrect results?

Those are real design challenges, and together with the race problems Jeff mentioned, they pose a much bigger obstacle than the rebasing you mentioned above.

You report an error and tell the user to raise the limit, and cover this in your install docs. It's what watchman does:

https://github.com/facebook/watchman/blob/master/watchman/error_category.cpp#L28-L45
https://facebook.github.io/watchman/docs/install.html#system-specific-preparation

Quoted 14 lines
Quoted 8 lines

You simply adjust the limit. E.g. I deployed and tested the hook version of inotify (using watchman) in a sizable development environment, and written my own code using the API. This was all before fanotify(7) existed. IIRC I set most of the limits to 2^24 or 2^20. I've used it with really large Git repos, including with David Turner's 2015-04-03-1M-git for testing (git ls-files | wc -l on that is around a quarter-million).

Quoted 5 lines

If you have a humongous repository and don't have root on your own machine you're out of luck. But I think most people who'd use this are either using their own laptop, or e.g. in a corporate setting where administrators would tweak the sysctl limits given the performance advantages (as I did).

Quoted 6 lines

This conjecture that most people who'd use this are using their own laptop or have a corporate setting where administrators would tweak the sysctl limits according to engineers' wishes strikes me as totally made up from thin air, nothing else.

In other words, I find it an incredibly unconvincing argument.

It's from a sample size of one experience of deploying this in a BigCorp setting.

But sure, perhaps things are done differently where you work/have worked. My experience is that even if you're dealing with some BOFHs and e.g. are using shared racked development servers it's generally not an insurmountable task to get some useful software installed, or some system configuration tweaked.

In this case we're talking about ~40MB of kernel memory for 1 million dirs IIRC, that coupled with the target audience that benefits most from this probably being deployments that are *painfully* aware of their "git status" slowness...

Quoted 7 lines

I prefer not to address the rest of your mail, as I found it not only a lengthy tangent (basically trying to talk Jeff into adding Linux support in what could have been a much shorter mail), but actively distracting from the already long patch series Jeff presented. It is so long, in fact, that we had to put in an exemption in GitGitGadget because it is already longer than a usually-unreasonable 30 patches. Also, at this point, insisting on Linux support (in so many words) is unhelpful.

This part of the tread started because Jeff H. claimed upthread:

[...]inotify was a non-starter, so I removed the Linux version from our patch series

But after I noted that it works just fine, you just need to change some sysctl limits.

It seems at this point we're debating whether some installations of Linux have BOFH-y enough administrators that they won't tweak sysctl limits for you. OK, but given that I've run this thing in a production setting it's clearly not a "non-starter". I think it could be useful for a lot of users.

I'll reply with more (and hopefully helpful) specifics to Jeff's mail.

Quoted 11 lines

Let me summarize why I think this is unhelpful: In Git, it is our tradition to develop incrementally, for better or worse. Jeff's effort brought us to a point where we already have Windows and macOS support, i.e. support for the most prevalent development platforms (see e.g.
https://insights.stackoverflow.com/survey/2020#technology-developers-primary-operating-systems).
We already established multiple obstacles for Linux support, therefore demanding Linux support to be included Right Now would increase the patch series even further, making it even less reviewable, being even less incremental, hold up the current known-to-work-well state, force Jeff to work on something he probably cannot work on right now, and therefore delaying the entire effort even further.

I think we just disagree. I wouldn't call my opinion "unhelpful" any more than yours.

I don't think Git's ever had anything like a major feature (built in, config settings, etc. etc.) contributed by a propriterary OS vendor that works on that vendor's OS, as well as another vendor's propriterary OS, but not comparable free systems

Is that less incremental and perhaps less practical? Sure. It's not an entirely practical viewpoint. I work on free software partly for idealistic reasons. I'd prefer if the project I'm working on doesn't give users a carrot to pick proprietary systems over free ones.

But ultimately it's not my call, but Junio's.

#231 lore raw
In reply to Jeff Hostetler
Quoted 78 lines · Jeff Hostetler
Quoted 68 lines · Ævar Arnfjörð Bjarmason
Quoted 35 lines · Jeff Hostetler
Quoted 10 lines · Ævar Arnfjörð Bjarmason
Quoted 8 lines · Jeff Hostetler via GitGitGadget

Here is V3 of my patch series to add a builtin FSMonitor daemon to Git. I rebased this series onto v2.32.0.

V3 addresses most of the previous review comments and things we've learned from our experimental testing of V2. (A version of V2 was shipped as an experimental feature in the v2.32.0-based releases of Git for Windows and VFS for Git.)

Quoted 8 lines
Quoted 2 lines

There are still a few items that I need to address, but that list is getting very short.

...

Quoted 2 lines

fsmonitor-fs-listen-win32: stub in backend for Windows
fsmonitor-fs-listen-macos: stub in backend for MacOS

I left some light comments on the repo-settings.c part of this to follow
up from a previous round.

Thanks.

Quoted 6 lines

Any other testing of it is stalled by there being no linux backend for
it as part of this series. I see from spelunking repos that Johannes had a WIP compat/fsmonitor/linux.c which looks like it could/should mostly work, but the API names all changed since then, and after a short try I gave up on trying to rebase it.

The early Linux version was dropped because inotify does not give recursive coverage -- only the requested directory. Using inotify requires adding a watch to each subdirectory (recursively) in the worktree. There's a system limit on the maximum number of watched directories (defaults to 8K IIRC) and that limit is system-wide.

Quoted 8 lines

Since the whole point was to support large very large repos, using inotify was a non-starter, so I removed the Linux version from our patch series. For example, the first repo I tried it on (outside of the test suite) had 25K subdirectories.

I'm told there is a new fanotify API in recent Linux kernels that is a better fit for what we need, but we haven't had time to investigate it yet.

That default limit is a bit annoying, but I don't see how it's a blocker
in any way.
You simply adjust the limit. E.g. I deployed and tested the hook version
of inotify (using watchman) in a sizable development environment, and written my own code using the API. This was all before fanotify(7) existed. IIRC I set most of the limits to 2^24 or 2^20. I've used it with really large Git repos, including with David Turner's 2015-04-03-1M-git for testing (git ls-files | wc -l on that is around a quarter-million).
If you have a humongous repository and don't have root on your own machine you're out of luck. But I think most people who'd use this are either using their own laptop, or e.g. in a corporate setting where administrators would tweak the sysctl limits given the performance advantages (as I did).
Once you adjust the limits Linux deals with large trees just fine, it
just has overly conservative limits for most things in sysctl. The API is a bit annoying, your event loop needs to run around and add watches. AFAICT you need Linux 5.1 for fanotify(7) to be useful, e.g. Debian stable, RHEL etc. aren't using anything that new. So having an inotify backend as well as possibly a fanotify one would be very useful.

Quoted 8 lines

And linux.git's docs suggest that the default limit was bumped from 8192
to 1M in v5.10, a really recent kernel, so if you've got that you've also got fanotify.
In any case, even if Linux's inotify limit was something hardcoded and
impossible to change you could still use such an API to test & debug the basics of this feature on that platform.

Quoted 2 lines

Good points. If the inotify limits can be increased into the millions then we can revisit supporting it.

I also replied in the side-thread on this topic:
https://lore.kernel.org/git/[email protected]/

Quoted 3 lines

[...] I do worry about possible race
conditions as we have to scan the worktree and add/delete a watch for each directory, but we don't need to worry about that right now.

In the watchman code there's a scheduleRecrawl() in such cases, but it's not just something that happens on Linux.

On Windows it's if ReadDirectoryChangesW() returns ERROR_NOTIFY_ENUM_DIR. This builtin version doesn't check that at all. Here's someone who seems to work at MSFT talking about it:

    https://devblogs.microsoft.com/oldnewthing/20110812-00/?p=9913

So isn't this an issue on Windows as well?

Quoted 2 lines

I do want to have Linux support eventually, but I was saving it for a later effort (and/or looking for volunteers).

OK, upthread you said it was a "non-starter" because of the adjustable limits we've been discussing...

Quoted 7 lines

My IPC-based builtin daemon complements the existing hook-based fsmonitor support that Ben Peart and Kevin Willford added a few years ago. That model (and PERL hook script and Watchman integration) work fine for Linux, so the advantages of a builtin monitor aren't as compelling.

For example, on Linux, hook process creation is fast, PERL is fast,[...]

Did you or Ben ever try to reproduce what I noted at:

https://lore.kernel.org/git/CACBZZX5e58bWuf3NdDYTxu2KyZj29hHONzN=rp-7vXd8nURyWQ@mail.gmail.com/

And more recently with reference to that at:

https://lore.kernel.org/git/[email protected]/

I.e. when I tested it back then the issue wasn't Perl's performance, it was something between watchman and the user, happening in git. I.e. watchman would return in the single-digit milliseconds, git would take hundreds or thousands of ms.

Maybe I missed some intervening analysis, but this didn't have to do with process overhead back then.

Also, watchman has a JSON network interface. So for a "native" solution I also wonder if you or Ben tried a solution that involved just talking to that over the local network. The hook/perl script was just a trivial aid to make doing that easier. Wouldn't that be just as fast (or near enough) on Windows?

Ben mentioned in the linked thread that there was some talks between Microsoft (or well, the "Git team") and the watchman people. So again, I missed some in-between discussions. I'm just wondering about the design choices...

Quoted 6 lines

and it is easy to just apt-get a tool like Watchman. But on Windows, process creation is slow, NTFS is slow, PERL is available as part of the Git-for-Windows distribution, and installing third-party tools like Watchman onto a collection of enterprise users' machines is a chore, so it made sense of us to pick platforms where the existing hook model has issues and add other platforms later.

If we're trying to do an end-run around package systems being a pain would including the relevant part of watchman in contrib + talking to it over its network interface be a replacement for some/most/all of this series?

Quoted 10 lines

Besides, this patch series is already at 34 commits. And some of them are quite large. Adding another platform would just make it even larger.

Right now I'm more interested in the larger question of whether we WANT to have a builtin fsmonitor and do we like the overall design of what I have here? Picking up a new platform, whether it is Linux, or AIX, or Solaris, or Nonstop, or whatever, should nicely fit into one platform-specific file in compat/fsmonitor and not take that long.

Sure, FWIW I'm not opposed to this being a built-in per-se, or us re-implementing parts of watchman here, and not being able to test this series meaningfully a lot of these questions are probably easy to answer on a supported OS...

Quoted 30 lines
Quoted 16 lines
Quoted 10 lines
Quoted 6 lines

I'd really prefer for git not to have features that place free platforms
at a disadvantage against proprietary platforms if it can be avoided, and in this case the lack of a Linux backend also means much less widespread testing of the feature among the development community / CI.

This feature is always going to have platform-specific components, so the lack of one platform or another should not stop us from discussing it for the platforms that can be supported.

(I think per the above that's s/can be/are/)

Quoted 4 lines

And given the size and complexity of the platform-specific code, we should not assume that "just test it on Linux" is sufficient. Yes, there are some common/shared routines/data structures in the daemon, but hard/tricky parts are in the platform layer.

Quoted 13 lines

I think we're talking past each other a bit here. I'm not saying that
you can get full or meaningful testing for it on Windows if you test it on Linux, or the other way around.
Of course there's platform-specific stuff, although there's also a lot
of non-platform-specific stuff, so even a very basic implementation of inotify would make reviwing this easier / give access to more reviewers. I'm saying that I prefer that Git as a free software project not be in
the situation of saying the best use-case for a given size/shape of repo is to use Git in combination with proprietary operating systems over freely licensed ones.

Quoted 5 lines

I wouldn't worry about that. Even without Watchman integration, Linux runs things so much faster than anything else it's not funny.

If anything, we need things like fsmonitor on Windows to help keep up with Linux.

Even on Linux if you do a "git status" on a humongous repository it can take 1-2 seconds (see old but still valid linked numbers above), whereas if you've been logging fs events and ask a daemon "what changed since time xyz" it can take <5ms.

But that's just been on repositories I've tested. I'd assumed that this part of GVFS wasn't something that could have been fairly easily replaced by having the relevant developers run a Linux distro in vmware or whatever, adn that's been consistent with my own testing on still-big-but-smaller repos than that.

#234 lore raw
In reply to Ævar Arnfjörð Bjarmason

Hi Ævar,

Quoted 2 lines · Ævar Arnfjörð Bjarmason

I think we just disagree. I wouldn't call my opinion "unhelpful" any more than yours.

You still misunderstand. This is not about any "opinion" of yours, it is about your delay tactics to make it deliberately difficult to finish this patch series, by raising the bar beyond what is reasonable for a single patch series.

And you keep doing it. I would appreciate if you just stopped with all those tangents and long and many replies that do not seem designed to help the patch series stabilize, but do the opposite.

Ciao,
Johannes

#191 lore raw

Re: [PATCH v3 19/34] fsmonitor-fs-listen-win32: implement FSMonitor backend on Windows

In reply to Jeff Hostetler via GitGitGadget

Hi Jeff,

On Thu, 1 Jul 2021, Jeff Hostetler via GitGitGadget wrote:

Jeff Hostetler <[email protected]>

the win32 backend to register a watch on the working tree irectory (recursively). Also watch the <gitdir> if it is side the working tree. And to collect path change notifications atches and publish.

-off-by: Jeff Hostetler <[email protected]>

t/fsmonitor/fsmonitor-fs-listen-win32.c | 530 +++++++++++++++++++ e changed, 530 insertions(+)

Quoted 10 lines
compat/fsmonitor/fsmonitor-fs-listen-win32.c +5 -0
index 880446b49e3..d707d47a0d7 100644
@@ -2,20 +2,550 @@
+ [...]
+
+static struct one_watch *create_watch(struct fsmonitor_daemon_state *state,
+				      const char *path)
+{

compat/fsmonitor/fsmonitor-fs-listen-win32.c line 16

Quoted 10 lines
compat/fsmonitor/fsmonitor-fs-listen-win32.c +10 -0
+	struct one_watch *watch = NULL;
+	DWORD desired_access = FILE_LIST_DIRECTORY;
+	DWORD share_mode =
+		FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE;
+	HANDLE hDir;
+
+	hDir = CreateFileA(path,
+			   desired_access, share_mode, NULL, OPEN_EXISTING,
+			   FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
+			   NULL);

The *A() family of Win32 API functions disagree with Git in one very interesting aspect: Git always assumes UTF-8, while e.g. CreateFileA() will use the current Win32 locale to internally transform to wide characters and then call CreateFileW().

This poses no problem when your locale is US American and your paths contain no non-ASCII characters.

In the Git for Windows bug tracker, it was reported that it _does_ cause problems when venturing outside such a cozy scenario (for full details, see https://github.com/git-for-windows/git/issues/3262)

I need this (and merged it before starting the process to release Git for Windows v2.32.0(2)) to fix that (could I ask you to integrate this in case a re-roll will become necessary?):

-- snipsnap --
From: Johannes Schindelin <[email protected]>
Date: Mon, 5 Jul 2021 13:51:05 +0200
Subject: [PATCH] fixup! fsmonitor-fs-listen-win32: implement FSMonitor backend
on Windows

Let's keep avoiding the *A() family of Win32 API functions because they are susceptible to incoherent encoding problems. In Git for Windows, we always assume paths to be UTF-8 encoded. Let's use the dedicated helper to convert such a path to the wide character version, and then use the *W() function instead.

---
 compat/fsmonitor/fsmonitor-fs-listen-win32.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)
compat/fsmonitor/fsmonitor-fs-listen-win32.c +7 -1
index ba087b292df..3b42ab311d9 100644
@@ -111,8 +111,14 @@ static struct one_watch *create_watch(struct fsmonitor_daemon_state *state,
 	DWORD share_mode =
 		FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE;
 	HANDLE hDir;
+	wchar_t wpath[MAX_PATH];
 
-	hDir = CreateFileA(path,
+	if (xutftowcs_path(wpath, path) < 0) {
+		error(_("could not convert to wide characters: '%s'"), path);
+		return NULL;
+	}
+
+	hDir = CreateFileW(wpath,
 			   desired_access, share_mode, NULL, OPEN_EXISTING,
 			   FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
 			   NULL);

--
2.32.0.windows.1.15.gf1590a75e2d

#236 lore raw
In reply to Johannes Schindelin
Quoted 2 lines · Johannes Schindelin

In Git, it is our tradition to develop incrementally, for better or worse.

We develop incrementally, but the first version of a big feature has to have as many eyes on it as possible in order to track potential issues in the future.

It is very common to look back at an API and say "oh, if only wad thought of that back then". Therefore we should do our best to think about it now.

We want to avoid future aw-shucks.

Quoted 10 lines

Jeff's effort
brought us to a point where we already have Windows and macOS support, i.e. support for the most prevalent development platforms (see e.g.
https://insights.stackoverflow.com/survey/2020#technology-developers-primary-operating-systems).
We already established multiple obstacles for Linux support, therefore demanding Linux support to be included Right Now would increase the patch series even further, making it even less reviewable, being even less incremental, hold up the current known-to-work-well state, force Jeff to work on something he probably cannot work on right now, and therefore delaying the entire effort even further.

This is a red herring.

You don't need to send the Linux support as part of the patch series, you can simply provide a branch for the people that want to give it a try.

Even if it's not ready for wide use, even if it needs a specific version of Linux, even if it's a proof of concept, you can provide it.

The real reason it's not provided is laziness (this is not an attack, laziness is good trait in a programmer, although not always).

Cheers.

Signature · 1 line
Felipe Contreras
#235 lore raw
In reply to Johannes Schindelin
Quoted 8 lines · Johannes Schindelin
Quoted 2 lines · Ævar Arnfjörð Bjarmason

I think we just disagree. I wouldn't call my opinion "unhelpful" any more than yours.

You still misunderstand. This is not about any "opinion" of yours, it is about your delay tactics to make it deliberately difficult to finish this patch series,

The whole job of a reviewer is to *delay* the inclusion of the patch series being reviewed until it has passed his/her personal standards.

Ævar is just doing his job (metaphorically).

Quoted 2 lines

by raising the bar beyond what is reasonable for a single patch series.

It is not up to you to determine what is reasonable for a patch series, and given that you have a vested interest you are also not an objective observer.

I have hundreds of patches pending review, and I would love for anyone to try find issues with them, even if I ultimately disagreed with their assessment.

The Git project doesn't owe your patches any preferential treatment. The review process will take as long as the review process takes. You can't put deadlines on open source projects.

Signature · 1 line
Felipe Contreras
#127 lore raw

Re: [PATCH v3 02/34] fsmonitor--daemon: man page

In reply to Ævar Arnfjörð Bjarmason
Quoted 98 lines · Ævar Arnfjörð Bjarmason
Quoted 81 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Create a manual page describing the git fsmonitor--daemon feature.

---
  Documentation/git-fsmonitor--daemon.txt | 75 +++++++++++++++++++++++++
  1 file changed, 75 insertions(+)

create mode 100644 Documentation/git-fsmonitor--daemon.txt

Documentation/git-fsmonitor--daemon.txt new file +65 -0
new file mode 100644
index 00000000000..154e7684daa
@@ -0,0 +1,75 @@
+git-fsmonitor--daemon(1)
+========================
+
+NAME
+----
+git-fsmonitor--daemon - A Built-in File System Monitor
+
+SYNOPSIS
+--------
+[verse]
+'git fsmonitor--daemon' start
+'git fsmonitor--daemon' run
+'git fsmonitor--daemon' stop
+'git fsmonitor--daemon' status
+
+DESCRIPTION
+-----------
+
+A daemon to watch the working directory for file and directory
+changes using platform-specific file system notification facilities.
+
+This daemon communicates directly with commands like `git status`
+using the link:technical/api-simple-ipc.html[simple IPC] interface
+instead of the slower linkgit:githooks[5] interface.
+
+This daemon is built into Git so that no third-party tools are
+required.
+
+OPTIONS
+-------
+
+start::
+	Starts a daemon in the background.
+
+run::
+	Runs a daemon in the foreground.
+
+stop::
+	Stops the daemon running in the current working
+	directory, if present.
+
+status::
+	Exits with zero status if a daemon is watching the
+	current working directory.
+
+REMARKS
+-------
+
+This daemon is a long running process used to watch a single working
+directory and maintain a list of the recently changed files and
+directories.  Performance of commands such as `git status` can be
+increased if they just ask for a summary of changes to the working
+directory and can avoid scanning the disk.
+
+When `core.useBuiltinFSMonitor` is set to `true` (see
+linkgit:git-config[1]) commands, such as `git status`, will ask the
+daemon for changes and automatically start it (if necessary).
+
+For more information see the "File System Monitor" section in
+linkgit:git-update-index[1].
+
+CAVEATS
+-------
+
+The fsmonitor daemon does not currently know about submodules and does

Documentation/git-fsmonitor--daemon.txt line 75

Quoted 10 lines
Documentation/git-fsmonitor--daemon.txt new file +10 -0
+not know to filter out file system events that happen within a
+submodule.  If fsmonitor daemon is watching a super repo and a file is
+modified within the working directory of a submodule, it will report
+the change (as happening against the super repo).  However, the client
+will properly ignore these extra events, so performance may be affected
+but it will not cause an incorrect result.
+
+GIT
+---
+Part of the linkgit:git[1] suite

Later in the series we incrementally add features to the daemon, so this is describing a state that doesn't exist yet at this point.

Quoted 7 lines

I think it would be better to start with a stup here and add documentation as we add features, e.g. the patch tha adds "start" should add that to the synopsis + options etc.

See the outstanding ab/config-based-hooks-base for a small example of that.

I like to lead the series with the documentation that summarizes the purpose of the entire feature or patch series. This gives the reviewer the context for the complete series that follows. In the past, we've had discussions on the list about how hard it is to review a series when the foo.c comes (alphabetically) before foo.h in the patch and all the documentation is attached to the prototypes in the .h file so the reviewer needs to bounce around in the patch or series to read the intent and then go back to the beginning to read the code. In that spirit, I think that having the complete man page come first provides necessary context and is helpful.

The argument that the man-page should grow as the feature grows presumes that there is a meaningful cut-point mid-series where you would adopt the first portion and delay the second to a later release or something. That division would not be useful/usable.

And it just clutters up later commits in the series with man-page deltas.

So I'd like to keep it as it unless there are further objections.

Jeff

#170 lore raw

Re: [PATCH v3 14/34] fsmonitor--daemon: implement 'start' command

In reply to Ævar Arnfjörð Bjarmason

My response here is in addition to Dscho's remarks on this topic. He makes excellent points that I'll just #include here. I do want to add my own $0.02 here.

Quoted 86 lines · Ævar Arnfjörð Bjarmason
Quoted 68 lines · Jeff Hostetler via GitGitGadget
builtin/fsmonitor--daemon.c +68 -0
+#ifdef GIT_WINDOWS_NATIVE
+/*
+ * Create a background process to run the daemon.  It should be completely
+ * disassociated from the terminal.
+ *
+ * Conceptually like `daemonize()` but different because Windows does not
+ * have `fork(2)`.  Spawn a normal Windows child process but without the
+ * limitations of `start_command()` and `finish_command()`.
+ *
+ * The child process execs the "git fsmonitor--daemon run" command.
+ *
+ * The current process returns so that the caller can wait for the child
+ * to startup before exiting.
+ */
+static int spawn_background_fsmonitor_daemon(pid_t *pid)
+{
+	char git_exe[MAX_PATH];
+	struct strvec args = STRVEC_INIT;
+	int in, out;
+
+	GetModuleFileNameA(NULL, git_exe, MAX_PATH);
+
+	in = open("/dev/null", O_RDONLY);
+	out = open("/dev/null", O_WRONLY);
+
+	strvec_push(&args, git_exe);
+	strvec_push(&args, "fsmonitor--daemon");
+	strvec_push(&args, "run");
+	strvec_pushf(&args, "--ipc-threads=%d", fsmonitor__ipc_threads);
+
+	*pid = mingw_spawnvpe(args.v[0], args.v, NULL, NULL, in, out, out);
+	close(in);
+	close(out);
+
+	strvec_clear(&args);
+
+	if (*pid < 0)
+		return error(_("could not spawn fsmonitor--daemon in the background"));
+
+	return 0;
+}
+#else
+/*
+ * Create a background process to run the daemon.  It should be completely
+ * disassociated from the terminal.
+ *
+ * This is adapted from `daemonize()`.  Use `fork()` to directly
+ * create and run the daemon in the child process.
+ *
+ * The fork-child can just call the run code; it does not need to exec
+ * it.
+ *
+ * The fork-parent returns the child PID so that we can wait for the
+ * child to startup before exiting.
+ */
+static int spawn_background_fsmonitor_daemon(pid_t *pid)
+{
+	*pid = fork();
+
+	switch (*pid) {
+	case 0:
+		if (setsid() == -1)
+			error_errno(_("setsid failed"));
+		close(0);
+		close(1);
+		close(2);
+		sanitize_stdfds();
+

builtin/fsmonitor--daemon.c static int try_to_run_foreground_daemon(void) · line 349

Quoted 10 lines
builtin/fsmonitor--daemon.c +10 -0
+		return !!fsmonitor_run_daemon();
+
+	case -1:
+		return error_errno(_("could not spawn fsmonitor--daemon in the background"));
+
+	default:
+		return 0;
+	}
+}
+#endif

The spawn_background_fsmonitor_daemon() function here is almost the same as daemonize(). I wonder if this & the Windows-specific one you have here can't be refactored into an API from what's now in setup.c.

Quoted 4 lines

Then we could make builtin/gc.c and daemon.c use that, so Windows could have background GC, and we'd have a more battle-tested central codepath for this tricky bit.

I'd rather not refactor all of this and add unnecessary generality and complexity just to save duplicating some of the code in daemonize().

And I'd rather not destabilize existing commands like gc and daemon by changing the daemonize() layer on them. If those commands need help, let's have a separate conversation _later_ about what help they need and if it makes sense to combine them.

Quoted 4 lines

It seems to me like the only limitations on it are to have this return slightly more general things (e.g. not set its own errors, return structured data), and maybe some callback for what to do in the child/parent.

There are several issues here when trying to start a background process and we're already on the edge of the behavioral differences between Windows and Unix -- let's not make things more confusing with multiple callbacks, returning structures, custom errors, and etc.

Also, since Windows doesn't do fork(), we don't have child/parent branches in the call, so this whole "just pretend it's all Unix" model doesn't work.

Even if we did pretend I'd still need ifdef'd callback routines to either call fsmonitor_run_daemon() or build a command line (or have blocks of functions that "just happen to never be called on one platform or the other").

What I have here is an API that the primary (read: parent) calls and gets back a 0 or -1 (with error message). And that's it. The primary can then wait for the child (whether from fork or CreateProcess) to become responsive or fail to start. And then the primary can exit (with or without error).

So I think we're good. Yes, there is an ifdef here, but I think it is worth it.

Quoted 74 lines
Quoted 60 lines
builtin/fsmonitor--daemon.c +59 -0
+/*
+ * This is adapted from `wait_or_whine()`.  Watch the child process and
+ * let it get started and begin listening for requests on the socket
+ * before reporting our success.
+ */
+static int wait_for_background_startup(pid_t pid_child)
+{
+	int status;
+	pid_t pid_seen;
+	enum ipc_active_state s;
+	time_t time_limit, now;
+
+	time(&time_limit);
+	time_limit += fsmonitor__start_timeout_sec;
+
+	for (;;) {
+		pid_seen = waitpid(pid_child, &status, WNOHANG);
+
+		if (pid_seen == -1)
+			return error_errno(_("waitpid failed"));
+		else if (pid_seen == 0) {
+			/*
+			 * The child is still running (this should be
+			 * the normal case).  Try to connect to it on
+			 * the socket and see if it is ready for
+			 * business.
+			 *
+			 * If there is another daemon already running,
+			 * our child will fail to start (possibly
+			 * after a timeout on the lock), but we don't
+			 * care (who responds) if the socket is live.
+			 */
+			s = fsmonitor_ipc__get_state();
+			if (s == IPC_STATE__LISTENING)
+				return 0;
+
+			time(&now);
+			if (now > time_limit)
+				return error(_("fsmonitor--daemon not online yet"));
+		} else if (pid_seen == pid_child) {
+			/*
+			 * The new child daemon process shutdown while
+			 * it was starting up, so it is not listening
+			 * on the socket.
+			 *
+			 * Try to ping the socket in the odd chance
+			 * that another daemon started (or was already
+			 * running) while our child was starting.
+			 *
+			 * Again, we don't care who services the socket.
+			 */
+			s = fsmonitor_ipc__get_state();
+			if (s == IPC_STATE__LISTENING)
+				return 0;
+
+			/*
+			 * We don't care about the WEXITSTATUS() nor
+			 * any of the WIF*(status) values because
+			 * `cmd_fsmonitor__daemon()` does the `!!result`

builtin/fsmonitor--daemon.c static int try_to_run_foreground_daemon(void) · line 419

Quoted 10 lines
builtin/fsmonitor--daemon.c +10 -0
+			 * trick on all function return values.
+			 *
+			 * So it is sufficient to just report the
+			 * early shutdown as an error.
+			 */
+			return error(_("fsmonitor--daemon failed to start"));
+		} else
+			return error(_("waitpid is confused"));
+	}
+}

Ditto this. could we extend the wait_or_whine() function (or some extended version thereof) to do what you need with callbacks?

Quoted 9 lines

It seems the main difference is just being able to pass down a flag for waitpid(), and the loop needing to check EINTR or not depending on whether WNOHANG is passed.

For e.g. the "We don't care about the WEXITSTATUS()" you'd get that behavior with an adjusted wait_or_whine(). Wouldn't it be better to report what exit status it exits with e.g. if the top-level process is signalled? We do so in trace2 for other things we spawn...

Again, I don't want to mix my usage here with the existing code and destabilize all existing callers. Here we are spinning to give the child a chance to *start* and confirm that it is in a listening state and ready for connections. We do not wait for the child to exit (unless it dies quickly without becoming ready).

We want to end our wait as soon as we confirm that the child is ready and return. All I really need from the system is waitpid().

Also, since we started the child in my spawn_background...(), it is not in the children_to_clean list, so there is no need to mess with that.

So I'd like to leave this as is.

Jeff

#179 lore raw

Re: [PATCH v3 17/34] fsmonitor--daemon: define token-ids

In reply to Ævar Arnfjörð Bjarmason
Quoted 14 lines · Ævar Arnfjörð Bjarmason
Quoted 11 lines · Jeff Hostetler via GitGitGadget
builtin/fsmonitor--daemon.c +11 -0
+	if (!test_env_value) {
+		struct timeval tv;
+		struct tm tm;
+		time_t secs;
+
+		gettimeofday(&tv, NULL);
+		secs = tv.tv_sec;
+		gmtime_r(&secs, &tm);
+
+		strbuf_addf(&token->token_id,
+			    "%"PRIu64".%d.%4d%02d%02dT%02d%02d%02d.%06ldZ",
Quoted 8 lines

builtin/fsmonitor--daemon.c static int do_as_client__status(void) · line 202

Quoted 5 lines
builtin/fsmonitor--daemon.c +5 -0
+			    flush_count++,
+			    getpid(),
+			    tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
+			    tm.tm_hour, tm.tm_min, tm.tm_sec,
+			    (long)tv.tv_usec);

Just bikeshedding, but can we have tokens that mostly sort numeric-wise by time order? So time at the start, not the flush_count/getpid.

As I described in a rather large comment in the code, tokens are opaque strings -- without a less-than / greater-than relationship -- just a random string that the daemon can use (along with a sequence number) to ensure that a later request is well-defined.

Here I'm using a counter, pid, and date-stamp. I'd prefer using a GUID or UUID just to drive that home, but I didn't want to add a new .lib or .a to the build if not necessary.

Perhaps I should compute this portion as hex(hash(time())) to remove the temptation to look inside my opaque token ??

Quoted 7 lines

Maybe I'm missing something, but couldn't we just re-use the trace2 SID
+ a more trivial trailer? It would have the nice property that you could find the trace2 SID whenever you looked at such a token (could e.g. split them by "/" too), and add the tv_usec, flush_count+whatever else is needed to make it unique after the "/", no?

I would rather keep Trace2 out of this. The SID is another opaque string and I don't want to reach inside it.

Jeff

#192 lore raw

Re: [PATCH v3 19/34] fsmonitor-fs-listen-win32: implement FSMonitor backend on Windows

In reply to Johannes Schindelin
Quoted 92 lines · Johannes Schindelin

Hi Jeff,

On Thu, 1 Jul 2021, Jeff Hostetler via GitGitGadget wrote:

Jeff Hostetler <[email protected]>

the win32 backend to register a watch on the working tree irectory (recursively). Also watch the <gitdir> if it is side the working tree. And to collect path change notifications atches and publish.

-off-by: Jeff Hostetler <[email protected]>

t/fsmonitor/fsmonitor-fs-listen-win32.c | 530 +++++++++++++++++++ e changed, 530 insertions(+)

Quoted 10 lines
compat/fsmonitor/fsmonitor-fs-listen-win32.c +5 -0
index 880446b49e3..d707d47a0d7 100644
@@ -2,20 +2,550 @@
+ [...]
+
+static struct one_watch *create_watch(struct fsmonitor_daemon_state *state,
+				      const char *path)
+{

compat/fsmonitor/fsmonitor-fs-listen-win32.c line 16

Quoted 10 lines
compat/fsmonitor/fsmonitor-fs-listen-win32.c +10 -0
+	struct one_watch *watch = NULL;
+	DWORD desired_access = FILE_LIST_DIRECTORY;
+	DWORD share_mode =
+		FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE;
+	HANDLE hDir;
+
+	hDir = CreateFileA(path,
+			   desired_access, share_mode, NULL, OPEN_EXISTING,
+			   FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
+			   NULL);

The *A() family of Win32 API functions disagree with Git in one very interesting aspect: Git always assumes UTF-8, while e.g. CreateFileA() will use the current Win32 locale to internally transform to wide characters and then call CreateFileW().

This poses no problem when your locale is US American and your paths contain no non-ASCII characters.

In the Git for Windows bug tracker, it was reported that it _does_ cause problems when venturing outside such a cozy scenario (for full details, see https://github.com/git-for-windows/git/issues/3262)

I need this (and merged it before starting the process to release Git for Windows v2.32.0(2)) to fix that (could I ask you to integrate this in case a re-roll will become necessary?):

-- snipsnap --
From: Johannes Schindelin <[email protected]>
Date: Mon, 5 Jul 2021 13:51:05 +0200
Subject: [PATCH] fixup! fsmonitor-fs-listen-win32: implement FSMonitor backend
on Windows

Let's keep avoiding the *A() family of Win32 API functions because they are susceptible to incoherent encoding problems. In Git for Windows, we always assume paths to be UTF-8 encoded. Let's use the dedicated helper to convert such a path to the wide character version, and then use the *W() function instead.

---
  compat/fsmonitor/fsmonitor-fs-listen-win32.c | 8 +++++++-
  1 file changed, 7 insertions(+), 1 deletion(-)
compat/fsmonitor/fsmonitor-fs-listen-win32.c +7 -2
index ba087b292df..3b42ab311d9 100644
@@ -111,8 +111,14 @@ static struct one_watch *create_watch(struct fsmonitor_daemon_state *state,
  	DWORD share_mode =
  		FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE;
  	HANDLE hDir;
+	wchar_t wpath[MAX_PATH];
 
-	hDir = CreateFileA(path,
+	if (xutftowcs_path(wpath, path) < 0) {
+		error(_("could not convert to wide characters: '%s'"), path);
+		return NULL;
+	}
+
+	hDir = CreateFileW(wpath,
  			   desired_access, share_mode, NULL, OPEN_EXISTING,
  			   FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
  			   NULL);
--
Quoted 2 lines

2.32.0.windows.1.15.gf1590a75e2d

Thanks for the heads up. I'll pull this into my next release. Jeff

#184 lore raw

Re: [PATCH v3 19/34] fsmonitor-fs-listen-win32: implement FSMonitor backend on Windows

In reply to Ævar Arnfjörð Bjarmason
Quoted 14 lines · Ævar Arnfjörð Bjarmason
Quoted 10 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Teach the win32 backend to register a watch on the working tree root directory (recursively). Also watch the <gitdir> if it is not inside the working tree. And to collect path change notifications into batches and publish.

---
  compat/fsmonitor/fsmonitor-fs-listen-win32.c | 530 +++++++++++++++++++
Quoted 7 lines

<bikeshed mode> Spying on the early history of this (looking for the Linux backend) I saw that at some point we had just compat/fsmonitor/linux.c, and presumably some of compat/fsmonitor/{windows,win32,macos,darwin}.c.

At some point those filenames became much much longer.

Once upon a time having "foo/bar/win32.c" and "abc/def/win32.c" would cause confusion in the debugger (I've long since forgotten which). Breaking at win32.c:30 was no longer unique.

Also, if the Makefile sends all .o's to the root directory or a unified OBJS directory rather than to the subdir containing the .c, then we have another issue during linking...

So, having been burned too many times, I prefer to make source filenames unique when possible.

Quoted 7 lines

I've noticed you tend to prefer really long file and function names, e.g. your borrowed daemonize() became spawn_background_fsmonitor_daemon(), I think aiming for shorter filenames & function names helps, e.g. these long names widen diffstats, and many people who hack on the code stick religiously to 80 character width terminals.

I prefer self-documenting code.

Jeff

#198 lore raw

Re: [PATCH v3 23/34] t/helper/test-touch: add helper to touch a series of files

In reply to Junio C Hamano
Quoted 24 lines · Junio C Hamano
Quoted 9 lines · Jeff Hostetler via GitGitGadget
t/helper/test-touch.c new file +3 -0
new file mode 100644
index 00000000000..e9b3b754f1f
@@ -0,0 +1,126 @@
+/*
+ * test-touch.c: variation on /usr/bin/touch to speed up tests
+ * with a large number of files (primarily on Windows where child

t/helper/test-touch.c line 13

Quoted 10 lines
t/helper/test-touch.c new file +10 -0
+ * process are very, very expensive).
+ */
+
+#include "test-tool.h"
+#include "cache.h"
+#include "parse-options.h"
+
+char *seq_pattern;
+int seq_start = 1;
+int seq_count = 1;

With this in, "make sparse" dies like this:

Quoted 5 lines

SP t/helper/test-touch.c
t/helper/test-touch.c:11:6: error: symbol 'seq_pattern' was not declared. Should it be static? t/helper/test-touch.c:12:5: error: symbol 'seq_start' was not declared. Should it be static? t/helper/test-touch.c:13:5: error: symbol 'seq_count' was not declared. Should it be static?

I'll fix. Thanks!
Jeff

#201 lore raw

Re: [PATCH v3 24/34] t/perf/p7519: speed up test using "test-tool touch"

In reply to Ævar Arnfjörð Bjarmason
Quoted 56 lines · Ævar Arnfjörð Bjarmason
Quoted 40 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Change p7519 to use a single "test-tool touch" command to update the mtime on a series of (thousands) files instead of invoking thousands of commands to update a single file.

This is primarily for Windows where process creation is so very slow and reduces the test run time by minutes.

---
  t/perf/p7519-fsmonitor.sh | 14 ++++++--------
  1 file changed, 6 insertions(+), 8 deletions(-)
t/perf/p7519-fsmonitor.sh +5 -6
index 5eb5044a103..f74e6014a0a 100755
@@ -119,10 +119,11 @@ test_expect_success "one time repo setup" '
  	fi &&
 
  	mkdir 1_file 10_files 100_files 1000_files 10000_files &&
-	for i in $(test_seq 1 10); do touch 10_files/$i; done &&
-	for i in $(test_seq 1 100); do touch 100_files/$i; done &&
-	for i in $(test_seq 1 1000); do touch 1000_files/$i; done &&
-	for i in $(test_seq 1 10000); do touch 10000_files/$i; done &&
+	test-tool touch sequence --pattern="10_files/%d" --start=1 --count=10 &&
+	test-tool touch sequence --pattern="100_files/%d" --start=1 --count=100 &&
+	test-tool touch sequence --pattern="1000_files/%d" --start=1 --count=1000 &&
+	test-tool touch sequence --pattern="10000_files/%d" --start=1 --count=10000 &&
+
  	git add 1_file 10_files 100_files 1000_files 10000_files &&
  	git commit -qm "Add files" &&
 
@@ -200,15 +201,12 @@ test_fsmonitor_suite() {
  	# Update the mtimes on upto 100k files to make status think
  	# that they are dirty.  For simplicity, omit any files with
  	# LFs (i.e. anything that ls-files thinks it needs to dquote).
-	# Then fully backslash-quote the paths to capture any
-	# whitespace so that they pass thru xargs properly.

t/perf/p7519-fsmonitor.sh test_fsmonitor_suite() { · line 211

Quoted 10 lines
t/perf/p7519-fsmonitor.sh +1 -2
  	#
  	test_perf_w_drop_caches "status (dirty) ($DESC)" '
  		git ls-files | \
  			head -100000 | \
  			grep -v \" | \
-			sed '\''s/\(.\)/\\\1/g'\'' | \
-			xargs test-tool chmtime -300 &&
+			test-tool touch stdin &&
  		git status
  	'

Did you try to replace this with some variant of:

Quoted 5 lines
     test_seq 1 10000 | xargs touch

Which (depending on your xargs version) would invoke "touch" commands with however many argv items it thinks you can handle.

a quick test on my Windows machine shows that

	test_seq 1 10000 | xargs touch

takes 3.1 seconds.

just a simple

	test_seq 1 10000 >/dev/null

take 0.2 seconds.

using my test-tool helper cuts that time in half.

Jeff

#202 lore raw

Re: [PATCH v3 24/34] t/perf/p7519: speed up test using "test-tool touch"

Quoted 16 lines · Jeff Hostetler
Quoted 7 lines · Ævar Arnfjörð Bjarmason

Did you try to replace this with some variant of:

     test_seq 1 10000 | xargs touch

Which (depending on your xargs version) would invoke "touch" commands with however many argv items it thinks you can handle.

a quick test on my Windows machine shows that

        test_seq 1 10000 | xargs touch

takes 3.1 seconds.

Quoted 7 lines

just a simple

        test_seq 1 10000 >/dev/null

take 0.2 seconds.

using my test-tool helper cuts that time in half.

Yeah, test_seq is pretty bad; it's just a loop in shell. Is there a 'seq' on windows, and does using it instead of test_seq make things faster with Ævar's suggested command?

I'd really like to modify test_seq to use seq when it's available and fall back to the looping-in-shell when we need to for various platforms.

Maybe it'd even make sense to write a 'test-tool seq' and make test_seq use that just so we can rip out that super lame shell looping.

#203 lore raw

Re: [PATCH v3 24/34] t/perf/p7519: speed up test using "test-tool touch"

Quoted 12 lines · Elijah Newren
Quoted 11 lines · Jeff Hostetler

a quick test on my Windows machine shows that

        test_seq 1 10000 | xargs touch

takes 3.1 seconds.

just a simple

        test_seq 1 10000 >/dev/null
Quoted 7 lines
Quoted 3 lines

take 0.2 seconds.

using my test-tool helper cuts that time in half.

Yeah, test_seq is pretty bad; it's just a loop in shell. Is there a 'seq' on windows, and does using it instead of test_seq make things faster with Ævar's suggested command?

Unless I am misreading Jeff's message, I do not think that makes sense. Counting to 10000 in shell loop is trivial (0.2 seconds), but letting touch invoked 10000 times to create (or smudge mtime of, but I suspect that is not what is going on here) 10000 files takes 3.1 seconds, and of course a native binary that creates 10000 files with a single invocation would be faster.

Quoted 3 lines

I'd really like to modify test_seq to use seq when it's available and fall back to the looping-in-shell when we need to for various platforms.

So, if I am reading Jeff correctly, that optimizes something that is not a bottleneck.

Quoted 3 lines

Maybe it'd even make sense to write a 'test-tool seq' and make test_seq use that just so we can rip out that super lame shell looping.

#204 lore raw

Re: [PATCH v3 24/34] t/perf/p7519: speed up test using "test-tool touch"

Quoted 30 lines · Junio C Hamano
Quoted 12 lines · Elijah Newren
Quoted 11 lines · Jeff Hostetler

a quick test on my Windows machine shows that

        test_seq 1 10000 | xargs touch

takes 3.1 seconds.

just a simple

        test_seq 1 10000 >/dev/null
Quoted 7 lines
Quoted 3 lines

take 0.2 seconds.

using my test-tool helper cuts that time in half.

Yeah, test_seq is pretty bad; it's just a loop in shell. Is there a 'seq' on windows, and does using it instead of test_seq make things faster with Ævar's suggested command?

Unless I am misreading Jeff's message, I do not think that makes sense. Counting to 10000 in shell loop is trivial (0.2 seconds), but letting touch invoked 10000 times to create (or smudge mtime of, but I suspect that is not what is going on here) 10000 files takes 3.1 seconds, and of course a native binary that creates 10000 files with a single invocation would be faster.

Quoted 6 lines
Quoted 3 lines

I'd really like to modify test_seq to use seq when it's available and fall back to the looping-in-shell when we need to for various platforms.

So, if I am reading Jeff correctly, that optimizes something that is not a bottleneck.

Oh, indeed. Sorry, I misread.

#128 lore raw

Re: [PATCH v3 02/34] fsmonitor--daemon: man page

In reply to Jeff Hostetler
Quoted 116 lines · Jeff Hostetler
Quoted 94 lines · Ævar Arnfjörð Bjarmason
Quoted 81 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Create a manual page describing the git fsmonitor--daemon feature.

---
  Documentation/git-fsmonitor--daemon.txt | 75 +++++++++++++++++++++++++
  1 file changed, 75 insertions(+)

create mode 100644 Documentation/git-fsmonitor--daemon.txt

Documentation/git-fsmonitor--daemon.txt new file +65 -0
new file mode 100644
index 00000000000..154e7684daa
@@ -0,0 +1,75 @@
+git-fsmonitor--daemon(1)
+========================
+
+NAME
+----
+git-fsmonitor--daemon - A Built-in File System Monitor
+
+SYNOPSIS
+--------
+[verse]
+'git fsmonitor--daemon' start
+'git fsmonitor--daemon' run
+'git fsmonitor--daemon' stop
+'git fsmonitor--daemon' status
+
+DESCRIPTION
+-----------
+
+A daemon to watch the working directory for file and directory
+changes using platform-specific file system notification facilities.
+
+This daemon communicates directly with commands like `git status`
+using the link:technical/api-simple-ipc.html[simple IPC] interface
+instead of the slower linkgit:githooks[5] interface.
+
+This daemon is built into Git so that no third-party tools are
+required.
+
+OPTIONS
+-------
+
+start::
+	Starts a daemon in the background.
+
+run::
+	Runs a daemon in the foreground.
+
+stop::
+	Stops the daemon running in the current working
+	directory, if present.
+
+status::
+	Exits with zero status if a daemon is watching the
+	current working directory.
+
+REMARKS
+-------
+
+This daemon is a long running process used to watch a single working
+directory and maintain a list of the recently changed files and
+directories.  Performance of commands such as `git status` can be
+increased if they just ask for a summary of changes to the working
+directory and can avoid scanning the disk.
+
+When `core.useBuiltinFSMonitor` is set to `true` (see
+linkgit:git-config[1]) commands, such as `git status`, will ask the
+daemon for changes and automatically start it (if necessary).
+
+For more information see the "File System Monitor" section in
+linkgit:git-update-index[1].
+
+CAVEATS
+-------
+
+The fsmonitor daemon does not currently know about submodules and does

Documentation/git-fsmonitor--daemon.txt line 75

Quoted 10 lines
Documentation/git-fsmonitor--daemon.txt new file +10 -0
+not know to filter out file system events that happen within a
+submodule.  If fsmonitor daemon is watching a super repo and a file is
+modified within the working directory of a submodule, it will report
+the change (as happening against the super repo).  However, the client
+will properly ignore these extra events, so performance may be affected
+but it will not cause an incorrect result.
+
+GIT
+---
+Part of the linkgit:git[1] suite

Later in the series we incrementally add features to the daemon, so

Quoted 9 lines

this
is describing a state that doesn't exist yet at this point. I think it would be better to start with a stup here and add documentation as we add features, e.g. the patch tha adds "start" should add that to the synopsis + options etc.
See the outstanding ab/config-based-hooks-base for a small example of
that.

I like to lead the series with the documentation that summarizes the purpose of the entire feature or patch series. This gives the reviewer the context for the complete series that follows. In the past, we've had discussions on the list about how hard it is to review a series when the foo.c comes (alphabetically) before foo.h in the patch and all the documentation is attached to the prototypes in the .h file so the reviewer needs to bounce around in the patch or series to read the intent and then go back to the beginning to read the code. In that spirit, I think that having the complete man page come first provides necessary context and is helpful.

Quoted 4 lines

The argument that the man-page should grow as the feature grows presumes that there is a meaningful cut-point mid-series where you would adopt the first portion and delay the second to a later release or something. That division would not be useful/usable.

Isn't there such a meaningful cut-off point?

In 08/34[1] you add a skeleton of the daemon, so then the NAME/SYNOPSIS (empty)/DESCRIPTION/REMARKS/GIT sections could be added, in 09/34[3] you add stop/status commands, so then the SYNOPSIS/OPTIONS could be updated/created with those, same for the addition of run/start in 13/34[5] and 14/35[6] respectively.

Quoted 4 lines

And it just clutters up later commits in the series with man-page deltas.

So I'd like to keep it as it unless there are further objections.

I'll leave it to you to do with the feedback as you choose,

I suggested this because I think it's much easier to read patches that are larger because they incrementally update docs or tests along with code, than smaller ones that are e.g. "add docs" followed by incrementally modifying the code.

That's because you can consider those atomically. If earlier doc changes refer to later code changes you're left jumping back & forth and wondering if the code you're reading that doesn't match the docs yet is a bug, or if it's solved in some later change you're now needing to mentally keep track of.

Which is not some theoretical concern b.t.w., but exactly what I found myself doing when reading this series, hence the suggestion.

1. https://lore.kernel.org/git/f88db92d4259d1c29827e97e957daf6eda39c551.1625150864.git.gitgitgadget@gmail.com/
2. https://lore.kernel.org/git/[email protected]/
3. https://lore.kernel.org/git/02e21384ef0ca4909e0bda2c78fa63c06be22a50.1625150864.git.gitgitgadget@gmail.com/
4. https://lore.kernel.org/git/5d6646df93a17659af66f136295444d1bd834090.1625150864.git.gitgitgadget@gmail.com/
5. https://lore.kernel.org/git/5d6646df93a17659af66f136295444d1bd834090.1625150864.git.gitgitgadget@gmail.com/
6. https://lore.kernel.org/git/9fe902aad87f1192705fb69ea212a2d066d0286d.1625150864.git.gitgitgadget@gmail.com/

#205 lore raw

Re: [PATCH v3 24/34] t/perf/p7519: speed up test using "test-tool touch"

In reply to Elijah Newren
Quoted 21 lines · Elijah Newren
Quoted 20 lines · Jeff Hostetler
Quoted 7 lines · Ævar Arnfjörð Bjarmason

Did you try to replace this with some variant of:

      test_seq 1 10000 | xargs touch

Which (depending on your xargs version) would invoke "touch" commands with however many argv items it thinks you can handle.

a quick test on my Windows machine shows that

         test_seq 1 10000 | xargs touch

takes 3.1 seconds.

just a simple

         test_seq 1 10000 >/dev/null
Quoted 8 lines
Quoted 3 lines

take 0.2 seconds.

using my test-tool helper cuts that time in half.

Yeah, test_seq is pretty bad; it's just a loop in shell. Is there a 'seq' on windows, and does using it instead of test_seq make things faster with Ævar's suggested command?

The Git for Windows SDK bash environment does have a /usr/bin/seq which appears to be from GNU coreutils 8.32. (This is different from the version that I have on my Mac (which doesn't have a version number).)

Using it:

	seq 1 10000 >/dev/null

takes 0.04 seconds instead of 0.2.

However, it doesn't help the touch.

	seq 1 10000 | xargs touch

still takes ~3.1 seconds.

FWIW, the xargs is clustering the 10,000 files into ~4 command lines, so there is a little bit of Windows process overhead, but not that much.

seq 1 10000 | xargs wc -l | grep total

Quoted 8 lines

I'd really like to modify test_seq to use seq when it's available and fall back to the looping-in-shell when we need to for various platforms.

Maybe it'd even make sense to write a 'test-tool seq' and make test_seq use that just so we can rip out that super lame shell looping.

#211 lore raw

Re: [PATCH v3 24/34] t/perf/p7519: speed up test using "test-tool touch"

In reply to Jeff Hostetler via GitGitGadget
Quoted 20 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Change p7519 to use a single "test-tool touch" command to update the mtime on a series of (thousands) files instead of invoking thousands of commands to update a single file.

This is primarily for Windows where process creation is so very slow and reduces the test run time by minutes.

---
  t/perf/p7519-fsmonitor.sh | 14 ++++++--------
  1 file changed, 6 insertions(+), 8 deletions(-)
t/perf/p7519-fsmonitor.sh +0 -0
index 5eb5044a103..f74e6014a0a 100755
@@ -119,10 +119,11 @@ test_expect_success "one time repo setup" '
  	fi &&

t/perf/p7519-fsmonitor.sh test_expect_success "one time repo setup" ' · line 125

Quoted 10 lines
t/perf/p7519-fsmonitor.sh +4 -4
 
  	mkdir 1_file 10_files 100_files 1000_files 10000_files &&
-	for i in $(test_seq 1 10); do touch 10_files/$i; done &&
-	for i in $(test_seq 1 100); do touch 100_files/$i; done &&
-	for i in $(test_seq 1 1000); do touch 1000_files/$i; done &&
-	for i in $(test_seq 1 10000); do touch 10000_files/$i; done &&
+	test-tool touch sequence --pattern="10_files/%d" --start=1 --count=10 &&
+	test-tool touch sequence --pattern="100_files/%d" --start=1 --count=100 &&
+	test-tool touch sequence --pattern="1000_files/%d" --start=1 --count=1000 &&
+	test-tool touch sequence --pattern="10000_files/%d" --start=1 --count=10000 &&

The big win in taking *minutes* off of the run time of this test was getting rid of the for loops and one touch invocation per file.

So whether we keep my test-tool touch command or switch to test_seq or seq is open for debate. Mine seems quicker, but it is more or less round off error in the larger picture considering what we started with.

I'll play with this a bit.
Jeff

#206 lore raw

Re: [PATCH v3 24/34] t/perf/p7519: speed up test using "test-tool touch"

In reply to Jeff Hostetler
Quoted 4 lines · Jeff Hostetler

FWIW, the xargs is clustering the 10,000 files into ~4 command lines, so there is a little bit of Windows process overhead, but not that much.

Quoted 9 lines

seq 1 10000 | xargs wc -l | grep total

Quoted 7 lines

I'd really like to modify test_seq to use seq when it's available and fall back to the looping-in-shell when we need to for various platforms.
Maybe it'd even make sense to write a 'test-tool seq' and make test_seq use that just so we can rip out that super lame shell looping.

So what lame in this picture is not shell, or process overhead, but I/O performance.

I've seen some noises about Windows file creation performance raised as an issue when doing initial checkout followed by "git clone", and an idea floated to create a bunch of open file handles for writing in threads when checkout (really the caller that repeatedly calls entry.c:write_entry() by iterating the in-core index) starts, and write out the contents in parallel, as a workaround. When I heard it, I somehow thought it was meant as a not-so-funny joke, but from the sounds of it, the I/O performance may be so horrible to require such a hack to be usable there. Sigh...

#171 lore raw

Re: [PATCH v3 14/34] fsmonitor--daemon: implement 'start' command

In reply to Jeff Hostetler
Quoted 95 lines · Jeff Hostetler

My response here is in addition to Dscho's remarks on this topic. He makes excellent points that I'll just #include here. I do want to add my own $0.02 here.

Quoted 80 lines · Ævar Arnfjörð Bjarmason
Quoted 68 lines · Jeff Hostetler via GitGitGadget
builtin/fsmonitor--daemon.c +68 -0
+#ifdef GIT_WINDOWS_NATIVE
+/*
+ * Create a background process to run the daemon.  It should be completely
+ * disassociated from the terminal.
+ *
+ * Conceptually like `daemonize()` but different because Windows does not
+ * have `fork(2)`.  Spawn a normal Windows child process but without the
+ * limitations of `start_command()` and `finish_command()`.
+ *
+ * The child process execs the "git fsmonitor--daemon run" command.
+ *
+ * The current process returns so that the caller can wait for the child
+ * to startup before exiting.
+ */
+static int spawn_background_fsmonitor_daemon(pid_t *pid)
+{
+	char git_exe[MAX_PATH];
+	struct strvec args = STRVEC_INIT;
+	int in, out;
+
+	GetModuleFileNameA(NULL, git_exe, MAX_PATH);
+
+	in = open("/dev/null", O_RDONLY);
+	out = open("/dev/null", O_WRONLY);
+
+	strvec_push(&args, git_exe);
+	strvec_push(&args, "fsmonitor--daemon");
+	strvec_push(&args, "run");
+	strvec_pushf(&args, "--ipc-threads=%d", fsmonitor__ipc_threads);
+
+	*pid = mingw_spawnvpe(args.v[0], args.v, NULL, NULL, in, out, out);
+	close(in);
+	close(out);
+
+	strvec_clear(&args);
+
+	if (*pid < 0)
+		return error(_("could not spawn fsmonitor--daemon in the background"));
+
+	return 0;
+}
+#else
+/*
+ * Create a background process to run the daemon.  It should be completely
+ * disassociated from the terminal.
+ *
+ * This is adapted from `daemonize()`.  Use `fork()` to directly
+ * create and run the daemon in the child process.
+ *
+ * The fork-child can just call the run code; it does not need to exec
+ * it.
+ *
+ * The fork-parent returns the child PID so that we can wait for the
+ * child to startup before exiting.
+ */
+static int spawn_background_fsmonitor_daemon(pid_t *pid)
+{
+	*pid = fork();
+
+	switch (*pid) {
+	case 0:
+		if (setsid() == -1)
+			error_errno(_("setsid failed"));
+		close(0);
+		close(1);
+		close(2);
+		sanitize_stdfds();
+

builtin/fsmonitor--daemon.c static int try_to_run_foreground_daemon(void) · line 349

Quoted 10 lines
builtin/fsmonitor--daemon.c +10 -0
+		return !!fsmonitor_run_daemon();
+
+	case -1:
+		return error_errno(_("could not spawn fsmonitor--daemon in the background"));
+
+	default:
+		return 0;
+	}
+}
+#endif
Quoted 9 lines

The spawn_background_fsmonitor_daemon() function here is almost the same
as daemonize(). I wonder if this & the Windows-specific one you have here can't be refactored into an API from what's now in setup.c. Then we could make builtin/gc.c and daemon.c use that, so Windows could
have background GC, and we'd have a more battle-tested central codepath for this tricky bit.

Quoted 7 lines

I'd rather not refactor all of this and add unnecessary generality and complexity just to save duplicating some of the code in daemonize().

And I'd rather not destabilize existing commands like gc and daemon by changing the daemonize() layer on them. If those commands need help, let's have a separate conversation _later_ about what help they need and if it makes sense to combine them.

Johannes suggested in
https://lore.kernel.org/git/[email protected]/
that (if I understand that correctly, and I just skimmed the linked isse some days ago), that even if such a refactoring was done these two functions are solving subtly different problems, or something. I.e. we couldn't use it for daemonize().

Which I'd say is interesting for the code comments/commit message at least, i.e. how they're solving subtly different problems (not being able to run this & not being able to test on Windows I haven't poked at it myself).

Quoted 5 lines
Quoted 4 lines

It seems to me like the only limitations on it are to have this return slightly more general things (e.g. not set its own errors, return structured data), and maybe some callback for what to do in the child/parent.

Quoted 8 lines

There are several issues here when trying to start a background process and we're already on the edge of the behavioral differences between Windows and Unix -- let's not make things more confusing with multiple callbacks, returning structures, custom errors, and etc.

Also, since Windows doesn't do fork(), we don't have child/parent branches in the call, so this whole "just pretend it's all Unix" model doesn't work.

Fair enough, And I think replied-to above.

Quoted 6 lines

Even if we did pretend I'd still need ifdef'd callback routines to either call fsmonitor_run_daemon() or build a command line (or have blocks of functions that "just happen to never be called on one platform or the other").

Quoted 8 lines

What I have here is an API that the primary (read: parent) calls and gets back a 0 or -1 (with error message). And that's it. The primary can then wait for the child (whether from fork or CreateProcess) to become responsive or fail to start. And then the primary can exit (with or without error).

So I think we're good. Yes, there is an ifdef here, but I think it is worth it.

FWIW what I was noting here & elsewhere is that yes, you need to ifdef some of it, but the code you're proposing to add here is using a different pattern than the one generally preferred in this codebase.

I.e. check out how we do it for threading, we intentionally compile the "if (thread) {code}" clauses on platforms we know don't have threading, ditto the code around PCRE in grep.c.

Similarly, here in e.g. spawn_background_fsmonitor_daemon just the GetModuleFileNameA() and mingw_spawnvpe() are Windows-specifics (and could be calls to some helper that *is* ifdef'd).

In this case it's not a big deal, but as a general pattern it helps to e.g. avoid subtle syntax errors in nested ifdefs and the like, and generally encourages keeping the ifdef'd code as small as possible.

Quoted 81 lines
Quoted 71 lines
Quoted 59 lines
builtin/fsmonitor--daemon.c +59 -0
+/*
+ * This is adapted from `wait_or_whine()`.  Watch the child process and
+ * let it get started and begin listening for requests on the socket
+ * before reporting our success.
+ */
+static int wait_for_background_startup(pid_t pid_child)
+{
+	int status;
+	pid_t pid_seen;
+	enum ipc_active_state s;
+	time_t time_limit, now;
+
+	time(&time_limit);
+	time_limit += fsmonitor__start_timeout_sec;
+
+	for (;;) {
+		pid_seen = waitpid(pid_child, &status, WNOHANG);
+
+		if (pid_seen == -1)
+			return error_errno(_("waitpid failed"));
+		else if (pid_seen == 0) {
+			/*
+			 * The child is still running (this should be
+			 * the normal case).  Try to connect to it on
+			 * the socket and see if it is ready for
+			 * business.
+			 *
+			 * If there is another daemon already running,
+			 * our child will fail to start (possibly
+			 * after a timeout on the lock), but we don't
+			 * care (who responds) if the socket is live.
+			 */
+			s = fsmonitor_ipc__get_state();
+			if (s == IPC_STATE__LISTENING)
+				return 0;
+
+			time(&now);
+			if (now > time_limit)
+				return error(_("fsmonitor--daemon not online yet"));
+		} else if (pid_seen == pid_child) {
+			/*
+			 * The new child daemon process shutdown while
+			 * it was starting up, so it is not listening
+			 * on the socket.
+			 *
+			 * Try to ping the socket in the odd chance
+			 * that another daemon started (or was already
+			 * running) while our child was starting.
+			 *
+			 * Again, we don't care who services the socket.
+			 */
+			s = fsmonitor_ipc__get_state();
+			if (s == IPC_STATE__LISTENING)
+				return 0;
+
+			/*
+			 * We don't care about the WEXITSTATUS() nor
+			 * any of the WIF*(status) values because
+			 * `cmd_fsmonitor__daemon()` does the `!!result`

builtin/fsmonitor--daemon.c static int try_to_run_foreground_daemon(void) · line 419

Quoted 10 lines
builtin/fsmonitor--daemon.c +10 -0
+			 * trick on all function return values.
+			 *
+			 * So it is sufficient to just report the
+			 * early shutdown as an error.
+			 */
+			return error(_("fsmonitor--daemon failed to start"));
+		} else
+			return error(_("waitpid is confused"));
+	}
+}

Ditto this. could we extend the wait_or_whine() function (or some extended version thereof) to do what you need with callbacks?

Quoted 9 lines

It seems the main difference is just being able to pass down a flag for
waitpid(), and the loop needing to check EINTR or not depending on whether WNOHANG is passed.
For e.g. the "We don't care about the WEXITSTATUS()" you'd get that behavior with an adjusted wait_or_whine(). Wouldn't it be better to report what exit status it exits with e.g. if the top-level process is signalled? We do so in trace2 for other things we spawn...

Quoted 8 lines

Again, I don't want to mix my usage here with the existing code and destabilize all existing callers. Here we are spinning to give the child a chance to *start* and confirm that it is in a listening state and ready for connections. We do not wait for the child to exit (unless it dies quickly without becoming ready).

We want to end our wait as soon as we confirm that the child is ready and return. All I really need from the system is waitpid().

Will this code behave correctly if the daemon we start is signalled per the WIFSIGNALED() cases the code this is derived handles, but this does not?

But sure, I just meant to point out that the flip side to "destabilize all existing callers" is reviewing new code that may be subtly buggy, and those subtle bugs (if any) would be smoked out if we were forced to extend run-command.c, i.e. to use whatever feature(s) this needs for all existing callers.

#180 lore raw

Re: [PATCH v3 17/34] fsmonitor--daemon: define token-ids

In reply to Jeff Hostetler
Quoted 28 lines · Jeff Hostetler
Quoted 13 lines · Ævar Arnfjörð Bjarmason
Quoted 11 lines · Jeff Hostetler via GitGitGadget
builtin/fsmonitor--daemon.c +11 -0
+	if (!test_env_value) {
+		struct timeval tv;
+		struct tm tm;
+		time_t secs;
+
+		gettimeofday(&tv, NULL);
+		secs = tv.tv_sec;
+		gmtime_r(&secs, &tm);
+
+		strbuf_addf(&token->token_id,
+			    "%"PRIu64".%d.%4d%02d%02dT%02d%02d%02d.%06ldZ",
Quoted 8 lines

builtin/fsmonitor--daemon.c static int do_as_client__status(void) · line 202

Quoted 5 lines
builtin/fsmonitor--daemon.c +5 -0
+			    flush_count++,
+			    getpid(),
+			    tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
+			    tm.tm_hour, tm.tm_min, tm.tm_sec,
+			    (long)tv.tv_usec);

Just bikeshedding, but can we have tokens that mostly sort numeric-wise
by time order? So time at the start, not the flush_count/getpid.

As I described in a rather large comment in the code, tokens are opaque strings -- without a less-than / greater-than relationship -- just a random string that the daemon can use (along with a sequence number) to ensure that a later request is well-defined.

Quoted 6 lines

Here I'm using a counter, pid, and date-stamp. I'd prefer using a GUID or UUID just to drive that home, but I didn't want to add a new .lib or .a to the build if not necessary.

Perhaps I should compute this portion as hex(hash(time())) to remove the temptation to look inside my opaque token ??

Why does it matter if someone looks inside your opaque token if the code is treating it as opaque by just doing a strcmp(old,new) on it?

I just suggested this as a debugging aid, i.e. when you the human (as opposed to the program) are looking at this behavior it's handy to look at the token and see that your cookies don't match, and that they look to be N seconds apart.

And furthermore, if git crashes or whatever you can now easily look up what process crashed if you've got the leftover cookie, if you've also got trace2 logs.

Quoted 10 lines
Quoted 7 lines

Maybe I'm missing something, but couldn't we just re-use the trace2 SID
+ a more trivial trailer? It would have the nice property that you could find the trace2 SID whenever you looked at such a token (could e.g. split them by "/" too), and add the tv_usec, flush_count+whatever else is needed to make it unique after the "/", no?

I would rather keep Trace2 out of this. The SID is another opaque string and I don't want to reach inside it.

For the purposes of the git.git codebase it's fine to reach inside of it, especially for a "I'd like a near-enough-UUID, and I know the trace2 SID already does that per-program", so you just need e.g. a sequence counter within the program to ensure global uniqueness with other git processes for such a cookie.

#185 lore raw

Re: [PATCH v3 19/34] fsmonitor-fs-listen-win32: implement FSMonitor backend on Windows

In reply to Jeff Hostetler
Quoted 24 lines · Jeff Hostetler
Quoted 9 lines · Ævar Arnfjörð Bjarmason
Quoted 7 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Teach the win32 backend to register a watch on the working tree root directory (recursively). Also watch the <gitdir> if it is not inside the working tree. And to collect path change notifications into batches and publish.

Quoted 9 lines
Quoted 3 lines
---
  compat/fsmonitor/fsmonitor-fs-listen-win32.c | 530 +++++++++++++++++++

<bikeshed mode> Spying on the early history of this (looking for the Linux backend) I saw that at some point we had just compat/fsmonitor/linux.c, and presumably some of compat/fsmonitor/{windows,win32,macos,darwin}.c.
At some point those filenames became much much longer.

Once upon a time having "foo/bar/win32.c" and "abc/def/win32.c" would cause confusion in the debugger (I've long since forgotten which). Breaking at win32.c:30 was no longer unique.

Quoted 6 lines

Also, if the Makefile sends all .o's to the root directory or a unified OBJS directory rather than to the subdir containing the .c, then we have another issue during linking...

So, having been burned too many times, I prefer to make source filenames unique when possible.

A much shorter name like compat/fsmonitor/fsmon-win32.c would achieve that goal.

Quoted 9 lines
Quoted 7 lines

I've noticed you tend to prefer really long file and function names, e.g. your borrowed daemonize() became spawn_background_fsmonitor_daemon(), I think aiming for shorter filenames & function names helps, e.g. these long names widen diffstats, and many people who hack on the code stick religiously to 80 character width terminals.

I prefer self-documenting code.

Sure, I'm not saying daemonize() is an ideal name, just suggesting that you can both get uniqueness & self-documentation and not need to split to multiple lines in some common cases to stay within the "We try to keep to at most 80 characters per line" in CodingGuidelines in this series.

#207 lore raw

Re: [PATCH v3 24/34] t/perf/p7519: speed up test using "test-tool touch"

In reply to Junio C Hamano
Quoted 19 lines · Junio C Hamano
Quoted 4 lines · Jeff Hostetler

FWIW, the xargs is clustering the 10,000 files into ~4 command lines, so there is a little bit of Windows process overhead, but not that much.

Quoted 9 lines

seq 1 10000 | xargs wc -l | grep total

Quoted 7 lines

I'd really like to modify test_seq to use seq when it's available and fall back to the looping-in-shell when we need to for various platforms.
Maybe it'd even make sense to write a 'test-tool seq' and make test_seq use that just so we can rip out that super lame shell looping.

So what lame in this picture is not shell, or process overhead, but I/O performance.

Quoted 10 lines

I've seen some noises about Windows file creation performance raised as an issue when doing initial checkout followed by "git clone", and an idea floated to create a bunch of open file handles for writing in threads when checkout (really the caller that repeatedly calls entry.c:write_entry() by iterating the in-core index) starts, and write out the contents in parallel, as a workaround. When I heard it, I somehow thought it was meant as a not-so-funny joke, but from the sounds of it, the I/O performance may be so horrible to require such a hack to be usable there. Sigh...

Yes, there are some things here (that I believe to be I/O related) on Windows that I want to look at when I wrap up FSMonitor. And yes, some of them sound pretty stupid.

Jeff

#208 lore raw

Re: [PATCH v3 24/34] t/perf/p7519: speed up test using "test-tool touch"

In reply to Jeff Hostetler
Quoted 64 lines · Jeff Hostetler
Quoted 51 lines · Ævar Arnfjörð Bjarmason
Quoted 38 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Change p7519 to use a single "test-tool touch" command to update the mtime on a series of (thousands) files instead of invoking thousands of commands to update a single file.

This is primarily for Windows where process creation is so very slow and reduces the test run time by minutes.

---
  t/perf/p7519-fsmonitor.sh | 14 ++++++--------
  1 file changed, 6 insertions(+), 8 deletions(-)
t/perf/p7519-fsmonitor.sh +5 -4
index 5eb5044a103..f74e6014a0a 100755
@@ -119,10 +119,11 @@ test_expect_success "one time repo setup" '
  	fi &&
    	mkdir 1_file 10_files 100_files 1000_files 10000_files &&
-	for i in $(test_seq 1 10); do touch 10_files/$i; done &&
-	for i in $(test_seq 1 100); do touch 100_files/$i; done &&
-	for i in $(test_seq 1 1000); do touch 1000_files/$i; done &&
-	for i in $(test_seq 1 10000); do touch 10000_files/$i; done &&
+	test-tool touch sequence --pattern="10_files/%d" --start=1 --count=10 &&
+	test-tool touch sequence --pattern="100_files/%d" --start=1 --count=100 &&
+	test-tool touch sequence --pattern="1000_files/%d" --start=1 --count=1000 &&
+	test-tool touch sequence --pattern="10000_files/%d" --start=1 --count=10000 &&
+
  	git add 1_file 10_files 100_files 1000_files 10000_files &&
  	git commit -qm "Add files" &&
  @@ -200,15 +201,12 @@ test_fsmonitor_suite() {
  	# Update the mtimes on upto 100k files to make status think
  	# that they are dirty.  For simplicity, omit any files with
  	# LFs (i.e. anything that ls-files thinks it needs to dquote).
t/perf/p7519-fsmonitor.sh +0 -2
-	# Then fully backslash-quote the paths to capture any
-	# whitespace so that they pass thru xargs properly.

t/perf/p7519-fsmonitor.sh test_fsmonitor_suite() { · line 211

Quoted 10 lines
t/perf/p7519-fsmonitor.sh +1 -2
  	#
  	test_perf_w_drop_caches "status (dirty) ($DESC)" '
  		git ls-files | \
  			head -100000 | \
  			grep -v \" | \
-			sed '\''s/\(.\)/\\\1/g'\'' | \
-			xargs test-tool chmtime -300 &&
+			test-tool touch stdin &&
  		git status
  	'

Did you try to replace this with some variant of:

Quoted 5 lines
     test_seq 1 10000 | xargs touch

Which (depending on your xargs version) would invoke "touch" commands
with however many argv items it thinks you can handle.

a quick test on my Windows machine shows that

	test_seq 1 10000 | xargs touch

takes 3.1 seconds.

Quoted 7 lines

just a simple

	test_seq 1 10000 >/dev/null

take 0.2 seconds.

using my test-tool helper cuts that time in half.

There's what Elijah mentioned about test_seq, so maybe it's just that.

But what I was suggesting was using the xargs mode where it does N arguments at a time.

Does this work for you, and does it cause xargs to invoke "touch" with the relevant N number of arguments, and does it help with the performance?

    test_seq 1 10000 | xargs touch
    test_seq 1 10000 | xargs -n 10 touch
    test_seq 1 10000 | xargs -n 100 touch
    test_seq 1 10000 | xargs -n 1000 touch

etc.

Also I didn't notice this before, but the -300 part of "chmtime -300" was redundant before then? I.e. you're implicitly changing it to "=+0" instead with your "touch" helper, are you not?

#172 lore raw

Re: [PATCH v3 14/34] fsmonitor--daemon: implement 'start' command

In reply to Ævar Arnfjörð Bjarmason
Quoted 98 lines · Ævar Arnfjörð Bjarmason
Quoted 95 lines · Jeff Hostetler

My response here is in addition to Dscho's remarks on this topic. He makes excellent points that I'll just #include here. I do want to add my own $0.02 here.

Quoted 74 lines · Ævar Arnfjörð Bjarmason
Quoted 59 lines · Jeff Hostetler via GitGitGadget
builtin/fsmonitor--daemon.c +59 -0
+/*
+ * This is adapted from `wait_or_whine()`.  Watch the child process and
+ * let it get started and begin listening for requests on the socket
+ * before reporting our success.
+ */
+static int wait_for_background_startup(pid_t pid_child)
+{
+	int status;
+	pid_t pid_seen;
+	enum ipc_active_state s;
+	time_t time_limit, now;
+
+	time(&time_limit);
+	time_limit += fsmonitor__start_timeout_sec;
+
+	for (;;) {
+		pid_seen = waitpid(pid_child, &status, WNOHANG);
+
+		if (pid_seen == -1)
+			return error_errno(_("waitpid failed"));
+		else if (pid_seen == 0) {
+			/*
+			 * The child is still running (this should be
+			 * the normal case).  Try to connect to it on
+			 * the socket and see if it is ready for
+			 * business.
+			 *
+			 * If there is another daemon already running,
+			 * our child will fail to start (possibly
+			 * after a timeout on the lock), but we don't
+			 * care (who responds) if the socket is live.
+			 */
+			s = fsmonitor_ipc__get_state();
+			if (s == IPC_STATE__LISTENING)
+				return 0;
+
+			time(&now);
+			if (now > time_limit)
+				return error(_("fsmonitor--daemon not online yet"));
+		} else if (pid_seen == pid_child) {
+			/*
+			 * The new child daemon process shutdown while
+			 * it was starting up, so it is not listening
+			 * on the socket.
+			 *
+			 * Try to ping the socket in the odd chance
+			 * that another daemon started (or was already
+			 * running) while our child was starting.
+			 *
+			 * Again, we don't care who services the socket.
+			 */
+			s = fsmonitor_ipc__get_state();
+			if (s == IPC_STATE__LISTENING)
+				return 0;
+
+			/*
+			 * We don't care about the WEXITSTATUS() nor
+			 * any of the WIF*(status) values because
+			 * `cmd_fsmonitor__daemon()` does the `!!result`

builtin/fsmonitor--daemon.c static int try_to_run_foreground_daemon(void) · line 419

Quoted 10 lines
builtin/fsmonitor--daemon.c +10 -0
+			 * trick on all function return values.
+			 *
+			 * So it is sufficient to just report the
+			 * early shutdown as an error.
+			 */
+			return error(_("fsmonitor--daemon failed to start"));
+		} else
+			return error(_("waitpid is confused"));
+	}
+}

Ditto this. could we extend the wait_or_whine() function (or some extended version thereof) to do what you need with callbacks?

Quoted 9 lines

It seems the main difference is just being able to pass down a flag for
waitpid(), and the loop needing to check EINTR or not depending on whether WNOHANG is passed.
For e.g. the "We don't care about the WEXITSTATUS()" you'd get that behavior with an adjusted wait_or_whine(). Wouldn't it be better to report what exit status it exits with e.g. if the top-level process is signalled? We do so in trace2 for other things we spawn...

Again, I don't want to mix my usage here with the existing code and destabilize all existing callers. Here we are spinning to give the child a chance to *start* and confirm that it is in a listening state and ready for connections. We do not wait for the child to exit (unless it dies quickly without becoming ready).

Quoted 6 lines
Quoted 2 lines

We want to end our wait as soon as we confirm that the child is ready and return. All I really need from the system is waitpid().

Will this code behave correctly if the daemon we start is signalled per the WIFSIGNALED() cases the code this is derived handles, but this does not?

We're only waiting until the child gets started and is able to receive requests -- what happens to it after we have confirmed that it is ready is not our concern (after all, the parent is about to exit anyway and the child is going to continue on).

If waitpid() gives us a WIFSIGNALED (or any other WIF*() state) before we have spoken to it, we will return a "failed to start".

But again, that signal would have to arrive immediately after we spawned it and *before* we could talk to it. If the child is signaled after we confirmed it was ready, we don't care because the parent process will be gone.

(If the child is signaled or is killed (or crashes or whatever), the next Git command (like "status") that tries to talk to it will re-start it implicitly -- the git fsmonitor--daemon start command here is an explicit start.)

Quoted 7 lines

But sure, I just meant to point out that the flip side to "destabilize all existing callers" is reviewing new code that may be subtly buggy, and those subtle bugs (if any) would be smoked out if we were forced to extend run-command.c, i.e. to use whatever feature(s) this needs for all existing callers.

That would/could have a massive footprint. And I've already established that my usage here is sufficiently different from existing uses that the result would be a mess. IMHO.

Jeff

#209 lore raw

Re: [PATCH v3 24/34] t/perf/p7519: speed up test using "test-tool touch"

In reply to Ævar Arnfjörð Bjarmason
Quoted 80 lines · Ævar Arnfjörð Bjarmason
Quoted 64 lines · Jeff Hostetler
Quoted 51 lines · Ævar Arnfjörð Bjarmason
Quoted 38 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Change p7519 to use a single "test-tool touch" command to update the mtime on a series of (thousands) files instead of invoking thousands of commands to update a single file.

This is primarily for Windows where process creation is so very slow and reduces the test run time by minutes.

---
   t/perf/p7519-fsmonitor.sh | 14 ++++++--------
   1 file changed, 6 insertions(+), 8 deletions(-)
t/perf/p7519-fsmonitor.sh +5 -4
index 5eb5044a103..f74e6014a0a 100755
@@ -119,10 +119,11 @@ test_expect_success "one time repo setup" '
   	fi &&
     	mkdir 1_file 10_files 100_files 1000_files 10000_files &&
-	for i in $(test_seq 1 10); do touch 10_files/$i; done &&
-	for i in $(test_seq 1 100); do touch 100_files/$i; done &&
-	for i in $(test_seq 1 1000); do touch 1000_files/$i; done &&
-	for i in $(test_seq 1 10000); do touch 10000_files/$i; done &&
+	test-tool touch sequence --pattern="10_files/%d" --start=1 --count=10 &&
+	test-tool touch sequence --pattern="100_files/%d" --start=1 --count=100 &&
+	test-tool touch sequence --pattern="1000_files/%d" --start=1 --count=1000 &&
+	test-tool touch sequence --pattern="10000_files/%d" --start=1 --count=10000 &&
+
   	git add 1_file 10_files 100_files 1000_files 10000_files &&
   	git commit -qm "Add files" &&
   @@ -200,15 +201,12 @@ test_fsmonitor_suite() {
   	# Update the mtimes on upto 100k files to make status think
   	# that they are dirty.  For simplicity, omit any files with
   	# LFs (i.e. anything that ls-files thinks it needs to dquote).
t/perf/p7519-fsmonitor.sh +0 -2
-	# Then fully backslash-quote the paths to capture any
-	# whitespace so that they pass thru xargs properly.

t/perf/p7519-fsmonitor.sh test_fsmonitor_suite() { · line 211

Quoted 10 lines
t/perf/p7519-fsmonitor.sh +1 -2
   	#
   	test_perf_w_drop_caches "status (dirty) ($DESC)" '
   		git ls-files | \
   			head -100000 | \
   			grep -v \" | \
-			sed '\''s/\(.\)/\\\1/g'\'' | \
-			xargs test-tool chmtime -300 &&
+			test-tool touch stdin &&
   		git status
   	'

Did you try to replace this with some variant of:

Quoted 5 lines
      test_seq 1 10000 | xargs touch

Which (depending on your xargs version) would invoke "touch" commands
with however many argv items it thinks you can handle.

a quick test on my Windows machine shows that

	test_seq 1 10000 | xargs touch

takes 3.1 seconds.

Quoted 7 lines

just a simple

	test_seq 1 10000 >/dev/null

take 0.2 seconds.

using my test-tool helper cuts that time in half.

There's what Elijah mentioned about test_seq, so maybe it's just that.

But what I was suggesting was using the xargs mode where it does N arguments at a time.

Quoted 8 lines

Does this work for you, and does it cause xargs to invoke "touch" with the relevant N number of arguments, and does it help with the performance?

     test_seq 1 10000 | xargs touch
     test_seq 1 10000 | xargs -n 10 touch
     test_seq 1 10000 | xargs -n 100 touch
     test_seq 1 10000 | xargs -n 1000 touch

The GFW SDK version of xargs does have -n N and it does work as advertised. And it does slow down things considerably. Letting it do ~2500 per command in 4 commands took the 3.1 seconds listed above.

Add a -n 100 to it takes 5.7 seconds, so process creation overhead is a factor here.

Quoted 7 lines

etc.

Also I didn't notice this before, but the -300 part of "chmtime -300" was redundant before then? I.e. you're implicitly changing it to "=+0" instead with your "touch" helper, are you not?

Right. I'm changing it to the current time.

Jeff

#129 lore raw

Re: [PATCH v3 02/34] fsmonitor--daemon: man page

In reply to Ævar Arnfjörð Bjarmason

Hi Ævar,

Quoted 8 lines · Ævar Arnfjörð Bjarmason

[snip Ævar's suggestion to populate the manual page incrementally, interspersed with the commits that finalize implementing the respective functionality]

I suggested this because I think it's much easier to read patches that are larger because they incrementally update docs or tests along with code, than smaller ones that are e.g. "add docs" followed by incrementally modifying the code.

My experience is the exact opposite of yours: shorter patches are easier to read.

Quoted 1 line

That's because you can consider those atomically.

No, in a patch series you cannot consider any patch completely atomically. Just like you don't consider any paragraph in any well-written book out of context.

Quoted 4 lines

If earlier doc changes refer to later code changes you're left jumping back & forth and wondering if the code you're reading that doesn't match the docs yet is a bug, or if it's solved in some later change you're now needing to mentally keep track of.

You only keep jumping back and forth when reviewing _patches_. We try to do code review on this mailing list, which means that you have the code locally and review it in the correct context. When you do that, a design document is quite helpful. And the proposed manual page serves as such a design document.

Quoted 2 lines

Which is not some theoretical concern b.t.w., but exactly what I found myself doing when reading this series, hence the suggestion.

Part of the problem here seems to be that this patch series saw many reviewer suggestions that forced it to increase in length. That was probably not very helpful, after all.

The first iteration had 23 patches. Reviews forced it to grow to 28 patches in the second iteration. And that was still not enough, therefore the third iteration consisted of 34 patches. And if you had had your way, requiring Jeff to include a Linux backend in the same patch series, it would have to increase in size again, and not just by a little.

This all sounds like we're truly falling into the trap of ignoring the rule that the perfect is the enemy of the good.

I really would like to come back to a focused review that truly improves the patches at hand, and avoids conflating the review of the actual patch series with matters of personal taste (which is a recipe for disagreement). After all, we are interested in getting this feature out to users who need to work with very large worktrees, right? At least that's my goal here.

Ciao,
Johannes

#147 lore raw

Re: [PATCH v3 11/34] fsmonitor-fs-listen-win32: stub in backend for Windows

In reply to Ævar Arnfjörð Bjarmason

Hi Ævar,

Quoted 47 lines · Ævar Arnfjörð Bjarmason
Quoted 44 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Stub in empty backend for fsmonitor--daemon on Windows.

---
 Makefile                                     | 13 ++++++
 compat/fsmonitor/fsmonitor-fs-listen-win32.c | 21 +++++++++
 compat/fsmonitor/fsmonitor-fs-listen.h       | 49 ++++++++++++++++++++
 config.mak.uname                             |  2 +
 contrib/buildsystems/CMakeLists.txt          |  5 ++
 5 files changed, 90 insertions(+)
 create mode 100644 compat/fsmonitor/fsmonitor-fs-listen-win32.c
 create mode 100644 compat/fsmonitor/fsmonitor-fs-listen.h
Makefile +10 -0
index c45caacf2c3..a2a6e1f20f6 100644
@@ -467,6 +467,11 @@ all::
 # directory, and the JSON compilation database 'compile_commands.json' will be
 # created at the root of the repository.
 #
+# If your platform supports a built-in fsmonitor backend, set
+# FSMONITOR_DAEMON_BACKEND to the "<name>" of the corresponding
+# `compat/fsmonitor/fsmonitor-fs-listen-<name>.c` that implements the
+# `fsmonitor_fs_listen__*()` routines.
+#
 # Define DEVELOPER to enable more compiler warnings. Compiler version
 # and family are auto detected, but could be overridden by defining
 # COMPILER_FEATURES (see config.mak.dev). You can still set
@@ -1929,6 +1934,11 @@ ifdef NEED_ACCESS_ROOT_HANDLER
 	COMPAT_OBJS += compat/access.o
 endif
 
+ifdef FSMONITOR_DAEMON_BACKEND
+	COMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND
+	COMPAT_OBJS += compat/fsmonitor/fsmonitor-fs-listen-$(FSMONITOR_DAEMON_BACKEND).o
+endif
+
 ifeq ($(TCLTK_PATH),)
 NO_TCLTK = NoThanks
 endif
@@ -2793,6 +2803,9 @@ GIT-BUILD-OPTIONS: FORCE
Quoted 8 lines

Makefile GIT-BUILD-OPTIONS: FORCE · line 2808

Quoted 6 lines
Makefile +3 -0
 	@echo PAGER_ENV=\''$(subst ','\'',$(subst ','\'',$(PAGER_ENV)))'\' >>$@+
 	@echo DC_SHA1=\''$(subst ','\'',$(subst ','\'',$(DC_SHA1)))'\' >>$@+
 	@echo X=\'$(X)\' >>$@+
+ifdef FSMONITOR_DAEMON_BACKEND
+	@echo FSMONITOR_DAEMON_BACKEND=\''$(subst ','\'',$(subst ','\'',$(FSMONITOR_DAEMON_BACKEND)))'\' >>$@+
+endif

Why put this in an ifdef?

Why not? What benefit does this question bring to improving this patch series?

Ciao,
Dscho

#158 lore raw

Re: [PATCH v3 12/34] fsmonitor-fs-listen-macos: stub in backend for MacOS

In reply to Ævar Arnfjörð Bjarmason

Hi Ævar,

Quoted 55 lines · Ævar Arnfjörð Bjarmason
Quoted 44 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Stub in empty implementation of fsmonitor--daemon
backend for MacOS.

---
 compat/fsmonitor/fsmonitor-fs-listen-macos.c | 20 ++++++++++++++++++++
 config.mak.uname                             |  2 ++
 contrib/buildsystems/CMakeLists.txt          |  3 +++
 3 files changed, 25 insertions(+)
 create mode 100644 compat/fsmonitor/fsmonitor-fs-listen-macos.c
compat/fsmonitor/fsmonitor-fs-listen-macos.c new file +20 -0
new file mode 100644
index 00000000000..b91058d1c4f
@@ -0,0 +1,20 @@
+#include "cache.h"
+#include "fsmonitor.h"
+#include "fsmonitor-fs-listen.h"
+
+int fsmonitor_fs_listen__ctor(struct fsmonitor_daemon_state *state)
+{
+	return -1;
+}
+
+void fsmonitor_fs_listen__dtor(struct fsmonitor_daemon_state *state)
+{
+}
+
+void fsmonitor_fs_listen__stop_async(struct fsmonitor_daemon_state *state)
+{
+}
+
+void fsmonitor_fs_listen__loop(struct fsmonitor_daemon_state *state)
+{
+}
config.mak.uname +0 -0
index fcd88b60b14..394355463e1 100644

config.mak.uname ifeq ($(uname_S),Darwin) · line 150

Quoted 4 lines
config.mak.uname +1 -0
@@ -147,6 +147,8 @@ ifeq ($(uname_S),Darwin)
 			MSGFMT = /usr/local/opt/gettext/bin/msgfmt
 		endif
 	endif
+	FSMONITOR_DAEMON_BACKEND = macos

A rather trivial point, but can't we pick one of "macos" or "darwin" (I'd think going with the existing uname is better) and name the file after the uname (or lower-case thereof)?

Quoted 3 lines

Makes these make rules more consistent too, we could just set this to "YesPlease" here, and then lower case the uname for the file compilation/include.

So you suggest that we name the new stuff after an uname that reflects a name that is no longer relevant? I haven't seen a real Darwin system in quite a long time, have you?

I don't find such a suggestion constructive, I have to admit.

Ciao,
Johannes

#186 lore raw

Re: [PATCH v3 19/34] fsmonitor-fs-listen-win32: implement FSMonitor backend on Windows

In reply to Ævar Arnfjörð Bjarmason

Hi Ævar,

Quoted 45 lines · Ævar Arnfjörð Bjarmason
Quoted 24 lines · Jeff Hostetler
Quoted 9 lines · Ævar Arnfjörð Bjarmason
Quoted 7 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Teach the win32 backend to register a watch on the working tree root directory (recursively). Also watch the <gitdir> if it is not inside the working tree. And to collect path change notifications into batches and publish.

Quoted 9 lines
Quoted 3 lines
---
  compat/fsmonitor/fsmonitor-fs-listen-win32.c | 530 +++++++++++++++++++

<bikeshed mode> Spying on the early history of this (looking for the Linux backend) I saw that at some point we had just compat/fsmonitor/linux.c, and presumably some of compat/fsmonitor/{windows,win32,macos,darwin}.c.
At some point those filenames became much much longer.

Once upon a time having "foo/bar/win32.c" and "abc/def/win32.c" would cause confusion in the debugger (I've long since forgotten which). Breaking at win32.c:30 was no longer unique.

Quoted 6 lines

Also, if the Makefile sends all .o's to the root directory or a unified OBJS directory rather than to the subdir containing the .c, then we have another issue during linking...

So, having been burned too many times, I prefer to make source filenames unique when possible.

A much shorter name like compat/fsmonitor/fsmon-win32.c would achieve that goal.

Quoted 8 lines
Quoted 7 lines

I've noticed you tend to prefer really long file and function names, e.g. your borrowed daemonize() became spawn_background_fsmonitor_daemon(), I think aiming for shorter filenames & function names helps, e.g. these long names widen diffstats, and many people who hack on the code stick religiously to 80 character width terminals.

Quoted 7 lines
Quoted 1 line

I prefer self-documenting code.

Sure, I'm not saying daemonize() is an ideal name, just suggesting that you can both get uniqueness & self-documentation and not need to split to multiple lines in some common cases to stay within the "We try to keep to at most 80 characters per line" in CodingGuidelines in this series.

While you are entitled to have your taste, I have to point out that Jeff is just as entitled to their taste, and I don't think that you can claim that yours is better.

So I wonder what the intended outcome of this review is? To make the patch better? Or to pit taste against taste?

Ciao,
Johannes

#187 lore raw

Re: [PATCH v3 19/34] fsmonitor-fs-listen-win32: implement FSMonitor backend on Windows

Quoted 57 lines · Johannes Schindelin

Hi Ævar,

Quoted 45 lines · Ævar Arnfjörð Bjarmason
Quoted 24 lines · Jeff Hostetler
Quoted 9 lines · Ævar Arnfjörð Bjarmason
Quoted 7 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Teach the win32 backend to register a watch on the working tree root directory (recursively). Also watch the <gitdir> if it is not inside the working tree. And to collect path change notifications into batches and publish.

Quoted 9 lines
Quoted 3 lines
---
  compat/fsmonitor/fsmonitor-fs-listen-win32.c | 530 +++++++++++++++++++

<bikeshed mode> Spying on the early history of this (looking for the Linux backend) I saw that at some point we had just compat/fsmonitor/linux.c, and presumably some of compat/fsmonitor/{windows,win32,macos,darwin}.c.
At some point those filenames became much much longer.

Once upon a time having "foo/bar/win32.c" and "abc/def/win32.c" would cause confusion in the debugger (I've long since forgotten which). Breaking at win32.c:30 was no longer unique.

Quoted 6 lines

Also, if the Makefile sends all .o's to the root directory or a unified OBJS directory rather than to the subdir containing the .c, then we have another issue during linking...

So, having been burned too many times, I prefer to make source filenames unique when possible.

A much shorter name like compat/fsmonitor/fsmon-win32.c would achieve that goal.

Quoted 8 lines
Quoted 7 lines

I've noticed you tend to prefer really long file and function names, e.g. your borrowed daemonize() became spawn_background_fsmonitor_daemon(), I think aiming for shorter filenames & function names helps, e.g. these long names widen diffstats, and many people who hack on the code stick religiously to 80 character width terminals.

Quoted 7 lines
Quoted 1 line

I prefer self-documenting code.

Sure, I'm not saying daemonize() is an ideal name, just suggesting that you can both get uniqueness & self-documentation and not need to split to multiple lines in some common cases to stay within the "We try to keep to at most 80 characters per line" in CodingGuidelines in this series.

Quoted 6 lines

While you are entitled to have your taste, I have to point out that Jeff is just as entitled to their taste, and I don't think that you can claim that yours is better.

So I wonder what the intended outcome of this review is? To make the patch better? Or to pit taste against taste?

Neither, to address a misunderstanding.

Sure, if a reviewer points out "maybe change X to Y" and the reply is "I like X better than Y", fair enough.

My reading of Jeff H.'s upthread was that he'd misunderstood my suggesting of that Y for a Z.

I.e. that shortening a name like fsmonitor-fs-listen-win32.c (X) necessarily had to mean that we'd have a win32.c (Z), negatively impacting some debugging workflows, as opposed to just a shorter-but-unique name like fsmon-win32.c (Y).

Ditto for daemonize() (X/Z) and spawn_background_fsmonitor_daemon() (X).

I'm certain that with this reply we're thoroughly into the "respectfully disagree" territory as opposed to having a misunderstanding.

I also take and agree your implied point that there's no point in having a yes/no/yes/no/yes argument on-list, and I did not mean to engage in such a thing, only to clear up the misunderstanding, if any.

I'll only say that I don't think that something like long variable/file etc. names is *just* a matter of taste, seeing as we have a fairly strict "keep to at most 80 characters per line" as the 2nd item in the C coding style (after "use tabs, not spaces").

That matter of taste for one developer objectively makes it harder to stay within the bounds of the coding style for furute maintenance.

We do have active contributors that I understand actually use terminals of that size to work on this project (CC'd, but maybe I misrecall that for one/both). I'm not one of those people, but I do find that maintaining code with needlessly long identifiers in this codebase is painful.

E.g. in a patch I just submitted I've been working on similarly long identifiers in the refs code[1], and with say a long variable/type name and using a long-named function you get to the point of needing to place each individual argument of the function on its own line, or near enough to that.

1. https://lore.kernel.org/git/[email protected]/

#159 lore raw

Re: [PATCH v3 12/34] fsmonitor-fs-listen-macos: stub in backend for MacOS

In reply to Johannes Schindelin
Quoted 59 lines · Johannes Schindelin

Hi Ævar,

Quoted 55 lines · Ævar Arnfjörð Bjarmason
Quoted 44 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Stub in empty implementation of fsmonitor--daemon
backend for MacOS.

---
 compat/fsmonitor/fsmonitor-fs-listen-macos.c | 20 ++++++++++++++++++++
 config.mak.uname                             |  2 ++
 contrib/buildsystems/CMakeLists.txt          |  3 +++
 3 files changed, 25 insertions(+)
 create mode 100644 compat/fsmonitor/fsmonitor-fs-listen-macos.c
compat/fsmonitor/fsmonitor-fs-listen-macos.c new file +20 -0
new file mode 100644
index 00000000000..b91058d1c4f
@@ -0,0 +1,20 @@
+#include "cache.h"
+#include "fsmonitor.h"
+#include "fsmonitor-fs-listen.h"
+
+int fsmonitor_fs_listen__ctor(struct fsmonitor_daemon_state *state)
+{
+	return -1;
+}
+
+void fsmonitor_fs_listen__dtor(struct fsmonitor_daemon_state *state)
+{
+}
+
+void fsmonitor_fs_listen__stop_async(struct fsmonitor_daemon_state *state)
+{
+}
+
+void fsmonitor_fs_listen__loop(struct fsmonitor_daemon_state *state)
+{
+}
config.mak.uname +0 -0
index fcd88b60b14..394355463e1 100644

config.mak.uname ifeq ($(uname_S),Darwin) · line 150

Quoted 4 lines
config.mak.uname +1 -0
@@ -147,6 +147,8 @@ ifeq ($(uname_S),Darwin)
 			MSGFMT = /usr/local/opt/gettext/bin/msgfmt
 		endif
 	endif
+	FSMONITOR_DAEMON_BACKEND = macos

A rather trivial point, but can't we pick one of "macos" or "darwin" (I'd think going with the existing uname is better) and name the file after the uname (or lower-case thereof)?

Quoted 7 lines
Quoted 3 lines

Makes these make rules more consistent too, we could just set this to "YesPlease" here, and then lower case the uname for the file compilation/include.

So you suggest that we name the new stuff after an uname that reflects a name that is no longer relevant? I haven't seen a real Darwin system in quite a long time, have you?

It's not current? On an Mac Mini M1 which got released this year:

% uname -s
Darwin

We then have the same in config.mak.uname, it seemed the most obvious and consistent to carry that through to file inclusion.

#190 lore raw

Re: [PATCH v3 19/34] fsmonitor-fs-listen-win32: implement FSMonitor backend on Windows

In reply to Johannes Schindelin
Quoted 11 lines · Johannes Schindelin
Quoted 9 lines · Ævar Arnfjörð Bjarmason
Quoted 1 line · Jeff Hostetler

I prefer self-documenting code.

Sure, I'm not saying daemonize() is an ideal name, just suggesting that you can both get uniqueness & self-documentation and not need to split to multiple lines in some common cases to stay within the "We try to keep to at most 80 characters per line" in CodingGuidelines in this series.

Quoted 6 lines

While you are entitled to have your taste, I have to point out that Jeff is just as entitled to their taste, and I don't think that you can claim that yours is better.

So I wonder what the intended outcome of this review is? To make the patch better? Or to pit taste against taste?

Unless you read minds you can't possibly know what the taste of other people will be.

So you put forward what *you* think is better, and then find out if others agree with your taste or not. If it turns out you are the only one that thinks it's better, so be it.

For what it's worth, I agree with Ævar that daemonize() is better and I find your statement "I prefer self-documenting code" a) not an argument,
b) not a valid argument if we fill in the dots, and c) passively aggressive.

Each one of us can only do one thing: express our opinion. What else can we do?

Reviewers should not be chastised for expressing their opinion.

Signature · 1 line
Felipe Contreras
#148 lore raw

Re: [PATCH v3 11/34] fsmonitor-fs-listen-win32: stub in backend for Windows

In reply to Johannes Schindelin
Quoted 60 lines · Johannes Schindelin

Hi Ævar,

Quoted 47 lines · Ævar Arnfjörð Bjarmason
Quoted 44 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Stub in empty backend for fsmonitor--daemon on Windows.

---
 Makefile                                     | 13 ++++++
 compat/fsmonitor/fsmonitor-fs-listen-win32.c | 21 +++++++++
 compat/fsmonitor/fsmonitor-fs-listen.h       | 49 ++++++++++++++++++++
 config.mak.uname                             |  2 +
 contrib/buildsystems/CMakeLists.txt          |  5 ++
 5 files changed, 90 insertions(+)
 create mode 100644 compat/fsmonitor/fsmonitor-fs-listen-win32.c
 create mode 100644 compat/fsmonitor/fsmonitor-fs-listen.h
Makefile +10 -0
index c45caacf2c3..a2a6e1f20f6 100644
@@ -467,6 +467,11 @@ all::
 # directory, and the JSON compilation database 'compile_commands.json' will be
 # created at the root of the repository.
 #
+# If your platform supports a built-in fsmonitor backend, set
+# FSMONITOR_DAEMON_BACKEND to the "<name>" of the corresponding
+# `compat/fsmonitor/fsmonitor-fs-listen-<name>.c` that implements the
+# `fsmonitor_fs_listen__*()` routines.
+#
 # Define DEVELOPER to enable more compiler warnings. Compiler version
 # and family are auto detected, but could be overridden by defining
 # COMPILER_FEATURES (see config.mak.dev). You can still set
@@ -1929,6 +1934,11 @@ ifdef NEED_ACCESS_ROOT_HANDLER
 	COMPAT_OBJS += compat/access.o
 endif
 
+ifdef FSMONITOR_DAEMON_BACKEND
+	COMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND
+	COMPAT_OBJS += compat/fsmonitor/fsmonitor-fs-listen-$(FSMONITOR_DAEMON_BACKEND).o
+endif
+
 ifeq ($(TCLTK_PATH),)
 NO_TCLTK = NoThanks
 endif
@@ -2793,6 +2803,9 @@ GIT-BUILD-OPTIONS: FORCE
Quoted 8 lines

Makefile GIT-BUILD-OPTIONS: FORCE · line 2808

Quoted 6 lines
Makefile +3 -0
 	@echo PAGER_ENV=\''$(subst ','\'',$(subst ','\'',$(PAGER_ENV)))'\' >>$@+
 	@echo DC_SHA1=\''$(subst ','\'',$(subst ','\'',$(DC_SHA1)))'\' >>$@+
 	@echo X=\'$(X)\' >>$@+
+ifdef FSMONITOR_DAEMON_BACKEND
+	@echo FSMONITOR_DAEMON_BACKEND=\''$(subst ','\'',$(subst ','\'',$(FSMONITOR_DAEMON_BACKEND)))'\' >>$@+
+endif

Why put this in an ifdef?

Quoted 2 lines

Why not? What benefit does this question bring to improving this patch series?

I think that when adding code to the Makefile it makes sense to follow the prevailing pattern, unless there's a good reason to do otherwise, e.g. on my build:

	$ grep "''" GIT-BUILD-OPTIONS 
	NO_CURL=''
	NO_EXPAT=''
	NO_PERL=''
	NO_PTHREADS=''
	NO_PYTHON=''
	NO_UNIX_SOCKETS=''
	X=''

Why does the FSMONITOR_DAEMON_BACKEND option require a nonexistent line as opposed to an empty one?

#153 lore raw

Re: [PATCH v3 11/34] fsmonitor-fs-listen-win32: stub in backend for Windows

In reply to Johannes Schindelin
Quoted 13 lines · Johannes Schindelin
Quoted 11 lines · Ævar Arnfjörð Bjarmason

Makefile GIT-BUILD-OPTIONS: FORCE · line 2808

Quoted 7 lines · Jeff Hostetler via GitGitGadget
Makefile +3 -0
@@ -2793,6 +2803,9 @@ GIT-BUILD-OPTIONS: FORCE
 	@echo PAGER_ENV=\''$(subst ','\'',$(subst ','\'',$(PAGER_ENV)))'\' >>$@+
 	@echo DC_SHA1=\''$(subst ','\'',$(subst ','\'',$(DC_SHA1)))'\' >>$@+
 	@echo X=\'$(X)\' >>$@+
+ifdef FSMONITOR_DAEMON_BACKEND
+	@echo FSMONITOR_DAEMON_BACKEND=\''$(subst ','\'',$(subst ','\'',$(FSMONITOR_DAEMON_BACKEND)))'\' >>$@+
+endif

Why put this in an ifdef?

Quoted 2 lines

Why not? What benefit does this question bring to improving this patch series?

This is a common debate tactic known as "shifting the burden of proof".

Ævar does not need to prove that your patch is undesirable, *you* have to prove that it is desirable.

You have the burden of proof, so you should answer the question.

https://www.logicallyfallacious.com/logicalfallacies/Shifting-of-the-Burden-of-Proof

Signature · 1 line
Felipe Contreras
#130 lore raw

Re: [PATCH v3 02/34] fsmonitor--daemon: man page

In reply to Johannes Schindelin
Quoted 6 lines · Johannes Schindelin
Quoted 4 lines · Ævar Arnfjörð Bjarmason

[snip Ævar's suggestion to populate the manual page incrementally, interspersed with the commits that finalize implementing the respective functionality]

Quoted 7 lines
Quoted 4 lines

I suggested this because I think it's much easier to read patches that are larger because they incrementally update docs or tests along with code, than smaller ones that are e.g. "add docs" followed by incrementally modifying the code.

My experience is the exact opposite of yours: shorter patches are easier to read.

It depends.

Quoted 5 lines
Quoted 1 line

That's because you can consider those atomically.

No, in a patch series you cannot consider any patch completely atomically. Just like you don't consider any paragraph in any well-written book out of context.

But you do not put every sentence in a paragraph.

Sometimes a paragraph can contain a single sentence, or a single word even. But other times to properly read what is being tried to say you need a pretty big paragraph.

Quoted 2 lines

This all sounds like we're truly falling into the trap of ignoring the rule that the perfect is the enemy of the good.

Have you established that this is good enough?

Signature · 1 line
Felipe Contreras
#149 lore raw

Re: [PATCH v3 11/34] fsmonitor-fs-listen-win32: stub in backend for Windows

In reply to Ævar Arnfjörð Bjarmason
Quoted 28 lines · Ævar Arnfjörð Bjarmason
Quoted 10 lines
Quoted 10 lines
Quoted 8 lines
Makefile +5 -0
+ifdef FSMONITOR_DAEMON_BACKEND
+	COMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND
+	COMPAT_OBJS += compat/fsmonitor/fsmonitor-fs-listen-$(FSMONITOR_DAEMON_BACKEND).o
+endif
+
 ifeq ($(TCLTK_PATH),)
 NO_TCLTK = NoThanks
 endif

...

Quoted 4 lines
Quoted 1 line

Why put this in an ifdef?

Why not? What benefit does this question bring to improving this patch series?

I think that when adding code to the Makefile it makes sense to follow the prevailing pattern, unless there's a good reason to do otherwise, e.g. on my build:

	$ grep "''" GIT-BUILD-OPTIONS 
	NO_CURL=''
	NO_EXPAT=''
	NO_PERL=''
	NO_PTHREADS=''
	NO_PYTHON=''
	NO_UNIX_SOCKETS=''
	X=''
Quoted 2 lines

Why does the FSMONITOR_DAEMON_BACKEND option require a nonexistent line as opposed to an empty one?

I do not quite get the question.

#!/bin/sh
cat >make.file <<\EOF
all::
ifeq ($(FSMONITOR_DAEMON_BACKEND),)
echo it is empty
endif
ifdef FSMONITOR_DAEMON_BACKEND
echo it is undefined
endif
EOF

echo "unset???"
make -f make.file

echo "set to empty???"
make -f make.file FSMONITOR_DAEMON_BACKEND=

These two make invocations will give us the same result, showing that "is it set to empty" and "is it unset" are the same.

#150 lore raw

Re: [PATCH v3 11/34] fsmonitor-fs-listen-win32: stub in backend for Windows

Quoted 7 lines · Junio C Hamano

#!/bin/sh
cat >make.file <<\EOF
all::
ifeq ($(FSMONITOR_DAEMON_BACKEND),)
echo it is empty
endif
ifdef FSMONITOR_DAEMON_BACKEND

An obvious typo. This must be "ifndef", of course.

Quoted 12 lines

echo it is undefined
endif
EOF

echo "unset???"
make -f make.file

echo "set to empty???"
make -f make.file FSMONITOR_DAEMON_BACKEND=

These two make invocations will give us the same result, showing that "is it set to empty" and "is it unset" are the same.

#188 lore raw

Re: [PATCH v3 19/34] fsmonitor-fs-listen-win32: implement FSMonitor backend on Windows

In reply to Ævar Arnfjörð Bjarmason
Quoted 42 lines · Ævar Arnfjörð Bjarmason
Quoted 41 lines · Johannes Schindelin

Hi Ævar,

Quoted 37 lines · Ævar Arnfjörð Bjarmason
Quoted 24 lines · Jeff Hostetler
Quoted 9 lines · Ævar Arnfjörð Bjarmason
Quoted 7 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Teach the win32 backend to register a watch on the working tree root directory (recursively). Also watch the <gitdir> if it is not inside the working tree. And to collect path change notifications into batches and publish.

Quoted 9 lines
Quoted 3 lines
---
  compat/fsmonitor/fsmonitor-fs-listen-win32.c | 530 +++++++++++++++++++

<bikeshed mode> Spying on the early history of this (looking for the Linux backend) I saw that at some point we had just compat/fsmonitor/linux.c, and presumably some of compat/fsmonitor/{windows,win32,macos,darwin}.c.
At some point those filenames became much much longer.

Once upon a time having "foo/bar/win32.c" and "abc/def/win32.c" would cause confusion in the debugger (I've long since forgotten which). Breaking at win32.c:30 was no longer unique.

Quoted 6 lines

Also, if the Makefile sends all .o's to the root directory or a unified OBJS directory rather than to the subdir containing the .c, then we have another issue during linking...

So, having been burned too many times, I prefer to make source filenames unique when possible.

A much shorter name like compat/fsmonitor/fsmon-win32.c would achieve that goal.

Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines

I've noticed you tend to prefer really long file and function names, e.g. your borrowed daemonize() became spawn_background_fsmonitor_daemon(), I think aiming for shorter filenames & function names helps, e.g. these long names widen diffstats, and many people who hack on the code stick religiously to 80 character width terminals.

At least "daemon"/"daemonize" already implies "background"; so even if we have the extra function, "spawn_fsmon_daemon()" would be enough info.

Quoted 22 lines
Quoted 10 lines
Quoted 9 lines
Quoted 3 lines
Quoted 1 line

I prefer self-documenting code.

Sure, I'm not saying daemonize() is an ideal name, just suggesting that you can both get uniqueness & self-documentation and not need to split to multiple lines in some common cases to stay within the "We try to keep to at most 80 characters per line" in CodingGuidelines in this series.

Quoted 6 lines

While you are entitled to have your taste, I have to point out that Jeff is just as entitled to their taste, and I don't think that you can claim that yours is better.

So I wonder what the intended outcome of this review is? To make the patch better? Or to pit taste against taste?

Neither, to address a misunderstanding.

Sure, if a reviewer points out "maybe change X to Y" and the reply is "I like X better than Y", fair enough.

Quoted 7 lines

My reading of Jeff H.'s upthread was that he'd misunderstood my suggesting of that Y for a Z.

I.e. that shortening a name like fsmonitor-fs-listen-win32.c (X) necessarily had to mean that we'd have a win32.c (Z), negatively impacting some debugging workflows, as opposed to just a shorter-but-unique name like fsmon-win32.c (Y).

Short-as-possible-while-being-meaningful is a pretty important usability thing git. There's a good reason git supports OID prefix abbreviations, after all.

Not my area of expertise, but AFAIK git's rename detection is affected by basename; and I've encountered debugger confusion with non-unique basenames while debugging other codebases.

My brain works like a naive "strcmp"/"memcmp": long common prefixes slows down my ability to differentiate filenames.

Having lots of common terms/prefixes on the screen works like camoflage to me and slows down my ability to process things. I suppose my eyes and cognitive abilities are below average; and even worse due to the pandemic numbing my brain.

Quoted 1 line

Ditto for daemonize() (X/Z) and spawn_background_fsmonitor_daemon() (X).

(what I said above)

Quoted 12 lines

I'm certain that with this reply we're thoroughly into the "respectfully disagree" territory as opposed to having a misunderstanding.

I also take and agree your implied point that there's no point in having a yes/no/yes/no/yes argument on-list, and I did not mean to engage in such a thing, only to clear up the misunderstanding, if any.

I'll only say that I don't think that something like long variable/file etc. names is *just* a matter of taste, seeing as we have a fairly strict "keep to at most 80 characters per line" as the 2nd item in the C coding style (after "use tabs, not spaces").

Quoted 8 lines

That matter of taste for one developer objectively makes it harder to stay within the bounds of the coding style for furute maintenance.

We do have active contributors that I understand actually use terminals of that size to work on this project (CC'd, but maybe I misrecall that for one/both). I'm not one of those people, but I do find that maintaining code with needlessly long identifiers in this codebase is painful.

Thanks for Cc-ing me.

Yes, I'm one of those developers. Accessibility matters to me:
my eyesight certainly isn't getting better with age (nor do I expect anyone elses'). I need giant fonts to reduce eye and neck strain.

Fwiw, newspaper publishers figured out line width decades/centuries ago and wrap lines despite having large sheets to work on.

I mostly work over mosh or ssh to reduce noise and heat locally. There's no bandwidth for VNC or similar, and graphical stuff tends to be unstable UI-wise anyways so I stick to the terminal.

Taste does have much to do with it: I favor stable, reliable tools (e.g. POSIX, Perl5, git) that works well on both old and new hardware. I avoid mainstream "desktop" software since they tend to have unstable UIs which break users' workflows while requiring more powerful HW.

Complex graphics drivers tend to get unreliable, too, especially when one is stuck with old HW that gets limited support from vendors. It's also difficult to fix complex drivers as a hobbyist given the one-off HW/vendor-specific knowledge required.

So we shouldn't expect a developer with old HW can have more than a standard text terminal. This is an accessibility problem for developers lacking in finances.

This is also a problem for developers wishing to backdoors+bugs found in modern systems (IntelME, AMD-PSP, endless stream of CPU bugs).

Back to health-related accessibility; I've also had joint problems for many years so shorter identifiers helps reduce typing I need to do. I mostly had that under control pre-pandemic, but it's been a huge struggle to find adequate replacements for activities I used to rely on to manage the pain.

#151 lore raw

Re: [PATCH v3 11/34] fsmonitor-fs-listen-win32: stub in backend for Windows

In reply to Junio C Hamano
Quoted 46 lines · Junio C Hamano
Quoted 28 lines · Ævar Arnfjörð Bjarmason
Quoted 10 lines
Quoted 10 lines
Quoted 8 lines
Makefile +5 -0
+ifdef FSMONITOR_DAEMON_BACKEND
+	COMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND
+	COMPAT_OBJS += compat/fsmonitor/fsmonitor-fs-listen-$(FSMONITOR_DAEMON_BACKEND).o
+endif
+
 ifeq ($(TCLTK_PATH),)
 NO_TCLTK = NoThanks
 endif

...

Quoted 4 lines
Quoted 1 line

Why put this in an ifdef?

Why not? What benefit does this question bring to improving this patch series?

I think that when adding code to the Makefile it makes sense to follow the prevailing pattern, unless there's a good reason to do otherwise, e.g. on my build:

	$ grep "''" GIT-BUILD-OPTIONS 
	NO_CURL=''
	NO_EXPAT=''
	NO_PERL=''
	NO_PTHREADS=''
	NO_PYTHON=''
	NO_UNIX_SOCKETS=''
	X=''
Quoted 2 lines

Why does the FSMONITOR_DAEMON_BACKEND option require a nonexistent line as opposed to an empty one?

I do not quite get the question.

#!/bin/sh
cat >make.file <<\EOF
all::
ifeq ($(FSMONITOR_DAEMON_BACKEND),)
echo it is empty
endif
ifdef FSMONITOR_DAEMON_BACKEND
echo it is undefined
endif
EOF

Quoted 8 lines

echo "unset???"
make -f make.file

echo "set to empty???"
make -f make.file FSMONITOR_DAEMON_BACKEND=

These two make invocations will give us the same result, showing that "is it set to empty" and "is it unset" are the same.

Indeed, which is why I'm pointing out that wrapping it in an ifdef is pointless, which is why we don't do it for the other ones.

We do have a bunch of ifdef'd things there for perf etc., I'm not sure if it matters or not for those.

#154 lore raw

Re: [PATCH v3 11/34] fsmonitor-fs-listen-win32: stub in backend for Windows

In reply to Ævar Arnfjörð Bjarmason
Quoted 61 lines · Ævar Arnfjörð Bjarmason
Quoted 44 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Stub in empty backend for fsmonitor--daemon on Windows.

---
  Makefile                                     | 13 ++++++
  compat/fsmonitor/fsmonitor-fs-listen-win32.c | 21 +++++++++
  compat/fsmonitor/fsmonitor-fs-listen.h       | 49 ++++++++++++++++++++
  config.mak.uname                             |  2 +
  contrib/buildsystems/CMakeLists.txt          |  5 ++
  5 files changed, 90 insertions(+)

create mode 100644 compat/fsmonitor/fsmonitor-fs-listen-win32.c create mode 100644 compat/fsmonitor/fsmonitor-fs-listen.h

Makefile +10 -0
index c45caacf2c3..a2a6e1f20f6 100644
@@ -467,6 +467,11 @@ all::
  # directory, and the JSON compilation database 'compile_commands.json' will be
  # created at the root of the repository.
  #
+# If your platform supports a built-in fsmonitor backend, set
+# FSMONITOR_DAEMON_BACKEND to the "<name>" of the corresponding
+# `compat/fsmonitor/fsmonitor-fs-listen-<name>.c` that implements the
+# `fsmonitor_fs_listen__*()` routines.
+#
  # Define DEVELOPER to enable more compiler warnings. Compiler version
  # and family are auto detected, but could be overridden by defining
  # COMPILER_FEATURES (see config.mak.dev). You can still set
@@ -1929,6 +1934,11 @@ ifdef NEED_ACCESS_ROOT_HANDLER
  	COMPAT_OBJS += compat/access.o
  endif
 
+ifdef FSMONITOR_DAEMON_BACKEND
+	COMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND
+	COMPAT_OBJS += compat/fsmonitor/fsmonitor-fs-listen-$(FSMONITOR_DAEMON_BACKEND).o
+endif
+
  ifeq ($(TCLTK_PATH),)
  NO_TCLTK = NoThanks
  endif

Makefile GIT-BUILD-OPTIONS: FORCE · line 2808

Quoted 6 lines
Makefile +3 -0
@@ -2793,6 +2803,9 @@ GIT-BUILD-OPTIONS: FORCE
  	@echo PAGER_ENV=\''$(subst ','\'',$(subst ','\'',$(PAGER_ENV)))'\' >>$@+
  	@echo DC_SHA1=\''$(subst ','\'',$(subst ','\'',$(DC_SHA1)))'\' >>$@+
  	@echo X=\'$(X)\' >>$@+
+ifdef FSMONITOR_DAEMON_BACKEND
+	@echo FSMONITOR_DAEMON_BACKEND=\''$(subst ','\'',$(subst ','\'',$(FSMONITOR_DAEMON_BACKEND)))'\' >>$@+
+endif

Why put this in an ifdef?

In 342e9ef2d9e (Introduce a performance testing framework, 2012-02-17) we started doing that for some perf/test options (which b.t.w., I don't really see the reason for, maybe it's some subtlety in how test-lib.sh picks those up).

Quoted 6 lines

But for all the other compile-time stuff we don't ifdef it, we just define it, and then you get an empty value or not.

This would AFAICT be the first build-time-for-the-C-program option we ifdef for writing a line to GIT-BUILD-OPTIONS.

(I'm going to respond here on the original question rather than on any of the follow up responses in an attempt at diffusing things a bit.)

I added the ifdef because I thought it to be the *most conservative* thing that I could do. The output of the generated file on unsupported platforms should be *identical* to what it was before my changes. I only alter the contents of the generated file on supported platforms.

Later, when the generated file is consumed, we don't need to worry about the effect (if any) on incremental compiles -- we will know that it won't be set -- just like it was not set in the original compile.

That change appears right before a 12 other ifdef'd symbols also being written to that generated file. Most are test and perf, but some are not. But my point is that the pattern is present already.

The original question also references a 9.5 year old commit which uses the same pattern as I've used here. It also muddies the water on why it was/wasn't needed back then. And hints at possible side-effects in some of our test scripts. So it is clear that the confusion/disagreements that we are having with the current patch and whether or not to ifdef are not new.

So, is there value in being explicit and having the ifdef ??

There are well defined Make rules (and Junio gave us a very elegant little script to demonstrate that), but the subtleties are there. Especially with our use generated files like GIT-BUILD-OPTIONS. We have a mailing list full of experts and yet this question received a lot more discussion than I thought possible or necessary, but it took a test script to demonstrate that the results are the same and it doesn't matter. Perhaps the clarity is worth it for the price of a simple ifdef.

So, how much time have we (collectively) wasted discussing this subtlety ??

To summarize, I added the ifdef to make it explicitly clear that I'm not altering behavior on unsupported platforms. I can remove it from V4 if desired or I can keep it. (We all now know that it doesn't functionally matter -- it does however, provide clarity.)

Sorry if this sounded like a rant,
Jeff

#137 lore raw

Re: [PATCH v3 06/34] fsmonitor: config settings are repository-specific

In reply to Ævar Arnfjörð Bjarmason
Quoted 11 lines · Ævar Arnfjörð Bjarmason

On Thu, Jul 01 2021, Jeff Hostetler via GitGitGadget wrote:

In a reference to a discussion[1] about an earlier version of this patch you said:

I'm going to ignore all of the thread responses to this patch dealing with how we acquire config settings and macros and etc. Those issues are completely independent of FSMonitor (which is already way too big).

Quoted 5 lines

Since then the changes to repo-settings.c have become a lot larger, so let's take a look...

1. https://lore.kernel.org/git/[email protected]/
2. https://lore.kernel.org/git/[email protected]/

Yes, there was a large conversation about re-doing how config values are acquired or whatever and it was nested inside of the context of a completely unrelated topic. It still is.

I would like to focus on FSMonitor using the existing config API. I'm only looking up ~2 config values. And that code is fairly minor considering everything else in this patch series.

Yes, there's a bizarre initialization with memset(-1), but it can wait.

Later (in a clean context) we can address and focus on the config API and/or structure initialization that you're talking about here.

FWIW, in V4 I'll refactor the block of code that I added into the body of prepare_repo_settings() to address your later concerns. This version will hopefully be more readable.

Quoted 29 lines
Quoted 13 lines
repo-settings.c +3 -0
index 0cfe8b787db..faf197ff60a 100644
@@ -5,10 +5,42 @@
 
  #define UPDATE_DEFAULT_BOOL(s,v) do { if (s == -1) { s = v; } } while(0)
 
+/*
+ * Return 1 if the repo/workdir is incompatible with FSMonitor.
+ */

repo-settings.c line 20

Quoted 10 lines
repo-settings.c +10 -0
+static int is_repo_incompatible_with_fsmonitor(struct repository *r)
+{
+	const char *const_strval;
+
+	/*
+	 * Bare repositories don't have a working directory and
+	 * therefore, nothing to watch.
+	 */
+	if (!r->worktree)
+		return 1;

Looking ahead in this series you end up using FSMONITOR_MODE_INCOMPATIBLE in two places in the codebase. In builtin/update-index.c to throw a "repository is incompatible with fsmonitor" error.

Quoted 5 lines

Can't that case just be replaced with setup_work_tree()? Other sub-modes of update-index already die implicitly on that, e.g.:

	$ git update-index test
	fatal: this operation must be run in a work tree

I will refactor that static function in V4, but I want it to return an indication of whether the repo is compatible only and let the command print the error/die as is appropriate for the daemon and/or update-index. The daemon should not start on an incompatible repo. Likewise, update-index should not enable the extension in the index.

We share some of that code with the client side code, like status, which wants to talk to the hook or daemon if supported/present/allowed. If the repo is incompatible, then status should just behave in the classic manner. So I don't want the detection code to print those error messages or die.

Quoted 11 lines

The other case is:

+ prepare_repo_settings(the_repository);
+ if (!the_repository->worktree)
+ return error(_("fsmonitor-daemon does not support bare repos '%s'"),
+ xgetcwd());
+ if (the_repository->settings.fsmonitor_mode == FSMONITOR_MODE_INCOMPATIBLE)
+ return error(_("fsmonitor-daemon is incompatible with this repo '%s'"),
+ the_repository->worktree);

Quoted 1 line

I.e. we just checked the_repository->worktree, but it's not that, but....

yes, I currently have 2 types of repos that I want to disable both the hook and daemon version of FSMonitor. I'll update the error messages to specify the reason why we are incompatible.

Quoted 10 lines
Quoted 10 lines
repo-settings.c +9 -0
+
+	/*
+	 * GVFS (aka VFS for Git) is incompatible with FSMonitor.
+	 *
+	 * Granted, core Git does not know anything about GVFS and
+	 * we shouldn't make assumptions about a downstream feature,
+	 * but users can install both versions.  And this can lead
+	 * to incorrect results from core Git commands.  So, without
+	 * bringing in any of the GVFS code, do a simple config test
Quoted 8 lines

repo-settings.c line 34

Quoted 5 lines
repo-settings.c +5 -0
+	 * for a published config setting.  (We do not look at the
+	 * various *_TEST_* environment variables.)
+	 */
+	if (!repo_config_get_value(r, "core.virtualfilesystem", &const_strval))
+		return 1;

I'm skeptical of us hardcoding a third-party software config variable. Can't GitVFS handle this somehow on its end?

Adding the test for a GVFS-specific config setting is questionable. And perhaps we should move it to our downstream fork.

The value in putting it here for now (at least) is that it makes clear the structure for supporting other types of incompatible repos.

For example, perhaps we want to disallow repos that are on remote file systems (since we might not be able to get FS events). It would be nice to be able to prevent the daemon from starting and/or from status from trying to connect to a daemon that will be a disappointment. The code I have here serves as a model for adding such additional restrictions. And keeps us from trying to prematurely collapse the code into a simple expression.

Quoted 7 lines

But just in terms of implementation it seems the end result of that is to emit a very confusing error to the user. Sinc we already checked for bare repos we run into this and instead of sayingwhen we should really say "hey, maybe disable your core.virtualFileSystem setting", we say "your repo is incompatible".

I'll update the error messages to make that clear.

Quoted 19 lines

repo-settings.c line 43

Quoted 9 lines
repo-settings.c +5 -0
+
+	return 0;
+}
+
  void prepare_repo_settings(struct repository *r)
  {
  	int value;
  	char *strval;
+	const char *const_strval;

Can be declared in the "else" below.

Quoted 7 lines
repo-settings.c +0 -0
  	if (r->settings.initialized)
  		return;
@@ -26,6 +58,22 @@ void prepare_repo_settings(struct repository *r)
  	UPDATE_DEFAULT_BOOL(r->settings.commit_graph_read_changed_paths, 1);
  	UPDATE_DEFAULT_BOOL(r->settings.gc_write_commit_graph, 1);
 
Quoted 7 lines

repo-settings.c void prepare_repo_settings(struct repository *r) · line 62

Quoted 2 lines
repo-settings.c +2 -0
+	r->settings.fsmonitor_hook_path = NULL;
+	r->settings.fsmonitor_mode = FSMONITOR_MODE_DISABLED;

With the memset earlier (b.t.w. I've got a patch to fix all this bizarre behavior in repo-settings.c, but have been waiting on this series we implicitly set it to FSMONITOR_MODE_UNSET (-1) with the memset, but then never use that ever.

I'm working around the bogus -1 value that the structure has been initialize with and that I'm inheriting. I'll completely set my fsmonitor_mode variable so that I don't care what it is initialized to.

I created the _UNSET value as a reminder that there is this memset(-1) there and that must be attended to.

In my V4 I'll add comments to that effect.

Quoted 17 lines

Your code in update-index.c then for a check against "FSMONITOR_MODE_DISABLED" says "core.useBuiltinFSMonitor is unset;".

repo-settings.c void prepare_repo_settings(struct repository *r) · line 64

Quoted 2 lines
repo-settings.c +2 -0
+	if (is_repo_incompatible_with_fsmonitor(r))
+		r->settings.fsmonitor_mode = FSMONITOR_MODE_INCOMPATIBLE;

Style: should have {} braces on all arms.

repo-settings.c void prepare_repo_settings(struct repository *r) · line 67

Quoted 3 lines
repo-settings.c +3 -0
+	else if (!repo_config_get_bool(r, "core.usebuiltinfsmonitor", &value)
+		   && value)
+		r->settings.fsmonitor_mode = FSMONITOR_MODE_IPC;

Here you're conflating false with whether the variable is set at all. I guess that works out here since if it's false we want to fall through to...

Quoted 3 lines

repo-settings.c void prepare_repo_settings(struct repository *r) · line 68

Quoted 1 line
repo-settings.c +1 -0
+	else {

...ignoring it and looing at core.fsmonitor instead.

yes, if core.useBuiltinFSMonitor is true, we do not need to look at the hook pathname.

Quoted 7 lines

repo-settings.c void prepare_repo_settings(struct repository *r) · line 70

Quoted 3 lines
repo-settings.c +2 -0
+		if (repo_config_get_pathname(r, "core.fsmonitor", &const_strval))
+			const_strval = getenv("GIT_TEST_FSMONITOR");

If it's not set we pay attention to GIT_TEST_FSMONITOR, so the behavior from the old git_config_get_fsmonitor(). So even if the env variable is set we want to take the config variable over it, correct?

GIT_TEST_FSMONITOR sets the hook path for testing and we're not using the hook API at all. So it is kind of ill-defined what that test env var should do if you have IPC turned on, so I'm ignoring it.

Quoted 10 lines

repo-settings.c void prepare_repo_settings(struct repository *r) · line 72

Quoted 3 lines
repo-settings.c +2 -0
+		if (const_strval && *const_strval) {
+			r->settings.fsmonitor_hook_path = strdup(const_strval);

We had a strbuf_detach()'d string in the case of repo_config_get_pathname(), but here we strdup() it again in case we were in the getenv() codepath. This code probably leaks memory now anyway, but perhaps it's better to split up the two so we make it easier to deal with who owns/frees what in the future.

repo_config_get_pathname() returns a const char** which implies that it is not a detached buffer. it is a deep trek thru the config code, but it eventually gets to git_configset_get_*() which is documented as returning a pointer into a configset cache and that the caller should not free it. so dup'ing it is appropriate.

Similarly, getenv() is also returning a pointer to a buffer that the caller does not own. so dup'ing it here is also appropriate.

Thanks,
Jeff

#152 lore raw

Re: [PATCH v3 11/34] fsmonitor-fs-listen-win32: stub in backend for Windows

In reply to Ævar Arnfjörð Bjarmason
Quoted 24 lines · Ævar Arnfjörð Bjarmason
Quoted 24 lines
Quoted 4 lines
Quoted 2 lines
Quoted 1 line

Why put this in an ifdef?

...

Why does the FSMONITOR_DAEMON_BACKEND option require a nonexistent line as opposed to an empty one?

I do not quite get the question.

#!/bin/sh
cat >make.file <<\EOF
all::
ifeq ($(FSMONITOR_DAEMON_BACKEND),)
echo it is empty
endif
ifndef FSMONITOR_DAEMON_BACKEND
echo it is undefined
endif
EOF

echo "unset???"
make -f make.file

echo "set to empty???"
make -f make.file FSMONITOR_DAEMON_BACKEND=

Quoted 8 lines
Quoted 2 lines

These two make invocations will give us the same result, showing that "is it set to empty" and "is it unset" are the same.

Indeed, which is why I'm pointing out that wrapping it in an ifdef is pointless, which is why we don't do it for the other ones.

We do have a bunch of ifdef'd things there for perf etc., I'm not sure if it matters or not for those.

Sorry, but I still do not get the question. There are bunch of ifndef in Makefile in addition to ifeq/ifneq and your question

FSMONITOR_DAEMON_BACKEND option require a nonexistent line as opposed to an empty one?

is asking "why is it X" when X is not quite true. I presume that your "wrapping it in an ifdef" refers to a construct like this:

Quoted 4 lines
Quoted 4 lines
Quoted 4 lines

Makefile ifdef NEED_ACCESS_ROOT_HANDLER · line 1940

Quoted 4 lines
Makefile +4 -0
+ifdef FSMONITOR_DAEMON_BACKEND
+	COMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND
+	COMPAT_OBJS += compat/fsmonitor/fsmonitor-fs-listen-$(FSMONITOR_DAEMON_BACKEND).o
+endif

but is your suggestion that it should be written like this instead?

Quoted 4 lines
Quoted 4 lines
Quoted 4 lines

Makefile ifdef NEED_ACCESS_ROOT_HANDLER · line 1940

Quoted 4 lines
Makefile +4 -0
+ifneq ($(FSMONITOR_DAEMON_BACKEND),)
+	COMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND
+	COMPAT_OBJS += compat/fsmonitor/fsmonitor-fs-listen-$(FSMONITOR_DAEMON_BACKEND).o
+endif

I do not think the latter is any easier to follow (and we have many ifdef and ifndef in our Makefile already). Perhaps I will see what you mean when I see your "better alternative", but so far, I am not successfully guessing what it is.

#141 lore raw

Re: [PATCH v3 08/34] fsmonitor--daemon: add a built-in fsmonitor daemon

In reply to Ævar Arnfjörð Bjarmason
Quoted 60 lines · Ævar Arnfjörð Bjarmason

On Thu, Jul 01 2021, Jeff Hostetler via GitGitGadget wrote:

A general comment on this series (including previous patches). We've usually tried to bend over backwards in git's codebase not to have big ifdef blocks, so we compile most code the same everywhere. We waste a bit of object code, but that's fine.

See 9c897c5c2ad (pack-objects: remove #ifdef NO_PTHREADS, 2018-11-03) for a good exmaple of bad code being turned to good.

E.g. in this case:

Quoted 31 lines
builtin/fsmonitor--daemon.c new file +31 -0
+#ifdef HAVE_FSMONITOR_DAEMON_BACKEND
+
+int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)
+{
+	const char *subcmd;
+
+	struct option options[] = {
+		OPT_END()
+	};
+
+	if (argc < 2)
+		usage_with_options(builtin_fsmonitor__daemon_usage, options);
+
+	if (argc == 2 && !strcmp(argv[1], "-h"))
+		usage_with_options(builtin_fsmonitor__daemon_usage, options);
+
+	git_config(git_default_config, NULL);
+
+	subcmd = argv[1];
+	argv--;
+	argc++;
+
+	argc = parse_options(argc, argv, prefix, options,
+			     builtin_fsmonitor__daemon_usage, 0);
+
+	die(_("Unhandled subcommand '%s'"), subcmd);
+}
+
+#else
+int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)
+{

builtin/fsmonitor--daemon.c line 53

Quoted 10 lines
builtin/fsmonitor--daemon.c new file +10 -0
+	struct option options[] = {
+		OPT_END()
+	};
+
+	if (argc == 2 && !strcmp(argv[1], "-h"))
+		usage_with_options(builtin_fsmonitor__daemon_usage, options);
+
+	die(_("fsmonitor--daemon not supported on this platform"));
+}
+#endif

This whole thing could really just be a -DHAVE_FSMONITOR_DAEMON_BACKEND=1 or -DHAVE_FSMONITOR_DAEMON_BACKEND=0 somewhere (depending), and then somewhere in the middle of the first function:

Quoted 3 lines
	if (!HAVE_FSMONITOR_DAEMON_BACKEND)
	    	die(_("fsmonitor--daemon not supported on this platform"));

This whole file will be filled up with ~1500 lines of static functions that only make sense when the daemon is supported and that make calls to platform-specific backends.

I suppose we could stub in an empty backend (something like that in 11/34 and 12/34) and hack in all stuff in the makefile to link to it in the unsupported case, but that seems like a lot of effort just to avoid an ifdef here.

I mean, the intent of the #else block is quite clear and we're not fooling the reader with a large source file of code that will never be used on their platform.

We could consider splitting this source file into a supported and unsupported version and have the makefile select the right .c file. We'd have to move the usage and stuff to a shared header and etc. That would eliminate the ifdef, but it would break the convention of the source filename matching the command name.

I'm not sure it's worth the bother TBH.

Jeff

#189 lore raw

Re: [PATCH v3 19/34] fsmonitor-fs-listen-win32: implement FSMonitor backend on Windows

In reply to Eric Wong
Quoted 7 lines · Eric Wong
Quoted 6 lines · Ævar Arnfjörð Bjarmason
Quoted 5 lines · Johannes Schindelin

Hi Ævar,

Quoted 1 line · Ævar Arnfjörð Bjarmason
Quoted 7 lines
Quoted 7 lines
Quoted 7 lines
Quoted 7 lines · Jeff Hostetler
Quoted 6 lines · Ævar Arnfjörð Bjarmason
Quoted 4 lines · Jeff Hostetler via GitGitGadget
Quoted 3 lines

From: Jeff Hostetler <[email protected]>

...

Eric, welcome to the conversation and thanks for sharing your concerns.

For my upcoming V4 I've shortened the filenames of the various backends, renamed the -macos one to -darwin, and shortened the names of the fsm-listener API, and the names of those static functions associated with starting the daemon in the background.

I think this covers all of the issues raised across several patches in the series.

Jeff

#210 lore raw

Re: [PATCH v3 24/34] t/perf/p7519: speed up test using "test-tool touch"

In reply to Jeff Hostetler
Quoted 87 lines · Jeff Hostetler
Quoted 81 lines · Ævar Arnfjörð Bjarmason
Quoted 64 lines · Jeff Hostetler
Quoted 51 lines · Ævar Arnfjörð Bjarmason
Quoted 38 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Change p7519 to use a single "test-tool touch" command to update the mtime on a series of (thousands) files instead of invoking thousands of commands to update a single file.

This is primarily for Windows where process creation is so very slow and reduces the test run time by minutes.

---
   t/perf/p7519-fsmonitor.sh | 14 ++++++--------
   1 file changed, 6 insertions(+), 8 deletions(-)
t/perf/p7519-fsmonitor.sh +5 -4
index 5eb5044a103..f74e6014a0a 100755
@@ -119,10 +119,11 @@ test_expect_success "one time repo setup" '
   	fi &&
     	mkdir 1_file 10_files 100_files 1000_files 10000_files &&
-	for i in $(test_seq 1 10); do touch 10_files/$i; done &&
-	for i in $(test_seq 1 100); do touch 100_files/$i; done &&
-	for i in $(test_seq 1 1000); do touch 1000_files/$i; done &&
-	for i in $(test_seq 1 10000); do touch 10000_files/$i; done &&
+	test-tool touch sequence --pattern="10_files/%d" --start=1 --count=10 &&
+	test-tool touch sequence --pattern="100_files/%d" --start=1 --count=100 &&
+	test-tool touch sequence --pattern="1000_files/%d" --start=1 --count=1000 &&
+	test-tool touch sequence --pattern="10000_files/%d" --start=1 --count=10000 &&
+
   	git add 1_file 10_files 100_files 1000_files 10000_files &&
   	git commit -qm "Add files" &&
   @@ -200,15 +201,12 @@ test_fsmonitor_suite() {
   	# Update the mtimes on upto 100k files to make status think
   	# that they are dirty.  For simplicity, omit any files with
   	# LFs (i.e. anything that ls-files thinks it needs to dquote).
t/perf/p7519-fsmonitor.sh +0 -2
-	# Then fully backslash-quote the paths to capture any
-	# whitespace so that they pass thru xargs properly.

t/perf/p7519-fsmonitor.sh test_fsmonitor_suite() { · line 211

Quoted 10 lines
t/perf/p7519-fsmonitor.sh +1 -2
   	#
   	test_perf_w_drop_caches "status (dirty) ($DESC)" '
   		git ls-files | \
   			head -100000 | \
   			grep -v \" | \
-			sed '\''s/\(.\)/\\\1/g'\'' | \
-			xargs test-tool chmtime -300 &&
+			test-tool touch stdin &&
   		git status
   	'

Did you try to replace this with some variant of:

Quoted 5 lines
      test_seq 1 10000 | xargs touch

Which (depending on your xargs version) would invoke "touch" commands
with however many argv items it thinks you can handle.

a quick test on my Windows machine shows that

	test_seq 1 10000 | xargs touch

takes 3.1 seconds.

Quoted 7 lines

just a simple

	test_seq 1 10000 >/dev/null

take 0.2 seconds.

using my test-tool helper cuts that time in half.

There's what Elijah mentioned about test_seq, so maybe it's just that.
But what I was suggesting was using the xargs mode where it does N arguments at a time.
Does this work for you, and does it cause xargs to invoke "touch" with
the relevant N number of arguments, and does it help with the performance?

Quoted 4 lines
     test_seq 1 10000 | xargs touch
     test_seq 1 10000 | xargs -n 10 touch
     test_seq 1 10000 | xargs -n 100 touch
     test_seq 1 10000 | xargs -n 1000 touch
Quoted 6 lines

The GFW SDK version of xargs does have -n N and it does work as advertised. And it does slow down things considerably. Letting it do ~2500 per command in 4 commands took the 3.1 seconds listed above.

Add a -n 100 to it takes 5.7 seconds, so process creation overhead is a factor here.

Doesn't -n 2500 being faster than -n 100 suggest the opposite of process overhead being the deciding factor? With -n 2500 you'll invoke 4 touch processes, so one takes 2500/3.1 =~ 0.8s to run, whereas with -n 100 you invoke 100 of them, so if the overall time is then 5.7 seconds that's 5.7/100 =~ ~0.06s.

Or am I misunderstanding you, or does some implicit parallelism kick in with that version of xargs depending on -n?

Quoted 8 lines
Quoted 6 lines

etc.
Also I didn't notice this before, but the -300 part of "chmtime -300"
was redundant before then? I.e. you're implicitly changing it to "=+0" instead with your "touch" helper, are you not?

Right. I'm changing it to the current time.

If that "while we're at it change the behavior of the test" is wanted I think it should be called out in the commit message. Right now it looks like it might be an unintentional regression in the test.

#173 lore raw

Re: [PATCH v3 14/34] fsmonitor--daemon: implement 'start' command

In reply to Jeff Hostetler
Quoted 105 lines · Jeff Hostetler
Quoted 97 lines · Ævar Arnfjörð Bjarmason
Quoted 95 lines · Jeff Hostetler

My response here is in addition to Dscho's remarks on this topic. He makes excellent points that I'll just #include here. I do want to add my own $0.02 here.

Quoted 74 lines · Ævar Arnfjörð Bjarmason
Quoted 59 lines · Jeff Hostetler via GitGitGadget
(quoted diff) +59 -0
+/*
+ * This is adapted from `wait_or_whine()`.  Watch the child process and
+ * let it get started and begin listening for requests on the socket
+ * before reporting our success.
+ */
+static int wait_for_background_startup(pid_t pid_child)
+{
+	int status;
+	pid_t pid_seen;
+	enum ipc_active_state s;
+	time_t time_limit, now;
+
+	time(&time_limit);
+	time_limit += fsmonitor__start_timeout_sec;
+
+	for (;;) {
+		pid_seen = waitpid(pid_child, &status, WNOHANG);
+
+		if (pid_seen == -1)
+			return error_errno(_("waitpid failed"));
+		else if (pid_seen == 0) {
+			/*
+			 * The child is still running (this should be
+			 * the normal case).  Try to connect to it on
+			 * the socket and see if it is ready for
+			 * business.
+			 *
+			 * If there is another daemon already running,
+			 * our child will fail to start (possibly
+			 * after a timeout on the lock), but we don't
+			 * care (who responds) if the socket is live.
+			 */
+			s = fsmonitor_ipc__get_state();
+			if (s == IPC_STATE__LISTENING)
+				return 0;
+
+			time(&now);
+			if (now > time_limit)
+				return error(_("fsmonitor--daemon not online yet"));
+		} else if (pid_seen == pid_child) {
+			/*
+			 * The new child daemon process shutdown while
+			 * it was starting up, so it is not listening
+			 * on the socket.
+			 *
+			 * Try to ping the socket in the odd chance
+			 * that another daemon started (or was already
+			 * running) while our child was starting.
+			 *
+			 * Again, we don't care who services the socket.
+			 */
+			s = fsmonitor_ipc__get_state();
+			if (s == IPC_STATE__LISTENING)
+				return 0;
+
+			/*
+			 * We don't care about the WEXITSTATUS() nor
+			 * any of the WIF*(status) values because
+			 * `cmd_fsmonitor__daemon()` does the `!!result`

Review

Quoted 10 lines
(quoted diff) +10 -0
+			 * trick on all function return values.
+			 *
+			 * So it is sufficient to just report the
+			 * early shutdown as an error.
+			 */
+			return error(_("fsmonitor--daemon failed to start"));
+		} else
+			return error(_("waitpid is confused"));
+	}
+}

Ditto this. could we extend the wait_or_whine() function (or some extended version thereof) to do what you need with callbacks?

Quoted 9 lines

It seems the main difference is just being able to pass down a flag for
waitpid(), and the loop needing to check EINTR or not depending on whether WNOHANG is passed.
For e.g. the "We don't care about the WEXITSTATUS()" you'd get that behavior with an adjusted wait_or_whine(). Wouldn't it be better to report what exit status it exits with e.g. if the top-level process is signalled? We do so in trace2 for other things we spawn...

Again, I don't want to mix my usage here with the existing code and destabilize all existing callers. Here we are spinning to give the child a chance to *start* and confirm that it is in a listening state and ready for connections. We do not wait for the child to exit (unless it dies quickly without becoming ready).

Quoted 6 lines
Quoted 2 lines

We want to end our wait as soon as we confirm that the child is ready and return. All I really need from the system is waitpid().

Will this code behave correctly if the daemon we start is signalled per
the WIFSIGNALED() cases the code this is derived handles, but this does not?

Quoted 7 lines

We're only waiting until the child gets started and is able to receive requests -- what happens to it after we have confirmed that it is ready is not our concern (after all, the parent is about to exit anyway and the child is going to continue on).

If waitpid() gives us a WIFSIGNALED (or any other WIF*() state) before we have spoken to it, we will return a "failed to start".

So in wait_or_whine() and finish_command() we capture all of that in trace2 logs, we explicitly don't want that in this case? We do concern ourselves with the exact exit status/signal status etc. of children we start in most other scenarios for trace2 logging purposes.

Quoted 19 lines

But again, that signal would have to arrive immediately after we spawned it and *before* we could talk to it. If the child is signaled after we confirmed it was ready, we don't care because the parent process will be gone.

(If the child is signaled or is killed (or crashes or whatever), the next Git command (like "status") that tries to talk to it will re-start it implicitly -- the git fsmonitor--daemon start command here is an explicit start.)

Quoted 7 lines

But sure, I just meant to point out that the flip side to "destabilize
all existing callers" is reviewing new code that may be subtly buggy, and those subtle bugs (if any) would be smoked out if we were forced to extend run-command.c, i.e. to use whatever feature(s) this needs for all existing callers.

Quoted 3 lines

That would/could have a massive footprint. And I've already established that my usage here is sufficiently different from existing uses that the result would be a mess. IMHO.

I hadn't see this before but it seems pretty much exactly the same code was already added (by you) in 36a7eb68760 (t0052: add simple-ipc tests and t/helper/test-simple-ipc tool, 2021-03-22), perhaps splitting it into a utility function for the two to use with a callback mechanism would reduce the footprint?

What I was suggesting was some continuation of the below.

(I stopped once I noticed the changes I was making to builtin/fsmonitor--daemon.c didn't even compile (almost the entire file is hidden behind a macro), but I've commented on that aspect elsewhere. I.e. it's nice to be able to do tree-wide refactoring without tripping over code hidden by ifdefs)

It passes all current tests for whatever that's worth, obviously not a pretty API, and I'm not claiming it's correct.

But I think it's clear how the trace2/error handling part of it could be further extracted into some utility, so this would just be a mode of run-command.

Not saying you need to do it, but the comments about us explicitly not caring at all about the exit state make me wonder if there's some reason for why someone else would be tripping over some landmine if they did that refactoring.

Anyway, just a thought. I see from other feedback that you seem to be getting pretty exasperated with me.

I'm just trying to help this along, usually being able to piggy-back on existing in-tree code and proving the correctness by passing all in-tree tests with that piggy-backing helps more than hinders.

builtin/fsmonitor--daemon.c +6 -5
index 25f18f2726b..7365fff95f4 100644
@@ -8,6 +8,7 @@
 #include "simple-ipc.h"
 #include "khash.h"
 #include "pkt-line.h"
+#include "run-command.h"
 
 static const char * const builtin_fsmonitor__daemon_usage[] = {
 	N_("git fsmonitor--daemon start [<options>]"),
@@ -1403,11 +1404,12 @@ static int wait_for_background_startup(pid_t pid_child)
 	time_limit += fsmonitor__start_timeout_sec;
 
 	for (;;) {
+		int saved_errno = 0;
+		code = wait_or_whine_extended(pid_child, &pid_seen, "TODO",
+					      0, WNOHANG, &saved_errno);
 		pid_seen = waitpid(pid_child, &status, WNOHANG);
 
-		if (pid_seen == -1)
-			return error_errno(_("waitpid failed"));
-		else if (pid_seen == 0) {
+		if (pid_seen == 0) {
 			/*
 			 * The child is still running (this should be
 			 * the normal case).  Try to connect to it on
@@ -1452,8 +1454,7 @@ static int wait_for_background_startup(pid_t pid_child)
 			 * early shutdown as an error.
 			 */
 			return error(_("fsmonitor--daemon failed to start"));
-		} else
-			return error(_("waitpid is confused"));
+		}
 	}
 }
 
run-command.c +22 -9
index aacc336f951..856e7d87c40 100644
@@ -543,24 +543,28 @@ static inline void set_cloexec(int fd)
 		fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
 }
 
-static int wait_or_whine(pid_t pid, const char *argv0, int in_signal)
+int wait_or_whine_extended(pid_t pid, pid_t *waiting, const char *argv0,
+			   int in_signal, int waitpid_options, int *failed_errno)
 {
 	int status, code = -1;
-	pid_t waiting;
-	int failed_errno = 0;
 
-	while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR)
+	if (waitpid_options & WNOHANG)
+		*waiting = waitpid(pid, &status, waitpid_options);
+	else
+		while ((*waiting = waitpid(pid, &status, waitpid_options)) < 0 &&
+		       errno == EINTR)
 		;	/* nothing */
+
 	if (in_signal) {
 		if (WIFEXITED(status))
 			code = WEXITSTATUS(status);
 		return code;
 	}
 
-	if (waiting < 0) {
-		failed_errno = errno;
+	if (*waiting < 0) {
+		*failed_errno = errno;
 		error_errno("waitpid for %s failed", argv0);
-	} else if (waiting != pid) {
+	} else if (*waiting != pid) {
 		error("waitpid is confused (%s)", argv0);
 	} else if (WIFSIGNALED(status)) {
 		code = WTERMSIG(status);
@@ -574,14 +578,23 @@ static int wait_or_whine(pid_t pid, const char *argv0, int in_signal)
 		code += 128;
 	} else if (WIFEXITED(status)) {
 		code = WEXITSTATUS(status);
+	} else if (waitpid_options & WNOHANG && *waiting == 0) {
+		code = 0;
 	} else {
 		error("waitpid is confused (%s)", argv0);
 	}
 
-	clear_child_for_cleanup(pid);
+	return code;
+}
 
+static int wait_or_whine(pid_t pid, const char *argv0, int in_signal)
+{
+	pid_t ignore;
+	int failed_errno = 0;
+	int ret = wait_or_whine_extended(pid, &ignore, argv0, in_signal, 0, &failed_errno);
+	clear_child_for_cleanup(pid);
 	errno = failed_errno;
-	return code;
+	return ret;
 }
 
 static void trace_add_env(struct strbuf *dst, const char *const *deltaenv)
run-command.h +3 -0
index e321d23bbd2..fe39730f87a 100644
@@ -182,6 +182,9 @@ void child_process_clear(struct child_process *);
 
 int is_executable(const char *name);
 
+int wait_or_whine_extended(pid_t pid, pid_t *waiting, const char *argv0,
+			   int in_signal, int waitpid_options, int *failed_errno);
+
 /**
  * Start a sub-process. Takes a pointer to a `struct child_process`
  * that specifies the details and returns pipe FDs (if requested).
t/helper/test-simple-ipc.c +9 -13
index 91345180750..44658a46713 100644
@@ -9,6 +9,7 @@
 #include "parse-options.h"
 #include "thread-utils.h"
 #include "strvec.h"
+#include "run-command.h"
 
 #ifndef SUPPORTS_SIMPLE_IPC
 int cmd__simple_ipc(int argc, const char **argv)
@@ -349,7 +350,7 @@ static int spawn_server(pid_t *pid)
  */
 static int wait_for_server_startup(pid_t pid_child)
 {
-	int status;
+	int code;
 	pid_t pid_seen;
 	enum ipc_active_state s;
 	time_t time_limit, now;
@@ -358,12 +359,10 @@ static int wait_for_server_startup(pid_t pid_child)
 	time_limit += cl_args.max_wait_sec;
 
 	for (;;) {
-		pid_seen = waitpid(pid_child, &status, WNOHANG);
-
-		if (pid_seen == -1)
-			return error_errno(_("waitpid failed"));
-
-		else if (pid_seen == 0) {
+		int saved_errno = 0;
+		code = wait_or_whine_extended(pid_child, &pid_seen, "TODO",
+					      0, WNOHANG, &saved_errno);
+		if (pid_seen == 0) {
 			/*
 			 * The child is still running (this should be
 			 * the normal case).  Try to connect to it on
@@ -384,9 +383,7 @@ static int wait_for_server_startup(pid_t pid_child)
 				return error(_("daemon not online yet"));
 
 			continue;
-		}
-
-		else if (pid_seen == pid_child) {
+		} else if (pid_seen == pid_child) {
 			/*
 			 * The new child daemon process shutdown while
 			 * it was starting up, so it is not listening
@@ -412,10 +409,9 @@ static int wait_for_server_startup(pid_t pid_child)
 			 * early shutdown as an error.
 			 */
 			return error(_("daemon failed to start"));
+		} else if (code) {
+			BUG("??");
 		}
-
-		else
-			return error(_("waitpid is confused"));
 	}
 }
 
#155 lore raw

Re: [PATCH v3 11/34] fsmonitor-fs-listen-win32: stub in backend for Windows

In reply to Jeff Hostetler
Quoted 68 lines · Jeff Hostetler
Quoted 54 lines · Ævar Arnfjörð Bjarmason
Quoted 43 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Stub in empty backend for fsmonitor--daemon on Windows.

---
  Makefile                                     | 13 ++++++
  compat/fsmonitor/fsmonitor-fs-listen-win32.c | 21 +++++++++
  compat/fsmonitor/fsmonitor-fs-listen.h       | 49 ++++++++++++++++++++
  config.mak.uname                             |  2 +
  contrib/buildsystems/CMakeLists.txt          |  5 ++
  5 files changed, 90 insertions(+)

create mode 100644 compat/fsmonitor/fsmonitor-fs-listen-win32.c create mode 100644 compat/fsmonitor/fsmonitor-fs-listen.h

Makefile +9 -0
index c45caacf2c3..a2a6e1f20f6 100644
@@ -467,6 +467,11 @@ all::
  # directory, and the JSON compilation database 'compile_commands.json' will be
  # created at the root of the repository.
  #
+# If your platform supports a built-in fsmonitor backend, set
+# FSMONITOR_DAEMON_BACKEND to the "<name>" of the corresponding
+# `compat/fsmonitor/fsmonitor-fs-listen-<name>.c` that implements the
+# `fsmonitor_fs_listen__*()` routines.
+#
  # Define DEVELOPER to enable more compiler warnings. Compiler version
  # and family are auto detected, but could be overridden by defining
  # COMPILER_FEATURES (see config.mak.dev). You can still set
@@ -1929,6 +1934,11 @@ ifdef NEED_ACCESS_ROOT_HANDLER
  	COMPAT_OBJS += compat/access.o
  endif
  +ifdef FSMONITOR_DAEMON_BACKEND
+	COMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND
+	COMPAT_OBJS += compat/fsmonitor/fsmonitor-fs-listen-$(FSMONITOR_DAEMON_BACKEND).o
+endif
+
  ifeq ($(TCLTK_PATH),)
  NO_TCLTK = NoThanks
  endif

Makefile GIT-BUILD-OPTIONS: FORCE · line 2808

Quoted 6 lines
Makefile +3 -0
@@ -2793,6 +2803,9 @@ GIT-BUILD-OPTIONS: FORCE
  	@echo PAGER_ENV=\''$(subst ','\'',$(subst ','\'',$(PAGER_ENV)))'\' >>$@+
  	@echo DC_SHA1=\''$(subst ','\'',$(subst ','\'',$(DC_SHA1)))'\' >>$@+
  	@echo X=\'$(X)\' >>$@+
+ifdef FSMONITOR_DAEMON_BACKEND
+	@echo FSMONITOR_DAEMON_BACKEND=\''$(subst ','\'',$(subst ','\'',$(FSMONITOR_DAEMON_BACKEND)))'\' >>$@+
+endif

Why put this in an ifdef?
In 342e9ef2d9e (Introduce a performance testing framework, 2012-02-17)

Quoted 9 lines

we started doing that for some perf/test options (which b.t.w., I don't really see the reason for, maybe it's some subtlety in how test-lib.sh picks those up).
But for all the other compile-time stuff we don't ifdef it, we just define it, and then you get an empty value or not.
This would AFAICT be the first build-time-for-the-C-program option we
ifdef for writing a line to GIT-BUILD-OPTIONS.

(I'm going to respond here on the original question rather than on any of the follow up responses in an attempt at diffusing things a bit.)

Quoted 8 lines

I added the ifdef because I thought it to be the *most conservative* thing that I could do. The output of the generated file on unsupported platforms should be *identical* to what it was before my changes. I only alter the contents of the generated file on supported platforms.

Later, when the generated file is consumed, we don't need to worry about the effect (if any) on incremental compiles -- we will know that it won't be set -- just like it was not set in the original compile.

Okey, so e.g. when we added e.g. USE_LIBPCRE2 we added it TO GIT-BUILD-OPTIONS unconditionally, so if you pulled that commit you'd trigger a rebuild on anything that cares about GIT-BUILD-OPTIONS (which is almost everything).

But you'd like to have the line not added to avoid that one-off recompile....

Quoted 29 lines

That change appears right before a 12 other ifdef'd symbols also being written to that generated file. Most are test and perf, but some are not. But my point is that the pattern is present already.

The original question also references a 9.5 year old commit which uses the same pattern as I've used here. It also muddies the water on why it was/wasn't needed back then. And hints at possible side-effects in some of our test scripts. So it is clear that the confusion/disagreements that we are having with the current patch and whether or not to ifdef are not new.

So, is there value in being explicit and having the ifdef ??

There are well defined Make rules (and Junio gave us a very elegant little script to demonstrate that), but the subtleties are there. Especially with our use generated files like GIT-BUILD-OPTIONS. We have a mailing list full of experts and yet this question received a lot more discussion than I thought possible or necessary, but it took a test script to demonstrate that the results are the same and it doesn't matter. Perhaps the clarity is worth it for the price of a simple ifdef.

So, how much time have we (collectively) wasted discussing this subtlety ??

Quoted 7 lines

To summarize, I added the ifdef to make it explicitly clear that I'm not altering behavior on unsupported platforms. I can remove it from V4 if desired or I can keep it. (We all now know that it doesn't functionally matter -- it does however, provide clarity.)

Sorry if this sounded like a rant,

...I asked because I've looked at that ifdef soup around GIT-BUILD-OPTIONS and wondered if I could make it go away, and before a patch lands is a good time to ask "what's this pattern for?", as opposed to inferring this after the fact.

For me it was just a minor curiosity, I didn't expect to start this big discussion about it. I expected just a "oh, I just copy/pasted that from the lines at the end" or something, which would be fair enough.

I really don't care which one we go for here. If you want to change it fine, if not that's fine too.

I have noticed a pattern where you seem to really carefully consider why you'd like X over Y. I.e. it wasn't just copy/pasting in this case if I understand you correctly, but a carefully thought out decision to not do it like the other C-level-GIT-BUILD-OPTIONS.

Okey, fair enough, but that decision then doesn't go into the commit message, and then when I innocently ask about it...

..I guess I'll stop before this starts resembling a rant on my part :)

Anyway, I have also had really non-trivial comments on this fsmonitor series, not just a few bikeshed comments. I.e. the un-addressed question about the wildly different performance numbers we seem to have seen in our respective testing:
https://lore.kernel.org/git/[email protected]

I think that's much more interesting than this relatively light-reading bikeshedding I had while giving this a read-through.

#220 lore raw

Re: [PATCH v3 29/34] fsmonitor--daemon: use a cookie file to sync with file system

In reply to Ævar Arnfjörð Bjarmason
Quoted 32 lines · Ævar Arnfjörð Bjarmason
Quoted 10 lines · Jeff Hostetler via GitGitGadget

From: Jeff Hostetler <[email protected]>

Teach fsmonitor--daemon client threads to create a cookie file inside the .git directory and then wait until FS events for the cookie are observed by the FS listener thread.

This helps address the racy nature of file system events by blocking the client response until the kernel has drained any event backlog.

Quoted 8 lines

This is especially important on MacOS where kernel events are only issued with a limited frequency. See the latency argument of FSeventStreamCreate(). The kernel only signals every latency seconds, but does not guarantee that the kernel queue is completely drained, so we may have to wait more than one interval. If we increase the frequency, the system is more likely to drop events. We avoid these issues by having each client thread create a unique cookie file and then wait until it is seen in the event stream.

Is this a guaranteed property of any API fsmonitor might need to work with (linux, darwin, Windows) that if I perform a bunch of FS operations on my working tree, that if I finish up by touching this cookie file that that'll happen last?

I'd think that wouldn't be the case, i.e. on POSIX filesystems unless you run around fsyncing both files and directories you're not guaranteed that they're on disk, and even then the kernel might decide to sync your cookie earlier, won't it?

Quoted 8 lines

E.g. on Linux you can even have cross-FS watches, and mix & match different FS types. I'd expect to get events in whatever implementation-defined order the VFS layer + FS decided to sync them to disk in & get to firing off an event for me.

Or do these APIs all guarantee that a linear view of the world is presented to the API consumer?

Theoretically, none of these APIs guarantee a complete linear ordering. We receive events from the FS in the order that the FS decides to perform the actual IO. And the inner workings of the FS is private. Even if we directly read the journal rather than listening for notifications, we probably still don't know whether the FS reordered the queue of things heading to disk.

However in practice, the events for the cookie files do tend to arrive in order. And the net effect is that the worker thread in the daemon is sync'd up with IO activity that was initiated before the request.

BTW Watchman also uses cookie files for this same reason.

It should also be noted that some operations are just racy. If you're doing a bunch of IO in one window and a 'git status' in another window, your result will be racy -- status (without FSM) makes 2 passes on the
disk: the first to verify mtimes on items in the index and the second to look for untracked files. the status result may be "blurry" (for lack of a better word). So the same questions
"does the FS reorder my IO?",
"did status see the fully sync'd FS?",
and etc can also be asked in the normal (non FSM) case, right?

So it may be the case that having an fsmonitor (mine, Watchman, etc) and the untracked-cache, we'll have less skew in status results because the status process shouldn't have to do any scanning. But I'm not sure I want to make that assertion yet.

Thanks,
Jeff

#160 lore raw

Re: [PATCH v3 12/34] fsmonitor-fs-listen-macos: stub in backend for MacOS

In reply to Ævar Arnfjörð Bjarmason

Hi Ævar,

Quoted 6 lines · Ævar Arnfjörð Bjarmason
Quoted 3 lines · Johannes Schindelin

So you suggest that we name the new stuff after an uname that reflects a name that is no longer relevant? I haven't seen a real Darwin system in quite a long time, have you?

Quoted 7 lines

It's not current? On an Mac Mini M1 which got released this year:

% uname -s
Darwin

We then have the same in config.mak.uname, it seemed the most obvious and consistent to carry that through to file inclusion.

Sorry. I assumed that you knew that Darwin was the name for an open source Operating System. See
https://en.wikipedia.org/wiki/Darwin_%28operating_system%29 for more details.

Ciao,
Johannes

#161 lore raw

Re: [PATCH v3 12/34] fsmonitor-fs-listen-macos: stub in backend for MacOS

Quoted 18 lines · Johannes Schindelin

Hi Ævar,

Quoted 6 lines · Ævar Arnfjörð Bjarmason
Quoted 3 lines · Johannes Schindelin

So you suggest that we name the new stuff after an uname that reflects a name that is no longer relevant? I haven't seen a real Darwin system in quite a long time, have you?

Quoted 7 lines

It's not current? On an Mac Mini M1 which got released this year:

% uname -s
Darwin

We then have the same in config.mak.uname, it seemed the most obvious and consistent to carry that through to file inclusion.

Quoted 7 lines

Sorry. I assumed that you knew that Darwin was the name for an open source Operating System. See
https://en.wikipedia.org/wiki/Darwin_%28operating_system%29 for more details.

Ciao,
Johannes

Sorry, but I do not see that you are being more constructive than the other party, whom you blame to be not constructive, in this exchange.

The part of the file that the patch applies to uses $(uname_S) to implement platform specific special cases, and we are looking at

	ifeq ($(uname_S),Darwin)
		...
		FSMONITOR_DAEMON_BACKEND = macos
		...
	endif

I find it a fair question why the name used there has to be different from the one we can automatically and mechanically get out of "uname -s".

Then you respond that uname output is no longer relevant because Darwin is a name that is no longer relevant? And when asked why the name is no longer relevant, you make a sniding comment implying that the other party does not know the name is an operating system?

What is going on here?

It does not really matter how "Darwin" is described in an encyclopedia in the context of this discussion. What matters is that it is what the system's "uname -s" currently uses to identify itself, and what we guard the section of makefile snippet with, isn't it?

ci/lib.sh seems to have an attempt to unify/translate among these names, and

* on azure-pipelines, it wants to translate darwin to osx
* on github-actions, it wants to translate macos to osx

Presumably that is because these two systems call the platform with these two different names, and you want to pick a middle ground that nobody uses to be neutral, or something?

Also, in contrib/vscode/init.sh, I see Darwin obtained from "uname -s" gets translated to "macOS".

In any case, if your argument was "we picked macos because we use the same token elsewhere, while trying to translate away from Darwin as much as possible for such and such reasons", I would have found it a productive exchange, but unfortunately that is not what I am seeing here.

#162 lore raw

Re: [PATCH v3 12/34] fsmonitor-fs-listen-macos: stub in backend for MacOS

Quoted 8 lines · Junio C Hamano
Quoted 6 lines · Johannes Schindelin

Hi Ævar,

Quoted 2 lines · Ævar Arnfjörð Bjarmason

On Fri, Jul 16 2021, Johannes Schindelin wrote:

...

I'm not sure that there is a "correct" answer here, but for the sake of harmony, in V4 I'll set this to "darwin" and update the name of the backend driver source file to match. So that we are consistently using 1 term throughout "Makefile" and "config.mak.uname".

	ifeq ($(uname_S),Darwin)
	...
		FSMONITOR_DAEMON_BACKEND = darwin
	endif

FWIW, I suspect that it is not worth the effort to directly set the backend name from $(uname_S). For example, on Windows we currently have two different uname values depending on which compiler is being used.

	ifeq ($(uname_S),Windows)
	...
		FSMONITOR_DAEMON_BACKEND = win32
	endif

	ifneq (,$(findstring MINGW,$(uname_S)))
	...
		FSMONITOR_DAEMON_BACKEND = win32
	endif

Also, since the backend layer is highly platform-specific, it may be a while (if ever) before we have universal coverage for all platforms. Until then, we can simply set $FSMONITOR_DAEMON_BACKEND to a literal value on a platform-by-platform basis as support is added.

Thanks,
Jeff

#163 lore raw

Re: [PATCH v3 12/34] fsmonitor-fs-listen-macos: stub in backend for MacOS

Quoted 39 lines · Jeff Hostetler
Quoted 8 lines · Junio C Hamano
Quoted 6 lines · Johannes Schindelin

Hi Ævar,

Quoted 2 lines · Ævar Arnfjörð Bjarmason

On Fri, Jul 16 2021, Johannes Schindelin wrote:

...

I'm not sure that there is a "correct" answer here, but for the sake of harmony, in V4 I'll set this to "darwin" and update the name of the backend driver source file to match. So that we are consistently using 1 term throughout "Makefile" and "config.mak.uname".

	ifeq ($(uname_S),Darwin)
	...
		FSMONITOR_DAEMON_BACKEND = darwin
	endif

FWIW, I suspect that it is not worth the effort to directly set the backend name from $(uname_S). For example, on Windows we currently have two different uname values depending on which compiler is being used.

	ifeq ($(uname_S),Windows)
	...
		FSMONITOR_DAEMON_BACKEND = win32
	endif

	ifneq (,$(findstring MINGW,$(uname_S)))
	...
		FSMONITOR_DAEMON_BACKEND = win32
	endif
Quoted 4 lines

Also, since the backend layer is highly platform-specific, it may be a while (if ever) before we have universal coverage for all platforms. Until then, we can simply set $FSMONITOR_DAEMON_BACKEND to a literal value on a platform-by-platform basis as support is added.

Re "harmony": For what it's worth I don't think you should change it on my accord.

I should probably have more explicitly said (but I've also been trying to check the general verbosity of my E-Mails), that when I read a series like this and have some general trivial comments like this, I mean them as something like:

Just a thought while reading this through, i.e. a person familiar with the general codebase but not necessarily your specific are. Maybe this suggestion makes things easier/simpler, but if you think not and decide not to take the suggestion that's fine too.

I.e. that along with the general implicit suggestion that I'd say applies in general on list that if someone is perplexed by a patch by default that's a comment on the commit message.

That person (i.e. me in this case) could also just be hopelessly confused & nothing needs to change. When I get comments like that I sometimes change things, sometimes not. You should do the same.

As noted in another reply on this general thread & what's cooking I seem to have poked a bit of a hornet's nest here that I wasn't expecting to poke. I'd not been following earlier rounds of this topic, and didn't know that it had (seemingly) reached some phase of critical updates only in the minds of its authors.

#164 lore raw

Re: [PATCH v3 12/34] fsmonitor-fs-listen-macos: stub in backend for MacOS

In reply to Jeff Hostetler
Quoted 10 lines · Jeff Hostetler

I'm not sure that there is a "correct" answer here, but for the sake of harmony, in V4 I'll set this to "darwin" and update the name of the backend driver source file to match. So that we are consistently using 1 term throughout "Makefile" and "config.mak.uname".

	ifeq ($(uname_S),Darwin)
	...
		FSMONITOR_DAEMON_BACKEND = darwin
	endif

...

I do not think it would help "harmony" to change the name, but any one of 'darwin', 'macos' and 'osx' would be fine.

It was irritating to see a simple "why is this particular name chosen?" answered with such a hostility, when even a "we have no deep reasoning behind the choice of the name. It is only seen by developers in names of the source files, and it does not make much difference" would have been sufficient. I somehow view it more problematic.

I guess the blame goes both ways, though. We all have worked with each other long enough to know which of your recipients are prone to go overly defensive when asked questions, and we should know that it would help to prepend "I am just being curious but..." to your questions whose answers do not make a huge difference at the end either way (or not asking such questions at all).

Thanks.

#165 lore raw

Re: [PATCH v3 12/34] fsmonitor-fs-listen-macos: stub in backend for MacOS

Quoted 6 lines · Junio C Hamano

I guess the blame goes both ways, though. We all have worked with each other long enough to know which of your recipients are prone to go overly defensive when asked questions, and we should know that it would help to prepend "I am just being curious but..." to your questions whose answers do not make a huge difference at the end either way (or not asking such questions at all).

I disagree. It's not OK for contributors to react defensively when asked questions, and in particular I don't think it's OK that some contributors are punished for merely disagreeing, while others are given a pass for snarling at feedback. This is double standards.

The Git project should not play favorites, and all contributors should be asked to assume good faith.

https://en.wikipedia.org/wiki/Wikipedia:Assume_good_faith

It should be implied that the feedback given is to try to improve the project, it should not need to be stated.

Signature · 1 line
Felipe Contreras