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