arcane-os 0.54.1 → 0.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/README.md +35 -28
- package/browser-runtime/core/client.mjs +552 -0
- package/browser-runtime/core/contracts.mjs +85 -0
- package/docs/architecture.md +31 -23
- package/docs/compatibility.md +10 -1
- package/docs/consumer-updates.md +82 -0
- package/docs/platform-targets.md +20 -11
- package/docs/publishing.md +41 -18
- package/docs/reference/README.md +19 -7
- package/docs/reference/availability-and-normalization.md +15 -5
- package/docs/reference/behavioral-testing.md +7 -6
- package/docs/reference/cli.md +12 -4
- package/docs/reference/core-client.md +163 -0
- package/docs/reference/core-native-packaging.md +149 -0
- package/docs/reference/core-runtime.md +185 -0
- package/docs/reference/inventory/package-api.json +271 -5
- package/docs/reference/protocols.md +38 -14
- package/docs/reference/pwa.md +17 -0
- package/docs/reference/sdk-api.md +772 -15
- package/package.json +10 -3
- package/schemas/arcane-app.schema.json +10 -0
- package/schemas/arcane-package.schema.json +10 -0
- package/schemas/native-build-plan.schema.json +8 -0
- package/src/app-descriptor.mjs +18 -1
- package/src/app-layout.mjs +6 -2
- package/src/cli/main.mjs +8 -6
- package/src/core/host.mjs +23 -0
- package/src/core/hosts/arcane-core-loader.cjs +2 -0
- package/src/core/hosts/linux/arcane_core_process.c +568 -0
- package/src/core/hosts/linux/arcane_core_process.h +127 -0
- package/src/core/hosts/sea-launcher.cjs +17 -0
- package/src/core/hosts/windows/ArcaneCoreProcess.cs +416 -0
- package/src/core/runtime.mjs +345 -0
- package/src/core/stdio.mjs +209 -0
- package/src/core-classic-source.mjs +43 -0
- package/src/import-map.mjs +62 -0
- package/src/native/portable-layout.mjs +184 -0
- package/src/native/portable-provider.mjs +211 -0
- package/src/native-plan.mjs +10 -4
- package/src/native-provider-loader.mjs +15 -4
- package/src/packager/core.mjs +131 -32
- package/src/process.mjs +8 -4
- package/src/targets/index.mjs +3 -1
- package/src/toolchain.mjs +23 -6
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const {createRequire} = require('node:module');
|
|
2
|
+
const {dirname, join} = require('node:path');
|
|
3
|
+
|
|
4
|
+
// Filesystem CommonJS owns dynamic import of the unchanged ESM service graph.
|
|
5
|
+
// The embedded SEA entry itself can load only built-in modules.
|
|
6
|
+
const load = createRequire(process.execPath);
|
|
7
|
+
try {
|
|
8
|
+
Promise.resolve(load(join(dirname(process.execPath), 'arcane-core-loader.cjs')))
|
|
9
|
+
.catch(reportStartupFailure);
|
|
10
|
+
} catch (error) {
|
|
11
|
+
reportStartupFailure(error);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function reportStartupFailure(error) {
|
|
15
|
+
console.error('Arcane Core startup failed:', error);
|
|
16
|
+
process.exitCode = 1;
|
|
17
|
+
}
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections.Generic;
|
|
3
|
+
using System.Diagnostics;
|
|
4
|
+
using System.Globalization;
|
|
5
|
+
using System.IO;
|
|
6
|
+
using System.Text;
|
|
7
|
+
using System.Threading;
|
|
8
|
+
using System.Threading.Tasks;
|
|
9
|
+
|
|
10
|
+
namespace Arcane.Core.Hosts.Windows
|
|
11
|
+
{
|
|
12
|
+
/// <summary>
|
|
13
|
+
/// Owns one caller-selected Core child and its complete framed transport.
|
|
14
|
+
/// Output callbacks run on background threads; errors can also be reported
|
|
15
|
+
/// during Start. Callbacks must not synchronously wait for CloseAsync or
|
|
16
|
+
/// Completion. CloseAsync drains; it never kills the child.
|
|
17
|
+
/// </summary>
|
|
18
|
+
public sealed class ArcaneCoreProcess
|
|
19
|
+
{
|
|
20
|
+
private static readonly Encoding Utf8 = new UTF8Encoding(false, true);
|
|
21
|
+
private static readonly Encoding Ascii = Encoding.GetEncoding(
|
|
22
|
+
"us-ascii", EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback);
|
|
23
|
+
private readonly object stateLock = new object();
|
|
24
|
+
private readonly Process process;
|
|
25
|
+
private readonly Action<string> onMessage;
|
|
26
|
+
private readonly Action<string> onDiagnostic;
|
|
27
|
+
private readonly Action<Exception> onError;
|
|
28
|
+
private readonly List<Exception> failures = new List<Exception>();
|
|
29
|
+
private readonly TaskCompletionSource<int> exited = new TaskCompletionSource<int>();
|
|
30
|
+
private readonly TaskCompletionSource<object> completion = new TaskCompletionSource<object>();
|
|
31
|
+
private Task writes = Task.FromResult<object>(null);
|
|
32
|
+
private Task inputClosing;
|
|
33
|
+
private Task outputReader;
|
|
34
|
+
private Task diagnosticReader;
|
|
35
|
+
private Task lifetime;
|
|
36
|
+
private bool accepting = true;
|
|
37
|
+
private bool closeRequested;
|
|
38
|
+
private int exitObserved;
|
|
39
|
+
private int inputClosed;
|
|
40
|
+
|
|
41
|
+
private ArcaneCoreProcess(ProcessStartInfo startInfo, Action<string> onMessage,
|
|
42
|
+
Action<string> onDiagnostic, Action<Exception> onError)
|
|
43
|
+
{
|
|
44
|
+
this.onMessage = onMessage;
|
|
45
|
+
this.onDiagnostic = onDiagnostic;
|
|
46
|
+
this.onError = onError;
|
|
47
|
+
// Observing the fault internally does not change the faulted task
|
|
48
|
+
// returned to callers; all errors are also retained and reported.
|
|
49
|
+
completion.Task.ContinueWith(
|
|
50
|
+
failed => failed.Exception.Handle(error => true), CancellationToken.None,
|
|
51
|
+
TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
|
|
52
|
+
process = new Process { StartInfo = startInfo, EnableRaisingEvents = true };
|
|
53
|
+
process.Exited += OnExited;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// <summary>
|
|
57
|
+
/// Starts the selected executable without shell execution or a visible
|
|
58
|
+
/// console. Arguments use the selected executable's command-line syntax.
|
|
59
|
+
/// Diagnostic text is decoded as UTF-8 without changing line endings.
|
|
60
|
+
/// </summary>
|
|
61
|
+
public static ArcaneCoreProcess Start(string executable, string arguments,
|
|
62
|
+
string workingDirectory, Action<string> onMessage,
|
|
63
|
+
Action<string> onDiagnostic, Action<Exception> onError = null)
|
|
64
|
+
{
|
|
65
|
+
if (String.IsNullOrWhiteSpace(executable)) throw new ArgumentException("Select a Core executable.", "executable");
|
|
66
|
+
if (String.IsNullOrWhiteSpace(workingDirectory)) throw new ArgumentException("Select a Core working directory.", "workingDirectory");
|
|
67
|
+
if (onMessage == null) throw new ArgumentNullException("onMessage");
|
|
68
|
+
if (onDiagnostic == null) throw new ArgumentNullException("onDiagnostic");
|
|
69
|
+
ProcessStartInfo startInfo = new ProcessStartInfo
|
|
70
|
+
{
|
|
71
|
+
FileName = executable,
|
|
72
|
+
Arguments = arguments ?? String.Empty,
|
|
73
|
+
WorkingDirectory = workingDirectory,
|
|
74
|
+
UseShellExecute = false,
|
|
75
|
+
CreateNoWindow = true,
|
|
76
|
+
WindowStyle = ProcessWindowStyle.Hidden,
|
|
77
|
+
RedirectStandardInput = true,
|
|
78
|
+
RedirectStandardOutput = true,
|
|
79
|
+
RedirectStandardError = true
|
|
80
|
+
};
|
|
81
|
+
ArcaneCoreProcess host = new ArcaneCoreProcess(startInfo, onMessage, onDiagnostic, onError);
|
|
82
|
+
try
|
|
83
|
+
{
|
|
84
|
+
if (!host.process.Start()) throw new IOException("The selected Core process did not start.");
|
|
85
|
+
}
|
|
86
|
+
catch (Exception error)
|
|
87
|
+
{
|
|
88
|
+
host.Report(error);
|
|
89
|
+
host.process.Exited -= host.OnExited;
|
|
90
|
+
try { host.process.Dispose(); }
|
|
91
|
+
catch (Exception disposalError) { host.Report(disposalError); }
|
|
92
|
+
AggregateException failure = new AggregateException("Core process startup failed.", host.SnapshotFailures());
|
|
93
|
+
host.completion.TrySetException(failure);
|
|
94
|
+
throw failure;
|
|
95
|
+
}
|
|
96
|
+
// All callbacks and exit observation exist before either reader starts.
|
|
97
|
+
host.outputReader = Task.Run(host.ReadOutputAsync);
|
|
98
|
+
host.diagnosticReader = Task.Run(host.ReadDiagnosticsAsync);
|
|
99
|
+
host.lifetime = host.CompleteAsync();
|
|
100
|
+
host.lifetime.ContinueWith(host.ObserveLifetimeFailure, CancellationToken.None,
|
|
101
|
+
TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
|
|
102
|
+
return host;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// <summary>
|
|
106
|
+
/// Settles only after child exit, accepted writes, both output readers
|
|
107
|
+
/// and resource disposal. Every transport/callback failure is retained.
|
|
108
|
+
/// </summary>
|
|
109
|
+
public Task Completion { get { return completion.Task; } }
|
|
110
|
+
|
|
111
|
+
/// <summary>Writes this exact JSON string after earlier accepted sends.</summary>
|
|
112
|
+
public Task SendAsync(string json)
|
|
113
|
+
{
|
|
114
|
+
if (json == null) throw new ArgumentNullException("json");
|
|
115
|
+
lock (stateLock)
|
|
116
|
+
{
|
|
117
|
+
if (!accepting)
|
|
118
|
+
{
|
|
119
|
+
TaskCompletionSource<object> rejected = new TaskCompletionSource<object>();
|
|
120
|
+
rejected.SetException(new InvalidOperationException("The Core transport is closing."));
|
|
121
|
+
return rejected.Task;
|
|
122
|
+
}
|
|
123
|
+
writes = writes.ContinueWith(
|
|
124
|
+
previous => WriteAfterAsync(previous, json), CancellationToken.None,
|
|
125
|
+
TaskContinuationOptions.None, TaskScheduler.Default).Unwrap();
|
|
126
|
+
return writes;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public Task CloseAsync()
|
|
131
|
+
{
|
|
132
|
+
lock (stateLock) closeRequested = true;
|
|
133
|
+
BeginInputClose();
|
|
134
|
+
return Completion;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private async Task WriteAfterAsync(Task previous, string json)
|
|
138
|
+
{
|
|
139
|
+
// A failed earlier write also faults later accepted sends; it cannot
|
|
140
|
+
// leave a caller waiting on a request that was never transmitted.
|
|
141
|
+
await previous.ConfigureAwait(false);
|
|
142
|
+
try
|
|
143
|
+
{
|
|
144
|
+
if (Volatile.Read(ref exitObserved) != 0) throw new IOException("Core exited before the accepted write completed.");
|
|
145
|
+
byte[] body = Utf8.GetBytes(json);
|
|
146
|
+
// Length exists only in the Content-Length transport frame.
|
|
147
|
+
byte[] header = Ascii.GetBytes("Content-Length: " + body.Length.ToString(CultureInfo.InvariantCulture) + "\r\n\r\n");
|
|
148
|
+
Stream input = process.StandardInput.BaseStream;
|
|
149
|
+
await input.WriteAsync(header, 0, header.Length).ConfigureAwait(false);
|
|
150
|
+
await input.WriteAsync(body, 0, body.Length).ConfigureAwait(false);
|
|
151
|
+
await input.FlushAsync().ConfigureAwait(false);
|
|
152
|
+
}
|
|
153
|
+
catch (Exception error)
|
|
154
|
+
{
|
|
155
|
+
Report(error);
|
|
156
|
+
BeginInputClose();
|
|
157
|
+
throw;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private Task BeginInputClose()
|
|
162
|
+
{
|
|
163
|
+
lock (stateLock)
|
|
164
|
+
{
|
|
165
|
+
if (inputClosing != null) return inputClosing;
|
|
166
|
+
accepting = false;
|
|
167
|
+
inputClosing = writes.ContinueWith(
|
|
168
|
+
CloseInputAfterWritesAsync, CancellationToken.None,
|
|
169
|
+
TaskContinuationOptions.None, TaskScheduler.Default).Unwrap();
|
|
170
|
+
return inputClosing;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private async Task CloseInputAfterWritesAsync(Task acceptedWrites)
|
|
175
|
+
{
|
|
176
|
+
try { await acceptedWrites.ConfigureAwait(false); }
|
|
177
|
+
catch { /* The originating write already reported the complete error. */ }
|
|
178
|
+
CloseInputStream();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private void CloseInputStream()
|
|
182
|
+
{
|
|
183
|
+
if (Interlocked.Exchange(ref inputClosed, 1) != 0) return;
|
|
184
|
+
try { process.StandardInput.BaseStream.Close(); }
|
|
185
|
+
catch (Exception error) { Report(error); }
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private void OnExited(object sender, EventArgs args)
|
|
189
|
+
{
|
|
190
|
+
if (Interlocked.Exchange(ref exitObserved, 1) != 0) return;
|
|
191
|
+
bool requested;
|
|
192
|
+
lock (stateLock) requested = closeRequested;
|
|
193
|
+
int exitCode = 0;
|
|
194
|
+
try
|
|
195
|
+
{
|
|
196
|
+
exitCode = process.ExitCode;
|
|
197
|
+
if (!requested || exitCode != 0)
|
|
198
|
+
Report(new IOException("Core process exited " + (requested ? "during shutdown" : "unexpectedly")
|
|
199
|
+
+ " with exit code " + exitCode.ToString(CultureInfo.InvariantCulture) + "."));
|
|
200
|
+
}
|
|
201
|
+
catch (Exception error) { Report(error); }
|
|
202
|
+
BeginInputClose();
|
|
203
|
+
// A dead child cannot accept the remaining writes. Closing its pipe
|
|
204
|
+
// unblocks an in-flight write; queued sends observe that same failure.
|
|
205
|
+
CloseInputStream();
|
|
206
|
+
exited.TrySetResult(exitCode);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private async Task ReadOutputAsync()
|
|
210
|
+
{
|
|
211
|
+
Stream output = null;
|
|
212
|
+
try
|
|
213
|
+
{
|
|
214
|
+
output = process.StandardOutput.BaseStream;
|
|
215
|
+
while (true)
|
|
216
|
+
{
|
|
217
|
+
string message = await ReadFrameAsync(output).ConfigureAwait(false);
|
|
218
|
+
if (message == null) break;
|
|
219
|
+
try { onMessage(message); }
|
|
220
|
+
catch (Exception error)
|
|
221
|
+
{
|
|
222
|
+
IOException callbackError = new IOException("The Core message callback failed.", error);
|
|
223
|
+
callbackError.Data["coreMessage"] = message;
|
|
224
|
+
Report(callbackError);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
BeginInputClose();
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
catch (Exception error)
|
|
231
|
+
{
|
|
232
|
+
Report(error);
|
|
233
|
+
BeginInputClose();
|
|
234
|
+
}
|
|
235
|
+
if (output != null) await DrainUnparsedAsync(output, "stdout").ConfigureAwait(false);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private async Task ReadDiagnosticsAsync()
|
|
239
|
+
{
|
|
240
|
+
Stream diagnostic = null;
|
|
241
|
+
byte[] input = new byte[4096];
|
|
242
|
+
byte[] pending = new byte[0];
|
|
243
|
+
try
|
|
244
|
+
{
|
|
245
|
+
diagnostic = process.StandardError.BaseStream;
|
|
246
|
+
while (true)
|
|
247
|
+
{
|
|
248
|
+
int read = await diagnostic.ReadAsync(input, 0, input.Length).ConfigureAwait(false);
|
|
249
|
+
byte[] combined = new byte[pending.Length + read];
|
|
250
|
+
Buffer.BlockCopy(pending, 0, combined, 0, pending.Length);
|
|
251
|
+
Buffer.BlockCopy(input, 0, combined, pending.Length, read);
|
|
252
|
+
pending = combined;
|
|
253
|
+
int complete = read == 0 ? combined.Length : CompleteUtf8Prefix(combined);
|
|
254
|
+
string text = Utf8.GetString(combined, 0, complete);
|
|
255
|
+
if (text.Length != 0)
|
|
256
|
+
{
|
|
257
|
+
try { onDiagnostic(text); }
|
|
258
|
+
catch (Exception error)
|
|
259
|
+
{
|
|
260
|
+
IOException callbackError = new IOException("The Core diagnostic callback failed.", error);
|
|
261
|
+
callbackError.Data["coreDiagnostic"] = text;
|
|
262
|
+
Report(callbackError);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
pending = new byte[combined.Length - complete];
|
|
266
|
+
Buffer.BlockCopy(combined, complete, pending, 0, pending.Length);
|
|
267
|
+
if (read == 0) break;
|
|
268
|
+
}
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
catch (Exception error)
|
|
272
|
+
{
|
|
273
|
+
error.Data["stderr"] = pending;
|
|
274
|
+
Report(error);
|
|
275
|
+
BeginInputClose();
|
|
276
|
+
}
|
|
277
|
+
if (diagnostic != null) await DrainUnparsedAsync(diagnostic, "stderr").ConfigureAwait(false);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private static int CompleteUtf8Prefix(byte[] input)
|
|
281
|
+
{
|
|
282
|
+
if (input.Length == 0) return 0;
|
|
283
|
+
int start = input.Length - 1;
|
|
284
|
+
while (start > 0 && (input[start] & 0xc0) == 0x80) start--;
|
|
285
|
+
int first = input[start];
|
|
286
|
+
int required = first < 0x80 ? 1 : first >= 0xc2 && first <= 0xdf ? 2
|
|
287
|
+
: first >= 0xe0 && first <= 0xef ? 3 : first >= 0xf0 && first <= 0xf4 ? 4 : 1;
|
|
288
|
+
return input.Length - start < required ? start : input.Length;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private async Task DrainUnparsedAsync(Stream stream, string channel)
|
|
292
|
+
{
|
|
293
|
+
byte[] buffer = new byte[4096];
|
|
294
|
+
try
|
|
295
|
+
{
|
|
296
|
+
int read;
|
|
297
|
+
while ((read = await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) != 0)
|
|
298
|
+
{
|
|
299
|
+
byte[] content = new byte[read];
|
|
300
|
+
Buffer.BlockCopy(buffer, 0, content, 0, read);
|
|
301
|
+
IOException diagnostic = new IOException("Unparsed Core " + channel + " is retained in this error's Data.");
|
|
302
|
+
diagnostic.Data[channel] = content;
|
|
303
|
+
Report(diagnostic);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
catch (Exception error) { Report(error); }
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private static async Task<string> ReadFrameAsync(Stream stream)
|
|
310
|
+
{
|
|
311
|
+
using (MemoryStream header = new MemoryStream())
|
|
312
|
+
using (MemoryStream body = new MemoryStream())
|
|
313
|
+
{
|
|
314
|
+
try
|
|
315
|
+
{
|
|
316
|
+
byte[] next = new byte[1];
|
|
317
|
+
byte[] separator = new byte[] { 13, 10, 13, 10 };
|
|
318
|
+
int matched = 0;
|
|
319
|
+
while (matched != separator.Length)
|
|
320
|
+
{
|
|
321
|
+
int read = await stream.ReadAsync(next, 0, 1).ConfigureAwait(false);
|
|
322
|
+
if (read == 0)
|
|
323
|
+
{
|
|
324
|
+
if (header.Length == 0) return null;
|
|
325
|
+
throw new EndOfStreamException("Core stdout ended during a frame header.");
|
|
326
|
+
}
|
|
327
|
+
header.WriteByte(next[0]);
|
|
328
|
+
matched = next[0] == separator[matched] ? matched + 1 : next[0] == 13 ? 1 : 0;
|
|
329
|
+
}
|
|
330
|
+
byte[] headerBytes = header.ToArray();
|
|
331
|
+
string headerText = Ascii.GetString(headerBytes, 0, headerBytes.Length - separator.Length);
|
|
332
|
+
long? contentLength = null;
|
|
333
|
+
foreach (string line in headerText.Split(new string[] { "\r\n" }, StringSplitOptions.None))
|
|
334
|
+
{
|
|
335
|
+
if (!line.StartsWith("Content-Length:", StringComparison.OrdinalIgnoreCase)) continue;
|
|
336
|
+
long length;
|
|
337
|
+
if (contentLength.HasValue || !Int64.TryParse(line.Substring(15).Trim(), NumberStyles.None,
|
|
338
|
+
CultureInfo.InvariantCulture, out length))
|
|
339
|
+
throw new InvalidDataException("Core frame Content-Length is invalid.");
|
|
340
|
+
contentLength = length;
|
|
341
|
+
}
|
|
342
|
+
if (!contentLength.HasValue) throw new InvalidDataException("Core frame Content-Length is missing.");
|
|
343
|
+
long remaining = contentLength.Value;
|
|
344
|
+
byte[] buffer = new byte[8192];
|
|
345
|
+
while (remaining != 0)
|
|
346
|
+
{
|
|
347
|
+
int read = await stream.ReadAsync(buffer, 0, (int)Math.Min(remaining, buffer.Length)).ConfigureAwait(false);
|
|
348
|
+
if (read == 0) throw new EndOfStreamException("Core stdout ended during a frame body.");
|
|
349
|
+
body.Write(buffer, 0, read);
|
|
350
|
+
remaining -= read;
|
|
351
|
+
}
|
|
352
|
+
return Utf8.GetString(body.ToArray());
|
|
353
|
+
}
|
|
354
|
+
catch (Exception error)
|
|
355
|
+
{
|
|
356
|
+
error.Data["frameHeader"] = header.ToArray();
|
|
357
|
+
error.Data["frameBody"] = body.ToArray();
|
|
358
|
+
throw;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
private async Task CompleteAsync()
|
|
364
|
+
{
|
|
365
|
+
try
|
|
366
|
+
{
|
|
367
|
+
await exited.Task.ConfigureAwait(false);
|
|
368
|
+
Task readers = Task.WhenAll(outputReader, diagnosticReader);
|
|
369
|
+
try { await readers.ConfigureAwait(false); }
|
|
370
|
+
catch
|
|
371
|
+
{
|
|
372
|
+
foreach (Exception error in readers.Exception.InnerExceptions) Report(error);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
catch (Exception error) { Report(error); }
|
|
376
|
+
try { await BeginInputClose().ConfigureAwait(false); }
|
|
377
|
+
catch (Exception error) { Report(error); }
|
|
378
|
+
finally
|
|
379
|
+
{
|
|
380
|
+
process.Exited -= OnExited;
|
|
381
|
+
try { process.StandardOutput.Close(); }
|
|
382
|
+
catch (Exception error) { Report(error); }
|
|
383
|
+
try { process.StandardError.Close(); }
|
|
384
|
+
catch (Exception error) { Report(error); }
|
|
385
|
+
try { process.Dispose(); }
|
|
386
|
+
catch (Exception error) { Report(error); }
|
|
387
|
+
Exception[] errors = SnapshotFailures();
|
|
388
|
+
if (errors.Length != 0)
|
|
389
|
+
completion.TrySetException(new AggregateException("Core process completed with errors.", errors));
|
|
390
|
+
else completion.TrySetResult(null);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
private void ObserveLifetimeFailure(Task failed)
|
|
395
|
+
{
|
|
396
|
+
foreach (Exception error in failed.Exception.InnerExceptions) Report(error);
|
|
397
|
+
completion.TrySetException(new AggregateException("Core lifetime observation failed.", SnapshotFailures()));
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
private void Report(Exception error)
|
|
401
|
+
{
|
|
402
|
+
lock (stateLock) failures.Add(error);
|
|
403
|
+
if (onError == null) return;
|
|
404
|
+
try { onError(error); }
|
|
405
|
+
catch (Exception callbackError)
|
|
406
|
+
{
|
|
407
|
+
lock (stateLock) failures.Add(callbackError);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private Exception[] SnapshotFailures()
|
|
412
|
+
{
|
|
413
|
+
lock (stateLock) return failures.ToArray();
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|