diff --git a/server/etcdserver/raft.go b/server/etcdserver/raft.go index 3db8a57c3b6..704e45a7aac 100644 --- a/server/etcdserver/raft.go +++ b/server/etcdserver/raft.go @@ -377,7 +377,14 @@ func (r *raftNode) apply() chan toApply { } func (r *raftNode) stop() { - r.stopped <- struct{}{} + select { + case r.stopped <- struct{}{}: + // Not already stopped, so trigger it + case <-r.done: + // Has already been stopped - no need to do anything + return + } + // Block until the stop has been acknowledged by start() <-r.done } diff --git a/server/etcdserver/raft_test.go b/server/etcdserver/raft_test.go index 6644d557c73..d4a9cf40a9a 100644 --- a/server/etcdserver/raft_test.go +++ b/server/etcdserver/raft_test.go @@ -285,3 +285,29 @@ func TestExpvarWithNoRaftStatus(t *testing.T) { _ = kv.Value.String() }) } + +func TestStopRaftNodeMoreThanOnce(t *testing.T) { + n := newNopReadyNode() + r := newRaftNode(raftNodeConfig{ + lg: zaptest.NewLogger(t), + Node: n, + storage: mockstorage.NewStorageRecorder(""), + raftStorage: raft.NewMemoryStorage(), + transport: newNopTransporter(), + }) + r.start(&raftReadyHandler{}) + + for i := 0; i < 2; i++ { + stopped := make(chan struct{}) + go func() { + r.stop() + close(stopped) + }() + + select { + case <-stopped: + case <-time.After(time.Second): + t.Errorf("*raftNode.stop() is blocked !") + } + } +}