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  }