github.com/luckypickle/go-ethereum-vet@v1.14.2/les/distributor.go (about) 1 // Copyright 2017 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 // Package light implements on-demand retrieval capable state and chain objects 18 // for the Ethereum Light Client. 19 package les 20 21 import ( 22 "container/list" 23 "sync" 24 "time" 25 ) 26 27 // requestDistributor implements a mechanism that distributes requests to 28 // suitable peers, obeying flow control rules and prioritizing them in creation 29 // order (even when a resend is necessary). 30 type requestDistributor struct { 31 reqQueue *list.List 32 lastReqOrder uint64 33 peers map[distPeer]struct{} 34 peerLock sync.RWMutex 35 stopChn, loopChn chan struct{} 36 loopNextSent bool 37 lock sync.Mutex 38 } 39 40 // distPeer is an LES server peer interface for the request distributor. 41 // waitBefore returns either the necessary waiting time before sending a request 42 // with the given upper estimated cost or the estimated remaining relative buffer 43 // value after sending such a request (in which case the request can be sent 44 // immediately). At least one of these values is always zero. 45 type distPeer interface { 46 waitBefore(uint64) (time.Duration, float64) 47 canQueue() bool 48 queueSend(f func()) 49 } 50 51 // distReq is the request abstraction used by the distributor. It is based on 52 // three callback functions: 53 // - getCost returns the upper estimate of the cost of sending the request to a given peer 54 // - canSend tells if the server peer is suitable to serve the request 55 // - request prepares sending the request to the given peer and returns a function that 56 // does the actual sending. Request order should be preserved but the callback itself should not 57 // block until it is sent because other peers might still be able to receive requests while 58 // one of them is blocking. Instead, the returned function is put in the peer's send queue. 59 type distReq struct { 60 getCost func(distPeer) uint64 61 canSend func(distPeer) bool 62 request func(distPeer) func() 63 64 reqOrder uint64 65 sentChn chan distPeer 66 element *list.Element 67 } 68 69 // newRequestDistributor creates a new request distributor 70 func newRequestDistributor(peers *peerSet, stopChn chan struct{}) *requestDistributor { 71 d := &requestDistributor{ 72 reqQueue: list.New(), 73 loopChn: make(chan struct{}, 2), 74 stopChn: stopChn, 75 peers: make(map[distPeer]struct{}), 76 } 77 if peers != nil { 78 peers.notify(d) 79 } 80 go d.loop() 81 return d 82 } 83 84 // registerPeer implements peerSetNotify 85 func (d *requestDistributor) registerPeer(p *peer) { 86 d.peerLock.Lock() 87 d.peers[p] = struct{}{} 88 d.peerLock.Unlock() 89 } 90 91 // unregisterPeer implements peerSetNotify 92 func (d *requestDistributor) unregisterPeer(p *peer) { 93 d.peerLock.Lock() 94 delete(d.peers, p) 95 d.peerLock.Unlock() 96 } 97 98 // registerTestPeer adds a new test peer 99 func (d *requestDistributor) registerTestPeer(p distPeer) { 100 d.peerLock.Lock() 101 d.peers[p] = struct{}{} 102 d.peerLock.Unlock() 103 } 104 105 // distMaxWait is the maximum waiting time after which further necessary waiting 106 // times are recalculated based on new feedback from the servers 107 const distMaxWait = time.Millisecond * 10 108 109 // main event loop 110 func (d *requestDistributor) loop() { 111 for { 112 select { 113 case <-d.stopChn: 114 d.lock.Lock() 115 elem := d.reqQueue.Front() 116 for elem != nil { 117 close(elem.Value.(*distReq).sentChn) 118 elem = elem.Next() 119 } 120 d.lock.Unlock() 121 return 122 case <-d.loopChn: 123 d.lock.Lock() 124 d.loopNextSent = false 125 loop: 126 for { 127 peer, req, wait := d.nextRequest() 128 if req != nil && wait == 0 { 129 chn := req.sentChn // save sentChn because remove sets it to nil 130 d.remove(req) 131 send := req.request(peer) 132 if send != nil { 133 peer.queueSend(send) 134 } 135 chn <- peer 136 close(chn) 137 } else { 138 if wait == 0 { 139 // no request to send and nothing to wait for; the next 140 // queued request will wake up the loop 141 break loop 142 } 143 d.loopNextSent = true // a "next" signal has been sent, do not send another one until this one has been received 144 if wait > distMaxWait { 145 // waiting times may be reduced by incoming request replies, if it is too long, recalculate it periodically 146 wait = distMaxWait 147 } 148 go func() { 149 time.Sleep(wait) 150 d.loopChn <- struct{}{} 151 }() 152 break loop 153 } 154 } 155 d.lock.Unlock() 156 } 157 } 158 } 159 160 // selectPeerItem represents a peer to be selected for a request by weightedRandomSelect 161 type selectPeerItem struct { 162 peer distPeer 163 req *distReq 164 weight int64 165 } 166 167 // Weight implements wrsItem interface 168 func (sp selectPeerItem) Weight() int64 { 169 return sp.weight 170 } 171 172 // nextRequest returns the next possible request from any peer, along with the 173 // associated peer and necessary waiting time 174 func (d *requestDistributor) nextRequest() (distPeer, *distReq, time.Duration) { 175 checkedPeers := make(map[distPeer]struct{}) 176 elem := d.reqQueue.Front() 177 var ( 178 bestPeer distPeer 179 bestReq *distReq 180 bestWait time.Duration 181 sel *weightedRandomSelect 182 ) 183 184 d.peerLock.RLock() 185 defer d.peerLock.RUnlock() 186 187 for (len(d.peers) > 0 || elem == d.reqQueue.Front()) && elem != nil { 188 req := elem.Value.(*distReq) 189 canSend := false 190 for peer := range d.peers { 191 if _, ok := checkedPeers[peer]; !ok && peer.canQueue() && req.canSend(peer) { 192 canSend = true 193 cost := req.getCost(peer) 194 wait, bufRemain := peer.waitBefore(cost) 195 if wait == 0 { 196 if sel == nil { 197 sel = newWeightedRandomSelect() 198 } 199 sel.update(selectPeerItem{peer: peer, req: req, weight: int64(bufRemain*1000000) + 1}) 200 } else { 201 if bestReq == nil || wait < bestWait { 202 bestPeer = peer 203 bestReq = req 204 bestWait = wait 205 } 206 } 207 checkedPeers[peer] = struct{}{} 208 } 209 } 210 next := elem.Next() 211 if !canSend && elem == d.reqQueue.Front() { 212 close(req.sentChn) 213 d.remove(req) 214 } 215 elem = next 216 } 217 218 if sel != nil { 219 c := sel.choose().(selectPeerItem) 220 return c.peer, c.req, 0 221 } 222 return bestPeer, bestReq, bestWait 223 } 224 225 // queue adds a request to the distribution queue, returns a channel where the 226 // receiving peer is sent once the request has been sent (request callback returned). 227 // If the request is cancelled or timed out without suitable peers, the channel is 228 // closed without sending any peer references to it. 229 func (d *requestDistributor) queue(r *distReq) chan distPeer { 230 d.lock.Lock() 231 defer d.lock.Unlock() 232 233 if r.reqOrder == 0 { 234 d.lastReqOrder++ 235 r.reqOrder = d.lastReqOrder 236 } 237 238 back := d.reqQueue.Back() 239 if back == nil || r.reqOrder > back.Value.(*distReq).reqOrder { 240 r.element = d.reqQueue.PushBack(r) 241 } else { 242 before := d.reqQueue.Front() 243 for before.Value.(*distReq).reqOrder < r.reqOrder { 244 before = before.Next() 245 } 246 r.element = d.reqQueue.InsertBefore(r, before) 247 } 248 249 if !d.loopNextSent { 250 d.loopNextSent = true 251 d.loopChn <- struct{}{} 252 } 253 254 r.sentChn = make(chan distPeer, 1) 255 return r.sentChn 256 } 257 258 // cancel removes a request from the queue if it has not been sent yet (returns 259 // false if it has been sent already). It is guaranteed that the callback functions 260 // will not be called after cancel returns. 261 func (d *requestDistributor) cancel(r *distReq) bool { 262 d.lock.Lock() 263 defer d.lock.Unlock() 264 265 if r.sentChn == nil { 266 return false 267 } 268 269 close(r.sentChn) 270 d.remove(r) 271 return true 272 } 273 274 // remove removes a request from the queue 275 func (d *requestDistributor) remove(r *distReq) { 276 r.sentChn = nil 277 if r.element != nil { 278 d.reqQueue.Remove(r.element) 279 r.element = nil 280 } 281 }