mirror of
https://github.com/deepseek-ai/3FS.git
synced 2026-11-05 00:47:47 +00:00
[GH-ISSUE #426] admin_cli destroys storage RDMA QPs without graceful close, leaving server-side orphan QPs that later fail WRType::CHECK #244
Labels
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
deepseek-ai/3FS#244
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @liaoyunkun on GitHub (Aug 27, 2026).
Original GitHub issue: https://github.com/deepseek-ai/3FS/issues/426
Title
admin_clidestroys storage RDMA QPs without graceful close, leaving server-side orphan QPs that later failWRType::CHECKBody
Summary
We observed that a short-lived
admin_clicommand which creates aStorageClientcan destroy its storage-facing RC QPs without sending the 3FSgraceful-close message. The storage-side acceptor QPs remain
READY/RTSandare only detected as dead much later, when the periodic zero-byte
WRType::CHECKRDMA Write exhausts the RC retry budget.We reproduced the complete lifecycle for one QP and found that the cause is the
shutdown order in
admin_cli:IBManager::stop()runs beforeStorageClientdestruction. Consequently,
Transport::~Transport()callsIBManager::close()aftersocketManager_has already been reset, andIBManager::closeImpl()silently lets theIBSocketdestruct without callingcloseGracefully().The relevant code is unchanged on current
mainat22fca04564c7cc230fd8b9523b8b92864e1dad47.Environment
maincommit22fca04564c7cc230fd8b9523b8b92864e1dad47for the files involved below.admin_cli create-targetinvocation that accesses analready existing target. No fio or storage data workload is required for the
isolated reproduction.
change close, CHECK, QP or retry behavior.
Production observation
One deployment command connected to storage1 through storage5 in sequence and
then exited. Approximately 1783.5 seconds after each storage-side QP became
ready, all five QPs reported the same first completion error within a 0.47 ms
window:
admin_clipeer QPNThe first completion on every QP had these fields:
These QPs were established by
admin_clibefore the data workload started;they were not application data QPs.
Isolated one-QP reproduction
We then ran one idempotent
create-targetcommand against an existing target,with no fio process running, and recorded both QP endpoints and process
lifecycle:
Measured durations:
As a control, the same
admin_cliprocess gracefully closed its mgmtd QP andlogged the local close post before QP destruction. Only the storage-facing QP
was destroyed with
closed=false.Source analysis
1.
IBManageris stopped beforestorageClientis destructedstorageClientis declared before theIBManager::stop()scope guard:admin_cli.cclines 95-111C++ local objects are destroyed in reverse declaration order, so the scope
guard stops
IBManagerbeforestorageClientis destructed.The normal exit path explicitly stops
mgmtdClientand the generic networkclient, but does not stop or reset
storageClient,metaClientorcoreClient:admin_cli.cclines 229-2372. The late
StorageClientdestructor drops transports after the manager is goneStorageClientImpl::~StorageClientImpl()callsstop(), which stops itsmessengers and drops their transports:
StorageClientImpl.cclines 1410 and 1467-1492An RDMA
Transportdestructor passes its socket toIBManager::close():Transport.cclines 59-643.
closeImpl()does not close the socket if the manager has stoppedIBManager::reset()first stops and resetssocketManager_. Later,closeImpl()only hands the socket to the manager whensocketManager_isnon-null:
IBDevice.cclines 822-842When
socketManager_ == nullptr, the function parameter is simply destructed.The drainer, which is responsible for calling
closeGracefully(), is bypassed.4. The peer is left waiting for the application-level close notification
The normal 3FS close path posts a zero-byte
IBV_WR_RDMA_WRITE_WITH_IMMcarryingImmData::close(). Direct local QPdestruction does not send that application-level notification.
The storage acceptor therefore still considers the socket
READY. Itsperiodic liveness CHECK is a zero-byte, signaled
IBV_WR_RDMA_WRITE; after thepeer QP no longer exists, that CHECK eventually completes with
IBV_WC_RETRY_EXC_ERR.Expected behavior
Before
IBManager::stop():QP in bounded time.
Actual behavior
The initiator QP is destroyed with
state=READYandclosed=false. The peeracceptor QP remains
RTSuntil a later CHECK consumes the full RC retry budgetand reports
transport retry counter exceeded.Impact
storage service.
data workload or RDMA fabric.
minutes before being reclaimed.
errors and consume QP resources.
Suggested fix
At minimum, explicitly stop and release all clients that may own RDMA
transports before stopping the generic client and
IBManager, for example:It may also be useful for
IBManager::closeImpl()to log or reject aREADY/not-closed socket whensocketManager_ == nullptr, rather than silentlydestroying it.
A regression test could run a storage-using
admin_clicommand and assert that:WRType::CHECK / IBV_WC_RETRY_EXC_ERRappears after the originalretry horizon.
Possibly related issue
Issue #192 reports a different symptom (a crash while closing an IBSocket on an
E810 device) and includes
admin_cli/Transport::~Transport()in the stack.This report is not a duplicate: the symptom here is a non-crashing, silent
bypass of graceful close followed by delayed server-side CHECK retry exhaustion.
@PerryLink commented on GitHub (Sep 13, 2026):
Static check on main supports the mechanism. In src/client/bin/admin_cli.cc the IBManager::stop() guard is registered at :111 (SCOPE_EXIT) while storageClient is assigned later at :173, so at function exit the guard runs FIRST - stop() executes before StorageClient destruction, matching your ordering claim. Then the Transport destructor path calls IBManager::close() with the manager already stopped, and IBSocket::~IBSocket itself only modifies the QP to ERR (ibv_modify_qp) - no closeGracefully()/graceful-close message - which matches the orphaned server-side QPs you observed. The ~1783.5s CHECK-exhaustion window is your production observation【未核实】(we did not run an RDMA setup), and the exact closeImpl silent-destruct sequence we did not re-verify line-by-line【未核实】. A fix direction: run the stop guard after client teardown (or close the storage client explicitly before IBManager::stop), plus a graceful-close path in IBSocket teardown.