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