Skip to content

Commit 4a17732

Browse files
authored
Merge pull request #444 from jmhbnz/backport-failpoints-injection-2
[1.3] Backport perform unmap when mlock fails or both meta pages corrupted
2 parents ad36005 + 95acc50 commit 4a17732

2 files changed

Lines changed: 65 additions & 3 deletions

File tree

db.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ func (db *DB) hasSyncedFreelist() bool {
424424

425425
// mmap opens the underlying memory-mapped file and initializes the meta references.
426426
// minsz is the minimum size that the new mmap can be.
427-
func (db *DB) mmap(minsz int) error {
427+
func (db *DB) mmap(minsz int) (err error) {
428428
db.mmaplock.Lock()
429429
defer db.mmaplock.Unlock()
430430

@@ -459,17 +459,27 @@ func (db *DB) mmap(minsz int) error {
459459
}
460460

461461
// Unmap existing data before continuing.
462-
if err := db.munmap(); err != nil {
462+
if err = db.munmap(); err != nil {
463463
return err
464464
}
465465

466466
// Memory-map the data file as a byte slice.
467467
// gofail: var mapError string
468468
// return errors.New(mapError)
469-
if err := mmap(db, size); err != nil {
469+
if err = mmap(db, size); err != nil {
470470
return err
471471
}
472472

473+
// Perform unmmap on any error to reset all data fields:
474+
// dataref, data, datasz, meta0 and meta1.
475+
defer func() {
476+
if err != nil {
477+
if unmapErr := db.munmap(); unmapErr != nil {
478+
err = fmt.Errorf("%w; rollback unmap also failed: %v", err, unmapErr)
479+
}
480+
}
481+
}()
482+
473483
if db.Mlock {
474484
// Don't allow swapping of data file
475485
if err := db.mlock(fileSize); err != nil {
@@ -553,13 +563,17 @@ func (db *DB) mmapSize(size int) (int, error) {
553563
}
554564

555565
func (db *DB) munlock(fileSize int) error {
566+
// gofail: var munlockError string
567+
// return errors.New(munlockError)
556568
if err := munlock(db, fileSize); err != nil {
557569
return fmt.Errorf("munlock error: " + err.Error())
558570
}
559571
return nil
560572
}
561573

562574
func (db *DB) mlock(fileSize int) error {
575+
// gofail: var mlockError string
576+
// return errors.New(mlockError)
563577
if err := mlock(db, fileSize); err != nil {
564578
return fmt.Errorf("mlock error: " + err.Error())
565579
}

tests/failpoint/db_failpoint_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package failpoint
22

33
import (
4+
"fmt"
45
"path/filepath"
56
"testing"
67
"time"
78

89
"github.com/stretchr/testify/require"
910

1011
bolt "go.etcd.io/bbolt"
12+
"go.etcd.io/bbolt/internal/btesting"
1113
gofail "go.etcd.io/gofail/runtime"
1214
)
1315

@@ -46,3 +48,49 @@ func TestFailpoint_UnmapFail_DbClose(t *testing.T) {
4648
err = db.Close()
4749
require.NoError(t, err)
4850
}
51+
52+
func TestFailpoint_mLockFail(t *testing.T) {
53+
err := gofail.Enable("mlockError", `return("mlock somehow failed")`)
54+
require.NoError(t, err)
55+
56+
f := filepath.Join(t.TempDir(), "db")
57+
_, err = bolt.Open(f, 0666, &bolt.Options{Mlock: true})
58+
require.Error(t, err)
59+
require.ErrorContains(t, err, "mlock somehow failed")
60+
61+
// It should work after disabling the failpoint.
62+
err = gofail.Disable("mlockError")
63+
require.NoError(t, err)
64+
65+
_, err = bolt.Open(f, 0666, &bolt.Options{Mlock: true})
66+
require.NoError(t, err)
67+
}
68+
69+
func TestFailpoint_mLockFail_When_remap(t *testing.T) {
70+
db := btesting.MustCreateDB(t)
71+
db.Mlock = true
72+
73+
err := gofail.Enable("mlockError", `return("mlock somehow failed in allocate")`)
74+
require.NoError(t, err)
75+
76+
err = db.Fill([]byte("data"), 1, 10000,
77+
func(tx int, k int) []byte { return []byte(fmt.Sprintf("%04d", k)) },
78+
func(tx int, k int) []byte { return make([]byte, 100) },
79+
)
80+
81+
require.Error(t, err)
82+
require.ErrorContains(t, err, "mlock somehow failed in allocate")
83+
84+
// It should work after disabling the failpoint.
85+
err = gofail.Disable("mlockError")
86+
require.NoError(t, err)
87+
db.MustClose()
88+
db.MustReopen()
89+
90+
err = db.Fill([]byte("data"), 1, 10000,
91+
func(tx int, k int) []byte { return []byte(fmt.Sprintf("%04d", k)) },
92+
func(tx int, k int) []byte { return make([]byte, 100) },
93+
)
94+
95+
require.NoError(t, err)
96+
}

0 commit comments

Comments
 (0)