mirror of
https://github.com/deepseek-ai/3FS.git
synced 2026-11-05 00:47:47 +00:00
[GH-ISSUE #431] RDMA handshake fails with IBDeviceNotFound(2016) / "peer doesn't have active port" despite the peer's RDMA port being ACTIVE with a valid GID #246
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#246
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 @sivabreddy on GitHub (Oct 14, 2026).
Original GitHub issue: https://github.com/deepseek-ai/3FS/issues/431
Summary
During a basic raw write to a freshly mounted 3FS filesystem (via the FUSE client), every write attempt fails with an RDMA connection error raised during the storage-client-to-storage-node handshake. The storage node's own log shows no error at all in the same time window, and the storage node's own
/sys/class/infiniband/<dev>/ports/1/stateshowsACTIVEwith a valid, non-zero GID at the OS level. A controlled comparison test ruled out our owndevice_filterconfiguration as the cause.Environment
mlx5devices, exposed to pods as a Kubernetes shared RDMA device-plugin resource, attached via Multus CNImgmtd, 1meta, 2storagenodes, 1 FUSE client (single-instance test deployment of each)device_filterinstorage_main_launcher.tomlrestricted to one device (mlx5_0), the device confirmed to have a valid, routable GID on this clusterSteps to reproduce
mgmtd,meta, 2xstorage, FUSE client) with RDMA transport enabledhf3fs_fuse_maindd if=/dev/urandom of=/mnt/3fs/test/testfile bs=1M count=10Expected
The write completes. (This exact test reportedly completed successfully twice earlier, on the same cluster, under a different
device_filterdevice list.)Actual
The write hangs, then fails. FUSE client error log:
The storage node named in the error (the connection target) shows zero errors or warnings in its own log for the same time window — only normal startup INFO lines.
What we traced so far
We traced
IBDeviceNotFound(2016)toIBQueryRsp::selectDeviceinsrc/common/net/ib/IBConnect.cc. From reading the surrounding code, this appears to fire when the peer's own advertised device list comes back empty during the connection handshake.We then checked the storage node's own RDMA state directly at the OS level:
/sys/class/infiniband/mlx5_0/ports/1/state→4: ACTIVE/sys/class/infiniband/mlx5_0/ports/1/gids/*→ a valid, non-zero GID is presentSo at the OS/kernel level, the device the storage node should be advertising is active and has a valid GID — but the handshake-level code reports it has no usable device to offer.
Ruling out our own config as the cause
To check whether our own
device_filterrestriction (["mlx5_0"]only) was itself causing this, we reverted only thestoragenodes to a broader, previously-useddevice_filterlist (severalmlx5_*devices) and retried the same write. The identical failure reproduced — same error code, same message — which rules out ourdevice_filtersetting as the explanation.One more data point
With a broader
device_filterlist, this exact write test reportedly completed successfully twice in an earlier session on this same cluster. A later retest, with freshly created pods, could not reproduce that success at all — under either the broad or the restricteddevice_filterlist. We were not able to isolate why the handshake-level device advertisement disagrees with the OS-level port state, or why behavior changed between the two sessions.We're filing this in case it's a known failure class, or in case
IBQueryRsp::selectDevice/ whatever populates the peer's advertised device list is a useful starting point for your team to look at.Happy to provide more detail (fuller logs, config excerpts) on request — we've redacted internal addressing (IPs, hostnames) from this report for our own confidentiality.
@sivabreddy commented on GitHub (Oct 19, 2026):
Update: root cause found, with a small patch
The cause is in
queryRoCEv2GID()(src/common/net/ib/IBDevice.cc). It occurs when more than one RDMA pod runs on the same node and the RDMA subsystem is in netnssharedmode. With a small change (below), the full stack works on our cluster: writes, reads, cross-node chain replication and the FUSE mount. The build was commit22fca04(the currentmain) + the patch.Corrections to my original report
/var/log/3fs/*.log, which I did not read in the first session. From the code, the storage process logs the device drop at startup (failed to query GID type from sysfs, thenFailed to open IBDevice ..., skip it). I did not keep the logs from the unpatched binary.mainhas not changed since22fca04, so the build was that commit.device_filteris not the cause.What happens
mlx5_0device (RoCE, portACTIVE) through Multus, with its own network interface and its own RoCE v2 GID (the pod's IPv4 address).rdma systemshowsnetns shared.ibv_query_gid()also returns the GIDs of other pods' interfaces on the same node. Reading/sys/class/infiniband/mlx5_0/ports/1/gid_attrs/types/<index>for those entries fails withEINVAL.queryRoCEv2GID()checks the indexes in order. On the first non-zero, non-link-local GID, it reads the sysfs type. If that read fails, it returnskIBOpenPortFailed, soIBDevice::openfails and the device is skipped (skip_unusable_device = true).allow_no_usable_devices = true, sostorage_mainstill started.IBConnectService::querythen returned an empty device list, and the client gotIBDeviceNotFound(2016)"peer doesn't have active port".Summary of a probe in a second RDMA pod on the same node (a small C program with the same logic as
queryRoCEv2GID()): indexes 0 and 1 are link-local and are skipped. Index 2 is the first pod's IPv4 GID, and reading its sysfs type fails, so the device is dropped. In the first pod, the same program reaches its own RoCE v2 GID at index 3 and succeeds.Patch
On bare metal, or with netns
exclusive, every GID type is readable, so the behavior does not change. On a kernel withoutgid_attrs, the device still fails ("RoCE v2 GID not found" instead of "query GID type failed").One side effect:
queryRoCEv2GID()also runs fromIBDevice::openPort()for each new connection, so the WARN lines repeat for each connection. If you prefer, the message can be DBG, or rate-limited.With the patch, an RDMA pod that shares a node with two other RDMA pods logs:
We also had up to 5 RDMA pods on one node; that pod skipped 8 GIDs of 4 other pods.
Verified with the patch (commit
22fca04+ patch, 2 nodes)mgmtd,metaand 2 ×storageareHEARTBEAT_CONNECTED. The chain isSERVING, with the head target on node 1 and the tail target on node 2, bothUPTODATE.admin_cli write-file/read-file: 5 writes (2 × 10 MB, 3 × 2 MB). The 2 × 10 MB files read back with matching MD5. Both targets show 20 MB used.OffloadingConnector,fstier). Instance B found and loaded all 67 blocks that instance A wrote (read bytes = written bytes = 562,036,736, 2 trials).rx_write_requestsby 2,097,155 (about 4 GiB / 2,048-byte RoCE packets), while the pod's TCP interface moved 1.8 MB.rx_read_requestsby 15,076.hf3fsbackend (USRBIO client built from the same patched source): loads of 7K–53K-token prefixes took 0.30–2.07 s, compared with 0.52–6.11 s to recompute (1.7–2.6× faster). 8 concurrent 14K-token requests: 3.1 s wall time, compared with 8.6 s (24,000 of 112,128 tokens were computed because of the host-memory tier size).fstier on the FUSE mount: all blocks loaded, but serially, at about 0.6 GB/s.dd(direct I/O, 4 MiB, 1 GiB per stream) on a 2-replica chain:ib_write_bwdoes 379 Gb/s.SERVING(1/2), and reads through FUSE continued with a stall of about 20 s. The chain wasSERVINGwith both targetsUPTODATE95 s after the service was back.Observation after the storage failure (not a 3FS bug)
One SGLang instance that read through USRBIO kept failing its reads after the chain was healthy again, until the process restarted. The cause is in SGLang's USRBIO client: after
ioring.wait(min_results=n, timeout=…)returns partial results, SGLang does not reap the remaining I/Os, so the ring stays full and every laterprepare()fails with EAGAIN. A client-side fix solved it. We will report it to SGLang.Two smaller notes
allow_no_usable_devices = true, the device loss is silent at the RPC level. A client message such as "peer has no usable RDMA device" would point to the peer's startup log.admin_clistorage client must not useforce_use_tcp = truefor writes: the storage server logsupdate no RDMA socketand the write fails.You can apply the patch directly if you agree with the approach; I can also open a PR. Another option, which I did not test, is to check
gid_attrs/ndevs/<index>before using a GID. I can provide more logs.