fix(centralui): P6-12 — dispose sandbox-run CTS in ApiMethodForm + SharedScriptForm
Neither Test Run form implemented IDisposable, so navigating away mid-run left _runCts uncancelled: the sandbox kept the run alive until its own internal script timeout, and the eventual continuation touched a torn-down component's _running/_runResult fields. TemplateScriptDialog already got this right — both forms now mirror it exactly: @implements IDisposable plus a Dispose() that cancels + disposes _runCts. ScriptAnalysisService.RunInSandboxAsync is marked virtual (test-only seam, mirroring the existing virtual on DebugStreamService.StartStreamAsync/ StopStream) so a TaskCompletionSource-gated stub can hold a run open deterministically. New bUnit tests (ScriptFormSandboxDisposalTests) prove, for both forms: disposing mid-run cancels the token the sandbox observed, and the cancelled continuation resolves with no unhandled exception and no _runResult/ _running mutation. Proof of failure: reverting only the two .razor changes (keeping the virtual seam) reproduces all 4 failures — the token is never cancelled and the no-op assertions time out.
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
@inject IInboundApiKeyAdmin ApiKeyAdmin
|
||||
@inject ScriptAnalysis.ScriptAnalysisService AnalysisService
|
||||
@inject NavigationManager NavigationManager
|
||||
@implements IDisposable
|
||||
|
||||
<div class="container-fluid mt-3">
|
||||
<button class="btn btn-outline-secondary btn-sm mb-2" @onclick="GoBack">← Back</button>
|
||||
@@ -377,6 +378,14 @@
|
||||
|
||||
private void GoBack() => NavigationManager.NavigateTo("/design/external-systems");
|
||||
|
||||
/// <summary>Cancels an in-flight Test Run when the component is torn down.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_runCts?.Cancel();
|
||||
_runCts?.Dispose();
|
||||
_runCts = null;
|
||||
}
|
||||
|
||||
private void ToggleTestRunPanel() => _showTestRun = !_showTestRun;
|
||||
|
||||
private async Task RunInSandboxAsync()
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
@inject ScriptAnalysis.ScriptAnalysisService AnalysisService
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
@inject NavigationManager NavigationManager
|
||||
@implements IDisposable
|
||||
|
||||
<div class="container-fluid mt-3">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
@@ -241,6 +242,14 @@
|
||||
NavigationManager.NavigateTo("/design/shared-scripts");
|
||||
}
|
||||
|
||||
/// <summary>Cancels an in-flight Test Run when the component is torn down.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_runCts?.Cancel();
|
||||
_runCts?.Dispose();
|
||||
_runCts = null;
|
||||
}
|
||||
|
||||
private void CheckCompilation()
|
||||
{
|
||||
var syntaxError = ValidateSyntaxLocally(_formCode);
|
||||
|
||||
@@ -242,7 +242,7 @@ public class ScriptAnalysisService
|
||||
/// <param name="request">The sandbox run request including code, kind, parameters, and optional instance binding.</param>
|
||||
/// <param name="ct">Cancellation token for the caller; combined with an internal timeout CTS.</param>
|
||||
/// <returns>A <see cref="SandboxRunResult"/> describing the outcome, return value, console output, and any error.</returns>
|
||||
public async Task<SandboxRunResult> RunInSandboxAsync(SandboxRunRequest request, CancellationToken ct)
|
||||
public virtual async Task<SandboxRunResult> RunInSandboxAsync(SandboxRunRequest request, CancellationToken ct)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Code))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user