github.com/shyftnetwork/go-empyrean@v1.8.3-0.20191127201940-fbfca9338f04/swarm/network/fetcher.go (about) 1 // Copyright 2018 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 network 18 19 import ( 20 "context" 21 "sync" 22 "time" 23 24 "github.com/ShyftNetwork/go-empyrean/log" 25 "github.com/ShyftNetwork/go-empyrean/p2p/enode" 26 "github.com/ShyftNetwork/go-empyrean/swarm/storage" 27 ) 28 29 var searchTimeout = 1 * time.Second 30 31 // Time to consider peer to be skipped. 32 // Also used in stream delivery. 33 var RequestTimeout = 10 * time.Second 34 35 var maxHopCount uint8 = 20 // maximum number of forwarded requests (hops), to make sure requests are not forwarded forever in peer loops 36 37 type RequestFunc func(context.Context, *Request) (*enode.ID, chan struct{}, error) 38 39 // Fetcher is created when a chunk is not found locally. It starts a request handler loop once and 40 // keeps it alive until all active requests are completed. This can happen: 41 // 1. either because the chunk is delivered 42 // 2. or becuse the requestor cancelled/timed out 43 // Fetcher self destroys itself after it is completed. 44 // TODO: cancel all forward requests after termination 45 type Fetcher struct { 46 protoRequestFunc RequestFunc // request function fetcher calls to issue retrieve request for a chunk 47 addr storage.Address // the address of the chunk to be fetched 48 offerC chan *enode.ID // channel of sources (peer node id strings) 49 requestC chan uint8 // channel for incoming requests (with the hopCount value in it) 50 skipCheck bool 51 } 52 53 type Request struct { 54 Addr storage.Address // chunk address 55 Source *enode.ID // nodeID of peer to request from (can be nil) 56 SkipCheck bool // whether to offer the chunk first or deliver directly 57 peersToSkip *sync.Map // peers not to request chunk from (only makes sense if source is nil) 58 HopCount uint8 // number of forwarded requests (hops) 59 } 60 61 // NewRequest returns a new instance of Request based on chunk address skip check and 62 // a map of peers to skip. 63 func NewRequest(addr storage.Address, skipCheck bool, peersToSkip *sync.Map) *Request { 64 return &Request{ 65 Addr: addr, 66 SkipCheck: skipCheck, 67 peersToSkip: peersToSkip, 68 } 69 } 70 71 // SkipPeer returns if the peer with nodeID should not be requested to deliver a chunk. 72 // Peers to skip are kept per Request and for a time period of RequestTimeout. 73 // This function is used in stream package in Delivery.RequestFromPeers to optimize 74 // requests for chunks. 75 func (r *Request) SkipPeer(nodeID string) bool { 76 val, ok := r.peersToSkip.Load(nodeID) 77 if !ok { 78 return false 79 } 80 t, ok := val.(time.Time) 81 if ok && time.Now().After(t.Add(RequestTimeout)) { 82 // deadine expired 83 r.peersToSkip.Delete(nodeID) 84 return false 85 } 86 return true 87 } 88 89 // FetcherFactory is initialised with a request function and can create fetchers 90 type FetcherFactory struct { 91 request RequestFunc 92 skipCheck bool 93 } 94 95 // NewFetcherFactory takes a request function and skip check parameter and creates a FetcherFactory 96 func NewFetcherFactory(request RequestFunc, skipCheck bool) *FetcherFactory { 97 return &FetcherFactory{ 98 request: request, 99 skipCheck: skipCheck, 100 } 101 } 102 103 // New contructs a new Fetcher, for the given chunk. All peers in peersToSkip are not requested to 104 // deliver the given chunk. peersToSkip should always contain the peers which are actively requesting 105 // this chunk, to make sure we don't request back the chunks from them. 106 // The created Fetcher is started and returned. 107 func (f *FetcherFactory) New(ctx context.Context, source storage.Address, peersToSkip *sync.Map) storage.NetFetcher { 108 fetcher := NewFetcher(source, f.request, f.skipCheck) 109 go fetcher.run(ctx, peersToSkip) 110 return fetcher 111 } 112 113 // NewFetcher creates a new Fetcher for the given chunk address using the given request function. 114 func NewFetcher(addr storage.Address, rf RequestFunc, skipCheck bool) *Fetcher { 115 return &Fetcher{ 116 addr: addr, 117 protoRequestFunc: rf, 118 offerC: make(chan *enode.ID), 119 requestC: make(chan uint8), 120 skipCheck: skipCheck, 121 } 122 } 123 124 // Offer is called when an upstream peer offers the chunk via syncing as part of `OfferedHashesMsg` and the node does not have the chunk locally. 125 func (f *Fetcher) Offer(ctx context.Context, source *enode.ID) { 126 // First we need to have this select to make sure that we return if context is done 127 select { 128 case <-ctx.Done(): 129 return 130 default: 131 } 132 133 // This select alone would not guarantee that we return of context is done, it could potentially 134 // push to offerC instead if offerC is available (see number 2 in https://golang.org/ref/spec#Select_statements) 135 select { 136 case f.offerC <- source: 137 case <-ctx.Done(): 138 } 139 } 140 141 // Request is called when an upstream peer request the chunk as part of `RetrieveRequestMsg`, or from a local request through FileStore, and the node does not have the chunk locally. 142 func (f *Fetcher) Request(ctx context.Context, hopCount uint8) { 143 // First we need to have this select to make sure that we return if context is done 144 select { 145 case <-ctx.Done(): 146 return 147 default: 148 } 149 150 if hopCount >= maxHopCount { 151 log.Debug("fetcher request hop count limit reached", "hops", hopCount) 152 return 153 } 154 155 // This select alone would not guarantee that we return of context is done, it could potentially 156 // push to offerC instead if offerC is available (see number 2 in https://golang.org/ref/spec#Select_statements) 157 select { 158 case f.requestC <- hopCount + 1: 159 case <-ctx.Done(): 160 } 161 } 162 163 // start prepares the Fetcher 164 // it keeps the Fetcher alive within the lifecycle of the passed context 165 func (f *Fetcher) run(ctx context.Context, peers *sync.Map) { 166 var ( 167 doRequest bool // determines if retrieval is initiated in the current iteration 168 wait *time.Timer // timer for search timeout 169 waitC <-chan time.Time // timer channel 170 sources []*enode.ID // known sources, ie. peers that offered the chunk 171 requested bool // true if the chunk was actually requested 172 hopCount uint8 173 ) 174 gone := make(chan *enode.ID) // channel to signal that a peer we requested from disconnected 175 176 // loop that keeps the fetching process alive 177 // after every request a timer is set. If this goes off we request again from another peer 178 // note that the previous request is still alive and has the chance to deliver, so 179 // rerequesting extends the search. ie., 180 // if a peer we requested from is gone we issue a new request, so the number of active 181 // requests never decreases 182 for { 183 select { 184 185 // incoming offer 186 case source := <-f.offerC: 187 log.Trace("new source", "peer addr", source, "request addr", f.addr) 188 // 1) the chunk is offered by a syncing peer 189 // add to known sources 190 sources = append(sources, source) 191 // launch a request to the source iff the chunk was requested (not just expected because its offered by a syncing peer) 192 doRequest = requested 193 194 // incoming request 195 case hopCount = <-f.requestC: 196 log.Trace("new request", "request addr", f.addr) 197 // 2) chunk is requested, set requested flag 198 // launch a request iff none been launched yet 199 doRequest = !requested 200 requested = true 201 202 // peer we requested from is gone. fall back to another 203 // and remove the peer from the peers map 204 case id := <-gone: 205 log.Trace("peer gone", "peer id", id.String(), "request addr", f.addr) 206 peers.Delete(id.String()) 207 doRequest = requested 208 209 // search timeout: too much time passed since the last request, 210 // extend the search to a new peer if we can find one 211 case <-waitC: 212 log.Trace("search timed out: rerequesting", "request addr", f.addr) 213 doRequest = requested 214 215 // all Fetcher context closed, can quit 216 case <-ctx.Done(): 217 log.Trace("terminate fetcher", "request addr", f.addr) 218 // TODO: send cancelations to all peers left over in peers map (i.e., those we requested from) 219 return 220 } 221 222 // need to issue a new request 223 if doRequest { 224 var err error 225 sources, err = f.doRequest(ctx, gone, peers, sources, hopCount) 226 if err != nil { 227 log.Info("unable to request", "request addr", f.addr, "err", err) 228 } 229 } 230 231 // if wait channel is not set, set it to a timer 232 if requested { 233 if wait == nil { 234 wait = time.NewTimer(searchTimeout) 235 defer wait.Stop() 236 waitC = wait.C 237 } else { 238 // stop the timer and drain the channel if it was not drained earlier 239 if !wait.Stop() { 240 select { 241 case <-wait.C: 242 default: 243 } 244 } 245 // reset the timer to go off after searchTimeout 246 wait.Reset(searchTimeout) 247 } 248 } 249 doRequest = false 250 } 251 } 252 253 // doRequest attempts at finding a peer to request the chunk from 254 // * first it tries to request explicitly from peers that are known to have offered the chunk 255 // * if there are no such peers (available) it tries to request it from a peer closest to the chunk address 256 // excluding those in the peersToSkip map 257 // * if no such peer is found an error is returned 258 // 259 // if a request is successful, 260 // * the peer's address is added to the set of peers to skip 261 // * the peer's address is removed from prospective sources, and 262 // * a go routine is started that reports on the gone channel if the peer is disconnected (or terminated their streamer) 263 func (f *Fetcher) doRequest(ctx context.Context, gone chan *enode.ID, peersToSkip *sync.Map, sources []*enode.ID, hopCount uint8) ([]*enode.ID, error) { 264 var i int 265 var sourceID *enode.ID 266 var quit chan struct{} 267 268 req := &Request{ 269 Addr: f.addr, 270 SkipCheck: f.skipCheck, 271 peersToSkip: peersToSkip, 272 HopCount: hopCount, 273 } 274 275 foundSource := false 276 // iterate over known sources 277 for i = 0; i < len(sources); i++ { 278 req.Source = sources[i] 279 var err error 280 sourceID, quit, err = f.protoRequestFunc(ctx, req) 281 if err == nil { 282 // remove the peer from known sources 283 // Note: we can modify the source although we are looping on it, because we break from the loop immediately 284 sources = append(sources[:i], sources[i+1:]...) 285 foundSource = true 286 break 287 } 288 } 289 290 // if there are no known sources, or none available, we try request from a closest node 291 if !foundSource { 292 req.Source = nil 293 var err error 294 sourceID, quit, err = f.protoRequestFunc(ctx, req) 295 if err != nil { 296 // if no peers found to request from 297 return sources, err 298 } 299 } 300 // add peer to the set of peers to skip from now 301 peersToSkip.Store(sourceID.String(), time.Now()) 302 303 // if the quit channel is closed, it indicates that the source peer we requested from 304 // disconnected or terminated its streamer 305 // here start a go routine that watches this channel and reports the source peer on the gone channel 306 // this go routine quits if the fetcher global context is done to prevent process leak 307 go func() { 308 select { 309 case <-quit: 310 gone <- sourceID 311 case <-ctx.Done(): 312 } 313 }() 314 return sources, nil 315 }