{"record":{"id":"581b6779f1ea3c2b","repo":"dotnet/reactive","slug":"argumentoutofrangeexception-capacity-groupbyuntil","errorCode":null,"errorMessage":"ArgumentOutOfRangeException: capacity","messagePattern":"ArgumentOutOfRangeException: capacity","errorType":"validation","errorClass":"ArgumentOutOfRangeException","httpStatus":null,"severity":"error","filePath":"AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupByUntil.cs","lineNumber":360,"sourceCode":"                throw new ArgumentNullException(nameof(durationSelector));\n            if (comparer == null)\n                throw new ArgumentNullException(nameof(comparer));\n\n            return GroupByUntil(observer, subscription, keySelector, durationSelector, int.MaxValue, comparer);\n        }\n\n        public static ValueTask<(IAsyncObserver<TSource>, IAsyncDisposable)> GroupByUntil<TSource, TKey, TDuration>(IAsyncObserver<IGroupedAsyncObservable<TKey, TSource>> observer, IAsyncDisposable subscription, Func<TSource, TKey> keySelector, Func<IGroupedAsyncObservable<TKey, TSource>, IAsyncObservable<TDuration>> durationSelector, int capacity)\n        {\n            if (observer == null)\n                throw new ArgumentNullException(nameof(observer));\n            if (subscription == null)\n                throw new ArgumentNullException(nameof(subscription));\n            if (keySelector == null)\n                throw new ArgumentNullException(nameof(keySelector));\n            if (durationSelector == null)\n                throw new ArgumentNullException(nameof(durationSelector));\n            if (capacity < 0)\n                throw new ArgumentOutOfRangeException(nameof(capacity));\n\n            return GroupByUntil(observer, subscription, keySelector, durationSelector, capacity, EqualityComparer<TKey>.Default);\n        }\n\n        public static ValueTask<(IAsyncObserver<TSource>, IAsyncDisposable)> GroupByUntil<TSource, TKey, TDuration>(IAsyncObserver<IGroupedAsyncObservable<TKey, TSource>> observer, IAsyncDisposable subscription, Func<TSource, TKey> keySelector, Func<IGroupedAsyncObservable<TKey, TSource>, IAsyncObservable<TDuration>> durationSelector, int capacity, IEqualityComparer<TKey> comparer)\n        {\n            if (observer == null)\n                throw new ArgumentNullException(nameof(observer));\n            if (subscription == null)\n                throw new ArgumentNullException(nameof(subscription));\n            if (keySelector == null)\n                throw new ArgumentNullException(nameof(keySelector));\n            if (durationSelector == null)\n                throw new ArgumentNullException(nameof(durationSelector));\n            if (capacity < 0)\n                throw new ArgumentOutOfRangeException(nameof(capacity));\n            if (comparer == null)\n                throw new ArgumentNullException(nameof(comparer));","sourceCodeStart":342,"sourceCodeEnd":378,"githubUrl":"https://github.com/dotnet/reactive/blob/94b5d5ab912789f5abe9a72138a25bbd716fe59c/AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupByUntil.cs#L342-L378","documentation":"The capacity-taking GroupByUntil overload requires a non-negative capacity (the maximum number of concurrently live groups held by the operator's internal map). A negative value was passed, so the library throws ArgumentOutOfRangeException immediately. This is a fail-fast guard against an obviously meaningless configuration value.","triggerScenarios":"Calling GroupByUntil(observer, subscription, keySelector, durationSelector, capacity) (GroupByUntil.cs:349) with capacity < 0, e.g. a computed limit that evaluated to -1 because a config value was missing or a subtraction underflowed.","commonSituations":"Reading a group-limit setting from configuration where the default is -1 meaning 'unset'; computing capacity from a collection size that came back empty; integer arithmetic returning a negative sentinel.","solutions":["Pass 0 or a positive capacity; use int.MaxValue (the overload without capacity does this) for unlimited groups","Clamp the value before calling: Math.Max(0, configuredCapacity)","Fix the upstream configuration or computation that produced the negative number"],"exampleFix":"// before\nvar res = source.GroupByUntil(k => k, g => g.Take(1), config.MaxGroups); // -1 when unset\n// after\nvar res = source.GroupByUntil(k => k, g => g.Take(1), config.MaxGroups is int c && c >= 0 ? c : int.MaxValue);","handlingStrategy":"validation","validationCode":"if (capacity < 0) throw new ArgumentException(\"capacity must be non-negative\", nameof(capacity));\n// or normalize: capacity = capacity is int c && c >= 0 ? c : int.MaxValue;","typeGuard":"bool IsValidCapacity(int capacity) => capacity >= 0;","tryCatchPattern":"try { var res = source.GroupByUntil(keySelector, durationSelector, capacity); }\ncatch (ArgumentOutOfRangeException ex) when (ex.ParamName == \"capacity\") { /* fall back to int.MaxValue or a sane default */ }","preventionTips":["Translate 'unlimited' config sentinels (-1, 0?) to int.MaxValue at configuration load time","Clamp numeric settings with Math.Max when reading them","Validate settings at startup, not at query construction"],"tags":["csharp","argument-out-of-range","async-rx","groupbyuntil"],"backgroundTag":"argument-out-of-range","analyzedSha":"94b5d5ab912789f5abe9a72138a25bbd716fe59c","analyzedAt":"2026-09-15T02:26:24.759Z","contentChangedAt":"2026-09-15T02:26:24.759Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}