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