fix(central-ui,cli): Flow Debugger client waits derive from the method timeout / wait window instead of a fixed 2 minutes (arch-review F4)
Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
This commit is contained in:
@@ -12,6 +12,7 @@ using ZB.MOM.WW.ScadaBridge.Commons.Messages.Notification;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Debugging;
|
||||
using ZB.MOM.WW.ScadaBridge.Communication;
|
||||
using ZB.MOM.WW.ScadaBridge.ManagementService;
|
||||
using ZB.MOM.WW.ScadaBridge.ManagementService.FlowDebug;
|
||||
using ZB.MOM.WW.ScadaBridge.ScriptAnalysis;
|
||||
using ZB.MOM.WW.ScadaBridge.Security;
|
||||
|
||||
@@ -23,21 +24,46 @@ namespace ZB.MOM.WW.ScadaBridge.CentralUI.Services;
|
||||
/// the run/replay commands go to the per-node <c>ManagementActor</c> through
|
||||
/// <see cref="ManagementActorHolder"/>, exactly as <see cref="DataConnectionMoveService"/>
|
||||
/// does, with the current Blazor principal projected to an <see cref="AuthenticatedUser"/>
|
||||
/// so the server's Deployer gate runs against the real identity. The Live-run Ask waits up
|
||||
/// to <see cref="RunAskTimeout"/> (the server bounds a run by the method timeout + grace and
|
||||
/// a value-change by its wait window; both fit inside two minutes).
|
||||
/// so the server's Deployer gate runs against the real identity. The Live-run Ask wait is
|
||||
/// derived from the server's own bound (<see cref="RunAskTimeoutFor"/> /
|
||||
/// <see cref="ValueChangeAskTimeoutFor"/>) rather than a fixed duration (arch-review F4) —
|
||||
/// a run against a method with a long <c>TimeoutSeconds</c> used to be cut off client-side
|
||||
/// well before the server's own grace period expired.
|
||||
/// Retry / Discard of a parked outbound node (follow-ups plan 2026-09-06 D4) goes through
|
||||
/// <see cref="CommunicationService"/> to the SAME central-local proxies the Site Calls and
|
||||
/// Notification report pages use, so the page and the report never disagree on what a relay does.
|
||||
/// </summary>
|
||||
public sealed class FlowDebugService : IFlowDebugService
|
||||
{
|
||||
/// <summary>Ask timeout for the two Live-run commands.</summary>
|
||||
public static readonly TimeSpan RunAskTimeout = TimeSpan.FromMinutes(2);
|
||||
|
||||
/// <summary>Ask timeout for Replay (a central DB read).</summary>
|
||||
public static readonly TimeSpan ReplayAskTimeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
/// <summary>Slack the client adds over the server's own bound (Ask hop + serialization).</summary>
|
||||
public static readonly TimeSpan ClientSlack = TimeSpan.FromSeconds(15);
|
||||
|
||||
/// <summary>Floor for a method-run Ask wait, regardless of how short the method's own timeout is.</summary>
|
||||
public static readonly TimeSpan MinRunAskTimeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
/// <summary>
|
||||
/// Client-side Ask wait for <see cref="RunMethodAsync"/>: the method's own
|
||||
/// <c>TimeoutSeconds</c> plus the server's <see cref="FlowDebugRunner.RunGrace"/> plus
|
||||
/// <see cref="ClientSlack"/>, floored at <see cref="MinRunAskTimeout"/> — the server bounds
|
||||
/// the run itself at <c>timeoutSeconds + RunGrace</c> (see <c>FlowDebugRunner.RunMethodAsync</c>),
|
||||
/// so the client must wait at least that long or it cuts the run off before the server does.
|
||||
/// </summary>
|
||||
public static TimeSpan RunAskTimeoutFor(int timeoutSeconds)
|
||||
{
|
||||
var t = TimeSpan.FromSeconds(Math.Max(1, timeoutSeconds)) + FlowDebugRunner.RunGrace + ClientSlack;
|
||||
return t < MinRunAskTimeout ? MinRunAskTimeout : t;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Client-side Ask wait for <see cref="RunValueChangeAsync"/>: the trigger-wait window
|
||||
/// plus <see cref="ClientSlack"/>.
|
||||
/// </summary>
|
||||
public static TimeSpan ValueChangeAskTimeoutFor(int waitSeconds)
|
||||
=> TimeSpan.FromSeconds(Math.Max(1, waitSeconds)) + ClientSlack;
|
||||
|
||||
/// <summary>Upper bound on the template parent chain the script resolver walks (cycle guard).</summary>
|
||||
private const int MaxTemplateChain = 16;
|
||||
|
||||
@@ -176,12 +202,19 @@ public sealed class FlowDebugService : IFlowDebugService
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<FlowDebugOutcome> RunMethodAsync(int apiMethodId, JsonElement? parameters, CancellationToken cancellationToken = default)
|
||||
=> SendAndBuildAsync(new FlowDebugRunMethodCommand(apiMethodId, parameters), RunAskTimeout, cancellationToken);
|
||||
public async Task<FlowDebugOutcome> RunMethodAsync(int apiMethodId, JsonElement? parameters, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var method = await _inboundApi.GetApiMethodByIdAsync(apiMethodId, cancellationToken);
|
||||
var timeout = RunAskTimeoutFor(method?.TimeoutSeconds ?? 30);
|
||||
return await SendAndBuildAsync(new FlowDebugRunMethodCommand(apiMethodId, parameters), timeout, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<FlowDebugOutcome> RunValueChangeAsync(int instanceId, string attributeName, string value, CancellationToken cancellationToken = default)
|
||||
=> SendAndBuildAsync(new FlowDebugRunValueChangeCommand(instanceId, attributeName, value), RunAskTimeout, cancellationToken);
|
||||
{
|
||||
var command = new FlowDebugRunValueChangeCommand(instanceId, attributeName, value);
|
||||
return SendAndBuildAsync(command, ValueChangeAskTimeoutFor(command.WaitSeconds), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<FlowDebugOutcome> ReplayAsync(Guid executionId, CancellationToken cancellationToken = default)
|
||||
|
||||
Reference in New Issue
Block a user