Skip to content

Notify a late-registering mailbox reader about buffered blocks - #19323

Closed
davecromberge wants to merge 1 commit into
apache:masterfrom
davecromberge:fix-contrib/mse-inmemory-mailbox-reader-race
Closed

Notify a late-registering mailbox reader about buffered blocks#19323
davecromberge wants to merge 1 commit into
apache:masterfrom
davecromberge:fix-contrib/mse-inmemory-mailbox-reader-race

Conversation

@davecromberge

Copy link
Copy Markdown
Member

ReceivingMailbox.registerReader only assigned the reader. If a sender had already offered data or EOS, the earlier notifyReader() found no reader and the wake-up was dropped, so the reader blocked until the query deadline and the query failed with EXECUTION_TIMEOUT (250).

Reachable whenever a sender completes before the receiving stage registers — most easily on the in-memory path, where a leaf worker co-located with the consuming stage can finish in ~1ms. Observed in production on a count(*) that otherwise takes single-digit ms.

registerReader now delivers the pending wake-up if blocks or EOS are already buffered, under the existing lock. Three tests cover data-before-registration, EOS-before-registration, and the empty-mailbox case (must not notify).

registerReader only assigned the reader. If a sender had already offered data
or EOS, notifyReader() had found no reader and the wake-up was dropped, so the
reader blocked until the query deadline. Reachable when a sender completes
before the receiving stage registers, most easily on the in-memory path.
@codecov-commenter

codecov-commenter commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 60.00000% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.12%. Comparing base (f5fee8e) to head (03e707d).
⚠️ Report is 6 commits behind head on master.

Files with missing lines Patch % Lines
...g/apache/pinot/query/mailbox/ReceivingMailbox.java 60.00% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff            @@
##             master   #19323   +/-   ##
=========================================
  Coverage     67.12%   67.12%           
  Complexity     1424     1424           
=========================================
  Files          3462     3462           
  Lines        220677   220700   +23     
  Branches      35255    35259    +4     
=========================================
+ Hits         148136   148154   +18     
- Misses        60708    60728   +20     
+ Partials      11833    11818   -15     
Flag Coverage Δ
integration 100.00% <ø> (ø)
integration1 100.00% <ø> (ø)
integration2 0.00% <ø> (?)
java-25 67.12% <60.00%> (+<0.01%) ⬆️
lane-a 100.00% <ø> (ø)
lane-b 0.00% <ø> (ø)
temurin 67.12% <60.00%> (+<0.01%) ⬆️
unittests 67.12% <60.00%> (+<0.01%) ⬆️
unittests1 57.81% <60.00%> (-0.01%) ⬇️
unittests2 39.13% <0.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@yashmayya yashmayya added bug Something is not working as expected multi-stage Related to the multi-stage query engine labels Aug 20, 2026
@yashmayya
yashmayya requested review from gortiz and yashmayya August 20, 2026 18:32

@yashmayya yashmayya left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@davecromberge thanks for the PR. However, I tried to run the scenario with an agent and I do not think the root cause holds. Here's the evidence:

readBlockBlocking polls every mailbox one time before it waits on _newDataReady (BlockingMultiStreamConsumer.java:179), and the consumer registers all readers in its constructor (line 67). Registration always comes first, so that first poll finds any block that arrived before it. The lost wake-up costs nothing here.

I ran three scenarios through the real consumer on master, with a 3-second deadline:

  • data and EOS before registration: 1 ms
  • EOS only before registration: 0 ms
  • two mailboxes, one finished before registration: 203 ms (200 ms was the slow sender)

No scenario waited to the deadline, so I do not think this PR corrects the count(*) timeout.

One path does drop a wake-up permanently. In state UPSTREAM_FINISHED, with _count at 0 and _pendingData more than 0, poll returns null (ReceivingMailbox.java:657). If the EOS is an error EOS, drainDataBlocks makes each parked sender return ALREADY_TERMINATED without a notifyReader call (line 628). The reader then waits to the deadline, and the query reports a timeout instead of the real error. With a full 1-slot queue and 6 parked senders, the reader waited to the deadline in 34 of 40 runs. The same test with a success EOS waited in 0 of 40 runs.

Can you share the evidence that ties your timeout to the registration order? A
No reader to notify log line appears in the benign case too, so this line does
not prove the fault.

@davecromberge

Copy link
Copy Markdown
Member Author

You're right — thanks for digging into it. Registration always precedes the reader's first scan, so the dropped wake-up is redundant with that scan and this cannot be the cause of the timeout. Closing.

Opened #19329 for the _pendingData path you identified, with a deterministic regression test.

davecromberge added a commit to permutive-engineering/pinot that referenced this pull request Aug 21, 2026
).

Notify the mailbox reader when the last pending data offer gives up. An error
EOS drains the queued data blocks and makes every parked sender give up; while
those offers are still in flight `poll` returns null and asks the reader to wait
for them, but a sender that gives up returns ALREADY_TERMINATED without
notifying and the reader coalesces notifications into a single slot. Nothing
wakes the reader again, so it blocks until the query deadline and the query
reports EXECUTION_TIMEOUT instead of the real error.

Replaces the previously carried cherry-pick of
fix-contrib/mse-inmemory-mailbox-reader-race (9c6408a, upstream
apache#19323), which is closed: that registerReader wake-up is provably
unreachable because the reader scans every mailbox before it parks, so a
notification dropped before registration is always redundant with that scan.

Upstream PR: apache#19329 (open)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something is not working as expected multi-stage Related to the multi-stage query engine

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants