-
Notifications
You must be signed in to change notification settings - Fork 649
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
tests/robustness: add issue72 reproducer
Signed-off-by: Wei Fu <[email protected]>
- Loading branch information
Showing
1 changed file
with
127 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
//go:build linux | ||
|
||
package robustness | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
"time" | ||
|
||
"go.etcd.io/bbolt" | ||
"go.etcd.io/bbolt/internal/btesting" | ||
gofail "go.etcd.io/gofail/runtime" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
// TestIssue72 reproduces issue 72. | ||
// | ||
// When application inserts key/value, that key can be changed by application | ||
// during insertion. bbolt should copy key before seeking and updating. Otherwise, | ||
// some branch node could point to freed page. | ||
// | ||
// REF: https://github.com/etcd-io/bbolt/issues/72 | ||
func TestIssue72(t *testing.T) { | ||
db := btesting.MustCreateDB(t) | ||
|
||
bucketName := []byte(t.Name()) | ||
err := db.Update(func(tx *bbolt.Tx) error { | ||
_, txerr := tx.CreateBucket(bucketName) | ||
return txerr | ||
}) | ||
require.NoError(t, err) | ||
|
||
// The layout is like: | ||
// | ||
// +--+--+--+ | ||
// +------+1 |3 |10+---+ | ||
// | +-++--+--+ | | ||
// | | | | ||
// | | | | ||
// +v-+--+ +v-+--+ +-v+--+--+ | ||
// |1 |2 | |3 |4 | |10|11|12| | ||
// +--+--+ +--+--+ +--+--+--+ | ||
// | ||
err = db.Update(func(tx *bbolt.Tx) error { | ||
bk := tx.Bucket(bucketName) | ||
|
||
for _, id := range []int{1, 2, 3, 4, 10, 11, 12} { | ||
if txerr := bk.Put(idToBytes(id), make([]byte, 1000)); txerr != nil { | ||
return txerr | ||
} | ||
} | ||
return nil | ||
}) | ||
require.NoError(t, err) | ||
|
||
require.NoError(t, gofail.Enable("beforeBucketPut", `sleep(5000)`)) | ||
|
||
// +--+--+--+ | ||
// +------+1 |3 |1 +---+ | ||
// | +-++--+--+ | | ||
// | | | | ||
// | | | | ||
// +v-+--+ +v-+--+ +-v+--+--+--+ | ||
// |1 |2 | |3 |4 | |1 |10|11|12| | ||
// +--+--+ +--+--+ +--+--+--+--+ | ||
// | ||
key := idToBytes(13) | ||
updatedKey := idToBytes(1) | ||
err = db.Update(func(tx *bbolt.Tx) error { | ||
bk := tx.Bucket(bucketName) | ||
|
||
go func() { | ||
time.Sleep(3 * time.Second) | ||
copy(key, updatedKey) | ||
}() | ||
return bk.Put(key, make([]byte, 100)) | ||
}) | ||
require.NoError(t, err) | ||
|
||
require.NoError(t, gofail.Disable("beforeBucketPut")) | ||
|
||
// bbolt inserts 100 into last branch page. Since there are two `1` | ||
// keys in branch, spill operation will update first `1` pointer and | ||
// then last one won't be updated and continues to point to freed page. | ||
// | ||
// | ||
// +--+--+--+ | ||
// +---------------+1 |3 |1 +---------+ | ||
// | +--++-+--+ | | ||
// | | | | ||
// | | | | ||
// | +--+--+ +v-+--+ +-----v-----+ | ||
// | |1 |2 | |3 |4 | |freed page | | ||
// | +--+--+ +--+--+ +-----------+ | ||
// | | ||
// +v-+--+--+--+---+ | ||
// |1 |10|11|12|100| | ||
// +--+--+--+--+---+ | ||
err = db.Update(func(tx *bbolt.Tx) error { | ||
return tx.Bucket(bucketName).Put(idToBytes(100), make([]byte, 100)) | ||
}) | ||
require.NoError(t, err) | ||
|
||
defer func() { | ||
if r := recover(); r != nil { | ||
t.Logf("panic info:\n %v", r) | ||
} | ||
}() | ||
|
||
// Add more keys to ensure branch node to spill. | ||
err = db.Update(func(tx *bbolt.Tx) error { | ||
bk := tx.Bucket(bucketName) | ||
|
||
for _, id := range []int{101, 102, 103, 104, 105} { | ||
if txerr := bk.Put(idToBytes(id), make([]byte, 1000)); txerr != nil { | ||
return txerr | ||
} | ||
} | ||
return nil | ||
}) | ||
require.NoError(t, err) | ||
} | ||
|
||
func idToBytes(id int) []byte { | ||
return []byte(fmt.Sprintf("%010d", id)) | ||
} |