github.com/hyperion-hyn/go-ethereum@v2.4.0+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 "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 req := elem.Value.(*distReq) 118 close(req.sentChn) 119 req.sentChn = nil 120 elem = elem.Next() 121 } 122 d.lock.Unlock() 123 return 124 case <-d.loopChn: 125 d.lock.Lock() 126 d.loopNextSent = false 127 loop: 128 for { 129 peer, req, wait := d.nextRequest() 130 if req != nil && wait == 0 { 131 chn := req.sentChn // save sentChn because remove sets it to nil 132 d.remove(req) 133 send := req.request(peer) 134 if send != nil { 135 peer.queueSend(send) 136 } 137 chn <- peer 138 close(chn) 139 } else { 140 if wait == 0 { 141 // no request to send and nothing to wait for; the next 142 // queued request will wake up the loop 143 break loop 144 } 145 d.loopNextSent = true // a "next" signal has been sent, do not send another one until this one has been received 146 if wait > distMaxWait { 147 // waiting times may be reduced by incoming request replies, if it is too long, recalculate it periodically 148 wait = distMaxWait 149 } 150 go func() { 151 time.Sleep(wait) 152 d.loopChn <- struct{}{} 153 }() 154 break loop 155 } 156 } 157 d.lock.Unlock() 158 } 159 } 160 } 161 162 // selectPeerItem represents a peer to be selected for a request by weightedRandomSelect 163 type selectPeerItem struct { 164 peer distPeer 165 req *distReq 166 weight int64 167 } 168 169 // Weight implements wrsItem interface 170 func (sp selectPeerItem) Weight() int64 { 171 return sp.weight 172 } 173 174 // nextRequest returns the next possible request from any peer, along with the 175 // associated peer and necessary waiting time 176 func (d *requestDistributor) nextRequest() (distPeer, *distReq, time.Duration) { 177 checkedPeers := make(map[distPeer]struct{}) 178 elem := d.reqQueue.Front() 179 var ( 180 bestPeer distPeer 181 bestReq *distReq 182 bestWait time.Duration 183 sel *weightedRandomSelect 184 ) 185 186 d.peerLock.RLock() 187 defer d.peerLock.RUnlock() 188 189 for (len(d.peers) > 0 || elem == d.reqQueue.Front()) && elem != nil { 190 req := elem.Value.(*distReq) 191 canSend := false 192 for peer := range d.peers { 193 if _, ok := checkedPeers[peer]; !ok && peer.canQueue() && req.canSend(peer) { 194 canSend = true 195 cost := req.getCost(peer) 196 wait, bufRemain := peer.waitBefore(cost) 197 if wait == 0 { 198 if sel == nil { 199 sel = newWeightedRandomSelect() 200 } 201 sel.update(selectPeerItem{peer: peer, req: req, weight: int64(bufRemain*1000000) + 1}) 202 } else { 203 if bestReq == nil || wait < bestWait { 204 bestPeer = peer 205 bestReq = req 206 bestWait = wait 207 } 208 } 209 checkedPeers[peer] = struct{}{} 210 } 211 } 212 next := elem.Next() 213 if !canSend && elem == d.reqQueue.Front() { 214 close(req.sentChn) 215 d.remove(req) 216 } 217 elem = next 218 } 219 220 if sel != nil { 221 c := sel.choose().(selectPeerItem) 222 return c.peer, c.req, 0 223 } 224 return bestPeer, bestReq, bestWait 225 } 226 227 // queue adds a request to the distribution queue, returns a channel where the 228 // receiving peer is sent once the request has been sent (request callback returned). 229 // If the request is cancelled or timed out without suitable peers, the channel is 230 // closed without sending any peer references to it. 231 func (d *requestDistributor) queue(r *distReq) chan distPeer { 232 d.lock.Lock() 233 defer d.lock.Unlock() 234 235 if r.reqOrder == 0 { 236 d.lastReqOrder++ 237 r.reqOrder = d.lastReqOrder 238 } 239 240 back := d.reqQueue.Back() 241 if back == nil || r.reqOrder > back.Value.(*distReq).reqOrder { 242 r.element = d.reqQueue.PushBack(r) 243 } else { 244 before := d.reqQueue.Front() 245 for before.Value.(*distReq).reqOrder < r.reqOrder { 246 before = before.Next() 247 } 248 r.element = d.reqQueue.InsertBefore(r, before) 249 } 250 251 if !d.loopNextSent { 252 d.loopNextSent = true 253 d.loopChn <- struct{}{} 254 } 255 256 r.sentChn = make(chan distPeer, 1) 257 return r.sentChn 258 } 259 260 // cancel removes a request from the queue if it has not been sent yet (returns 261 // false if it has been sent already). It is guaranteed that the callback functions 262 // will not be called after cancel returns. 263 func (d *requestDistributor) cancel(r *distReq) bool { 264 d.lock.Lock() 265 defer d.lock.Unlock() 266 267 if r.sentChn == nil { 268 return false 269 } 270 271 close(r.sentChn) 272 d.remove(r) 273 return true 274 } 275 276 // remove removes a request from the queue 277 func (d *requestDistributor) remove(r *distReq) { 278 r.sentChn = nil 279 if r.element != nil { 280 d.reqQueue.Remove(r.element) 281 r.element = nil 282 } 283 }