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