Sandbox: Start aus einem langlebigen Thread - das Elternsterbe-Signal haengt am Thread (#654)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
6204f1247c
commit
f97dfa06d6
@@ -219,7 +219,7 @@ public sealed class BwrapSandbox : ISandboxRunner
|
|||||||
|
|
||||||
private async Task<SandboxResult> RunProcessAsync(ProcessStartInfo start, SandboxRequest request, CancellationToken ct)
|
private async Task<SandboxResult> RunProcessAsync(ProcessStartInfo start, SandboxRequest request, CancellationToken ct)
|
||||||
{
|
{
|
||||||
using var process = Process.Start(start) ?? throw new InvalidOperationException("The sandbox did not start.");
|
using var process = await Spawner.StartAsync(start);
|
||||||
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||||
timeout.CancelAfter(request.Limits.Timeout);
|
timeout.CancelAfter(request.Limits.Timeout);
|
||||||
|
|
||||||
@@ -411,6 +411,52 @@ public sealed class BwrapSandbox : ISandboxRunner
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// #654: Starts every sandbox from one thread that lives as long as the process.
|
||||||
|
///
|
||||||
|
/// <para><b>Why.</b> <c>--die-with-parent</c> uses the kernel's parent-death signal, and that
|
||||||
|
/// is bound to the <i>thread</i> that forked, not to the process. .NET forks on the calling
|
||||||
|
/// thread — a thread-pool thread, and the pool retires idle threads. Seen on the test machine
|
||||||
|
/// under load: the pool dropped a thread, bwrap got SIGKILL (exit 137), and when it happened
|
||||||
|
/// while bwrap was still building the sandbox, the inside lived on as an orphan holding the
|
||||||
|
/// output pipes. Started from a thread that never ends, the signal fires only when the
|
||||||
|
/// instance itself dies — which is what it is for.</para>
|
||||||
|
/// </summary>
|
||||||
|
internal static class Spawner
|
||||||
|
{
|
||||||
|
private static readonly System.Collections.Concurrent.BlockingCollection<(ProcessStartInfo Start, TaskCompletionSource<Process> Done)> Queue = new();
|
||||||
|
|
||||||
|
static Spawner()
|
||||||
|
{
|
||||||
|
var thread = new Thread(() =>
|
||||||
|
{
|
||||||
|
foreach (var (start, done) in Queue.GetConsumingEnumerable())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
done.SetResult(Process.Start(start) ?? throw new InvalidOperationException("The sandbox did not start."));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
done.SetException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
IsBackground = true,
|
||||||
|
Name = "ClawdDotNet sandbox spawner"
|
||||||
|
};
|
||||||
|
thread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task<Process> StartAsync(ProcessStartInfo start)
|
||||||
|
{
|
||||||
|
var done = new TaskCompletionSource<Process>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
Queue.Add((start, done));
|
||||||
|
return done.Task;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>#654: What the installed bwrap can do beyond the basics.</summary>
|
/// <summary>#654: What the installed bwrap can do beyond the basics.</summary>
|
||||||
public sealed record BwrapFeatures(bool DisableUserns, bool ClearEnv)
|
public sealed record BwrapFeatures(bool DisableUserns, bool ClearEnv)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user