What do Subscription.request(n) and Subscription.cancel() do, and what are the rules around calling them?
answer
- request(n) = demand, additive/cumulative
- n<=0 -> onError(IllegalArgumentException)
- Long.MAX_VALUE = unbounded (default subscribe)
- cancel() = stop + release, idempotent, best-effort
- only the Subscriber calls them, they go upstream
basics
~20 srequest(n) tells the Publisher the Subscriber is ready to receive up to n more elements — it is the demand signal. cancel() tells the Publisher to stop emitting and release resources. Both are called by the Subscriber on its Subscription.
solid answer
~50 sSubscription has two methods, both invoked by the Subscriber. request(long n) signals demand: it permits the Publisher to send up to n additional onNext elements, and demand is cumulative and additive across calls. n must be positive; requesting n <= 0 is a spec violation and the Publisher must respond with onError (IllegalArgumentException). request(Long.MAX_VALUE) means effectively unbounded demand (fast path, no flow control). cancel() asks the Publisher to stop sending and clean up; after cancel the Subscriber may still receive already-in-flight signals but should expect no new ones, and cancel is idempotent. Both request and cancel must be safe to call from within onNext/onSubscribe and are the Subscriber's only levers on the stream. This demand mechanism is exactly what Reactor builds its operators on; in Reactor you call them via BaseSubscriber.request/cancel or let operators manage them.
code
java · 19 linesimport org.reactivestreams.Subscription;
import reactor.core.publisher.BaseSubscriber;
import reactor.core.publisher.Flux;
Flux.range(1, 100).subscribe(new BaseSubscriber<Integer>() {
@Override protected void hookOnSubscribe(Subscription s) {
request(2); // demand exactly 2 to start (NOT Long.MAX_VALUE)
}
@Override protected void hookOnNext(Integer value) {
System.out.println("got " + value);
if (value == 4) {
cancel(); // stop early; idempotent, releases upstream
} else {
request(1); // cumulative: ask for one more each time
}
}
});
// Prints got 1, got 2, got 3, got 4, then cancels — never reaches 100.
// request(0) or request(-1) here would instead trigger onError(IllegalArgumentException).go deeper
Knows request means 'ask for items' and cancel means 'stop'.
Knows demand is cumulative, n>0 required, Long.MAX_VALUE = unbounded.
Explains the n<=0 onError rule, cancel idempotency, and best-effort trailing signals.
Connects request/cancel to how operators (take/limitRate) and WebFlux client-disconnect drive the stream.
`Subscription` is the private, one-to-one channel between a single Publisher and a single Subscriber, handed to the Subscriber in `onSubscribe`. It exposes exactly two methods, and **only the Subscriber calls them** — they flow *upstream*. **`request(long n)` — demand signaling:** - Tells the Publisher: 'you may now send me up to `n` more `onNext` elements.' Nothing is emitted until the Subscriber requests. - Demand is **additive/cumulative**: `request(3)` then `request(2)` yields an outstanding demand of 5. The Publisher tracks the running total and must never emit more `onNext` than has been requested. - **`n` must be > 0.** Calling `request(n)` with `n <= 0` is a specification violation (Rule 3.9); the Publisher must signal `onError` with an `IllegalArgumentException`. This is a favorite interview trap. - **`request(Long.MAX_VALUE)`** is treated as **effectively unbounded** demand — the Publisher may emit freely without further requests. Reactor's `subscribe()` with no explicit demand and operators like `.subscribe(consumer)` request `Long.MAX_VALUE` by default, which is why simple pipelines 'just run'. - `request` may be called re-entrantly (e.g., from inside `onNext`) — the spec requires the Publisher to tolerate this without unbounded recursion (Rule 3.3, bounded stack). **`cancel()` — teardown:** - Tells the Publisher the Subscriber wants no more elements and the Publisher should stop and release resources (close connections, cancel timers, etc.). - **Idempotent**: calling `cancel()` multiple times has no additional effect (Rule 3.7). - After `cancel()`, the Publisher should *eventually* stop, but because everything is asynchronous, a few already-dispatched `onNext` signals may still arrive; the Subscriber must tolerate this (best-effort, not instantaneous). - Cancellation is how operators like `take(n)`, `timeout`, and `next()` stop an upstream early, and how WebFlux aborts a stream when the HTTP client disconnects. **Relationship to the rest of the contract:** - `request`/`cancel` are the *upstream* half of the protocol; `onNext`/`onError`/`onComplete` are the *downstream* half. Together they form a push model bounded by pull-based demand. - Terminal signals (`onComplete`/`onError`) implicitly end the subscription — you don't cancel after a terminal. **Reactor usage:** Extend `reactor.core.publisher.BaseSubscriber` and call `request(n)`/`cancel()` (or `requestUnbounded()`); or rely on operators. `Flux.range(1, 100).take(3)` internally requests, receives 3, then cancels upstream. **Gotchas:** - Forgetting to call `request(n)` in a hand-rolled Subscriber means the stream hangs — nothing is ever emitted. - Requesting 0 or negative is not 'request nothing', it is an error condition. - `cancel()` is not a guarantee of immediate silence; design for a possible trailing element. - Note: detailed *backpressure strategies* (buffer/drop/latest/error on overflow) belong to the backpressure topic — here the focus is purely the `request(n)`/`cancel()` primitives the spec defines.
- What happens if you call request(0) or request(-1)?It is a spec violation (Rule 3.9). The Publisher must respond by signaling onError with an IllegalArgumentException. It does not mean 'pause' or 'request nothing' — those semantics don't exist; you simply stop calling request to pause.
- After cancel(), can the Subscriber still receive onNext?Possibly. Cancellation is asynchronous and best-effort, so signals already in flight may still be delivered. A correct Subscriber tolerates a few trailing onNext calls after cancel; it just should not expect any *new* work to start.
- How does Reactor's default .subscribe(consumer) request demand?It requests Long.MAX_VALUE — unbounded demand — so the whole sequence runs without manual flow control. To exert flow control you supply a custom Subscriber (e.g., BaseSubscriber) or use operators like limitRate that re-window the requests.
saying these in an interview costs you the question
- Saying request(0) pauses or requests nothing
- Thinking the Publisher calls request/cancel
- Believing demand is per-call, not cumulative
- Assuming cancel() stops delivery instantly with zero trailing elements
- Not knowing Long.MAX_VALUE means unbounded