Repository navigation
Expand file tree
/
Copy pathOutOfProcessNodeInstance.cs
More file actions
152 lines (128 loc) · 6.03 KB
/
Copy pathOutOfProcessNodeInstance.cs
File metadata and controls
152 lines (128 loc) · 6.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.NodeServices {
/**
* Class responsible for launching the Node child process, determining when it is ready to accept invocations,
* and finally killing it when the parent process exits. Also it restarts the child process if it dies.
*/
public abstract class OutOfProcessNodeInstance : INodeServices {
private object _childProcessLauncherLock;
private bool disposed;
private StringAsTempFile _entryPointScript;
private string _projectPath;
private string _commandLineArguments;
private Process _nodeProcess;
private TaskCompletionSource<bool> _nodeProcessIsReadySource;
protected Process NodeProcess {
get {
// This is only exposed to support the unreliable OutOfProcessNodeRunner, which is just to verify that
// other hosting/transport mechanisms are possible. This shouldn't really be exposed.
return this._nodeProcess;
}
}
public OutOfProcessNodeInstance(string entryPointScript, string projectPath, string commandLineArguments = null)
{
this._childProcessLauncherLock = new object();
this._entryPointScript = new StringAsTempFile(entryPointScript);
this._projectPath = projectPath;
this._commandLineArguments = commandLineArguments ?? string.Empty;
}
public abstract Task<T> Invoke<T>(NodeInvocationInfo invocationInfo);
public Task<T> Invoke<T>(string moduleName, params object[] args) {
return this.InvokeExport<T>(moduleName, null, args);
}
public async Task<T> InvokeExport<T>(string moduleName, string exportedFunctionName, params object[] args) {
return await this.Invoke<T>(new NodeInvocationInfo {
ModuleName = moduleName,
ExportedFunctionName = exportedFunctionName,
Args = args
});
}
protected async Task EnsureReady() {
lock (this._childProcessLauncherLock) {
if (this._nodeProcess == null || this._nodeProcess.HasExited) {
var startInfo = new ProcessStartInfo("node") {
Arguments = "\"" + this._entryPointScript.FileName + "\" " + this._commandLineArguments,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
WorkingDirectory = this._projectPath
};
// Append projectPath to NODE_PATH so it can locate node_modules
var existingNodePath = Environment.GetEnvironmentVariable("NODE_PATH") ?? string.Empty;
if (existingNodePath != string.Empty) {
existingNodePath += ":";
}
var nodePathValue = existingNodePath + Path.Combine(this._projectPath, "node_modules");
#if NET451
startInfo.EnvironmentVariables.Add("NODE_PATH", nodePathValue);
#else
startInfo.Environment.Add("NODE_PATH", nodePathValue);
#endif
this.OnBeforeLaunchProcess();
this._nodeProcess = Process.Start(startInfo);
this.ConnectToInputOutputStreams();
}
}
var task = this._nodeProcessIsReadySource.Task;
var initializationSucceeded = await task;
if (!initializationSucceeded) {
throw new InvalidOperationException("The Node.js process failed to initialize", task.Exception);
}
}
private void ConnectToInputOutputStreams() {
var initializationIsCompleted = false; // TODO: Make this thread-safe? (Interlocked.Exchange etc.)
this._nodeProcessIsReadySource = new TaskCompletionSource<bool>();
this._nodeProcess.OutputDataReceived += (sender, evt) => {
if (evt.Data == "[Microsoft.AspNetCore.NodeServices:Listening]" && !initializationIsCompleted) {
this._nodeProcessIsReadySource.SetResult(true);
initializationIsCompleted = true;
} else if (evt.Data != null) {
this.OnOutputDataReceived(evt.Data);
}
};
this._nodeProcess.ErrorDataReceived += (sender, evt) => {
if (evt.Data != null) {
this.OnErrorDataReceived(evt.Data);
if (!initializationIsCompleted) {
this._nodeProcessIsReadySource.SetResult(false);
initializationIsCompleted = true;
}
}
};
this._nodeProcess.BeginOutputReadLine();
this._nodeProcess.BeginErrorReadLine();
}
protected virtual void OnBeforeLaunchProcess() {
}
protected virtual void OnOutputDataReceived(string outputData) {
Console.WriteLine("[Node] " + outputData);
}
protected virtual void OnErrorDataReceived(string errorData) {
Console.WriteLine("[Node] " + errorData);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposed) {
if (disposing) {
this._entryPointScript.Dispose();
}
if (this._nodeProcess != null && !this._nodeProcess.HasExited) {
this._nodeProcess.Kill(); // TODO: Is there a more graceful way to end it? Or does this still let it perform any cleanup?
}
disposed = true;
}
}
~OutOfProcessNodeInstance() {
Dispose (false);
}
}
}