fix(inbound-api): defer DI-scope disposal to handler completion and count abandoned executions — closes the timeout use-after-dispose
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
@@ -728,6 +728,26 @@ public class InboundScriptExecutorTests
|
||||
return new SqliteGateway("DataSource=file:movein-exec?mode=memory&cache=shared");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TimedOutScript_ScopeDisposalDeferred_NoOrphanLeak()
|
||||
{
|
||||
// Handler ignores the token and keeps running past the method timeout.
|
||||
var release = new TaskCompletionSource();
|
||||
_executor.RegisterHandler("slow", async _ => { await release.Task; return 1; });
|
||||
var method = new ApiMethod("slow", "unused") { Id = 9, TimeoutSeconds = 1 };
|
||||
|
||||
var result = await _executor.ExecuteAsync(
|
||||
method, new Dictionary<string, object?>(), _route, TimeSpan.FromMilliseconds(50));
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Contains("timed out", result.ErrorMessage);
|
||||
Assert.Equal(1, _executor.AbandonedExecutionCount); // orphan is COUNTED
|
||||
|
||||
release.SetResult(); // orphan finishes; continuation cleans up
|
||||
await Task.Delay(200);
|
||||
Assert.Equal(0, _executor.AbandonedExecutionCount); // ...and DECREMENTED
|
||||
}
|
||||
|
||||
private sealed class CompileLogCounter
|
||||
{
|
||||
public int CompilationFailures;
|
||||
|
||||
Reference in New Issue
Block a user