1

I'm working on a custom server based on HttpListener. It's working fairly well, until I try to use a FileSystemWatcher to make it able to respond to changing script content without needing to reboot the server. It doesn't appear to ever fire.

The relevant code looks like this:

foreach (var script in Directory.EnumerateFiles(folder, mask))
{
    var filename = Path.GetFileNameWithoutExtension(script);
    var compileResult = scriptCompiler.CompileScript(script);
    if (compileResult.Errors.Count > 0)
        throw new Exception(compileResult.Errors.ToString());
    AddScriptType(compileResult.GeneratedAssembly.GetType(filename));
}
this._watcher = new FileSystemWatcher(folder, mask);
_watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
var changed = (s, e) => RebuildScript(scriptCompiler, e);
_watcher.Changed += changed;
_watcher.Created += changed;
_watcher.Deleted += this.ScriptDeleted;
_watcher.EnableRaisingEvents = true;

This seems pretty straightforward, but RebuildScript never gets called if I go in and change one of the script files.

I can't help but wonder if HttpListener is interfering with some messaging architecture needed to make this work properly. The server runs on the main thread, in an infinite loop of waiting for an HTTP request, processing it, and returning the response. Could that be causing FileSystemWatcher to never receive events from the OS? If not, how do I figure out what's causing this to fail?

Mason Wheeler
  • 82,511
  • 50
  • 270
  • 477
  • It doesn't, unless you use its SynchronizingObject member. LastWrite is famously lazy and any decent text editor will avoid overwriting files to avoid catastrophic data loss. Add more NotifyFilter options to get ahead. – Hans Passant Apr 03 '18 at 16:51
  • @HansPassant It shouldn't avoid overwriting files when I hit Save, though! But even at this point, the event does not fire. – Mason Wheeler Apr 03 '18 at 16:53
  • It does not overwrite the file, it renames files. – Hans Passant Apr 03 '18 at 16:54
  • @HansPassant Even in Notepad? (I tested this with the simplest thing possible, specifically to avoid weird complications like this, and it still didn't fire.) – Mason Wheeler Apr 03 '18 at 16:59
  • 1
    I don't know, it is something you can find out with FileSystemWatcher. – Hans Passant Apr 03 '18 at 17:01
  • Both the reference code and .NET Core repos contain the relevant code, https://github.com/Microsoft/referencesource/blob/master/System/services/io/system/io/FileSystemWatcher.cs and https://github.com/dotnet/corefx/tree/master/src/System.IO.FileSystem.Watcher So in your case, you have to dig the code. There seems to be quite limited documentation other than that. – Lex Li Apr 03 '18 at 17:42
  • Can you post the actual code that initializes the FSW? Is `_watcher` an object built using a custom class derived from FSW?. The code shown here doesn't set `EnableRaisingEvents=true` and uses `OnChange`, which I doubt will compile as it is (the event is `Change`). In normal conditions, using Notepad to modify a text file, the subscribed event, with a `NotifyFilter = NotifyFilters.LastWrite`, is raised twice. -- The FSW events are marshalled from the system thread pool. You have to set a `SynchronizingObject` to call the event delegates on the same thread where the component is created. – Jimi Apr 03 '18 at 22:24
  • @jim My mistake. I was recreating this from memory, from a different system. I've edited it to correct the event name. So I need to set `EnableRaisingEvents=true` before it will work? – Mason Wheeler Apr 03 '18 at 23:06
  • Yes, of course. Without `EnableRaisingEvents=true`, FSW is a sitting duck. When you set it to false, it will stop the notifications. The default is `false`, when a class object is first initialized. This is a good idea, because the FSW could begin raising events before it's completly configured. – Jimi Apr 03 '18 at 23:16
  • @jimi All right. I set it `true`, and now when I change something in Notepad, I get a Deleted event but no corresponding Created or Changed event... – Mason Wheeler Apr 03 '18 at 23:34
  • So, you have changed/added more than one event handler and `NofityFilters`. Post the code you're using now, we're blind here. – Jimi Apr 03 '18 at 23:41
  • @jimi Updated with actual code. `ScriptDeleted` gets called now, but `RebuildScript` never does. – Mason Wheeler Apr 04 '18 at 00:04
  • I suggest to assign to the `Created` event a proper event handler, and call your `RebuildScript()` method from there (I don't know what `scriptCompiler` is, but it looks like an implicit type assignment in a Lambda). Assign a separate handler to the `Created` event. The NotifyFilters you use are related to what you want to be notified of. `LastWrite` is useful for a `Changed` event, could be coupled with `Size`, `FileName` for `Deleted`, `Created` and `Renamed`. `DirectoryName` for Directory changes. `LastAccess` is +- useless in Windows, it's only set once (when a file is first created) – Jimi Apr 04 '18 at 00:30
  • 1
    "It does not overwrite the file, it renames files". That does mean that you are interested in the Renamed event. – Hans Passant Apr 04 '18 at 00:32
  • Well, I meant "I suggest to assign to the **`Changed`** event a proper event handler". – Jimi Apr 04 '18 at 01:34
  • @HansPassant That ended up working. Thanks! – Mason Wheeler Apr 04 '18 at 01:40

1 Answers1

0

As @HansPassant mentioned in the comments, the options set for NotifyFilter prevent the event from firing.

According to https://msdn.microsoft.com/en-us/library/system.io.filesystemwatcher.notifyfilter(v=vs.110).aspx , "[t]he default is the bitwise OR combination of LastWrite, FileName, and DirectoryName."

This post discusses choosing the appropriate FileSystemWatcher filters to achieve your goals. Which filter of FileSystemWatcher do I need to use for finding new files. As @DavidBrabant mentioned in an answer in this post, I would suggest setting _watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName;