src/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
37 / 951
3.9%
Branches:
22 / 1391
1.6%
Functions:
7 / 57
12.3%

ps/rdma/petps_client.cc
Line Branch Exec Source
1 #include "ps/rdma/petps_client.h"
2
3 #include <algorithm>
4 #include <cstdlib>
5 #include <cstring>
6 #include <fstream>
7 #include <iostream>
8 #include <stdexcept>
9 #include <thread>
10
11 #include <folly/portability/GFlags.h>
12
13 #include "base/config.h"
14 #include "ps/rdma/control_plane.h"
15 #include "ps/rdma/rdma_deployment.h"
16 #include "ps/rdma/rdma_common.h"
17 #include "ps/rdma/rc_options.h"
18
19 DECLARE_int32(global_id);
20 DECLARE_int32(num_server_processes);
21 DECLARE_int32(num_client_processes);
22 DECLARE_int32(value_size);
23 DECLARE_int32(max_kv_num_per_request);
24 DEFINE_string(rdma_get_response_mode,
25 "direct_sg",
26 "RDMA GET response mode: direct_sg or staging_copy");
27
28 namespace petps {
29 namespace {
30
31 using petps::Exchange;
32 using petps::NamespaceToken;
33 using petps::NowNs;
34
35 ✗ std::size_t ComputeMaxGetKeysPerRpc(std::size_t value_size) {
36 ✗ return GetKeysPerRpcByResponseBudget(
37 value_size,
38 static_cast<std::size_t>(FLAGS_rdma_rc_mtu_bytes),
39 ✗ static_cast<std::size_t>(FLAGS_rdma_rc_target_response_mtu));
40 }
41
42 ✗ void FillBaseDescriptor(
43 RequestDescriptor* descriptor,
44 std::uint64_t seq,
45 std::size_t key_count,
46 const RcClientQpView& view,
47 std::uint32_t shard_id,
48 std::uint32_t client_id,
49 std::size_t value_size) {
50
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (value_size == 0) {
51 ✗ value_size = static_cast<std::size_t>(FLAGS_value_size);
52 }
53 ✗ *descriptor = RequestDescriptor{};
54 ✗ descriptor->seq = seq;
55 ✗ descriptor->shard_id = shard_id;
56 ✗ descriptor->client_id = client_id;
57 ✗ descriptor->qp_index = static_cast<std::uint32_t>(view.qp_index);
58 ✗ descriptor->key_count = static_cast<std::uint32_t>(key_count);
59 ✗ descriptor->value_size = static_cast<std::uint32_t>(value_size);
60 ✗ descriptor->embedding_dim =
61 ✗ static_cast<std::uint32_t>(value_size / sizeof(float));
62 ✗ descriptor->payload_offset =
63 ✗ static_cast<std::uint32_t>(Align64(sizeof(RequestDescriptor)));
64 ✗ descriptor->client_response_addr =
65 ✗ reinterpret_cast<std::uint64_t>(view.response_payload);
66 ✗ descriptor->client_status_addr = reinterpret_cast<std::uint64_t>(view.status);
67 ✗ }
68
69 } // namespace
70
71 ✗ PetPSClient::PetPSClient(const std::string& host, int port, int shard)
72 ✗ : PetPSClient(host, port, shard, -1) {}
73
74 2 PetPSClient::PetPSClient(
75 2 const std::string& host, int port, int shard, int logical_client_id)
76 2 : namespace_token_(NamespaceToken()),
77 2 shard_(shard),
78
3/6
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
✓ Branch 20 taken 2 times.
✗ Branch 21 not taken.
2 explicit_client_id_(logical_client_id) {
79 (void)host;
80 (void)port;
81 2 }
82
83 2 PetPSClient::~PetPSClient() = default;
84
85 ✗ void PetPSClient::Barrier(const std::string&, int) {}
86
87 ✗ void PetPSClient::InitializeTransport() {
88
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (transport_ != nullptr) {
89 ✗ return;
90 }
91 ✗ client_id_ =
92 ✗ explicit_client_id_ >= 0
93
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ ? explicit_client_id_
94 ✗ : (FLAGS_rdma_rc_client_id_base >= 0
95
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ ? FLAGS_rdma_rc_client_id_base
96 ✗ : FLAGS_global_id - FLAGS_num_server_processes);
97
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (client_id_ < 0) {
98
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("invalid RC write logical client_id");
99 }
100 ✗ const int logical_num_clients =
101 ✗ FLAGS_rdma_rc_num_logical_clients >= 0
102
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ ? FLAGS_rdma_rc_num_logical_clients
103 : FLAGS_num_client_processes;
104
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (client_id_ >= logical_num_clients) {
105
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("RC write logical client_id out of range");
106 }
107 ✗ config_.shard_id = shard_;
108 ✗ config_.client_id = client_id_;
109 ✗ config_.num_clients = logical_num_clients;
110 ✗ config_.qps_per_client_per_shard = FLAGS_rdma_rc_qps_per_client_per_shard;
111 ✗ config_.slots_per_qp = FLAGS_rdma_rc_slots_per_qp;
112 ✗ config_.request_slot_bytes =
113 ✗ static_cast<std::size_t>(FLAGS_rdma_rc_request_slot_bytes);
114 ✗ config_.response_slot_bytes =
115 ✗ static_cast<std::size_t>(FLAGS_rdma_rc_response_slot_bytes);
116
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ config_.control_plane_host = FLAGS_rdma_control_plane_host;
117 ✗ config_.control_plane_port = FLAGS_rdma_control_plane_port;
118 ✗ config_.control_plane_timeout_ms = FLAGS_rdma_control_plane_timeout_ms;
119
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ config_.namespace_token = namespace_token_;
120
121 ✗ const char* configured_path = std::getenv("RECSTORE_CONFIG");
122 const std::string config_path =
123
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ configured_path != nullptr && *configured_path != '\0'
124 ✗ ? std::string(configured_path)
125
0/16
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ : base::ResolveRecStoreConfigPath().string();
126
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ std::ifstream config_file(config_path);
127
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ if (!config_file.is_open()) {
128
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ throw std::runtime_error("cannot open RDMA client config: " + config_path);
129 }
130 ✗ nlohmann::json config_json;
131
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ config_file >> config_json;
132 const auto deployment =
133
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ recstore::ParseResolvedRdmaDeploymentConfig(config_json);
134
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ const auto& fabric = recstore::LocalRdmaFabric(deployment, FLAGS_global_id);
135 ✗ config_.node_id = FLAGS_global_id;
136 ✗ config_.num_servers = deployment.num_shards;
137 ✗ config_.num_os_clients = deployment.num_clients;
138
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ config_.deployment_id = deployment.deployment_id;
139 ✗ config_.deployment_epoch = deployment.epoch;
140 ✗ config_.protocol_version = deployment.protocol_version;
141
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ config_.configuration_digest = deployment.configuration_digest;
142
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ config_.fabric_digest = deployment.fabric_digest;
143
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ config_.fabric = fabric;
144
145
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ transport_ = std::make_unique<RcShardClientTransport>(config_);
146 // One control-plane client per shard client: the WaitServer handshake and
147 // every slow-path health probe share one channel/stub instead of connecting
148 // per probe.
149 control_plane_ =
150 ✗ std::make_unique<RdmaControlPlaneClient>(RdmaControlPlaneEndpoint{
151
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ config_.control_plane_host,
152 ✗ config_.control_plane_port,
153 ✗ config_.control_plane_timeout_ms,
154
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ config_.deployment_id,
155 ✗ config_.deployment_epoch,
156 ✗ config_.protocol_version,
157
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ config_.configuration_digest,
158
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ config_.fabric_digest,
159
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ config_.num_servers,
160 ✗ });
161
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ control_plane_->WaitServer(shard_, config_.control_plane_timeout_ms);
162 // Built in place: QpContext holds atomics and is therefore neither copyable
163 // nor movable, so a growing vector operation on it would not compile.
164 ✗ qps_ = std::vector<QpContext>(
165
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ static_cast<std::size_t>(config_.qps_per_client_per_shard));
166
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ for (int qp = 0; qp < config_.qps_per_client_per_shard; ++qp) {
167 ✗ QpContext& context = qps_[static_cast<std::size_t>(qp)];
168 ✗ context.qp_index = qp;
169
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ context.slots.reserve(static_cast<std::size_t>(config_.slots_per_qp));
170
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ for (int slot_in_qp = 0; slot_in_qp < config_.slots_per_qp; ++slot_in_qp) {
171 ✗ context.slots.push_back(
172
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ SlotContext{transport_->OpenSlot(qp, slot_in_qp), 1, false});
173 }
174 }
175 ✗ }
176
177 ✗ void PetPSClient::InitThread() {
178
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ std::lock_guard<std::mutex> guard(mu_);
179
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ InitializeTransport();
180 ✗ thread_initialized_ = true;
181 ✗ }
182
183 ✗ std::size_t PetPSClient::ResponseBufferBytes(std::size_t key_count) const {
184 ✗ return GetResponseBytes(
185 key_count, static_cast<std::size_t>(FLAGS_value_size)) +
186 ✗ sizeof(std::int32_t);
187 }
188
189 ✗ void* PetPSClient::GetReceiveBuffer(size_t size) {
190
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ std::lock_guard<std::mutex> guard(mu_);
191 // Reuse an idle pooled buffer instead of allocating fresh heap memory every
192 // batch. Fresh mmap'd pages must be faulted in (zero-filled) on first write,
193 // which is the dominant cost on the RDMA submit critical path.
194
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ while (!receive_buffer_free_.empty()) {
195 ✗ const std::size_t idx = receive_buffer_free_.back();
196 ✗ receive_buffer_free_.pop_back();
197 ✗ auto& buf = receive_buffers_[idx];
198
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (buf.size() >= size) {
199 // Content is fully overwritten by the RPC completion before it is read,
200 // so a reuse needs no zero-fill here.
201 ✗ return buf.data();
202 }
203 ✗ const char* old_data = buf.data();
204
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ buf.assign(size, 0);
205
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (old_data != buf.data()) {
206
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ receive_buffer_index_.erase(old_data);
207
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ receive_buffer_index_.emplace(buf.data(), idx);
208 }
209 ✗ return buf.data();
210 }
211
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ receive_buffers_.emplace_back(size, 0);
212 ✗ receive_buffer_index_.emplace(
213
0/2
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ receive_buffers_.back().data(), receive_buffers_.size() - 1);
214 ✗ return receive_buffers_.back().data();
215 ✗ }
216
217 ✗ void PetPSClient::ReturnGetReceiveBuffer(const float* buffer) {
218
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (buffer == nullptr) {
219 ✗ return;
220 }
221
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ std::lock_guard<std::mutex> guard(mu_);
222 const auto it =
223
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ receive_buffer_index_.find(reinterpret_cast<const char*>(buffer));
224
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (it != receive_buffer_index_.end()) {
225
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ receive_buffer_free_.push_back(it->second);
226 }
227 ✗ }
228
229 ✗ const float* PetPSClient::BorrowGetResultPayload(
230 int rpc_id,
231 std::size_t* key_count,
232 std::size_t* response_bytes,
233 std::int32_t* status_code) {
234 ✗ PendingRpc pending;
235 {
236
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ std::lock_guard<std::mutex> guard(mu_);
237
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ if (!PendingRpcLocked(rpc_id, &pending)) {
238 ✗ return nullptr;
239 }
240
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ }
241
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (pending.failed) {
242
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ throw ShardUnreachableError(
243
0/8
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ "shard " + std::to_string(shard_) + " unreachable: lane " +
244
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ std::to_string(pending.qp_index) + " rebuilt during wait");
245 }
246
247
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ auto& slot = SlotAt(pending.qp_index, pending.slot_in_qp);
248 ✗ const bool profile_enabled = FLAGS_rdma_rc_profile_interval_ms > 0;
249
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ const std::uint64_t wait_start_ns = profile_enabled ? NowNs() : 0;
250 ✗ const std::int32_t rc_status = WaitForStatusWord(
251
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ pending.qp_index, slot.view.status, pending.seq, pending.lane_epoch);
252
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (profile_enabled) {
253 ✗ profile_.wait_rpc_count.fetch_add(1, std::memory_order_relaxed);
254 ✗ profile_.wait_status_ns.fetch_add(
255
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ NowNs() - wait_start_ns, std::memory_order_relaxed);
256 }
257
258 ✗ const std::size_t actual_response_bytes = std::min<std::size_t>(
259 ✗ slot.view.status->response_bytes, pending.response_bytes);
260
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (key_count != nullptr) {
261 ✗ *key_count = pending.key_count;
262 }
263
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (response_bytes != nullptr) {
264 ✗ *response_bytes = actual_response_bytes;
265 }
266
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (status_code != nullptr) {
267 ✗ *status_code = rc_status;
268 }
269
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (pending.recv_buffer != nullptr) {
270 ✗ const int value_size =
271 ✗ pending.key_count == 0
272
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ ? FLAGS_value_size
273 ✗ : static_cast<int>(pending.response_bytes / pending.key_count);
274 auto* user_status =
275 ✗ FixedSlotStatusWord(pending.recv_buffer, pending.key_count, value_size);
276 ✗ *user_status = rc_status;
277 }
278
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ MaybeReportProfile();
279 ✗ return reinterpret_cast<const float*>(slot.view.response_payload);
280 }
281
282 ✗ bool PetPSClient::HasAvailableSlots(std::size_t required) const {
283
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ std::lock_guard<std::mutex> guard(mu_);
284 ✗ std::size_t available = 0;
285
0/2
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ for (const auto& qp : qps_) {
286 // Drain old GETs before an acquire can rebuild a lane and invalidate them.
287
0/6
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ if (qp.dead.load(std::memory_order_relaxed) ||
288 ✗ qp.dirty.load(std::memory_order_relaxed)) {
289 ✗ return false;
290 }
291
0/2
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ for (const auto& slot : qp.slots) {
292
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (!slot.busy) {
293 ✗ ++available;
294 }
295 }
296 }
297 ✗ return available >= required;
298 ✗ }
299
300 ✗ PetPSClient::SlotHandle PetPSClient::AcquireIdleSlot() {
301
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (FLAGS_rdma_rc_profile_interval_ms > 0) {
302 ✗ profile_.acquire_qp_count.fetch_add(1, std::memory_order_relaxed);
303 }
304 ✗ int usable_lanes = 0;
305 ✗ int dead_lanes = 0;
306 ✗ int degraded_lanes = 0;
307
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ for (std::size_t qp_index = 0; qp_index < qps_.size(); ++qp_index) {
308 ✗ auto& qp = qps_[qp_index];
309
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (qp.dead.load(std::memory_order_relaxed)) {
310 ✗ ++dead_lanes;
311 ✗ continue;
312 }
313
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (qp.dirty.load(std::memory_order_relaxed)) {
314 // A degraded lane is rebuilt lazily here, under mu_, if a QP rebuild can
315 // cure the cause. The cooldown keeps a sustained outage from burning the
316 // rebuild budget on every acquire; a non-rebuildable lane stays retired.
317 ✗ const auto now = std::chrono::steady_clock::now();
318 ✗ if (!FLAGS_rdma_lane_rebuild_enabled ||
319
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ !qp.rebuildable.load(std::memory_order_relaxed) ||
320
0/8
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ now < qp.next_rebuild_at ||
321
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ !TryRebuildLaneLocked(static_cast<int>(qp_index))) {
322 ✗ ++degraded_lanes;
323 ✗ continue;
324 }
325 }
326 ✗ ++usable_lanes;
327
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ for (std::size_t slot_in_qp = 0; slot_in_qp < qp.slots.size();
328 ++slot_in_qp) {
329
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (!qp.slots[slot_in_qp].busy) {
330 ✗ qp.slots[slot_in_qp].busy = true;
331 return SlotHandle{
332 static_cast<int>(qp_index),
333 static_cast<int>(slot_in_qp),
334 ✗ };
335 }
336 }
337 }
338
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (FLAGS_rdma_rc_profile_interval_ms > 0) {
339 ✗ profile_.acquire_qp_failures.fetch_add(1, std::memory_order_relaxed);
340 }
341
0/6
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ if (usable_lanes == 0 && !qps_.empty()) {
342
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ throw ShardUnreachableError(
343
0/8
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ "shard " + std::to_string(shard_) + " unreachable: no usable lane (" +
344
0/6
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ std::to_string(dead_lanes) + " dead, " +
345
0/10
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ std::to_string(degraded_lanes) + " of " + std::to_string(qps_.size()) +
346 ✗ " degraded)");
347 }
348
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("no idle RC write slot available");
349 }
350
351 ✗ void PetPSClient::MarkLaneDirty(
352 int qp_index,
353 const char* reason,
354 const RdmaServerHealthSnapshot* snapshot,
355 std::int64_t waited_ms,
356 bool rebuildable) {
357 ✗ auto& lane = qps_[static_cast<std::size_t>(qp_index)];
358 // A lane can be marked dirty by more than one cause; a rebuildable cause must
359 // not be masked by an earlier non-rebuildable one, so OR the flag in.
360 ✗ const bool was_rebuildable = lane.rebuildable.load(std::memory_order_relaxed);
361 ✗ lane.rebuildable.store(
362
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ was_rebuildable || rebuildable, std::memory_order_relaxed);
363 const bool already_dirty =
364 ✗ lane.dirty.exchange(true, std::memory_order_relaxed);
365
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (already_dirty) {
366 ✗ return;
367 }
368 const std::string verdict =
369
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ snapshot != nullptr && snapshot->known
370 ✗ ? RdmaServerHealthToString(snapshot->health)
371
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ : "unknown";
372
0/6
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ LOG(WARNING) << "component=rdma_rc_client event=lane_dirty shard=" << shard_
373
0/8
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ << " client_id=" << client_id_ << " lane=" << qp_index
374 << " reason=" << reason << " verdict=" << verdict
375
0/12
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ << " last_seen_age_ms="
376
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ << (snapshot != nullptr ? snapshot->last_seen_age_ms : -1)
377
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ << " waited_ms=" << waited_ms;
378 ✗ }
379
380 ✗ bool PetPSClient::TryRebuildLaneLocked(int qp_index) {
381 // Runs under mu_, which is what keeps submits/polls off the lane while its QP
382 // is reset. The bilateral handshake can block for up to
383 // --rdma-lane-rebuild-wait-ms, but only for lanes of this shard, which is the
384 // shard that just failed.
385 ✗ auto& lane = qps_[static_cast<std::size_t>(qp_index)];
386
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (lane.dead.load(std::memory_order_relaxed)) {
387 ✗ return false;
388 }
389 lane.next_rebuild_at =
390 ✗ std::chrono::steady_clock::now() +
391 ✗ std::chrono::milliseconds(
392
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ static_cast<std::int64_t>(FLAGS_rdma_lane_rebuild_cooldown_ms));
393
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (lane.rebuild_attempts >= FLAGS_rdma_lane_rebuild_max_attempts) {
394 ✗ lane.dead.store(true, std::memory_order_relaxed);
395
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ LOG(WARNING) << "component=rdma_rc_client event=lane_rebuild_exhausted"
396
0/8
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ << " shard=" << shard_ << " client_id=" << client_id_
397
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ << " lane=" << qp_index
398
0/6
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ << " attempts=" << lane.rebuild_attempts;
399 ✗ return false;
400 }
401 ✗ ++lane.rebuild_attempts;
402 ✗ bool rebuilt = false;
403 try {
404
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ rebuilt = transport_->RebuildLane(qp_index);
405
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ } catch (const std::exception& error) {
406
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ LOG(WARNING) << "component=rdma_rc_client event=lane_rebuild_failed"
407
0/8
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ << " shard=" << shard_ << " client_id=" << client_id_
408
0/12
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ << " lane=" << qp_index << " error=\"" << error.what() << "\"";
409 ✗ return false;
410 ✗ }
411
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (!rebuilt) {
412 ✗ return false;
413 }
414 // The QP was reset, so every slot of this lane is free again and any RPC
415 // still pending on it lost its response. Mark those entries failed instead of
416 // erasing them: a missing entry makes QueryRPCFinished() report "done" and
417 // WaitRPCFinish() return silently, so the caller would decode a response that
418 // never arrived (a zeroed buffer decodes as RpcStatus::kOk). The marker also
419 // keeps RevokeRPCResource from clearing a slot that a new request has since
420 // reused.
421
0/2
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ for (auto& slot : lane.slots) {
422 ✗ slot.busy = false;
423 }
424
0/2
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ for (auto& entry : pending_rpcs_) {
425
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (entry.second.qp_index == qp_index) {
426 ✗ entry.second.failed = true;
427 }
428 }
429 // Bump the epoch before clearing dirty so a waiter can never observe
430 // "clean lane with the old epoch" and mistake a reused slot for its own.
431 ✗ lane.epoch.fetch_add(1, std::memory_order_relaxed);
432 ✗ lane.dirty.store(false, std::memory_order_relaxed);
433 ✗ lane.rebuildable.store(false, std::memory_order_relaxed);
434 ✗ lane.generation = transport_->LaneGeneration(qp_index);
435 // A successful handshake is stronger evidence than the cached health verdict:
436 // drop it so a stale `dead` cannot retire this lane again within the TTL.
437 ✗ health_probe_gate_.InvalidateVerdict();
438
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ LOG(WARNING) << "component=rdma_rc_client event=lane_recovered"
439
0/8
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ << " shard=" << shard_ << " client_id=" << client_id_
440
0/10
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ << " lane=" << qp_index << " generation=" << lane.generation
441
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ << " attempts=" << lane.rebuild_attempts;
442 ✗ return true;
443 }
444
445 ✗ void PetPSClient::MaybeProbeShardHealth(int qp_index, std::int64_t waited_ms) {
446 ✗ const int ttl_ms = FLAGS_rdma_health_probe_interval_ms;
447 ✗ const auto now = std::chrono::steady_clock::now();
448 // Sibling lanes share this shard's verdict: reusing it retires every lane of
449 // a dead shard in one interval instead of one lane per interval.
450 ✗ RdmaServerHealthSnapshot snapshot;
451
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ if (health_probe_gate_.TryReadVerdict(now, ttl_ms, &snapshot)) {
452
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ ApplyShardHealthVerdict(qp_index, snapshot, waited_ms, /*cached=*/true);
453 ✗ return;
454 }
455
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ if (!health_probe_gate_.TryBeginProbe(now, ttl_ms)) {
456 ✗ return;
457 }
458
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ snapshot = control_plane_->GetServerHealth(
459 shard_, FLAGS_rdma_health_probe_timeout_ms);
460
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ health_probe_gate_.EndProbe();
461
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ health_probe_gate_.StoreVerdict(std::chrono::steady_clock::now(), snapshot);
462
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ ApplyShardHealthVerdict(qp_index, snapshot, waited_ms, /*cached=*/false);
463 }
464
465 ✗ void PetPSClient::ApplyShardHealthVerdict(
466 int qp_index,
467 const RdmaServerHealthSnapshot& snapshot,
468 std::int64_t waited_ms,
469 bool cached) {
470
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (!IsShardUnreachable(snapshot)) {
471
0/14
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ VLOG(1) << "component=rdma_rc_client event=health_probe shard=" << shard_
472
0/10
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ << " lane=" << qp_index << " known=" << snapshot.known
473 ✗ << " health=" << RdmaServerHealthToString(snapshot.health)
474
0/8
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ << " last_seen_age_ms=" << snapshot.last_seen_age_ms
475
0/8
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ << " waited_ms=" << waited_ms << " cached=" << cached;
476 ✗ return;
477 }
478 ✗ MarkLaneDirty(
479 qp_index, "health_dead", &snapshot, waited_ms, /*rebuildable=*/true);
480
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ throw ShardUnreachableError(
481
0/8
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ "shard " + std::to_string(shard_) + " unreachable: control plane " +
482
0/6
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ "reports dead (lane=" + std::to_string(qp_index) +
483
0/6
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ " last_seen_age_ms=" + std::to_string(snapshot.last_seen_age_ms) +
484
0/6
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ " waited_ms=" + std::to_string(waited_ms) + ")");
485 }
486
487 ✗ void PetPSClient::FailLaneOnCompletionError(
488 int qp_index,
489 const RawVerbsCompletionError& error,
490 std::int64_t waited_ms) {
491 // A completion error means the lane's QP is dead (retry exhaustion, flush or
492 // a protocol fault) and its slots can never be reused safely, so the lane is
493 // retired exactly like a lane that lost a completion.
494 const std::string reason =
495
0/4
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ std::string("cq_") + RawVerbsErrorClassToString(error.error_class);
496
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ MarkLaneDirty(qp_index,
497 reason.c_str(),
498 nullptr,
499 waited_ms,
500 ✗ IsRawVerbsUnreachableError(error.error_class));
501
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (IsRawVerbsUnreachableError(error.error_class)) {
502
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ throw ShardUnreachableError(
503
0/8
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ "shard " + std::to_string(shard_) + " unreachable: lane " +
504
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ std::to_string(qp_index) +
505
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ " RDMA completion failed: " + DescribeRawVerbsCompletionError(error));
506 }
507
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ throw RawVerbsCompletionException(
508 error,
509
0/12
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ "shard=" + std::to_string(shard_) + " lane=" + std::to_string(qp_index) +
510
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ " waited_ms=" + std::to_string(waited_ms));
511 ✗ }
512
513 ✗ std::int32_t PetPSClient::WaitForStatusWord(
514 int qp_index,
515 const StatusWord* status,
516 std::uint64_t seq,
517 std::uint64_t lane_epoch) {
518 ✗ const auto start = std::chrono::steady_clock::now();
519 ✗ const bool probe_wanted = FLAGS_rdma_health_probe_after_ms > 0;
520 ✗ const bool harvest_wanted = FLAGS_rdma_cq_harvest_interval_ms > 0;
521 ✗ int spin_iterations = 0;
522 ✗ std::int64_t next_harvest_ms = FLAGS_rdma_cq_harvest_interval_ms;
523
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ while (!StatusWordDone(*status, seq)) {
524 ✗ const auto& lane = qps_[static_cast<std::size_t>(qp_index)];
525
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (lane.dirty.load(std::memory_order_relaxed)) {
526 // A sibling request already retired this lane (health verdict or lost
527 // completion): fail fast instead of waiting for the full wait budget.
528
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ throw ShardUnreachableError(
529
0/8
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ "shard " + std::to_string(shard_) + " unreachable: lane " +
530
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ std::to_string(qp_index) + " retired");
531 }
532
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (lane.epoch.load(std::memory_order_relaxed) != lane_epoch) {
533 // The lane was rebuilt while this RPC was in flight: its slot was freed
534 // and may already belong to another request, so this response will never
535 // arrive. Fail fast instead of blocking on the reused slot.
536
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ throw ShardUnreachableError(
537
0/8
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ "shard " + std::to_string(shard_) + " unreachable: lane " +
538
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ std::to_string(qp_index) + " rebuilt during wait");
539 }
540
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (spin_iterations < FLAGS_rdma_rc_wait_spin_iterations) {
541 ✗ ++spin_iterations;
542 } else {
543 ✗ spin_iterations = 0;
544 ✗ std::this_thread::yield();
545 }
546 ✗ std::int64_t elapsed_ms = 0;
547
0/6
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ if (FLAGS_rdma_wait_timeout_ms > 0 || probe_wanted || harvest_wanted) {
548
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
549
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ std::chrono::steady_clock::now() - start)
550 ✗ .count();
551 }
552
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (FLAGS_rdma_wait_timeout_ms > 0 &&
553
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ elapsed_ms > FLAGS_rdma_wait_timeout_ms) {
554 // The completion never came: the slot must not be reused, so the whole
555 // lane is retired before reporting the existing timeout.
556
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ MarkLaneDirty(
557 qp_index, "wait_timeout", nullptr, elapsed_ms, /*rebuildable=*/true);
558
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("RC write RPC wait timeout");
559 }
560
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (harvest_wanted && elapsed_ms >= next_harvest_ms) {
561 ✗ next_harvest_ms = elapsed_ms + FLAGS_rdma_cq_harvest_interval_ms;
562 ✗ RawVerbsCompletionError completion_error;
563 ✗ bool lane_failed = false;
564 {
565 // Harvest under the client mutex so it serializes with submit drains.
566
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ std::lock_guard<std::mutex> guard(mu_);
567 try {
568 lane_failed =
569
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ transport_->HarvestLaneCompletion(qp_index, &completion_error);
570 ✗ } catch (...) {
571 // A failed CQ poll/repost leaves completion unknown. Retire the lane
572 // before cleanup can release its slots, preserving the original
573 // error.
574
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ MarkLaneDirty(qp_index,
575 "cq_harvest_exception",
576 nullptr,
577 elapsed_ms,
578 /*rebuildable=*/false);
579 ✗ throw;
580 ✗ }
581 ✗ }
582
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (lane_failed) {
583 ✗ FailLaneOnCompletionError(qp_index, completion_error, elapsed_ms);
584 }
585 }
586
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (probe_wanted && elapsed_ms >= FLAGS_rdma_health_probe_after_ms) {
587
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ MaybeProbeShardHealth(qp_index, elapsed_ms);
588 }
589 }
590 ✗ return status->status;
591 }
592
593 ✗ PetPSClient::SlotContext& PetPSClient::SlotAt(int qp_index, int slot_in_qp) {
594 ✗ auto& qp = qps_.at(static_cast<std::size_t>(qp_index));
595 ✗ return qp.slots.at(static_cast<std::size_t>(slot_in_qp));
596 }
597
598 const PetPSClient::SlotContext&
599 ✗ PetPSClient::SlotAt(int qp_index, int slot_in_qp) const {
600 ✗ const auto& qp = qps_.at(static_cast<std::size_t>(qp_index));
601 ✗ return qp.slots.at(static_cast<std::size_t>(slot_in_qp));
602 }
603
604 ✗ void PetPSClient::EnsureThreadInitializedLocked() const {
605
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (!thread_initialized_) {
606
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("PetPSClient::InitThread must be called first");
607 }
608 ✗ }
609
610 8 bool PetPSClient::PendingRpcLocked(int rpc_id, PendingRpc* pending) const {
611
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 const auto it = pending_rpcs_.find(rpc_id);
612
1/2
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
8 if (it == pending_rpcs_.end()) {
613 8 return false;
614 }
615
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (pending != nullptr) {
616 ✗ *pending = it->second;
617 }
618 ✗ return true;
619 }
620
621 ✗ bool PetPSClient::RpcFailedLocked(int rpc_id) const {
622
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ const auto it = pending_rpcs_.find(rpc_id);
623
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ return it != pending_rpcs_.end() && it->second.failed;
624 }
625
626 ✗ bool PetPSClient::RequestPayloadFitsSlot(std::size_t payload_bytes) const {
627 ✗ return Align64(sizeof(RequestDescriptor)) + payload_bytes +
628 ✗ Align64(sizeof(CommitWord)) <=
629 ✗ config_.request_slot_bytes;
630 }
631
632 ✗ float* PetPSClient::AllocateStatusReceiveBufferLocked() {
633
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ receive_buffers_.emplace_back(sizeof(std::int32_t), 0);
634 ✗ receive_buffer_index_.emplace(
635
0/2
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ receive_buffers_.back().data(), receive_buffers_.size() - 1);
636 ✗ return reinterpret_cast<float*>(receive_buffers_.back().data());
637 }
638
639 ✗ void PetPSClient::MaybeReportProfile() {
640
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (FLAGS_rdma_rc_profile_interval_ms <= 0) {
641 ✗ return;
642 }
643
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ const std::uint64_t now = NowNs();
644 ✗ const std::uint64_t interval =
645 ✗ static_cast<std::uint64_t>(FLAGS_rdma_rc_profile_interval_ms) * 1000000;
646 std::uint64_t expected =
647 ✗ profile_.next_report_ns.load(std::memory_order_relaxed);
648
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (expected == 0) {
649 ✗ profile_.next_report_ns.compare_exchange_strong(
650 expected, now + interval, std::memory_order_relaxed);
651 ✗ return;
652 }
653
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (now < expected ||
654
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ !profile_.next_report_ns.compare_exchange_strong(
655 expected, now + interval, std::memory_order_relaxed)) {
656 ✗ return;
657 }
658
659 ✗ const std::uint64_t submit_count = Exchange(&profile_.submit_rpc_count);
660 ✗ const std::uint64_t wait_count = Exchange(&profile_.wait_rpc_count);
661 ✗ const std::uint64_t revoke_count = Exchange(&profile_.revoke_rpc_count);
662 ✗ const std::uint64_t submit_ns = Exchange(&profile_.submit_request_ns);
663 ✗ const std::uint64_t wait_ns = Exchange(&profile_.wait_status_ns);
664 ✗ const std::uint64_t copy_ns = Exchange(&profile_.copy_response_ns);
665 ✗ const std::uint64_t revoke_ns = Exchange(&profile_.revoke_resource_ns);
666 ✗ const std::uint64_t pending_samples = Exchange(&profile_.pending_rpc_samples);
667 ✗ const std::uint64_t pending_sum = Exchange(&profile_.pending_rpc_sum);
668 std::cout
669
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ << "component=rdma_rc_client_profile" << " shard=" << shard_
670
0/10
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ << " client_id=" << client_id_ << " submit_count=" << submit_count
671
0/8
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ << " wait_count=" << wait_count << " revoke_count=" << revoke_count
672
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ << " acquire_qp_count=" << Exchange(&profile_.acquire_qp_count)
673
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ << " acquire_qp_failures=" << Exchange(&profile_.acquire_qp_failures)
674
0/6
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ << " submit_avg_ns=" << (submit_count == 0 ? 0 : submit_ns / submit_count)
675
0/6
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ << " wait_status_avg_ns=" << (wait_count == 0 ? 0 : wait_ns / wait_count)
676
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ << " copy_response_avg_ns="
677
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ << (wait_count == 0 ? 0 : copy_ns / wait_count)
678
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ << " copied_bytes=" << Exchange(&profile_.response_bytes_copied)
679
0/6
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ << " revoke_avg_ns=" << (revoke_count == 0 ? 0 : revoke_ns / revoke_count)
680
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ << " pending_rpc_peak=" << Exchange(&profile_.pending_rpc_peak)
681
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ << " pending_rpc_avg="
682
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ << (pending_samples == 0 ? 0 : pending_sum / pending_samples)
683
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ << " pending_rpc_last="
684
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ << profile_.pending_rpc_last.load(std::memory_order_relaxed) << std::endl;
685 }
686
687 ✗ void PetPSClient::FillGetDescriptor(
688 RequestDescriptor* descriptor,
689 std::uint64_t seq,
690 std::size_t key_count,
691 std::size_t response_bytes,
692 std::size_t value_size,
693 const RcClientQpView& view) const {
694 ✗ FillBaseDescriptor(
695 descriptor,
696 seq,
697 key_count,
698 view,
699 ✗ static_cast<std::uint32_t>(shard_),
700 ✗ static_cast<std::uint32_t>(client_id_),
701 value_size);
702 ✗ descriptor->op = static_cast<std::uint16_t>(RcOp::kGet);
703 ✗ descriptor->payload_bytes =
704 ✗ static_cast<std::uint32_t>(GetRequestBytes(key_count));
705 ✗ descriptor->response_bytes = static_cast<std::uint32_t>(response_bytes);
706
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (FLAGS_rdma_get_response_mode == "direct_sg") {
707 ✗ descriptor->flags |= kRcFlagGetDirectSg | kRcFlagGetAllowFallbackCopy;
708
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ } else if (FLAGS_rdma_get_response_mode != "staging_copy") {
709
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ LOG(FATAL) << "unsupported --rdma_get_response_mode="
710
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ << FLAGS_rdma_get_response_mode;
711 }
712 ✗ }
713
714 ✗ void PetPSClient::FillPutDescriptor(
715 RequestDescriptor* descriptor,
716 std::uint64_t seq,
717 std::size_t key_count,
718 std::size_t payload_bytes,
719 const RcClientQpView& view) const {
720 ✗ FillBaseDescriptor(
721 descriptor,
722 seq,
723 key_count,
724 view,
725 ✗ static_cast<std::uint32_t>(shard_),
726 ✗ static_cast<std::uint32_t>(client_id_),
727 0);
728 ✗ descriptor->op = static_cast<std::uint16_t>(RcOp::kPut);
729 ✗ descriptor->payload_bytes = static_cast<std::uint32_t>(payload_bytes);
730 ✗ descriptor->response_bytes = 0;
731 ✗ }
732
733 ✗ void PetPSClient::FillUpdateDescriptor(
734 RequestDescriptor* descriptor,
735 std::uint64_t seq,
736 std::size_t key_count,
737 std::size_t payload_bytes,
738 const std::string& table_name,
739 const RcClientQpView& view) const {
740 ✗ FillPutDescriptor(descriptor, seq, key_count, payload_bytes, view);
741 ✗ descriptor->op = static_cast<std::uint16_t>(RcOp::kUpdate);
742
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (!CopyTableName(table_name, &descriptor->table_name)) {
743
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("UPDATE table name too long");
744 }
745 ✗ }
746
747 ✗ void PetPSClient::FillUpdateFlatDescriptor(
748 RequestDescriptor* descriptor,
749 std::uint64_t seq,
750 std::size_t key_count,
751 std::size_t payload_bytes,
752 std::size_t embedding_dim,
753 const std::string& table_name,
754 const RcClientQpView& view) const {
755 ✗ FillUpdateDescriptor(
756 descriptor, seq, key_count, payload_bytes, table_name, view);
757 ✗ descriptor->op = static_cast<std::uint16_t>(RcOp::kUpdateFlat);
758 ✗ descriptor->embedding_dim = static_cast<std::uint32_t>(embedding_dim);
759 ✗ }
760
761 ✗ void PetPSClient::FillInitTableDescriptor(
762 RequestDescriptor* descriptor,
763 std::uint64_t seq,
764 const std::string& table_name,
765 const RcClientQpView& view) const {
766 ✗ FillPutDescriptor(
767 descriptor, seq, /*key_count=*/0, InitTablePayloadBytes(), view);
768 ✗ descriptor->op = static_cast<std::uint16_t>(RcOp::kInitTable);
769
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (!CopyTableName(table_name, &descriptor->table_name)) {
770
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("INIT table name too long");
771 }
772 ✗ }
773
774 ✗ int PetPSClient::SubmitRpcLocked(
775 SlotContext* slot,
776 const RequestDescriptor& descriptor,
777 const void* payload,
778 std::size_t payload_bytes,
779 float* recv_buffer,
780 std::size_t key_count,
781 std::size_t response_bytes) {
782
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (slot == nullptr) {
783
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("slot context is null");
784 }
785 ✗ ResetStatusWord(slot->view.status, descriptor.seq);
786 ✗ const bool profile_enabled = FLAGS_rdma_rc_profile_interval_ms > 0;
787
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ const std::uint64_t submit_start_ns = profile_enabled ? NowNs() : 0;
788 try {
789
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ transport_->SubmitRequest(slot->view, descriptor, payload, payload_bytes);
790
0/3
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ } catch (const RawVerbsCompletionException& error) {
791 // Draining the previous request on this slot surfaced a dead QP: the slot
792 // must not be reused, so the lane is retired before failing this request.
793 ✗ FailLaneOnCompletionError(
794 slot->view.qp_index, error.error(), /*waited_ms=*/-1);
795 ✗ } catch (const RcTrackedCompletionTimeout&) {
796 // The previous request's completion never arrived: its slot memory may
797 // still be read by the server, so the lane is retired before the timeout
798 // is reported unchanged.
799
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ MarkLaneDirty(slot->view.qp_index,
800 "drain_timeout",
801 nullptr,
802 /*waited_ms=*/-1,
803 /*rebuildable=*/true);
804 ✗ throw;
805 ✗ }
806
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (profile_enabled) {
807 ✗ profile_.submit_rpc_count.fetch_add(1, std::memory_order_relaxed);
808 ✗ profile_.submit_request_ns.fetch_add(
809
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ NowNs() - submit_start_ns, std::memory_order_relaxed);
810 }
811
0/16
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
✗ VLOG(1) << "component=rdma_rc_client event=submit shard=" << shard_
812
0/8
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ << " client_id=" << client_id_ << " qp=" << slot->view.qp_index
813
0/10
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ << " slot=" << slot->view.slot_index << " seq=" << descriptor.seq
814
0/8
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ << " op=" << descriptor.op << " key_count=" << key_count
815
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ << " payload_bytes=" << payload_bytes
816
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ << " response_bytes=" << response_bytes;
817
818 ✗ const int rpc_id = next_rpc_id_.fetch_add(1);
819 ✗ pending_rpcs_.emplace(
820 rpc_id,
821 ✗ PendingRpc{
822 ✗ slot->view.qp_index,
823 ✗ slot->view.slot_in_qp,
824 ✗ slot->view.slot_index,
825 ✗ descriptor.seq,
826 recv_buffer,
827 key_count,
828 response_bytes,
829
0/2
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ qps_[static_cast<std::size_t>(slot->view.qp_index)].epoch.load(
830 std::memory_order_relaxed),
831 });
832
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (profile_enabled) {
833 ✗ const std::uint64_t pending_size = pending_rpcs_.size();
834 ✗ profile_.pending_rpc_samples.fetch_add(1, std::memory_order_relaxed);
835 ✗ profile_.pending_rpc_sum.fetch_add(pending_size, std::memory_order_relaxed);
836 ✗ profile_.pending_rpc_last.store(pending_size, std::memory_order_relaxed);
837 std::uint64_t peak =
838 ✗ profile_.pending_rpc_peak.load(std::memory_order_relaxed);
839
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ while (pending_size > peak &&
840
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ !profile_.pending_rpc_peak.compare_exchange_weak(
841 peak, pending_size, std::memory_order_relaxed)) {
842 }
843
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ MaybeReportProfile();
844 }
845 ✗ return rpc_id;
846 }
847
848 ✗ int PetPSClient::GetParameter(const base::ConstArray<uint64_t>& keys,
849 base::RecTensor& values) {
850
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (!recstore::IsFloatEmbeddingValues(
851 ✗ values, static_cast<int64_t>(keys.Size()))) {
852 ✗ return -1;
853 }
854
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (keys.Size() == 0) {
855 ✗ return 0;
856 }
857 ✗ const int embedding_dim = static_cast<int>(values.shape(1));
858 ✗ const int value_size = embedding_dim * static_cast<int>(sizeof(float));
859 const std::size_t response_bytes =
860 ✗ FixedSlotResponseBytes(keys.Size(), value_size);
861 ✗ float* recv = static_cast<float*>(GetReceiveBuffer(response_bytes));
862 ✗ const int rpc_id = GetParameter(keys, recv, false, 0, embedding_dim);
863 ✗ const auto* status = FixedSlotStatusWord(recv, keys.Size(), value_size);
864
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (*status != static_cast<std::int32_t>(RpcStatus::kOk)) {
865 ✗ RevokeRPCResource(rpc_id);
866 ✗ return -1;
867 }
868 ✗ std::memcpy(values.data_as<float>(),
869 recv,
870 ✗ keys.Size() * static_cast<std::size_t>(value_size));
871 ✗ RevokeRPCResource(rpc_id);
872 ✗ return 0;
873 }
874
875 ✗ int PetPSClient::PutParameter(const std::vector<uint64_t>& keys,
876 const base::RecTensor& values) {
877
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ return PutParameter(base::ConstArray<uint64_t>(keys), values);
878 }
879
880 ✗ int PetPSClient::InitEmbeddingTable(
881 const std::string& table_name,
882 const recstore::EmbeddingTableConfig& config) {
883 ✗ return InitEmbeddingTable(
884 ✗ table_name, config.num_embeddings, config.embedding_dim, config.table_id);
885 }
886
887
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ void PetPSClient::Command(recstore::PSCommand) { Barrier("rdma_command", 0); }
888
889 uint64_t
890 ✗ PetPSClient::PrefetchParameter(const base::ConstArray<uint64_t>& keys) {
891
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (keys.Size() == 0) {
892
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::invalid_argument("RDMA prefetch requires at least one key");
893 }
894 // ponytail: single-RPC prefetch only. A batch larger than the RC response
895 // budget throws here; chunking lives in the sharded routing layer, not on the
896 // single-shard transport.
897 ✗ const int embedding_dim = FLAGS_value_size / sizeof(float);
898 const std::size_t response_bytes =
899 ✗ FixedSlotResponseBytes(keys.Size(), FLAGS_value_size);
900
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ float* recv = static_cast<float*>(GetReceiveBuffer(response_bytes));
901 const int rpc_id =
902
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ GetParameter(keys, recv, /*isAsync=*/true, 0, embedding_dim);
903
904
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ std::lock_guard<std::mutex> guard(prefetch_mu_);
905 ✗ const uint64_t prefetch_id = next_prefetch_id_++;
906 ✗ prefetches_.emplace(
907
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ prefetch_id, PrefetchState{rpc_id, recv, keys.Size(), embedding_dim});
908 ✗ return prefetch_id;
909 ✗ }
910
911 ✗ bool PetPSClient::IsPrefetchDone(uint64_t prefetch_id) {
912
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ std::lock_guard<std::mutex> guard(prefetch_mu_);
913
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ const auto it = prefetches_.find(prefetch_id);
914
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (it == prefetches_.end()) {
915 ✗ return false;
916 }
917
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ return QueryRPCFinished(it->second.rpc_id);
918 ✗ }
919
920 ✗ void PetPSClient::WaitForPrefetch(uint64_t prefetch_id) {
921 ✗ int rpc_id = -1;
922 {
923
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ std::lock_guard<std::mutex> guard(prefetch_mu_);
924
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ const auto it = prefetches_.find(prefetch_id);
925
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (it == prefetches_.end()) {
926 ✗ return;
927 }
928 ✗ rpc_id = it->second.rpc_id;
929
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ }
930 ✗ WaitRPCFinish(rpc_id);
931 }
932
933 ✗ bool PetPSClient::GetPrefetchResult(uint64_t prefetch_id,
934 base::RecTensor& values) {
935 ✗ PrefetchState state;
936 {
937
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ std::lock_guard<std::mutex> guard(prefetch_mu_);
938
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ const auto it = prefetches_.find(prefetch_id);
939
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (it == prefetches_.end()) {
940 ✗ return false;
941 }
942 ✗ state = it->second;
943
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ prefetches_.erase(it);
944
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ }
945 ✗ bool failed = false;
946 {
947
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ std::lock_guard<std::mutex> guard(mu_);
948
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ failed = RpcFailedLocked(state.rpc_id);
949 ✗ }
950
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (failed) {
951 // The lane was rebuilt while this prefetch was in flight, so the response
952 // buffer holds no result. The status word cannot tell (a zero-filled buffer
953 // decodes as kOk), so check the pending marker first.
954
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ RevokeRPCResource(state.rpc_id);
955 ✗ return false;
956 }
957 const auto* status =
958 ✗ FixedSlotStatusWord(state.recv_buffer, state.key_count, FLAGS_value_size);
959
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (*status != static_cast<std::int32_t>(RpcStatus::kOk)) {
960
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ RevokeRPCResource(state.rpc_id);
961 ✗ return false;
962 }
963
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ const bool discard = values.data() == nullptr && values.dim() == 0;
964
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (!discard) {
965 ✗ if (!recstore::EnsureEmbeddingOutput(
966
0/6
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ values, static_cast<int64_t>(state.key_count)) ||
967
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ values.shape(1) != state.embedding_dim) {
968
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ RevokeRPCResource(state.rpc_id);
969 ✗ return false;
970 }
971 ✗ std::memcpy(
972 ✗ values.data_as<float>(),
973 ✗ state.recv_buffer,
974
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ state.key_count * static_cast<std::size_t>(state.embedding_dim) *
975 sizeof(float));
976 }
977
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ RevokeRPCResource(state.rpc_id);
978 ✗ return true;
979 }
980
981 ✗ int PetPSClient::GetParameter(
982 base::ConstArray<uint64_t> keys,
983 float* values,
984 bool isAsync,
985 int,
986 int embedding_dim) {
987
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (keys.Size() == 0) {
988 ✗ auto* status =
989 reinterpret_cast<std::int32_t*>(reinterpret_cast<char*>(values));
990 ✗ *status = static_cast<std::int32_t>(RpcStatus::kOk);
991 ✗ return 0;
992 }
993 ✗ const std::size_t value_size =
994 embedding_dim > 0
995
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ ? static_cast<std::size_t>(embedding_dim) * sizeof(float)
996 ✗ : static_cast<std::size_t>(FLAGS_value_size);
997 ✗ int rpc_id = 0;
998 {
999
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ std::lock_guard<std::mutex> guard(mu_);
1000
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ EnsureThreadInitializedLocked();
1001
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (keys.Size() > ComputeMaxGetKeysPerRpc(value_size)) {
1002
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ throw std::runtime_error(
1003 ✗ "single-shard GET batch exceeds RC response budget");
1004 }
1005
1006
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ const SlotHandle slot_handle = AcquireIdleSlot();
1007
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ auto& slot = SlotAt(slot_handle.qp_index, slot_handle.slot_in_qp);
1008 ✗ RequestDescriptor descriptor;
1009 const std::size_t response_bytes =
1010 ✗ GetResponseBytes(keys.Size(), value_size);
1011 ✗ FillGetDescriptor(
1012 &descriptor,
1013
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ slot.next_seq++,
1014 ✗ keys.Size(),
1015 response_bytes,
1016 value_size,
1017 ✗ slot.view);
1018
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (descriptor.payload_bytes >
1019 ✗ PutPayloadBudget(config_.request_slot_bytes)) {
1020 ✗ slot.busy = false;
1021
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("GET request exceeds RC request slot");
1022 }
1023
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ rpc_id = SubmitRpcLocked(
1024 &slot,
1025 descriptor,
1026 ✗ keys.Data(),
1027 ✗ descriptor.payload_bytes,
1028 values,
1029 ✗ keys.Size(),
1030 response_bytes);
1031 ✗ }
1032
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (!isAsync) {
1033 ✗ WaitRPCFinish(rpc_id);
1034 }
1035 ✗ return rpc_id;
1036 }
1037
1038 2 bool PetPSClient::QueryRPCFinished(int rpc_id) {
1039
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 std::lock_guard<std::mutex> guard(mu_);
1040 2 PendingRpc pending;
1041
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
2 if (!PendingRpcLocked(rpc_id, &pending)) {
1042 2 return true;
1043 }
1044
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (pending.failed) {
1045
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ throw ShardUnreachableError(
1046
0/8
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ "shard " + std::to_string(shard_) + " unreachable: lane " +
1047
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ std::to_string(pending.qp_index) + " rebuilt during wait");
1048 }
1049
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ const auto& slot = SlotAt(pending.qp_index, pending.slot_in_qp);
1050 ✗ return StatusWordDone(*slot.view.status, pending.seq);
1051 2 }
1052
1053 4 void PetPSClient::WaitRPCFinish(int rpc_id) {
1054 4 PendingRpc pending;
1055 {
1056
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 std::lock_guard<std::mutex> guard(mu_);
1057
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
4 if (!PendingRpcLocked(rpc_id, &pending)) {
1058 4 return;
1059 }
1060
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (pending.failed) {
1061
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ throw ShardUnreachableError(
1062
0/8
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ "shard " + std::to_string(shard_) + " unreachable: lane " +
1063
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ std::to_string(pending.qp_index) + " rebuilt during wait");
1064 }
1065
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
4 }
1066
1067
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ auto& slot = SlotAt(pending.qp_index, pending.slot_in_qp);
1068 ✗ const bool profile_enabled = FLAGS_rdma_rc_profile_interval_ms > 0;
1069
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ const std::uint64_t wait_start_ns = profile_enabled ? NowNs() : 0;
1070 ✗ const std::int32_t status_code = WaitForStatusWord(
1071
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ pending.qp_index, slot.view.status, pending.seq, pending.lane_epoch);
1072
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (profile_enabled) {
1073 ✗ profile_.wait_rpc_count.fetch_add(1, std::memory_order_relaxed);
1074 ✗ profile_.wait_status_ns.fetch_add(
1075
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ NowNs() - wait_start_ns, std::memory_order_relaxed);
1076 }
1077
0/16
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
✗ VLOG(1) << "component=rdma_rc_client event=done shard=" << shard_
1078
0/8
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ << " client_id=" << client_id_ << " qp=" << pending.qp_index
1079
0/10
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ << " slot=" << pending.slot_index << " seq=" << pending.seq
1080
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ << " status=" << status_code
1081
0/6
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ << " response_bytes=" << pending.response_bytes;
1082 ✗ const std::size_t actual_response_bytes = std::min<std::size_t>(
1083 ✗ slot.view.status->response_bytes, pending.response_bytes);
1084
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (actual_response_bytes > 0 && !FLAGS_rdma_rc_skip_client_copy) {
1085
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ const std::uint64_t copy_start_ns = profile_enabled ? NowNs() : 0;
1086 ✗ std::memcpy(
1087 ✗ pending.recv_buffer, slot.view.response_payload, actual_response_bytes);
1088
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (profile_enabled) {
1089 ✗ profile_.copy_response_ns.fetch_add(
1090
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ NowNs() - copy_start_ns, std::memory_order_relaxed);
1091 ✗ profile_.response_bytes_copied.fetch_add(
1092 actual_response_bytes, std::memory_order_relaxed);
1093 }
1094 }
1095 ✗ const int value_size =
1096 ✗ pending.key_count == 0
1097
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ ? FLAGS_value_size
1098 ✗ : static_cast<int>(pending.response_bytes / pending.key_count);
1099 auto* user_status =
1100 ✗ FixedSlotStatusWord(pending.recv_buffer, pending.key_count, value_size);
1101 ✗ *user_status = status_code;
1102
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ MaybeReportProfile();
1103 }
1104
1105 2 void PetPSClient::RevokeRPCResource(int rpc_id) {
1106
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 std::lock_guard<std::mutex> guard(mu_);
1107
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 const auto it = pending_rpcs_.find(rpc_id);
1108
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 if (it == pending_rpcs_.end()) {
1109 2 return;
1110 }
1111 ✗ const bool profile_enabled = FLAGS_rdma_rc_profile_interval_ms > 0;
1112
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ const std::uint64_t revoke_start_ns = profile_enabled ? NowNs() : 0;
1113 ✗ auto& qp = qps_[static_cast<std::size_t>(it->second.qp_index)];
1114 ✗ auto& slot = qp.slots[static_cast<std::size_t>(it->second.slot_in_qp)];
1115
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (it->second.failed) {
1116 // A lane rebuild already released this slot; it may now belong to another
1117 // request, so clearing it would corrupt that request's commit word.
1118
0/14
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ VLOG(1) << "component=rdma_rc_client event=revoke_skipped_failed_rpc"
1119
0/8
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ << " shard=" << shard_ << " lane=" << it->second.qp_index
1120
0/6
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ << " slot=" << it->second.slot_in_qp;
1121
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ } else if (qp.dirty.load(std::memory_order_relaxed)) {
1122 // A retired lane never hands its slots back: the server may still read the
1123 // request payload, or the completion may still arrive later.
1124
0/14
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ VLOG(1) << "component=rdma_rc_client event=revoke_skipped_dirty_lane"
1125
0/8
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ << " shard=" << shard_ << " lane=" << it->second.qp_index
1126
0/6
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ << " slot=" << it->second.slot_in_qp;
1127 } else {
1128
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ transport_->ClearRequestSlot(slot.view);
1129 ✗ slot.busy = false;
1130 }
1131 // NOTE: the recv_buffer is NOT returned to the pool here. In the adapter's
1132 // sync chunk window the batch still references this buffer until
1133 // FinalizeBatchIfNeeded consumes it; returning it early would let a later
1134 // GetReceiveBuffer overwrite the response. The adapter returns chunk buffers
1135 // to the pool via ReturnGetReceiveBuffer() after the batch is finalized.
1136
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ pending_rpcs_.erase(it);
1137
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (profile_enabled) {
1138 ✗ const std::uint64_t pending_size = pending_rpcs_.size();
1139 ✗ profile_.pending_rpc_samples.fetch_add(1, std::memory_order_relaxed);
1140 ✗ profile_.pending_rpc_sum.fetch_add(pending_size, std::memory_order_relaxed);
1141 ✗ profile_.pending_rpc_last.store(pending_size, std::memory_order_relaxed);
1142 ✗ profile_.revoke_rpc_count.fetch_add(1, std::memory_order_relaxed);
1143 ✗ profile_.revoke_resource_ns.fetch_add(
1144
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ NowNs() - revoke_start_ns, std::memory_order_relaxed);
1145
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ MaybeReportProfile();
1146 }
1147
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 }
1148
1149 ✗ int PetPSClient::PutParameter(const base::ConstArray<uint64_t>& keys,
1150 const base::RecTensor& values) {
1151
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ if (!recstore::IsFloatEmbeddingValues(
1152 ✗ values, static_cast<int64_t>(keys.Size()))) {
1153 ✗ return -1;
1154 }
1155
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (keys.Size() == 0) {
1156 ✗ return 0;
1157 }
1158
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ const int64_t D = values.shape(1);
1159
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ const float* src = values.data_as<float>();
1160
1161 ✗ std::size_t begin = 0;
1162 ✗ const std::size_t total_keys = static_cast<std::size_t>(keys.Size());
1163
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ while (begin < total_keys) {
1164 const std::size_t end =
1165 ✗ std::min(begin + static_cast<std::size_t>(FLAGS_max_kv_num_per_request),
1166 ✗ total_keys);
1167 const base::ConstArray<uint64_t> key_slice =
1168
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ keys.SubArray(static_cast<int>(begin), static_cast<int>(end));
1169 base::RecTensor value_slice(
1170 ✗ const_cast<float*>(src + begin * static_cast<size_t>(D)),
1171
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ {static_cast<int64_t>(end - begin), D});
1172
1173 ✗ std::string payload;
1174 ✗ std::string error;
1175 const std::size_t payload_bytes =
1176
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ PutPayloadBytes(key_slice, value_slice, &payload, &error);
1177
0/6
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ if (payload_bytes == 0 && key_slice.Size() != 0) {
1178
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ throw std::runtime_error("RC PUT payload build failed: " + error);
1179 }
1180
1181 ✗ float* recv = nullptr;
1182 ✗ int rpc_id = 0;
1183 {
1184
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ std::lock_guard<std::mutex> guard(mu_);
1185
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ EnsureThreadInitializedLocked();
1186
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ const SlotHandle slot_handle = AcquireIdleSlot();
1187
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ auto& slot = SlotAt(slot_handle.qp_index, slot_handle.slot_in_qp);
1188 ✗ RequestDescriptor descriptor;
1189 ✗ FillPutDescriptor(
1190 &descriptor,
1191
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ slot.next_seq++,
1192 ✗ key_slice.Size(),
1193 payload_bytes,
1194 ✗ slot.view);
1195
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ if (!RequestPayloadFitsSlot(payload_bytes)) {
1196 ✗ slot.busy = false;
1197
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("PUT request exceeds RC request slot");
1198 }
1199
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ recv = AllocateStatusReceiveBufferLocked();
1200
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ rpc_id = SubmitRpcLocked(
1201 ✗ &slot, descriptor, payload.data(), payload_bytes, recv, 0, 0);
1202 ✗ }
1203
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ WaitRPCFinish(rpc_id);
1204 ✗ const auto* status = reinterpret_cast<const std::int32_t*>(recv);
1205
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ RevokeRPCResource(rpc_id);
1206
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (*status != static_cast<std::int32_t>(RpcStatus::kOk)) {
1207 ✗ return -1;
1208 }
1209 ✗ begin = end;
1210
0/6
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ }
1211
1212 ✗ return 0;
1213 }
1214
1215 ✗ int PetPSClient::InitEmbeddingTable(
1216 const std::string& table_name,
1217 std::uint64_t num_embeddings,
1218 std::uint64_t embedding_dim,
1219 std::uint64_t table_id) {
1220 const std::array<std::uint64_t, 3> payload_words = {
1221 num_embeddings,
1222 embedding_dim,
1223 table_id,
1224 ✗ };
1225
1226 ✗ float* recv = nullptr;
1227 ✗ int rpc_id = 0;
1228 {
1229
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ std::lock_guard<std::mutex> guard(mu_);
1230
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ EnsureThreadInitializedLocked();
1231
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ const SlotHandle slot_handle = AcquireIdleSlot();
1232
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ auto& slot = SlotAt(slot_handle.qp_index, slot_handle.slot_in_qp);
1233 ✗ RequestDescriptor descriptor;
1234 ✗ FillInitTableDescriptor(
1235
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ &descriptor, slot.next_seq++, table_name, slot.view);
1236
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ if (!RequestPayloadFitsSlot(descriptor.payload_bytes)) {
1237 ✗ slot.busy = false;
1238
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("INIT request exceeds RC request slot");
1239 }
1240
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ recv = AllocateStatusReceiveBufferLocked();
1241
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ rpc_id = SubmitRpcLocked(
1242 &slot,
1243 descriptor,
1244 ✗ payload_words.data(),
1245 ✗ descriptor.payload_bytes,
1246 recv,
1247 0,
1248 0);
1249 ✗ }
1250
1251
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ WaitRPCFinish(rpc_id);
1252 ✗ PendingRpc pending;
1253 ✗ std::int32_t rpc_status =
1254 static_cast<std::int32_t>(RpcStatus::kInvalidPayload);
1255 ✗ std::uint32_t tag_word = 0;
1256 {
1257
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ std::lock_guard<std::mutex> guard(mu_);
1258
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ if (PendingRpcLocked(rpc_id, &pending)) {
1259
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ const auto& slot = SlotAt(pending.qp_index, pending.slot_in_qp);
1260 ✗ rpc_status = slot.view.status->status;
1261 ✗ tag_word = slot.view.status->reserved;
1262 }
1263 ✗ }
1264
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ RevokeRPCResource(rpc_id);
1265
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (rpc_status != static_cast<std::int32_t>(RpcStatus::kOk)) {
1266 ✗ return -1;
1267 }
1268 ✗ return static_cast<int>(tag_word);
1269 }
1270
1271 ✗ int PetPSClient::UpdateParameter(const std::string& table_name,
1272 const base::ConstArray<uint64_t>& keys,
1273 const base::RecTensor& grads) {
1274
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (keys.Size() == 0) {
1275 ✗ return 0;
1276 }
1277
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ if (!recstore::IsFloatEmbeddingValues(
1278 ✗ grads, static_cast<int64_t>(keys.Size()))) {
1279 ✗ return -1;
1280 }
1281
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ const int64_t D = grads.shape(1);
1282
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ const float* src = grads.data_as<float>();
1283
1284 ✗ std::size_t begin = 0;
1285 ✗ const std::size_t total_keys = static_cast<std::size_t>(keys.Size());
1286
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ while (begin < total_keys) {
1287 const std::size_t end =
1288 ✗ std::min(begin + static_cast<std::size_t>(FLAGS_max_kv_num_per_request),
1289 ✗ total_keys);
1290 const base::ConstArray<uint64_t> key_slice =
1291
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ keys.SubArray(static_cast<int>(begin), static_cast<int>(end));
1292 base::RecTensor grad_slice(
1293 ✗ const_cast<float*>(src + begin * static_cast<size_t>(D)),
1294
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ {static_cast<int64_t>(end - begin), D});
1295
1296 ✗ std::string payload;
1297 ✗ std::string error;
1298 const std::size_t payload_bytes =
1299
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ UpdatePayloadBytes(key_slice, grad_slice, &payload, &error);
1300
0/6
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ if (payload_bytes == 0 && key_slice.Size() != 0) {
1301
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ throw std::runtime_error("RC UPDATE payload build failed: " + error);
1302 }
1303
1304 ✗ float* recv = nullptr;
1305 ✗ int rpc_id = 0;
1306 {
1307
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ std::lock_guard<std::mutex> guard(mu_);
1308
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ EnsureThreadInitializedLocked();
1309
1310
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ const SlotHandle slot_handle = AcquireIdleSlot();
1311
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ auto& slot = SlotAt(slot_handle.qp_index, slot_handle.slot_in_qp);
1312 ✗ RequestDescriptor descriptor;
1313 ✗ FillUpdateDescriptor(
1314 &descriptor,
1315
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ slot.next_seq++,
1316 ✗ key_slice.Size(),
1317 payload_bytes,
1318 table_name,
1319 ✗ slot.view);
1320
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ if (!RequestPayloadFitsSlot(payload_bytes)) {
1321 ✗ slot.busy = false;
1322
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("UPDATE request exceeds RC request slot");
1323 }
1324
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ recv = AllocateStatusReceiveBufferLocked();
1325
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ rpc_id = SubmitRpcLocked(
1326 ✗ &slot, descriptor, payload.data(), payload_bytes, recv, 0, 0);
1327 ✗ }
1328
1329
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ WaitRPCFinish(rpc_id);
1330 ✗ const auto* status = reinterpret_cast<const std::int32_t*>(recv);
1331
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ RevokeRPCResource(rpc_id);
1332
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (*status != static_cast<std::int32_t>(RpcStatus::kOk)) {
1333 ✗ return -1;
1334 }
1335 ✗ begin = end;
1336
0/6
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ }
1337
1338 ✗ return 0;
1339 }
1340
1341 ✗ int PetPSClient::SubmitUpdateParameterFlat(
1342 const std::string& table_name,
1343 base::ConstArray<uint64_t> keys,
1344 const float* grads,
1345 std::size_t embedding_dim) {
1346
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (keys.Size() == 0) {
1347 ✗ return 0;
1348 }
1349
0/6
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ if (grads == nullptr || embedding_dim == 0 ||
1350
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ keys.Size() > static_cast<std::size_t>(FLAGS_max_kv_num_per_request)) {
1351 ✗ return -1;
1352 }
1353
1354 const std::size_t payload_bytes =
1355 ✗ FlatUpdatePayloadBytes(keys.Size(), embedding_dim);
1356
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (payload_bytes == 0) {
1357
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("RC UPDATE payload has invalid shape");
1358 }
1359
1360
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ std::lock_guard<std::mutex> guard(mu_);
1361
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ EnsureThreadInitializedLocked();
1362
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ const SlotHandle slot_handle = AcquireIdleSlot();
1363
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ auto& slot = SlotAt(slot_handle.qp_index, slot_handle.slot_in_qp);
1364
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ if (!RequestPayloadFitsSlot(payload_bytes)) {
1365 ✗ slot.busy = false;
1366
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("UPDATE request exceeds RC request slot");
1367 }
1368 ✗ const std::size_t key_bytes = keys.Size() * sizeof(std::uint64_t);
1369 ✗ auto* payload = static_cast<char*>(slot.view.payload);
1370 ✗ std::memcpy(payload, keys.Data(), key_bytes);
1371 ✗ std::memcpy(
1372 ✗ payload + key_bytes, grads, keys.Size() * embedding_dim * sizeof(float));
1373
1374 ✗ RequestDescriptor descriptor;
1375 ✗ FillUpdateFlatDescriptor(
1376 &descriptor,
1377
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ slot.next_seq++,
1378 ✗ keys.Size(),
1379 payload_bytes,
1380 embedding_dim,
1381 table_name,
1382 ✗ slot.view);
1383
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ float* recv = AllocateStatusReceiveBufferLocked();
1384
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ return SubmitRpcLocked(&slot, descriptor, payload, payload_bytes, recv, 0, 0);
1385 ✗ }
1386
1387 ✗ int PetPSClient::SubmitUpdateParameterFlatGather(
1388 const std::string& table_name,
1389 const std::uint64_t* keys,
1390 const float* grads,
1391 std::size_t num_rows,
1392 std::size_t embedding_dim,
1393 const std::size_t* row_indices,
1394 std::size_t row_count) {
1395
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (row_count == 0) {
1396 ✗ return 0;
1397 }
1398
0/8
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ if (keys == nullptr || grads == nullptr || row_indices == nullptr ||
1399 ✗ embedding_dim == 0 ||
1400
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ row_count > static_cast<std::size_t>(FLAGS_max_kv_num_per_request)) {
1401 ✗ return -1;
1402 }
1403
1404 const std::size_t payload_bytes =
1405 ✗ FlatUpdatePayloadBytes(row_count, embedding_dim);
1406
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (payload_bytes == 0) {
1407
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("RC UPDATE gather payload has invalid shape");
1408 }
1409
1410
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ std::lock_guard<std::mutex> guard(mu_);
1411
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ EnsureThreadInitializedLocked();
1412
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ const SlotHandle slot_handle = AcquireIdleSlot();
1413
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ auto& slot = SlotAt(slot_handle.qp_index, slot_handle.slot_in_qp);
1414
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ if (!RequestPayloadFitsSlot(payload_bytes)) {
1415 ✗ slot.busy = false;
1416
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("UPDATE gather request exceeds RC request slot");
1417 }
1418 ✗ std::string error;
1419 ✗ if (PackFlatUpdatePayloadGather(
1420 keys,
1421 grads,
1422 num_rows,
1423 embedding_dim,
1424 row_indices,
1425 row_count,
1426
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ slot.view.payload,
1427 payload_bytes,
1428
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ &error) == 0) {
1429 ✗ slot.busy = false;
1430
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ throw std::runtime_error("RC UPDATE gather payload build failed: " + error);
1431 }
1432
1433 ✗ RequestDescriptor descriptor;
1434 ✗ FillUpdateFlatDescriptor(
1435 &descriptor,
1436 ✗ slot.next_seq++,
1437 row_count,
1438 payload_bytes,
1439 embedding_dim,
1440 table_name,
1441
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ slot.view);
1442
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ float* recv = AllocateStatusReceiveBufferLocked();
1443 ✗ return SubmitRpcLocked(
1444
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ &slot, descriptor, slot.view.payload, payload_bytes, recv, 0, 0);
1445 ✗ }
1446
1447 ✗ size_t PetPSClient::SparseGradientCommitPayloadLimit() const {
1448 ✗ return PutPayloadBudget(FLAGS_rdma_rc_request_slot_bytes);
1449 }
1450
1451 ✗ int64_t PetPSClient::ExchangeSparseGradientCommit(const std::string& table,
1452 const std::string& data) {
1453 ✗ return WaitSparseGradientCommit(SubmitSparseGradientCommit(table, data));
1454 }
1455
1456 ✗ int PetPSClient::SubmitSparseGradientCommit(const std::string& table,
1457 const std::string& data) {
1458 {
1459
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ std::lock_guard<std::mutex> guard(mu_);
1460
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ EnsureThreadInitializedLocked();
1461
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ if (!RequestPayloadFitsSlot(data.size())) {
1462
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("Sparse gradient commit exceeds RDMA slot");
1463 }
1464
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ const auto slot_handle = AcquireIdleSlot();
1465
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ auto& slot = SlotAt(slot_handle.qp_index, slot_handle.slot_in_qp);
1466 ✗ RequestDescriptor descriptor;
1467 ✗ FillUpdateFlatDescriptor(
1468
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ &descriptor, slot.next_seq++, 0, data.size(), 0, table, slot.view);
1469 ✗ descriptor.op = static_cast<uint16_t>(RcOp::kSparseGradientCommit);
1470 ✗ descriptor.response_bytes = sizeof(int64_t);
1471
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ return SubmitRpcLocked(
1472 &slot,
1473 descriptor,
1474 ✗ data.data(),
1475 data.size(),
1476 nullptr,
1477 0,
1478 ✗ sizeof(int64_t));
1479 ✗ }
1480 }
1481
1482 ✗ int64_t PetPSClient::WaitSparseGradientCommit(int rpc) {
1483 try {
1484 ✗ size_t bytes = 0;
1485 ✗ int32_t status = 0;
1486
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ const auto* result = BorrowGetResultPayload(rpc, nullptr, &bytes, &status);
1487
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (status != static_cast<int32_t>(RpcStatus::kOk) ||
1488
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ bytes != sizeof(int64_t)) {
1489
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("RDMA sparse gradient commit failed");
1490 }
1491 int64_t committed;
1492 ✗ std::memcpy(&committed, result, sizeof(committed));
1493
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ RevokeRPCResource(rpc);
1494 ✗ return committed;
1495 ✗ } catch (...) {
1496
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ RevokeRPCResource(rpc);
1497 ✗ throw;
1498 ✗ }
1499 }
1500
1501 2 int PetPSClient::WaitUpdateParameter(int rpc_id) {
1502
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (rpc_id == 0) {
1503 ✗ return 0;
1504 }
1505
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 WaitRPCFinish(rpc_id);
1506 2 PendingRpc pending;
1507 {
1508
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 std::lock_guard<std::mutex> guard(mu_);
1509
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
2 if (!PendingRpcLocked(rpc_id, &pending)) {
1510 2 return -1;
1511 }
1512
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 }
1513 ✗ const auto status =
1514 ✗ *reinterpret_cast<const std::int32_t*>(pending.recv_buffer);
1515
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ RevokeRPCResource(rpc_id);
1516
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ return status == static_cast<std::int32_t>(RpcStatus::kOk) ? 0 : -1;
1517 }
1518
1519 ✗ int PetPSClient::FakePutParameter(base::ConstArray<uint64_t> keys,
1520 float* values) {
1521 ✗ const int embedding_dim = FLAGS_value_size / sizeof(float);
1522 base::RecTensor tensor(
1523
0/4
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ values, {static_cast<int64_t>(keys.Size()), embedding_dim});
1524
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ return PutParameter(keys, tensor);
1525 ✗ }
1526
1527 } // namespace petps
1528