1use crate::{
2 database::Database,
3 error::{mdbx_result, Error, Result},
4 flags::EnvironmentFlags,
5 transaction::{RO, RW},
6 txn_manager::{TxnManager, TxnManagerMessage, TxnPtr},
7 txn_pool::ReadTxnPool,
8 Mode, SyncMode, Transaction, TransactionKind,
9};
10use byteorder::{ByteOrder, NativeEndian};
11use mem::size_of;
12use std::{
13 ffi::CString,
14 fmt::{self, Debug},
15 mem,
16 ops::{Bound, RangeBounds},
17 path::Path,
18 ptr,
19 sync::{mpsc::sync_channel, Arc},
20 thread::sleep,
21 time::Duration,
22};
23use tracing::warn;
24
25#[cfg(feature = "read-tx-timeouts")]
27const DEFAULT_MAX_READ_TRANSACTION_DURATION: Duration = Duration::from_secs(5 * 60);
28
29#[derive(Clone)]
34pub struct Environment {
35 inner: Arc<EnvironmentInner>,
36}
37
38impl Environment {
39 pub fn builder() -> EnvironmentBuilder {
41 EnvironmentBuilder {
42 flags: EnvironmentFlags::default(),
43 max_readers: None,
44 max_dbs: None,
45 sync_bytes: None,
46 sync_period: None,
47 rp_augment_limit: None,
48 loose_limit: None,
49 dp_reserve_limit: None,
50 txn_dp_limit: None,
51 spill_max_denominator: None,
52 spill_min_denominator: None,
53 geometry: None,
54 log_level: None,
55 kind: Default::default(),
56 handle_slow_readers: None,
57 #[cfg(feature = "read-tx-timeouts")]
58 max_read_transaction_duration: None,
59 }
60 }
61
62 #[inline]
64 pub fn is_write_map(&self) -> bool {
65 self.inner.env_kind.is_write_map()
66 }
67
68 #[inline]
70 pub fn env_kind(&self) -> EnvironmentKind {
71 self.inner.env_kind
72 }
73
74 #[inline]
76 pub fn is_read_write(&self) -> Result<bool> {
77 Ok(!self.is_read_only()?)
78 }
79
80 #[inline]
82 pub fn is_read_only(&self) -> Result<bool> {
83 Ok(matches!(self.info()?.mode(), Mode::ReadOnly))
84 }
85
86 #[inline]
88 pub(crate) fn txn_manager(&self) -> &TxnManager {
89 &self.inner.txn_manager
90 }
91
92 #[cfg(feature = "read-tx-timeouts")]
94 pub fn timed_out_not_aborted_transactions(&self) -> usize {
95 self.inner.txn_manager.timed_out_not_aborted_read_transactions().unwrap_or(0)
96 }
97
98 #[inline]
103 pub fn begin_ro_txn(&self) -> Result<Transaction<RO>> {
104 if let Some(txn_ptr) = self.inner.ro_txn_pool.pop() {
105 return Ok(Transaction::new_from_ptr(self.clone(), txn_ptr));
106 }
107 Transaction::new(self.clone())
108 }
109
110 #[inline]
112 pub(crate) fn ro_txn_pool(&self) -> &ReadTxnPool {
113 &self.inner.ro_txn_pool
114 }
115
116 pub fn begin_rw_txn(&self) -> Result<Transaction<RW>> {
119 let mut warned = false;
120 let txn = loop {
121 let (tx, rx) = sync_channel(0);
122 self.txn_manager().send_message(TxnManagerMessage::Begin {
123 parent: TxnPtr(ptr::null_mut()),
124 flags: RW::OPEN_FLAGS,
125 sender: tx,
126 });
127 let res = rx.recv().unwrap();
128 if matches!(&res, Err(Error::Busy)) {
129 if !warned {
130 warned = true;
131 warn!(target: "libmdbx", "Process stalled, awaiting read-write transaction lock.");
132 }
133 sleep(Duration::from_millis(250));
134 continue
135 }
136
137 break res
138 }?;
139 Ok(Transaction::new_from_ptr(self.clone(), txn.0))
140 }
141
142 #[inline]
147 pub(crate) fn env_ptr(&self) -> *mut ffi::MDBX_env {
148 self.inner.env
149 }
150
151 #[inline]
157 #[doc(hidden)]
158 pub fn with_raw_env_ptr<F, T>(&self, f: F) -> T
159 where
160 F: FnOnce(*mut ffi::MDBX_env) -> T,
161 {
162 f(self.env_ptr())
163 }
164
165 pub fn sync(&self, force: bool) -> Result<bool> {
167 mdbx_result(unsafe { ffi::mdbx_env_sync_ex(self.env_ptr(), force, false) })
168 }
169
170 pub fn stat(&self) -> Result<Stat> {
172 unsafe {
173 let mut stat = Stat::new();
174 mdbx_result(ffi::mdbx_env_stat_ex(
175 self.env_ptr(),
176 ptr::null(),
177 stat.mdbx_stat(),
178 size_of::<Stat>(),
179 ))?;
180 Ok(stat)
181 }
182 }
183
184 pub fn info(&self) -> Result<Info> {
186 unsafe {
187 let mut info = Info(mem::zeroed());
188 mdbx_result(ffi::mdbx_env_info_ex(
189 self.env_ptr(),
190 ptr::null(),
191 &mut info.0,
192 size_of::<Info>(),
193 ))?;
194 Ok(info)
195 }
196 }
197
198 pub fn freelist(&self) -> Result<usize> {
224 let mut freelist: usize = 0;
225 let txn = self.begin_ro_txn()?;
226 let db = Database::freelist_db();
227 let cursor = txn.cursor(db.dbi())?;
228
229 for result in cursor.iter_slices() {
230 let (_key, value) = result?;
231 if value.len() < size_of::<u32>() {
232 return Err(Error::Corrupted)
233 }
234 let s = &value[..size_of::<u32>()];
235 freelist += NativeEndian::read_u32(s) as usize;
236 }
237
238 Ok(freelist)
239 }
240}
241
242struct EnvironmentInner {
247 env: *mut ffi::MDBX_env,
251 env_kind: EnvironmentKind,
253 txn_manager: TxnManager,
255 ro_txn_pool: ReadTxnPool,
257}
258
259impl Drop for EnvironmentInner {
260 fn drop(&mut self) {
261 #[cfg(feature = "read-tx-timeouts")]
262 self.txn_manager.stop_read_transaction_monitor();
263
264 self.ro_txn_pool.drain();
266
267 unsafe {
269 ffi::mdbx_env_close_ex(self.env, false);
270 }
271 }
272}
273
274unsafe impl Send for EnvironmentInner {}
277unsafe impl Sync for EnvironmentInner {}
278
279#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
283pub enum EnvironmentKind {
284 #[default]
286 Default,
287 WriteMap,
298}
299
300impl EnvironmentKind {
301 #[inline]
303 pub const fn is_write_map(&self) -> bool {
304 matches!(self, Self::WriteMap)
305 }
306
307 pub(crate) const fn extra_flags(&self) -> ffi::MDBX_env_flags_t {
309 match self {
310 Self::Default => ffi::MDBX_ENV_DEFAULTS,
311 Self::WriteMap => ffi::MDBX_WRITEMAP,
312 }
313 }
314}
315
316#[derive(Copy, Clone, Debug)]
317pub(crate) struct EnvPtr(pub(crate) *mut ffi::MDBX_env);
318unsafe impl Send for EnvPtr {}
319unsafe impl Sync for EnvPtr {}
320
321#[derive(Debug)]
325#[repr(transparent)]
326pub struct Stat(ffi::MDBX_stat);
327
328impl Stat {
329 pub(crate) const fn new() -> Self {
331 unsafe { Self(mem::zeroed()) }
332 }
333
334 pub(crate) const fn mdbx_stat(&mut self) -> *mut ffi::MDBX_stat {
336 &mut self.0
337 }
338}
339
340impl Stat {
341 #[inline]
343 pub const fn page_size(&self) -> u32 {
344 self.0.ms_psize
345 }
346
347 #[inline]
349 pub const fn depth(&self) -> u32 {
350 self.0.ms_depth
351 }
352
353 #[inline]
355 pub const fn branch_pages(&self) -> usize {
356 self.0.ms_branch_pages as usize
357 }
358
359 #[inline]
361 pub const fn leaf_pages(&self) -> usize {
362 self.0.ms_leaf_pages as usize
363 }
364
365 #[inline]
367 pub const fn overflow_pages(&self) -> usize {
368 self.0.ms_overflow_pages as usize
369 }
370
371 #[inline]
373 pub const fn entries(&self) -> usize {
374 self.0.ms_entries as usize
375 }
376}
377
378#[derive(Debug)]
379#[repr(transparent)]
380pub struct GeometryInfo(ffi::MDBX_envinfo__bindgen_ty_1);
381
382impl GeometryInfo {
383 pub const fn min(&self) -> u64 {
384 self.0.lower
385 }
386}
387
388#[derive(Debug)]
392#[repr(transparent)]
393pub struct Info(ffi::MDBX_envinfo);
394
395impl Info {
396 pub const fn geometry(&self) -> GeometryInfo {
397 GeometryInfo(self.0.mi_geo)
398 }
399
400 #[inline]
402 pub const fn map_size(&self) -> usize {
403 self.0.mi_mapsize as usize
404 }
405
406 #[inline]
408 pub const fn last_pgno(&self) -> usize {
409 self.0.mi_last_pgno as usize
410 }
411
412 #[inline]
414 pub const fn last_txnid(&self) -> usize {
415 self.0.mi_recent_txnid as usize
416 }
417
418 #[inline]
420 pub const fn max_readers(&self) -> usize {
421 self.0.mi_maxreaders as usize
422 }
423
424 #[inline]
426 pub const fn num_readers(&self) -> usize {
427 self.0.mi_numreaders as usize
428 }
429
430 #[inline]
432 pub const fn latter_reader_txnid(&self) -> u64 {
433 self.0.mi_latter_reader_txnid
434 }
435
436 #[inline]
438 pub const fn page_ops(&self) -> PageOps {
439 PageOps {
440 newly: self.0.mi_pgop_stat.newly,
441 cow: self.0.mi_pgop_stat.cow,
442 clone: self.0.mi_pgop_stat.clone,
443 split: self.0.mi_pgop_stat.split,
444 merge: self.0.mi_pgop_stat.merge,
445 spill: self.0.mi_pgop_stat.spill,
446 unspill: self.0.mi_pgop_stat.unspill,
447 wops: self.0.mi_pgop_stat.wops,
448 prefault: self.0.mi_pgop_stat.prefault,
449 mincore: self.0.mi_pgop_stat.mincore,
450 msync: self.0.mi_pgop_stat.msync,
451 fsync: self.0.mi_pgop_stat.fsync,
452 }
453 }
454
455 #[inline]
457 pub const fn mode(&self) -> Mode {
458 let mode = self.0.mi_mode as ffi::MDBX_env_flags_t;
459 if (mode & ffi::MDBX_RDONLY) != 0 {
460 Mode::ReadOnly
461 } else if (mode & ffi::MDBX_UTTERLY_NOSYNC) != 0 {
462 Mode::ReadWrite { sync_mode: SyncMode::UtterlyNoSync }
463 } else if (mode & ffi::MDBX_NOMETASYNC) != 0 {
464 Mode::ReadWrite { sync_mode: SyncMode::NoMetaSync }
465 } else if (mode & ffi::MDBX_SAFE_NOSYNC) != 0 {
466 Mode::ReadWrite { sync_mode: SyncMode::SafeNoSync }
467 } else {
468 Mode::ReadWrite { sync_mode: SyncMode::Durable }
469 }
470 }
471}
472
473impl fmt::Debug for Environment {
474 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
475 f.debug_struct("Environment").field("kind", &self.inner.env_kind).finish_non_exhaustive()
476 }
477}
478
479#[derive(Clone, Debug, PartialEq, Eq)]
484pub enum PageSize {
485 MinimalAcceptable,
486 Set(usize),
487}
488
489#[derive(Clone, Debug, PartialEq, Eq)]
491pub struct PageOps {
492 pub newly: u64,
494 pub cow: u64,
496 pub clone: u64,
498 pub split: u64,
500 pub merge: u64,
502 pub spill: u64,
504 pub unspill: u64,
506 pub wops: u64,
508 pub msync: u64,
510 pub fsync: u64,
512 pub prefault: u64,
514 pub mincore: u64,
516}
517
518#[derive(Clone, Debug, PartialEq, Eq)]
520pub struct Geometry<R> {
521 pub size: Option<R>,
523 pub growth_step: Option<isize>,
524 pub shrink_threshold: Option<isize>,
525 pub page_size: Option<PageSize>,
526}
527
528impl<R> Default for Geometry<R> {
529 fn default() -> Self {
530 Self { size: None, growth_step: None, shrink_threshold: None, page_size: None }
531 }
532}
533
534pub type HandleSlowReadersCallback = extern "C" fn(
578 env: *const ffi::MDBX_env,
579 txn: *const ffi::MDBX_txn,
580 pid: ffi::mdbx_pid_t,
581 tid: ffi::mdbx_tid_t,
582 laggard: u64,
583 gap: std::ffi::c_uint,
584 space: usize,
585 retry: std::ffi::c_int,
586) -> HandleSlowReadersReturnCode;
587
588#[derive(Debug)]
589#[repr(i32)]
590pub enum HandleSlowReadersReturnCode {
591 Error = -2,
593 ProceedWithoutKillingReader = -1,
596 Success = 0,
601 ClearReaderSlot = 1,
605 ReaderProcessTerminated = 2,
608}
609
610#[derive(Debug, Clone)]
612pub struct EnvironmentBuilder {
613 flags: EnvironmentFlags,
614 max_readers: Option<u64>,
615 max_dbs: Option<u64>,
616 sync_bytes: Option<u64>,
617 sync_period: Option<u64>,
618 rp_augment_limit: Option<u64>,
619 loose_limit: Option<u64>,
620 dp_reserve_limit: Option<u64>,
621 txn_dp_limit: Option<u64>,
622 spill_max_denominator: Option<u64>,
623 spill_min_denominator: Option<u64>,
624 geometry: Option<Geometry<(Option<usize>, Option<usize>)>>,
625 log_level: Option<ffi::MDBX_log_level_t>,
626 kind: EnvironmentKind,
627 handle_slow_readers: Option<HandleSlowReadersCallback>,
628 #[cfg(feature = "read-tx-timeouts")]
629 max_read_transaction_duration: Option<read_transactions::MaxReadTransactionDuration>,
632}
633
634impl EnvironmentBuilder {
635 pub fn open(&self, path: &Path) -> Result<Environment> {
639 self.open_with_permissions(path, 0o644)
640 }
641
642 pub fn open_with_permissions(
646 &self,
647 path: &Path,
648 mode: ffi::mdbx_mode_t,
649 ) -> Result<Environment> {
650 let mut env: *mut ffi::MDBX_env = ptr::null_mut();
651 unsafe {
652 if let Some(log_level) = self.log_level {
653 ffi::mdbx_setup_debug(log_level, ffi::MDBX_DBG_DONTCHANGE, None);
656 }
657
658 mdbx_result(ffi::mdbx_env_create(&mut env))?;
659
660 if let Err(e) = (|| {
661 if let Some(geometry) = &self.geometry {
662 let mut min_size = -1;
663 let mut max_size = -1;
664
665 if let Some(size) = geometry.size {
666 if let Some(size) = size.0 {
667 min_size = size as isize;
668 }
669
670 if let Some(size) = size.1 {
671 max_size = size as isize;
672 }
673 }
674
675 mdbx_result(ffi::mdbx_env_set_geometry(
676 env,
677 min_size,
678 -1,
679 max_size,
680 geometry.growth_step.unwrap_or(-1),
681 geometry.shrink_threshold.unwrap_or(-1),
682 match geometry.page_size {
683 None => -1,
684 Some(PageSize::MinimalAcceptable) => 0,
685 Some(PageSize::Set(size)) => size as isize,
686 },
687 ))?;
688 }
689 for (opt, v) in [
690 (ffi::MDBX_opt_max_db, self.max_dbs),
691 (ffi::MDBX_opt_rp_augment_limit, self.rp_augment_limit),
692 (ffi::MDBX_opt_loose_limit, self.loose_limit),
693 (ffi::MDBX_opt_dp_reserve_limit, self.dp_reserve_limit),
694 (ffi::MDBX_opt_txn_dp_limit, self.txn_dp_limit),
695 (ffi::MDBX_opt_spill_max_denominator, self.spill_max_denominator),
696 (ffi::MDBX_opt_spill_min_denominator, self.spill_min_denominator),
697 ] {
698 if let Some(v) = v {
699 mdbx_result(ffi::mdbx_env_set_option(env, opt, v))?;
700 }
701 }
702
703 if let Some(max_readers) = self.max_readers {
705 mdbx_result(ffi::mdbx_env_set_option(
706 env,
707 ffi::MDBX_opt_max_readers,
708 max_readers,
709 ))?;
710 }
711
712 if let Some(handle_slow_readers) = self.handle_slow_readers {
713 mdbx_result(ffi::mdbx_env_set_hsr(
714 env,
715 convert_hsr_fn(Some(handle_slow_readers)),
716 ))?;
717 }
718
719 #[cfg(unix)]
720 fn path_to_bytes<P: AsRef<Path>>(path: P) -> Vec<u8> {
721 use std::os::unix::ffi::OsStrExt;
722 path.as_ref().as_os_str().as_bytes().to_vec()
723 }
724
725 #[cfg(windows)]
726 fn path_to_bytes<P: AsRef<Path>>(path: P) -> Vec<u8> {
727 path.as_ref().to_string_lossy().to_string().into_bytes()
731 }
732
733 let path = match CString::new(path_to_bytes(path)) {
734 Ok(path) => path,
735 Err(_) => return Err(Error::Invalid),
736 };
737 mdbx_result(ffi::mdbx_env_open(
738 env,
739 path.as_ptr(),
740 self.flags.make_flags() | self.kind.extra_flags(),
741 mode,
742 ))?;
743
744 for (opt, v) in [
745 (ffi::MDBX_opt_sync_bytes, self.sync_bytes),
746 (ffi::MDBX_opt_sync_period, self.sync_period),
747 ] {
748 if let Some(v) = v {
749 mdbx_result(ffi::mdbx_env_set_option(env, opt, v))?;
750 }
751 }
752
753 Ok(())
754 })() {
755 ffi::mdbx_env_close_ex(env, false);
756
757 return Err(e)
758 }
759 }
760
761 let env_ptr = EnvPtr(env);
762
763 #[cfg(not(feature = "read-tx-timeouts"))]
764 let txn_manager = TxnManager::new(env_ptr);
765
766 #[cfg(feature = "read-tx-timeouts")]
767 let txn_manager = {
768 if let crate::MaxReadTransactionDuration::Set(duration) = self
769 .max_read_transaction_duration
770 .unwrap_or(read_transactions::MaxReadTransactionDuration::Set(
771 DEFAULT_MAX_READ_TRANSACTION_DURATION,
772 ))
773 {
774 TxnManager::new_with_max_read_transaction_duration(env_ptr, duration)
775 } else {
776 TxnManager::new(env_ptr)
777 }
778 };
779
780 let env = EnvironmentInner {
781 env,
782 txn_manager,
783 env_kind: self.kind,
784 ro_txn_pool: ReadTxnPool::new(),
785 };
786
787 Ok(Environment { inner: Arc::new(env) })
788 }
789
790 pub const fn set_kind(&mut self, kind: EnvironmentKind) -> &mut Self {
792 self.kind = kind;
793 self
794 }
795
796 pub const fn write_map(&mut self) -> &mut Self {
800 self.set_kind(EnvironmentKind::WriteMap)
801 }
802
803 pub const fn set_flags(&mut self, flags: EnvironmentFlags) -> &mut Self {
805 self.flags = flags;
806 self
807 }
808
809 pub const fn set_max_readers(&mut self, max_readers: u64) -> &mut Self {
815 self.max_readers = Some(max_readers);
816 self
817 }
818
819 pub const fn set_max_dbs(&mut self, v: usize) -> &mut Self {
829 self.max_dbs = Some(v as u64);
830 self
831 }
832
833 pub const fn set_sync_bytes(&mut self, v: usize) -> &mut Self {
836 self.sync_bytes = Some(v as u64);
837 self
838 }
839
840 pub fn set_sync_period(&mut self, v: Duration) -> &mut Self {
843 let as_mdbx_units = (v.as_secs_f64() * 65536f64) as u64;
845 self.sync_period = Some(as_mdbx_units);
846 self
847 }
848
849 pub const fn set_rp_augment_limit(&mut self, v: u64) -> &mut Self {
850 self.rp_augment_limit = Some(v);
851 self
852 }
853
854 pub const fn set_loose_limit(&mut self, v: u64) -> &mut Self {
855 self.loose_limit = Some(v);
856 self
857 }
858
859 pub const fn set_dp_reserve_limit(&mut self, v: u64) -> &mut Self {
860 self.dp_reserve_limit = Some(v);
861 self
862 }
863
864 pub const fn set_txn_dp_limit(&mut self, v: u64) -> &mut Self {
865 self.txn_dp_limit = Some(v);
866 self
867 }
868
869 pub fn set_spill_max_denominator(&mut self, v: u8) -> &mut Self {
870 self.spill_max_denominator = Some(v.into());
871 self
872 }
873
874 pub fn set_spill_min_denominator(&mut self, v: u8) -> &mut Self {
875 self.spill_min_denominator = Some(v.into());
876 self
877 }
878
879 pub fn set_geometry<R: RangeBounds<usize>>(&mut self, geometry: Geometry<R>) -> &mut Self {
882 let convert_bound = |bound: Bound<&usize>| match bound {
883 Bound::Included(v) | Bound::Excluded(v) => Some(*v),
884 _ => None,
885 };
886 self.geometry = Some(Geometry {
887 size: geometry.size.map(|range| {
888 (convert_bound(range.start_bound()), convert_bound(range.end_bound()))
889 }),
890 growth_step: geometry.growth_step,
891 shrink_threshold: geometry.shrink_threshold,
892 page_size: geometry.page_size,
893 });
894 self
895 }
896
897 pub const fn set_log_level(&mut self, log_level: ffi::MDBX_log_level_t) -> &mut Self {
898 self.log_level = Some(log_level);
899 self
900 }
901
902 pub fn set_handle_slow_readers(&mut self, hsr: HandleSlowReadersCallback) -> &mut Self {
905 self.handle_slow_readers = Some(hsr);
906 self
907 }
908}
909
910#[cfg(feature = "read-tx-timeouts")]
911pub(crate) mod read_transactions {
912 use crate::EnvironmentBuilder;
913 use std::time::Duration;
914
915 #[derive(Debug, Clone, Copy)]
917 #[cfg(feature = "read-tx-timeouts")]
918 pub enum MaxReadTransactionDuration {
919 Unbounded,
921 Set(Duration),
923 }
924
925 #[cfg(feature = "read-tx-timeouts")]
926 impl MaxReadTransactionDuration {
927 pub const fn as_duration(&self) -> Option<Duration> {
928 match self {
929 Self::Unbounded => None,
930 Self::Set(duration) => Some(*duration),
931 }
932 }
933 }
934
935 impl EnvironmentBuilder {
936 pub const fn set_max_read_transaction_duration(
938 &mut self,
939 max_read_transaction_duration: MaxReadTransactionDuration,
940 ) -> &mut Self {
941 self.max_read_transaction_duration = Some(max_read_transaction_duration);
942 self
943 }
944 }
945}
946
947fn convert_hsr_fn(callback: Option<HandleSlowReadersCallback>) -> ffi::MDBX_hsr_func {
949 unsafe { std::mem::transmute(callback) }
950}
951
952#[cfg(test)]
953mod tests {
954 use crate::{Environment, Error, Geometry, HandleSlowReadersReturnCode, PageSize, WriteFlags};
955 use std::{
956 ops::RangeInclusive,
957 sync::atomic::{AtomicBool, Ordering},
958 };
959
960 #[test]
961 fn test_handle_slow_readers_callback() {
962 static CALLED: AtomicBool = AtomicBool::new(false);
963
964 extern "C" fn handle_slow_readers(
965 _env: *const ffi::MDBX_env,
966 _txn: *const ffi::MDBX_txn,
967 _pid: ffi::mdbx_pid_t,
968 _tid: ffi::mdbx_tid_t,
969 _laggard: u64,
970 _gap: std::ffi::c_uint,
971 _space: usize,
972 _retry: std::ffi::c_int,
973 ) -> HandleSlowReadersReturnCode {
974 CALLED.store(true, Ordering::Relaxed);
975 HandleSlowReadersReturnCode::ProceedWithoutKillingReader
976 }
977
978 let tempdir = tempfile::tempdir().unwrap();
979 let env = Environment::builder()
980 .set_geometry(Geometry::<RangeInclusive<usize>> {
981 size: Some(0..=1024 * 1024), page_size: Some(PageSize::MinimalAcceptable), ..Default::default()
984 })
985 .set_handle_slow_readers(handle_slow_readers)
986 .open(tempdir.path())
987 .unwrap();
988
989 {
991 let tx = env.begin_rw_txn().unwrap();
992 let db = tx.open_db(None).unwrap();
993 for i in 0usize..1_000 {
994 tx.put(db.dbi(), i.to_le_bytes(), b"0", WriteFlags::empty()).unwrap()
995 }
996 tx.commit().unwrap();
997 }
998
999 let _tx_ro = env.begin_ro_txn().unwrap();
1001
1002 {
1004 let tx = env.begin_rw_txn().unwrap();
1005 let db = tx.open_db(None).unwrap();
1006 for i in 0usize..1_000 {
1007 tx.put(db.dbi(), i.to_le_bytes(), b"1", WriteFlags::empty()).unwrap();
1008 }
1009 tx.commit().unwrap();
1010 }
1011
1012 {
1015 let tx = env.begin_rw_txn().unwrap();
1016 let db = tx.open_db(None).unwrap();
1017 for i in 1_000usize..1_000_000 {
1018 match tx.put(db.dbi(), i.to_le_bytes(), b"0", WriteFlags::empty()) {
1019 Ok(_) => {}
1020 Err(Error::MapFull) => break,
1021 result @ Err(_) => result.unwrap(),
1022 }
1023 }
1024 let _ = tx.commit();
1028 }
1029
1030 assert!(CALLED.load(Ordering::Relaxed));
1032 }
1033}