Skip to content

Commit

Permalink
Merge pull request #444 from jmhbnz/backport-failpoints-injection-2
Browse files Browse the repository at this point in the history
[1.3] Backport perform unmap when mlock fails or both meta pages corrupted
  • Loading branch information
ahrtr authored Mar 30, 2023
2 parents ad36005 + 95acc50 commit 4a17732
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 3 deletions.
20 changes: 17 additions & 3 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ func (db *DB) hasSyncedFreelist() bool {

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

Expand Down Expand Up @@ -459,17 +459,27 @@ func (db *DB) mmap(minsz int) error {
}

// Unmap existing data before continuing.
if err := db.munmap(); err != nil {
if err = db.munmap(); err != nil {
return err
}

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

// Perform unmmap on any error to reset all data fields:
// dataref, data, datasz, meta0 and meta1.
defer func() {
if err != nil {
if unmapErr := db.munmap(); unmapErr != nil {
err = fmt.Errorf("%w; rollback unmap also failed: %v", err, unmapErr)
}
}
}()

if db.Mlock {
// Don't allow swapping of data file
if err := db.mlock(fileSize); err != nil {
Expand Down Expand Up @@ -553,13 +563,17 @@ func (db *DB) mmapSize(size int) (int, error) {
}

func (db *DB) munlock(fileSize int) error {
// gofail: var munlockError string
// return errors.New(munlockError)
if err := munlock(db, fileSize); err != nil {
return fmt.Errorf("munlock error: " + err.Error())
}
return nil
}

func (db *DB) mlock(fileSize int) error {
// gofail: var mlockError string
// return errors.New(mlockError)
if err := mlock(db, fileSize); err != nil {
return fmt.Errorf("mlock error: " + err.Error())
}
Expand Down
48 changes: 48 additions & 0 deletions tests/failpoint/db_failpoint_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package failpoint

import (
"fmt"
"path/filepath"
"testing"
"time"

"github.com/stretchr/testify/require"

bolt "go.etcd.io/bbolt"
"go.etcd.io/bbolt/internal/btesting"
gofail "go.etcd.io/gofail/runtime"
)

Expand Down Expand Up @@ -46,3 +48,49 @@ func TestFailpoint_UnmapFail_DbClose(t *testing.T) {
err = db.Close()
require.NoError(t, err)
}

func TestFailpoint_mLockFail(t *testing.T) {
err := gofail.Enable("mlockError", `return("mlock somehow failed")`)
require.NoError(t, err)

f := filepath.Join(t.TempDir(), "db")
_, err = bolt.Open(f, 0666, &bolt.Options{Mlock: true})
require.Error(t, err)
require.ErrorContains(t, err, "mlock somehow failed")

// It should work after disabling the failpoint.
err = gofail.Disable("mlockError")
require.NoError(t, err)

_, err = bolt.Open(f, 0666, &bolt.Options{Mlock: true})
require.NoError(t, err)
}

func TestFailpoint_mLockFail_When_remap(t *testing.T) {
db := btesting.MustCreateDB(t)
db.Mlock = true

err := gofail.Enable("mlockError", `return("mlock somehow failed in allocate")`)
require.NoError(t, err)

err = db.Fill([]byte("data"), 1, 10000,
func(tx int, k int) []byte { return []byte(fmt.Sprintf("%04d", k)) },
func(tx int, k int) []byte { return make([]byte, 100) },
)

require.Error(t, err)
require.ErrorContains(t, err, "mlock somehow failed in allocate")

// It should work after disabling the failpoint.
err = gofail.Disable("mlockError")
require.NoError(t, err)
db.MustClose()
db.MustReopen()

err = db.Fill([]byte("data"), 1, 10000,
func(tx int, k int) []byte { return []byte(fmt.Sprintf("%04d", k)) },
func(tx int, k int) []byte { return make([]byte, 100) },
)

require.NoError(t, err)
}

0 comments on commit 4a17732

Please sign in to comment.