Skip to content

fix(sdk): error callback should await shard flush on producer shutdown - #3953

Open
haubur wants to merge 2 commits into
apache:masterfrom
haubur:fix/producer-shutdown
Open

fix(sdk): error callback should await shard flush on producer shutdown#3953
haubur wants to merge 2 commits into
apache:masterfrom
haubur:fix/producer-shutdown

Conversation

@haubur

@haubur haubur commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR address?

Closes #3947

Rationale

On shutdown() the IggyProducer calls the shutdown() method of the Dispatcher which broadcasts
a termination signal to it's workers. This immediately breaks the loop of the error callback task, which then
does not receive any errors from the final flush anymore.

What changed?

The error callback task does not receive the stop signal anymore. The channel is still open,
and shards can put errors in the queue. On join in shutdown() (after shard tasks are joined) the
error callback is awaited for as long as the queue is emptied.

Local Execution

  • Passed
  • Pre-commit hooks ran

AI Usage

Test proposed by Claude code.

@github-actions

Copy link
Copy Markdown

Thanks for the PR. It is labeled S-waiting-on-review and queued for review.

Slash commands (own line, regular comment) move it around the queue:

  • /ready - back to S-waiting-on-review after addressing feedback
  • /author - flip to S-waiting-on-author while you finish changes
  • /request-review @user-or-team - request a reviewer

See CONTRIBUTING.md for details.

@github-actions github-actions Bot added the S-waiting-on-review PR is waiting on a reviewer label Aug 22, 2026
@codecov

codecov Bot commented Aug 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.23810% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.95%. Comparing base (1a98748) to head (ffd2238).

Files with missing lines Patch % Lines
core/sdk/src/clients/producer_dispatcher.rs 95.23% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##             master    #3953       +/-   ##
=============================================
- Coverage     83.99%   69.95%   -14.05%     
  Complexity     1358     1358               
=============================================
  Files          1215     1215               
  Lines        170548   146054    -24494     
  Branches     138292   113799    -24493     
=============================================
- Hits         143252   102168    -41084     
- Misses        23439    39997    +16558     
- Partials       3857     3889       +32     
Components Coverage Δ
Rust Core 66.72% <95.23%> (-18.17%) ⬇️
Java SDK 66.67% <ø> (ø)
C# SDK 75.03% <ø> (+0.07%) ⬆️
Python SDK 90.13% <ø> (ø)
PHP SDK 84.48% <ø> (ø)
Node SDK 95.90% <ø> (+0.09%) ⬆️
Go SDK 68.29% <ø> (ø)
Files with missing lines Coverage Δ
core/sdk/src/clients/producer_dispatcher.rs 94.53% <95.23%> (+0.56%) ⬆️

... and 318 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

let (stop_tx, _) = broadcast::channel::<()>(1);

let mut stop_rx = stop_tx.subscribe();
// A task that receives errors from shards when writing messages failed.

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.

restates what the spawn does, drop it.


// After shards are closed await the error callback task,
// that might drain queued errors from the final flush.
if let Err(e) = self._join_handle.await {

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.

_join_handle is awaited here, the underscore prefix says unused.


// Passes, if the error callback is hit once after final
// flush from shutdown.
assert_eq!(error_called.load(Ordering::SeqCst), 1);

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.

the stub drops ctx, so this proves the callback ran but not that the failed batch came back with it - which is the point of #3947. record ctx.messages.len() in the stub and assert it is 1.

let mut mock = MockProducerCoreBackend::new();
// mock a failed send to be caught by the error callback
mock.expect_send_internal()
.times(1)

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.

.times(1) is not enforced here - the last Arc owner of the mock is the shard task, so a violated expectation panics there and shutdown() swallows it as "shard panicked". the error_called assert carries the test, just don't rely on times.

#[tokio::test]
async fn test_shutdown_reports_errors_from_final_flush() {
let mut mock = MockProducerCoreBackend::new();
// mock a failed send to be caught by the error callback

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.

also at 448, 466, 469. these restate the code, drop them (and the stray "block/ ").

@github-actions github-actions Bot added S-waiting-on-author PR is waiting on author response and removed S-waiting-on-review PR is waiting on a reviewer labels Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-author PR is waiting on author response

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(Rust SDK): producer shutdown() error_callback is closed immediately, misses potential flush failures

2 participants