Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 919d7af

Browse files
committed
Add small timeout to FSW MOVED_FROM polling on Linux
inotify handles renames via a pair of MOVED_FROM and MOVED_TO events that are potentially delivered separately and that for some moves may not have a MOVED_TO at all (such as if moving a file out of the watched path). This means that if we get a MOVED_FROM and we haven't yet received any further events, we have a choice: - Do we wait until we get another event to make a decision about how to process the MOVED_FROM, seeing if the next event is a MOVED_TO (in which case the MOVED_FROM is treated as part of the rename) or if it's something else (in which case the MOVED_FROM is treated as a deletion). - Do we assume we won't get an associated MOVED_TO, and immediately treat the MOVED_FROM as a deletion. We originally implemented the former, as it doesn't suffer from the race condition the latter suffers from, which might treat some MOVED_FROM/MOVED_TO pairs as a rename and others as a creation/deletion, based on timing. But it suffers from another problem, that if you just get a MOVED_FROM and no other event arrives (or arrives for a while) the event associated with the MOVED_FROM could be delayed (indefinitely if another event never arrives). #2587 switched from the former to the latter, under the assumption that it's the least bad of the two options. As discussed at #2655 (comment), a compromise can be to wait a few milliseconds for the MOVED_TO to arrive, as that's been shown to handle a vast majority of the cases where a MOVED_TO is on its way. This commit simply adds that timeout to our call to poll. As long as this code was being modified, the commit also adds a try/finally around the handle manipulation, just in case an exception happens to emerge.
1 parent 1ddfc6d commit 919d7af

File tree

1 file changed

+26
-7
lines changed

1 file changed

+26
-7
lines changed

src/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Linux.cs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -601,15 +601,34 @@ private void ProcessEvents()
601601
// so we will send a DELETE for this event and a CREATE when the MOVED_TO is eventually processed.
602602
if (_bufferPos == _bufferAvailable)
603603
{
604-
bool success = false;
605-
Interop.libc.PollFlags resultFlags;
606-
_inotifyHandle.DangerousAddRef(ref success);
607-
Debug.Assert(success, "Failed to add-ref inotify handle");
608-
int result = Interop.libc.poll(_inotifyHandle.DangerousGetHandle().ToInt32(), Interop.libc.PollFlags.POLLIN, 0, out resultFlags);
609-
_inotifyHandle.DangerousRelease();
604+
int pollResult;
605+
bool gotRef = false;
606+
try
607+
{
608+
_inotifyHandle.DangerousAddRef(ref gotRef);
609+
610+
// Do the poll with a small timeout value. Community research showed that a few milliseconds
611+
// was enough to allow the vast majority of MOVED_TO events that were going to show
612+
// up to actually arrive. This doesn't need to be perfect; there's always the chance
613+
// that a MOVED_TO could show up after whatever timeout is specified, in which case
614+
// it'll just result in a delete + create instead of a rename. We need the value to be
615+
// small so that we don't significantly delay the delivery of the deleted event in case
616+
// that's actually what's needed (otherwise it'd be fine to block indefinitely waiting
617+
// for the next event to arrive).
618+
const int MillisecondsTimeout = 2;
619+
Interop.libc.PollFlags resultFlags;
620+
pollResult = Interop.libc.poll(_inotifyHandle.DangerousGetHandle().ToInt32(), Interop.libc.PollFlags.POLLIN, MillisecondsTimeout, out resultFlags);
621+
}
622+
finally
623+
{
624+
if (gotRef)
625+
{
626+
_inotifyHandle.DangerousRelease();
627+
}
628+
}
610629

611630
// If we error or don't have any signaled handles, send the deleted event
612-
if (result <= 0)
631+
if (pollResult <= 0)
613632
{
614633
// There isn't any more data in the queue so this is a deleted event
615634
watcher.NotifyFileSystemEventArgs(WatcherChangeTypes.Deleted, expandedName);

0 commit comments

Comments
 (0)