heed/txn.rs
1use std::borrow::Cow;
2use std::ops::Deref;
3use std::ptr;
4
5use crate::mdb::error::mdb_result;
6use crate::mdb::ffi;
7use crate::{Env, Result};
8
9/// A read-only transaction.
10///
11/// ## LMDB Limitations
12///
13/// It's a must to keep read transactions short-lived.
14///
15/// Active Read transactions prevent the reuse of pages freed
16/// by newer write transactions, thus the database can grow quickly.
17///
18/// ## OSX/Darwin Limitation
19///
20/// At least 10 transactions can be active at the same time in the same process, since only 10 POSIX semaphores can
21/// be active at the same time for a process. Threads are in the same process space.
22///
23/// If the process crashes in the POSIX semaphore locking section of the transaction, the semaphore will be kept locked.
24///
25/// Note: if your program already use POSIX semaphores, you will have less available for heed/LMDB!
26///
27/// You may increase the limit by editing it **at your own risk**: `/Library/LaunchDaemons/sysctl.plist`
28pub struct RoTxn<'e> {
29 pub(crate) txn: *mut ffi::MDB_txn,
30 env: Cow<'e, Env>,
31}
32
33impl<'e> RoTxn<'e> {
34 pub(crate) fn new(env: &'e Env) -> Result<RoTxn<'e>> {
35 let mut txn: *mut ffi::MDB_txn = ptr::null_mut();
36
37 unsafe {
38 mdb_result(ffi::mdb_txn_begin(
39 env.env_mut_ptr(),
40 ptr::null_mut(),
41 ffi::MDB_RDONLY,
42 &mut txn,
43 ))?
44 };
45
46 Ok(RoTxn {
47 txn,
48 env: Cow::Borrowed(env),
49 })
50 }
51
52 pub(crate) fn static_read_txn(env: Env) -> Result<RoTxn<'static>> {
53 let mut txn: *mut ffi::MDB_txn = ptr::null_mut();
54
55 unsafe {
56 mdb_result(ffi::mdb_txn_begin(
57 env.env_mut_ptr(),
58 ptr::null_mut(),
59 ffi::MDB_RDONLY,
60 &mut txn,
61 ))?
62 };
63
64 Ok(RoTxn {
65 txn,
66 env: Cow::Owned(env),
67 })
68 }
69
70 pub(crate) fn env_mut_ptr(&self) -> *mut ffi::MDB_env {
71 self.env.env_mut_ptr()
72 }
73
74 /// Return the LMDB transaction identifier for this read snapshot.
75 ///
76 /// Read-only transactions with the same identifier observe the same
77 /// coherent database snapshot. This is useful when independent cursors
78 /// must scan one snapshot concurrently.
79 pub fn id(&self) -> usize {
80 unsafe { ffi::mdb_txn_id(self.txn) }
81 }
82
83 /// Commit a read transaction.
84 ///
85 /// Synchronizing some [`Env`] metadata with the global handle.
86 ///
87 /// ## LMDB
88 ///
89 /// It's mandatory in a multi-process setup to call [`RoTxn::commit`] upon read-only database opening.
90 /// After the transaction opening, the database is dropped. The next transaction might return
91 /// `Io(Os { code: 22, kind: InvalidInput, message: "Invalid argument" })` known as `EINVAL`.
92 pub fn commit(mut self) -> Result<()> {
93 let result = unsafe { mdb_result(ffi::mdb_txn_commit(self.txn)) };
94 self.txn = ptr::null_mut();
95 result.map_err(Into::into)
96 }
97}
98
99impl Drop for RoTxn<'_> {
100 fn drop(&mut self) {
101 if !self.txn.is_null() {
102 abort_txn(self.txn);
103 }
104 }
105}
106
107#[cfg(feature = "read-txn-no-tls")]
108unsafe impl Send for RoTxn<'_> {}
109
110fn abort_txn(txn: *mut ffi::MDB_txn) {
111 // Asserts that the transaction hasn't been already committed.
112 assert!(!txn.is_null());
113 unsafe { ffi::mdb_txn_abort(txn) }
114}
115
116/// A read-write transaction.
117///
118/// ## LMDB Limitations
119///
120/// Only one [`RwTxn`] may exist in the same environment at the same time.
121/// If two exist, the new one may wait on a mutex for [`RwTxn::commit`] or [`RwTxn::abort`] to
122/// be called for the first one.
123///
124/// ## OSX/Darwin Limitation
125///
126/// At least 10 transactions can be active at the same time in the same process, since only 10 POSIX semaphores can
127/// be active at the same time for a process. Threads are in the same process space.
128///
129/// If the process crashes in the POSIX semaphore locking section of the transaction, the semaphore will be kept locked.
130///
131/// Note: if your program already use POSIX semaphores, you will have less available for heed/LMDB!
132///
133/// You may increase the limit by editing it **at your own risk**: `/Library/LaunchDaemons/sysctl.plist`
134pub struct RwTxn<'p> {
135 pub(crate) txn: RoTxn<'p>,
136}
137
138impl<'p> RwTxn<'p> {
139 pub(crate) fn new(env: &'p Env) -> Result<RwTxn<'p>> {
140 let mut txn: *mut ffi::MDB_txn = ptr::null_mut();
141
142 unsafe {
143 mdb_result(ffi::mdb_txn_begin(
144 env.env_mut_ptr(),
145 ptr::null_mut(),
146 0,
147 &mut txn,
148 ))?
149 };
150
151 Ok(RwTxn {
152 txn: RoTxn {
153 txn,
154 env: Cow::Borrowed(env),
155 },
156 })
157 }
158
159 pub(crate) fn nested(env: &'p Env, parent: &'p mut RwTxn) -> Result<RwTxn<'p>> {
160 let mut txn: *mut ffi::MDB_txn = ptr::null_mut();
161 let parent_ptr: *mut ffi::MDB_txn = parent.txn.txn;
162
163 unsafe {
164 mdb_result(ffi::mdb_txn_begin(
165 env.env_mut_ptr(),
166 parent_ptr,
167 0,
168 &mut txn,
169 ))?
170 };
171
172 Ok(RwTxn {
173 txn: RoTxn {
174 txn,
175 env: Cow::Borrowed(env),
176 },
177 })
178 }
179
180 pub(crate) fn env_mut_ptr(&self) -> *mut ffi::MDB_env {
181 self.txn.env.env_mut_ptr()
182 }
183
184 /// Commit all the operations of a transaction into the database.
185 /// The transaction is reset.
186 pub fn commit(mut self) -> Result<()> {
187 let result = unsafe { mdb_result(ffi::mdb_txn_commit(self.txn.txn)) };
188 self.txn.txn = ptr::null_mut();
189 result.map_err(Into::into)
190 }
191
192 /// Abandon all the operations of the transaction instead of saving them.
193 /// The transaction is reset.
194 pub fn abort(mut self) {
195 abort_txn(self.txn.txn);
196 self.txn.txn = ptr::null_mut();
197 }
198}
199
200impl<'p> Deref for RwTxn<'p> {
201 type Target = RoTxn<'p>;
202
203 fn deref(&self) -> &Self::Target {
204 &self.txn
205 }
206}