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 }