ps/rdma/raw_verbs_transport.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <cstddef> | ||
| 4 | #include <cstdint> | ||
| 5 | #include <memory> | ||
| 6 | #include <stdexcept> | ||
| 7 | #include <string> | ||
| 8 | #include <vector> | ||
| 9 | |||
| 10 | #include <infiniband/verbs.h> | ||
| 11 | |||
| 12 | #include "base/array.h" | ||
| 13 | #include "ps/rdma/global_address.h" | ||
| 14 | #include "ps/rdma/rdma_deployment.h" | ||
| 15 | |||
| 16 | namespace petps { | ||
| 17 | |||
| 18 | class RdmaControlPlaneClient; | ||
| 19 | |||
| 20 | struct RawVerbsConfig { | ||
| 21 | int global_id = 0; | ||
| 22 | int local_lane = 0; | ||
| 23 | int remote_lane = 0; | ||
| 24 | int only_node_id = -1; // Optional single peer node filter. | ||
| 25 | int num_servers = 1; | ||
| 26 | int num_clients = 1; | ||
| 27 | std::string device_name; | ||
| 28 | std::uint8_t port_num = 0; | ||
| 29 | recstore::RdmaFabricMode fabric_mode = recstore::RdmaFabricMode::kIb; | ||
| 30 | int gid_index = -1; | ||
| 31 | std::uint8_t hop_limit = 0; | ||
| 32 | std::uint8_t traffic_class = 0; | ||
| 33 | std::uint32_t flow_label = 0; | ||
| 34 | std::string deployment_id; | ||
| 35 | std::uint64_t deployment_epoch = 0; | ||
| 36 | std::uint32_t protocol_version = recstore::kRdmaDeploymentProtocolVersion; | ||
| 37 | std::string configuration_digest; | ||
| 38 | std::string fabric_digest; | ||
| 39 | std::uint32_t max_inline_data = 0; | ||
| 40 | bool connect_to_servers = true; | ||
| 41 | bool connect_to_clients = true; | ||
| 42 | std::size_t local_region_bytes = 128 * 1024 * 1024; | ||
| 43 | std::uint64_t local_base_addr = 0; | ||
| 44 | // Global offset of local_base_addr inside this lane's logical address | ||
| 45 | // window. A lane may allocate only its own slice while still publishing | ||
| 46 | // globally-addressed offsets to peers, which subtracts this origin again. | ||
| 47 | std::uint64_t region_origin_offset = 0; | ||
| 48 | std::string control_plane_host = "127.0.0.1"; | ||
| 49 | int control_plane_port = 25100; | ||
| 50 | int control_plane_timeout_ms = 30000; | ||
| 51 | std::uint64_t allocation_start_offset = 0; | ||
| 52 | std::uint64_t reserved_region_offset = 0; | ||
| 53 | std::uint64_t reserved_region_bytes = 0; | ||
| 54 | }; | ||
| 55 | |||
| 56 | 2 | inline std::string RawVerbsMetaKey( | |
| 57 | int publisher_node_id, | ||
| 58 | int publisher_lane, | ||
| 59 | int receiver_node_id, | ||
| 60 | int receiver_lane) { | ||
| 61 | 4 | return "raw-verbs-meta-" + std::to_string(publisher_node_id) + "-lane-" + | |
| 62 | 8 | std::to_string(publisher_lane) + "-to-" + | |
| 63 | 8 | std::to_string(receiver_node_id) + "-lane-" + | |
| 64 | 6 | std::to_string(receiver_lane); | |
| 65 | } | ||
| 66 | |||
| 67 | inline bool | ||
| 68 | 16 | ShouldRawVerbsConnectToNode(const RawVerbsConfig& config, int node_id) { | |
| 69 | 16 | if (node_id == config.global_id) { | |
| 70 | 4 | return false; | |
| 71 | } | ||
| 72 | 12 | if (node_id < 0 || node_id >= config.num_servers + config.num_clients) { | |
| 73 | ✗ | return false; | |
| 74 | } | ||
| 75 | 12 | if (config.only_node_id >= 0 && node_id != config.only_node_id) { | |
| 76 | ✗ | return false; | |
| 77 | } | ||
| 78 | 12 | const bool is_server = node_id < config.num_servers; | |
| 79 | 12 | return is_server ? config.connect_to_servers : config.connect_to_clients; | |
| 80 | } | ||
| 81 | |||
| 82 | struct RawVerbsReservedRegion { | ||
| 83 | std::uint64_t offset = 0; | ||
| 84 | std::uint64_t bytes = 0; | ||
| 85 | }; | ||
| 86 | |||
| 87 | class RawVerbsRegionAllocator { | ||
| 88 | public: | ||
| 89 | 6 | explicit RawVerbsRegionAllocator(std::uint64_t limit_bytes, | |
| 90 | std::uint64_t allocation_start_offset = 0) | ||
| 91 | 6 | : limit_bytes_(limit_bytes), allocation_offset_(allocation_start_offset) { | |
| 92 | 6 | if (allocation_start_offset > limit_bytes) { | |
| 93 | ✗ | throw std::runtime_error( | |
| 94 | ✗ | "raw verbs allocation start outside local memory"); | |
| 95 | } | ||
| 96 | 6 | } | |
| 97 | |||
| 98 | 6 | void SetReservedRegion(RawVerbsReservedRegion reserved) { | |
| 99 | 6 | if (reserved.bytes != 0) { | |
| 100 | 6 | const std::uint64_t reserved_end = reserved.offset + reserved.bytes; | |
| 101 | 6 | if (reserved_end < reserved.offset || reserved_end > limit_bytes_) { | |
| 102 | 2 | throw std::runtime_error( | |
| 103 | 4 | "raw verbs reserved region outside local memory"); | |
| 104 | } | ||
| 105 | } | ||
| 106 | 4 | reserved_ = reserved; | |
| 107 | 4 | const std::uint64_t reserved_end = reserved.offset + reserved.bytes; | |
| 108 | 4 | if (reserved.bytes != 0 && allocation_offset_ >= reserved.offset && | |
| 109 | ✗ | allocation_offset_ < reserved_end) { | |
| 110 | ✗ | allocation_offset_ = reserved_end; | |
| 111 | } | ||
| 112 | 4 | } | |
| 113 | |||
| 114 | 12 | std::uint64_t Allocate(std::size_t bytes) { | |
| 115 | 12 | const std::uint64_t aligned = Align(bytes); | |
| 116 | 12 | std::uint64_t offset = allocation_offset_; | |
| 117 | 12 | const std::uint64_t reserved_begin = reserved_.offset; | |
| 118 | 12 | const std::uint64_t reserved_end = reserved_.offset + reserved_.bytes; | |
| 119 | 12 | if (reserved_.bytes != 0) { | |
| 120 | 12 | if (offset >= reserved_begin && offset < reserved_end) { | |
| 121 | 2 | offset = reserved_end; | |
| 122 | 10 | } else if (offset < reserved_begin && offset + aligned > reserved_begin) { | |
| 123 | ✗ | offset = reserved_end; | |
| 124 | } | ||
| 125 | } | ||
| 126 | 12 | if (offset + aligned < offset || offset + aligned > limit_bytes_) { | |
| 127 | ✗ | throw std::runtime_error("raw verbs registered region exhausted"); | |
| 128 | } | ||
| 129 | 12 | allocation_offset_ = offset + aligned; | |
| 130 | 12 | return offset; | |
| 131 | } | ||
| 132 | |||
| 133 | 2 | std::uint64_t Checkpoint() const { return allocation_offset_; } | |
| 134 | |||
| 135 | 2 | void Restore(std::uint64_t checkpoint) { | |
| 136 | 2 | if (checkpoint > limit_bytes_) { | |
| 137 | ✗ | throw std::runtime_error( | |
| 138 | ✗ | "raw verbs allocation checkpoint outside local memory"); | |
| 139 | } | ||
| 140 | 2 | allocation_offset_ = checkpoint; | |
| 141 | 2 | } | |
| 142 | |||
| 143 | 6 | std::uint64_t current_offset() const { return allocation_offset_; } | |
| 144 | |||
| 145 | private: | ||
| 146 | 12 | static std::uint64_t Align(std::size_t bytes) { | |
| 147 | 12 | return (static_cast<std::uint64_t>(bytes) + 63) & ~std::uint64_t{63}; | |
| 148 | } | ||
| 149 | |||
| 150 | std::uint64_t limit_bytes_ = 0; | ||
| 151 | std::uint64_t allocation_offset_ = 0; | ||
| 152 | RawVerbsReservedRegion reserved_{}; | ||
| 153 | }; | ||
| 154 | |||
| 155 | class RawVerbsRegionAllocatorScope { | ||
| 156 | public: | ||
| 157 | 2 | explicit RawVerbsRegionAllocatorScope(RawVerbsRegionAllocator* allocator) | |
| 158 | 2 | : allocator_(allocator), | |
| 159 | 2 | checkpoint_(allocator != nullptr ? allocator->Checkpoint() : 0) {} | |
| 160 | |||
| 161 | 2 | ~RawVerbsRegionAllocatorScope() { | |
| 162 | 2 | if (allocator_ != nullptr) { | |
| 163 | 2 | allocator_->Restore(checkpoint_); | |
| 164 | } | ||
| 165 | 2 | } | |
| 166 | |||
| 167 | RawVerbsRegionAllocatorScope(const RawVerbsRegionAllocatorScope&) = delete; | ||
| 168 | RawVerbsRegionAllocatorScope& | ||
| 169 | operator=(const RawVerbsRegionAllocatorScope&) = delete; | ||
| 170 | |||
| 171 | private: | ||
| 172 | RawVerbsRegionAllocator* allocator_ = nullptr; | ||
| 173 | std::uint64_t checkpoint_ = 0; | ||
| 174 | }; | ||
| 175 | |||
| 176 | struct RawVerbsRemoteMemory { | ||
| 177 | std::uint16_t node_id = 0; | ||
| 178 | std::uint64_t base_addr = 0; | ||
| 179 | std::uint32_t rkey = 0; | ||
| 180 | }; | ||
| 181 | |||
| 182 | struct RawVerbsCompletion { | ||
| 183 | std::uint64_t wr_id = 0; | ||
| 184 | std::uint32_t imm_data = 0; | ||
| 185 | bool has_imm = false; | ||
| 186 | ibv_wc_opcode opcode = IBV_WC_SEND; | ||
| 187 | }; | ||
| 188 | |||
| 189 | // Classification of a non-successful CQ completion (device-layer failure | ||
| 190 | // semantics). Only the peer-side classes say the remote node stopped | ||
| 191 | // answering; local classes mean the fault is ours and a QP rebuild would not | ||
| 192 | // help. | ||
| 193 | enum class RawVerbsErrorClass : std::uint8_t { | ||
| 194 | kPeerUnreachable = 0, // Retry / RNR retry exhausted: no ACK from the peer. | ||
| 195 | kPeerRejected = 1, // The peer answered but rejected the operation. | ||
| 196 | kLocalError = 2, // Local length/protection/QP-operation fault. | ||
| 197 | kFlushed = 3, // The WR was flushed from a QP that was already dead. | ||
| 198 | kOther = 4, | ||
| 199 | }; | ||
| 200 | |||
| 201 | 10 | inline const char* RawVerbsErrorClassToString(RawVerbsErrorClass error_class) { | |
| 202 | 10 | switch (error_class) { | |
| 203 | 2 | case RawVerbsErrorClass::kPeerUnreachable: | |
| 204 | 2 | return "peer_unreachable"; | |
| 205 | 2 | case RawVerbsErrorClass::kPeerRejected: | |
| 206 | 2 | return "peer_rejected"; | |
| 207 | 2 | case RawVerbsErrorClass::kLocalError: | |
| 208 | 2 | return "local_error"; | |
| 209 | 2 | case RawVerbsErrorClass::kFlushed: | |
| 210 | 2 | return "flushed"; | |
| 211 | 2 | case RawVerbsErrorClass::kOther: | |
| 212 | 2 | return "other"; | |
| 213 | } | ||
| 214 | ✗ | return "other"; | |
| 215 | } | ||
| 216 | |||
| 217 | inline RawVerbsErrorClass | ||
| 218 | 26 | ClassifyRawVerbsCompletionStatus(ibv_wc_status status) { | |
| 219 | 26 | switch (status) { | |
| 220 | 4 | case IBV_WC_RETRY_EXC_ERR: | |
| 221 | case IBV_WC_RNR_RETRY_EXC_ERR: | ||
| 222 | 4 | return RawVerbsErrorClass::kPeerUnreachable; | |
| 223 | 6 | case IBV_WC_REM_INV_REQ_ERR: | |
| 224 | case IBV_WC_REM_ACCESS_ERR: | ||
| 225 | case IBV_WC_REM_OP_ERR: | ||
| 226 | 6 | return RawVerbsErrorClass::kPeerRejected; | |
| 227 | 10 | case IBV_WC_LOC_LEN_ERR: | |
| 228 | case IBV_WC_LOC_QP_OP_ERR: | ||
| 229 | case IBV_WC_LOC_PROT_ERR: | ||
| 230 | case IBV_WC_LOC_ACCESS_ERR: | ||
| 231 | case IBV_WC_LOC_RDD_VIOL_ERR: | ||
| 232 | 10 | return RawVerbsErrorClass::kLocalError; | |
| 233 | 2 | case IBV_WC_WR_FLUSH_ERR: | |
| 234 | 2 | return RawVerbsErrorClass::kFlushed; | |
| 235 | 4 | default: | |
| 236 | 4 | return RawVerbsErrorClass::kOther; | |
| 237 | } | ||
| 238 | } | ||
| 239 | |||
| 240 | // True when the completion error means the shard cannot be reached on this | ||
| 241 | // lane: the peer stopped answering, or the lane was already flushed dead. | ||
| 242 | 26 | inline bool IsRawVerbsUnreachableError(RawVerbsErrorClass error_class) { | |
| 243 | 26 | return error_class == RawVerbsErrorClass::kPeerUnreachable || | |
| 244 | 26 | error_class == RawVerbsErrorClass::kFlushed; | |
| 245 | } | ||
| 246 | |||
| 247 | struct RawVerbsCompletionError { | ||
| 248 | ibv_wc_status status = IBV_WC_SUCCESS; | ||
| 249 | std::uint64_t wr_id = 0; | ||
| 250 | ibv_wc_opcode opcode = IBV_WC_SEND; | ||
| 251 | RawVerbsErrorClass error_class = RawVerbsErrorClass::kOther; | ||
| 252 | }; | ||
| 253 | |||
| 254 | // Human-readable form, e.g. "raw verbs CQ error: retry exceeded | ||
| 255 | // class=peer_unreachable wr_id=0 opcode=0". | ||
| 256 | std::string | ||
| 257 | DescribeRawVerbsCompletionError(const RawVerbsCompletionError& error); | ||
| 258 | |||
| 259 | class RawVerbsCompletionException : public std::runtime_error { | ||
| 260 | public: | ||
| 261 | explicit RawVerbsCompletionException(const RawVerbsCompletionError& error, | ||
| 262 | const std::string& context = ""); | ||
| 263 | ✗ | const RawVerbsCompletionError& error() const { return error_; } | |
| 264 | |||
| 265 | private: | ||
| 266 | RawVerbsCompletionError error_; | ||
| 267 | }; | ||
| 268 | |||
| 269 | enum class RawVerbsPollOutcome : std::uint8_t { | ||
| 270 | kEmpty, // No completion available. | ||
| 271 | kCompletion, // A successful completion was returned. | ||
| 272 | kError, // A completion error was returned instead of thrown. | ||
| 273 | }; | ||
| 274 | |||
| 275 | struct RawVerbsSge { | ||
| 276 | const void* data = nullptr; | ||
| 277 | std::size_t bytes = 0; | ||
| 278 | }; | ||
| 279 | |||
| 280 | inline constexpr int kRawVerbsPollBatchSize = 16; | ||
| 281 | |||
| 282 | class RawVerbsCompletionBatchCursor { | ||
| 283 | public: | ||
| 284 | ✗ | bool HasCachedCompletion() const { return current_ < size_; } | |
| 285 | |||
| 286 | ✗ | void Reset(ibv_wc* entries, int size) { | |
| 287 | ✗ | entries_ = entries; | |
| 288 | ✗ | size_ = size; | |
| 289 | ✗ | current_ = 0; | |
| 290 | ✗ | } | |
| 291 | |||
| 292 | ✗ | ibv_wc* TakeCachedCompletion() { | |
| 293 | ✗ | if (!HasCachedCompletion()) { | |
| 294 | ✗ | return nullptr; | |
| 295 | } | ||
| 296 | ✗ | return entries_ + current_++; | |
| 297 | } | ||
| 298 | |||
| 299 | private: | ||
| 300 | ibv_wc* entries_ = nullptr; | ||
| 301 | int size_ = 0; | ||
| 302 | int current_ = 0; | ||
| 303 | }; | ||
| 304 | |||
| 305 | struct RawVerbsNodeMeta { | ||
| 306 | std::string deployment_id; | ||
| 307 | std::string configuration_digest; | ||
| 308 | std::string fabric_digest; | ||
| 309 | std::uint16_t node_id = 0; | ||
| 310 | std::int32_t logical_id = -1; | ||
| 311 | std::uint16_t lid = 0; | ||
| 312 | std::uint32_t qpn = 0; | ||
| 313 | std::uint32_t psn = 3185; | ||
| 314 | std::uint32_t rkey = 0; | ||
| 315 | std::uint64_t base_addr = 0; | ||
| 316 | std::uint8_t gid[16] = {}; | ||
| 317 | std::uint32_t protocol_version = recstore::kRdmaDeploymentProtocolVersion; | ||
| 318 | std::uint64_t deployment_epoch = 0; | ||
| 319 | std::uint8_t node_role = 0; | ||
| 320 | std::uint8_t port_num = 0; | ||
| 321 | std::uint8_t link_layer = 0; | ||
| 322 | std::int32_t gid_index = -1; | ||
| 323 | std::uint8_t active_mtu = 0; | ||
| 324 | std::uint8_t fabric_mode = 0; | ||
| 325 | // This lane's rebuild generation. Bumped before the QP is republished so the | ||
| 326 | // peer can observe "this side rebuilt" and re-pair the QP. | ||
| 327 | std::uint64_t lane_generation = 0; | ||
| 328 | }; | ||
| 329 | |||
| 330 | class RawVerbsTransport { | ||
| 331 | public: | ||
| 332 | explicit RawVerbsTransport(const RawVerbsConfig& config); | ||
| 333 | ~RawVerbsTransport(); | ||
| 334 | |||
| 335 | RawVerbsTransport(const RawVerbsTransport&) = delete; | ||
| 336 | RawVerbsTransport& operator=(const RawVerbsTransport&) = delete; | ||
| 337 | |||
| 338 | void RegisterMemoryRegion(void* base, std::size_t bytes); | ||
| 339 | void* AllocateRegistered(std::size_t bytes); | ||
| 340 | std::uint64_t SaveAllocationState() const; | ||
| 341 | void RestoreAllocationState(std::uint64_t checkpoint); | ||
| 342 | GlobalAddress LocalAddress(void* ptr) const; | ||
| 343 | void* LocalPointer(GlobalAddress address) const; | ||
| 344 | |||
| 345 | void Publish(); | ||
| 346 | void Connect(); | ||
| 347 | void PublishAndConnect(); | ||
| 348 | RawVerbsNodeMeta LocalMeta() const; | ||
| 349 | |||
| 350 | void Write(const void* local, | ||
| 351 | GlobalAddress remote, | ||
| 352 | std::size_t bytes, | ||
| 353 | std::uint64_t wr_id, | ||
| 354 | bool signaled); | ||
| 355 | void WriteSg(base::ConstArray<RawVerbsSge> sges, | ||
| 356 | GlobalAddress remote, | ||
| 357 | std::uint64_t wr_id, | ||
| 358 | bool signaled); | ||
| 359 | void WriteWithImm(const void* local, | ||
| 360 | GlobalAddress remote, | ||
| 361 | std::size_t bytes, | ||
| 362 | std::uint32_t imm_data, | ||
| 363 | std::uint64_t wr_id, | ||
| 364 | bool signaled); | ||
| 365 | void Read(void* local, | ||
| 366 | GlobalAddress remote, | ||
| 367 | std::size_t bytes, | ||
| 368 | std::uint64_t wr_id, | ||
| 369 | bool signaled); | ||
| 370 | void SendDoorbell( | ||
| 371 | std::uint16_t node_id, std::uint32_t imm_data, std::uint64_t wr_id); | ||
| 372 | bool Poll(RawVerbsCompletion* completion, int timeout_ms); | ||
| 373 | // Non-blocking variant of Poll() for wait loops that must notice a broken QP | ||
| 374 | // without waiting for the next drain: a completion error is returned through | ||
| 375 | // `error` (which must not be null) instead of being thrown. | ||
| 376 | RawVerbsPollOutcome | ||
| 377 | TryPoll(RawVerbsCompletion* completion, RawVerbsCompletionError* error); | ||
| 378 | // Rebuilds the QP for one peer node after a device-layer failure, reusing the | ||
| 379 | // PD / MR / registered memory and the peer metadata captured by Connect(): | ||
| 380 | // reset -> init -> RTR -> RTS. The reset flushes every outstanding WR on the | ||
| 381 | // QP, so those completions are dropped from the CQ afterwards and the receive | ||
| 382 | // queue is re-armed because reset flushes it too. Only peer-side failures | ||
| 383 | // (retry exhaustion / flush) are worth rebuilding; callers must decide that | ||
| 384 | // from RawVerbsErrorClass. The QP is a shared resource, so a rebuild is only | ||
| 385 | // half of recovery: the peer must rebuild its side too, which the caller | ||
| 386 | // coordinates through the control plane. Must not run concurrently with | ||
| 387 | // submits or polls on the same lane, and only on a QP whose CQ carries no | ||
| 388 | // other poster, because the post-reset drain would discard that peer's | ||
| 389 | // completions (see RcShardServerTransport::RebuildClientLaneIfStale). | ||
| 390 | void RebuildQp(std::uint16_t node_id); | ||
| 391 | // Lane generation handshake. The lane generation is the local side's rebuild | ||
| 392 | // counter toward `node_id`; `AdvanceLaneGeneration` bumps it and republishes | ||
| 393 | // the metadata so the peer can observe the change. `LaneGeneration` reads | ||
| 394 | // that counter back; the peer's counter is read from the meta returned by | ||
| 395 | // RefreshPeerMeta(). | ||
| 396 | std::uint64_t AdvanceLaneGeneration(std::uint16_t node_id); | ||
| 397 | std::uint64_t LaneGeneration(std::uint16_t node_id) const; | ||
| 398 | // Re-reads `node_id`'s published metadata (GetMeta), validates it, and stores | ||
| 399 | // it for a subsequent RebuildQp(). Used to learn a peer's lane generation and | ||
| 400 | // to pick up addressing changes if the peer process restarted. `timeout_ms` | ||
| 401 | // overrides the configured control-plane deadline; callers on a data-plane | ||
| 402 | // thread pass a short bound so a sick control plane cannot stall it. | ||
| 403 | RawVerbsNodeMeta RefreshPeerMeta(std::uint16_t node_id, int timeout_ms = -1); | ||
| 404 | std::uint32_t max_inline_data(std::uint16_t node_id) const; | ||
| 405 | |||
| 406 | private: | ||
| 407 | struct Impl; | ||
| 408 | ibv_mr* FindLocalMr(const void* ptr, std::size_t bytes) const; | ||
| 409 | // Lazily opens (and caches) the one control-plane client this transport owns. | ||
| 410 | // Publish()/Connect()/RefreshPeerMeta() share it so a lane uses a single | ||
| 411 | // channel instead of a fresh connection per metadata RPC. | ||
| 412 | RdmaControlPlaneClient& ControlPlane(); | ||
| 413 | std::unique_ptr<Impl> impl_; | ||
| 414 | }; | ||
| 415 | |||
| 416 | class RawVerbsTransportAllocationScope { | ||
| 417 | public: | ||
| 418 | explicit RawVerbsTransportAllocationScope(RawVerbsTransport* transport) | ||
| 419 | : transport_(transport), | ||
| 420 | checkpoint_( | ||
| 421 | transport != nullptr ? transport->SaveAllocationState() : 0) {} | ||
| 422 | |||
| 423 | ~RawVerbsTransportAllocationScope() { | ||
| 424 | if (transport_ != nullptr) { | ||
| 425 | transport_->RestoreAllocationState(checkpoint_); | ||
| 426 | } | ||
| 427 | } | ||
| 428 | |||
| 429 | RawVerbsTransportAllocationScope(const RawVerbsTransportAllocationScope&) = | ||
| 430 | delete; | ||
| 431 | RawVerbsTransportAllocationScope& | ||
| 432 | operator=(const RawVerbsTransportAllocationScope&) = delete; | ||
| 433 | |||
| 434 | private: | ||
| 435 | RawVerbsTransport* transport_ = nullptr; | ||
| 436 | std::uint64_t checkpoint_ = 0; | ||
| 437 | }; | ||
| 438 | |||
| 439 | } // namespace petps | ||
| 440 |