github.com/MetalBlockchain/metalgo@v1.11.9/snow/consensus/snowman/poll/set.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package poll 5 6 import ( 7 "errors" 8 "fmt" 9 "strings" 10 "time" 11 12 "github.com/prometheus/client_golang/prometheus" 13 "go.uber.org/zap" 14 15 "github.com/MetalBlockchain/metalgo/ids" 16 "github.com/MetalBlockchain/metalgo/utils/bag" 17 "github.com/MetalBlockchain/metalgo/utils/linked" 18 "github.com/MetalBlockchain/metalgo/utils/logging" 19 "github.com/MetalBlockchain/metalgo/utils/metric" 20 ) 21 22 var ( 23 errFailedPollsMetric = errors.New("failed to register polls metric") 24 errFailedPollDurationMetrics = errors.New("failed to register poll_duration metrics") 25 ) 26 27 type pollHolder interface { 28 GetPoll() Poll 29 StartTime() time.Time 30 } 31 32 type poll struct { 33 Poll 34 start time.Time 35 } 36 37 func (p poll) GetPoll() Poll { 38 return p 39 } 40 41 func (p poll) StartTime() time.Time { 42 return p.start 43 } 44 45 type set struct { 46 log logging.Logger 47 numPolls prometheus.Gauge 48 durPolls metric.Averager 49 factory Factory 50 // maps requestID -> poll 51 polls *linked.Hashmap[uint32, pollHolder] 52 } 53 54 // NewSet returns a new empty set of polls 55 func NewSet( 56 factory Factory, 57 log logging.Logger, 58 reg prometheus.Registerer, 59 ) (Set, error) { 60 numPolls := prometheus.NewGauge(prometheus.GaugeOpts{ 61 Name: "polls", 62 Help: "Number of pending network polls", 63 }) 64 if err := reg.Register(numPolls); err != nil { 65 return nil, fmt.Errorf("%w: %w", errFailedPollsMetric, err) 66 } 67 68 durPolls, err := metric.NewAverager( 69 "poll_duration", 70 "time (in ns) this poll took to complete", 71 reg, 72 ) 73 if err != nil { 74 return nil, fmt.Errorf("%w: %w", errFailedPollDurationMetrics, err) 75 } 76 77 return &set{ 78 log: log, 79 numPolls: numPolls, 80 durPolls: durPolls, 81 factory: factory, 82 polls: linked.NewHashmap[uint32, pollHolder](), 83 }, nil 84 } 85 86 // Add to the current set of polls 87 // Returns true if the poll was registered correctly and the network sample 88 // should be made. 89 func (s *set) Add(requestID uint32, vdrs bag.Bag[ids.NodeID]) bool { 90 if _, exists := s.polls.Get(requestID); exists { 91 s.log.Debug("dropping poll", 92 zap.String("reason", "duplicated request"), 93 zap.Uint32("requestID", requestID), 94 ) 95 return false 96 } 97 98 s.log.Verbo("creating poll", 99 zap.Uint32("requestID", requestID), 100 zap.Stringer("validators", &vdrs), 101 ) 102 103 s.polls.Put(requestID, poll{ 104 Poll: s.factory.New(vdrs), // create the new poll 105 start: time.Now(), 106 }) 107 s.numPolls.Inc() // increase the metrics 108 return true 109 } 110 111 // Vote registers the connections response to a query for [id]. If there was no 112 // query, or the response has already be registered, nothing is performed. 113 func (s *set) Vote(requestID uint32, vdr ids.NodeID, vote ids.ID) []bag.Bag[ids.ID] { 114 holder, exists := s.polls.Get(requestID) 115 if !exists { 116 s.log.Verbo("dropping vote", 117 zap.String("reason", "unknown poll"), 118 zap.Stringer("validator", vdr), 119 zap.Uint32("requestID", requestID), 120 ) 121 return nil 122 } 123 124 p := holder.GetPoll() 125 126 s.log.Verbo("processing vote", 127 zap.Stringer("validator", vdr), 128 zap.Uint32("requestID", requestID), 129 zap.Stringer("vote", vote), 130 ) 131 132 p.Vote(vdr, vote) 133 if !p.Finished() { 134 return nil 135 } 136 137 return s.processFinishedPolls() 138 } 139 140 // processFinishedPolls checks for other dependent finished polls and returns them all if finished 141 func (s *set) processFinishedPolls() []bag.Bag[ids.ID] { 142 var results []bag.Bag[ids.ID] 143 144 // iterate from oldest to newest 145 iter := s.polls.NewIterator() 146 for iter.Next() { 147 holder := iter.Value() 148 p := holder.GetPoll() 149 if !p.Finished() { 150 // since we're iterating from oldest to newest, if the next poll has not finished, 151 // we can break and return what we have so far 152 break 153 } 154 155 s.log.Verbo("poll finished", 156 zap.Uint32("requestID", iter.Key()), 157 zap.Stringer("poll", holder.GetPoll()), 158 ) 159 s.durPolls.Observe(float64(time.Since(holder.StartTime()))) 160 s.numPolls.Dec() // decrease the metrics 161 162 results = append(results, p.Result()) 163 s.polls.Delete(iter.Key()) 164 } 165 166 // only gets here if the poll has finished 167 // results will have values if this and other newer polls have finished 168 return results 169 } 170 171 // Drop registers the connections response to a query for [id]. If there was no 172 // query, or the response has already be registered, nothing is performed. 173 func (s *set) Drop(requestID uint32, vdr ids.NodeID) []bag.Bag[ids.ID] { 174 holder, exists := s.polls.Get(requestID) 175 if !exists { 176 s.log.Verbo("dropping vote", 177 zap.String("reason", "unknown poll"), 178 zap.Stringer("validator", vdr), 179 zap.Uint32("requestID", requestID), 180 ) 181 return nil 182 } 183 184 s.log.Verbo("processing dropped vote", 185 zap.Stringer("validator", vdr), 186 zap.Uint32("requestID", requestID), 187 ) 188 189 poll := holder.GetPoll() 190 191 poll.Drop(vdr) 192 if !poll.Finished() { 193 return nil 194 } 195 196 return s.processFinishedPolls() 197 } 198 199 // Len returns the number of outstanding polls 200 func (s *set) Len() int { 201 return s.polls.Len() 202 } 203 204 func (s *set) String() string { 205 sb := strings.Builder{} 206 sb.WriteString(fmt.Sprintf("current polls: (Size = %d)", s.polls.Len())) 207 iter := s.polls.NewIterator() 208 for iter.Next() { 209 requestID := iter.Key() 210 poll := iter.Value().(Poll) 211 sb.WriteString(fmt.Sprintf("\n RequestID %d:\n %s", requestID, poll.PrefixedString(" "))) 212 } 213 return sb.String() 214 }