Skip to main content

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}