github.com/core-coin/go-core/v2@v2.1.9/les/distributor.go (about) 1 // Copyright 2017 by the Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core 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-core 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-core 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/core-coin/go-core/v2/common/mclock" 25 "github.com/core-coin/go-core/v2/les/utils" 26 ) 27 28 // requestDistributor implements a mechanism that distributes requests to 29 // suitable peers, obeying flow control rules and prioritizing them in creation 30 // order (even when a resend is necessary). 31 type requestDistributor struct { 32 clock mclock.Clock 33 reqQueue *list.List 34 lastReqOrder uint64 35 peers map[distPeer]struct{} 36 peerLock sync.RWMutex 37 loopChn chan struct{} 38 loopNextSent bool 39 lock sync.Mutex 40 41 closeCh chan struct{} 42 wg sync.WaitGroup 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()) bool 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() 68 69 reqOrder uint64 70 sentChn chan distPeer 71 element *list.Element 72 waitForPeers mclock.AbsTime 73 enterQueue mclock.AbsTime 74 } 75 76 // newRequestDistributor creates a new request distributor 77 func newRequestDistributor(peers *serverPeerSet, clock mclock.Clock) *requestDistributor { 78 d := &requestDistributor{ 79 clock: clock, 80 reqQueue: list.New(), 81 loopChn: make(chan struct{}, 2), 82 closeCh: make(chan struct{}), 83 peers: make(map[distPeer]struct{}), 84 } 85 if peers != nil { 86 peers.subscribe(d) 87 } 88 d.wg.Add(1) 89 go d.loop() 90 return d 91 } 92 93 // registerPeer implements peerSetNotify 94 func (d *requestDistributor) registerPeer(p *serverPeer) { 95 d.peerLock.Lock() 96 d.peers[p] = struct{}{} 97 d.peerLock.Unlock() 98 } 99 100 // unregisterPeer implements peerSetNotify 101 func (d *requestDistributor) unregisterPeer(p *serverPeer) { 102 d.peerLock.Lock() 103 delete(d.peers, p) 104 d.peerLock.Unlock() 105 } 106 107 // registerTestPeer adds a new test peer 108 func (d *requestDistributor) registerTestPeer(p distPeer) { 109 d.peerLock.Lock() 110 d.peers[p] = struct{}{} 111 d.peerLock.Unlock() 112 } 113 114 var ( 115 // distMaxWait is the maximum waiting time after which further necessary waiting 116 // times are recalculated based on new feedback from the servers 117 distMaxWait = time.Millisecond * 50 118 119 // waitForPeers is the time window in which a request does not fail even if it 120 // has no suitable peers to send to at the moment 121 waitForPeers = time.Second * 3 122 ) 123 124 // main event loop 125 func (d *requestDistributor) loop() { 126 defer d.wg.Done() 127 for { 128 select { 129 case <-d.closeCh: 130 d.lock.Lock() 131 elem := d.reqQueue.Front() 132 for elem != nil { 133 req := elem.Value.(*distReq) 134 close(req.sentChn) 135 req.sentChn = nil 136 elem = elem.Next() 137 } 138 d.lock.Unlock() 139 return 140 case <-d.loopChn: 141 d.lock.Lock() 142 d.loopNextSent = false 143 loop: 144 for { 145 peer, req, wait := d.nextRequest() 146 if req != nil && wait == 0 { 147 chn := req.sentChn // save sentChn because remove sets it to nil 148 d.remove(req) 149 send := req.request(peer) 150 if send != nil { 151 peer.queueSend(send) 152 requestSendDelay.Update(time.Duration(d.clock.Now() - req.enterQueue)) 153 } 154 chn <- peer 155 close(chn) 156 } else { 157 if wait == 0 { 158 // no request to send and nothing to wait for; the next 159 // queued request will wake up the loop 160 break loop 161 } 162 d.loopNextSent = true // a "next" signal has been sent, do not send another one until this one has been received 163 if wait > distMaxWait { 164 // waiting times may be reduced by incoming request replies, if it is too long, recalculate it periodically 165 wait = distMaxWait 166 } 167 go func() { 168 d.clock.Sleep(wait) 169 d.loopChn <- struct{}{} 170 }() 171 break loop 172 } 173 } 174 d.lock.Unlock() 175 } 176 } 177 } 178 179 // selectPeerItem represents a peer to be selected for a request by weightedRandomSelect 180 type selectPeerItem struct { 181 peer distPeer 182 req *distReq 183 weight uint64 184 } 185 186 func selectPeerWeight(i interface{}) uint64 { 187 return i.(selectPeerItem).weight 188 } 189 190 // nextRequest returns the next possible request from any peer, along with the 191 // associated peer and necessary waiting time 192 func (d *requestDistributor) nextRequest() (distPeer, *distReq, time.Duration) { 193 checkedPeers := make(map[distPeer]struct{}) 194 elem := d.reqQueue.Front() 195 var ( 196 bestWait time.Duration 197 sel *utils.WeightedRandomSelect 198 ) 199 200 d.peerLock.RLock() 201 defer d.peerLock.RUnlock() 202 203 peerCount := len(d.peers) 204 for (len(checkedPeers) < peerCount || elem == d.reqQueue.Front()) && elem != nil { 205 req := elem.Value.(*distReq) 206 canSend := false 207 now := d.clock.Now() 208 if req.waitForPeers > now { 209 canSend = true 210 wait := time.Duration(req.waitForPeers - now) 211 if bestWait == 0 || wait < bestWait { 212 bestWait = wait 213 } 214 } 215 for peer := range d.peers { 216 if _, ok := checkedPeers[peer]; !ok && peer.canQueue() && req.canSend(peer) { 217 canSend = true 218 cost := req.getCost(peer) 219 wait, bufRemain := peer.waitBefore(cost) 220 if wait == 0 { 221 if sel == nil { 222 sel = utils.NewWeightedRandomSelect(selectPeerWeight) 223 } 224 sel.Update(selectPeerItem{peer: peer, req: req, weight: uint64(bufRemain*1000000) + 1}) 225 } else { 226 if bestWait == 0 || wait < bestWait { 227 bestWait = wait 228 } 229 } 230 checkedPeers[peer] = struct{}{} 231 } 232 } 233 next := elem.Next() 234 if !canSend && elem == d.reqQueue.Front() { 235 close(req.sentChn) 236 d.remove(req) 237 } 238 elem = next 239 } 240 241 if sel != nil { 242 c := sel.Choose().(selectPeerItem) 243 return c.peer, c.req, 0 244 } 245 return nil, nil, bestWait 246 } 247 248 // queue adds a request to the distribution queue, returns a channel where the 249 // receiving peer is sent once the request has been sent (request callback returned). 250 // If the request is cancelled or timed out without suitable peers, the channel is 251 // closed without sending any peer references to it. 252 func (d *requestDistributor) queue(r *distReq) chan distPeer { 253 d.lock.Lock() 254 defer d.lock.Unlock() 255 256 if r.reqOrder == 0 { 257 d.lastReqOrder++ 258 r.reqOrder = d.lastReqOrder 259 r.waitForPeers = d.clock.Now() + mclock.AbsTime(waitForPeers) 260 } 261 // Assign the timestamp when the request is queued no matter it's 262 // a new one or re-queued one. 263 r.enterQueue = d.clock.Now() 264 265 back := d.reqQueue.Back() 266 if back == nil || r.reqOrder > back.Value.(*distReq).reqOrder { 267 r.element = d.reqQueue.PushBack(r) 268 } else { 269 before := d.reqQueue.Front() 270 for before.Value.(*distReq).reqOrder < r.reqOrder { 271 before = before.Next() 272 } 273 r.element = d.reqQueue.InsertBefore(r, before) 274 } 275 276 if !d.loopNextSent { 277 d.loopNextSent = true 278 d.loopChn <- struct{}{} 279 } 280 281 r.sentChn = make(chan distPeer, 1) 282 return r.sentChn 283 } 284 285 // cancel removes a request from the queue if it has not been sent yet (returns 286 // false if it has been sent already). It is guaranteed that the callback functions 287 // will not be called after cancel returns. 288 func (d *requestDistributor) cancel(r *distReq) bool { 289 d.lock.Lock() 290 defer d.lock.Unlock() 291 292 if r.sentChn == nil { 293 return false 294 } 295 296 close(r.sentChn) 297 d.remove(r) 298 return true 299 } 300 301 // remove removes a request from the queue 302 func (d *requestDistributor) remove(r *distReq) { 303 r.sentChn = nil 304 if r.element != nil { 305 d.reqQueue.Remove(r.element) 306 r.element = nil 307 } 308 } 309 310 func (d *requestDistributor) close() { 311 close(d.closeCh) 312 d.wg.Wait() 313 }