Skip to content

test: add an SQL reproducer for the canceled hash join partition hang - #27345

Merged
alamb merged 1 commit into
apache:mainfrom
adriangb:test-21625-slt-reproducer
Nov 13, 2026
Merged

alamb merged 1 commit into
apache:mainfrom
adriangb:test-21625-slt-reproducer

Conversation

@adriangb

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Rationale for this change

#21666 fixed a hang in partitioned hash joins with dynamic filters (first seen as TPC-H Q18 hanging). Its only test is a Rust unit test of the coordinator. No SQL-level test covers the plan shape that triggers the hang, so a planner or execution change could bring it back unnoticed.

What changes are included in this PR?

One new file, join_dynamic_filter_canceled_partition.slt. It is a small, deterministic reproducer:

  • A Partitioned inner join sits on top of a Partitioned child join on the same key, with no repartition between them.
  • The parent build side holds only k = 1, so 3 of the 4 parent partitions have an empty build side. They complete without polling their probe side, so their child partitions are dropped before they report to the shared dynamic-filter coordinator.
  • The one non-empty parent partition polls its child partition, which waits for the dropped partitions. Before the fix, it waits forever.

The hang does not depend on timing: the hash routing of k = 1 is fixed, and a child partition reports only when its parent polls it. An EXPLAIN assertion locks the plan shape the reproducer needs. The file comments link to the issue and describe the sequence of events.

What is the testing strategy for this PR?

  • The new test passes on main.
  • With the fix disabled (the report_canceled_partition call in HashJoinStream::drop replaced by a no-op), the test hangs. It never completed and was killed by a 500 s timeout.
  • With datafusion-cli, the same query hangs 5 of 5 times at 887595660^ and returns 2 rows 5 of 5 times at 887595660.

Note: a regression shows up as a hang, not a failed assertion, so CI would catch it by the job timeout.

Are there any user-facing changes?

No. Test-only.

🤖 Generated with Claude Code

The fix for apache#21625 has only a
Rust unit test. No SQL test covers the plan shape that hung TPC-H Q18.

This adds a sqllogictest that reproduces the hang deterministically. A
partitioned inner join sits on top of a partitioned child join on the same
key. The parent build side holds one key, so 3 of 4 parent partitions are
empty and drop their child partitions before those report to the dynamic
filter. An EXPLAIN assertion locks the plan shape the reproducer needs.

Without the fix in apache#21666, the
query hangs. With the fix, it returns 2 rows.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@github-actions github-actions Bot added the sqllogictest SQL Logic Tests (.slt) label Nov 11, 2026
@adriangb

Copy link
Copy Markdown
Contributor Author

@kosiew could I have you take a look at this?

@kosiew kosiew 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.

@adriangb,

Thanks for adding SQL-level regression coverage for the canceled hash join partition hang. The test constructs the required nested partitioned join shape, verifies it through EXPLAIN, and checks that execution completes with the expected duplicate-preserving result.

I found no correctness or compatibility concerns, and the test passes locally. The inline suggestions are optional maintenance improvements, not acceptance requirements. I am approving the change.

# The reproducer needs this exact shape: both joins Partitioned, `s` on the
# parent build side, and the child join directly on the parent probe side.
query TT
EXPLAIN SELECT s.k, ab.v

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.

Consider setting datafusion.explain.physical_plan_only = true around this EXPLAIN and resetting it afterward. The physical plan already captures all cancellation prerequisites, while the logical-plan snapshot adds maintenance overhead from unrelated formatting changes.

statement ok
set datafusion.execution.target_partitions = 4;

# Force both joins into Partitioned mode.

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.

Consider explicitly setting datafusion.optimizer.enable_dynamic_filter_pushdown = true and resetting it during cleanup to document the coordinator prerequisite. This also enables join dynamic-filter pushdown, so a separate setting is unnecessary; both currently default to true, and the existing EXPLAIN already guards against missing filters.

@alamb
alamb added this pull request to the merge queue Nov 13, 2026
Merged via the queue into apache:main with commit 49f64f9 Nov 13, 2026
44 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

sqllogictest SQL Logic Tests (.slt) v57.0.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants