Skip to content

rdma: add a log callback ABI to the RC data plane - #2363

Open
potatogim wants to merge 6 commits into
versity:mainfrom
potatogim:rc-parity-pr2
Open

rdma: add a log callback ABI to the RC data plane#2363
potatogim wants to merge 6 commits into
versity:mainfrom
potatogim:rc-parity-pr2

Conversation

@potatogim

@potatogim potatogim commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Part of #2361.

Adds a log callback ABI to the RC data plane so gateway operators can see session-level diagnostics (reap events, READY data-phase outcomes, init failures) instead of only having raw stderr.

rc_server_set_log_sink() installs a plain function pointer after init that stays valid until destroy. The gateway registers a fixed cgo trampoline (closures cannot cross the boundary), copies each message immediately per the lifetime contract, and never calls the sink while holding the session map lock. Error-level lines keep their existing stderr output, and --debug enables the level-2 diagnostic stream.

While verifying the init failure path I noticed a loader bug on rdma-core 61+ hosts: as far as I can tell, ibv_poll_cq, ibv_post_send, and ibv_post_recv are no longer exported as library symbols in modern rdma-core. verbs.h ships them as static inline wrappers that dispatch through cq->context->ops, so the dlsym-based loader treated the installed library as incomplete and init failed with a bare RC_E_INTERNAL. The loader now resolves those three calls through the ops table of a briefly opened device, and deviceless and gid-hint-mismatch init failures now print which step failed.

While verifying the sink end to end I ran into two more defects, both fixed here. First, the Prepare and ReadyTransfer Go wrappers embedded string views built from heap strings inside request structs passed to C by pointer, so every live PREPARE or READY call with header-derived strings panicked at the cgo pointer-check boundary and the route returned a 500; the string bytes are now pinned with runtime.Pinner for the duration of the call. Second, the session id only lived as the sessions map key, so the reap record logged an empty id; the record now carries its own copy. A deviceless cgo regression test covers the panic path by calling the real wrapper with heap-backed strings and an opcode that returns from C argument validation before the server handle is touched.

Verified on a host with rdma-core 61 and a passthrough mlx5_0 VF: archive rebuild, full vgwrdma link, stub and unit tests, the new cgo boundary test under GODEBUG=cgocheck=1 and GOEXPERIMENT=cgocheck2, --debug startup reaching a listening gateway, a signed POST /.hipobj-rc/prepare returning a complete session (id, QPN, PSN, staging address and rkey) instead of the previous 500, reap records reaching the gateway log through the callback with the session id present, and clean SIGTERM shutdown.

The hipobj-rc-v2 data plane started with its session, queue pair,
staging and timeout limits hardcoded at the rcserver.Init call
site, so operators could not size the RC plane for their hardware
the way they can for the cuObject backend. Add one flag per limit
plus the READY admission slot count, all defaulting to the values
the gateway passes today, and validate them through a new
rdmamode.V2ValidationError consulted only when the RC data plane
is enabled, mirroring the stale-value handling of the v1 settings.
Counts are parsed as uint64 and range-checked against the uint32
narrowing at the DeviceOpts boundary, and the timeouts carry an
upper bound that keeps the nowMs + timeout deadline arithmetic in
the C core from wrapping.
Wire C-side diagnostics (session reap, READY data phase outcome,
init failures) through a sink callback so the gateway can surface
them next to its own logs instead of losing them in stderr noise.

The sink is a plain C function pointer installed once after init
and valid until destroy: the Go side registers a fixed cgo
trampoline (closures cannot cross the boundary), copies the
message immediately per the lifetime contract, and never runs
under the session map lock. Error-level lines keep the existing
stderr output; --debug enables the level-2 diagnostic stream.
The verbs loader and device enumeration failures returned
RC_E_INTERNAL without any stderr trace, which made a VM or
container without RDMA indistinguishable from a genuine
library problem. Print the failing step so operators can tell
the two apart at startup.
ibv_poll_cq, ibv_post_send, and ibv_post_recv stopped being
exported library symbols in modern rdma-core: verbs.h ships them
as static inline wrappers that dispatch through
cq->context->ops. dlsym therefore returned null for them and the
loader rejected perfectly usable libraries, failing RC server
init with a bare RC_E_INTERNAL on hosts with rdma-core 61+.

Open the first device briefly, read the three function pointers
from its context ops table, and close it again. The check now
only requires symbols that actually exist in the library, and
the failure mode for an ops-less provider is explicit.
@versity-github

Copy link
Copy Markdown
Collaborator

This won't automatically run in continuous integration without approval. A member of the Versity organization must allow it.

The Prepare and ReadyTransfer wrappers embed string views built from
Go heap strings inside request structs passed to C by pointer. The
cgo pointer check rejects such requests when the string data is an
unpinned Go heap pointer, so any live PREPARE or READY call with
header-derived strings panicked at the call boundary and the route
returned a 500. Constant strings passed the check because their data
lives in read-only static storage, which is why standalone callers
kept working while the gateway did not.

Pin the string bytes with runtime.Pinner for the duration of the cgo
call and drop the now redundant KeepAlive calls in those two
wrappers. The other string-taking wrappers pass rc_str_in by value
and are unaffected.

Also add a deviceless cgo boundary regression test that calls the
real Prepare wrapper with heap-backed interior-pointer strings and an
invalid opcode, so C returns from argument validation before the
server handle is touched.
The session id only existed as the sessions map key; the session
record itself kept an empty id string, so the terminal reap record
logged an empty id for every expired, cancelled, or destroyed
session. Copy the id into the record at creation time so teardown
logs identify the session they describe.
@potatogim

Copy link
Copy Markdown
Contributor Author

The MacOS s3cmd check failure looks unrelated to this change - the log stops at
error installing go dependencies: go: -d flag is deprecated. -d=true is a no-op
before reaching the build. That reads like the workflow's go install invocation hit the
deprecation on a newer Go, not something this diff touches. I may well be missing
something. Whenever convenient, could someone take a look or retry?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants