Skip to content

Commit 28e8840

Browse files
authored
Merge pull request #29 from wojas/cgo-txn-new-fix
Fix: allocate C memory for MDB_val in readonly Txn
2 parents 2b2b787 + b706dbc commit 28e8840

File tree

3 files changed

+91
-9
lines changed

3 files changed

+91
-9
lines changed

lmdb/env.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,13 @@ func (env *Env) FD() (uintptr, error) {
113113
// to avoid constant value overflow errors at compile time.
114114
const fdInvalid = ^uintptr(0)
115115

116-
mf := new(C.mdb_filehandle_t)
117-
ret := C.mdb_env_get_fd(env._env, mf)
116+
var mf C.mdb_filehandle_t
117+
ret := C.mdb_env_get_fd(env._env, &mf)
118118
err := operrno("mdb_env_get_fd", ret)
119119
if err != nil {
120120
return 0, err
121121
}
122-
fd := uintptr(*mf)
122+
fd := uintptr(mf)
123123

124124
if fd == fdInvalid {
125125
return 0, errNotOpen

lmdb/txn.go

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ type Txn struct {
6363
// reset/renewed
6464
id uintptr
6565

66+
// Pointer to scratch space for key and val in readonly transactions
67+
cbuf unsafe.Pointer
68+
6669
env *Env
6770
_txn *C.MDB_txn
6871
key *C.MDB_val
@@ -87,12 +90,20 @@ func beginTxn(env *Env, parent *Txn, flags uint) (*Txn, error) {
8790
txn.key = env.ckey
8891
txn.val = env.cval
8992
} else {
90-
// It is not easy to share C.MDB_val values in this scenario unless
91-
// there is a synchronized pool involved, which will increase
92-
// overhead. Further, allocating these values with C will add
93-
// overhead both here and when the values are freed.
94-
txn.key = new(C.MDB_val)
95-
txn.val = new(C.MDB_val)
93+
// Readonly transaction.
94+
// We cannot share global C.MDB_val values in this scenario, because
95+
// there can be multiple simultaneous read transactions.
96+
// Allocate C memory for two values in one call.
97+
// This is freed in clearTxn(), which is always called at the end
98+
// of a transaction through Commit() or Abort().
99+
if C.sizeof_MDB_val == 0 {
100+
panic("zero C.sizeof_MDB_val") // should never happen
101+
}
102+
txn.cbuf = C.malloc(2 * C.sizeof_MDB_val)
103+
txn.key = (*C.MDB_val)(txn.cbuf)
104+
txn.val = (*C.MDB_val)(unsafe.Pointer(
105+
uintptr(txn.cbuf) + uintptr(C.sizeof_MDB_val),
106+
))
96107
}
97108
} else {
98109
// Because parent Txn objects cannot be used while a sub-Txn is active
@@ -240,6 +251,14 @@ func (txn *Txn) clearTxn() {
240251
// sure the value returned for an invalid Txn is more or less consistent
241252
// for people familiar with the C semantics.
242253
txn.resetID()
254+
255+
// Release C allocated buffer, if used
256+
if txn.cbuf != nil {
257+
txn.key = nil
258+
txn.val = nil
259+
C.free(txn.cbuf)
260+
txn.cbuf = nil
261+
}
243262
}
244263

245264
// resetID has to be called anytime the value of Txn.getID() may change

lmdb/txn_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,69 @@ func TestTxn_Reset_writeTxn(t *testing.T) {
918918
}
919919
}
920920

921+
// This test demonstrates that in a readonly transaction C memory is allocated
922+
// for the values, and freed during a Reset.
923+
func TestTxn_Reset_readonly_C_free(t *testing.T) {
924+
env := setup(t)
925+
path, err := env.Path()
926+
if err != nil {
927+
env.Close()
928+
t.Error(err)
929+
return
930+
}
931+
defer os.RemoveAll(path)
932+
defer env.Close()
933+
934+
runtime.LockOSThread()
935+
defer runtime.UnlockOSThread()
936+
937+
txn, err := env.BeginTxn(nil, Readonly)
938+
if err != nil {
939+
t.Error(err)
940+
return
941+
}
942+
defer txn.Abort()
943+
944+
// Since this is a readonly transaction, the global Env key/val cannot be
945+
// reused and new C memory must be allocated.
946+
if txn.key == env.ckey || txn.val == env.cval {
947+
t.Error("env.ckey and cval must not be used in a readonly Txn")
948+
}
949+
if txn.cbuf == nil {
950+
t.Error("cbuf expected to not be nil when opening a readonly Txn")
951+
}
952+
953+
// Reset must not free the buffer
954+
txn.Reset()
955+
if txn.cbuf == nil {
956+
t.Error("cbuf must not be nil after Reset")
957+
return
958+
}
959+
960+
// Renew must not free the buffer
961+
err = txn.Renew()
962+
if err != nil {
963+
t.Error(err)
964+
return
965+
}
966+
if txn.cbuf == nil {
967+
t.Error("cbuf must not be nil after Renew")
968+
return
969+
}
970+
971+
// Abort must free the buffer
972+
txn.Abort()
973+
if txn.cbuf != nil {
974+
t.Error("cbuf expected to be nil, C memory not freed")
975+
}
976+
if txn.key != nil || txn.val != nil {
977+
t.Error("key and val expected to be nil after C memory free")
978+
}
979+
980+
// A second Abort must not panic
981+
txn.Abort()
982+
}
983+
921984
func TestTxn_UpdateLocked(t *testing.T) {
922985
env := setup(t)
923986
path, err := env.Path()

0 commit comments

Comments
 (0)