Repository navigation
Share CFRunLoop for FSEventStreams in FileSystemWatcher on OSX - #33865
Conversation
|
/cc @marek-safar |
|
@dotnet-bot test Windows x64 Debug Build |
|
@dotnet-bot test Windows x86 Release Build |
stephentoub
left a comment
There was a problem hiding this comment.
Thanks for working on this.
| { | ||
| lock (StopLock) | ||
| // A reference to the RunLoop that we can use to start or stop a Watcher | ||
| private static CFRunLoopRef _watcherRunLoop = IntPtr.Zero; |
| // A reference to the RunLoop that we can use to start or stop a Watcher | ||
| private static CFRunLoopRef _watcherRunLoop = IntPtr.Zero; | ||
|
|
||
| private static int scheduledStreamsCount = 0; |
There was a problem hiding this comment.
Nit: s_scheduledStreamsCount
|
|
||
| private static int scheduledStreamsCount = 0; | ||
|
|
||
| private static object lockObject = new object(); |
There was a problem hiding this comment.
Nit: readonly
Nit: s_lockObject
|
|
||
| public static void ScheduleEventStream(SafeEventStreamHandle eventStream) | ||
| { | ||
| if(_watcherRunLoop == IntPtr.Zero) |
There was a problem hiding this comment.
Nit: space before open paren
| new Thread(WatchForFileSystemEventsThreadStart) { IsBackground = true }.Start(new object[] { runLoopStarted, eventStream }); | ||
| runLoopStarted.Wait(); | ||
|
|
||
| Interlocked.Increment(ref scheduledStreamsCount); |
There was a problem hiding this comment.
This increment needs to be done before the thread is started to avoid a race condition.
There was a problem hiding this comment.
Is starting/stopping FileSystemWatcher generally a hotly contended path? I wouldn't think so. In which case, I'd prefer we avoid trying to be lock-free and instead just use a lock, where we have a better chance of getting the synchronization correct.
| Interop.EventStream.FSEventStreamUnscheduleFromRunLoop(eventStream, _watcherRunLoop, Interop.RunLoop.kCFRunLoopDefaultMode); | ||
| Interlocked.Decrement(ref scheduledStreamsCount); | ||
|
|
||
| if (scheduledStreamsCount == 0) |
There was a problem hiding this comment.
This isn't thread-safe. It should be using the return value of Decrement.
Also what happens if this thread decrements to 0, goes to stop the loop, and then another thread increments and assumes it's already been started?
|
|
||
|
|
||
| // Schedule the EventStream to run on the thread's RunLoop | ||
| Interop.EventStream.FSEventStreamScheduleWithRunLoop(_eventStream, _watcherRunLoop, Interop.RunLoop.kCFRunLoopDefaultMode); |
There was a problem hiding this comment.
Why is the first call / the one spinning up the thread different from all of the others? i.e. why does this call need to be part of WatchForFilesystemEventsThreadStart rather than just being in ScheduleEventStream?
There was a problem hiding this comment.
RunLoop won't run without any scheduled event stream.
|
|
||
| private void CancellationCallback() | ||
| { | ||
| lock (StopLock) { |
There was a problem hiding this comment.
Shouldn't this be the same lock object that's used to coordinate other activities within StaticWatcherRunLoopManager?
| bool started = Interop.EventStream.FSEventStreamStart(_eventStream); | ||
| if (!started) | ||
| { | ||
| // Try to get the Watcher to raise the error event; if we can't do that, just silently exist since the watcher is gone anyway |
There was a problem hiding this comment.
Nit: preexisting your change, but exist => exit
|
cc: @JeremyKuhne |
|
@dotnet-bot test Outerloop OSX x64 Debug Build please |
1 similar comment
|
@dotnet-bot test Outerloop OSX x64 Debug Build please |
|
@dotnet-bot test Outerloop OSX x64 Debug Build please |
|
@dotnet-bot test Outerloop OSX x64 Debug Build please |
|
@dotnet-bot test OSX x64 Debug Build please |
|
@stephentoub good to merge? |
stephentoub
left a comment
There was a problem hiding this comment.
Thanks for working on this. Looking pretty good. A few more comments.
|
|
||
| public static void UnscheduleFromRunLoop(SafeEventStreamHandle eventStream) | ||
| { | ||
| if (s_watcherRunLoop != IntPtr.Zero) |
There was a problem hiding this comment.
In what situation would UnscheduleFromRunLoop be called with s_watcherRunLoop == IntPtr.Zero? And do we really need the double-checked locking here, or could this outer check be removed?
There was a problem hiding this comment.
Can you remove the outer check and add an assert that s_watcherRunLoop isn't zero?
| lock(s_lockObject) | ||
| { | ||
| Interop.CoreFoundation.CFRelease(runLoop); | ||
| s_watcherRunLoop = IntPtr.Zero; |
There was a problem hiding this comment.
This looks like a bug / race condition. What happens if the last watcher gets removed and then immediately after that a new one is created? We could end up stomping here over the new value set by the latter.
Is this write even necessary? Aren't we already setting this to zero when unscheduling the last watcher?
There was a problem hiding this comment.
This should be removed, right?
|
@dotnet-bot test Outerloop OSX x64 Debug Build please |
1 similar comment
|
@dotnet-bot test Outerloop OSX x64 Debug Build please |
|
@dotnet-bot test Outerloop OSX x64 Debug Build please |
1 similar comment
|
@dotnet-bot test Outerloop OSX x64 Debug Build please |
|
@dotnet-bot test OSX x64 Debug Build please |
|
@dotnet-bot test Outerloop OSX x64 Debug Build please |
|
@dotnet-bot test Windows x86 Release Build please |
# Conflicts: # src/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.File.Create.cs
|
@dotnet-bot test Outerloop OSX x64 Debug Build please |
|
@dotnet-bot test Outerloop OSX x64 Debug Build please |
1 similar comment
|
@dotnet-bot test Outerloop OSX x64 Debug Build please |
| [OuterLoop] | ||
| [Fact] | ||
| public void FileSystemWatcher_File_Create_MultipleWatchers_ExecutionContextFlowed() | ||
| public void FileSystemWatcher_File_Create_EnablingDisablingNotAffectRaisingEvent() |
There was a problem hiding this comment.
Could this use the ExpectEvent helper? Or if not, use ExecuteWithRetry around the whole body? We've had too many CI failures due to FSW flakiness, so we have retries in pretty much all of our FSW tests that actually expect events. The ExpectEvent helper has a built-in retry.
There was a problem hiding this comment.
wrapped in ExecuteWithRetry helper method
| [OuterLoop] | ||
| [Fact] | ||
| public void FileSystemWatcher_File_Create_MultipleWatchers_ExecutionContextFlowed() | ||
| public void FileSystemWatcher_File_Create_EnablingDisablingNotAffectRaisingEvent() |
There was a problem hiding this comment.
We should probably make this Outerloop as well, since it's going to end up creating 100 threads, and that could in theory cause issues for other concurrently running tests if the CI machine was resource starved.
| [OuterLoop] | ||
| [Fact] | ||
| public void FileSystemWatcher_File_Create_WatchOwnPath() | ||
| { |
There was a problem hiding this comment.
Same ExecuteWithRetry comment/question.
| { | ||
| string fileName = Path.Combine(TestDirectory, "file"); | ||
| AutoResetEvent[] autoResetEvents = new AutoResetEvent[64]; | ||
| FileSystemWatcher[] watchers = new FileSystemWatcher[64]; |
There was a problem hiding this comment.
We should probably dispose all of these in a finally: that way we'll promptly tear them down when the test completes, even in the case of an exception or assert failure.
…vent test in ExecuteWithRetry helper method and mark it with Outerloop
…try helper method
…leSystemWatcher_File_Create_ForceLoopRestart test
|
@dotnet-bot test Outerloop OSX x64 Debug Build please |
|
@dotnet-bot test Outerloop OSX x64 Debug Build please |
|
@dotnet-bot test Outerloop Linux x64 Debug Build please |
|
@dotnet-bot test Outerloop Linux x64 Debug Build please |
|
Thanks! |
|
Thank you for review and help! |
…t/corefx#33865) * Share CFRunLoop for FSEventStreams in FileSystemWatcher on OSX * Address review comments * Add execution context capturing * Fix nits * Add some tests to check multiple watchers * Address review comments * Update FileSystemWatcher tests * Add timeout for non-expected events * Wrap FileSystemWatcher_File_Create_EnablingDisablingNotAffectRaisingEvent test in ExecuteWithRetry helper method and mark it with Outerloop * Wrap FileSystemWatcher_File_Create_WatchOwnPath test in ExecuteWithRetry helper method * Move disposing FileSystemWatcher instances to a finally section in FileSystemWatcher_File_Create_ForceLoopRestart test * Use null-conditional operator before disposing Commit migrated from dotnet/corefx@8a06434
This is an attempt to solve the problem https://github.com/dotnet/corefx/issues/30600 using the idea of sharing CFRunLoop for FSEventStreams.
I added StaticWatcherRunLoopManager class to share RunLoop among FSEventStreams and updated Start and CancellationCallback methods.
I applied these changes to the latest Mono and used Visual Studio Community 2017 for Mac Version 7.7 (build 1868) to run a sample with 200 FileSystemWatcher instances. mono/mono#11945
It showed reduced number of used threads.