github.com/daragao/go-ethereum@v1.8.14-0.20180809141559-45eaef243198/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  	"time"
    22  
    23  	"github.com/ethereum/go-ethereum/swarm/log"
    24  	"github.com/ethereum/go-ethereum/swarm/spancontext"
    25  	opentracing "github.com/opentracing/opentracing-go"
    26  )
    27  
    28  var (
    29  	// NetStore.Get timeout for get and get retries
    30  	// This is the maximum period that the Get will block.
    31  	// If it is reached, Get will return ErrChunkNotFound.
    32  	netStoreRetryTimeout = 30 * time.Second
    33  	// Minimal period between calling get method on NetStore
    34  	// on retry. It protects calling get very frequently if
    35  	// it returns ErrChunkNotFound very fast.
    36  	netStoreMinRetryDelay = 3 * time.Second
    37  	// Timeout interval before retrieval is timed out.
    38  	// It is used in NetStore.get on waiting for ReqC to be
    39  	// closed on a single retrieve request.
    40  	searchTimeout = 10 * time.Second
    41  )
    42  
    43  // NetStore implements the ChunkStore interface,
    44  // this chunk access layer assumed 2 chunk stores
    45  // local storage eg. LocalStore and network storage eg., NetStore
    46  // access by calling network is blocking with a timeout
    47  type NetStore struct {
    48  	localStore *LocalStore
    49  	retrieve   func(ctx context.Context, chunk *Chunk) error
    50  }
    51  
    52  func NewNetStore(localStore *LocalStore, retrieve func(ctx context.Context, chunk *Chunk) error) *NetStore {
    53  	return &NetStore{localStore, retrieve}
    54  }
    55  
    56  // Get is the entrypoint for local retrieve requests
    57  // waits for response or times out
    58  //
    59  // Get uses get method to retrieve request, but retries if the
    60  // ErrChunkNotFound is returned by get, until the netStoreRetryTimeout
    61  // is reached.
    62  func (ns *NetStore) Get(ctx context.Context, addr Address) (chunk *Chunk, err error) {
    63  
    64  	var sp opentracing.Span
    65  	ctx, sp = spancontext.StartSpan(
    66  		ctx,
    67  		"netstore.get.global")
    68  	defer sp.Finish()
    69  
    70  	timer := time.NewTimer(netStoreRetryTimeout)
    71  	defer timer.Stop()
    72  
    73  	// result and resultC provide results from the goroutine
    74  	// where NetStore.get is called.
    75  	type result struct {
    76  		chunk *Chunk
    77  		err   error
    78  	}
    79  	resultC := make(chan result)
    80  
    81  	// quitC ensures that retring goroutine is terminated
    82  	// when this function returns.
    83  	quitC := make(chan struct{})
    84  	defer close(quitC)
    85  
    86  	// do retries in a goroutine so that the timer can
    87  	// force this method to return after the netStoreRetryTimeout.
    88  	go func() {
    89  		// limiter ensures that NetStore.get is not called more frequently
    90  		// then netStoreMinRetryDelay. If NetStore.get takes longer
    91  		// then netStoreMinRetryDelay, the next retry call will be
    92  		// without a delay.
    93  		limiter := time.NewTimer(netStoreMinRetryDelay)
    94  		defer limiter.Stop()
    95  
    96  		for {
    97  			chunk, err := ns.get(ctx, addr, 0)
    98  			if err != ErrChunkNotFound {
    99  				// break retry only if the error is nil
   100  				// or other error then ErrChunkNotFound
   101  				select {
   102  				case <-quitC:
   103  					// Maybe NetStore.Get function has returned
   104  					// by the timer.C while we were waiting for the
   105  					// results. Terminate this goroutine.
   106  				case resultC <- result{chunk: chunk, err: err}:
   107  					// Send the result to the parrent goroutine.
   108  				}
   109  				return
   110  
   111  			}
   112  			select {
   113  			case <-quitC:
   114  				// NetStore.Get function has returned, possibly
   115  				// by the timer.C, which makes this goroutine
   116  				// not needed.
   117  				return
   118  			case <-limiter.C:
   119  			}
   120  			// Reset the limiter for the next iteration.
   121  			limiter.Reset(netStoreMinRetryDelay)
   122  			log.Debug("NetStore.Get retry chunk", "key", addr)
   123  		}
   124  	}()
   125  
   126  	select {
   127  	case r := <-resultC:
   128  		return r.chunk, r.err
   129  	case <-timer.C:
   130  		return nil, ErrChunkNotFound
   131  	}
   132  }
   133  
   134  // GetWithTimeout makes a single retrieval attempt for a chunk with a explicit timeout parameter
   135  func (ns *NetStore) GetWithTimeout(ctx context.Context, addr Address, timeout time.Duration) (chunk *Chunk, err error) {
   136  	return ns.get(ctx, addr, timeout)
   137  }
   138  
   139  func (ns *NetStore) get(ctx context.Context, addr Address, timeout time.Duration) (chunk *Chunk, err error) {
   140  	if timeout == 0 {
   141  		timeout = searchTimeout
   142  	}
   143  
   144  	var sp opentracing.Span
   145  	ctx, sp = spancontext.StartSpan(
   146  		ctx,
   147  		"netstore.get")
   148  	defer sp.Finish()
   149  
   150  	if ns.retrieve == nil {
   151  		chunk, err = ns.localStore.Get(ctx, addr)
   152  		if err == nil {
   153  			return chunk, nil
   154  		}
   155  		if err != ErrFetching {
   156  			return nil, err
   157  		}
   158  	} else {
   159  		var created bool
   160  		chunk, created = ns.localStore.GetOrCreateRequest(ctx, addr)
   161  
   162  		if chunk.ReqC == nil {
   163  			return chunk, nil
   164  		}
   165  
   166  		if created {
   167  			err := ns.retrieve(ctx, chunk)
   168  			if err != nil {
   169  				// mark chunk request as failed so that we can retry it later
   170  				chunk.SetErrored(ErrChunkUnavailable)
   171  				return nil, err
   172  			}
   173  		}
   174  	}
   175  
   176  	t := time.NewTicker(timeout)
   177  	defer t.Stop()
   178  
   179  	select {
   180  	case <-t.C:
   181  		// mark chunk request as failed so that we can retry
   182  		chunk.SetErrored(ErrChunkNotFound)
   183  		return nil, ErrChunkNotFound
   184  	case <-chunk.ReqC:
   185  	}
   186  	chunk.SetErrored(nil)
   187  	return chunk, nil
   188  }
   189  
   190  // Put is the entrypoint for local store requests coming from storeLoop
   191  func (ns *NetStore) Put(ctx context.Context, chunk *Chunk) {
   192  	ns.localStore.Put(ctx, chunk)
   193  }
   194  
   195  // Close chunk store
   196  func (ns *NetStore) Close() {
   197  	ns.localStore.Close()
   198  }