88 "net/http"
99 "net/url"
1010 "sync"
11+ "sync/atomic"
1112 "time"
1213
1314 "github.com/interuss/dss/pkg/logging"
@@ -42,6 +43,10 @@ type Consensus struct {
4243 confState raftpb.ConfState
4344 snapshotIndex uint64
4445 appliedIndex uint64
46+
47+ // failed is set once handleReady exits, meaning this node's consensus
48+ // loop has stopped running and it can no longer process or apply entries.
49+ failed atomic.Bool
4550}
4651
4752func NewConsensus (ctx context.Context , logger * zap.Logger , connectParams params.ConnectParameters , provider snapshotProvider , commitC chan <- EntryCommit ) (* Consensus , error ) {
@@ -103,7 +108,8 @@ func NewConsensus(ctx context.Context, logger *zap.Logger, connectParams params.
103108 consensus .logger .Error ("handleReady exited with error, shutting down consensus" , zap .Error (err ))
104109 }
105110
106- consensus .Stop (context .Background ())
111+ consensus .failed .Store (true )
112+ consensus .Stop (ctx )
107113 }()
108114
109115 return consensus , nil
@@ -126,6 +132,16 @@ func (c *Consensus) Stop(ctx context.Context) {
126132 })
127133}
128134
135+ // IsHealthy reports whether this node's consensus loop is still running and
136+ // the cluster currently has an elected leader. A node with no known leader
137+ // has lost quorum (or hasn't joined yet) and cannot serve requests.
138+ func (c * Consensus ) IsHealthy () bool {
139+ if c .failed .Load () {
140+ return false
141+ }
142+ return c .node .Status ().Lead != 0
143+ }
144+
129145// ProposeValue blocks until the proposal is committed and applied / dropped or until ctx is cancelled.
130146func (c * Consensus ) ProposeValue (ctx context.Context , requestType string , payload any , readOnly bool ) (any , error ) {
131147 proposal , err := newProposal (ctx , requestType , payload , readOnly )
0 commit comments