Skip to content

Add move to new process #122

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 15 additions & 1 deletion ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
<!-- INodeJSService generated docs -->
<!-- NodeJSProcessOptions generated docs -->

Expand Down Expand Up @@ -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; }
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public partial class HttpNodeJSPoolService
ImmutableArray<ISymbol> memberSymbols = interfaceSymbol.GetMembers();
foreach (ISymbol memberSymbol in memberSymbols)
{
if (cancellationToken.IsCancellationRequested)
if (cancellationToken.IsCancellationRequested || memberSymbol is not IMethodSymbol methodSymbol)
{
return;
}
Expand All @@ -112,10 +112,18 @@ public partial class HttpNodeJSPoolService
AppendLine().
AppendLine(" /// <inheritdoc />").
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(@";
}");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,16 @@ public static partial class StaticNodeJSService
ImmutableArray<ISymbol> 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();
Expand All @@ -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(@";
}");
}
Expand Down
11 changes: 11 additions & 0 deletions src/NodeJS/INodeJSService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -309,5 +309,16 @@ public interface INodeJSService : IDisposable
/// <exception cref="ObjectDisposedException">Thrown if this instance is disposed or if it attempts to use a disposed dependency.</exception>
/// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken"/> is cancelled.</exception>
Task<bool> TryInvokeFromCacheAsync(string cacheIdentifier, string? exportName = null, object?[]? args = null, CancellationToken cancellationToken = default);

/// <summary>Moves subsequent invocations to a new NodeJS process.</summary>
/// <remarks>
/// <para>This method exposes the system used by file watching (see <see cref="OutOfProcessNodeJSServiceOptions.EnableFileWatching"/>) and process retries
/// (see <see cref="OutOfProcessNodeJSServiceOptions.NumProcessRetries"/>) to move to new processes.</para>
/// <para>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.</para>
/// <para>The method respects <see cref="OutOfProcessNodeJSServiceOptions.GracefulProcessShutdown"/>.</para>
/// </remarks>
void MoveToNewProcess();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,12 @@ public virtual async Task<bool> TryInvokeFromCacheAsync(string moduleCacheIdenti
return (await TryInvokeFromCacheAsync<Void>(moduleCacheIdentifier, exportName, args, cancellationToken).ConfigureAwait(false)).Item1;
}

/// <inheritdoc />
public virtual void MoveToNewProcess()
{
MoveToNewProcess(true);
}

internal virtual async Task<(bool, T?)> TryInvokeCoreAsync<T>(InvocationRequest invocationRequest, CancellationToken cancellationToken)
{
if (_disposed)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public class OutOfProcessNodeJSServiceOptions
/// </remarks>
public IEnumerable<string> WatchFileNamePatterns { get; set; } = new[] { "*.js", "*.jsx", "*.ts", "*.tsx", "*.json", "*.html" };

/// <summary>The value specifying whether NodeJS processes shutdown gracefully when a file changes or an invocation is retried in a new process.</summary>
/// <summary>The value specifying whether NodeJS processes shutdown gracefully when moving to a new process.</summary>
/// <remarks>
/// <para>If this value is true, NodeJS processes shutdown gracefully. Otherwise they're killed immediately.</para>
/// <para>What's a graceful shutdown? When the library creates a new NodeJS process, the old NodeJS process
Expand Down
19 changes: 19 additions & 0 deletions test/NodeJS/HttpNodeJSServiceIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>(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<int>(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()
{
Expand Down