diff --git a/Changelog.md b/Changelog.md index 204a3f1..819df8e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -3,7 +3,11 @@ This project uses [semantic versioning](http://semver.org/spec/v2.0.0.html). Ref *[Semantic Versioning in Practice](https://www.jering.tech/articles/semantic-versioning-in-practice)* for an overview of semantic versioning. -## [Unreleased](https://github.com/JeringTech/Javascript.NodeJS/compare/6.0.1...HEAD) +## [Unreleased](https://github.com/JeringTech/Javascript.NodeJS/compare/6.1.0...HEAD) + +## [6.1.0](https://github.com/JeringTech/Javascript.NodeJS/compare/6.0.1...6.1.0) - Nov 4, 2021 +### Additions +- Added `INodeJSService.MoveToNewProcess` method. ([#122](https://github.com/JeringTech/Javascript.NodeJS/pull/122)). ## [6.0.1](https://github.com/JeringTech/Javascript.NodeJS/compare/6.0.0...6.0.1) - May 24, 2021 ### Fixes diff --git a/ReadMe.md b/ReadMe.md index 96ecb56..4ae9e1e 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1218,6 +1218,20 @@ Thrown if this instance is disposed or if it attempts to use a disposed dependen `OperationCanceledException` Thrown if `cancellationToken` is cancelled. +##### INodeJSService.MoveToNewProcess() +Moves subsequent invocations to a new NodeJS process. +```csharp +void MoveToNewProcess() +``` +###### Remarks +This method exposes the system used by file watching (see `OutOfProcessNodeJSServiceOptions.EnableFileWatching`) and process retries +(see `OutOfProcessNodeJSServiceOptions.NumProcessRetries`) to move to new processes. + +When is access to this system useful? Consider the situation where your application uses file watching. +If your application knows when files change (e.g. your application is the actor changing files) you can manually invoke this method instead of using file +watching. This enables you to avoid the overhead of file watching. + +The method respects `OutOfProcessNodeJSServiceOptions.GracefulProcessShutdown`. @@ -1409,7 +1423,7 @@ This value does nothing if `OutOfProcessNodeJSServiceOptions.EnableFileWatching` Defaults to "*.js", "*.jsx", "*.ts", "*.tsx", "*.json" and "*.html". ##### OutOfProcessNodeJSServiceOptions.GracefulProcessShutdown -The value specifying whether NodeJS processes shutdown gracefully when a file changes or an invocation is retried in a new process. +The value specifying whether NodeJS processes shutdown gracefully when moving to a new process. ```csharp public bool GracefulProcessShutdown { get; set; } ``` diff --git a/generators/Jering.Javascript.NodeJS.CodeGenerators/HttpNodeJSPoolServiceGenerator.cs b/generators/Jering.Javascript.NodeJS.CodeGenerators/HttpNodeJSPoolServiceGenerator.cs index 5a348ef..bec683e 100644 --- a/generators/Jering.Javascript.NodeJS.CodeGenerators/HttpNodeJSPoolServiceGenerator.cs +++ b/generators/Jering.Javascript.NodeJS.CodeGenerators/HttpNodeJSPoolServiceGenerator.cs @@ -102,7 +102,7 @@ public partial class HttpNodeJSPoolService ImmutableArray memberSymbols = interfaceSymbol.GetMembers(); foreach (ISymbol memberSymbol in memberSymbols) { - if (cancellationToken.IsCancellationRequested) + if (cancellationToken.IsCancellationRequested || memberSymbol is not IMethodSymbol methodSymbol) { return; } @@ -112,10 +112,18 @@ public partial class HttpNodeJSPoolService AppendLine(). AppendLine(" /// "). Append(" public "). - AppendLine(memberSymbol.ToDisplayString(_declarationSymbolDisplayFormat)). + AppendLine(methodSymbol.ToDisplayString(_declarationSymbolDisplayFormat)). Append(@" { - return GetHttpNodeJSService()."). - Append(memberSymbol.ToDisplayString(_invocationSymbolDisplayFormat)). + "); + + if (methodSymbol.ReturnType.SpecialType != SpecialType.System_Void) + { + classBuilder.Append("return "); + } + + classBuilder. + Append("GetHttpNodeJSService()."). + Append(methodSymbol.ToDisplayString(_invocationSymbolDisplayFormat)). AppendLine(@"; }"); } diff --git a/generators/Jering.Javascript.NodeJS.CodeGenerators/StaticNodeJSServiceGenerator.cs b/generators/Jering.Javascript.NodeJS.CodeGenerators/StaticNodeJSServiceGenerator.cs index 875021b..2a7318d 100644 --- a/generators/Jering.Javascript.NodeJS.CodeGenerators/StaticNodeJSServiceGenerator.cs +++ b/generators/Jering.Javascript.NodeJS.CodeGenerators/StaticNodeJSServiceGenerator.cs @@ -103,16 +103,16 @@ public static partial class StaticNodeJSService ImmutableArray memberSymbols = interfaceSymbol.GetMembers(); foreach(ISymbol memberSymbol in memberSymbols) { - if (cancellationToken.IsCancellationRequested) + if (cancellationToken.IsCancellationRequested || memberSymbol is not IMethodSymbol methodSymbol) { return; } // Get leading trivia - SyntaxReference? syntaxReference = memberSymbol.DeclaringSyntaxReferences.FirstOrDefault(); + SyntaxReference? syntaxReference = methodSymbol.DeclaringSyntaxReferences.FirstOrDefault(); if(syntaxReference == null) { - context.ReportDiagnostic(Diagnostic.Create(_missingMemberDeclaration, null, memberSymbol.Name)); + context.ReportDiagnostic(Diagnostic.Create(_missingMemberDeclaration, null, methodSymbol.Name)); continue; } string leadingTrivia = syntaxReference.GetSyntax().GetLeadingTrivia().ToFullString(); @@ -121,10 +121,18 @@ public static partial class StaticNodeJSService classBuilder. Append(leadingTrivia). Append("public static "). - AppendLine(memberSymbol.ToDisplayString(_declarationSymbolDisplayFormat)). + AppendLine(methodSymbol.ToDisplayString(_declarationSymbolDisplayFormat)). Append(@" { - return GetOrCreateNodeJSService()."). - Append(memberSymbol.ToDisplayString(_invocationSymbolDisplayFormat)). + "); + + if (methodSymbol.ReturnType.SpecialType != SpecialType.System_Void) + { + classBuilder.Append("return "); + } + + classBuilder. + Append("GetOrCreateNodeJSService()."). + Append(methodSymbol.ToDisplayString(_invocationSymbolDisplayFormat)). AppendLine(@"; }"); } diff --git a/src/NodeJS/INodeJSService.cs b/src/NodeJS/INodeJSService.cs index a1f6b53..45ae032 100644 --- a/src/NodeJS/INodeJSService.cs +++ b/src/NodeJS/INodeJSService.cs @@ -309,5 +309,16 @@ public interface INodeJSService : IDisposable /// Thrown if this instance is disposed or if it attempts to use a disposed dependency. /// Thrown if is cancelled. Task TryInvokeFromCacheAsync(string cacheIdentifier, string? exportName = null, object?[]? args = null, CancellationToken cancellationToken = default); + + /// Moves subsequent invocations to a new NodeJS process. + /// + /// This method exposes the system used by file watching (see ) and process retries + /// (see ) to move to new processes. + /// When is access to this system useful? Consider the situation where your application uses file watching. + /// If your application knows when files change (e.g. your application is the actor changing files) you can manually invoke this method instead of using file + /// watching. This enables you to avoid the overhead of file watching. + /// The method respects . + /// + void MoveToNewProcess(); } } diff --git a/src/NodeJS/NodeJSServiceImplementations/OutOfProcess/OutOfProcessNodeJSService.cs b/src/NodeJS/NodeJSServiceImplementations/OutOfProcess/OutOfProcessNodeJSService.cs index fab9dcc..6a58760 100644 --- a/src/NodeJS/NodeJSServiceImplementations/OutOfProcess/OutOfProcessNodeJSService.cs +++ b/src/NodeJS/NodeJSServiceImplementations/OutOfProcess/OutOfProcessNodeJSService.cs @@ -230,6 +230,12 @@ public virtual async Task TryInvokeFromCacheAsync(string moduleCacheIdenti return (await TryInvokeFromCacheAsync(moduleCacheIdentifier, exportName, args, cancellationToken).ConfigureAwait(false)).Item1; } + /// + public virtual void MoveToNewProcess() + { + MoveToNewProcess(true); + } + internal virtual async Task<(bool, T?)> TryInvokeCoreAsync(InvocationRequest invocationRequest, CancellationToken cancellationToken) { if (_disposed) diff --git a/src/NodeJS/NodeJSServiceImplementations/OutOfProcess/OutOfProcessNodeJSServiceOptions.cs b/src/NodeJS/NodeJSServiceImplementations/OutOfProcess/OutOfProcessNodeJSServiceOptions.cs index 0a4844a..0407b16 100644 --- a/src/NodeJS/NodeJSServiceImplementations/OutOfProcess/OutOfProcessNodeJSServiceOptions.cs +++ b/src/NodeJS/NodeJSServiceImplementations/OutOfProcess/OutOfProcessNodeJSServiceOptions.cs @@ -98,7 +98,7 @@ public class OutOfProcessNodeJSServiceOptions /// public IEnumerable WatchFileNamePatterns { get; set; } = new[] { "*.js", "*.jsx", "*.ts", "*.tsx", "*.json", "*.html" }; - /// The value specifying whether NodeJS processes shutdown gracefully when a file changes or an invocation is retried in a new process. + /// The value specifying whether NodeJS processes shutdown gracefully when moving to a new process. /// /// If this value is true, NodeJS processes shutdown gracefully. Otherwise they're killed immediately. /// What's a graceful shutdown? When the library creates a new NodeJS process, the old NodeJS process diff --git a/test/NodeJS/HttpNodeJSServiceIntegrationTests.cs b/test/NodeJS/HttpNodeJSServiceIntegrationTests.cs index 346fbd9..c7cbc6c 100644 --- a/test/NodeJS/HttpNodeJSServiceIntegrationTests.cs +++ b/test/NodeJS/HttpNodeJSServiceIntegrationTests.cs @@ -1141,6 +1141,25 @@ public async void FileWatching_RespectsGracefulShutdownOptionWhenItsFalse() Assert.Equal(newProcessID1, newProcessID2); // Long running invocation should complete in new process } + [Fact(Timeout = TIMEOUT_MS)] + public async void MoveToNewProcess_MovesToNewProcess() + { + // Arrange + // Create initial module + const string dummyModule = @"module.exports = (callback) => callback(null, process.pid)"; + HttpNodeJSService testSubject = CreateHttpNodeJSService(); + + // Act + int initialProcessID = await testSubject.InvokeFromStringAsync(dummyModule).ConfigureAwait(false); + var initialProcess = Process.GetProcessById(initialProcessID); // Create Process instance for initial process so we can verify that it gets killed + testSubject.MoveToNewProcess(); + initialProcess.WaitForExit(); // If we don't successfully shift to a new process, test will timeout. + int newProcessID = await testSubject.InvokeFromStringAsync(dummyModule).ConfigureAwait(false); + + // Assert + Assert.NotEqual(initialProcessID, newProcessID); // Long running invocation should complete in new process + } + [Fact(Timeout = TIMEOUT_MS)] public async void NewProcessRetries_RetriesFailedInvocationInNewProcess() {