@@ -23,6 +23,7 @@ public abstract class OutOfProcessNodeInstance : INodeInstance
23
23
private bool _disposed ;
24
24
private readonly StringAsTempFile _entryPointScript ;
25
25
private readonly Process _nodeProcess ;
26
+ private bool _nodeProcessNeedsRestart ;
26
27
27
28
public OutOfProcessNodeInstance ( string entryPointScript , string projectPath , string commandLineArguments = null )
28
29
{
@@ -33,16 +34,19 @@ public OutOfProcessNodeInstance(string entryPointScript, string projectPath, str
33
34
34
35
public async Task < T > InvokeExportAsync < T > ( string moduleName , string exportNameOrNull , params object [ ] args )
35
36
{
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 )
40
38
{
41
39
// This special kind of exception triggers a transparent retry - NodeServicesImpl will launch
42
40
// 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 ) ;
44
45
}
45
46
47
+ // Wait until the connection is established. This will throw if the connection fails to initialize.
48
+ await _connectionIsReadySource . Task ;
49
+
46
50
return await InvokeExportAsync < T > ( new NodeInvocationInfo
47
51
{
48
52
ModuleName = moduleName ,
@@ -115,7 +119,17 @@ private static Process LaunchNodeProcess(string entryPointFilename, string proje
115
119
startInfo . Environment [ "NODE_PATH" ] = nodePathValue ;
116
120
#endif
117
121
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 ;
119
133
}
120
134
121
135
private void ConnectToInputOutputStreams ( )
@@ -134,7 +148,7 @@ private void ConnectToInputOutputStreams()
134
148
// Temporarily, the file-watching logic is in Node, so look out for the
135
149
// signal that we need to restart. This can be removed once the file-watching
136
150
// logic is moved over to the .NET side.
137
- Dispose ( ) ;
151
+ _nodeProcessNeedsRestart = true ;
138
152
}
139
153
else if ( evt . Data != null )
140
154
{
0 commit comments