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 { txn, env: Cow::Borrowed(env) })
47    }
48
49    pub(crate) fn static_read_txn(env: Env) -> Result<RoTxn<'static>> {
50        let mut txn: *mut ffi::MDB_txn = ptr::null_mut();
51
52        unsafe {
53            mdb_result(ffi::mdb_txn_begin(
54                env.env_mut_ptr(),
55                ptr::null_mut(),
56                ffi::MDB_RDONLY,
57                &mut txn,
58            ))?
59        };
60
61        Ok(RoTxn { txn, env: Cow::Owned(env) })
62    }
63
64    pub(crate) fn env_mut_ptr(&self) -> *mut ffi::MDB_env {
65        self.env.env_mut_ptr()
66    }
67
68    /// Commit a read transaction.
69    ///
70    /// Synchronizing some [`Env`] metadata with the global handle.
71    ///
72    /// ## LMDB
73    ///
74    /// It's mandatory in a multi-process setup to call [`RoTxn::commit`] upon read-only database opening.
75    /// After the transaction opening, the database is dropped. The next transaction might return
76    /// `Io(Os { code: 22, kind: InvalidInput, message: "Invalid argument" })` known as `EINVAL`.
77    pub fn commit(mut self) -> Result<()> {
78        let result = unsafe { mdb_result(ffi::mdb_txn_commit(self.txn)) };
79        self.txn = ptr::null_mut();
80        result.map_err(Into::into)
81    }
82}
83
84impl Drop for RoTxn<'_> {
85    fn drop(&mut self) {
86        if !self.txn.is_null() {
87            abort_txn(self.txn);
88        }
89    }
90}
91
92#[cfg(feature = "read-txn-no-tls")]
93unsafe impl Send for RoTxn<'_> {}
94
95fn abort_txn(txn: *mut ffi::MDB_txn) {
96    // Asserts that the transaction hasn't been already committed.
97    assert!(!txn.is_null());
98    unsafe { ffi::mdb_txn_abort(txn) }
99}
100
101/// A read-write transaction.
102///
103/// ## LMDB Limitations
104///
105/// Only one [`RwTxn`] may exist in the same environment at the same time.
106/// If two exist, the new one may wait on a mutex for [`RwTxn::commit`] or [`RwTxn::abort`] to
107/// be called for the first one.
108///
109/// ## OSX/Darwin Limitation
110///
111/// At least 10 transactions can be active at the same time in the same process, since only 10 POSIX semaphores can
112/// be active at the same time for a process. Threads are in the same process space.
113///
114/// If the process crashes in the POSIX semaphore locking section of the transaction, the semaphore will be kept locked.
115///
116/// Note: if your program already use POSIX semaphores, you will have less available for heed/LMDB!
117///
118/// You may increase the limit by editing it **at your own risk**: `/Library/LaunchDaemons/sysctl.plist`
119pub struct RwTxn<'p> {
120    pub(crate) txn: RoTxn<'p>,
121}
122
123impl<'p> RwTxn<'p> {
124    pub(crate) fn new(env: &'p Env) -> Result<RwTxn<'p>> {
125        let mut txn: *mut ffi::MDB_txn = ptr::null_mut();
126
127        unsafe { mdb_result(ffi::mdb_txn_begin(env.env_mut_ptr(), ptr::null_mut(), 0, &mut txn))? };
128
129        Ok(RwTxn { txn: RoTxn { txn, env: Cow::Borrowed(env) } })
130    }
131
132    pub(crate) fn nested(env: &'p Env, parent: &'p mut RwTxn) -> Result<RwTxn<'p>> {
133        let mut txn: *mut ffi::MDB_txn = ptr::null_mut();
134        let parent_ptr: *mut ffi::MDB_txn = parent.txn.txn;
135
136        unsafe { mdb_result(ffi::mdb_txn_begin(env.env_mut_ptr(), parent_ptr, 0, &mut txn))? };
137
138        Ok(RwTxn { txn: RoTxn { txn, env: Cow::Borrowed(env) } })
139    }
140
141    pub(crate) fn env_mut_ptr(&self) -> *mut ffi::MDB_env {
142        self.txn.env.env_mut_ptr()
143    }
144
145    /// Commit all the operations of a transaction into the database.
146    /// The transaction is reset.
147    pub fn commit(mut self) -> Result<()> {
148        let result = unsafe { mdb_result(ffi::mdb_txn_commit(self.txn.txn)) };
149        self.txn.txn = ptr::null_mut();
150        result.map_err(Into::into)
151    }
152
153    /// Abandon all the operations of the transaction instead of saving them.
154    /// The transaction is reset.
155    pub fn abort(mut self) {
156        abort_txn(self.txn.txn);
157        self.txn.txn = ptr::null_mut();
158    }
159}
160
161impl<'p> Deref for RwTxn<'p> {
162    type Target = RoTxn<'p>;
163
164    fn deref(&self) -> &Self::Target {
165        &self.txn
166    }
167}