swarm/storage, swarm/network: Fix race condition

There was a race condition in writing/reading chunk.SData between
delivery.processReceivedChunks and dpa.retrieveWorker when the chunk was
still fetching
This commit is contained in:
Balint Gabor 2018-01-22 15:23:41 +01:00
parent 111b53d8bf
commit 295512f5a8
9 changed files with 42 additions and 25 deletions

View file

@ -18,6 +18,7 @@ package stream
import ( import (
"errors" "errors"
"fmt"
"time" "time"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
@ -167,12 +168,11 @@ R:
for req := range d.receiveC { for req := range d.receiveC {
// this should be has locally // this should be has locally
chunk, err := d.db.Get(req.Key) chunk, err := d.db.Get(req.Key)
if err != nil { if err == nil {
log.Error("not in db? ", "key", req.Key, "chunk", chunk)
continue R continue R
} }
if chunk.ReqC == nil { if err != storage.ErrFetching {
continue R panic(fmt.Sprintf("not in db? key %v chunk %v", req.Key, chunk))
} }
select { select {
case <-chunk.ReqC: case <-chunk.ReqC:

View file

@ -20,16 +20,28 @@ import (
"bytes" "bytes"
"crypto/rand" "crypto/rand"
"encoding/binary" "encoding/binary"
"flag"
"fmt" "fmt"
"hash" "hash"
"io" "io"
"os"
"sync" "sync"
"testing" "testing"
"time" "time"
"github.com/ethereum/go-ethereum/crypto/sha3" "github.com/ethereum/go-ethereum/crypto/sha3"
"github.com/ethereum/go-ethereum/log"
) )
var (
loglevel = flag.Int("loglevel", 2, "verbosity of logs")
)
func init() {
flag.Parse()
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
}
type brokenLimitedReader struct { type brokenLimitedReader struct {
lr io.Reader lr io.Reader
errAt int errAt int

View file

@ -699,7 +699,7 @@ func (s *DbStore) get(key Key) (chunk *Chunk, err error) {
decodeData(data, chunk) decodeData(data, chunk)
} else { } else {
err = notFound err = ErrNotFound
} }
return return
@ -712,8 +712,8 @@ func newMockGetDataFunc(mockStore *mock.NodeStore) func(key Key) (data []byte, e
return func(key Key) (data []byte, err error) { return func(key Key) (data []byte, err error) {
data, err = mockStore.Get(key) data, err = mockStore.Get(key)
if err == mock.ErrNotFound { if err == mock.ErrNotFound {
// preserve notFound error // preserve ErrNotFound error
err = notFound err = ErrNotFound
} }
return data, err return data, err
} }

View file

@ -142,8 +142,8 @@ func testDbStoreNotFound(t *testing.T, mock bool) {
defer db.close() defer db.close()
_, err = db.Get(ZeroKey) _, err = db.Get(ZeroKey)
if err != notFound { if err != ErrNotFound {
t.Errorf("Expected notFound, got %v", err) t.Errorf("Expected ErrNotFound, got %v", err)
} }
} }

View file

@ -48,8 +48,8 @@ const (
) )
var ( var (
notFound = errors.New("not found") ErrNotFound = errors.New("not found")
ErrFetching = errors.New("chunk still fetching")
// timeout interval before retrieval is timed out // timeout interval before retrieval is timed out
searchTimeout = 3 * time.Second searchTimeout = 3 * time.Second
) )

View file

@ -106,7 +106,15 @@ func (self *LocalStore) Put(chunk *Chunk) {
// 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) {
chunk, err = self.memStore.Get(key) chunk, err = self.memStore.Get(key)
if err == nil { if err == nil {
if chunk.ReqC != nil {
select {
case <-chunk.ReqC:
default:
return chunk, ErrFetching
}
}
return return
} }
chunk, err = self.DbStore.Get(key) chunk, err = self.DbStore.Get(key)
@ -123,12 +131,11 @@ func (self *LocalStore) GetOrCreateRequest(key Key) (chunk *Chunk, created bool)
var err error var err error
chunk, err = self.Get(key) chunk, err = self.Get(key)
if err == nil { if err == nil {
if chunk.ReqC == nil {
log.Trace(fmt.Sprintf("LocalStore.GetOrRetrieve: %v found locally", key)) log.Trace(fmt.Sprintf("LocalStore.GetOrRetrieve: %v found locally", key))
} else { return chunk, false
log.Trace(fmt.Sprintf("LocalStore.GetOrRetrieve: %v hit on an existing request", key))
// no need to launch again
} }
if err == ErrFetching {
log.Trace(fmt.Sprintf("LocalStore.GetOrRetrieve: %v hit on an existing request %v", key, chunk.ReqC))
return chunk, false return chunk, false
} }
// no data and no request status // no data and no request status

View file

@ -214,7 +214,7 @@ func (s *MemStore) Get(hash Key) (chunk *Chunk, err error) {
l := hash.bits(bitpos, node.bits) l := hash.bits(bitpos, node.bits)
st := node.subtree[l] st := node.subtree[l]
if st == nil { if st == nil {
return nil, notFound return nil, ErrNotFound
} }
bitpos += node.bits bitpos += node.bits
node = st node = st
@ -232,7 +232,7 @@ func (s *MemStore) Get(hash Key) (chunk *Chunk, err error) {
} }
} }
} else { } else {
err = notFound err = ErrNotFound
} }
return return

View file

@ -63,8 +63,8 @@ func TestMemStoreNotFound(t *testing.T) {
defer m.Close() defer m.Close()
_, err := m.Get(ZeroKey) _, err := m.Get(ZeroKey)
if err != notFound { if err != ErrNotFound {
t.Errorf("Expected notFound, got %v", err) t.Errorf("Expected ErrNotFound, got %v", err)
} }
} }

View file

@ -17,7 +17,6 @@
package storage package storage
import ( import (
"encoding/binary"
"time" "time"
) )
@ -40,7 +39,7 @@ func (self *NetStore) Get(key Key) (chunk *Chunk, err error) {
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 return chunk, nil
} }
if created { if created {
@ -53,10 +52,9 @@ func (self *NetStore) Get(key Key) (chunk *Chunk, err error) {
select { select {
case <-t.C: case <-t.C:
return nil, notFound return nil, ErrNotFound
case <-chunk.ReqC: case <-chunk.ReqC:
} }
chunk.Size = int64(binary.LittleEndian.Uint64(chunk.SData[0:8]))
return chunk, nil return chunk, nil
} }