Session and Registries¶
A VortexSession is a type-indexed map that holds all the runtime state for the Vortex
ecosystem. Each major component of Vortex defines its own session variable type containing a
registry of plugins. There is typically one session per process, and it is passed explicitly
through the API rather than relying on global or thread-local state.
Plugins are themselves also able to register state in the session, for example to hold caches or other shared resources.
Design¶
The session is built on two primitives from the vortex-session crate:
VortexSession– a cloneable, thread-safe map from RustTypeIdto a shared (Arc-wrapped) value. Any type that isClone + Send + Sync + Debug + 'staticcan be stored as a session variable.ArcSwapMap<K, V>– a concurrent copy-on-write map, used both as the backing store ofVortexSessionitself and (keyed by interned stringIds) as the plugin registries each component uses to look up registered plugins at runtime.
Because both primitives are backed by an ArcSwap, cloning is cheap and all clones share the
same state, with lock-free reads and copy-on-write writes. This makes it safe to hand the
session to multiple threads, tasks, or I/O operations without coordination.
Component Registries¶
Each Vortex crate defines a session variable that holds a registry for its extension points:
Session Variable |
Crate |
Registry Contents |
|---|---|---|
|
|
Extension dtype vtables (Date, Time, …) |
|
|
Array encoding vtables (ALP, FSST, …) |
|
|
Scalar function vtables |
|
|
Layout encoding vtables (Flat, Chunked, …) |
|
|
Async runtime handle |
|
|
CUDA context, kernels, and stream pool |
Session variables are created lazily on first access with their Default implementation, which
registers the built-in plugins for that component. For example, ArraySession::default()
registers the 14 built-in encodings (Null, Bool, Primitive, Struct, etc.), and
LayoutSession::default() registers the 5 built-in layouts (Flat, Struct, Chunked, Zoned, Dict).
Registering Plugins¶
Plugins register with the session by accessing the relevant component and calling register:
// Register a custom array encoding
session.arrays().register(MyEncoding);
// Register a custom layout
session.layouts().register(MyLayout::encoding());
// Register a custom scalar function
session.scalar_fns().register(MyScalarFnVTable);
Crates that bundle multiple plugins typically expose an initialize function that registers
everything at once. The top-level vortex crate calls these during VortexSession::default()
to register all built-in encodings.
Explicit Passing¶
Sessions are passed explicitly through constructors and method arguments. This means every API that needs access to registries – file readers, writers, scan builders, layout readers – receives the session directly rather than reaching for global state.
// Opening a file
session.open_options()
.open(reader)
.await?;
// Writing a file
session.write_options()
.write(&mut file, array_stream)
.await?;
// Scanning a layout
let filter = expr
.optimize_recursive(layout_reader.dtype())?
.bind(layout_reader.dtype())?;
ScanBuilder::new(session.clone(), layout_reader)
.with_filter(filter)
.into_array_stream()?;
Many APIs use extension traits to provide ergonomic methods directly on the session. For example,
OpenOptionsSessionExt adds .open_options() to any session that has ArraySession,
LayoutSession, and RuntimeSession registered. This lets the type system enforce that the
required components are present.
Constructing a Session¶
The VortexSession::default() provided by the top-level vortex crate constructs a session with
all built-in components and encodings:
let session = VortexSession::default();
For tests or specialized use-cases, sessions can be assembled from individual components by
starting from an empty session and chaining the .with::<T>() helpers:
let session = VortexSession::empty()
.with::<ArraySession>()
.with::<LayoutSession>()
.with::<ScalarFnSession>()
.with::<RuntimeSession>();