Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an experimental built-in FSMonitor #3082

Merged
merged 44 commits into from Mar 8, 2021

Conversation

dscho
Copy link
Member

@dscho dscho commented Mar 5, 2021

Thanks to the hard work of @kewillf and @jeffhostetler, we now have a file system watcher that is built into Git. This allows dramatic speed-ups of any Git command that needs to refresh the index in worktrees with many files, without having to install external tools like Watchman.

The intention is to contribute this feature to the Git mailing list, and we want to include it in Git for Windows v2.31.0 as an experimental, opt-in feature to invite testing.

[EDIT] The feature can be turned on by setting both feature.manyFiles=true and feature.experimental=true (or directly, via core.useBuiltinFSMonitor=true).

Note: the simple-ipc patch series is still under development, after some late reviews came in. We will most likely integrate a newer iteration before v2.31.0 is released.

jeffhostetler and others added 3 commits February 22, 2021 13:50
My "fsmonitor" feature contains 3 parts.  The first part "fsmonitor-prework"
is already upstream in "next".  This merge commit allows my development to
continue in parallel.
In 56c6910 (fsmonitor: change last update timestamp on the
index_state to opaque token, 2020-01-07), we forgot to adjust the part
of `unpack_trees()` that copies the FSMonitor "last-update" information
that we copy from the source index to the result index since 679f2f9
(unpack-trees: skip stat on fsmonitor-valid files, 2019-11-20).

Since the "last-update" information is no longer a 64-bit number, but a
free-form string that has been allocated, we need to duplicate it rather
than just copying it.

This is important because there _are_ cases when `unpack_trees()` will
perform a oneway merge that implicitly calls `refresh_fsmonitor()`
(which will allocate that "last-update" token). This happens _after_
that token was copied into the result index. However, we _then_ call
`check_updates()` on that index, which will _also_ call
`refresh_fsmonitor()`, accessing the "last-update" string, which by now
would be released already.

In the instance that lead to this patch, this caused a segmentation
fault during a lengthy, complicated rebase involving the todo command
`reset` that (crucially) had to updated many files. Unfortunately, it
seems very hard to trigger that crash, therefore this patch is not
accompanied by a regression test.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
In 56c6910 (fsmonitor: change last update timestamp on the
index_state to opaque token, 2020-01-07), we forgot to adjust
`discard_index()` to release the "last-update" token: it is no longer a
64-bit number, but a free-form string that has been allocated.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
@dscho
Copy link
Member Author

dscho commented Mar 5, 2021

Artifacts for testing have been generated here: https://github.com/dscho/git/actions/runs/625859180

jeffhostetler and others added 14 commits March 6, 2021 02:59
Teach `packet_write_gently()` to write the pkt-line header and the actual
buffer in 2 separate calls to `write_in_full()` and avoid the need for a
static buffer, thread-safe scratch space, or an excessively large stack
buffer.

Change `write_packetized_from_fd()` to allocate a temporary buffer rather
than using a static buffer to avoid similar issues here.

