swarm/network, swarm/storage: Make context fetcher-wide

This commit is contained in:
lash 2019-02-01 11:42:36 +01:00
parent a89170cfb2
commit 002cee3868
5 changed files with 89 additions and 85 deletions

View file

@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/swarm/spancontext"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
) )
@ -52,6 +53,8 @@ type Fetcher struct {
requestC chan uint8 // channel for incoming requests (with the hopCount value in it) requestC chan uint8 // channel for incoming requests (with the hopCount value in it)
searchTimeout time.Duration searchTimeout time.Duration
skipCheck bool skipCheck bool
ctx context.Context
quitFunc func()
} }
type Request struct { type Request struct {
@ -109,14 +112,15 @@ func NewFetcherFactory(request RequestFunc, skipCheck bool) *FetcherFactory {
// contain the peers which are actively requesting this chunk, to make sure we // contain the peers which are actively requesting this chunk, to make sure we
// don't request back the chunks from them. // don't request back the chunks from them.
// The created Fetcher is started and returned. // The created Fetcher is started and returned.
func (f *FetcherFactory) New(ctx context.Context, source storage.Address, peersToSkip *sync.Map) storage.NetFetcher { func (f *FetcherFactory) New(ctx context.Context, source storage.Address, peers *sync.Map) storage.NetFetcher {
fetcher := NewFetcher(source, f.request, f.skipCheck) fetcher := NewFetcher(ctx, source, f.request, f.skipCheck)
go fetcher.run(ctx, peersToSkip) go fetcher.run(peers)
return fetcher return fetcher
} }
// NewFetcher creates a new Fetcher for the given chunk address using the given request function. // NewFetcher creates a new Fetcher for the given chunk address using the given request function.
func NewFetcher(addr storage.Address, rf RequestFunc, skipCheck bool) *Fetcher { func NewFetcher(ctx context.Context, addr storage.Address, rf RequestFunc, skipCheck bool) *Fetcher {
sctx, sp := spancontext.StartSpan(ctx, "fetcher")
return &Fetcher{ return &Fetcher{
addr: addr, addr: addr,
protoRequestFunc: rf, protoRequestFunc: rf,
@ -124,14 +128,18 @@ func NewFetcher(addr storage.Address, rf RequestFunc, skipCheck bool) *Fetcher {
requestC: make(chan uint8), requestC: make(chan uint8),
searchTimeout: defaultSearchTimeout, searchTimeout: defaultSearchTimeout,
skipCheck: skipCheck, skipCheck: skipCheck,
ctx: sctx,
quitFunc: func() {
sp.Finish()
},
} }
} }
// Offer is called when an upstream peer offers the chunk via syncing as part of `OfferedHashesMsg` and the node does not have the chunk locally. // Offer is called when an upstream peer offers the chunk via syncing as part of `OfferedHashesMsg` and the node does not have the chunk locally.
func (f *Fetcher) Offer(ctx context.Context, source *enode.ID) { func (f *Fetcher) Offer(source *enode.ID) {
// First we need to have this select to make sure that we return if context is done // First we need to have this select to make sure that we return if context is done
select { select {
case <-ctx.Done(): case <-f.ctx.Done():
return return
default: default:
} }
@ -140,15 +148,15 @@ func (f *Fetcher) Offer(ctx context.Context, source *enode.ID) {
// push to offerC instead if offerC is available (see number 2 in https://golang.org/ref/spec#Select_statements) // push to offerC instead if offerC is available (see number 2 in https://golang.org/ref/spec#Select_statements)
select { select {
case f.offerC <- source: case f.offerC <- source:
case <-ctx.Done(): case <-f.ctx.Done():
} }
} }
// Request is called when an upstream peer request the chunk as part of `RetrieveRequestMsg`, or from a local request through FileStore, and the node does not have the chunk locally. // Request is called when an upstream peer request the chunk as part of `RetrieveRequestMsg`, or from a local request through FileStore, and the node does not have the chunk locally.
func (f *Fetcher) Request(ctx context.Context, hopCount uint8) { func (f *Fetcher) Request(hopCount uint8) {
// First we need to have this select to make sure that we return if context is done // First we need to have this select to make sure that we return if context is done
select { select {
case <-ctx.Done(): case <-f.ctx.Done():
return return
default: default:
} }
@ -162,13 +170,13 @@ func (f *Fetcher) Request(ctx context.Context, hopCount uint8) {
// push to offerC instead if offerC is available (see number 2 in https://golang.org/ref/spec#Select_statements) // push to offerC instead if offerC is available (see number 2 in https://golang.org/ref/spec#Select_statements)
select { select {
case f.requestC <- hopCount + 1: case f.requestC <- hopCount + 1:
case <-ctx.Done(): case <-f.ctx.Done():
} }
} }
// start prepares the Fetcher // start prepares the Fetcher
// it keeps the Fetcher alive within the lifecycle of the passed context // it keeps the Fetcher alive within the lifecycle of the passed context
func (f *Fetcher) run(ctx context.Context, peers *sync.Map) { func (f *Fetcher) run(peers *sync.Map) {
var ( var (
doRequest bool // determines if retrieval is initiated in the current iteration doRequest bool // determines if retrieval is initiated in the current iteration
wait *time.Timer // timer for search timeout wait *time.Timer // timer for search timeout
@ -219,16 +227,17 @@ func (f *Fetcher) run(ctx context.Context, peers *sync.Map) {
doRequest = requested doRequest = requested
// all Fetcher context closed, can quit // all Fetcher context closed, can quit
case <-ctx.Done(): case <-f.ctx.Done():
log.Trace("terminate fetcher", "request addr", f.addr) log.Trace("terminate fetcher", "request addr", f.addr)
// TODO: send cancellations to all peers left over in peers map (i.e., those we requested from) // TODO: send cancellations to all peers left over in peers map (i.e., those we requested from)
f.quitFunc()
return return
} }
// need to issue a new request // need to issue a new request
if doRequest { if doRequest {
var err error var err error
sources, err = f.doRequest(ctx, gone, peers, sources, hopCount) sources, err = f.doRequest(gone, peers, sources, hopCount)
if err != nil { if err != nil {
log.Info("unable to request", "request addr", f.addr, "err", err) log.Info("unable to request", "request addr", f.addr, "err", err)
} }
@ -266,7 +275,7 @@ func (f *Fetcher) run(ctx context.Context, peers *sync.Map) {
// * the peer's address is added to the set of peers to skip // * the peer's address is added to the set of peers to skip
// * the peer's address is removed from prospective sources, and // * the peer's address is removed from prospective sources, and
// * a go routine is started that reports on the gone channel if the peer is disconnected (or terminated their streamer) // * a go routine is started that reports on the gone channel if the peer is disconnected (or terminated their streamer)
func (f *Fetcher) doRequest(ctx context.Context, gone chan *enode.ID, peersToSkip *sync.Map, sources []*enode.ID, hopCount uint8) ([]*enode.ID, error) { func (f *Fetcher) doRequest(gone chan *enode.ID, peersToSkip *sync.Map, sources []*enode.ID, hopCount uint8) ([]*enode.ID, error) {
var i int var i int
var sourceID *enode.ID var sourceID *enode.ID
var quit chan struct{} var quit chan struct{}
@ -283,7 +292,7 @@ func (f *Fetcher) doRequest(ctx context.Context, gone chan *enode.ID, peersToSki
for i = 0; i < len(sources); i++ { for i = 0; i < len(sources); i++ {
req.Source = sources[i] req.Source = sources[i]
var err error var err error
sourceID, quit, err = f.protoRequestFunc(ctx, req) sourceID, quit, err = f.protoRequestFunc(f.ctx, req)
if err == nil { if err == nil {
// remove the peer from known sources // remove the peer from known sources
// Note: we can modify the source although we are looping on it, because we break from the loop immediately // Note: we can modify the source although we are looping on it, because we break from the loop immediately
@ -297,7 +306,7 @@ func (f *Fetcher) doRequest(ctx context.Context, gone chan *enode.ID, peersToSki
if !foundSource { if !foundSource {
req.Source = nil req.Source = nil
var err error var err error
sourceID, quit, err = f.protoRequestFunc(ctx, req) sourceID, quit, err = f.protoRequestFunc(f.ctx, req)
if err != nil { if err != nil {
// if no peers found to request from // if no peers found to request from
return sources, err return sources, err
@ -314,7 +323,7 @@ func (f *Fetcher) doRequest(ctx context.Context, gone chan *enode.ID, peersToSki
select { select {
case <-quit: case <-quit:
gone <- sourceID gone <- sourceID
case <-ctx.Done(): case <-f.ctx.Done():
} }
}() }()
return sources, nil return sources, nil

View file

@ -69,7 +69,11 @@ func (m *mockRequester) doRequest(ctx context.Context, request *Request) (*enode
func TestFetcherSingleRequest(t *testing.T) { func TestFetcherSingleRequest(t *testing.T) {
requester := newMockRequester() requester := newMockRequester()
addr := make([]byte, 32) addr := make([]byte, 32)
fetcher := NewFetcher(addr, requester.doRequest, true)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
fetcher := NewFetcher(ctx, addr, requester.doRequest, true)
peers := []string{"a", "b", "c", "d"} peers := []string{"a", "b", "c", "d"}
peersToSkip := &sync.Map{} peersToSkip := &sync.Map{}
@ -77,13 +81,9 @@ func TestFetcherSingleRequest(t *testing.T) {
peersToSkip.Store(p, time.Now()) peersToSkip.Store(p, time.Now())
} }
ctx, cancel := context.WithCancel(context.Background()) go fetcher.run(peersToSkip)
defer cancel()
go fetcher.run(ctx, peersToSkip) fetcher.Request(0)
rctx := context.Background()
fetcher.Request(rctx, 0)
select { select {
case request := <-requester.requestC: case request := <-requester.requestC:
@ -115,20 +115,19 @@ func TestFetcherSingleRequest(t *testing.T) {
func TestFetcherCancelStopsFetcher(t *testing.T) { func TestFetcherCancelStopsFetcher(t *testing.T) {
requester := newMockRequester() requester := newMockRequester()
addr := make([]byte, 32) addr := make([]byte, 32)
fetcher := NewFetcher(addr, requester.doRequest, true)
peersToSkip := &sync.Map{}
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
fetcher := NewFetcher(ctx, addr, requester.doRequest, true)
peersToSkip := &sync.Map{}
// we start the fetcher, and then we immediately cancel the context // we start the fetcher, and then we immediately cancel the context
go fetcher.run(ctx, peersToSkip) go fetcher.run(peersToSkip)
cancel() cancel()
rctx, rcancel := context.WithTimeout(ctx, 100*time.Millisecond)
defer rcancel()
// we call Request with an active context // we call Request with an active context
fetcher.Request(rctx, 0) fetcher.Request(0)
// fetcher should not initiate request, we can only check by waiting a bit and making sure no request is happening // fetcher should not initiate request, we can only check by waiting a bit and making sure no request is happening
select { select {
@ -140,23 +139,23 @@ func TestFetcherCancelStopsFetcher(t *testing.T) {
// TestFetchCancelStopsRequest tests that calling a Request function with a cancelled context does not initiate a request // TestFetchCancelStopsRequest tests that calling a Request function with a cancelled context does not initiate a request
func TestFetcherCancelStopsRequest(t *testing.T) { func TestFetcherCancelStopsRequest(t *testing.T) {
t.Skip("since context is now per fetcher, this test is likely redundant")
requester := newMockRequester(100 * time.Millisecond) requester := newMockRequester(100 * time.Millisecond)
addr := make([]byte, 32) addr := make([]byte, 32)
fetcher := NewFetcher(addr, requester.doRequest, true)
peersToSkip := &sync.Map{}
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
// we start the fetcher with an active context fetcher := NewFetcher(ctx, addr, requester.doRequest, true)
go fetcher.run(ctx, peersToSkip)
rctx, rcancel := context.WithCancel(context.Background()) peersToSkip := &sync.Map{}
rcancel()
// we start the fetcher with an active context
go fetcher.run(peersToSkip)
// we call Request with a cancelled context // we call Request with a cancelled context
fetcher.Request(rctx, 0) fetcher.Request(0)
// fetcher should not initiate request, we can only check by waiting a bit and making sure no request is happening // fetcher should not initiate request, we can only check by waiting a bit and making sure no request is happening
select { select {
@ -166,8 +165,7 @@ func TestFetcherCancelStopsRequest(t *testing.T) {
} }
// if there is another Request with active context, there should be a request, because the fetcher itself is not cancelled // if there is another Request with active context, there should be a request, because the fetcher itself is not cancelled
rctx = context.Background() fetcher.Request(0)
fetcher.Request(rctx, 0)
select { select {
case <-requester.requestC: case <-requester.requestC:
@ -182,19 +180,19 @@ func TestFetcherCancelStopsRequest(t *testing.T) {
func TestFetcherOfferUsesSource(t *testing.T) { func TestFetcherOfferUsesSource(t *testing.T) {
requester := newMockRequester(100 * time.Millisecond) requester := newMockRequester(100 * time.Millisecond)
addr := make([]byte, 32) addr := make([]byte, 32)
fetcher := NewFetcher(addr, requester.doRequest, true)
peersToSkip := &sync.Map{}
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
// start the fetcher fetcher := NewFetcher(ctx, addr, requester.doRequest, true)
go fetcher.run(ctx, peersToSkip)
peersToSkip := &sync.Map{}
// start the fetcher
go fetcher.run(peersToSkip)
rctx := context.Background()
// call the Offer function with the source peer // call the Offer function with the source peer
fetcher.Offer(rctx, &sourcePeerID) fetcher.Offer(&sourcePeerID)
// fetcher should not initiate request // fetcher should not initiate request
select { select {
@ -204,8 +202,7 @@ func TestFetcherOfferUsesSource(t *testing.T) {
} }
// call Request after the Offer // call Request after the Offer
rctx = context.Background() fetcher.Request(0)
fetcher.Request(rctx, 0)
// there should be exactly 1 request coming from fetcher // there should be exactly 1 request coming from fetcher
var request *Request var request *Request
@ -234,19 +231,19 @@ func TestFetcherOfferUsesSource(t *testing.T) {
func TestFetcherOfferAfterRequestUsesSourceFromContext(t *testing.T) { func TestFetcherOfferAfterRequestUsesSourceFromContext(t *testing.T) {
requester := newMockRequester(100 * time.Millisecond) requester := newMockRequester(100 * time.Millisecond)
addr := make([]byte, 32) addr := make([]byte, 32)
fetcher := NewFetcher(addr, requester.doRequest, true)
peersToSkip := &sync.Map{}
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
fetcher := NewFetcher(ctx, addr, requester.doRequest, true)
peersToSkip := &sync.Map{}
// start the fetcher // start the fetcher
go fetcher.run(ctx, peersToSkip) go fetcher.run(peersToSkip)
// call Request first // call Request first
rctx := context.Background() fetcher.Request(0)
fetcher.Request(rctx, 0)
// there should be a request coming from fetcher // there should be a request coming from fetcher
var request *Request var request *Request
@ -260,7 +257,7 @@ func TestFetcherOfferAfterRequestUsesSourceFromContext(t *testing.T) {
} }
// after the Request call Offer // after the Request call Offer
fetcher.Offer(context.Background(), &sourcePeerID) fetcher.Offer(&sourcePeerID)
// there should be a request coming from fetcher // there should be a request coming from fetcher
select { select {
@ -283,21 +280,21 @@ func TestFetcherOfferAfterRequestUsesSourceFromContext(t *testing.T) {
func TestFetcherRetryOnTimeout(t *testing.T) { func TestFetcherRetryOnTimeout(t *testing.T) {
requester := newMockRequester() requester := newMockRequester()
addr := make([]byte, 32) addr := make([]byte, 32)
fetcher := NewFetcher(addr, requester.doRequest, true)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
fetcher := NewFetcher(ctx, addr, requester.doRequest, true)
// set searchTimeOut to low value so the test is quicker // set searchTimeOut to low value so the test is quicker
fetcher.searchTimeout = 250 * time.Millisecond fetcher.searchTimeout = 250 * time.Millisecond
peersToSkip := &sync.Map{} peersToSkip := &sync.Map{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// start the fetcher // start the fetcher
go fetcher.run(ctx, peersToSkip) go fetcher.run(peersToSkip)
// call the fetch function with an active context // call the fetch function with an active context
rctx := context.Background() fetcher.Request(0)
fetcher.Request(rctx, 0)
// after 100ms the first request should be initiated // after 100ms the first request should be initiated
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
@ -339,7 +336,7 @@ func TestFetcherFactory(t *testing.T) {
fetcher := fetcherFactory.New(context.Background(), addr, peersToSkip) fetcher := fetcherFactory.New(context.Background(), addr, peersToSkip)
fetcher.Request(context.Background(), 0) fetcher.Request(0)
// check if the created fetchFunction really starts a fetcher and initiates a request // check if the created fetchFunction really starts a fetcher and initiates a request
select { select {
@ -353,7 +350,11 @@ func TestFetcherFactory(t *testing.T) {
func TestFetcherRequestQuitRetriesRequest(t *testing.T) { func TestFetcherRequestQuitRetriesRequest(t *testing.T) {
requester := newMockRequester() requester := newMockRequester()
addr := make([]byte, 32) addr := make([]byte, 32)
fetcher := NewFetcher(addr, requester.doRequest, true)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
fetcher := NewFetcher(ctx, addr, requester.doRequest, true)
// make sure the searchTimeout is long so it is sure the request is not // make sure the searchTimeout is long so it is sure the request is not
// retried because of timeout // retried because of timeout
@ -361,13 +362,9 @@ func TestFetcherRequestQuitRetriesRequest(t *testing.T) {
peersToSkip := &sync.Map{} peersToSkip := &sync.Map{}
ctx, cancel := context.WithCancel(context.Background()) go fetcher.run(peersToSkip)
defer cancel()
go fetcher.run(ctx, peersToSkip) fetcher.Request(0)
rctx := context.Background()
fetcher.Request(rctx, 0)
select { select {
case <-requester.requestC: case <-requester.requestC:
@ -460,17 +457,15 @@ func TestRequestSkipPeerPermanent(t *testing.T) {
func TestFetcherMaxHopCount(t *testing.T) { func TestFetcherMaxHopCount(t *testing.T) {
requester := newMockRequester() requester := newMockRequester()
addr := make([]byte, 32) addr := make([]byte, 32)
fetcher := NewFetcher(addr, requester.doRequest, true)
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
fetcher := NewFetcher(ctx, addr, requester.doRequest, true)
peersToSkip := &sync.Map{} peersToSkip := &sync.Map{}
go fetcher.run(ctx, peersToSkip) go fetcher.run(peersToSkip)
rctx := context.Background()
fetcher.Request(rctx, maxHopCount)
// if hopCount is already at max no request should be initiated // if hopCount is already at max no request should be initiated
select { select {

View file

@ -40,9 +40,9 @@ func (t *TestHandler) Close() {
type mockNetFetcher struct{} type mockNetFetcher struct{}
func (m *mockNetFetcher) Request(ctx context.Context, hopCount uint8) { func (m *mockNetFetcher) Request(hopCount uint8) {
} }
func (m *mockNetFetcher) Offer(ctx context.Context, source *enode.ID) { func (m *mockNetFetcher) Offer(source *enode.ID) {
} }
func newFakeNetFetcher(context.Context, storage.Address, *sync.Map) storage.NetFetcher { func newFakeNetFetcher(context.Context, storage.Address, *sync.Map) storage.NetFetcher {

View file

@ -34,8 +34,8 @@ type (
) )
type NetFetcher interface { type NetFetcher interface {
Request(ctx context.Context, hopCount uint8) Request(hopCount uint8)
Offer(ctx context.Context, source *enode.ID) Offer(source *enode.ID)
} }
// NetStore is an extension of local storage // NetStore is an extension of local storage
@ -271,9 +271,9 @@ func (f *fetcher) Fetch(rctx context.Context) (Chunk, error) {
if err := source.UnmarshalText([]byte(sourceIF.(string))); err != nil { if err := source.UnmarshalText([]byte(sourceIF.(string))); err != nil {
return nil, err return nil, err
} }
f.netFetcher.Offer(rctx, &source) f.netFetcher.Offer(&source)
} else { } else {
f.netFetcher.Request(rctx, hopCount) f.netFetcher.Request(hopCount)
} }
// wait until either the chunk is delivered or the context is done // wait until either the chunk is delivered or the context is done

View file

@ -46,12 +46,12 @@ type mockNetFetcher struct {
mu sync.Mutex mu sync.Mutex
} }
func (m *mockNetFetcher) Offer(ctx context.Context, source *enode.ID) { func (m *mockNetFetcher) Offer(source *enode.ID) {
m.offerCalled = true m.offerCalled = true
m.sources = append(m.sources, source) m.sources = append(m.sources, source)
} }
func (m *mockNetFetcher) Request(ctx context.Context, hopCount uint8) { func (m *mockNetFetcher) Request(hopCount uint8) {
m.mu.Lock() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()