swarm/network, storage: Removal of chunk request from MemStore upon timeout or failure (#284)

This commit is contained in:
Anton Evangelatov 2018-03-05 18:29:25 +01:00 committed by Balint Gabor
parent 9316ada27b
commit a3c93ca4c4
12 changed files with 205 additions and 54 deletions

View file

@ -39,11 +39,12 @@ import (
"github.com/ethereum/go-ethereum/swarm/network" "github.com/ethereum/go-ethereum/swarm/network"
"github.com/ethereum/go-ethereum/swarm/state" "github.com/ethereum/go-ethereum/swarm/state"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
colorable "github.com/mattn/go-colorable"
) )
var ( var (
adapter = flag.String("adapter", "sim", "type of simulation: sim|socket|exec|docker") adapter = flag.String("adapter", "sim", "type of simulation: sim|socket|exec|docker")
loglevel = flag.Int("loglevel", 2, "verbosity of logs") loglevel = flag.Int("loglevel", 4, "verbosity of logs")
) )
var ( var (
@ -63,8 +64,8 @@ func init() {
// protocol when using the exec adapter // protocol when using the exec adapter
adapters.RegisterServices(services) adapters.RegisterServices(services)
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(os.Stderr, log.TerminalFormat(false)))) log.PrintOrigins(true)
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
} }
// NewStreamerService // NewStreamerService
@ -73,10 +74,10 @@ func NewStreamerService(ctx *adapters.ServiceContext) (node.Service, error) {
addr := toAddr(id) addr := toAddr(id)
kad := network.NewKademlia(addr.Over(), network.NewKadParams()) kad := network.NewKademlia(addr.Over(), network.NewKadParams())
store := stores[id].(*storage.LocalStore) store := stores[id].(*storage.LocalStore)
db := storage.NewDBAPI(store) netStore := storage.NewNetStore(store, nil)
db := storage.NewDBAPI(netStore)
delivery := NewDelivery(kad, db) delivery := NewDelivery(kad, db)
deliveries[id] = delivery deliveries[id] = delivery
netStore := storage.NewNetStore(store, nil)
r := NewRegistry(addr, delivery, netStore, state.NewMemStore(), defaultSkipCheck) r := NewRegistry(addr, delivery, netStore, state.NewMemStore(), defaultSkipCheck)
RegisterSwarmSyncerServer(r, db) RegisterSwarmSyncerServer(r, db)
RegisterSwarmSyncerClient(r, db) RegisterSwarmSyncerClient(r, db)
@ -105,7 +106,9 @@ func newStreamerTester(t *testing.T) (*p2ptest.ProtocolTester, *Registry, *stora
return nil, nil, nil, removeDataDir, err return nil, nil, nil, removeDataDir, err
} }
db := storage.NewDBAPI(localStore) netStore := storage.NewNetStore(localStore, nil)
db := storage.NewDBAPI(netStore)
delivery := NewDelivery(to, db) delivery := NewDelivery(to, db)
streamer := NewRegistry(addr, delivery, localStore, state.NewMemStore(), defaultSkipCheck) streamer := NewRegistry(addr, delivery, localStore, state.NewMemStore(), defaultSkipCheck)
teardown := func() { teardown := func() {

View file

@ -38,7 +38,6 @@ type Delivery struct {
overlay network.Overlay overlay network.Overlay
receiveC chan *ChunkDeliveryMsg receiveC chan *ChunkDeliveryMsg
getPeer func(discover.NodeID) *Peer getPeer func(discover.NodeID) *Peer
quit chan struct{}
} }
func NewDelivery(overlay network.Overlay, db *storage.DBAPI) *Delivery { func NewDelivery(overlay network.Overlay, db *storage.DBAPI) *Delivery {
@ -139,6 +138,7 @@ func (d *Delivery) handleRetrieveRequestMsg(sp *Peer, req *RetrieveRequestMsg) e
if created { if created {
if err := d.RequestFromPeers(chunk.Key[:], false, sp.ID()); err != nil { if err := d.RequestFromPeers(chunk.Key[:], false, sp.ID()); err != nil {
log.Warn("unable to forward chunk request", "peer", sp.ID(), "key", chunk.Key, "err", err) log.Warn("unable to forward chunk request", "peer", sp.ID(), "key", chunk.Key, "err", err)
chunk.SetErrored(true)
return nil return nil
} }
} }
@ -148,11 +148,11 @@ func (d *Delivery) handleRetrieveRequestMsg(sp *Peer, req *RetrieveRequestMsg) e
select { select {
case <-chunk.ReqC: case <-chunk.ReqC:
case <-d.quit:
return
case <-t.C: case <-t.C:
chunk.SetErrored(true)
return return
} }
chunk.SetErrored(false)
if req.SkipCheck { if req.SkipCheck {
err := sp.Deliver(chunk, s.priority) err := sp.Deliver(chunk, s.priority)

View file

@ -47,10 +47,11 @@ func newIntervalsStreamerService(ctx *adapters.ServiceContext) (node.Service, er
addr := toAddr(id) addr := toAddr(id)
kad := network.NewKademlia(addr.Over(), network.NewKadParams()) kad := network.NewKademlia(addr.Over(), network.NewKadParams())
store := stores[id].(*storage.LocalStore) store := stores[id].(*storage.LocalStore)
db := storage.NewDBAPI(store)
netStore := storage.NewNetStore(store, nil)
db := storage.NewDBAPI(netStore)
delivery := NewDelivery(kad, db) delivery := NewDelivery(kad, db)
deliveries[id] = delivery deliveries[id] = delivery
netStore := storage.NewNetStore(store, nil)
r := NewRegistry(addr, delivery, netStore, state.NewMemStore(), defaultSkipCheck) r := NewRegistry(addr, delivery, netStore, state.NewMemStore(), defaultSkipCheck)
r.RegisterClientFunc(externalStreamName, func(p *Peer, t []byte, live bool) (Client, error) { r.RegisterClientFunc(externalStreamName, func(p *Peer, t []byte, live bool) (Client, error) {

View file

@ -108,7 +108,8 @@ func testSyncBetweenNodes(t *testing.T, nodes, conns, chunkCount int, skipCheck
// create DBAPI-s for all nodes // create DBAPI-s for all nodes
dbs := make([]*storage.DBAPI, nodes) dbs := make([]*storage.DBAPI, nodes)
for i := 0; i < nodes; i++ { for i := 0; i < nodes; i++ {
dbs[i] = storage.NewDBAPI(sim.Stores[i].(*storage.LocalStore)) netStore := storage.NewNetStore(sim.Stores[i].(*storage.LocalStore), nil)
dbs[i] = storage.NewDBAPI(netStore)
} }
// collect hashes in po 1 bin for each node // collect hashes in po 1 bin for each node

View file

@ -18,35 +18,34 @@ package storage
// wrapper of db-s to provide mockable custom local chunk store access to syncer // wrapper of db-s to provide mockable custom local chunk store access to syncer
type DBAPI struct { type DBAPI struct {
db *LDBStore ns *NetStore
loc *LocalStore
} }
func NewDBAPI(loc *LocalStore) *DBAPI { func NewDBAPI(ns *NetStore) *DBAPI {
return &DBAPI{loc.DbStore.(*LDBStore), loc} return &DBAPI{ns: ns}
} }
// to obtain the chunks from key or request db entry only // to obtain the chunks from key or request db entry only
func (self *DBAPI) Get(key Key) (*Chunk, error) { func (self *DBAPI) Get(key Key) (*Chunk, error) {
return self.loc.Get(key) return self.ns.localStore.Get(key)
} }
// current storage counter of chunk db // current storage counter of chunk db
func (self *DBAPI) CurrentBucketStorageIndex(po uint8) uint64 { func (self *DBAPI) CurrentBucketStorageIndex(po uint8) uint64 {
return self.db.CurrentBucketStorageIndex(po) return self.ns.localStore.DbStore.CurrentBucketStorageIndex(po)
} }
// iteration storage counter and proximity order // iteration storage counter and proximity order
func (self *DBAPI) Iterator(from uint64, to uint64, po uint8, f func(Key, uint64) bool) error { func (self *DBAPI) Iterator(from uint64, to uint64, po uint8, f func(Key, uint64) bool) error {
return self.db.SyncIterator(from, to, po, f) return self.ns.localStore.DbStore.SyncIterator(from, to, po, f)
} }
// to obtain the chunks from key or request db entry only // to obtain the chunks from key or request db entry only
func (self *DBAPI) GetOrCreateRequest(key Key) (*Chunk, bool) { func (self *DBAPI) GetOrCreateRequest(key Key) (*Chunk, bool) {
return self.loc.GetOrCreateRequest(key) return self.ns.localStore.GetOrCreateRequest(key)
} }
// to obtain the chunks from key or request db entry only // to obtain the chunks from key or request db entry only
func (self *DBAPI) Put(chunk *Chunk) { func (self *DBAPI) Put(chunk *Chunk) {
self.loc.Put(chunk) self.ns.localStore.Put(chunk)
} }

View file

@ -77,8 +77,8 @@ func NewLocalDPA(datadir string, basekey []byte) (*DPA, error) {
} }
return NewDPA(&LocalStore{ return NewDPA(&LocalStore{
NewMemStore(dbStore, singletonSwarmCacheCapacity), memStore: NewMemStore(dbStore, singletonSwarmCacheCapacity),
dbStore, DbStore: dbStore,
}, NewChunkerParams()), nil }, NewChunkerParams()), nil
} }

View file

@ -36,8 +36,8 @@ func TestDPArandom(t *testing.T) {
db.setCapacity(50000) db.setCapacity(50000)
memStore := NewMemStore(db, defaultCacheCapacity) memStore := NewMemStore(db, defaultCacheCapacity)
localStore := &LocalStore{ localStore := &LocalStore{
memStore, memStore: memStore,
db, DbStore: db,
} }
chunker := NewTreeChunker(NewChunkerParams()) chunker := NewTreeChunker(NewChunkerParams())
dpa := &DPA{ dpa := &DPA{
@ -94,8 +94,8 @@ func TestDPA_capacity(t *testing.T) {
db := tdb.LDBStore db := tdb.LDBStore
memStore := NewMemStore(db, 0) memStore := NewMemStore(db, 0)
localStore := &LocalStore{ localStore := &LocalStore{
memStore, memStore: memStore,
db, DbStore: db,
} }
chunker := NewTreeChunker(NewChunkerParams()) chunker := NewTreeChunker(NewChunkerParams())
dpa := &DPA{ dpa := &DPA{

View file

@ -19,6 +19,7 @@ package storage
import ( import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"sync"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
@ -47,8 +48,9 @@ func NewDefaultStoreParams() (self *StoreParams) {
// LocalStore is a combination of inmemory db over a disk persisted db // LocalStore is a combination of inmemory db over a disk persisted db
// implements a Get/Put with fallback (caching) logic using any 2 ChunkStores // implements a Get/Put with fallback (caching) logic using any 2 ChunkStores
type LocalStore struct { type LocalStore struct {
memStore ChunkStore memStore *MemStore
DbStore ChunkStore DbStore *LDBStore
mu sync.Mutex
} }
// This constructor uses MemStore and DbStore as components // This constructor uses MemStore and DbStore as components
@ -63,20 +65,6 @@ func NewLocalStore(hash SwarmHasher, params *StoreParams, basekey []byte, mockSt
}, nil }, nil
} }
func NewTestLocalStore(path string) (*LocalStore, error) {
basekey := make([]byte, 32)
hasher := MakeHashFunc("SHA3")
dbStore, err := NewLDBStore(path, hasher, singletonSwarmDbCapacity, func(k Key) (ret uint8) { return uint8(Proximity(basekey[:], k[:])) })
if err != nil {
return nil, err
}
localStore := &LocalStore{
memStore: NewMemStore(dbStore, singletonSwarmDbCapacity),
DbStore: dbStore,
}
return localStore, nil
}
func NewTestLocalStoreForAddr(path string, basekey []byte) (*LocalStore, error) { func NewTestLocalStoreForAddr(path string, basekey []byte) (*LocalStore, error) {
hasher := MakeHashFunc("SHA3") hasher := MakeHashFunc("SHA3")
dbStore, err := NewLDBStore(path, hasher, singletonSwarmDbCapacity, func(k Key) (ret uint8) { return uint8(Proximity(basekey[:], k[:])) }) dbStore, err := NewLDBStore(path, hasher, singletonSwarmDbCapacity, func(k Key) (ret uint8) { return uint8(Proximity(basekey[:], k[:])) })
@ -91,12 +79,15 @@ func NewTestLocalStoreForAddr(path string, basekey []byte) (*LocalStore, error)
} }
func (self *LocalStore) CacheCounter() uint64 { func (self *LocalStore) CacheCounter() uint64 {
return uint64(self.memStore.(*MemStore).Counter()) return uint64(self.memStore.Counter())
} }
// LocalStore is itself a chunk store // LocalStore is itself a chunk store
// unsafe, in that the data is not integrity checked // unsafe, in that the data is not integrity checked
func (self *LocalStore) Put(chunk *Chunk) { func (self *LocalStore) Put(chunk *Chunk) {
self.mu.Lock()
defer self.mu.Unlock()
chunk.Size = int64(binary.LittleEndian.Uint64(chunk.SData[0:8])) chunk.Size = int64(binary.LittleEndian.Uint64(chunk.SData[0:8]))
c := &Chunk{ c := &Chunk{
Key: Key(append([]byte{}, chunk.Key...)), Key: Key(append([]byte{}, chunk.Key...)),
@ -115,6 +106,13 @@ func (self *LocalStore) Put(chunk *Chunk) {
// so additional timeout may be needed to wrap this call if // so additional timeout may be needed to wrap this call if
// ChunkStores are remote and can have long latency // ChunkStores are remote and can have long latency
func (self *LocalStore) Get(key Key) (chunk *Chunk, err error) { func (self *LocalStore) Get(key Key) (chunk *Chunk, err error) {
self.mu.Lock()
defer self.mu.Unlock()
return self.get(key)
}
func (self *LocalStore) get(key Key) (chunk *Chunk, err error) {
chunk, err = self.memStore.Get(key) chunk, err = self.memStore.Get(key)
if err == nil { if err == nil {
if chunk.ReqC != nil { if chunk.ReqC != nil {
@ -137,13 +135,16 @@ func (self *LocalStore) Get(key Key) (chunk *Chunk, err error) {
// retrieve logic common for local and network chunk retrieval requests // retrieve logic common for local and network chunk retrieval requests
func (self *LocalStore) GetOrCreateRequest(key Key) (chunk *Chunk, created bool) { func (self *LocalStore) GetOrCreateRequest(key Key) (chunk *Chunk, created bool) {
self.mu.Lock()
defer self.mu.Unlock()
var err error var err error
chunk, err = self.Get(key) chunk, err = self.get(key)
if err == nil { if err == nil && !chunk.GetErrored() {
log.Trace(fmt.Sprintf("LocalStore.GetOrRetrieve: %v found locally", key)) log.Trace(fmt.Sprintf("LocalStore.GetOrRetrieve: %v found locally", key))
return chunk, false return chunk, false
} }
if err == ErrFetching { if err == ErrFetching && !chunk.GetErrored() {
log.Trace(fmt.Sprintf("LocalStore.GetOrRetrieve: %v hit on an existing request %v", key, chunk.ReqC)) log.Trace(fmt.Sprintf("LocalStore.GetOrRetrieve: %v hit on an existing request %v", key, chunk.ReqC))
return chunk, false return chunk, false
} }

View file

@ -48,12 +48,16 @@ func (self *NetStore) Get(key Key) (chunk *Chunk, err error) {
} else { } else {
var created bool var created bool
chunk, created = self.localStore.GetOrCreateRequest(key) chunk, created = self.localStore.GetOrCreateRequest(key)
if chunk.ReqC == nil { if chunk.ReqC == nil {
return chunk, nil return chunk, nil
} }
if created { if created {
if err := self.retrieve(chunk); err != nil { err := self.retrieve(chunk)
if err != nil {
// mark chunk request as failed so that we can retry it later
chunk.SetErrored(true)
return nil, err return nil, err
} }
} }
@ -64,9 +68,12 @@ func (self *NetStore) Get(key Key) (chunk *Chunk, err error) {
select { select {
case <-t.C: case <-t.C:
// mark chunk request as failed so that we can retry
chunk.SetErrored(true)
return nil, ErrChunkNotFound return nil, ErrChunkNotFound
case <-chunk.ReqC: case <-chunk.ReqC:
} }
chunk.SetErrored(false)
return chunk, nil return chunk, nil
} }

View file

@ -0,0 +1,119 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package storage
import (
"encoding/hex"
"errors"
"io/ioutil"
"testing"
"time"
"github.com/ethereum/go-ethereum/swarm/network"
)
var (
errUnknown = errors.New("unknown error")
)
type mockRetrieve struct {
requests map[string]int
}
func NewMockRetrieve() *mockRetrieve {
return &mockRetrieve{requests: make(map[string]int)}
}
func newDummyChunk(key Key) *Chunk {
chunk := NewChunk(key, make(chan bool))
chunk.SData = []byte{3, 4, 5}
chunk.Size = 3
return chunk
}
func (m *mockRetrieve) retrieve(chunk *Chunk) error {
hkey := hex.EncodeToString(chunk.Key)
m.requests[hkey] += 1
// on second call return error
if m.requests[hkey] == 2 {
return errUnknown
}
// on third call return data
if m.requests[hkey] == 3 {
*chunk = *newDummyChunk(chunk.Key)
go func() {
time.Sleep(50 * time.Millisecond)
close(chunk.ReqC)
}()
return nil
}
return nil
}
func TestNetstoreFailedRequest(t *testing.T) {
searchTimeout = 100 * time.Millisecond
// setup
addr := network.RandomAddr() // tested peers peer address
// temp datadir
datadir, err := ioutil.TempDir("", "netstore")
if err != nil {
t.Fatal(err)
}
localStore, err := NewTestLocalStoreForAddr(datadir, addr.Over())
if err != nil {
t.Fatal(err)
}
r := NewMockRetrieve()
netStore := NewNetStore(localStore, r.retrieve)
// first call
key := Key{}
_, err = netStore.Get(key)
if err == nil || err != ErrChunkNotFound {
t.Fatalf("expected to get ErrChunkNotFound, but got: %s", err)
}
// second call
_, err = netStore.Get(key)
if got := r.requests[hex.EncodeToString(key)]; got != 2 {
t.Fatalf("expected to have called retrieve two times, but got: %v", got)
}
if err != errUnknown {
t.Fatalf("expected to get an unknown error, but got: %s", err)
}
// third call
chunk, err := netStore.Get(key)
if got := r.requests[hex.EncodeToString(key)]; got != 3 {
t.Fatalf("expected to have called retrieve three times, but got: %v", got)
}
if err != nil || chunk == nil {
t.Fatalf("expected to get a chunk but got: %v, %s", chunk, err)
}
if len(chunk.SData) != 3 {
t.Fatalf("expected to get a chunk with size 3, but got: %v", chunk.SData)
}
}

View file

@ -24,6 +24,7 @@ import (
"fmt" "fmt"
"hash" "hash"
"io" "io"
"sync"
"github.com/ethereum/go-ethereum/bmt" "github.com/ethereum/go-ethereum/bmt"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -176,6 +177,22 @@ type Chunk struct {
C chan bool // to signal data delivery by the dpa C chan bool // to signal data delivery by the dpa
ReqC chan bool // to signal the request done ReqC chan bool // to signal the request done
dbStored chan bool // never remove a chunk from memStore before it is written to dbStore dbStored chan bool // never remove a chunk from memStore before it is written to dbStore
errored bool // flag which is set when the chunk request has errored or timeouted
erroredMu sync.Mutex
}
func (c *Chunk) SetErrored(val bool) {
c.erroredMu.Lock()
defer c.erroredMu.Unlock()
c.errored = val
}
func (c *Chunk) GetErrored() bool {
c.erroredMu.Lock()
defer c.erroredMu.Unlock()
return c.errored
} }
func NewChunk(key Key, reqC chan bool) *Chunk { func NewChunk(key Key, reqC chan bool) *Chunk {

View file

@ -146,7 +146,10 @@ func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, config *api.
HiveParams: config.HiveParams, HiveParams: config.HiveParams,
} }
db := storage.NewDBAPI(self.lstore) // init netStore
ns := storage.NewNetStore(self.lstore, self.streamer.Retrieve)
db := storage.NewDBAPI(ns)
delivery := stream.NewDelivery(to, db) delivery := stream.NewDelivery(to, db)
// TODO: decide on intervals store file location // TODO: decide on intervals store file location
stateStore, err := state.NewDBStore(filepath.Join(config.Path, "state-store.db")) stateStore, err := state.NewDBStore(filepath.Join(config.Path, "state-store.db"))
@ -160,10 +163,10 @@ func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, config *api.
self.bzz = network.NewBzz(bzzconfig, to, stateStore, stream.Spec, self.streamer.Run) self.bzz = network.NewBzz(bzzconfig, to, stateStore, stream.Spec, self.streamer.Run)
// set up DPA, the cloud storage local access layer // set up DPA, the cloud storage local access layer
dpaChunkStore := storage.NewNetStore(self.lstore, self.streamer.Retrieve) //dpaChunkStore := storage.NewNetStore(self.lstore, self.streamer.Retrieve)
log.Debug(fmt.Sprintf("-> Local Access to Swarm")) log.Debug(fmt.Sprintf("-> Local Access to Swarm"))
// Swarm Hash Merklised Chunking for Arbitrary-length Document/File storage // Swarm Hash Merklised Chunking for Arbitrary-length Document/File storage
self.dpa = storage.NewDPA(dpaChunkStore, self.config.ChunkerParams) self.dpa = storage.NewDPA(ns, self.config.ChunkerParams)
log.Debug(fmt.Sprintf("-> Content Store API")) log.Debug(fmt.Sprintf("-> Content Store API"))
// Pss = postal service over swarm (devp2p over bzz) // Pss = postal service over swarm (devp2p over bzz)