Skip to content

Commit 5efe17f

Browse files
committed
Fixing 64-bit alignment in structs that use sync.atomic
This change fixes segmentation faults on Arm v7. From https://golang.org/pkg/sync/atomic/: > On both ARM and x86-32, it is the caller's responsibility to arrange for 64-bit alignment of 64-bit words accessed atomically. The first word in a variable or in an allocated struct, array, or slice can be relied upon to be 64-bit aligned. Had to modify a couple of tests to get them to pass. Fixes #311.
1 parent 53e623c commit 5efe17f

File tree

5 files changed

+72
-18
lines changed

5 files changed

+72
-18
lines changed

db_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ func TestGetMore(t *testing.T) {
328328
}
329329
// n := 500000
330330
n := 10000
331-
m := 49 // Increasing would cause ErrTxnTooBig
331+
m := 45 // Increasing would cause ErrTxnTooBig
332332
for i := 0; i < n; i += m {
333333
txn := kv.NewTransaction(true)
334334
for j := i; j < i+m && j < n; j++ {
@@ -432,7 +432,7 @@ func TestExistsMore(t *testing.T) {
432432

433433
// n := 500000
434434
n := 10000
435-
m := 49
435+
m := 45
436436
for i := 0; i < n; i += m {
437437
if (i % 1000) == 0 {
438438
t.Logf("Putting i=%d\n", i)

levels.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,13 @@ import (
3232
)
3333

3434
type levelsController struct {
35-
elog trace.EventLog
35+
nextFileID uint64 // Atomic
36+
elog trace.EventLog
3637

3738
// The following are initialized once and const.
3839
levels []*levelHandler
3940
kv *DB
4041

41-
nextFileID uint64 // Atomic
42-
4342
cstatus compactStatus
4443
}
4544

skl/skl.go

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,20 @@ const (
5050
const MaxNodeSize = int(unsafe.Sizeof(node{}))
5151

5252
type node struct {
53+
// Multiple parts of the value are encoded as a single uint64 so that it
54+
// can be atomically loaded and stored:
55+
// value offset: uint32 (bits 0-31)
56+
// value size : uint16 (bits 32-47)
57+
// 12 bytes are allocated to ensure 8 byte alignment also on 32bit systems.
58+
value [12]byte
59+
5360
// A byte slice is 24 bytes. We are trying to save space here.
5461
keyOffset uint32 // Immutable. No need to lock to access key.
5562
keySize uint16 // Immutable. No need to lock to access key.
5663

5764
// Height of the tower.
5865
height uint16
5966

60-
// Multiple parts of the value are encoded as a single uint64 so that it
61-
// can be atomically loaded and stored:
62-
// value offset: uint32 (bits 0-31)
63-
// value size : uint16 (bits 32-47)
64-
value uint64
65-
6667
// Most nodes do not need to use the full height of the tower, since the
6768
// probability of each successive level decreases exponentially. Because
6869
// these elements are never accessed, they do not need to be allocated.
@@ -108,7 +109,7 @@ func newNode(arena *Arena, key []byte, v y.ValueStruct, height int) *node {
108109
node.keyOffset = arena.putKey(key)
109110
node.keySize = uint16(len(key))
110111
node.height = uint16(height)
111-
node.value = encodeValue(arena.putVal(v), v.EncodedSize())
112+
*node.value64BitAlignedPtr() = encodeValue(arena.putVal(v), v.EncodedSize())
112113
return node
113114
}
114115

@@ -134,8 +135,15 @@ func NewSkiplist(arenaSize int64) *Skiplist {
134135
}
135136
}
136137

138+
func (s *node) value64BitAlignedPtr() *uint64 {
139+
if uintptr(unsafe.Pointer(&s.value))%8 == 0 {
140+
return (*uint64)(unsafe.Pointer(&s.value))
141+
}
142+
return (*uint64)(unsafe.Pointer(&s.value[4]))
143+
}
144+
137145
func (s *node) getValueOffset() (uint32, uint16) {
138-
value := atomic.LoadUint64(&s.value)
146+
value := atomic.LoadUint64(s.value64BitAlignedPtr())
139147
return decodeValue(value)
140148
}
141149

@@ -146,7 +154,7 @@ func (s *node) key(arena *Arena) []byte {
146154
func (s *node) setValue(arena *Arena, v y.ValueStruct) {
147155
valOffset := arena.putVal(v)
148156
value := encodeValue(valOffset, v.EncodedSize())
149-
atomic.StoreUint64(&s.value, value)
157+
atomic.StoreUint64(s.value64BitAlignedPtr(), value)
150158
}
151159

152160
func (s *node) getNextOffset(h int) uint32 {

transaction.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,11 @@ func (u *uint64Heap) Pop() interface{} {
4646
}
4747

4848
type oracle struct {
49+
curRead uint64 // Managed by the mutex.
50+
refCount int64
4951
isManaged bool // Does not change value, so no locking required.
5052

5153
sync.Mutex
52-
curRead uint64
5354
nextCommit uint64
5455

5556
// These two structures are used to figure out when a commit is done. The minimum done commit is
@@ -59,8 +60,7 @@ type oracle struct {
5960

6061
// commits stores a key fingerprint and latest commit counter for it.
6162
// refCount is used to clear out commits map to avoid a memory blowup.
62-
commits map[uint64]uint64
63-
refCount int64
63+
commits map[uint64]uint64
6464
}
6565

6666
func (o *oracle) addRef() {
@@ -192,7 +192,7 @@ type Txn struct {
192192
func (txn *Txn) checkSize(e *entry) error {
193193
count := txn.count + 1
194194
// Extra bytes for version in key.
195-
size := txn.size + int64(e.estimateSize(txn.db.opt.ValueThreshold)) + 10
195+
size := txn.size + int64(e.estimateSize(txn.db.opt.ValueThreshold)) + 10
196196
if count >= txn.db.opt.maxBatchCount || size >= txn.db.opt.maxBatchSize {
197197
return ErrTxnTooBig
198198
}

transaction_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"strconv"
2424
"testing"
2525

26+
"github.com/dgraph-io/badger/options"
2627
"github.com/dgraph-io/badger/y"
2728

2829
"github.com/stretchr/testify/require"
@@ -617,3 +618,49 @@ func TestManagedDB(t *testing.T) {
617618
}
618619
txn.Discard()
619620
}
621+
622+
func TestArmV7Issue311Fix(t *testing.T) {
623+
dir, err := ioutil.TempDir("", "")
624+
if err != nil {
625+
t.Fatal(err)
626+
}
627+
defer os.RemoveAll(dir)
628+
629+
config := DefaultOptions
630+
config.TableLoadingMode = options.MemoryMap
631+
config.ValueLogFileSize = 16 << 20
632+
config.LevelOneSize = 8 << 20
633+
config.MaxTableSize = 2 << 20
634+
config.Dir = dir
635+
config.ValueDir = dir
636+
config.SyncWrites = false
637+
638+
db, err := Open(config)
639+
if err != nil {
640+
t.Fatalf("cannot open db at location %s: %v", dir, err)
641+
}
642+
643+
err = db.View(func(txn *Txn) error { return nil })
644+
if err != nil {
645+
t.Fatal(err)
646+
}
647+
648+
err = db.Update(func(txn *Txn) error {
649+
return txn.Set([]byte{0x11}, []byte{0x22})
650+
})
651+
if err != nil {
652+
t.Fatal(err)
653+
}
654+
655+
err = db.Update(func(txn *Txn) error {
656+
return txn.Set([]byte{0x11}, []byte{0x22})
657+
})
658+
659+
if err != nil {
660+
t.Fatal(err)
661+
}
662+
663+
if err = db.Close(); err != nil {
664+
t.Fatal(err)
665+
}
666+
}

0 commit comments

Comments
 (0)