fix: StartReplicaReceiver idempotency guard — skip if already running

P0 bug on real hardware: assignments are re-delivered every heartbeat
cycle (5s). First setupReplicaReceiver succeeds (receiver starts on
deterministic port). Second call fails with "bind: address already in
use" because the listener is already bound. The volume stays permanently
degraded, blocking all RF=2 sync_all replication.

Fix: skip StartReplicaReceiver if v.replRecv is already set. The
receiver only needs to start once per volume lifetime.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
pingqiu
2026-04-03 13:18:30 -07:00
co-authored by Claude Opus 4.6
parent 3da4c19046
commit 7012383c3f
+7
View File
@@ -1304,6 +1304,13 @@ func (v *BlockVol) ReplicaReceiverAddr() *ReplicaReceiverAddrInfo {
// advertisedHost:port instead of relying on outbound-IP fallback. On multi-NIC
// hosts, always provide advertisedHost to ensure cross-machine reachability.
func (v *BlockVol) StartReplicaReceiver(dataAddr, ctrlAddr string, advertisedHost ...string) error {
// Idempotency: skip if receiver is already running on this volume.
// Assignments are re-delivered on every heartbeat cycle; the receiver
// only needs to start once. Without this guard, the second Listen()
// fails with "bind: address already in use" and the volume stays degraded.
if v.replRecv != nil {
return nil
}
recv, err := NewReplicaReceiver(v, dataAddr, ctrlAddr, advertisedHost...)
if err != nil {
return err