Skip to content

Commit 9e235ec

Browse files
authored
Merge pull request #167 from JeringTech/fix_file_watcher_firing_extra_events
Fix extra file watcher events causing unnecessary Node.js restarts
2 parents 3f5eb53 + face7ed commit 9e235ec

18 files changed

Lines changed: 862 additions & 934 deletions

perf/NodeJS/FileWatchingBenchmarks.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using BenchmarkDotNet.Attributes;
2-
using BenchmarkDotNet.Diagnosers;
32
using Microsoft.Extensions.DependencyInjection;
43

54
namespace Jering.Javascript.NodeJS.Performance
@@ -29,7 +28,7 @@ public void HttpNodeJSService_FileWatching_GracefulShutdownEnabled_MoveToNewProc
2928
[Benchmark]
3029
public void HttpNodeJSService_FileWatching_GracefulShutdownEnabled_MoveToNewProcess()
3130
{
32-
_httpNodeJSService!.FileChangedHandler(DUMMY_PATH);
31+
_httpNodeJSService!.FileChangedHandler();
3332
}
3433

3534
[GlobalSetup(Target = nameof(HttpNodeJSService_FileWatching_GracefulShutdownDisabled_MoveToNewProcess))]
@@ -52,7 +51,7 @@ public void HttpNodeJSService_FileWatching_GracefulShutdownDisabled_MoveToNewPro
5251
[Benchmark]
5352
public void HttpNodeJSService_FileWatching_GracefulShutdownDisabled_MoveToNewProcess()
5453
{
55-
_httpNodeJSService!.FileChangedHandler(DUMMY_PATH);
54+
_httpNodeJSService!.FileChangedHandler();
5655
}
5756

5857
[GlobalCleanup]

src/NodeJS/NodeJSServiceCollectionExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public static IServiceCollection AddNodeJS(this IServiceCollection services)
3131
AddSingleton(typeof(INodeJSService), INodeJSServiceFactory).
3232
AddSingleton<IJsonService, JsonService>().
3333
AddSingleton<IEnvironmentService, EnvironmentService>().
34-
AddSingleton<IFileWatcherFactory, FileWatcherFactory>().
34+
AddSingleton<IFileWatcherService, FileWatcherService>().
3535
AddSingleton<ITaskService, TaskService>().
3636
AddTransient<IBlockDrainerService, BlockDrainerService>();
3737
#if NETCOREAPP3_1

src/NodeJS/NodeJSServiceImplementations/OutOfProcess/Http/HttpNodeJSService.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public class HttpNodeJSService : OutOfProcessNodeJSService
4848
/// <param name="httpNodeJSServiceOptionsAccessor">The <see cref="HttpNodeJSServiceOptions"/> accessor.</param>
4949
/// <param name="httpContentFactory">The factory for creating <see cref="HttpContent"/>s.</param>
5050
/// <param name="embeddedResourcesService">The service for retrieving NodeJS Http server scripts.</param>
51-
/// <param name="fileWatcherFactory">The service for creating <see cref="IFileWatcher"/>s</param>
51+
/// <param name="fileWatcherService">The service for watching files.</param>
5252
/// <param name="taskService">The service for utilizing tasks.</param>
5353
/// <param name="blockDrainerService">The service for draining code blocks.</param>
5454
/// <param name="httpClientService">The service for utilizing <see cref="HttpClient"/>.</param>
@@ -59,7 +59,7 @@ public HttpNodeJSService(IOptions<OutOfProcessNodeJSServiceOptions> outOfProcess
5959
IOptions<HttpNodeJSServiceOptions> httpNodeJSServiceOptionsAccessor,
6060
IHttpContentFactory httpContentFactory,
6161
IEmbeddedResourcesService embeddedResourcesService,
62-
IFileWatcherFactory fileWatcherFactory,
62+
IFileWatcherService fileWatcherService,
6363
ITaskService taskService,
6464
IBlockDrainerService blockDrainerService,
6565
IHttpClientService httpClientService,
@@ -70,7 +70,7 @@ public HttpNodeJSService(IOptions<OutOfProcessNodeJSServiceOptions> outOfProcess
7070
logger,
7171
outOfProcessNodeJSServiceOptionsAccessor,
7272
embeddedResourcesService,
73-
fileWatcherFactory,
73+
fileWatcherService,
7474
taskService,
7575
blockDrainerService,
7676
typeof(HttpNodeJSService).GetTypeInfo().Assembly,

src/NodeJS/NodeJSServiceImplementations/OutOfProcess/OutOfProcessNodeJSService.cs

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public abstract class OutOfProcessNodeJSService : INodeJSService
3131
private readonly IEmbeddedResourcesService _embeddedResourcesService;
3232
private readonly ITaskService _taskService;
3333
private readonly IBlockDrainerService _blockDrainerService;
34-
private readonly IFileWatcherFactory _fileWatcherFactory;
34+
private readonly IFileWatcherService _fileWatcherService;
3535
private readonly INodeJSProcessFactory _nodeProcessFactory;
3636
private readonly string _serverScriptName;
3737
private readonly Assembly _serverScriptAssembly;
@@ -48,7 +48,6 @@ public abstract class OutOfProcessNodeJSService : INodeJSService
4848

4949
private bool _disposed;
5050
private volatile INodeJSProcess? _nodeJSProcess; // Volatile since it's used in a double checked lock
51-
private IFileWatcher? _fileWatcher;
5251

5352
/// <summary>
5453
/// <para>This regex is used to determine successful initialization of the process.</para>
@@ -63,7 +62,7 @@ public abstract class OutOfProcessNodeJSService : INodeJSService
6362
/// <param name="logger"></param>
6463
/// <param name="optionsAccessor"></param>
6564
/// <param name="embeddedResourcesService"></param>
66-
/// <param name="fileWatcherFactory"></param>
65+
/// <param name="fileWatcherService"></param>
6766
/// <param name="taskService"></param>
6867
/// <param name="blockDrainerService"></param>
6968
/// <param name="serverScriptAssembly"></param>
@@ -72,13 +71,13 @@ protected OutOfProcessNodeJSService(INodeJSProcessFactory nodeProcessFactory,
7271
ILogger logger,
7372
IOptions<OutOfProcessNodeJSServiceOptions> optionsAccessor,
7473
IEmbeddedResourcesService embeddedResourcesService,
75-
IFileWatcherFactory fileWatcherFactory,
74+
IFileWatcherService fileWatcherService,
7675
ITaskService taskService,
7776
IBlockDrainerService blockDrainerService,
7877
Assembly serverScriptAssembly,
7978
string serverScriptName)
8079
{
81-
_fileWatcherFactory = fileWatcherFactory;
80+
_fileWatcherService = fileWatcherService;
8281
_taskService = taskService;
8382
_blockDrainerService = blockDrainerService;
8483

@@ -383,13 +382,6 @@ internal virtual async ValueTask ConnectIfNotConnectedAsync(CancellationToken ca
383382
return;
384383
}
385384

386-
// No need to listen for file events while connecting - the new NodeJS process will reload all files.
387-
if (_debugLoggingEnabled)
388-
{
389-
Logger.LogDebug(string.Format(Strings.LogDebug_OutOfProcessNodeJSService_StoppingFileWatcher, _nodeJSProcess?.SafeID));
390-
}
391-
_fileWatcher?.Stop();
392-
393385
await CreateNewProcessAndConnectAsync().ConfigureAwait(false);
394386
}
395387
finally
@@ -458,7 +450,6 @@ internal virtual async Task CreateNewProcessAndConnectAsync()
458450
{
459451
Logger.LogDebug(string.Format(Strings.LogDebug_OutOfProcessNodeJSService_StartingFileWatcher, _nodeJSProcess.SafeID));
460452
}
461-
_fileWatcher?.Start();
462453

463454
_nodeJSProcess.SetConnected();
464455

@@ -533,7 +524,7 @@ internal virtual (bool trackInvokeTasks, ConcurrentDictionary<Task, object?> tra
533524
return default;
534525
}
535526

536-
_fileWatcher = _fileWatcherFactory.Create(_options.WatchPath, _options.WatchSubdirectories, _options.WatchFileNamePatterns, FileChangedHandler);
527+
_fileWatcherService.AddFileChangedListenerAsync(FileChangedHandler);
537528

538529
if (!_options.GracefulProcessShutdown)
539530
{
@@ -578,19 +569,12 @@ internal virtual (bool trackInvokeTasks, ConcurrentDictionary<Task, object?> tra
578569
}
579570

580571
// FileSystemWatcher handles file events synchronously (one after another), storing pending events in a buffer - https://github.com/dotnet/runtime/blob/master/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Win32.cs.
581-
internal virtual void FileChangedHandler(string path)
572+
internal virtual void FileChangedHandler()
582573
{
583-
if (_infoLoggingEnabled)
584-
{
585-
Logger.LogInformation(string.Format(Strings.LogInformation_FileChangedMovingtoNewNodeJSProcess, path));
586-
}
587-
588-
// Immediately stop the fileWatcher so we don't get redundant events (see FileWatcher.Stop)
589574
if (_debugLoggingEnabled)
590575
{
591-
Logger.LogDebug(string.Format(Strings.LogDebug_OutOfProcessNodeJSService_StoppingFileWatcher, _nodeJSProcess?.SafeID));
576+
Logger.LogDebug(string.Format(Strings.LogDebug_FileChangedHandlerInvokedForProcess, _nodeJSProcess?.SafeID));
592577
}
593-
_fileWatcher?.Stop();
594578

595579
#pragma warning disable CS4014
596580
// No need to await
@@ -760,7 +744,6 @@ protected virtual void Dispose(bool disposing)
760744
if (disposing)
761745
{
762746
_nodeJSProcess?.Dispose();
763-
_fileWatcher?.Dispose();
764747
_connectingLock?.Dispose();
765748
_blockDrainerService?.Dispose();
766749
}

src/NodeJS/Strings.Designer.cs

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/NodeJS/Strings.resx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,4 +218,16 @@ Exception:
218218
<data name="LogDebug_OutOfProcessNodeJSService_StoppingFileWatcher" xml:space="preserve">
219219
<value>Stopping FileWatcher. Current process: {0}.</value>
220220
</data>
221+
<data name="LogDebug_InternalFileChangedHandlerCallDebounced" xml:space="preserve">
222+
<value>Internal file changed handler call debounced, file path: "{0}".</value>
223+
</data>
224+
<data name="LogDebug_InternalFileChangedHandlerCalled" xml:space="preserve">
225+
<value>Internal file changed handler called, file path: "{0}".</value>
226+
</data>
227+
<data name="LogInformation_InvokingRegisteredFileChangedHandlers" xml:space="preserve">
228+
<value>Invoking registered file changed handlers, file path: "{0}".</value>
229+
</data>
230+
<data name="LogDebug_FileChangedHandlerInvokedForProcess" xml:space="preserve">
231+
<value>File changed handler invoked for process: {0}.</value>
232+
</data>
221233
</root>

0 commit comments

Comments
 (0)