timsaucer opened a new pull request, #24723:
URL: https://github.com/apache/datafusion/pull/24723

   ## Which issue does this PR close?
   
   - Closes #24722.
   
   ## Rationale for this change
   
   Three `datafusion-ffi` constructors unwrap an already-foreign input and 
return its original handle, dropping the arguments passed alongside without an 
error or a warning:
   
   - `FFI_LogicalExtensionCodec::new` — discards `task_ctx_provider`
   - `FFI_PhysicalExtensionCodec::new` — discards `task_ctx_provider`
   - `FFI_TableProvider::new_with_ffi_codec` — discards `logical_codec`
   
   The consequence is that a consumer which imports a foreign codec can never 
rebind it. Re-wrapping with a different provider compiles, runs, and has no 
effect, so the handle keeps resolving against whatever session it was first 
built with. In `datafusion-python` that shows up as decode callbacks resolving 
names against a pre-fork session: a UDF registered after the fork is invisible 
to them, and the config they see is a stale snapshot.
   
   There is a second failure mode with the same root cause. The provider is 
held as a `Weak`, so a consumer that cannot rebind must keep the original 
session alive artificially or the capsule starts failing with 
`TaskContextProvider went out of scope over FFI boundary`.
   
   The two sibling constructors that hit the same case already do the opposite 
— `FFI_QueryPlanner::new_with_ffi_codecs` and 
`FFI_SessionRef::new_with_ffi_codecs` both adopt the supplied codecs on the 
unwrap path, and the former documents that guarantee explicitly. This PR makes 
the other three consistent with them.
   
   ## What changes are included in this PR?
   
   On the already-foreign path, each of the three constructors now clones the 
original handle and overwrites the relevant `#[repr(C)]` field before returning 
it, matching `FFI_QueryPlanner::new_with_ffi_codecs`:
   
   ```rust
   if let Some(codec) = (Arc::clone(&codec) as Arc<dyn Any>)
       .downcast_ref::<ForeignLogicalExtensionCodec>()
   {
       let mut codec = codec.0.clone();
       codec.task_ctx_provider = task_ctx_provider.into();
       return codec;
   }
   ```
   
   The `runtime` argument is a deliberate exception. Unlike the codecs and the 
task context provider, `runtime` lives in `private_data`, which belongs to the 
library that owns the handle — this side cannot write it without an ABI change. 
`FFI_SessionRef::new_with_ffi_codecs` already takes the same position 
("retaining its original private data and runtime"). Rather than leave that 
silent, all three constructors now document it, alongside the new 
adopt-on-unwrap guarantee.
   
   No public signatures change, and no behavior changes on the non-foreign path.
   
   ## Are these changes tested?
   
   Yes — five new unit tests, one per behavior, in each affected module's own 
test module. All five fail on `main` and pass here.
   
   - `ffi_logical_extension_codec_rebind_adopts_task_ctx_provider`
   - `ffi_logical_extension_codec_rebind_releases_original_session` — covers 
the dangling-`Weak` failure mode: session A is dropped after the rebind, and 
the handle stays usable
   - `ffi_physical_extension_codec_rebind_adopts_task_ctx_provider`
   - `test_rebind_foreign_table_provider_adopts_logical_codec`
   - `test_rebind_foreign_query_planner_adopts_codecs` — a control over the 
already-correct sibling, so the two paths stay in agreement
   
   Worth flagging for reviewers, since it is easy to write a test here that 
silently proves nothing: `impl From<&FFI_LogicalExtensionCodec> for Arc<dyn 
LogicalExtensionCodec>` compares `library_marker_id` first and returns the 
original local `Arc` on a match, so within one library the foreign branch is 
never reached. Each test overrides `library_marker_id` with 
`crate::mock_foreign_marker_id` and asserts the import really did produce a 
`Foreign*` wrapper before exercising the rebind.
   
   `cargo test -p datafusion-ffi --all-features` passes (152 tests).
   
   ## Are there any user-facing changes?
   
   Yes, a behavior change, though it replaces a silent no-op with the 
documented intent.
   
   Callers that pass a `task_ctx_provider` or `logical_codec` to these 
constructors alongside an already-foreign input previously had that argument 
ignored; it now takes effect. Anything relying on the old handle being returned 
untouched would see the change — but since the old path gave no way to observe 
or opt into that, it is hard to depend on deliberately.
   
   Downstream, this lets `datafusion-python` drop the workaround in 
https://github.com/apache/datafusion-python/pull/1677, which retains the 
pre-fork `SessionContext` purely to keep the `Weak` valid.
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to