| | | 1 | | namespace AsyncResponse; |
| | | 2 | | |
| | | 3 | | /// <summary> |
| | | 4 | | /// Thrown by <see cref="IRecoveryStateStore.GetAllAsync"/> when a correlation id has stored |
| | | 5 | | /// recovery registrations but this build cannot interpret <em>any</em> of them: malformed JSON, an |
| | | 6 | | /// incomplete identity, or a schema version outside |
| | | 7 | | /// <see cref="RecoveryStateSchema.IsReadable"/>. Stores whose registrations share one blob per |
| | | 8 | | /// correlation id (Redis, NATS) also throw it from <see cref="IRecoveryStateStore.SaveAsync"/> |
| | | 9 | | /// when the stored envelope itself is unparseable: a rewrite that read "unreadable" as "missing" |
| | | 10 | | /// would commit just the new registration over registrations it could not enumerate, destroying |
| | | 11 | | /// every armed callback the blob held. |
| | | 12 | | /// <para> |
| | | 13 | | /// The same distinction <see cref="FlowStateUnreadableException"/> draws for durable-flow ledgers, |
| | | 14 | | /// applied to the recovery path. An empty registration list means "nobody ever armed a recovery |
| | | 15 | | /// callback for this response", and the lost-subscriber dispatcher answers that by acknowledging |
| | | 16 | | /// the delivery — correctly, because there is nothing to run. Filtering unreadable rows down to an |
| | | 17 | | /// empty list made a corrupt or newer-schema registration indistinguishable from that, so a |
| | | 18 | | /// terminal response was acknowledged while the callback that should have received it never ran and |
| | | 19 | | /// the response ceased to exist. |
| | | 20 | | /// </para> |
| | | 21 | | /// <para> |
| | | 22 | | /// <b>Only the all-unreadable case throws.</b> When at least one registration is readable, those |
| | | 23 | | /// are dispatched and the unreadable remainder is logged and counted instead: failing a mixed batch |
| | | 24 | | /// would redeliver callbacks that already succeeded, and — since successful registrations are |
| | | 25 | | /// deleted — the redelivery would find only the unreadable rows and fail again, turning a partially |
| | | 26 | | /// corrupt record into a permanent redelivery loop. |
| | | 27 | | /// </para> |
| | | 28 | | /// </summary> |
| | | 29 | | public sealed class RecoveryStateUnreadableException : InvalidOperationException |
| | | 30 | | { |
| | | 31 | | /// <summary>Creates the exception for <paramref name="correlationId"/>.</summary> |
| | | 32 | | public RecoveryStateUnreadableException(string? correlationId, int unreadableCount) |
| | 18 | 33 | | : base( |
| | 18 | 34 | | $"All {unreadableCount} stored recovery registration(s) for correlation id '{correlationId}' are unreadable |
| | 18 | 35 | | "this build (malformed, incomplete identity, or an unsupported schema version). The response must not be " + |
| | 18 | 36 | | "acknowledged as handled; the delivery is retried or dead-lettered so a build that can read them, or an " + |
| | 18 | 37 | | "operator, resolves it.") |
| | | 38 | | { |
| | 18 | 39 | | CorrelationId = correlationId; |
| | 18 | 40 | | UnreadableCount = unreadableCount; |
| | 18 | 41 | | } |
| | | 42 | | |
| | | 43 | | /// <summary>The correlation id whose registrations could not be read.</summary> |
| | 8 | 44 | | public string? CorrelationId { get; } |
| | | 45 | | |
| | | 46 | | /// <summary>How many stored registrations were rejected, for metrics and operator triage.</summary> |
| | 4 | 47 | | public int UnreadableCount { get; } |
| | | 48 | | } |