Skip to content

[Dataflow Streaming] [MultiKey] Handle work cancellation exceptions in WorkFailureProcessor. - #40955

Merged
scwhittle merged 3 commits into
apache:masterfrom
arunpandianp:handle_work_cancellation_exception
Nov 11, 2026
Merged

scwhittle merged 3 commits into
apache:masterfrom
arunpandianp:handle_work_cancellation_exception

Conversation

@arunpandianp

Copy link
Copy Markdown
Contributor

When processing a multi-key bundle, if one key fails due to a Windmill work cancellation (WorkCancellingException or WorkItemCancelledException), unaffected sibling keys in the batch must not be misclassified as uncaught user exceptions.

This change adds WorkItemCancelledException.isWorkItemCancelledException(), updates WorkFailureProcessor.evaluateRetry() to return RetryEvaluation.RETRY_LOCALLY without reporting the failure or sleeping, and prevents infinite loops on circular causal chains in both isWorkCancellingException and isWorkItemCancelledException.

@github-actions github-actions Bot added runners dataflow size/s S: 10-49 lines changed labels Nov 2, 2026

@github-actions github-actions Bot 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.

📋 Review Summary

This PR handles cyclic causal chains when inspecting exceptions in WorkCancellingException, WorkItemCancelledException, and WorkFailureProcessor, preventing StackOverflowErrors or infinite loops during failure processing. The implementation using identity-based Sets (Collections.newSetFromMap(new IdentityHashMap<>())) and the accompanying unit tests are clean, idiomatic, and thorough.

🔍 General Feedback

  • The use of IdentityHashMap-backed sets avoids invoking potentially expensive or state-dependent equals/hashCode methods on Throwable instances while accurately detecting reference cycles.
  • Loop termination conditions in WorkCancellingException, WorkItemCancelledException, and WorkFailureProcessor.tryUnwrapException are safe and preserve existing unwrapping semantics.
  • Unit tests in WorkFailureProcessorTest cover both cyclic unwrapping and cyclic cause inspection for the cancellation exceptions.

💡 Tip: Comment /gemini review on this pull request to trigger a new Gemini code review.

@arunpandianp
arunpandianp force-pushed the handle_work_cancellation_exception branch 2 times, most recently from 99df0c2 to 6c2679b Compare November 2, 2026 23:52
When processing a multi-key bundle, if one key fails due to a Windmill work cancellation (WorkCancellingException or WorkItemCancelledException), unaffected sibling keys in the batch must not be misclassified as uncaught user exceptions.

This change adds WorkItemCancelledException.isWorkItemCancelledException(), updates WorkFailureProcessor.evaluateRetry() to return RetryEvaluation.RETRY_LOCALLY without reporting the failure or sleeping, and prevents infinite loops on circular causal chains in both isWorkCancellingException and isWorkItemCancelledException.
@arunpandianp
arunpandianp force-pushed the handle_work_cancellation_exception branch from 6c2679b to 25d1573 Compare November 2, 2026 23:54
@github-actions github-actions Bot added size/m M: 50-249 lines changed and removed size/s S: 10-49 lines changed labels Nov 2, 2026
@arunpandianp

Copy link
Copy Markdown
Contributor Author

R: @scwhittle

@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Stopping reviewer notifications for this pull request: review requested by someone other than the bot, ceding control. If you'd like to restart, comment assign set of reviewers

Set<Throwable> seen = Collections.newSetFromMap(new IdentityHashMap<>());
@Nullable Throwable throwable = t;
while (throwable != null) {
while (throwable != null && seen.add(throwable)) {

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.

do we actually have such cycles or is this more theoretical?

Is it worth avoiding the allocation in the case the input throwable doesn't have a cause?

I think there are other such cases where we iterate a throwable chain. Maybe we should have a helper taking the throwable class and doing this iteration, and use it in other cases too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

do we actually have such cycles or is this more theoretical?

Theoretical. Updated to avoid allocations and extracted logic to a common util.

}

if (WorkCancellingException.isWorkCancellingException(parsedException)
|| WorkItemCancelledException.isWorkItemCancelledException(parsedException)) {

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.

Should we have a helper that checks both? We won't have to iterate the throwable chain twice

Or should we change WorkCancellingException to WorkItemCancelledException after setting failed in the Work.java?

@arunpandianp arunpandianp Nov 7, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated to a helper checking both. Want to keep WorkCancellingException and WorkItemCancelledException separate as code catching them do different things today.

@arunpandianp
arunpandianp force-pushed the handle_work_cancellation_exception branch 2 times, most recently from 4d1b2e0 to b72fb40 Compare November 7, 2026 01:30
@arunpandianp
arunpandianp force-pushed the handle_work_cancellation_exception branch from b72fb40 to e05a432 Compare November 7, 2026 01:33
}

/** Returns whether {@code throwable} or any of its causes matches the given {@code predicate}. */
public static boolean hasAnyCause(@Nullable Throwable throwable, Predicate<Throwable> predicate) {

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.

how about hasMatchingCause? hasAnyCause is confusing to me

@codecov

codecov Bot commented Nov 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.83333% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 75.96%. Comparing base (12bbb17) to head (45920f9).
⚠️ Report is 187 commits behind head on master.

Files with missing lines Patch % Lines
...nners/dataflow/worker/OutputTooLargeException.java 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master   #40955      +/-   ##
============================================
- Coverage     79.83%   75.96%   -3.87%     
- Complexity        0     2291    +2291     
============================================
  Files           494      259     -235     
  Lines         83575    14421   -69154     
  Branches          0     1487    +1487     
============================================
- Hits          66722    10955   -55767     
+ Misses        16853     2775   -14078     
- Partials          0      691     +691     
Flag Coverage Δ
java 75.96% <95.83%> (?)
python ?

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:
  • ❄️ 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.

@scwhittle
scwhittle merged commit 2e5d84e into apache:master Nov 11, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dataflow runners size/m M: 50-249 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants