Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
22e51da
fix(python): stop async client hang after pool borrow/release (#7454)
jeremyprime Oct 28, 2026
97b3c0c
refactor(ffi): return pool metrics by value with a status (#7457)
nderraugh Oct 29, 2026
1c01b1c
fix(core): keep waiting for SpecificNode route connection across supe…
KevinFriedland88 Oct 29, 2026
3ea2878
feat(ffi): integrate rdma into ffi (#7458)
anitarua Oct 29, 2026
abe8133
feat(python): add rdma get/set to sync client
anitarua Sep 24, 2026
972ac3a
chore(ci): smoke test glide-sync with rdma
anitarua Sep 24, 2026
ad4135e
fix(python): BLOB.RDMA_* protocol updates
anitarua Oct 17, 2026
4e2a3d0
fix(python): better error handling for regions closed by rdma transfers
anitarua Oct 21, 2026
679ac4a
chore(python): clean up docs/comments
anitarua Oct 22, 2026
bb45546
fix(python): raise instead of corrupting memory when an rdma region i…
anitarua Oct 22, 2026
2375232
fix(python): free unclosed rdma regions on garbage collection
anitarua Oct 22, 2026
6f3630b
chore(python): rebase and fix conflicts
anitarua Oct 23, 2026
294ab9c
fix(python): avoid segfault on python 3.9 and add regression test
anitarua Oct 23, 2026
0cd7a3f
chore(python): update changelog and other docs
anitarua Oct 26, 2026
495fcd8
fix(python): take rdma region capacity from the registered memory
anitarua Oct 27, 2026
59731e0
fix(python): cancel rdma transfers with rdma_close_regions and defer …
anitarua Oct 27, 2026
aad52cd
fix(python): free an rdma region whose RdmaRegion fails to build
anitarua Oct 27, 2026
c107958
fix(python): free before warning to avoid potential leak
anitarua Oct 27, 2026
bf3cbb1
fix(python): additional python 3.9 segfault fixes
anitarua Oct 28, 2026
02bb313
chore(python): fix test mismatch after rebase
anitarua Oct 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/ffi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ on:

# Rust dependencies
- glide-core/**
- glide-rdma/**
- glide-telemetry/**
- glide-logger/**
- deny.toml

# GitHub workflows
- .github/workflows/ffi.yml
- .github/actions/install-engine/action.yml
- .github/actions/install-libfabric/action.yml
- .github/actions/install-protoc/action.yml
- .github/actions/install-rust/action.yml
- .github/actions/install-shared-dependencies/action.yml
Expand All @@ -34,13 +36,15 @@ on:

# Rust dependencies
- glide-core/**
- glide-rdma/**
- glide-telemetry/**
- glide-logger/**
- deny.toml

# GitHub workflows
- .github/workflows/ffi.yml
- .github/actions/install-engine/action.yml
- .github/actions/install-libfabric/action.yml
- .github/actions/install-protoc/action.yml
- .github/actions/install-rust/action.yml
- .github/actions/install-shared-dependencies/action.yml
Expand Down Expand Up @@ -89,6 +93,13 @@ jobs:
working-directory: ./ffi
run: cargo test

- name: Install libfabric
uses: ./.github/actions/install-libfabric

- name: Run glide-ffi RDMA tests
working-directory: ./ffi
run: cargo test --features rdma

- name: Run glide-ffi MIRI tests
working-directory: ./ffi/miri-tests
run: cargo miri test
Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/pypi-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,11 @@ jobs:
CIBW_ARCHS_MACOS: auto
CIBW_ENABLE: ${{ steps.cibw_config.outputs.enable_flags }}
MACOSX_DEPLOYMENT_TARGET: "10.12"
# Build RDMA into every published wheel. Users opt in per client,
# and libfabric is only loaded when they do.
# This replaces [tool.cibuildwheel.environment] in pyproject.toml
# rather than adding to it, so RELEASE_MODE must be repeated here.
CIBW_ENVIRONMENT: RELEASE_MODE=1 GLIDE_SYNC_RDMA=1

- name: Smoke test wheels
run: |
Expand All @@ -478,6 +483,13 @@ jobs:
/tmp/smoke-sync/bin/pip install --find-links python/glide-sync/wheels "valkey-glide-sync==${RELEASE_VERSION}"
/tmp/smoke-sync/bin/python -c "from glide_sync import GlideClient; print('Sync smoke test passed')"

/tmp/smoke-sync/bin/python - <<'PYEOF'
from glide_sync import GlideClient

assert GlideClient.rdma_available(), "wheel was built without RDMA support"
print("RDMA usable:", GlideClient.rdma_usable())
PYEOF

### UPLOAD WHEELS ###

- name: Upload Python Async client wheels
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ This is the Valkey GLIDE mono-repository containing a Rust core (`glide-core`) a
- `go/` - Go client bindings (CGO). See [go/AGENTS.md](./go/AGENTS.md).
- `glide-telemetry/` - OpenTelemetry integration crate.
- `glide-logger/` - Shared logging infrastructure.
- `glide-rdma/` - RDMA transfer layer over libfabric (used by glide-core behind the `rdma` feature; not yet exposed by language bindings).
- `glide-rdma/` - RDMA transfer layer over libfabric (used by glide-core behind the `rdma` feature, exposed over the C ABI by glide-ffi behind its own `rdma` feature, and used by the Python sync client).
- `utils/` - Shared utilities and cluster management tools (`cluster_manager.py`).
- `benchmarks/` - Performance benchmarks across languages.
- `examples/` - Usage examples for each language binding.
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
* Rust: Implement `ToSingleValkeyArg` and `ToValkeyArgs` for option types ([#7381](https://github.com/valkey-io/valkey-glide/issues/7381))
* Core: Add `glide-rdma` crate, an RDMA transfer layer over libfabric (loaded at runtime), for later RDMA support ([#7177](https://github.com/valkey-io/valkey-glide/pull/7177))
* Core: Add opt-in RDMA support to glide-core behind the `rdma` feature: `RdmaConfig` in the connection request, `Client::register_rdma_region` / `rdma_get` / `rdma_set` / `close_rdma`, and the `rdma_parked_bytes` / `rdma_leaked_bytes` counters. Transfers route to the key's primary and have no request timeout. `BLOB.RDMA_*` commands are refused when sent any other way, in every build ([#7412](https://github.com/valkey-io/valkey-glide/pull/7412))
* Core/FFI: Add opt-in RDMA to glide-ffi behind the `rdma` feature: `rdma_available`, `rdma_usable`, `rdma_checksum`, `register_rdma_region` over caller-owned memory, `rdma_get` / `rdma_set` (blocking, with `region_closed` saying whether the region can transfer again), and `free_rdma_region` with `RdmaMemoryHold` for memory the fabric still holds (`rdma_memory_leaked` says whether it is leaked rather than parked), plus the `rdma_parked_bytes` / `rdma_leaked_bytes` counters. Sync clients only; pools refuse an RDMA configuration, and `rdma_close_regions` cancels transfers in flight without freeing the client ([#7458](https://github.com/valkey-io/valkey-glide/pull/7458))
* Python: Add experimental RDMA to the sync client: `RdmaConfiguration` / `RdmaProvider` / `RdmaSetChecksum` in both client configurations, and `register_rdma_region`, `rdma_get`, `rdma_set`, `rdma_available`, `rdma_usable` and `rdma_checksum` on the sync client, transferring through windows of caller-owned memory. Published `valkey-glide-sync` wheels include RDMA; libfabric is loaded only when a client asks for it. The async client and `ClientPool` refuse an RDMA configuration ([#TODO](https://github.com/valkey-io/valkey-glide/pull/TODO))

### Fixes

Expand All @@ -29,6 +31,7 @@
* Node: Log the disconnect warning when the Disconnection push arrives ([#7277](https://github.com/valkey-io/valkey-glide/pull/7277))
* Core, Go, Java, Node, Python, Rust: use uint32 for Go BackoffStrategy fields and harden reconnect RetryStrategy against out-of-range values; in Java an all-zero BackoffStrategy now means zero retries, matching the other clients ([#6680](https://github.com/valkey-io/valkey-glide/pull/6680))
* Java, Node: An ordinary client and a pooled client could be handed the same id because ordinary-client handles and pooled `client_id`s came from separate counters that both started at 1, so closing an ordinary client (including `ClientPool.create`'s own connectivity probe) evicted a pooled client sharing its id — killing it outright, invalidating its scopes, or polluting its inflight accounting. Ordinary-client handles now draw from the same global allocator pooled ids use, so every id in the shared registry is unique ([#6968](https://github.com/valkey-io/valkey-glide/issues/6968))
* Python: A routed command on an ordinary async client could hang indefinitely (past its `request_timeout`, with no error) after an unrelated pool borrow was taken and released on the same event loop. The async client drew its shared-pipe id from a binding-local counter starting at 1, colliding with a pooled client's id (drawn from core's global sequence, also starting at 1) in the process-wide pipe registry; the pooled client displaced the ordinary client there, so the ordinary client's responses were routed away and dropped. The ordinary async client now draws its id from the same global allocator, matching Java and Node ([#7003](https://github.com/valkey-io/valkey-glide/issues/7003))
* Java, Go: Fix `slotForKey` returning the wrong cluster slot for a key with an empty hash tag. The first `}` after the first `{` now closes the tag, and an empty tag (`{}`) hashes the whole key, so a scope pinned with a key like `{}user}:1` targets the correct primary ([#7251](https://github.com/valkey-io/valkey-glide/issues/7251))
* Core: Scoped connections inherit the parent client's TLS certificate material and custom address resolver instead of silently falling back to system trust with no client certificate: a scope off an mTLS, custom-root-CA, or cert-reload parent now presents the same client certificate and trusts the same roots, uses the freshest material adopted after a rotation, and resolves its seed address through the parent's resolver on the standalone path. An acquire whose parent configured certificate material but has none to inherit yet fails with the reason rather than connecting without it ([#7173](https://github.com/valkey-io/valkey-glide/issues/7173))
* Core: IAM authentication now requires TLS. An IAM token is a bearer credential, so a client or scoped connection configured for IAM with TLS disabled was sending `AUTH <username> <token>` over plaintext TCP, where it could be captured and replayed. An IAM-configured client now fails to create when TLS is off — before any token manager exists, so initial AUTH, background refresh, and reconnect all fail closed — and a scope off an IAM parent is rejected before any connect when its request disables TLS. TLS-enabled IAM connections are unchanged. An empty IAM `cluster_name` or `region` is also now rejected at client creation, instead of failing later as an opaque connect timeout or server-side auth error ([#7023](https://github.com/valkey-io/valkey-glide/issues/7023))
Expand Down
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,5 +153,8 @@ FFI wrappers → `glide-core` → Valkey/Redis
<tests>glide-rdma/tests/</tests>
<core-integration>glide-core/src/rdma/, glide-core/src/client/rdma.rs, glide-core/src/client/rdma_connection.rs</core-integration>
<core-tests>glide-core/tests/test_rdma.rs, glide-core/tests/test_rdma_remote.rs</core-tests>
<ffi-integration>ffi/src/rdma_ffi.rs</ffi-integration>
<python-integration>python/glide-sync/glide_sync/glide_client.py, python/glide-shared/glide_shared/config.py</python-integration>
<python-tests>python/tests/sync_tests/test_sync_rdma.py, python/tests/test_rdma_config.py</python-tests>
</language>
</context-sources>
39 changes: 39 additions & 0 deletions ffi/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ edition = "2024"
[features]
default = ["pool-support"]
pool-support = []
# RDMA over fabric. libfabric is loaded at runtime.
rdma = ["dep:glide-rdma", "glide-core/rdma"]

[lib]
crate-type = ["staticlib", "rlib", "cdylib"]
Expand All @@ -21,6 +23,7 @@ crate-type = ["staticlib", "rlib", "cdylib"]
glide-logger = { path = "../glide-logger" }
glide-core-engine = { path = "../glide-core/redis-rs/redis", features = ["aio", "tokio-comp", "tokio-rustls-comp"] }
glide-core = { path = "../glide-core", features = ["proto"] }
glide-rdma = { path = "../glide-rdma", optional = true }

# External dependencies
bytes = "1"
Expand Down
30 changes: 30 additions & 0 deletions ffi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ The glide_ffi crate provides a C-compatible Foreign Function Interface (FFI) for

## Structure
• src/lib.rs: Defines the FFI interface.
• src/rdma_ffi.rs: The RDMA functions, compiled in with the `rdma` feature.
• cbindgen.toml: Configuration for generating the C header file (lib.h).
• Cargo.toml: Manages dependencies, including glide-core.

Expand All @@ -20,6 +21,35 @@ To run tests:
cargo test
```

## RDMA

RDMA is opt-in at build time through the `rdma` feature, which builds in `glide-core`
and `glide-rdma`'s RDMA support:

```bash
cargo build --release --features rdma
```

Without the feature, the RDMA functions still exist but registering and transferring fail
with a message saying RDMA was not compiled in and `rdma_available()` returns false.

The feature does not link libfabric. `libfabric` 2.4 or newer is loaded when a client
first opens a fabric, so it only has to be installed where the library runs. Set
`GLIDE_LIBFABRIC_PATH` to the library file to use a copy the default search does not
find. On Linux the search tries the EFA installer's copy under `/opt/amazon/efa` first.
`rdma_usable()` says whether a compatible libfabric loads.

The RDMA functions take synchronous clients only, and a pool refuses a connection
request with an RDMA configuration.

`cargo test` runs the RDMA tests that need no feature. The rest need libfabric
installed, and the ones in `tests/ffi_client_tests.rs` also need `valkey-server` on
`PATH`. To run all of them:

```bash
cargo test --features rdma
```

## Generating the C Header File

```bash
Expand Down
1 change: 1 addition & 0 deletions ffi/miri-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ path = "../src/lib.rs"

[features]
pool-support = []
rdma = []
6 changes: 6 additions & 0 deletions ffi/miri-tests/mock-glide-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod compression;
pub mod connection_request;
pub mod errors;
pub mod otel_db_semantics;
pub mod pool;
pub mod request_type;
pub mod scripts_container;
pub mod timeout_watchdog;
Expand Down Expand Up @@ -63,6 +64,11 @@ pub mod credential_provider_registry {
}
}

/// Re-exports the real rdma types that ffi/src/rdma_ffi.rs uses without the `rdma` feature.
pub mod rdma {
pub use glide_core::rdma::RdmaUnavailable;
}

/// Mock iam module — minimal stubs needed by ffi/src/lib.rs for miri tests.
pub mod iam {
use std::sync::Arc;
Expand Down
5 changes: 5 additions & 0 deletions ffi/miri-tests/mock-glide-core/src/pool.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0

pub fn allocate_client_id() -> u64 {
1
}
38 changes: 38 additions & 0 deletions ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,14 @@ pub unsafe extern "C" fn reinit_async_pipe(pipe_write_fd: i32) {
glide_core::timeout_watchdog::TimeoutWatchdog::reinit_global();
}

/// Allocate a client id from core's one global sequence, shared by pooled
/// clients and every id-keyed registry. Bindings must draw pipe/registry ids
/// from here rather than a local counter, or ids collide across the process.
#[unsafe(no_mangle)]
pub extern "C" fn glide_allocate_client_id() -> u64 {
glide_core::pool::allocate_client_id()
}

/// Create a new `SharedPipeWriter` and spawn its flush thread.
/// Returns a `&'static` reference by leaking the allocation (lives for
/// the lifetime of the process).
Expand Down Expand Up @@ -4178,6 +4186,16 @@ fn apply_json_options(
///
/// `client_adapter_ptr` is a pointer to a valid `GlideClient` returned in the `ConnectionResponse` from [`create_client`].
///
/// With RDMA, this also revokes every region the client registered, as
/// `rdma_close_regions` does, and only logs a region that fails to close. It
/// does not free the regions: the caller still owns each one and frees it with
/// `free_rdma_region`.
///
/// This is not a way to cancel a transfer, since it frees the client the
/// transfer may still be using. To cancel one, call `rdma_close_regions` from
/// another thread, and call this only once every `rdma_get` and `rdma_set` on
/// the client has returned.
///
/// # Panics
///
/// This function panics when called with a null `client_adapter_ptr`.
Expand All @@ -4188,6 +4206,8 @@ fn apply_json_options(
/// * `close_client` must be called after `free_connection_response` has been called to avoid creating a dangling pointer in the `ConnectionResponse`.
/// * `client_adapter_ptr` must be obtained from the `ConnectionResponse` returned from [`create_client`].
/// * `client_adapter_ptr` must be valid until `close_client` is called.
/// * No `rdma_get`, `rdma_set`, `register_rdma_region`, or `rdma_close_regions`
/// call on the client may be running or start after this is called.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn close_client(client_adapter_ptr: *const c_void) {
assert!(!client_adapter_ptr.is_null());
Expand All @@ -4199,6 +4219,21 @@ pub unsafe extern "C" fn close_client(client_adapter_ptr: *const c_void) {
glide_core::scope::unregister_client(client_id);
}

// Revoke RDMA regions so the server can no longer reach the caller's memory.
#[cfg(feature = "rdma")]
{
// SAFETY: the caller's reference is still held until the decrement below.
let adapter = unsafe { &*(client_adapter_ptr as *const ClientAdapter) };
if let Err(error) = adapter.core.client.close_rdma() {
glide_logger::log_error(
"close_client",
format!(
"could not revoke every RDMA region, so a transfer may still be waiting: {error}"
),
);
}
}

// This will bring the strong count down to 0 once all client requests are done.
unsafe { Arc::decrement_strong_count(client_adapter_ptr as *const ClientAdapter) };
}
Expand Down Expand Up @@ -8309,3 +8344,6 @@ mod tests_push_notification_safety {
mod pool_ffi;
#[cfg(feature = "pool-support")]
pub use pool_ffi::*;

mod rdma_ffi;
pub use rdma_ffi::*;
Loading