Skip to content

Commit be13f0b

Browse files
Centralise the child-process-terminating logic in NodeServicesImpl - don't also do it in OutOfProcessNodeInstance. This works towards connection draining.
1 parent 26e8bd8 commit be13f0b

File tree

1 file changed

+21
-7
lines changed

1 file changed

+21
-7
lines changed

src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public abstract class OutOfProcessNodeInstance : INodeInstance
2323
private bool _disposed;
2424
private readonly StringAsTempFile _entryPointScript;
2525
private readonly Process _nodeProcess;
26+
private bool _nodeProcessNeedsRestart;
2627

2728
public OutOfProcessNodeInstance(string entryPointScript, string projectPath, string commandLineArguments = null)
2829
{
@@ -33,16 +34,19 @@ public OutOfProcessNodeInstance(string entryPointScript, string projectPath, str
3334

3435
public async Task<T> InvokeExportAsync<T>(string moduleName, string exportNameOrNull, params object[] args)
3536
{
36-
// Wait until the connection is established. This will throw if the connection fails to initialize.
37-
await _connectionIsReadySource.Task;
38-
39-
if (_nodeProcess.HasExited)
37+
if (_nodeProcess.HasExited || _nodeProcessNeedsRestart)
4038
{
4139
// This special kind of exception triggers a transparent retry - NodeServicesImpl will launch
4240
// a new Node instance and pass the invocation to that one instead.
43-
throw new NodeInvocationException("The Node process has exited", null, nodeInstanceUnavailable: true);
41+
var message = _nodeProcess.HasExited
42+
? "The Node process has exited"
43+
: "The Node process needs to restart";
44+
throw new NodeInvocationException(message, null, nodeInstanceUnavailable: true);
4445
}
4546

47+
// Wait until the connection is established. This will throw if the connection fails to initialize.
48+
await _connectionIsReadySource.Task;
49+
4650
return await InvokeExportAsync<T>(new NodeInvocationInfo
4751
{
4852
ModuleName = moduleName,
@@ -115,7 +119,17 @@ private static Process LaunchNodeProcess(string entryPointFilename, string proje
115119
startInfo.Environment["NODE_PATH"] = nodePathValue;
116120
#endif
117121

118-
return Process.Start(startInfo);
122+
var process = Process.Start(startInfo);
123+
124+
// On Mac at least, a killed child process is left open as a zombie until the parent
125+
// captures its exit code. We don't need the exit code for this process, and don't want
126+
// to use process.WaitForExit() explicitly (we'd have to block the thread until it really
127+
// has exited), but we don't want to leave zombies lying around either. It's sufficient
128+
// to use process.EnableRaisingEvents so that .NET will grab the exit code and let the
129+
// zombie be cleaned away without having to block our thread.
130+
process.EnableRaisingEvents = true;
131+
132+
return process;
119133
}
120134

121135
private void ConnectToInputOutputStreams()
@@ -134,7 +148,7 @@ private void ConnectToInputOutputStreams()
134148
// Temporarily, the file-watching logic is in Node, so look out for the
135149
// signal that we need to restart. This can be removed once the file-watching
136150
// logic is moved over to the .NET side.
137-
Dispose();
151+
_nodeProcessNeedsRestart = true;
138152
}
139153
else if (evt.Data != null)
140154
{

0 commit comments

Comments
 (0)