[RDMA] Send/recv buffers share one registered memory pool → silent stall risk at large scale
Author: KanPlusCreated Aug 9, 2026Updated Aug 9, 2026
Summary
In bRPC RDMA, all connections' send-serialization buffers and receive pre-posted buffers come from a single process-wide registered memory pool (AllocBlock, block_pool.cpp:426), pinned via ibv_reg_mr. Unlike TCP (independent, pageable, kernel-managed per-socket buffers), this creates a structural send/recv coupling that can cause a silent, whole-pool stall under large-scale, mixed-load, under-provisioned deployments.
Root cause (with code pointers)
- Global IOBuf allocator is replaced by the registered pool:
rdma_helper.cpp:578. - Send buffer is released only after the peer ACKs (IMM):
rdma_endpoint.cpp:965. The peer can only receive if it has already posted a recv buffer — which also comes from the same pool. - On pool exhaustion,
PostRecv(1)fails (rdma_endpoint.cpp:983,AllocBlockFromreturns NULL atblock_pool.cpp:374). - Cycle: pool exhausted → recv re-post fails → no one can receive → in-flight sends never complete → ACK never returns → send buffers (same pool) never freed → pool never recovers → all connections sharing the pool stall.
- The per-connection credit window (
_window_size) is congestion control only and is independent ofAllocBlock; it provides no memory isolation.
When it happens (scenarios)
Three conditions together:
- Mixed load — large messages (64KB/2MB blocks) and small RPCs (8KB blocks) share one pool.
- High fan-out — many concurrent connections under load.
- Under-provisioned pool — especially since, by default
buckets=4, the 8KB pool (block_type 0, carrying all normal RPC send/recv buffers) cannot grow at runtime (CanExtendBlockRuntime,block_pool.cpp:139-141requiresbuckets==1orregion_num<1;InitBlockPoolalready setregion_num[0]=1). It is locked at the initial 1GB.
Typical deployments at risk: gateways/proxies mixing big transfers and small control RPCs, multi-tenant access layers, long-lived large-message streams.
Impact
- Whole-pool stall, not single-request failure. All RDMA connections sharing the pool hang simultaneously (availability-level).
- Silent and hard to diagnose. Only occasional
Fail to extend region/Fail to allocate rbufin logs — no "shared-pool deadlock" signal. Easy to misattribute to network/kernel (#3202 showedibv_post_send: Cannot allocate memory, rooted in kernel SQ memory, while the block pool had already triggered extend). - No per-connection fairness/isolation.
AllocBlockFromis LIFO + per-thread TLS hoarding + random bucket, biasing toward recently active (large-flow) threads.
Questions for the community
- Is the send/recv shared-pool coupling at scale considered expected behavior, or a real reliability risk?
- Should docs state explicit RDMA applicable / not-applicable boundaries for large-scale mixed load?
- Should
CanExtendBlockRuntime'sbuckets==1restriction be relaxed forblock_type 0so the 8KB pool can grow under default config? - Should a per-direction (at least receive-side) reserved/guaranteed memory mechanism be introduced to avoid recv refill being starved by sends?
- Should docs (
max_regionsdefault 16 vs code 3;bucketsrestriction) be aligned with code?
Static source analysis only; recommend a repro with mixed large/small flows + high fan-out + gradually shrinking pool.
Source: apache/brpc