Skip to content

Commit 99988e4

Browse files
author
Dan Laine
authored
Mark corruptabledb as corrupted upon an iterator error (#1829)
1 parent 2acf6a7 commit 99988e4

File tree

4 files changed

+278
-0
lines changed

4 files changed

+278
-0
lines changed

database/corruptabledb/db.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,34 @@ func (db *Database) NewBatch() database.Batch {
8989
}
9090
}
9191

92+
func (db *Database) NewIterator() database.Iterator {
93+
return &iterator{
94+
Iterator: db.Database.NewIterator(),
95+
db: db,
96+
}
97+
}
98+
99+
func (db *Database) NewIteratorWithStart(start []byte) database.Iterator {
100+
return &iterator{
101+
Iterator: db.Database.NewIteratorWithStart(start),
102+
db: db,
103+
}
104+
}
105+
106+
func (db *Database) NewIteratorWithPrefix(prefix []byte) database.Iterator {
107+
return &iterator{
108+
Iterator: db.Database.NewIteratorWithPrefix(prefix),
109+
db: db,
110+
}
111+
}
112+
113+
func (db *Database) NewIteratorWithStartAndPrefix(start, prefix []byte) database.Iterator {
114+
return &iterator{
115+
Iterator: db.Database.NewIteratorWithStartAndPrefix(start, prefix),
116+
db: db,
117+
}
118+
}
119+
92120
func (db *Database) corrupted() error {
93121
db.errorLock.RLock()
94122
defer db.errorLock.RUnlock()
@@ -127,3 +155,24 @@ func (b *batch) Write() error {
127155
}
128156
return b.db.handleError(b.Batch.Write())
129157
}
158+
159+
type iterator struct {
160+
database.Iterator
161+
db *Database
162+
}
163+
164+
func (it *iterator) Next() bool {
165+
if err := it.db.corrupted(); err != nil {
166+
return false
167+
}
168+
val := it.Iterator.Next()
169+
_ = it.db.handleError(it.Iterator.Error())
170+
return val
171+
}
172+
173+
func (it *iterator) Error() error {
174+
if err := it.db.corrupted(); err != nil {
175+
return err
176+
}
177+
return it.db.handleError(it.Iterator.Error())
178+
}

database/corruptabledb/db_test.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010

1111
"github.com/stretchr/testify/require"
1212

13+
"go.uber.org/mock/gomock"
14+
1315
"github.com/ava-labs/avalanchego/database"
1416
"github.com/ava-labs/avalanchego/database/memdb"
1517
)
@@ -75,3 +77,124 @@ func TestCorruption(t *testing.T) {
7577
})
7678
}
7779
}
80+
81+
func TestIterator(t *testing.T) {
82+
errIter := errors.New("iterator error")
83+
84+
type test struct {
85+
name string
86+
databaseErrBefore error
87+
modifyIter func(*gomock.Controller, *iterator)
88+
op func(*require.Assertions, *iterator)
89+
expectedErr error
90+
}
91+
92+
tests := []test{
93+
{
94+
name: "corrupted database; Next",
95+
databaseErrBefore: errTest,
96+
expectedErr: errTest,
97+
modifyIter: func(ctrl *gomock.Controller, iter *iterator) {},
98+
op: func(require *require.Assertions, iter *iterator) {
99+
require.False(iter.Next())
100+
},
101+
},
102+
{
103+
name: "Next corrupts database",
104+
databaseErrBefore: nil,
105+
expectedErr: errIter,
106+
modifyIter: func(ctrl *gomock.Controller, iter *iterator) {
107+
mockInnerIter := database.NewMockIterator(ctrl)
108+
mockInnerIter.EXPECT().Next().Return(false)
109+
mockInnerIter.EXPECT().Error().Return(errIter)
110+
iter.Iterator = mockInnerIter
111+
},
112+
op: func(require *require.Assertions, iter *iterator) {
113+
require.False(iter.Next())
114+
},
115+
},
116+
{
117+
name: "corrupted database; Error",
118+
databaseErrBefore: errTest,
119+
expectedErr: errTest,
120+
modifyIter: func(ctrl *gomock.Controller, iter *iterator) {},
121+
op: func(require *require.Assertions, iter *iterator) {
122+
err := iter.Error()
123+
require.ErrorIs(err, errTest)
124+
},
125+
},
126+
{
127+
name: "Error corrupts database",
128+
databaseErrBefore: nil,
129+
expectedErr: errIter,
130+
modifyIter: func(ctrl *gomock.Controller, iter *iterator) {
131+
mockInnerIter := database.NewMockIterator(ctrl)
132+
mockInnerIter.EXPECT().Error().Return(errIter)
133+
iter.Iterator = mockInnerIter
134+
},
135+
op: func(require *require.Assertions, iter *iterator) {
136+
err := iter.Error()
137+
require.ErrorIs(err, errIter)
138+
},
139+
},
140+
{
141+
name: "corrupted database; Key",
142+
databaseErrBefore: errTest,
143+
expectedErr: errTest,
144+
modifyIter: func(ctrl *gomock.Controller, iter *iterator) {},
145+
op: func(require *require.Assertions, iter *iterator) {
146+
_ = iter.Key()
147+
},
148+
},
149+
{
150+
name: "corrupted database; Value",
151+
databaseErrBefore: errTest,
152+
expectedErr: errTest,
153+
modifyIter: func(ctrl *gomock.Controller, iter *iterator) {},
154+
op: func(require *require.Assertions, iter *iterator) {
155+
_ = iter.Value()
156+
},
157+
},
158+
{
159+
name: "corrupted database; Release",
160+
databaseErrBefore: errTest,
161+
expectedErr: errTest,
162+
modifyIter: func(ctrl *gomock.Controller, iter *iterator) {},
163+
op: func(require *require.Assertions, iter *iterator) {
164+
iter.Release()
165+
},
166+
},
167+
}
168+
169+
for _, tt := range tests {
170+
t.Run(tt.name, func(t *testing.T) {
171+
require := require.New(t)
172+
ctrl := gomock.NewController(t)
173+
174+
// Make a database
175+
baseDB := memdb.New()
176+
corruptableDB := New(baseDB)
177+
178+
// Put a key-value pair in the database.
179+
require.NoError(corruptableDB.Put([]byte{0}, []byte{1}))
180+
181+
// Mark database as corupted, if applicable
182+
_ = corruptableDB.handleError(tt.databaseErrBefore)
183+
184+
// Make an iterator
185+
iter := &iterator{
186+
Iterator: corruptableDB.NewIterator(),
187+
db: corruptableDB,
188+
}
189+
190+
// Modify the iterator (optional)
191+
tt.modifyIter(ctrl, iter)
192+
193+
// Do an iterator operation
194+
tt.op(require, iter)
195+
196+
err := corruptableDB.corrupted()
197+
require.ErrorIs(err, tt.expectedErr)
198+
})
199+
}
200+
}

database/mock_iterator.go

Lines changed: 105 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/mocks.mockgen.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ github.com/ava-labs/avalanchego/api/server=Server=api/server/mock_server.go
22
github.com/ava-labs/avalanchego/chains/atomic=SharedMemory=chains/atomic/mock_shared_memory.go
33
github.com/ava-labs/avalanchego/codec=Manager=codec/mock_manager.go
44
github.com/ava-labs/avalanchego/database=Batch=database/mock_batch.go
5+
github.com/ava-labs/avalanchego/database=Iterator=database/mock_iterator.go
56
github.com/ava-labs/avalanchego/message=OutboundMessage=message/mock_message.go
67
github.com/ava-labs/avalanchego/message=OutboundMsgBuilder=message/mock_outbound_message_builder.go
78
github.com/ava-labs/avalanchego/network/peer=GossipTracker=network/peer/mock_gossip_tracker.go

0 commit comments

Comments
 (0)