dotnet/reactive · error · ArgumentOutOfRangeException

ArgumentOutOfRangeException: capacity

Error message

ArgumentOutOfRangeException: capacity

What it means

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.

Solutions

  1. Pass 0 or a positive capacity; use int.MaxValue (the overload without capacity does this) for unlimited groups
  2. Clamp the value before calling: Math.Max(0, configuredCapacity)
  3. Fix the upstream configuration or computation that produced the negative number

Example fix

// before
var res = source.GroupByUntil(k => k, g => g.Take(1), config.MaxGroups); // -1 when unset
// after
var res = source.GroupByUntil(k => k, g => g.Take(1), config.MaxGroups is int c && c >= 0 ? c : int.MaxValue);
Defensive patterns

Strategy: validation

Validate before calling

if (capacity < 0) throw new ArgumentException("capacity must be non-negative", nameof(capacity));
// or normalize: capacity = capacity is int c && c >= 0 ? c : int.MaxValue;

Type guard

bool IsValidCapacity(int capacity) => capacity >= 0;

Try / catch

try { var res = source.GroupByUntil(keySelector, durationSelector, capacity); }
catch (ArgumentOutOfRangeException ex) when (ex.ParamName == "capacity") { /* fall back to int.MaxValue or a sane default */ }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15). Data as JSON: /api/errors/581b6779f1ea3c2b. Report an issue: GitHub.

Appendix: source

Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupByUntil.cs:360

                throw new ArgumentNullException(nameof(durationSelector));
            if (comparer == null)
                throw new ArgumentNullException(nameof(comparer));

            return GroupByUntil(observer, subscription, keySelector, durationSelector, int.MaxValue, comparer);
        }

        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)
        {
            if (observer == null)
                throw new ArgumentNullException(nameof(observer));
            if (subscription == null)
                throw new ArgumentNullException(nameof(subscription));
            if (keySelector == null)
                throw new ArgumentNullException(nameof(keySelector));
            if (durationSelector == null)
                throw new ArgumentNullException(nameof(durationSelector));
            if (capacity < 0)
                throw new ArgumentOutOfRangeException(nameof(capacity));

            return GroupByUntil(observer, subscription, keySelector, durationSelector, capacity, EqualityComparer<TKey>.Default);
        }

        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)
        {
            if (observer == null)
                throw new ArgumentNullException(nameof(observer));
            if (subscription == null)
                throw new ArgumentNullException(nameof(subscription));
            if (keySelector == null)
                throw new ArgumentNullException(nameof(keySelector));
            if (durationSelector == null)
                throw new ArgumentNullException(nameof(durationSelector));
            if (capacity < 0)
                throw new ArgumentOutOfRangeException(nameof(capacity));
            if (comparer == null)
                throw new ArgumentNullException(nameof(comparer));

View on GitHub (pinned to 94b5d5ab91)