From c07f75a61212da1a9764118cf12aa3a865cde31c Mon Sep 17 00:00:00 2001 From: Jihyeon Gim Date: Sat, 5 Sep 2026 23:58:13 +0900 Subject: [PATCH] rdma: add a point-in-time session snapshot to the RC C ABI Expose rc_server_sessions_snapshot, which copies every live session into fixed rc_session_snapshot records under the map lock and invokes the callback once per record outside the lock. Each session records a monotonic creation timestamp, because the prepare/ready deadlines move as the session progresses and cannot serve as an age reference. The state byte combines the session state machine value with a reap-pending marker, so callers can distinguish sessions that are about to be reaped from healthy ones. Records whose op or target does not fit the fixed fields are skipped rather than truncated. --- cuwrapper/rc/rc_server_abi.cpp | 42 ++++++++++++++++++++++++++++++++++ cuwrapper/rc/rc_server_abi.h | 24 +++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/cuwrapper/rc/rc_server_abi.cpp b/cuwrapper/rc/rc_server_abi.cpp index 71e32c2a..7d7c5e6a 100644 --- a/cuwrapper/rc/rc_server_abi.cpp +++ b/cuwrapper/rc/rc_server_abi.cpp @@ -43,6 +43,9 @@ struct RcSession { V2Session core; uint64_t epoch = 0; std::atomic next_nonce{1}; + /* Monotonic creation timestamp for session-age observability; + * deadlines cannot serve that role because READY moves them. */ + uint64_t created_ms = 0; /* staged metadata from finish_staging (GET) or finish_put. */ std::string etag; std::string version_id; @@ -613,6 +616,7 @@ int rc_prepare(rc_server *srv, const rc_prepare_req *req, srv->opts.t_prep_ms ? hipObj::v2::clockSource().nowMs() + srv->opts.t_prep_ms : 0; + rs.created_ms = hipObj::v2::clockSource().nowMs(); /* The session record carries its own id copy: reap logging and the * terminal teardown record read core.id, while the map key is the * only other place the id lives. */ @@ -757,6 +761,44 @@ int rc_session_info(rc_server *srv, rc_str_in session_id, return RC_OK; } +int rc_server_sessions_snapshot(rc_server *srv, rc_snapshot_cb cb, + void *ctx) { + if (!srv || !cb) return RC_E_ARG; + if (srv->closing.load()) return RC_E_INTERNAL; + + /* Fixed records so the vector can move without invalidating the + * string pointers inside; the copies own everything the callback + * reads, so delivery happens outside the map lock and cannot race + * the reaper moving or erasing entries. */ + std::vector recs; + uint64_t now = hipObj::v2::clockSource().nowMs(); + { + std::lock_guard g(srv->map_mtx); + recs.reserve(srv->sessions_map.size()); + for (const auto &kv : srv->sessions_map) { + const RcSession &s = *kv.second; + rc_session_snapshot r{}; + if (s.core.id.size() != 32) continue; /* live ids are 32 hex */ + if (s.core.op.size() >= sizeof(r.op)) continue; + if (s.core.target.size() > sizeof(r.target) - 1) continue; + memcpy(r.session_id, s.core.id.data(), 32); + r.session_id[32] = 0; + memcpy(r.op, s.core.op.data(), s.core.op.size()); + r.op[s.core.op.size()] = 0; + memcpy(r.target, s.core.target.data(), s.core.target.size()); + r.target[s.core.target.size()] = 0; + r.target_len = (uint32_t)s.core.target.size(); + r.state = (uint8_t)s.core.state; + if (s.reap_pending) r.state |= RC_SNAPSHOT_REAP_PENDING; + r.age_ms = s.created_ms ? now - s.created_ms : 0; + r.staging_bytes = s.staging_len; + recs.push_back(r); + } + } + for (const auto &r : recs) cb(&r, ctx); + return RC_OK; +} + int rc_ready_transfer(rc_server *srv, const rc_ready_req *req, rc_ready_resp *resp) { if (!srv || !req || !resp) return RC_E_ARG; diff --git a/cuwrapper/rc/rc_server_abi.h b/cuwrapper/rc/rc_server_abi.h index 4606b1a5..f852c19b 100644 --- a/cuwrapper/rc/rc_server_abi.h +++ b/cuwrapper/rc/rc_server_abi.h @@ -151,6 +151,30 @@ typedef struct { uint8_t op; } rc_session_info_resp; +/* Session observability snapshot. The record is a point-in-time copy; + * the fields are valid only inside the callback invocation. + * state combines the session state machine value with the reap-pending + * marker (RC_SNAPSHOT_REAP_PENDING) because CANCEL and expiry only set + * that marker without moving the state. */ +enum { RC_SNAPSHOT_REAP_PENDING = 0x80 }; + +typedef struct { + char session_id[33]; /* 32 hex + NUL */ + char op[4]; /* "GET" or "PUT" */ + char target[2048]; + uint32_t target_len; + uint8_t state; /* SessState value | RC_SNAPSHOT_REAP_PENDING */ + uint64_t age_ms; /* now - created_ms; 0 when the clock is absent */ + uint64_t staging_bytes; +} rc_session_snapshot; + +/* Copies every live session under the map lock into fixed records and + * invokes cb(rec, ctx) once per record outside the lock, in map order. + * A session whose op or target does not fit is skipped (record too + * small), not truncated. Returns RC_E_ARG for null arguments. */ +typedef void (*rc_snapshot_cb)(const rc_session_snapshot *rec, void *ctx); +int rc_server_sessions_snapshot(rc_server *srv, rc_snapshot_cb cb, void *ctx); + /* Lifecycle. destroy waits for active calls and the reaper. */ int rc_server_init(const rc_device_opts *opts, rc_server **out); void rc_server_destroy(rc_server *srv);