Skip to content

Commit 7b83a49

Browse files
graydonleighmcculloch
authored andcommitted
archivist: range and scan fixes (#1735)
Fix a couple bugs with ranges in stellar-archivist. This just makes it behave more like it's documented to / how users would expect: - Treat the range values as inclusive rather than excluding the high endpoint. - Don't clobber the destination pointer when a mirror range doesn't end on that value. - Fix off-by-one in Range.Size() method - Tighten listing-driven scan precision, skip out-of-range checkpoints - Avoid listing all buckets when scanning a subrange. - Scan all buckets and emit error if missing root HAS
1 parent 9767ff8 commit 7b83a49

File tree

5 files changed

+153
-6
lines changed

5 files changed

+153
-6
lines changed

support/historyarchive/archive_test.go

+77
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,46 @@ func TestScan(t *testing.T) {
155155
GetRandomPopulatedArchive().Scan(opts)
156156
}
157157

158+
func TestScanSize(t *testing.T) {
159+
defer cleanup()
160+
opts := testOptions()
161+
arch := GetRandomPopulatedArchive()
162+
arch.Scan(opts)
163+
assert.Equal(t, opts.Range.Size(),
164+
len(arch.checkpointFiles["history"]))
165+
}
166+
167+
func TestScanSizeSubrange(t *testing.T) {
168+
defer cleanup()
169+
opts := testOptions()
170+
arch := GetRandomPopulatedArchive()
171+
opts.Range.Low = NextCheckpoint(opts.Range.Low)
172+
opts.Range.High = PrevCheckpoint(opts.Range.High)
173+
arch.Scan(opts)
174+
assert.Equal(t, opts.Range.Size(),
175+
len(arch.checkpointFiles["history"]))
176+
}
177+
178+
func TestScanSizeSubrangeFewBuckets(t *testing.T) {
179+
defer cleanup()
180+
opts := testOptions()
181+
arch := GetRandomPopulatedArchive()
182+
opts.Range.Low = 0x1ff
183+
opts.Range.High = 0x1ff
184+
arch.Scan(opts)
185+
// We should only scan one checkpoint worth of buckets.
186+
assert.Less(t, len(arch.allBuckets), 40)
187+
}
188+
189+
func TestScanSizeSubrangeAllBuckets(t *testing.T) {
190+
defer cleanup()
191+
opts := testOptions()
192+
arch := GetRandomPopulatedArchive()
193+
arch.Scan(opts)
194+
// We should scan all checkpoints worth of buckets.
195+
assert.Less(t, 300, len(arch.allBuckets))
196+
}
197+
158198
func countMissing(arch *Archive, opts *CommandOptions) int {
159199
n := 0
160200
arch.Scan(opts)
@@ -200,6 +240,43 @@ func TestMirrorThenRepair(t *testing.T) {
200240
assert.Equal(t, 0, countMissing(dst, opts))
201241
}
202242

243+
func (a *Archive) MustGetRootHAS() HistoryArchiveState {
244+
has, e := a.GetRootHAS()
245+
if e != nil {
246+
panic("failed to get root HAS")
247+
}
248+
return has
249+
}
250+
251+
func TestMirrorSubsetDoPointerUpdate(t *testing.T) {
252+
defer cleanup()
253+
opts := testOptions()
254+
src := GetRandomPopulatedArchive()
255+
dst := GetTestArchive()
256+
Mirror(src, dst, opts)
257+
oldHigh := opts.Range.High
258+
assert.Equal(t, oldHigh, dst.MustGetRootHAS().CurrentLedger)
259+
opts.Range.High = NextCheckpoint(oldHigh)
260+
src.AddRandomCheckpoint(opts.Range.High)
261+
Mirror(src, dst, opts)
262+
assert.Equal(t, opts.Range.High, dst.MustGetRootHAS().CurrentLedger)
263+
}
264+
265+
func TestMirrorSubsetNoPointerUpdate(t *testing.T) {
266+
defer cleanup()
267+
opts := testOptions()
268+
src := GetRandomPopulatedArchive()
269+
dst := GetTestArchive()
270+
Mirror(src, dst, opts)
271+
oldHigh := opts.Range.High
272+
assert.Equal(t, oldHigh, dst.MustGetRootHAS().CurrentLedger)
273+
src.AddRandomCheckpoint(NextCheckpoint(oldHigh))
274+
opts.Range.Low = 0x7f
275+
opts.Range.High = 0xff
276+
Mirror(src, dst, opts)
277+
assert.Equal(t, oldHigh, dst.MustGetRootHAS().CurrentLedger)
278+
}
279+
203280
func TestDryRunNoRepair(t *testing.T) {
204281
defer cleanup()
205282
opts := testOptions()

support/historyarchive/mirror.go

+14-2
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,20 @@ func Mirror(src *Archive, dst *Archive, opts *CommandOptions) error {
9393
log.Printf("copied %d checkpoints, %d buckets, range %s",
9494
opts.Range.Size(), len(bucketFetch), opts.Range)
9595
close(tick)
96-
e = dst.PutRootHAS(rootHAS, opts)
97-
errs += noteError(e)
96+
if rootHAS.CurrentLedger == opts.Range.High {
97+
log.Printf("updating destination archive current-ledger pointer to 0x%8.8x",
98+
rootHAS.CurrentLedger)
99+
e = dst.PutRootHAS(rootHAS, opts)
100+
errs += noteError(e)
101+
} else {
102+
dstHAS, e := dst.GetRootHAS()
103+
if e != nil {
104+
errs += noteError(e)
105+
} else {
106+
log.Printf("leaving destination archive current-ledger pointer at 0x%8.8x",
107+
dstHAS.CurrentLedger)
108+
}
109+
}
98110
if errs != 0 {
99111
return fmt.Errorf("%d errors while mirroring", errs)
100112
}

support/historyarchive/range.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func (r Range) String() string {
6767
func (r Range) Checkpoints() chan uint32 {
6868
ch := make(chan uint32)
6969
go func() {
70-
for i := uint64(r.Low); i < uint64(r.High); i += uint64(CheckpointFreq) {
70+
for i := uint64(r.Low); i <= uint64(r.High); i += uint64(CheckpointFreq) {
7171
ch <- uint32(i)
7272
}
7373
close(ch)
@@ -76,7 +76,7 @@ func (r Range) Checkpoints() chan uint32 {
7676
}
7777

7878
func (r Range) Size() int {
79-
return int(r.High-r.Low) / int(CheckpointFreq)
79+
return 1 + (int(r.High-r.Low) / int(CheckpointFreq))
8080
}
8181

8282
func (r Range) collapsedString() string {

support/historyarchive/range_test.go

+44
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,50 @@ import (
1010
"github.com/stretchr/testify/assert"
1111
)
1212

13+
func (r Range) allCheckpoints() []uint32 {
14+
var s []uint32
15+
for chk := range r.Checkpoints() {
16+
s = append(s, chk)
17+
}
18+
return s
19+
}
20+
21+
func TestRangeSize(t *testing.T) {
22+
assert.Equal(t, 1,
23+
MakeRange(0x3f, 0x3f).Size())
24+
25+
assert.Equal(t, 2,
26+
MakeRange(0x3f, 0x7f).Size())
27+
28+
assert.Equal(t, 2,
29+
MakeRange(0, 100).Size())
30+
31+
assert.Equal(t, 4,
32+
MakeRange(0xff3f, 0xffff).Size())
33+
}
34+
35+
func TestRangeEnumeration(t *testing.T) {
36+
assert.Equal(t,
37+
[]uint32{0x3f, 0x7f},
38+
MakeRange(0x3f, 0x7f).allCheckpoints())
39+
40+
assert.Equal(t,
41+
[]uint32{0x3f},
42+
MakeRange(0x3f, 0x3f).allCheckpoints())
43+
44+
assert.Equal(t,
45+
[]uint32{0x3f},
46+
MakeRange(0, 0).allCheckpoints())
47+
48+
assert.Equal(t,
49+
[]uint32{0x3f, 0x7f},
50+
MakeRange(0, 0x40).allCheckpoints())
51+
52+
assert.Equal(t,
53+
[]uint32{0xff},
54+
MakeRange(0xff, 0x40).allCheckpoints())
55+
}
56+
1357
func TestFmtRangeList(t *testing.T) {
1458

1559
assert.Equal(t,

support/historyarchive/scan.go

+16-2
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ func (arch *Archive) ScanCheckpointsFast(opts *CommandOptions) error {
133133
}
134134
ch, es := arch.ListCategoryCheckpoints(r.category, r.pathprefix)
135135
for n := range ch {
136+
if n < opts.Range.Low || n > opts.Range.High {
137+
continue
138+
}
136139
tick <- true
137140
arch.NoteCheckpointFile(r.category, n, true)
138141
if opts.Verify {
@@ -197,11 +200,22 @@ func (arch *Archive) ScanBuckets(opts *CommandOptions) error {
197200

198201
var errs uint32
199202

200-
// First scan _all_ buckets if we can; if not, we'll do an exists-check
201-
// on each bucket as we go. But this is faster when we can do it.
203+
// First scan _all_ buckets if we can (and should -- if asked to look at the
204+
// entire range); if not, we'll do an exists-check on each bucket as we
205+
// go. But this is faster when we can do it.
202206
doList := arch.backend.CanListFiles()
207+
has, err := arch.GetRootHAS()
208+
if err == nil {
209+
fullRange := MakeRange(0, has.CurrentLedger)
210+
doList = doList && opts.Range.Size() == fullRange.Size()
211+
} else {
212+
log.Print("Error retrieving root archive state, possibly corrupt archive:", err)
213+
log.Print("Continuing and will do an exists-check on each bucket as we go, this will be slower")
214+
}
203215
if doList {
204216
errs += noteError(arch.ScanAllBuckets())
217+
} else {
218+
log.Printf("Scanning buckets for %d checkpoints", opts.Range.Size())
205219
}
206220

207221
// Grab the set of checkpoints we have HASs for, to read references.

0 commit comments

Comments
 (0)