Merge nucleic/lucid-river-toad-6efj into dev
This commit is contained in:
@@ -58,8 +58,11 @@ public sealed class BrokerService : IBrokerEvents
|
||||
|
||||
try
|
||||
{
|
||||
var result = await DispatchAsync(method, @params, ct).ConfigureAwait(false);
|
||||
if (id is { } requestId)
|
||||
var result = await DispatchAsync(method, @params, id, ct).ConfigureAwait(false);
|
||||
// A null result means the handler already enqueued its own response because it had to
|
||||
// send it before doing something else (proc.exec — see below). Everything else
|
||||
// returns a value and is responded to here.
|
||||
if (result is not null && id is { } requestId)
|
||||
outbound.EnqueueJson(Rpc.Response(requestId, result));
|
||||
}
|
||||
catch (JsonException e)
|
||||
@@ -80,7 +83,10 @@ public sealed class BrokerService : IBrokerEvents
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<object> DispatchAsync(string method, JsonElement? p, CancellationToken ct)
|
||||
/// <summary>Returns the result to respond with, or null when the handler has already
|
||||
/// responded (it needed the response on the wire before continuing).</summary>
|
||||
private async Task<object?> DispatchAsync(
|
||||
string method, JsonElement? p, JsonElement? id, CancellationToken ct)
|
||||
{
|
||||
switch (method)
|
||||
{
|
||||
@@ -176,7 +182,32 @@ public sealed class BrokerService : IBrokerEvents
|
||||
var procId = Interlocked.Increment(ref nextProcId);
|
||||
var proc = await wslc.ExecAsync(procId, spec, ct).ConfigureAwait(false);
|
||||
lock (procsLock) procs[procId] = proc;
|
||||
return new { procId };
|
||||
// Respond BEFORE running it. Output and exit are enqueued on the same ordered
|
||||
// outbound queue as this response, so a command that finishes fast (`echo`) would
|
||||
// otherwise put `proc.exit` on the wire ahead of the `procId` naming it — and a
|
||||
// client that registers interest when it learns the procId then waits forever.
|
||||
// Hit on the first live run, hardware-confirmed (docs/WINDOWS_PORT.md §13.3).
|
||||
if (id is { } requestId)
|
||||
outbound.EnqueueJson(Rpc.Response(requestId, new { procId }));
|
||||
|
||||
try
|
||||
{
|
||||
await proc.StartAsync(ct).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// The response is already on the wire, so this failure CANNOT be reported as a
|
||||
// JSON-RPC error — that would put two responses under one id, which is a
|
||||
// protocol violation and left the client waiting for an exit that never came.
|
||||
// Report it the way the process itself would have: the reason on stderr, then
|
||||
// an exit. 126 is the shell's "command found but not executable", which is
|
||||
// what "could not start" means to every caller above.
|
||||
lock (procsLock) procs.Remove(procId);
|
||||
var reason = $"nucleic-brokerd: could not start process: {e.Message}\n";
|
||||
ProcOutput(procId, stderr: true, System.Text.Encoding.UTF8.GetBytes(reason));
|
||||
ProcExited(procId, 126);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case "proc.stdin":
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user