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