fix: cap automatic playout refreshes
This commit is contained in:
@@ -39,6 +39,7 @@ public sealed partial class MainWindow
|
||||
_legacyCompositionOptionsSource =
|
||||
new MutableLegacySceneCueCompositionOptionsSource(compositionOptions);
|
||||
_playoutEngine = PlayoutEngineFactory.Create(options);
|
||||
ResetLegacyRefreshStatus();
|
||||
_playoutEngine.StatusChanged += OnPlayoutStatusChanged;
|
||||
ObserveNativeTornadoState(_playoutEngine.Status.State);
|
||||
if (_databaseRuntime is not null)
|
||||
@@ -316,7 +317,10 @@ public sealed partial class MainWindow
|
||||
refreshLastSuccessAt = refreshStatus.LastSuccessAtUtc,
|
||||
refreshFaulted = refreshStatus.IsFaulted,
|
||||
refreshFaultCode = refreshStatus.FaultCode,
|
||||
refreshFaultMessage = refreshStatus.FaultMessage
|
||||
refreshFaultMessage = refreshStatus.FaultMessage,
|
||||
refreshCompletedCount = refreshStatus.CompletedRefreshCount,
|
||||
refreshMaximumCount = refreshStatus.MaximumRefreshCount,
|
||||
refreshLimitReached = refreshStatus.IsLimitReached
|
||||
});
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@@ -499,6 +503,9 @@ public sealed partial class MainWindow
|
||||
refreshFaulted = false,
|
||||
refreshFaultCode = (string?)null,
|
||||
refreshFaultMessage = (string?)null,
|
||||
refreshCompletedCount = 0,
|
||||
refreshMaximumCount = (int?)null,
|
||||
refreshLimitReached = false,
|
||||
changedAt
|
||||
});
|
||||
}
|
||||
@@ -546,6 +553,9 @@ public sealed partial class MainWindow
|
||||
refreshFaulted = false,
|
||||
refreshFaultCode = (string?)null,
|
||||
refreshFaultMessage = (string?)null,
|
||||
refreshCompletedCount = 0,
|
||||
refreshMaximumCount = (int?)null,
|
||||
refreshLimitReached = false,
|
||||
changedAt
|
||||
});
|
||||
}
|
||||
@@ -606,6 +616,9 @@ public sealed partial class MainWindow
|
||||
refreshFaulted = refreshStatus.IsFaulted,
|
||||
refreshFaultCode = refreshStatus.FaultCode,
|
||||
refreshFaultMessage = refreshStatus.FaultMessage,
|
||||
refreshCompletedCount = refreshStatus.CompletedRefreshCount,
|
||||
refreshMaximumCount = refreshStatus.MaximumRefreshCount,
|
||||
refreshLimitReached = refreshStatus.IsLimitReached,
|
||||
changedAt = status.ChangedAtUtc
|
||||
});
|
||||
}
|
||||
@@ -661,6 +674,9 @@ public sealed partial class MainWindow
|
||||
refreshFaulted = refreshStatus.IsFaulted,
|
||||
refreshFaultCode = refreshStatus.FaultCode,
|
||||
refreshFaultMessage = refreshStatus.FaultMessage,
|
||||
refreshCompletedCount = refreshStatus.CompletedRefreshCount,
|
||||
refreshMaximumCount = refreshStatus.MaximumRefreshCount,
|
||||
refreshLimitReached = refreshStatus.IsLimitReached,
|
||||
changedAt = status.ChangedAtUtc
|
||||
};
|
||||
}
|
||||
@@ -780,6 +796,7 @@ public sealed partial class MainWindow
|
||||
private void StartLegacyRefreshLoop(LegacyPlayoutWorkflow workflow)
|
||||
{
|
||||
StopLegacyRefreshLoop();
|
||||
ResetLegacyRefreshStatus();
|
||||
var cutCode = workflow.State.OnAirCutCode;
|
||||
if (!LegacySceneRefreshPolicy.TryGetInterval(cutCode, out var interval) ||
|
||||
interval <= TimeSpan.Zero ||
|
||||
@@ -792,16 +809,38 @@ public sealed partial class MainWindow
|
||||
_lifetimeCancellation.Token);
|
||||
var scheduler = new LegacyRefreshScheduler(
|
||||
interval,
|
||||
LegacySceneRefreshPolicy.RecurringInterval);
|
||||
_legacyRefreshEpoch.Replace(
|
||||
cancellation,
|
||||
status => new LegacyRefreshRuntimeStatus(
|
||||
true,
|
||||
null,
|
||||
status.LastSuccessAtUtc,
|
||||
LegacySceneRefreshPolicy.RecurringInterval,
|
||||
_playoutOptions?.MaximumAutomaticRefreshesPerTakeIn);
|
||||
if (scheduler.HasReachedMaximum)
|
||||
{
|
||||
cancellation.Dispose();
|
||||
_legacyRefreshTask = null;
|
||||
_legacyRefreshEpoch.Stop(_ => new LegacyRefreshRuntimeStatus(
|
||||
false,
|
||||
null,
|
||||
null));
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
scheduler.CompletedRefreshCount,
|
||||
scheduler.MaximumRefreshCount,
|
||||
scheduler.IsMaximumCallbackDrained));
|
||||
QueuePlayoutStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
_legacyRefreshEpoch.Replace(
|
||||
cancellation,
|
||||
_ => new LegacyRefreshRuntimeStatus(
|
||||
true,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
scheduler.CompletedRefreshCount,
|
||||
scheduler.MaximumRefreshCount,
|
||||
false));
|
||||
_legacyRefreshTask = RunLegacyRefreshLoopAsync(
|
||||
workflow,
|
||||
scheduler,
|
||||
@@ -836,6 +875,36 @@ public sealed partial class MainWindow
|
||||
return;
|
||||
}
|
||||
|
||||
if (scheduler.HasReachedMaximum)
|
||||
{
|
||||
if (!await scheduler.DrainMaximumCallbackAsync(
|
||||
cancellationToken => WaitForPlayCompletionAsync(
|
||||
engine,
|
||||
cancellationToken),
|
||||
token))
|
||||
{
|
||||
TrySetLegacyRefreshFault(
|
||||
cancellation,
|
||||
"PLAY_CALLBACK_TIMEOUT",
|
||||
"The configured automatic refresh limit was reached, but the final Tornado play callback was not observed in time. Verify PGM output before TAKE OUT.");
|
||||
QueuePlayoutStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
_legacyRefreshEpoch.TryUpdate(
|
||||
cancellation,
|
||||
status => status with
|
||||
{
|
||||
IsActive = false,
|
||||
NextAtUtc = null,
|
||||
CompletedRefreshCount = scheduler.CompletedRefreshCount,
|
||||
MaximumRefreshCount = scheduler.MaximumRefreshCount,
|
||||
IsLimitReached = scheduler.IsMaximumCallbackDrained
|
||||
});
|
||||
QueuePlayoutStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
// The original WinForms timer coalesced non-reentrant UI ticks but did
|
||||
// not wait for the vendor callback. The migration's safety invariant is
|
||||
// stricter: observe OnScenePlayed and then start a complete one-shot
|
||||
@@ -891,8 +960,9 @@ public sealed partial class MainWindow
|
||||
CancelActiveDatabaseHealthCheck();
|
||||
await _databaseActivityGate.WaitAsync(token);
|
||||
databaseActivityEntered = true;
|
||||
var result = await scheduler.ExecuteRefreshAsync(
|
||||
var result = await scheduler.ExecuteRefreshAndTrackSuccessAsync(
|
||||
workflow.RefreshOnAirAsync,
|
||||
static result => result.IsSuccess,
|
||||
token);
|
||||
// A failed, timed-out, or unknown update never repeats. The operator
|
||||
// must reconcile native/PGM state before starting another command.
|
||||
@@ -908,7 +978,6 @@ public sealed partial class MainWindow
|
||||
// MainForm changes timer1.Interval to 3000 after the first
|
||||
// successful same-scene refresh. The next interval does not
|
||||
// begin until this Play's completion callback is observed.
|
||||
scheduler.MarkRefreshSucceeded();
|
||||
if (!_legacyRefreshEpoch.TryUpdate(
|
||||
cancellation,
|
||||
_ => new LegacyRefreshRuntimeStatus(
|
||||
@@ -917,7 +986,10 @@ public sealed partial class MainWindow
|
||||
DateTimeOffset.UtcNow,
|
||||
false,
|
||||
null,
|
||||
null)))
|
||||
null,
|
||||
scheduler.CompletedRefreshCount,
|
||||
scheduler.MaximumRefreshCount,
|
||||
false)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -1056,10 +1128,16 @@ public sealed partial class MainWindow
|
||||
status.LastSuccessAtUtc,
|
||||
true,
|
||||
code,
|
||||
message));
|
||||
message,
|
||||
status.CompletedRefreshCount,
|
||||
status.MaximumRefreshCount,
|
||||
status.IsLimitReached));
|
||||
|
||||
private void ResetLegacyRefreshStatus() =>
|
||||
_legacyRefreshEpoch.Stop(_ => LegacyRefreshRuntimeStatus.Empty);
|
||||
_legacyRefreshEpoch.Stop(_ => LegacyRefreshRuntimeStatus.Empty with
|
||||
{
|
||||
MaximumRefreshCount = _playoutOptions?.MaximumAutomaticRefreshesPerTakeIn
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Provides the single fail-closed playout boundary used by every operator-data
|
||||
@@ -1158,7 +1236,10 @@ public sealed partial class MainWindow
|
||||
DateTimeOffset? LastSuccessAtUtc,
|
||||
bool IsFaulted,
|
||||
string? FaultCode,
|
||||
string? FaultMessage)
|
||||
string? FaultMessage,
|
||||
int CompletedRefreshCount,
|
||||
int? MaximumRefreshCount,
|
||||
bool IsLimitReached)
|
||||
{
|
||||
public static LegacyRefreshRuntimeStatus Empty { get; } = new(
|
||||
false,
|
||||
@@ -1166,6 +1247,9 @@ public sealed partial class MainWindow
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
null);
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user