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.
This commit is contained in:
Jihyeon Gim
2026-09-05 23:58:13 +09:00
parent f92d6d64b1
commit c07f75a612
2 changed files with 66 additions and 0 deletions
+42
View File
@@ -43,6 +43,9 @@ struct RcSession {
V2Session core;
uint64_t epoch = 0;
std::atomic<uint64_t> 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<rc_session_snapshot> recs;
uint64_t now = hipObj::v2::clockSource().nowMs();
{
std::lock_guard<std::mutex> 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;
+24
View File
@@ -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);