These changes are intended to make it easier to use pkt-line routines in
a multi-threaded context with multiple concurrent writers writing to
different streams.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
Remove the `packet_flush_gently()` call in `write_packetized_from_buf() and
`write_packetized_from_fd()` and require the caller to call it if desired.
Rename both functions to `write_packetized_from_*_no_flush()` to prevent
later merge accidents.

`write_packetized_from_buf()` currently only has one caller:
`apply_multi_file_filter()` in `convert.c`.  It always wants a flush packet
to be written after writing the payload.

However, we are about to introduce a caller that wants to write many
packets before a final flush packet, so let's make the caller responsible
for emitting the flush packet.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Introduce PACKET_READ_GENTLE_ON_READ_ERROR option to help libify the
packet readers.

So far, the (possibly indirect) callers of `get_packet_data()` can ask
that function to return an error instead of `die()`ing upon end-of-file.
However, random read errors will still cause the process to die.

So let's introduce an explicit option to tell the packet reader
machinery to please be nice and only return an error on read errors.

This change prepares pkt-line for use by long-running daemon processes.
Such processes should be able to serve multiple concurrent clients and
and survive random IO errors.  If there is an error on one connection,
a daemon should be able to drop that connection and continue serving
existing and future connections.

This ability will be used by a Git-aware "Builtin FSMonitor" feature
in a later patch series.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
Update the calling sequence of `read_packetized_to_strbuf()` to take
an options argument and not assume a fixed set of options.  Update the
only existing caller accordingly to explicitly pass the
formerly-assumed flags.

The `read_packetized_to_strbuf()` function calls `packet_read()` with
a fixed set of assumed options (`PACKET_READ_GENTLE_ON_EOF`).  This
assumption has been fine for the single existing caller
`apply_multi_file_filter()` in `convert.c`.

In a later commit we would like to add other callers to
`read_packetized_to_strbuf()` that need a different set of options.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
Brief design documentation for new IPC mechanism allowing
foreground Git client to talk with an existing daemon process
at a known location using a named pipe or unix domain socket.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
Create Windows implementation of "simple-ipc" using named pipes.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
The static helper function `unix_stream_socket()` calls `die()`.  This
is not appropriate for all callers.  Eliminate the wrapper function
and make the callers propagate the error.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
Update `unix_stream_listen()` to take an options structure to override
default behaviors.  This commit includes the size of the `listen()` backlog.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
Calls to `chdir()` are dangerous in a multi-threaded context.  If
`unix_stream_listen()` or `unix_stream_connect()` is given a socket
pathname that is too long to fit in a `sockaddr_un` structure, it will
`chdir()` to the parent directory of the requested socket pathname,
create the socket using a relative pathname, and then `chdir()` back.
This is not thread-safe.

Teach `unix_sockaddr_init()` to not allow calls to `chdir()` when this
flag is set.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
Create a wrapper class for `unix_stream_listen()` that uses a ".lock"
lockfile to create the unix domain socket in a race-free manner.

Unix domain sockets have a fundamental problem on Unix systems because
they persist in the filesystem until they are deleted.  This is
independent of whether a server is actually listening for connections.
Well-behaved servers are expected to delete the socket when they
shutdown.  A new server cannot easily tell if a found socket is
attached to an active server or is leftover cruft from a dead server.
The traditional solution used by `unix_stream_listen()` is to force
delete the socket pathname and then create a new socket.  This solves
the latter (cruft) problem, but in the case of the former, it orphans
the existing server (by stealing the pathname associated with the
socket it is listening on).

We cannot directly use a .lock lockfile to create the socket because
the socket is created by `bind(2)` rather than the `open(2)` mechanism
used by `tempfile.c`.

As an alternative, we hold a plain lockfile ("<path>.lock") as a
mutual exclusion device.  Under the lock, we test if an existing
socket ("<path>") is has an active server.  If not, we create a new
socket and begin listening.  Then we use "rollback" to delete the
lockfile in all cases.

This wrapper code conceptually exists at a higher-level than the core
unix_stream_connect() and unix_stream_listen() routines that it
consumes.  It is isolated in a wrapper class for clarity.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
Create Unix domain socket based implementation of "simple-ipc".

A set of `ipc_client` routines implement a client library to connect
to an `ipc_server` over a Unix domain socket, send a simple request,
and receive a single response.  Clients use blocking IO on the socket.

A set of `ipc_server` routines implement a thread pool to listen for
and concurrently service client connections.

The server creates a new Unix domain socket at a known location.  If a
socket already exists with that name, the server tries to determine if
another server is already listening on the socket or if the socket is
dead.  If socket is busy, the server exits with an error rather than
stealing the socket.  If the socket is dead, the server creates a new
one and starts up.

If while running, the server detects that its socket has been stolen
by another server, it automatically exits.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
Create t0052-simple-ipc.sh with unit tests for the "simple-ipc" mechanism.

Create t/helper/test-simple-ipc test tool to exercise the "simple-ipc"
functions.

When the tool is invoked with "run-daemon", it runs a server to listen
for "simple-ipc" connections on a test socket or named pipe and
responds to a set of commands to exercise/stress the communication
setup.

When the tool is invoked with "start-daemon", it spawns a "run-daemon"
command in the background and waits for the server to become ready
before exiting.  (This helps make unit tests in t0052 more predictable
and avoids the need for arbitrary sleeps in the test script.)

The tool also has a series of client "send" commands to send commands
and data to a server instance.

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
My "fsmonitor" feature contains 3 parts.  The second part "simple-ipc"
is upstream under review for a while already, and "simple-ipc2" is
intended to enter `next` soon after v2.31.0 has been released.  This
merge commit allows my development to continue in parallel.
This corresponds to git-for-windows#3053.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
@ghost

This comment has been minimized.

@ghost

This comment has been minimized.

@@ -66,7 +66,11 @@ core.fsmonitor::
will identify all files that may have changed since the

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we qualify this a little?

If set, the value of this variable is used as a command hook which will...

@@ -66,7 +66,11 @@ core.fsmonitor::
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].
+
See the "fsmonitor-watchman" section of linkgit:githooks[5].

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?? ... and see core.fsmonitorHookVersion ??

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I don't think we need this here, but you're right, I need to mention the core.useBuiltinFSMonitor option in githooks.txt.

See the "fsmonitor-watchman" section of linkgit:githooks[5].
+
Note: this option is ignored if the built-in FSMonitor is enabled via the
`core.useBuiltinFSMonitor` option (see linkgit:git-fsmonitor--daemon[1]).

core.fsmonitorHookVersion::
Sets the version of hook that is to be used when calling fsmonitor.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?? ... when calling the fsmonitor command specified in core.fsmonitor ??

I know I'm being paranoid here. I'm just looking for a way to tie core.fsmonitor and core.fsmonitorHookVersion together and break any association with our new builtin setting.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point!

+
See the "fsmonitor-watchman" section of linkgit:githooks[5].
+
Note: this option is ignored if the built-in FSMonitor is enabled via the

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps here, just refer to the core.useBuiltinFSMonitor setting.

Note: FSMonitor hooks (and this config setting) are ignored if the built-in FSMonitor is enabled (see `core.useBuiltinFSMonitor`).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, much nicer.

@@ -79,6 +83,13 @@ core.fsmonitorHookVersion::
something that can be used to determine what files have changed
without race conditions.

core.useBuiltinFSMonitor::
Enable the built-in filesystem event watcher. This requires a

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe embellish this a bit ??

If set to true, ignore the `core.fsmonitor` hook and enable the built-in filesystem
event watcher linkgit:git-fsmonitor--daemon[1].
+
Like external (hook-based) FSMonitors, the built-in FSMonitor can speed up Git
commands, such `git status`, that need to refresh the Git index in a worktree 
with many files.  The built-in FSMonitor facility eliminates the need to install and
maintain external third-party monitoring tool.
+
The built-in FSMonitor is currently available only on a limited set of supported
platforms.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reworded this only slightly, and replaced the previous version.

working directory. Commands, such as `git status`, should automatically
start it (if necessary) when `core.useBuiltinFSMonitor` is set to `true`.

To watch more than one working directory, start one instance of the

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to tell them to enable core.useBuiltinFSMonitor in each working directory
where they want to use it. This will cause a daemon instance to be started in each.
They shouldn't explicitly start it (without setting that bit).

Should we also mention git update-index --fsmonitor after setting the bit ??

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But after enabling the built-in FSMonitor, you do not need to call git update-index --fsmonitor, a simple git status will do, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I replaced the paragraph with this:

Configure the built-in FSMonitor via `core.useBuiltinFSMonitor` in each
working directory separately, or globally via `git config --global
core.useBuiltinFSMonitor true`.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, we don't need to run update-index, it will be automatically turned on when the next status-like command is invoked.

-------
The fsmonitor daemon is a long running process that will watch a single
working directory. Commands, such as `git status`, should automatically
start it (if necessary) when `core.useBuiltinFSMonitor` is set to `true`.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this have a gitlink ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes!

UPDATE_DEFAULT_BOOL(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_SKIPPING);
if (feature_many_files)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to see if this platform supports a FSMonitor backend ??

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot to mention that I have afsmonitor_ipc__is_supported() function in fsmonitor-ipc.c that is always defined and lets callers avoid #ifdefs.

git-for-windows-ci pushed a commit that referenced this pull request May 22, 2021
Add an experimental built-in FSMonitor
git-for-windows-ci pushed a commit that referenced this pull request May 22, 2021
Add an experimental built-in FSMonitor
git-for-windows-ci pushed a commit that referenced this pull request May 22, 2021
Add an experimental built-in FSMonitor
git-for-windows-ci pushed a commit that referenced this pull request May 22, 2021
Add an experimental built-in FSMonitor
git-for-windows-ci pushed a commit that referenced this pull request May 22, 2021
Add an experimental built-in FSMonitor
dscho pushed a commit to dscho/git that referenced this pull request May 22, 2021
git-for-windows-ci pushed a commit that referenced this pull request May 22, 2021
Add an experimental built-in FSMonitor
git-for-windows-ci pushed a commit that referenced this pull request May 22, 2021
Add an experimental built-in FSMonitor
dscho pushed a commit that referenced this pull request May 22, 2021
Add an experimental built-in FSMonitor
dscho pushed a commit that referenced this pull request May 22, 2021
Add an experimental built-in FSMonitor
git-for-windows-ci pushed a commit that referenced this pull request May 24, 2021
Add an experimental built-in FSMonitor
git-for-windows-ci pushed a commit that referenced this pull request May 24, 2021
Add an experimental built-in FSMonitor
git-for-windows-ci pushed a commit that referenced this pull request May 25, 2021
Add an experimental built-in FSMonitor
git-for-windows-ci pushed a commit that referenced this pull request May 25, 2021
Add an experimental built-in FSMonitor
git-for-windows-ci pushed a commit that referenced this pull request May 25, 2021
Add an experimental built-in FSMonitor
git-for-windows-ci pushed a commit that referenced this pull request May 25, 2021
Add an experimental built-in FSMonitor
git-for-windows-ci pushed a commit that referenced this pull request May 25, 2021
Add an experimental built-in FSMonitor
git-for-windows-ci pushed a commit that referenced this pull request May 25, 2021
Add an experimental built-in FSMonitor
git-for-windows-ci pushed a commit that referenced this pull request May 25, 2021
Add an experimental built-in FSMonitor
git-for-windows-ci pushed a commit that referenced this pull request May 25, 2021
Add an experimental built-in FSMonitor
git-for-windows-ci pushed a commit that referenced this pull request May 27, 2021
Add an experimental built-in FSMonitor
git-for-windows-ci pushed a commit that referenced this pull request May 27, 2021
Add an experimental built-in FSMonitor
git-for-windows-ci pushed a commit that referenced this pull request May 27, 2021
Add an experimental built-in FSMonitor
git-for-windows-ci pushed a commit that referenced this pull request May 28, 2021
Add an experimental built-in FSMonitor
git-for-windows-ci pushed a commit that referenced this pull request May 28, 2021
Add an experimental built-in FSMonitor
@asottile
Copy link

is there a way to opt out of this if the user has configured this option? running into this here: pre-commit/pre-commit#2046 (cloning in a temporary directory and then deleting the temporary directory fails)

@dscho
Copy link
Member Author

dscho commented Sep 13, 2021

Configuring via the command-line should work: git -c core.useBuiltinFSMonitor=false ...

But then, it's a bit of an anti-pattern to clone only to delete immediately. It would already be better to use a bare clone and the using git archive or git cat-file to extract the file(s) of interest. Not sure what your particular use case looks like, exactly.

@asottile
Copy link

git is being used as a distribution mechanism -- the repositories are ~immutable but need to be present on disk. they're not going to be interacted with by users so running a filesystem daemon for them doesn't really make sense (and if it is running against them, deleting them is problematic!)

though I suspect even git clone somerepo; rm -rf somerepo would trigger the same problems on windows -- so this might be an even broader problem than just my use case ?

@asottile
Copy link

I was able to reproduce my error pretty easily with some kinda normal workspace management commands:

git init pc2
git -C pc2 remote add origin https://github.com/pre-commit/pre-commit
git -C pc2 fetch origin HEAD --tags -q
git -C pc2 describe FETCH_HEAD --abbrev=0 --tags
sleep 5
rm -rf pc2

this produces:

C:\Users\Anthony\workspace>git init pc2 && git -C pc2 remote add origin https://github.com/pre-commit/pre-commit && git -C pc2 fetch origin HEAD --tags -q && git -C pc2 describe FETCH_HEAD --abbrev=0 --tags && sleep 5 && rm -rf pc2
Initialized empty Git repository in C:/Users/Anthony/workspace/pc2/.git/
starting fsmonitor-daemon in 'C:/Users/Anthony/workspace/pc2'
v2.15.0
rm: cannot remove 'pc2': Device or resource busy

@jeffhostetler
Copy link

The experimental version of FSMonitor shipped in v2.32 and v2.33 of git-for-windows does hold a lock on the root directory of the worktree (because it's CWD is there). This will be fixed in the next release.

The new FSMonitor feature is controlled by the core.useBuiltinFSMonitor boolean.

However, during the experimental releases, it is also (force) enabled when feature.experimental and feature.manyfiles are turned on.

Try turning off either/both of these feature.* settings and see if that helps.

(Oh, and in the next release, the "starting fsmonitor-daemon..." message will be suppressed by default to avoid causing problems for scripts parsing command output.)

Hope this helps and let me know if you have further problems.
Jeff

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants