< Summary - AsyncResponse (Release / net8.0+net10.0 / unit+integration)

Information
Class: AsyncResponse.FlowStateRetention
Assembly: AsyncResponse.Core
File(s): /_/src/AsyncResponse.Core/FlowStateRetention.cs
Line coverage
100%
Covered lines: 16
Uncovered lines: 0
Coverable lines: 16
Total lines: 71
Line coverage: 100%
Branch coverage
94%
Covered branches: 17
Total branches: 18
Branch coverage: 94.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
EffectiveTtl(...)100%88100%
RaiseFloor(...)100%44100%
FloorAt(...)100%11100%
Covers(...)100%22100%
IsTerminal(...)100%22100%
AddSaturating(...)50%22100%

File(s)

/_/src/AsyncResponse.Core/FlowStateRetention.cs

#LineLine coverage
 1namespace AsyncResponse;
 2
 3/// <summary>
 4/// The ledger retention floor (<see cref="FlowState.RetainUntilUtc"/>) at the write sites.
 5/// <para>
 6/// A park longer than the ordinary idle <c>StateExpiry</c> — a timer sleep, an awaited step's
 7/// window, a child flow's own park — needs the parked run's ledger AND every ancestor waiting on
 8/// it to outlive the wait. The TTL stamped by the park's own save covers that only until the
 9/// next write: every store recomputes expiry as "now + ttl", and the writers that can race a
 10/// park (an ancestor's replay re-parking on a stale child snapshot, an executor's per-attempt
 11/// save, a recovery or operator mutation) know nothing about the wait and stamp the plain
 12/// <c>StateExpiry</c>. The floor rides in the ledger itself, so whoever writes the ledger next
 13/// carries it forward: the TTL a write stamps is raised to reach the floor. That is what makes
 14/// a descendant's extension of an ancestor durable across the ancestor's own checkpoints, and
 15/// what lets the extension prove — by re-reading the ancestor — that a concurrent write which
 16/// beat its compare-and-swap left adequate retention behind.
 17/// </para>
 18/// <para>
 19/// Terminal runs ignore the floor: they have no wait in progress, and a failed run should not
 20/// be retained for the length of the sleep it never finished.
 21/// </para>
 22/// </summary>
 23internal static class FlowStateRetention
 24{
 25    /// <summary>
 26    /// The TTL a write must stamp for <paramref name="state"/>: <paramref name="requested"/>,
 27    /// raised to reach the state's retention floor when the run is live and the floor is further
 28    /// out. Saturated at the persistence ceiling (clock skew between replicas could otherwise push
 29    /// a floor stamped elsewhere a hair past it).
 30    /// </summary>
 31    public static TimeSpan EffectiveTtl(FlowState state, TimeSpan requested, DateTime nowUtc)
 32    {
 1302433        if (state.RetainUntilUtc is not { } floor || IsTerminal(state.Status))
 685034            return requested;
 35
 617436        var needed = floor - nowUtc;
 617437        if (needed <= requested)
 249138            return requested;
 39
 368340        return needed > AsyncResponseChannelOptions.MaxPersistenceTtl
 368341            ? AsyncResponseChannelOptions.MaxPersistenceTtl
 368342            : needed;
 43    }
 44
 45    /// <summary>
 46    /// Raises the floor of <paramref name="state"/> to <paramref name="nowUtc"/> +
 47    /// <paramref name="ttl"/> when that is further out than the current one (never lowers it) and
 48    /// returns the instant the floor now sits at.
 49    /// </summary>
 50    public static DateTime RaiseFloor(FlowState state, DateTime nowUtc, TimeSpan ttl)
 51    {
 229952        var until = FloorAt(nowUtc, ttl);
 229953        if (state.RetainUntilUtc is not { } floor || until > floor)
 217754            state.RetainUntilUtc = until;
 55
 229956        return state.RetainUntilUtc!.Value;
 57    }
 58
 59    /// <summary>The instant a floor stamped now for <paramref name="ttl"/> sits at (saturating).</summary>
 295160    public static DateTime FloorAt(DateTime nowUtc, TimeSpan ttl) => AddSaturating(nowUtc, ttl);
 61
 62    /// <summary>Whether the floor of <paramref name="state"/> already reaches <paramref name="until"/>.</summary>
 63    public static bool Covers(FlowState state, DateTime until)
 65664        => state.RetainUntilUtc is { } floor && floor >= until;
 65
 66    private static bool IsTerminal(FlowRunStatus status)
 761267        => status is FlowRunStatus.Succeeded or FlowRunStatus.Failed;
 68
 69    private static DateTime AddSaturating(DateTime instant, TimeSpan ttl)
 295170        => ttl > DateTime.MaxValue - instant ? DateTime.MaxValue : instant + ttl;
 71}