Race condition: inbound connections never call disconnect(), can wedge reconnects and gossip delivery
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 3/5
- Thời gian dự kiến
- 1-2 ngày
- Mức phù hợp với người mới
- 74/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- go
- Lĩnh vực
- networking
Hướng nghiên cứu
Bắt đầu tại network/transport/grpc/connection_manager.go ở handleInboundStream và so sánh quá trình teardown của nó với connect và openOutboundStreams. Truy vết connection.disconnect() trong connection.go và việc matching kết nối inbound trong connection_list.go, sau đó kiểm tra v2 gossip lookup trong network/transport/v2/protocol.go. Hoàn tất khi teardown inbound xóa các stream, outbox và danh tính peer trước khi loại bỏ, để một lần reconnect nhanh không bị từ chối hoặc bị che khuất bởi trạng thái cũ.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Observed symptom
Production logs show a peer repeatedly failing to receive gossip messages for extended periods, without any accompanying reconnect/disconnect log entries for that peer in between:
time="2026-09-03T07:58:26Z" level=info msg="Peer closed connection" module=Network peerAddr=46.101.96.139 peerAuthenticated=false peerDID= peerID=237fa970-0832-4540-869a-b6adf57b341f protocolVersion=2
time="2026-09-03T08:00:05Z" level=error msg="failed to send Gossip message" error="no connection available" module=Network peerID=b373e316-0884-4dc4-8760-1b6234956b17
time="2026-09-03T08:05:25Z" level=error msg="failed to send Gossip message" error="no connection available" module=Network peerID=b373e316-0884-4dc4-8760-1b6234956b17
(Different peerIDs in these three lines — the Peer closed connection line is not necessarily the direct cause of the gossip failures, but the pattern of a peer being stuck in a state where ConnectionList.Get(ByConnected(), ByPeer(...)) keeps returning nothing, for minutes at a time, points at a connection bookkeeping bug rather than a simple "peer is offline" situation.)
Root cause: asymmetry between inbound and outbound teardown
network/transport/grpc/connection_manager.go:
-
Outbound (
connect, line ~291-303) always tears the connection down fully on exit:defer func() { connection.disconnect() s.connections.remove(connection) }()and
openOutboundStreams(line ~446-451) additionally callsconnection.disconnect()as soon as any protocol stream closes. -
Inbound (
handleInboundStream, line ~582-644) only removes the connection from the list, it never callsconnection.disconnect():s.notifyObservers(peer, protocol, transport.StateConnected) connection.waitUntilDisconnected() s.notifyObservers(peer, protocol, transport.StateDisconnected) s.connections.remove(connection) return nil
Connection.disconnect() (network/transport/grpc/connection.go:122-143) is what clears mc.streams/mc.outboxes and resets the peer's ID/NodeDID/Authenticated fields. Skipping it for inbound connections leaves the conn object in a stale, half-torn-down state:
IsConnected()(len(mc.streams) > 0) keeps returningtruefor the dead connection, becausemc.streamsis never cleared.- The peer's
ID/NodeDIDare not reset, so the dead connection still matches onByPeerID/ByNodeDID.
Race window
getOrRegister for inbound connections (connection_list.go:101-106) matches purely on ByPeerID(peer.ID), ByNodeDID(peer.NodeDID) (no address component). Between the moment a stream is detected as closed (mc.cancelCtx() in connection.go:startReceiving/registerStream) and the moment s.connections.remove(connection) actually runs in handleInboundStream, the dead connection is still present in connectionList.list and still reports IsConnected() == true with its old peer identity intact.
If the same peer reconnects inbound inside that window:
getOrRegisterfinds the stale, not-yet-removed connection and reuses it (created == false).registerStream(connection.go:193-215) checksmc.streams[methodName] != nil— since the old (dead) stream was never cleared, this is still true, so the new stream registration is rejected andhandleInboundStreamreturnsErrAlreadyConnected, even though the "existing" connection is actually dead.- Independently, because
mc.outboxesfor the dead connection is also never closed, anything still trying toSend()on that staleConnection(e.g. gossip) writes into a channel that nobody is draining anymore (thestartSendinggoroutine already exited whenmc.ctxwas cancelled) — messages are silently dropped instead of surfacingErrNoConnection.
Both effects are consistent with a peer appearing intermittently unreachable for gossip (network/transport/v2/protocol.go:238-258, via connectionList.Get(grpc.ByConnected(), grpc.ByPeer(...))) well after the underlying TCP/gRPC connection is actually gone, without a clean, timely reconnect.
Suggested fix
Make handleInboundStream symmetric with the outbound path: call connection.disconnect() before (or as part of) s.connections.remove(connection), e.g.:
connection.waitUntilDisconnected()
s.notifyObservers(peer, protocol, transport.StateDisconnected)
connection.disconnect()
s.connections.remove(connection)
This ensures the connection's streams/outboxes are cleared and its peer identity is reset as soon as it's torn down, closing the race window where a stale, "connected"-looking Connection can shadow the peer during a fast reconnect.
Environment
- Observed on a V6.2-based deployment (network module, protocol v2, gRPC transport).
Assisted by AI
- Ngôn ngữ chính
- Go
- Star
- 29
- Fork
- 24
- Merge trung bình
- 2 ngày 18 giờ
- Pull request đã merge (30 ngày)
- 45
Chuẩn bị môi trường
- Có Dockerfile hoặc tệp Docker Compose
- Không có mẫu pull request
- Không có hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của nuts-foundation/nuts-node
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
nuts-foundation/nuts-node#4450 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug discuss
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 88/100
nuts-foundation/nuts-node#4436 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
auth bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
nuts-foundation/nuts-node#4435 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
nuts-foundation/nuts-node#4321 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 88/100
nuts-foundation/nuts-node#4230 ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của nuts-foundation/nuts-node
Issue tương tự
-
Project submission:Đang mở
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 72/100
slavakurilyak/awesome-ai-agents#845 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
Maintainer thường phản hồi trong vòng 3 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 74/100
JakeChampion/lang#13132 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
[BUG] deploy.restart_policy without condition drops the restart policyCó thể đã có người làm @akshat-lakhera đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
docker/compose#14383 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày