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