github.com/FUSIONFoundation/efsn@v3.6.2-0.20200916075423-dbb5dd5d2cc7+incompatible/swarm/storage/netstore.go (about) 1 // Copyright 2016 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 storage 18 19 import ( 20 "context" 21 "encoding/hex" 22 "fmt" 23 "sync" 24 "sync/atomic" 25 "time" 26 27 "github.com/FusionFoundation/efsn/p2p/discover" 28 "github.com/FusionFoundation/efsn/swarm/log" 29 30 lru "github.com/hashicorp/golang-lru" 31 ) 32 33 type ( 34 NewNetFetcherFunc func(ctx context.Context, addr Address, peers *sync.Map) NetFetcher 35 ) 36 37 type NetFetcher interface { 38 Request(ctx context.Context) 39 Offer(ctx context.Context, source *discover.NodeID) 40 } 41 42 // NetStore is an extension of local storage 43 // it implements the ChunkStore interface 44 // on request it initiates remote cloud retrieval using a fetcher 45 // fetchers are unique to a chunk and are stored in fetchers LRU memory cache 46 // fetchFuncFactory is a factory object to create a fetch function for a specific chunk address 47 type NetStore struct { 48 mu sync.Mutex 49 store SyncChunkStore 50 fetchers *lru.Cache 51 NewNetFetcherFunc NewNetFetcherFunc 52 closeC chan struct{} 53 } 54 55 var fetcherTimeout = 2 * time.Minute // timeout to cancel the fetcher even if requests are coming in 56 57 // NewNetStore creates a new NetStore object using the given local store. newFetchFunc is a 58 // constructor function that can create a fetch function for a specific chunk address. 59 func NewNetStore(store SyncChunkStore, nnf NewNetFetcherFunc) (*NetStore, error) { 60 fetchers, err := lru.New(defaultChunkRequestsCacheCapacity) 61 if err != nil { 62 return nil, err 63 } 64 return &NetStore{ 65 store: store, 66 fetchers: fetchers, 67 NewNetFetcherFunc: nnf, 68 closeC: make(chan struct{}), 69 }, nil 70 } 71 72 // Put stores a chunk in localstore, and delivers to all requestor peers using the fetcher stored in 73 // the fetchers cache 74 func (n *NetStore) Put(ctx context.Context, ch Chunk) error { 75 n.mu.Lock() 76 defer n.mu.Unlock() 77 78 // put to the chunk to the store, there should be no error 79 err := n.store.Put(ctx, ch) 80 if err != nil { 81 return err 82 } 83 84 // if chunk is now put in the store, check if there was an active fetcher and call deliver on it 85 // (this delivers the chunk to requestors via the fetcher) 86 if f := n.getFetcher(ch.Address()); f != nil { 87 f.deliver(ctx, ch) 88 } 89 return nil 90 } 91 92 // Get retrieves the chunk from the NetStore DPA synchronously. 93 // It calls NetStore.get, and if the chunk is not in local Storage 94 // it calls fetch with the request, which blocks until the chunk 95 // arrived or context is done 96 func (n *NetStore) Get(rctx context.Context, ref Address) (Chunk, error) { 97 chunk, fetch, err := n.get(rctx, ref) 98 if err != nil { 99 return nil, err 100 } 101 if chunk != nil { 102 return chunk, nil 103 } 104 return fetch(rctx) 105 } 106 107 func (n *NetStore) BinIndex(po uint8) uint64 { 108 return n.store.BinIndex(po) 109 } 110 111 func (n *NetStore) Iterator(from uint64, to uint64, po uint8, f func(Address, uint64) bool) error { 112 return n.store.Iterator(from, to, po, f) 113 } 114 115 // FetchFunc returns nil if the store contains the given address. Otherwise it returns a wait function, 116 // which returns after the chunk is available or the context is done 117 func (n *NetStore) FetchFunc(ctx context.Context, ref Address) func(context.Context) error { 118 chunk, fetch, _ := n.get(ctx, ref) 119 if chunk != nil { 120 return nil 121 } 122 return func(ctx context.Context) error { 123 _, err := fetch(ctx) 124 return err 125 } 126 } 127 128 // Close chunk store 129 func (n *NetStore) Close() { 130 close(n.closeC) 131 n.store.Close() 132 // TODO: loop through fetchers to cancel them 133 } 134 135 // get attempts at retrieving the chunk from LocalStore 136 // If it is not found then using getOrCreateFetcher: 137 // 1. Either there is already a fetcher to retrieve it 138 // 2. A new fetcher is created and saved in the fetchers cache 139 // From here on, all Get will hit on this fetcher until the chunk is delivered 140 // or all fetcher contexts are done. 141 // It returns a chunk, a fetcher function and an error 142 // If chunk is nil, the returned fetch function needs to be called with a context to return the chunk. 143 func (n *NetStore) get(ctx context.Context, ref Address) (Chunk, func(context.Context) (Chunk, error), error) { 144 n.mu.Lock() 145 defer n.mu.Unlock() 146 147 chunk, err := n.store.Get(ctx, ref) 148 if err != nil { 149 if err != ErrChunkNotFound { 150 log.Debug("Received error from LocalStore other than ErrNotFound", "err", err) 151 } 152 // The chunk is not available in the LocalStore, let's get the fetcher for it, or create a new one 153 // if it doesn't exist yet 154 f := n.getOrCreateFetcher(ref) 155 // If the caller needs the chunk, it has to use the returned fetch function to get it 156 return nil, f.Fetch, nil 157 } 158 159 return chunk, nil, nil 160 } 161 162 // getOrCreateFetcher attempts at retrieving an existing fetchers 163 // if none exists, creates one and saves it in the fetchers cache 164 // caller must hold the lock 165 func (n *NetStore) getOrCreateFetcher(ref Address) *fetcher { 166 if f := n.getFetcher(ref); f != nil { 167 return f 168 } 169 170 // no fetcher for the given address, we have to create a new one 171 key := hex.EncodeToString(ref) 172 // create the context during which fetching is kept alive 173 ctx, cancel := context.WithTimeout(context.Background(), fetcherTimeout) 174 // destroy is called when all requests finish 175 destroy := func() { 176 // remove fetcher from fetchers 177 n.fetchers.Remove(key) 178 // stop fetcher by cancelling context called when 179 // all requests cancelled/timedout or chunk is delivered 180 cancel() 181 } 182 // peers always stores all the peers which have an active request for the chunk. It is shared 183 // between fetcher and the NewFetchFunc function. It is needed by the NewFetchFunc because 184 // the peers which requested the chunk should not be requested to deliver it. 185 peers := &sync.Map{} 186 187 fetcher := newFetcher(ref, n.NewNetFetcherFunc(ctx, ref, peers), destroy, peers, n.closeC) 188 n.fetchers.Add(key, fetcher) 189 190 return fetcher 191 } 192 193 // getFetcher retrieves the fetcher for the given address from the fetchers cache if it exists, 194 // otherwise it returns nil 195 func (n *NetStore) getFetcher(ref Address) *fetcher { 196 key := hex.EncodeToString(ref) 197 f, ok := n.fetchers.Get(key) 198 if ok { 199 return f.(*fetcher) 200 } 201 return nil 202 } 203 204 // RequestsCacheLen returns the current number of outgoing requests stored in the cache 205 func (n *NetStore) RequestsCacheLen() int { 206 return n.fetchers.Len() 207 } 208 209 // One fetcher object is responsible to fetch one chunk for one address, and keep track of all the 210 // peers who have requested it and did not receive it yet. 211 type fetcher struct { 212 addr Address // address of chunk 213 chunk Chunk // fetcher can set the chunk on the fetcher 214 deliveredC chan struct{} // chan signalling chunk delivery to requests 215 cancelledC chan struct{} // chan signalling the fetcher has been cancelled (removed from fetchers in NetStore) 216 netFetcher NetFetcher // remote fetch function to be called with a request source taken from the context 217 cancel func() // cleanup function for the remote fetcher to call when all upstream contexts are called 218 peers *sync.Map // the peers which asked for the chunk 219 requestCnt int32 // number of requests on this chunk. If all the requests are done (delivered or context is done) the cancel function is called 220 deliverOnce *sync.Once // guarantees that we only close deliveredC once 221 } 222 223 // newFetcher creates a new fetcher object for the fiven addr. fetch is the function which actually 224 // does the retrieval (in non-test cases this is coming from the network package). cancel function is 225 // called either 226 // 1. when the chunk has been fetched all peers have been either notified or their context has been done 227 // 2. the chunk has not been fetched but all context from all the requests has been done 228 // The peers map stores all the peers which have requested chunk. 229 func newFetcher(addr Address, nf NetFetcher, cancel func(), peers *sync.Map, closeC chan struct{}) *fetcher { 230 cancelOnce := &sync.Once{} // cancel should only be called once 231 return &fetcher{ 232 addr: addr, 233 deliveredC: make(chan struct{}), 234 deliverOnce: &sync.Once{}, 235 cancelledC: closeC, 236 netFetcher: nf, 237 cancel: func() { 238 cancelOnce.Do(func() { 239 cancel() 240 }) 241 }, 242 peers: peers, 243 } 244 } 245 246 // Fetch fetches the chunk synchronously, it is called by NetStore.Get is the chunk is not available 247 // locally. 248 func (f *fetcher) Fetch(rctx context.Context) (Chunk, error) { 249 atomic.AddInt32(&f.requestCnt, 1) 250 defer func() { 251 // if all the requests are done the fetcher can be cancelled 252 if atomic.AddInt32(&f.requestCnt, -1) == 0 { 253 f.cancel() 254 } 255 }() 256 257 // The peer asking for the chunk. Store in the shared peers map, but delete after the request 258 // has been delivered 259 peer := rctx.Value("peer") 260 if peer != nil { 261 f.peers.Store(peer, time.Now()) 262 defer f.peers.Delete(peer) 263 } 264 265 // If there is a source in the context then it is an offer, otherwise a request 266 sourceIF := rctx.Value("source") 267 if sourceIF != nil { 268 var source *discover.NodeID 269 id := discover.MustHexID(sourceIF.(string)) 270 source = &id 271 f.netFetcher.Offer(rctx, source) 272 } else { 273 f.netFetcher.Request(rctx) 274 } 275 276 // wait until either the chunk is delivered or the context is done 277 select { 278 case <-rctx.Done(): 279 return nil, rctx.Err() 280 case <-f.deliveredC: 281 return f.chunk, nil 282 case <-f.cancelledC: 283 return nil, fmt.Errorf("fetcher cancelled") 284 } 285 } 286 287 // deliver is called by NetStore.Put to notify all pending requests 288 func (f *fetcher) deliver(ctx context.Context, ch Chunk) { 289 f.deliverOnce.Do(func() { 290 f.chunk = ch 291 // closing the deliveredC channel will terminate ongoing requests 292 close(f.deliveredC) 293 }) 294 }