mirror of
https://github.com/versity/versitygw.git
synced 2026-09-28 18:55:54 +00:00
rdma: port the hipObject v2 RC session core
Port the hipObject v2 reliable-connection session core into cuwrapper/rc: the session table and state machine, the wire codec for the hipobj-rc-v2 headers, request parsing, the injectable clock and randomness sources, the transport layer (QP/CQ lifecycle, RTR/RTS transitions, staging registration), the data phase (RDMA write with immediate for GET, receive with immediate for PUT), the RDMA token codec, and the dynamically loaded ibverbs shim (ibv-core.h plus the dlopen host binding). The sources are a port of the upstream hipObject v2 core, kept close to the original so the two trees can be diffed during review. Nothing links against them yet; a Makefile rule builds the objects into rdma/librcserver.a for the ABI layer that follows. Signed-off-by: Jihyeon Gim <potatogim@potatogim.net>
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
/* Copyright (c) Advanced Micro Devices, Inc. All rights reserved.
|
||||
* Copyright (c) Gluesys Inc. and Jihyeon Gim. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "v2-registry.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace hipObj {
|
||||
namespace v2 {
|
||||
|
||||
std::mutex& apiLock() {
|
||||
static std::mutex lock;
|
||||
return lock;
|
||||
}
|
||||
|
||||
// ---- RetiredRing ---------------------------------------------------
|
||||
|
||||
size_t RetiredRing::collectExpired(uint64_t nowMs) {
|
||||
size_t collected = 0;
|
||||
for (auto& slot : slots_) {
|
||||
if (slot.reservationId != 0 && slot.recorded && nowMs >= slot.expireAtMs) {
|
||||
slot = Slot{};
|
||||
--used_;
|
||||
++collected;
|
||||
}
|
||||
}
|
||||
return collected;
|
||||
}
|
||||
|
||||
uint64_t RetiredRing::reserve() {
|
||||
for (auto& slot : slots_) {
|
||||
if (slot.reservationId == 0) {
|
||||
slot.reservationId = nextReservationId_++;
|
||||
slot.recorded = false;
|
||||
slot.expireAtMs = 0;
|
||||
slot.qpn = 0;
|
||||
slot.psn = 0;
|
||||
++used_;
|
||||
++reserved_;
|
||||
return slot.reservationId;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void RetiredRing::unreserve(uint64_t reservationId) {
|
||||
if (reservationId == 0) {
|
||||
return;
|
||||
}
|
||||
for (auto& slot : slots_) {
|
||||
if (slot.reservationId == reservationId && !slot.recorded) {
|
||||
slot = Slot{};
|
||||
--used_;
|
||||
--reserved_;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RetiredRing::record(uint64_t reservationId, uint32_t qpn, uint32_t psn) {
|
||||
/* Precondition: reservationId refers to a Reserved slot owned by
|
||||
* the caller. All call sites hold the apiLock and the entry
|
||||
* invariant, so a miss is a programming error; treat it as a
|
||||
* no-op rather than corrupting the ring. */
|
||||
for (auto& slot : slots_) {
|
||||
if (slot.reservationId == reservationId && !slot.recorded) {
|
||||
slot.recorded = true;
|
||||
slot.expireAtMs = clockSource().nowMs() + kExpiryMs;
|
||||
slot.qpn = qpn;
|
||||
slot.psn = psn;
|
||||
--reserved_;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RetiredRing::contains(uint32_t qpn, uint32_t psn) const {
|
||||
for (const auto& slot : slots_) {
|
||||
if (slot.reservationId != 0 && slot.recorded && slot.qpn == qpn &&
|
||||
slot.psn == psn) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t RetiredRing::used() const {
|
||||
return used_;
|
||||
}
|
||||
|
||||
size_t RetiredRing::reservedCount() const {
|
||||
return reserved_;
|
||||
}
|
||||
|
||||
size_t RetiredRing::recordedCount() const {
|
||||
return used_ - reserved_;
|
||||
}
|
||||
|
||||
// ---- ConnectionRegistry ---------------------------------------------
|
||||
|
||||
bool ConnectionRegistry::reserveSlot() {
|
||||
if (entries_.size() + pendingReserves_ >= kMaxConnections) {
|
||||
return false;
|
||||
}
|
||||
++pendingReserves_;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConnectionRegistry::unreserveSlot() {
|
||||
if (pendingReserves_ > 0) {
|
||||
--pendingReserves_;
|
||||
}
|
||||
}
|
||||
|
||||
ConnId ConnectionRegistry::insert(ConnectionEntryV2&& entry) {
|
||||
if (pendingReserves_ == 0 ||
|
||||
entries_.size() + pendingReserves_ > kMaxConnections) {
|
||||
return 0;
|
||||
}
|
||||
--pendingReserves_;
|
||||
ConnId id = nextId_++;
|
||||
entries_.emplace(id, std::move(entry));
|
||||
return id;
|
||||
}
|
||||
|
||||
bool ConnectionRegistry::claimDestroy(ConnId id) {
|
||||
auto it = entries_.find(id);
|
||||
if (it == entries_.end()) {
|
||||
return false;
|
||||
}
|
||||
ConnectionEntryV2& entry = it->second;
|
||||
/* Single claimant: a live entry claims once; a poisoned entry may
|
||||
* be re-claimed for the destroy retry. */
|
||||
if (entry.destroying || (entry.destroyClaimed && !entry.poisoned)) {
|
||||
return false;
|
||||
}
|
||||
entry.destroying = true;
|
||||
entry.destroyClaimed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConnectionRegistry::commitDestroy(ConnId id, bool qpGone, bool cqGone) {
|
||||
auto it = entries_.find(id);
|
||||
if (it == entries_.end()) {
|
||||
return;
|
||||
}
|
||||
ConnectionEntryV2& entry = it->second;
|
||||
if (!entry.destroying) {
|
||||
return;
|
||||
}
|
||||
if (qpGone) {
|
||||
entry.conn.qp = nullptr;
|
||||
entry.conn.qpNum = 0;
|
||||
}
|
||||
if (cqGone) {
|
||||
entry.conn.cq = nullptr;
|
||||
}
|
||||
if (entry.conn.qp == nullptr && entry.conn.cq == nullptr) {
|
||||
entry.poisoned = false;
|
||||
entry.destroying = false;
|
||||
/* entry stays until eraseDestroyed(); destroyClaimed remains
|
||||
* true so no new claim can race the erase. */
|
||||
} else {
|
||||
entry.poisoned = true;
|
||||
entry.destroying = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ConnectionRegistry::eraseDestroyed(ConnId id) {
|
||||
auto it = entries_.find(id);
|
||||
if (it == entries_.end()) {
|
||||
return false;
|
||||
}
|
||||
ConnectionEntryV2& entry = it->second;
|
||||
if (entry.conn.qp != nullptr || entry.conn.cq != nullptr) {
|
||||
return false;
|
||||
}
|
||||
entries_.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConnectionRegistry::isPoisoned(ConnId id) const {
|
||||
auto it = entries_.find(id);
|
||||
return it != entries_.end() && it->second.poisoned;
|
||||
}
|
||||
|
||||
size_t ConnectionRegistry::size() const {
|
||||
return entries_.size();
|
||||
}
|
||||
|
||||
size_t ConnectionRegistry::pendingReserves() const {
|
||||
return pendingReserves_;
|
||||
}
|
||||
|
||||
RetiredRing& ConnectionRegistry::retired() {
|
||||
return retired_;
|
||||
}
|
||||
|
||||
#ifdef HIPOBJ_UNIT_TESTS
|
||||
ConnId ConnectionRegistry::insertRawForTest(ConnectionEntryV2&& entry) {
|
||||
ConnId id = nextId_++;
|
||||
entries_.emplace(id, std::move(entry));
|
||||
return id;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
ConnectionRegistry g_registry;
|
||||
ConnectionRegistry* g_registryOverride = nullptr;
|
||||
|
||||
} // namespace
|
||||
|
||||
ConnectionRegistry& registry() {
|
||||
return g_registryOverride ? *g_registryOverride
|
||||
: static_cast<ConnectionRegistry&>(g_registry);
|
||||
}
|
||||
|
||||
ConnectionRegistry* setRegistryForTest(ConnectionRegistry* r) {
|
||||
ConnectionRegistry* previous = g_registryOverride;
|
||||
g_registryOverride = r;
|
||||
return previous;
|
||||
}
|
||||
|
||||
} // namespace v2
|
||||
} // namespace hipObj
|
||||
Reference in New Issue
Block a user