From 81d80013827a997387aa9deb66e4f79672dee69d Mon Sep 17 00:00:00 2001 From: Onur Satici Date: Thu, 16 Jul 2026 13:47:49 +0100 Subject: [PATCH 1/4] Expose FFI runtime worker pool control Signed-off-by: Onur Satici --- vortex-ffi/cinclude/vortex.h | 13 +++++++++++++ vortex-ffi/src/lib.rs | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/vortex-ffi/cinclude/vortex.h b/vortex-ffi/cinclude/vortex.h index 1c2caa6fad8..2c9676db095 100644 --- a/vortex-ffi/cinclude/vortex.h +++ b/vortex-ffi/cinclude/vortex.h @@ -681,6 +681,19 @@ typedef struct { extern "C" { #endif // __cplusplus +/** + * Set the number of background worker threads driving the shared FFI runtime. + * + * This setting is process-global. Passing zero disables background execution. Increasing the + * count starts workers immediately; decreasing it signals excess workers to stop. + */ +void vx_runtime_set_worker_threads(size_t worker_threads); + +/** + * Return the configured number of background worker threads driving the shared FFI runtime. + */ +size_t vx_runtime_worker_count(void); + /** * Increase reference count on vx_array */ diff --git a/vortex-ffi/src/lib.rs b/vortex-ffi/src/lib.rs index 151359096d4..1e2f2237067 100644 --- a/vortex-ffi/src/lib.rs +++ b/vortex-ffi/src/lib.rs @@ -45,15 +45,36 @@ use vortex::dtype::FieldName; use vortex::error::VortexResult; use vortex::error::vortex_ensure; use vortex::io::runtime::current::CurrentThreadRuntime; +use vortex::io::runtime::current::CurrentThreadWorkerPool; #[cfg(all(feature = "mimalloc", not(miri)))] #[global_allocator] static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; /// A shared runtime for all FFI operations. -// TODO(ngates): also create a CurrentThreadPool to manage background worker threads. static RUNTIME: LazyLock = LazyLock::new(CurrentThreadRuntime::new); +/// Background workers that drive the shared FFI runtime. +/// +/// The pool starts empty so existing callers retain current-thread execution until they opt in +/// with [`vx_runtime_set_worker_threads`]. +static POOL: LazyLock = LazyLock::new(|| RUNTIME.new_pool()); + +/// Set the number of background worker threads driving the shared FFI runtime. +/// +/// This setting is process-global. Passing zero disables background execution. Increasing the +/// count starts workers immediately; decreasing it signals excess workers to stop. +#[unsafe(no_mangle)] +pub extern "C" fn vx_runtime_set_worker_threads(worker_threads: usize) { + POOL.set_workers(worker_threads); +} + +/// Return the configured number of background worker threads driving the shared FFI runtime. +#[unsafe(no_mangle)] +pub extern "C" fn vx_runtime_worker_count() -> usize { + POOL.worker_count() +} + /// Return the shared FFI runtime for layered FFI crates that drive Vortex streams produced through /// `vortex-ffi`. /// @@ -106,6 +127,17 @@ mod tests { use crate::sink::vx_array_sink_open_file; use crate::sink::vx_array_sink_push; use crate::string::vx_view; + use crate::vx_runtime_set_worker_threads; + use crate::vx_runtime_worker_count; + + #[test] + fn runtime_worker_pool_configuration() { + assert_eq!(vx_runtime_worker_count(), 0); + vx_runtime_set_worker_threads(2); + assert_eq!(vx_runtime_worker_count(), 2); + vx_runtime_set_worker_threads(0); + assert_eq!(vx_runtime_worker_count(), 0); + } /// Panic if error is NULL. Free the error if it's not pub(crate) fn assert_error(error: *mut vx_error) { From 49260f65befd68283aa2b85ae7dc2a99906970ac Mon Sep 17 00:00:00 2001 From: Onur Satici Date: Thu, 16 Jul 2026 14:42:08 +0100 Subject: [PATCH 2/4] mirignore Signed-off-by: Onur Satici --- vortex-ffi/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/vortex-ffi/src/lib.rs b/vortex-ffi/src/lib.rs index 1e2f2237067..d535a0f8621 100644 --- a/vortex-ffi/src/lib.rs +++ b/vortex-ffi/src/lib.rs @@ -131,6 +131,7 @@ mod tests { use crate::vx_runtime_worker_count; #[test] + #[cfg_attr(miri, ignore)] fn runtime_worker_pool_configuration() { assert_eq!(vx_runtime_worker_count(), 0); vx_runtime_set_worker_threads(2); From 288f4a11a24e5d5ffb9c4fd4d27799a839f46872 Mon Sep 17 00:00:00 2001 From: Onur Satici Date: Thu, 16 Jul 2026 15:50:53 +0100 Subject: [PATCH 3/4] test, docs Signed-off-by: Onur Satici --- vortex-ffi/README.md | 17 +++++++++++++++++ vortex-ffi/cinclude/vortex.h | 12 ++++++++++-- vortex-ffi/src/lib.rs | 24 ++++++++++++++++++------ vortex-ffi/tests/default_runtime.rs | 21 +++++++++++++++++++++ 4 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 vortex-ffi/tests/default_runtime.rs diff --git a/vortex-ffi/README.md b/vortex-ffi/README.md index ff3b6c94ff2..5cac7b7f490 100644 --- a/vortex-ffi/README.md +++ b/vortex-ffi/README.md @@ -1,5 +1,22 @@ # Vortex C bindings +## Runtime threading + +The FFI uses a shared, caller-driven runtime. By default Vortex creates no runtime worker threads: +the host threads currently executing FFI calls drive the runtime. Multiple host threads making +concurrent FFI calls can drive runtime work in parallel while keeping thread ownership entirely in +the host application. + +Applications that want a single FFI operation to make progress on additional threads may opt into +Vortex-owned background workers with `vx_runtime_set_worker_threads`. This is a process-global +setting shared by every FFI session. Calling it with a non-zero count changes the threading model +from host-thread-only execution to a combination of host threads and Vortex-owned workers. Calling +it with zero signals the background workers to stop and restores the host-thread-only +configuration. + +Applications that already supply concurrency through their own host threads should leave the +worker count at its default of zero to avoid oversubscription. + ## Updating Headers If you're developing FFI and want to rebuild `cinclude/vortex.h`, run: diff --git a/vortex-ffi/cinclude/vortex.h b/vortex-ffi/cinclude/vortex.h index 2c9676db095..b4c0039657e 100644 --- a/vortex-ffi/cinclude/vortex.h +++ b/vortex-ffi/cinclude/vortex.h @@ -684,13 +684,21 @@ extern "C" { /** * Set the number of background worker threads driving the shared FFI runtime. * - * This setting is process-global. Passing zero disables background execution. Increasing the + * Calling this with a non-zero count opts the process into a Vortex-owned thread pool. These + * background threads drive the same executor as host threads currently inside FFI calls. If this + * function is never called, Vortex creates no runtime worker threads and execution remains + * entirely host-thread-driven. + * + * This setting is process-global and affects all FFI sessions. Passing zero restores the + * host-thread-only configuration by signalling all background workers to stop. Increasing the * count starts workers immediately; decreasing it signals excess workers to stop. */ void vx_runtime_set_worker_threads(size_t worker_threads); /** - * Return the configured number of background worker threads driving the shared FFI runtime. + * Return the configured number of Vortex-owned background worker threads. + * + * Zero means the runtime is entirely driven by host threads entering FFI calls. */ size_t vx_runtime_worker_count(void); diff --git a/vortex-ffi/src/lib.rs b/vortex-ffi/src/lib.rs index d535a0f8621..8b2ef096b85 100644 --- a/vortex-ffi/src/lib.rs +++ b/vortex-ffi/src/lib.rs @@ -51,25 +51,37 @@ use vortex::io::runtime::current::CurrentThreadWorkerPool; #[global_allocator] static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; -/// A shared runtime for all FFI operations. +/// A shared, caller-driven runtime for all FFI operations. +/// +/// By default the runtime owns no threads: host threads drive its executor while they are inside +/// an FFI call. Concurrent calls from multiple host threads can therefore execute runtime tasks in +/// parallel without any Vortex-owned worker threads. static RUNTIME: LazyLock = LazyLock::new(CurrentThreadRuntime::new); -/// Background workers that drive the shared FFI runtime. +/// Optional Vortex-owned background workers that drive the shared FFI runtime. /// -/// The pool starts empty so existing callers retain current-thread execution until they opt in -/// with [`vx_runtime_set_worker_threads`]. +/// Creating the pool does not create threads. It starts empty so callers retain the default +/// host-thread-owned execution model until they opt in with [`vx_runtime_set_worker_threads`]. static POOL: LazyLock = LazyLock::new(|| RUNTIME.new_pool()); /// Set the number of background worker threads driving the shared FFI runtime. /// -/// This setting is process-global. Passing zero disables background execution. Increasing the +/// Calling this with a non-zero count opts the process into a Vortex-owned thread pool. These +/// background threads drive the same executor as host threads currently inside FFI calls. If this +/// function is never called, Vortex creates no runtime worker threads and execution remains +/// entirely host-thread-driven. +/// +/// This setting is process-global and affects all FFI sessions. Passing zero restores the +/// host-thread-only configuration by signalling all background workers to stop. Increasing the /// count starts workers immediately; decreasing it signals excess workers to stop. #[unsafe(no_mangle)] pub extern "C" fn vx_runtime_set_worker_threads(worker_threads: usize) { POOL.set_workers(worker_threads); } -/// Return the configured number of background worker threads driving the shared FFI runtime. +/// Return the configured number of Vortex-owned background worker threads. +/// +/// Zero means the runtime is entirely driven by host threads entering FFI calls. #[unsafe(no_mangle)] pub extern "C" fn vx_runtime_worker_count() -> usize { POOL.worker_count() diff --git a/vortex-ffi/tests/default_runtime.rs b/vortex-ffi/tests/default_runtime.rs new file mode 100644 index 00000000000..6c51960b080 --- /dev/null +++ b/vortex-ffi/tests/default_runtime.rs @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +use std::thread; + +use vortex::io::runtime::BlockingRuntime; +use vortex_ffi::ffi_runtime; +use vortex_ffi::vx_runtime_worker_count; + +#[test] +fn runtime_is_host_thread_driven_by_default() { + assert_eq!(vx_runtime_worker_count(), 0); + + let host_thread = thread::current().id(); + let task = ffi_runtime() + .handle() + .spawn(async move { thread::current().id() }); + let executor_thread = ffi_runtime().block_on(task); + + assert_eq!(executor_thread, host_thread); +} From 34ad733838472e763bbf3ff2a849b022f25563be Mon Sep 17 00:00:00 2001 From: Onur Satici Date: Thu, 16 Jul 2026 16:00:42 +0100 Subject: [PATCH 4/4] mirignore test Signed-off-by: Onur Satici --- vortex-ffi/tests/default_runtime.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/vortex-ffi/tests/default_runtime.rs b/vortex-ffi/tests/default_runtime.rs index 6c51960b080..a63ba45bbda 100644 --- a/vortex-ffi/tests/default_runtime.rs +++ b/vortex-ffi/tests/default_runtime.rs @@ -8,6 +8,7 @@ use vortex_ffi::ffi_runtime; use vortex_ffi::vx_runtime_worker_count; #[test] +#[cfg_attr(miri, ignore)] fn runtime_is_host_thread_driven_by_default() { assert_eq!(vx_runtime_worker_count(), 0);