Skip to content

Make FilterExec cooperative for all-rows-rejected loops#419

Draft
mpurins-coralogix wants to merge 3 commits intov50.3from
mpurins/filter-exec
Draft

Make FilterExec cooperative for all-rows-rejected loops#419
mpurins-coralogix wants to merge 3 commits intov50.3from
mpurins/filter-exec

Conversation

@mpurins-coralogix
Copy link
Copy Markdown
Collaborator

When a FilterExec predicate rejects every row in every batch, the inner loop in FilterExecStream::poll_next would spin without yielding to Tokio: each iteration consumed an input batch, produced an empty filtered batch, hit continue, and never exited the loop. On unbounded inputs this monopolised the worker and made the task uncancellable.

Insert a cooperative yield point at the top of each loop iteration and account for predicate work after each batch. Three variants gated on cfg(datafusion_coop) to mirror crate::coop::CooperativeStream:

  • tokio (default): poll_proceed(cx) + made_progress() per batch.
  • tokio_fallback: has_budget_remaining() check + drive consume_budget() once with the current cx after each batch.
  • per_stream: local u8 budget counter on FilterExecStream, reset to FILTER_YIELD_FREQUENCY = 128 (matches Tokio's task budget) after each yield.

Mark FilterExec as SchedulingType::Cooperative so the EnsureCooperative optimizer rule does not wrap it.

Add a discriminating integration test
filter_reject_all_batches_yields_without_cooperative_input backed by a self-contained NonCooperativeInfiniteExec test fixture and a query_yields_no_ensure_cooperative helper that bypasses the optimizer rule. The test fails (10s timeout) without this change and passes with it, isolating the cooperative behavior to FilterExec itself.

Which issue does this PR close?

Closes #.

Rationale for this change

What changes are included in this PR?

Are there any user-facing changes?

When a `FilterExec` predicate rejects every row in every batch, the
inner `loop` in `FilterExecStream::poll_next` would spin without
yielding to Tokio: each iteration consumed an input batch, produced an
empty filtered batch, hit `continue`, and never exited the loop. On
unbounded inputs this monopolised the worker and made the task
uncancellable.

Insert a cooperative yield point at the top of each loop iteration and
account for predicate work after each batch. Three variants gated on
`cfg(datafusion_coop)` to mirror `crate::coop::CooperativeStream`:

- `tokio` (default): `poll_proceed(cx)` + `made_progress()` per batch.
- `tokio_fallback`: `has_budget_remaining()` check + drive
  `consume_budget()` once with the current cx after each batch.
- `per_stream`: local `u8` budget counter on `FilterExecStream`,
  reset to `FILTER_YIELD_FREQUENCY = 128` (matches Tokio's task budget)
  after each yield.

Mark `FilterExec` as `SchedulingType::Cooperative` so the
`EnsureCooperative` optimizer rule does not wrap it.

Add a discriminating integration test
`filter_reject_all_batches_yields_without_cooperative_input` backed by
a self-contained `NonCooperativeInfiniteExec` test fixture and a
`query_yields_no_ensure_cooperative` helper that bypasses the
optimizer rule. The test fails (10s timeout) without this change and
passes with it, isolating the cooperative behavior to FilterExec
itself.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@mpurins-coralogix mpurins-coralogix requested a review from a team April 28, 2026 15:26
@github-actions github-actions Bot added the core label Apr 28, 2026
@avantgardnerio
Copy link
Copy Markdown

Standard questions: does this duplicate functionality that was solved in upstream at some point ahead of 50? If so, what PR, if not can we PR upstream.

@mpurins-coralogix
Copy link
Copy Markdown
Collaborator Author

Not this is new and has not been solved upstream. I have been following with one eye coop work in datafusion, and unless I missed something then it was never recognised that FilterExec needs fix as well.

@avantgardnerio
Copy link
Copy Markdown

Not this is new and has not been solved upstream. I have been following with one eye coop work in datafusion, and unless I missed something then it was never recognised that FilterExec needs fix as well.

Would you mind filing an issue for it? It would be best if upstream could extend their solution to cover filter exec, and then we can stop re-basing this commit during future upgrades.

@mpurins-coralogix
Copy link
Copy Markdown
Collaborator Author

@mpurins-coralogix
Copy link
Copy Markdown
Collaborator Author

also let's not merge this -- it's more of a poc/draft right now

@mpurins-coralogix mpurins-coralogix marked this pull request as draft April 28, 2026 16:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants