Skip to content

fix: restore per-element demand replenishment across async boundary - #3461

Draft
He-Pin wants to merge 2 commits into
apache:mainfrom
He-Pin:fix/3459-demand-replenishment-regression
Draft

fix: restore per-element demand replenishment across async boundary#3461
He-Pin wants to merge 2 commits into
apache:mainfrom
He-Pin:fix/3459-demand-replenishment-regression

Conversation

@He-Pin

@He-Pin He-Pin commented Aug 24, 2026

Copy link
Copy Markdown
Member

Motivation

PR #3288 batched elements at the async output boundary and flushed them when the interpreter parked. This deferred the demand round-trip to once per flush cycle (~16 elements) instead of per element, silently breaking external-source stages that drop elements when isAvailable(out) is false. The pekko-connectors UDP connector loses ~15/16 datagrams on M4.

Fixes #3459

Modification

Revert the output-side batching: send each element directly to the BoundarySubscriber via next() during onPush(), restoring the pre-M4 timing where demand is replenished after every element. Remove the now-unused enqueueBatchElement/flushBatch/clearBatch/flushOutputs machinery.

The BoundarySubscriber trait, OnNextBatch event class, and input-side receiveNextBatch are retained as internal API surface (no behavioral change on the input side).

Result

Demand across an internal async boundary is again replenished per element, fixing silent datagram loss in the UDP connector and any other stage that treats "no demand right now" as a signal to drop.

Tests

  • sbt "stream-tests / Test / testOnly org.apache.pekko.stream.FusingSpec" -- 19/19 passed
  • sbt "stream-tests / Test / testOnly org.apache.pekko.stream.scaladsl.PublisherSinkSpec org.apache.pekko.stream.io.FileSinkSpec org.apache.pekko.stream.scaladsl.FlowMapWithResourceSpec org.apache.pekko.stream.scaladsl.FlowSpec" -- 78/78 passed
  • sbt "stream / mimaReportBinaryIssues" -- passed

References

Fixes #3459

Motivation:
PR apache#3288 batched elements at the async output boundary and flushed them
when the interpreter parked. This deferred the demand round-trip to once
per flush cycle instead of per element, silently breaking external-source
stages (e.g. pekko-connectors UDP) that drop elements when isAvailable(out)
is false. See apache#3459.

Modification:
Revert the output-side batching: send each element directly to the
BoundarySubscriber via next() during onPush(), restoring the pre-M4
timing where demand is replenished after every element. Remove the
now-unused enqueueBatchElement/flushBatch/clearBatch/flushOutputs
machinery.

Result:
Demand across an internal async boundary is again replenished per
element, fixing silent datagram loss in the UDP connector and any
other stage that treats "no demand right now" as a signal to drop.

Tests:
- sbt "stream-tests / Test / testOnly org.apache.pekko.stream.FusingSpec" -- 19/19 passed
- sbt "stream-tests / Test / testOnly org.apache.pekko.stream.scaladsl.PublisherSinkSpec org.apache.pekko.stream.io.FileSinkSpec org.apache.pekko.stream.scaladsl.FlowMapWithResourceSpec org.apache.pekko.stream.scaladsl.FlowSpec" -- 78/78 passed
- sbt "stream / mimaReportBinaryIssues" -- passed

References:
Fixes apache#3459
@He-Pin
He-Pin marked this pull request as draft August 24, 2026 03:59
…e#3459)

Motivation:
External-source stages (e.g. UDP connector) that push via async callback
and check isAvailable(out) before pushing need per-element demand
replenishment across async boundaries. Without a directional test, this
behavior can silently regress again.

Modification:
Add a FusingSpec test that creates a GraphStageWithMaterializedValue
exposing an AsyncCallback, offers 100 elements rapidly across an async
boundary, and asserts zero drops.

Result:
The UDP connector failure pattern from apache#3459 is now covered by a
directional regression test in pekko core.

Tests:
- sbt "stream-tests / Test / testOnly org.apache.pekko.stream.FusingSpec" -- 20/20 passed

References:
Refs apache#3459
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.

Stream: demand across async boundary replenished per batch, not per element, since 2.0.0-M4

1 participant