Skip to content

Commit 75eb342

Browse files
authored
[2019-08] [System] Make FileSystemWatcher backend non-static (#16926)
[2019-08] [System] Make FileSystemWatcher backend non-static The FileSystemWatcher `watcher` field is the backend IFileWatcher that's supposed to be used for the actual operations. Each backend has a `bool GetInstance (out watcher)` method that returns a singleton object, so each new instance of FileSystemWatcher gets the same IFileWatcher instance. (They will get different `watcher_handle` instances which are used by some backends to uniquely identify this FileSystemWatcher). However when the first FileSystemWatcher instance is `Dispose`d, it will set `watcher = null` which will prevent the remaining instances from calling watcher?.StopDispatching (watcher_handle); watcher?.Dispose (watcher_handle); which means that the backend won't properly cleanup resources for any other FSW instances that have other `watcher_handle` values. Addresses #16709 <!-- Thank you for your Pull Request! If you are new to contributing to Mono, please try to do your best at conforming to our coding guidelines http://www.mono-project.com/community/contributing/coding-guidelines/ but don't worry if you get something wrong. One of the project members will help you to get things landed. Does your pull request fix any of the existing issues? Please use the following format: Fixes #issue-number --> Backport of #16922. /cc @steveisok @lambdageek
1 parent 5881981 commit 75eb342

File tree

2 files changed

+68
-2
lines changed

2 files changed

+68
-2
lines changed

mcs/class/System/System.IO/FileSystemWatcher.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public class FileSystemWatcher : Component, ISupportInitialize {
6161
SearchPattern2 pattern;
6262
bool disposed;
6363
string mangledFilter;
64-
static IFileWatcher watcher;
64+
IFileWatcher watcher;
6565
object watcher_handle;
6666
static object lockobj = new object ();
6767

mcs/class/System/Test/System.IO/FileSystemWatcherTest.cs

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using NUnit.Framework;
1212
using System;
1313
using System.IO;
14+
using System.Reflection;
1415

1516
using MonoTests.Helpers;
1617

@@ -109,7 +110,72 @@ public void LargeNumberOfInstances ()
109110
}
110111
}
111112
}
113+
114+
[Test]
115+
public void CreateTwoAndDispose ()
116+
{
117+
// Create two FSW instances and dispose them. Verify
118+
// that the backend IFileWatcher's Dispose
119+
// (watcher_handle) method got called.
120+
121+
// FIXME: This only works for the
122+
// CoreFXFileSystemWatcherProxy not the other backends.
123+
124+
using (var tmp = new TempDirectory ()) {
125+
// have to use reflection to poke at the private fields of FileSystemWatcher.
126+
var watcherField = typeof (FileSystemWatcher).GetField ("watcher", BindingFlags.Instance | BindingFlags.NonPublic);
127+
Assert.IsNotNull (watcherField);
128+
var watcherHandleField = typeof (FileSystemWatcher).GetField ("watcher_handle", BindingFlags.Instance | BindingFlags.NonPublic);
129+
Assert.IsNotNull (watcherHandleField);
130+
var proxyType = typeof (FileSystemWatcher).Assembly.GetType ("System.IO.CoreFXFileSystemWatcherProxy");
131+
Assert.IsNotNull (proxyType);
132+
133+
var fsw1 = new FileSystemWatcher (tmp.Path, "*");
134+
var fsw2 = new FileSystemWatcher (tmp.Path, "*");
135+
136+
object handle1 = null;
137+
object handle2 = null;
138+
139+
// using "using" to ensure that Dispose gets called even if we throw an exception
140+
using (var fsw11 = fsw1)
141+
using (var fsw22 = fsw2) {
142+
// at this point watcher and watcher_handle should be set
143+
144+
var watcher = watcherField.GetValue (fsw1);
145+
Assert.IsNotNull (watcher);
146+
if (!proxyType.IsAssignableFrom (watcher.GetType ()))
147+
Assert.Ignore ("Testing only CoreFXFileSystemWatcherProxy FSW backend");
148+
149+
handle1 = watcherHandleField.GetValue (fsw1);
150+
handle2 = watcherHandleField.GetValue (fsw2);
151+
152+
Assert.IsNotNull (handle1);
153+
Assert.IsNotNull (handle2);
154+
155+
}
156+
157+
// Dispose was called, now watcher_handle should be null
158+
159+
Assert.IsNull (watcherHandleField.GetValue (fsw1));
160+
Assert.IsNull (watcherHandleField.GetValue (fsw2));
161+
162+
// and moreover, the CoreFXFileSystemWatcherProxy shouldn't have entries for either handle1 or handle2
163+
164+
var proxyTypeInternalMapField = proxyType.GetField ("internal_map", BindingFlags.Static | BindingFlags.NonPublic);
165+
Assert.IsNotNull (proxyTypeInternalMapField);
166+
var internal_map = proxyTypeInternalMapField.GetValue (null)
167+
as global::System.Collections.Generic.IDictionary<object, global::System.IO.CoreFX.FileSystemWatcher>;
168+
Assert.IsNotNull (internal_map);
169+
170+
// This pair are the critical checks: after we call Dispose on fsw1 and fsw2, the
171+
// backend's internal map shouldn't have anything keyed on handle1 and handle2.
172+
// Therefore System.IO.CoreFX.FileSystemWatcher instances will be disposed of, too.
173+
Assert.IsFalse (internal_map.ContainsKey (handle1));
174+
Assert.IsFalse (internal_map.ContainsKey (handle2));
175+
}
176+
}
177+
112178
}
113179
}
114180

115-
#endif
181+
#endif

0 commit comments

Comments
 (0)