| | | 1 | | namespace AsyncResponse.Transports.GooglePubSub; |
| | | 2 | | |
| | | 3 | | internal static class GooglePubSubOptionsValidator |
| | | 4 | | { |
| | | 5 | | /// <summary>Validates the supplied options.</summary> |
| | | 6 | | public static string Required(string? value, string name) |
| | 3749 | 7 | | => !string.IsNullOrWhiteSpace(value) |
| | 3749 | 8 | | ? value |
| | 3749 | 9 | | : throw new InvalidOperationException($"{nameof(GooglePubSubAsyncResponseOptions)}.{name} must be configured |
| | | 10 | | |
| | | 11 | | /// <summary> |
| | | 12 | | /// Common validation shared by the publisher and both subscribers: requires the |
| | | 13 | | /// correlation-id attribute name and bounds every timer-armed timeout — the subscriber retry |
| | | 14 | | /// delays feed <c>Task.Delay</c>, and <c>ShutdownTimeout</c> feeds the publisher's |
| | | 15 | | /// <c>ShutdownAsync</c> and the subscriber's stop timeout in every ack mode. The timeouts |
| | | 16 | | /// previously had NO validation at all — a negative or over-ceiling value surfaced as a raw |
| | | 17 | | /// timer exception inside the retry loop. |
| | | 18 | | /// </summary> |
| | | 19 | | public static void ValidateTimeouts(GooglePubSubAsyncResponseOptions options) |
| | | 20 | | { |
| | | 21 | | // Both the publish and consume paths index the message-attribute map with |
| | | 22 | | // CorrelationIdAttribute; a null/empty value throws from the protobuf map on the very |
| | | 23 | | // first message (or, behind the dispatcher's catch-all, turns into a perpetual NACK |
| | | 24 | | // loop), so fail fast here — the ASB sibling validates its CorrelationIdProperty the |
| | | 25 | | // same way. |
| | 1179 | 26 | | _ = Required(options.CorrelationIdAttribute, nameof(options.CorrelationIdAttribute)); |
| | 1179 | 27 | | AsyncResponseChannelOptions.EnsureTimerBacked(options.SubscriberRetryBaseDelay, nameof(GooglePubSubAsyncResponse |
| | 1175 | 28 | | AsyncResponseChannelOptions.EnsureTimerBacked(options.SubscriberRetryMaxDelay, nameof(GooglePubSubAsyncResponseO |
| | 1175 | 29 | | if (options.SubscriberRetryBaseDelay > options.SubscriberRetryMaxDelay) |
| | 2 | 30 | | throw new InvalidOperationException( |
| | 2 | 31 | | $"{nameof(GooglePubSubAsyncResponseOptions)}.{nameof(options.SubscriberRetryBaseDelay)} cannot exceed {n |
| | 1173 | 32 | | AsyncResponseChannelOptions.EnsureTimerBacked(options.ShutdownTimeout, nameof(GooglePubSubAsyncResponseOptions), |
| | 1169 | 33 | | if (options.HostShutdownTimeout is { } hostShutdownTimeout && hostShutdownTimeout <= TimeSpan.Zero) |
| | 2 | 34 | | throw new InvalidOperationException($"{nameof(GooglePubSubAsyncResponseOptions)}.{nameof(options.HostShutdow |
| | 1167 | 35 | | } |
| | | 36 | | |
| | | 37 | | /// <summary> |
| | | 38 | | /// The first lease already lasts the client's 60-second ack deadline, so a smaller total |
| | | 39 | | /// extension cannot make Pub/Sub redeliver sooner — it would only shrink the in-flight ceiling |
| | | 40 | | /// the worker transport advertises, and with it every in-process durable-flow wait. |
| | | 41 | | /// </summary> |
| | 5 | 42 | | internal static readonly TimeSpan MinimumMaxTotalAckExtension = TimeSpan.FromMinutes(1); |
| | | 43 | | |
| | | 44 | | /// <summary>The SDK's own <c>ClientCount</c> range; outside it the client build throws.</summary> |
| | | 45 | | internal const int MaximumClientCount = 256; |
| | | 46 | | |
| | | 47 | | /// <summary> |
| | | 48 | | /// Bounds the ack-extension ceiling. The SDK arms one timer with it per pulled batch, so an |
| | | 49 | | /// over-ceiling value surfaces as a raw timer exception inside the streaming pull (and a |
| | | 50 | | /// perpetual subscriber restart loop) instead of failing startup. |
| | | 51 | | /// </summary> |
| | | 52 | | public static void ValidateMaxTotalAckExtension(GooglePubSubSubscriberOptions subscriberOptions, string optionPath) |
| | | 53 | | { |
| | 1151 | 54 | | AsyncResponseChannelOptions.EnsureTimerBacked(subscriberOptions.MaxTotalAckExtension, optionPath, nameof(GoogleP |
| | 1137 | 55 | | if (subscriberOptions.MaxTotalAckExtension < MinimumMaxTotalAckExtension) |
| | 4 | 56 | | throw new InvalidOperationException( |
| | 4 | 57 | | $"{optionPath}.{nameof(GooglePubSubSubscriberOptions.MaxTotalAckExtension)} must be at least {MinimumMax |
| | 4 | 58 | | "the first lease already lasts the Pub/Sub client's 60-second ack deadline, so a smaller value cannot sp |
| | 1133 | 59 | | } |
| | | 60 | | |
| | | 61 | | /// <summary> |
| | | 62 | | /// Validates the streaming-pull settings handed to the SDK's subscriber client. The SDK checks |
| | | 63 | | /// them only when the client is built — inside the supervised retry loop, where a bad value |
| | | 64 | | /// turns into an endless rebuild-and-fail cycle rather than a startup failure. |
| | | 65 | | /// </summary> |
| | | 66 | | public static void ValidateStreamingPull(GooglePubSubSubscriberOptions subscriberOptions, string optionPath) |
| | | 67 | | { |
| | 918 | 68 | | ValidateMaxTotalAckExtension(subscriberOptions, optionPath); |
| | | 69 | | |
| | 902 | 70 | | if (subscriberOptions.ClientCount is < 1 or > MaximumClientCount) |
| | 10 | 71 | | throw new InvalidOperationException( |
| | 10 | 72 | | $"{optionPath}.{nameof(GooglePubSubSubscriberOptions.ClientCount)} must be between 1 and {MaximumClientC |
| | | 73 | | |
| | 892 | 74 | | if (subscriberOptions.MaxOutstandingMessages <= 0) |
| | 4 | 75 | | throw new InvalidOperationException( |
| | 4 | 76 | | $"{optionPath}.{nameof(GooglePubSubSubscriberOptions.MaxOutstandingMessages)} must be positive."); |
| | | 77 | | |
| | 888 | 78 | | if (subscriberOptions.MaxOutstandingBytes <= 0) |
| | 4 | 79 | | throw new InvalidOperationException( |
| | 4 | 80 | | $"{optionPath}.{nameof(GooglePubSubSubscriberOptions.MaxOutstandingBytes)} must be positive."); |
| | 884 | 81 | | } |
| | | 82 | | } |