Skip to content

FFI_PlanProperties drops scheduling_type and evaluation_type, so every plan crossing FFI misreports both #25153

Description

@timsaucer

Describe the bug

FFI_PlanProperties (datafusion/ffi/src/plan_properties.rs:38-66) exposes accessors for output_partitioning, emission_type, boundedness, output_ordering, and schema — but not for scheduling_type or evaluation_type. Neither identifier appears anywhere in that file.

Reconstruction on the receiving side goes through PlanProperties::new (plan_properties.rs:189, :280, :299), which initialises both fields to their defaults:

// datafusion/physical-plan/src/execution_plan.rs:1521-1522
evaluation_type: EvaluationType::Lazy,
scheduling_type: SchedulingType::NonCooperative,

So every node that crosses an FFI boundary reports NonCooperative and Lazy regardless of what it actually is.

The visible consequence is in EnsureCooperative, which is the only rule in datafusion/physical-optimizer/src/ that reads either field. It wraps non-cooperative leaves in a CooperativeExec. Given a foreign leaf that is genuinely Cooperative, it sees NonCooperative and wraps it anyway — adding a redundant operator to the plan.

EvaluationType is also load-bearing in that rule: its ancestry walk treats an Eager ancestor as resetting the cooperative context (datafusion/physical-optimizer/src/ensure_coop.rs:100-103). With every foreign node reporting Lazy, that reset never triggers across a boundary.

To Reproduce

Any plan crossing FFI shows it. From a reproduction of the related planner-boundary issue, the same tree printed at two levels:

ForeignExecutionPlan { name: "CooperativeExec",
  properties: PlanProperties { ..., evaluation_type: Lazy,
                               scheduling_type: NonCooperative },
  children: [ EmptyExec { ...,
                cache: PlanProperties { ..., scheduling_type: Cooperative } } ] }

The EmptyExec is Cooperative. The foreign view of it is not — which is why the CooperativeExec above it was inserted at all.

Expected behavior

PlanProperties should round-trip through FFI without losing fields. A foreign node should report the same scheduling_type and evaluation_type as the node it wraps.

Additional context

The fix looks mechanical and follows patterns already in the file:

  • FFI_EmissionType and FFI_Boundedness (plan_properties.rs:201-260) are stabby enums with bidirectional From impls — the same shape works for SchedulingType and EvaluationType.
  • Two more fn-pointer fields on FFI_PlanProperties, matching the existing emission_type / boundedness entries.
  • PlanProperties::with_scheduling_type and with_evaluation_type already exist (datafusion/physical-plan/src/execution_plan.rs:1562,1570) and can be chained onto the three reconstruction sites.

Adding fields to a #[repr(C)] struct is an ABI change, but datafusion-ffi gates compatibility on the crate major version (datafusion/ffi/src/lib.rs:64), which bumps with each DataFusion major release.

This defect is the trigger for the most common symptom in the FFI query planner issue — the spurious CooperativeExec it produces is precisely the node that then fails to serialize. Fixing this does not fix that issue's general class, only its most frequent instance. See umbrella #25152 for the relationship.

Relationship to #22329. That issue covers the same family — FFI silently dropping optimizer-relevant information — but for a different struct: it lists defaulted methods missing from FFI_ExecutionPlan, whereas this is two missing fields on FFI_PlanProperties. Neither scheduling_type nor evaluation_type appears in its list. Worth noting while there: two entries in #22329 have since landed — apply_expressions and partition_statistics are both in the FFI_ExecutionPlan vtable today — so its list needs a refresh.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingffiChanges to the ffi crate

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions