Skip to content

Fix reactive data loader hanging on backpressured publishers (#273)#279

Merged
bbakerman merged 1 commit into
graphql-java:masterfrom
zenios:fix/issue-273-reactive-backpressure
Jun 21, 2026
Merged

Fix reactive data loader hanging on backpressured publishers (#273)#279
bbakerman merged 1 commit into
graphql-java:masterfrom
zenios:fix/issue-273-reactive-backpressure

Conversation

@zenios

@zenios zenios commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

The reactive subscribers (AbstractBatchSubscriber and friends) requested keys.size() from the upstream publisher exactly once in onSubscribe and then waited for onComplete to finalise the batch.

A reactive publisher only emits while it has outstanding demand. So a publisher that emits values lazily as more demand arrives - for example one that emits an entry not matching any requested key before the entry that does - consumes that single window of demand and then blocks. The matching value is never requested, the per-key future never completes, and the data loader hangs forever.

This change makes the subscribers manage demand properly:

  • track the outstanding demand and re-request another window whenever it drains to zero, repeating until the publisher completes or errors;
  • once a result has been received for every key there is nothing left to wait for, so cancel the upstream subscription and complete ourselves rather than blocking on a publisher that may never call onComplete.

Adds ReactiveBackpressureTest reproducing the hang for both the list and mapped publisher data loaders; both tests time out against the old code and pass with the fix.

…-java#273)

The reactive subscribers (`AbstractBatchSubscriber` and friends) requested
`keys.size()` from the upstream publisher exactly once in `onSubscribe` and
then waited for `onComplete` to finalise the batch.

A reactive publisher only emits while it has outstanding demand. So a
publisher that emits values lazily as more demand arrives - for example one
that emits an entry not matching any requested key before the entry that
does - consumes that single window of demand and then blocks. The matching
value is never requested, the per-key future never completes, and the data
loader hangs forever.

This change makes the subscribers manage demand properly:

  * track the outstanding demand and re-request another window whenever it
    drains to zero, repeating until the publisher completes or errors;
  * once a result has been received for every key there is nothing left to
    wait for, so cancel the upstream subscription and complete ourselves
    rather than blocking on a publisher that may never call `onComplete`.

Adds `ReactiveBackpressureTest` reproducing the hang for both the list and
mapped publisher data loaders; both tests time out against the old code and
pass with the fix.

@bbakerman bbakerman left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks very much for this. Demand management is an art and often gotten wrong.

I also like the idea we can ignore keys we didn't ask for. Probably not likely but possible and hence handling it is a good idea

@bbakerman bbakerman merged commit f197b5f into graphql-java:master Jun 21, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants