| | | 1 | | namespace AsyncResponse; |
| | | 2 | | |
| | | 3 | | /// <summary> |
| | | 4 | | /// Persists durable-flow ledgers with the atomic operations required for safe multi-replica |
| | | 5 | | /// execution. Implementations must provide insert-if-absent creation, revision-checked updates, |
| | | 6 | | /// and renewable owner-fenced execution leases. |
| | | 7 | | /// </summary> |
| | | 8 | | public interface IFlowStateStore |
| | | 9 | | { |
| | | 10 | | /// <summary> |
| | | 11 | | /// Checks deterministic creation constraints, including the serialized state-size budget, |
| | | 12 | | /// without writing state or contacting external infrastructure. The starter calls this before |
| | | 13 | | /// publishing its start job. Implementations must still validate actual writes; this is not a |
| | | 14 | | /// reservation. The default is a no-op for compatibility with application-owned stores. |
| | | 15 | | /// </summary> |
| | | 16 | | /// <exception cref="FlowStateTooLargeException">The initial state exceeds the store's budget.</exception> |
| | 380 | 17 | | void ValidateCreate(string flowId, FlowState state, TimeSpan ttl) { } |
| | | 18 | | |
| | | 19 | | /// <summary>Atomically creates a new flow ledger; returns false when the id already exists.</summary> |
| | | 20 | | Task<bool> TryCreateAsync( |
| | | 21 | | string flowId, |
| | | 22 | | FlowState state, |
| | | 23 | | TimeSpan ttl, |
| | | 24 | | CancellationToken cancellationToken = default); |
| | | 25 | | |
| | | 26 | | /// <summary> |
| | | 27 | | /// Loads the state of one flow run, or <c>null</c> when the run is genuinely gone — unknown, |
| | | 28 | | /// pruned, or expired. A row that exists but cannot be interpreted — malformed JSON, an |
| | | 29 | | /// unknown schema version, a revision inside the JSON that disagrees with the stored one, a |
| | | 30 | | /// flow id inside the JSON that is not the key — is <em>not</em> absence and must throw |
| | | 31 | | /// <see cref="FlowStateUnreadableException"/>: callers acknowledge a wake-up on <c>null</c>, |
| | | 32 | | /// so reporting a live-but-unreadable ledger that way strands the run. |
| | | 33 | | /// </summary> |
| | | 34 | | /// <exception cref="FlowStateUnreadableException">The ledger exists but is uninterpretable or inconsistent.</except |
| | | 35 | | Task<FlowState?> LoadAsync(string flowId, CancellationToken cancellationToken = default); |
| | | 36 | | |
| | | 37 | | /// <summary> |
| | | 38 | | /// Atomically replaces a ledger when its stored revision matches <paramref name="expectedRevision"/>. |
| | | 39 | | /// When <paramref name="leaseId"/> is supplied, that same unexpired execution lease must own |
| | | 40 | | /// the ledger. The supplied state's revision is the new revision to persist. |
| | | 41 | | /// </summary> |
| | | 42 | | Task<bool> TryUpdateAsync( |
| | | 43 | | string flowId, |
| | | 44 | | FlowState state, |
| | | 45 | | long expectedRevision, |
| | | 46 | | TimeSpan ttl, |
| | | 47 | | string? leaseId = null, |
| | | 48 | | CancellationToken cancellationToken = default); |
| | | 49 | | |
| | | 50 | | /// <summary>Atomically acquires an expired or unowned execution lease.</summary> |
| | | 51 | | Task<bool> TryAcquireLeaseAsync( |
| | | 52 | | string flowId, |
| | | 53 | | string leaseId, |
| | | 54 | | TimeSpan leaseDuration, |
| | | 55 | | CancellationToken cancellationToken = default); |
| | | 56 | | |
| | | 57 | | /// <summary>Renews an unexpired lease owned by <paramref name="leaseId"/>.</summary> |
| | | 58 | | Task<bool> TryRenewLeaseAsync( |
| | | 59 | | string flowId, |
| | | 60 | | string leaseId, |
| | | 61 | | TimeSpan leaseDuration, |
| | | 62 | | CancellationToken cancellationToken = default); |
| | | 63 | | |
| | | 64 | | /// <summary>Releases the lease when it is still owned by <paramref name="leaseId"/>.</summary> |
| | | 65 | | Task ReleaseLeaseAsync( |
| | | 66 | | string flowId, |
| | | 67 | | string leaseId, |
| | | 68 | | CancellationToken cancellationToken = default); |
| | | 69 | | |
| | | 70 | | /// <summary> |
| | | 71 | | /// Reports the execution lease currently persisted for <paramref name="flowId"/> — the raw |
| | | 72 | | /// owner and expiry, without judging whether it has lapsed. Returns |
| | | 73 | | /// <see cref="FlowLeaseObservation.Unheld"/> when no lease is held (or the ledger is absent), |
| | | 74 | | /// and <c>null</c> when this store cannot report leases at all, which is the default for |
| | | 75 | | /// compatibility with application-owned stores. |
| | | 76 | | /// <para> |
| | | 77 | | /// A wake-up that finds the lease held uses this to tell a live holder from a dead one: a |
| | | 78 | | /// lease whose owner or expiry changes while the wake-up waits was acquired or renewed by a |
| | | 79 | | /// live worker, so the wake-up is a duplicate; a lease that never changes belongs to a dead |
| | | 80 | | /// holder and is waited out to its <em>persisted</em> expiry, whatever lease duration issued |
| | | 81 | | /// it. A store that returns <c>null</c> gives the engine no such evidence, so a wake-up that |
| | | 82 | | /// cannot acquire the lease within its own lease window is never acknowledged: it fails with |
| | | 83 | | /// <see cref="DurableFlowLeaseContendedException"/> and the worker transport redelivers it. |
| | | 84 | | /// </para> |
| | | 85 | | /// </summary> |
| | | 86 | | Task<FlowLeaseObservation?> ObserveLeaseAsync(string flowId, CancellationToken cancellationToken = default) |
| | 6 | 87 | | => Task.FromResult<FlowLeaseObservation?>(null); |
| | | 88 | | |
| | | 89 | | /// <summary>Deletes the state of one flow run; <c>true</c> when an entry was removed.</summary> |
| | | 90 | | Task<bool> TryDeleteAsync(string flowId, CancellationToken cancellationToken = default); |
| | | 91 | | } |