Change owners, step through generations, pinning and finalization, or compare memory under load. The model explains concepts; the SDK examples measure real runtime observations.
Open the Memory Lab ↗Overload creates reachable work
A healthy collector cannot reclaim request payloads that pending work still owns. When arrival rate exceeds completion capacity, in-flight operations and queues can grow faster than the process can finish them. A memory incident during downstream slowness may therefore be a workload admission problem rather than a collector defect. Start with counts of active requests, queued payloads, waiting producers and simultaneous copies, then connect those owners to actual retained types.
Backpressure is a lifecycle policy: it decides who may enter, where waiting happens, what state waiting retains, and how cancellation ends that state. A bounded channel helps define one queue, but it does not automatically bound every caller waiting to write to it. If ten thousand callers allocate their large payload before awaiting a bounded channel, those waiting tasks can still retain ten thousand payloads outside the channel.
Acquire admission before expensive ownership
Place a bounded admission boundary before reading or materializing a large payload where the protocol permits. If waiting callers are allowed, bound their count or ensure their retained state is small and separately budgeted. A semaphore can limit active work while still allowing an unbounded number of WaitAsync callers, so the surrounding acceptance policy matters. Upstream servers, connection buffers and client behavior also contribute to the real system budget.
Choose an explicit policy for a full boundary: wait with a deadline, reject with a defined response, or drop only where the business contract permits loss. Cancellation must remove or finish the correct ownership path; signaling a token is not enough if the queued item or continuation still retains the payload. Return pooled buffers only after consumers stop, including failure and cancellation paths.
Count simultaneous copies, not just requests
A request may own the original body, a decompressed representation, a parsed object graph and a serialized output at the same time. A payload-size estimate multiplied only by request count can therefore substantially understate the live set. Streaming helps only when downstream stages do not rematerialize the complete data. Pool capacity and native-library buffers add further owners beyond the logical payload.
Replicas multiply per-process baselines and pools. Scaling out can reduce active work per instance while increasing total retained idle capacity across the fleet. Conversely, one hot partition can overload a single replica even when the fleet average looks safe. Measure per-instance and aggregate behavior, and include the slowest realistic downstream dependency rather than assuming ideal service time everywhere.
Trace the checked budget program
The portable program explicitly models four active workers, eight queued payloads, 256 KiB per payload and two simultaneous copies. Multiplying twelve owners by 256 and two gives 6,144 KiB. Changing only the queue to eight hundred raises the modeled payload ownership to 411,648 KiB. These are arithmetic consequences of stated assumptions, not sampled heap bytes or RSS.
The long multiplication begins before potentially large products, and checked arithmetic makes overflow a visible failure rather than a wrapped negative budget. Production estimates must validate inputs and distinguish KiB from decimal kilobytes. The program's final line labels the calculation as a model because object overhead, native memory, pool retention and other process state are absent. Adding those quantities requires nonoverlapping accounting.
Explore the interactive load budget
The budget tab assumes a ten-second fluid arrival interval. It computes service capacity from worker count and per-request service time, estimates active requests, and grows backlog at the positive difference between arrivals and capacity up to the configured queue. Increase service time to model a slow dependency, then increase queue capacity without changing worker count. Observe that a larger queue absorbs more owned payloads but does not improve service capacity.
The baseline, idle pool allowance and native allowance are deliberately separate from active payload copies. They must not double count the same arrays. The model does not predict p99 latency, GC pause distributions, scheduler fairness or exact RSS. It is a tool for testing assumptions and finding a plausible budget before measuring a representative application under real quotas and traffic.
Design a cancellation-safe pipeline
Write down the owner at each stage: admission, input read, queue, worker, downstream write and response completion. A successful enqueue can transfer ownership to the queue; a failed or cancelled enqueue must leave one clearly responsible owner that disposes or returns the item. If the queue drops an item, the drop policy needs a resource-release path. Channel completion and task completion are separate events, so shutdown must drain or dispose remaining owned items deliberately.
For an interview, ask what happens when every producer waits while holding a rented one-megabyte buffer. A strong answer identifies the unbounded ownership outside the channel, moves admission earlier and specifies full-queue behavior. It then proposes evidence: peak waiting callers, active leases, bytes retained by stage, cancellation cleanup and throughput under a slow consumer. Merely increasing GC frequency does not solve that graph.
Practice and review
Use the interactive comparison button to increase queue capacity to ten thousand. Keep arrival and service rates fixed and explain why capacity is unchanged while retained payload ownership rises. Restore the bounded queue and discuss where rejected or waiting callers go. If they retain full payloads elsewhere, add them to the model rather than declaring the system bounded.
Extend the portable budget with one MiB of independent per-worker workspace and a retained idle pool allowance. Keep the accounting nonoverlapping and test zero, negative and overflow inputs. Finish with a shutdown timeline covering queued, active and not-yet-admitted requests, including which component releases each buffer when cancellation interrupts the pipeline.
A working example
int workers = 4, queued = 8, payloadKiB = 256, copies = 2;
long liveKiB = checked((long)(workers + queued) * payloadKiB * copies);
Console.WriteLine($"bounded-live-KiB:{liveKiB}");
long overloadedKiB = checked((long)(workers + 800) * payloadKiB * copies);
Console.WriteLine($"large-queue-live-KiB:{overloadedKiB}");
Console.WriteLine("budget:model;not-measured-RSS");
Expected output
bounded-live-KiB:6144 large-queue-live-KiB:411648 budget:model;not-measured-RSS
Trace ownership before inferring collection. Expected portable output: bounded-live-KiB:6144 large-queue-live-KiB:411648 budget:model;not-measured-RSS