dotnet/reactive · error · InvalidOperationException

Strings_Core.HEAP_EMPTY

Error message

Strings_Core.HEAP_EMPTY

What it means

Peek() on System.Reactive's internal PriorityQueue throws InvalidOperationException when the queue is empty (_size == 0). The queue requires at least one scheduled item to inspect. The library treats peeking into an empty priority queue as a programming error rather than returning a default value.

Solutions

  1. Check Count > 0 before calling Peek, or use TryPeek-style logic (Count check plus Peek in one thread-safe step).
  2. If queue may be consumed concurrently, synchronize access with a lock around Count/Peek/Dequeue calls.
  3. Wrap Peek in try-catch for InvalidOperationException only if racing behavior is tolerated and treated as 'queue went empty'.

Example fix

// before
var next = queue.Peek();
// after
if (queue.Count == 0) return;
var next = queue.Peek();
Defensive patterns

Strategy: validation

Validate before calling

if (queue.Count > 0) { var next = queue.Peek(); ... }

Try / catch

try { next = queue.Peek(); } catch (InvalidOperationException) { /* queue became empty */ }

Prevention

When it happens

Trigger: Calling PriorityQueue<T>.Peek() when _size == 0, i.e. before any Enqueue or after all items have been dequeued/removed. The index also surfaces via scheduled work algorithms that Peek before checking Count.

Common situations: Custom scheduler implementations or tests that call Peek on a freshly created queue; race conditions where another thread dequeued the last item between a Count check and Peek (Count/Peek not atomic).

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at Rx.NET/Source/src/System.Reactive/Internal/PriorityQueue.cs:90

                if (first == index)
                {
                    break;
                }

                // swap index and first
                (_items[first], _items[index]) = (_items[index], _items[first]);
                index = first;
            }
        }

        public int Count => _size;

        public T Peek()
        {
            if (_size == 0)
            {
                throw new InvalidOperationException(Strings_Core.HEAP_EMPTY);
            }

            return _items[0].Value;
        }

        private void RemoveAt(int index)
        {
            _items[index] = _items[--_size];
            _items[_size] = default;

            if (Percolate(index) == index)
            {
                Heapify(index);
            }

            if (_size < _items.Length / 4)
            {
                var temp = _items;

View on GitHub (pinned to 94b5d5ab91)