| | | 1 | | namespace AsyncResponse; |
| | | 2 | | |
| | | 3 | | /// <summary> |
| | | 4 | | /// Thrown when a durable-flow ledger row exists but this build cannot interpret it: its JSON is |
| | | 5 | | /// malformed, or it carries a schema version outside <see cref="FlowStateSchema.IsReadable"/>. |
| | | 6 | | /// <para> |
| | | 7 | | /// This is the distinction <see cref="IFlowStateStore.LoadAsync"/> exists to preserve. A |
| | | 8 | | /// <c>null</c> load means the run is genuinely gone — never started, already pruned, TTL expired — |
| | | 9 | | /// and the only correct response is to acknowledge the wake-up and stop. An unreadable ledger |
| | | 10 | | /// means the opposite: the run is still there, still <see cref="FlowRunStatus.Running"/>, and the |
| | | 11 | | /// message being handled is very likely its only remaining wake-up. Collapsing the two let a |
| | | 12 | | /// replica that could not read a ledger report success and acknowledge that wake-up, stranding a |
| | | 13 | | /// live flow with nothing left to resume it. |
| | | 14 | | /// </para> |
| | | 15 | | /// <para> |
| | | 16 | | /// The realistic source is a rolling deployment, which is what the schema gate is for: an older |
| | | 17 | | /// replica draws a job whose ledger a newer replica already rewrote. Throwing routes that job into |
| | | 18 | | /// the transport's normal retry/dead-letter path, where a replica that <em>can</em> read the |
| | | 19 | | /// ledger gets its turn and an operator sees the ones that nothing can. |
| | | 20 | | /// </para> |
| | | 21 | | /// </summary> |
| | | 22 | | public sealed class FlowStateUnreadableException : InvalidOperationException |
| | | 23 | | { |
| | | 24 | | /// <summary>Creates the exception for <paramref name="flowId"/>.</summary> |
| | | 25 | | public FlowStateUnreadableException(string flowId, string reason, Exception? innerException = null) |
| | 183 | 26 | | : base( |
| | 183 | 27 | | $"Durable flow '{flowId}' has a stored ledger that this build cannot read ({reason}). " + |
| | 183 | 28 | | "The run still exists and must not be acknowledged as complete; the delivery is retried " + |
| | 183 | 29 | | "or dead-lettered so a build that can read it, or an operator, resolves it.", |
| | 183 | 30 | | innerException) |
| | | 31 | | { |
| | 183 | 32 | | FlowId = flowId; |
| | 183 | 33 | | Reason = reason; |
| | 183 | 34 | | } |
| | | 35 | | |
| | | 36 | | /// <summary>The flow whose ledger could not be read.</summary> |
| | 86 | 37 | | public string FlowId { get; } |
| | | 38 | | |
| | | 39 | | /// <summary>Why the ledger could not be read, for metrics and operator triage.</summary> |
| | 108 | 40 | | public string Reason { get; } |
| | | 41 | | } |