Skip to content

Commit bc832d0

Browse files
authored
fix(syncer): don't Head recursively (#248)
We don't need recursion here. If recent head request fails, all the waiters should simply return any known subjective head, like with other error cases. Closes #247
1 parent 3cf61a8 commit bc832d0

File tree

2 files changed

+51
-6
lines changed

2 files changed

+51
-6
lines changed

sync/sync_head.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,8 @@ func (s *Syncer[H]) Head(ctx context.Context, _ ...header.HeadOption[H]) (H, err
3131

3232
// single-flight protection ensure only one Head is requested at the time
3333
if !s.getter.Lock() {
34-
// means that other routine held the lock and set the subjective head for us,
35-
// so just recursively get it
36-
return s.Head(ctx)
34+
// means that other routine held the lock and set the subjective head
35+
return s.subjectiveHead(ctx)
3736
}
3837
defer s.getter.Unlock()
3938

sync/sync_head_test.go

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package sync
22

33
import (
44
"context"
5+
"fmt"
56
"sync"
67
"sync/atomic"
78
"testing"
@@ -15,6 +16,35 @@ import (
1516
"github.com/celestiaorg/go-header/local"
1617
)
1718

19+
func TestSyncer_HeadConcurrencyError(t *testing.T) {
20+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
21+
t.Cleanup(cancel)
22+
23+
suite := headertest.NewTestSuite(t)
24+
store := headertest.NewStore[*headertest.DummyHeader](t, suite, 1)
25+
26+
syncer, err := NewSyncer[*headertest.DummyHeader](
27+
errorGetter{},
28+
store,
29+
headertest.NewDummySubscriber(),
30+
WithRecencyThreshold(time.Nanosecond), // force recent requests
31+
)
32+
require.NoError(t, err)
33+
34+
var wg sync.WaitGroup
35+
for i := 0; i < 1000; i++ {
36+
wg.Add(1)
37+
go func() {
38+
defer wg.Done()
39+
40+
_, err := syncer.Head(ctx)
41+
require.NoError(t, err)
42+
}()
43+
}
44+
45+
wg.Wait()
46+
}
47+
1848
func TestSyncer_incomingNetworkHeadRaces(t *testing.T) {
1949
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
2050
t.Cleanup(cancel)
@@ -121,12 +151,10 @@ func (t *wrappedGetter) Head(ctx context.Context, options ...header.HeadOption[*
121151
}
122152

123153
func (t *wrappedGetter) Get(ctx context.Context, hash header.Hash) (*headertest.DummyHeader, error) {
124-
// TODO implement me
125154
panic("implement me")
126155
}
127156

128157
func (t *wrappedGetter) GetByHeight(ctx context.Context, u uint64) (*headertest.DummyHeader, error) {
129-
// TODO implement me
130158
panic("implement me")
131159
}
132160

@@ -135,6 +163,24 @@ func (t *wrappedGetter) GetRangeByHeight(
135163
from *headertest.DummyHeader,
136164
to uint64,
137165
) ([]*headertest.DummyHeader, error) {
138-
// TODO implement me
166+
panic("implement me")
167+
}
168+
169+
type errorGetter struct{}
170+
171+
func (e errorGetter) Head(ctx context.Context, h ...header.HeadOption[*headertest.DummyHeader]) (*headertest.DummyHeader, error) {
172+
time.Sleep(time.Millisecond * 1)
173+
return nil, fmt.Errorf("error")
174+
}
175+
176+
func (e errorGetter) Get(ctx context.Context, hash header.Hash) (*headertest.DummyHeader, error) {
177+
panic("implement me")
178+
}
179+
180+
func (e errorGetter) GetByHeight(ctx context.Context, u uint64) (*headertest.DummyHeader, error) {
181+
panic("implement me")
182+
}
183+
184+
func (e errorGetter) GetRangeByHeight(ctx context.Context, from *headertest.DummyHeader, to uint64) ([]*headertest.DummyHeader, error) {
139185
panic("implement me")
140186
}

0 commit comments

Comments
 (0)