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 Mode, SyncMode, Transaction, TransactionKind,
8};
9use byteorder::{ByteOrder, NativeEndian};
10use mem::size_of;
11use std::{
12 ffi::CString,
13 fmt::{self, Debug},
14 mem,
15 ops::{Bound, RangeBounds},
16 path::Path,
17 ptr,
18 sync::{mpsc::sync_channel, Arc},
19 thread::sleep,
20 time::Duration,
21};
22use tracing::warn;
23
24#[cfg(feature = "read-tx-timeouts")]
26const DEFAULT_MAX_READ_TRANSACTION_DURATION: Duration = Duration::from_secs(5 * 60);
27
28#[derive(Clone)]
33pub struct Environment {
34 inner: Arc<EnvironmentInner>,
35}
36
37impl Environment {
38 pub fn builder() -> EnvironmentBuilder {
40 EnvironmentBuilder {
41 flags: EnvironmentFlags::default(),
42 max_readers: None,
43 max_dbs: None,
44 sync_bytes: None,
45 sync_period: None,
46 rp_augment_limit: None,
47 loose_limit: None,
48 dp_reserve_limit: None,
49 txn_dp_limit: None,
50 spill_max_denominator: None,
51 spill_min_denominator: None,
52 geometry: None,
53 log_level: None,
54 kind: Default::default(),
55 handle_slow_readers: None,
56 #[cfg(feature = "read-tx-timeouts")]
57 max_read_transaction_duration: None,
58 }
59 }
60
61 #[inline]
63 pub fn is_write_map(&self) -> bool {
64 self.inner.env_kind.is_write_map()
65 }
66
67 #[inline]
69 pub fn env_kind(&self) -> EnvironmentKind {
70 self.inner.env_kind
71 }
72
73 #[inline]
75 pub fn is_read_write(&self) -> Result<bool> {
76 Ok(!self.is_read_only()?)
77 }
78
79 #[inline]
81 pub fn is_read_only(&self) -> Result<bool> {
82 Ok(matches!(self.info()?.mode(), Mode::ReadOnly))
83 }
84
85 #[inline]
87 pub(crate) fn txn_manager(&self) -> &TxnManager {
88 &self.inner.txn_manager
89 }
90
91 #[cfg(feature = "read-tx-timeouts")]
93 pub fn timed_out_not_aborted_transactions(&self) -> usize {
94 self.inner.txn_manager.timed_out_not_aborted_read_transactions().unwrap_or(0)
95 }
96
97 #[inline]
99 pub fn begin_ro_txn(&self) -> Result<Transaction<RO>> {
100 Transaction::new(self.clone())
101 }
102
103 pub fn begin_rw_txn(&self) -> Result<Transaction<RW>> {
106 let mut warned = false;
107 let txn = loop {
108 let (tx, rx) = sync_channel(0);
109 self.txn_manager().send_message(TxnManagerMessage::Begin {
110 parent: TxnPtr(ptr::null_mut()),
111 flags: RW::OPEN_FLAGS,
112 sender: tx,
113 });
114 let res = rx.recv().unwrap();
115 if matches!(&res, Err(Error::Busy)) {
116 if !warned {
117 warned = true;
118 warn!(target: "libmdbx", "Process stalled, awaiting read-write transaction lock.");
119 }
120 sleep(Duration::from_millis(250));
121 continue
122 }
123
124 break res
125 }?;
126 Ok(Transaction::new_from_ptr(self.clone(), txn.0))
127 }
128
129 #[inline]
134 pub(crate) fn env_ptr(&self) -> *mut ffi::MDBX_env {
135 self.inner.env
136 }
137
138 #[inline]
144 #[doc(hidden)]
145 pub fn with_raw_env_ptr<F, T>(&self, f: F) -> T
146 where
147 F: FnOnce(*mut ffi::MDBX_env) -> T,
148 {
149 f(self.env_ptr())
150 }
151
152 pub fn sync(&self, force: bool) -> Result<bool> {
154 mdbx_result(unsafe { ffi::mdbx_env_sync_ex(self.env_ptr(), force, false) })
155 }
156
157 pub fn stat(&self) -> Result<Stat> {
159 unsafe {
160 let mut stat = Stat::new();
161 mdbx_result(ffi::mdbx_env_stat_ex(
162 self.env_ptr(),
163 ptr::null(),
164 stat.mdb_stat(),
165 size_of::<Stat>(),
166 ))?;
167 Ok(stat)
168 }
169 }
170
171 pub fn info(&self) -> Result<Info> {
173 unsafe {
174 let mut info = Info(mem::zeroed());
175 mdbx_result(ffi::mdbx_env_info_ex(
176 self.env_ptr(),
177 ptr::null(),
178 &mut info.0,
179 size_of::<Info>(),
180 ))?;
181 Ok(info)
182 }
183 }
184
185 pub fn freelist(&self) -> Result<usize> {
211 let mut freelist: usize = 0;
212 let txn = self.begin_ro_txn()?;
213 let db = Database::freelist_db();
214 let cursor = txn.cursor(db.dbi())?;
215
216 for result in cursor.iter_slices() {
217 let (_key, value) = result?;
218 if value.len() < size_of::<u32>() {
219 return Err(Error::Corrupted)
220 }
221 let s = &value[..size_of::<u32>()];
222 freelist += NativeEndian::read_u32(s) as usize;
223 }
224
225 Ok(freelist)
226 }
227}
228
229struct EnvironmentInner {
234 env: *mut ffi::MDBX_env,
238 env_kind: EnvironmentKind,
240 txn_manager: TxnManager,
242}
243
244impl Drop for EnvironmentInner {
245 fn drop(&mut self) {
246 unsafe {
248 ffi::mdbx_env_close_ex(self.env, false);
249 }
250 }
251}
252
253unsafe impl Send for EnvironmentInner {}
256unsafe impl Sync for EnvironmentInner {}
257
258#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
262pub enum EnvironmentKind {
263 #[default]
265 Default,
266 WriteMap,
277}
278
279impl EnvironmentKind {
280 #[inline]
282 pub const fn is_write_map(&self) -> bool {
283 matches!(self, Self::WriteMap)
284 }
285
286 pub(crate) const fn extra_flags(&self) -> ffi::MDBX_env_flags_t {
288 match self {
289 Self::Default => ffi::MDBX_ENV_DEFAULTS,
290 Self::WriteMap => ffi::MDBX_WRITEMAP,
291 }
292 }
293}
294
295#[derive(Copy, Clone, Debug)]
296pub(crate) struct EnvPtr(pub(crate) *mut ffi::MDBX_env);
297unsafe impl Send for EnvPtr {}
298unsafe impl Sync for EnvPtr {}
299
300#[derive(Debug)]
304#[repr(transparent)]
305pub struct Stat(ffi::MDBX_stat);
306
307impl Stat {
308 pub(crate) const fn new() -> Self {
310 unsafe { Self(mem::zeroed()) }
311 }
312
313 pub(crate) const fn mdb_stat(&mut self) -> *mut ffi::MDBX_stat {
315 &mut self.0
316 }
317}
318
319impl Stat {
320 #[inline]
322 pub const fn page_size(&self) -> u32 {
323 self.0.ms_psize
324 }
325
326 #[inline]
328 pub const fn depth(&self) -> u32 {
329 self.0.ms_depth
330 }
331
332 #[inline]
334 pub const fn branch_pages(&self) -> usize {
335 self.0.ms_branch_pages as usize
336 }
337
338 #[inline]
340 pub const fn leaf_pages(&self) -> usize {
341 self.0.ms_leaf_pages as usize
342 }
343
344 #[inline]
346 pub const fn overflow_pages(&self) -> usize {
347 self.0.ms_overflow_pages as usize
348 }
349
350 #[inline]
352 pub const fn entries(&self) -> usize {
353 self.0.ms_entries as usize
354 }
355}
356
357#[derive(Debug)]
358#[repr(transparent)]
359pub struct GeometryInfo(ffi::MDBX_envinfo__bindgen_ty_1);
360
361impl GeometryInfo {
362 pub const fn min(&self) -> u64 {
363 self.0.lower
364 }
365}
366
367#[derive(Debug)]
371#[repr(transparent)]
372pub struct Info(ffi::MDBX_envinfo);
373
374impl Info {
375 pub const fn geometry(&self) -> GeometryInfo {
376 GeometryInfo(self.0.mi_geo)
377 }
378
379 #[inline]
381 pub const fn map_size(&self) -> usize {
382 self.0.mi_mapsize as usize
383 }
384
385 #[inline]
387 pub const fn last_pgno(&self) -> usize {
388 self.0.mi_last_pgno as usize
389 }
390
391 #[inline]
393 pub const fn last_txnid(&self) -> usize {
394 self.0.mi_recent_txnid as usize
395 }
396
397 #[inline]
399 pub const fn max_readers(&self) -> usize {
400 self.0.mi_maxreaders as usize
401 }
402
403 #[inline]
405 pub const fn num_readers(&self) -> usize {
406 self.0.mi_numreaders as usize
407 }
408
409 #[inline]
411 pub const fn page_ops(&self) -> PageOps {
412 PageOps {
413 newly: self.0.mi_pgop_stat.newly,
414 cow: self.0.mi_pgop_stat.cow,
415 clone: self.0.mi_pgop_stat.clone,
416 split: self.0.mi_pgop_stat.split,
417 merge: self.0.mi_pgop_stat.merge,
418 spill: self.0.mi_pgop_stat.spill,
419 unspill: self.0.mi_pgop_stat.unspill,
420 wops: self.0.mi_pgop_stat.wops,
421 prefault: self.0.mi_pgop_stat.prefault,
422 mincore: self.0.mi_pgop_stat.mincore,
423 msync: self.0.mi_pgop_stat.msync,
424 fsync: self.0.mi_pgop_stat.fsync,
425 }
426 }
427
428 #[inline]
430 pub const fn mode(&self) -> Mode {
431 let mode = self.0.mi_mode as ffi::MDBX_env_flags_t;
432 if (mode & ffi::MDBX_RDONLY) != 0 {
433 Mode::ReadOnly
434 } else if (mode & ffi::MDBX_UTTERLY_NOSYNC) != 0 {
435 Mode::ReadWrite { sync_mode: SyncMode::UtterlyNoSync }
436 } else if (mode & ffi::MDBX_NOMETASYNC) != 0 {
437 Mode::ReadWrite { sync_mode: SyncMode::NoMetaSync }
438 } else if (mode & ffi::MDBX_SAFE_NOSYNC) != 0 {
439 Mode::ReadWrite { sync_mode: SyncMode::SafeNoSync }
440 } else {
441 Mode::ReadWrite { sync_mode: SyncMode::Durable }
442 }
443 }
444}
445
446impl fmt::Debug for Environment {
447 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
448 f.debug_struct("Environment").field("kind", &self.inner.env_kind).finish_non_exhaustive()
449 }
450}
451
452#[derive(Clone, Debug, PartialEq, Eq)]
457pub enum PageSize {
458 MinimalAcceptable,
459 Set(usize),
460}
461
462#[derive(Clone, Debug, PartialEq, Eq)]
464pub struct PageOps {
465 pub newly: u64,
467 pub cow: u64,
469 pub clone: u64,
471 pub split: u64,
473 pub merge: u64,
475 pub spill: u64,
477 pub unspill: u64,
479 pub wops: u64,
481 pub msync: u64,
483 pub fsync: u64,
485 pub prefault: u64,
487 pub mincore: u64,
489}
490
491#[derive(Clone, Debug, PartialEq, Eq)]
493pub struct Geometry<R> {
494 pub size: Option<R>,
496 pub growth_step: Option<isize>,
497 pub shrink_threshold: Option<isize>,
498 pub page_size: Option<PageSize>,
499}
500
501impl<R> Default for Geometry<R> {
502 fn default() -> Self {
503 Self { size: None, growth_step: None, shrink_threshold: None, page_size: None }
504 }
505}
506
507pub type HandleSlowReadersCallback = extern "C" fn(
551 env: *const ffi::MDBX_env,
552 txn: *const ffi::MDBX_txn,
553 pid: ffi::mdbx_pid_t,
554 tid: ffi::mdbx_tid_t,
555 laggard: u64,
556 gap: std::ffi::c_uint,
557 space: usize,
558 retry: std::ffi::c_int,
559) -> HandleSlowReadersReturnCode;
560
561#[derive(Debug)]
562#[repr(i32)]
563pub enum HandleSlowReadersReturnCode {
564 Error = -2,
566 ProceedWithoutKillingReader = -1,
569 Success = 0,
574 ClearReaderSlot = 1,
578 ReaderProcessTerminated = 2,
581}
582
583#[derive(Debug, Clone)]
585pub struct EnvironmentBuilder {
586 flags: EnvironmentFlags,
587 max_readers: Option<u64>,
588 max_dbs: Option<u64>,
589 sync_bytes: Option<u64>,
590 sync_period: Option<u64>,
591 rp_augment_limit: Option<u64>,
592 loose_limit: Option<u64>,
593 dp_reserve_limit: Option<u64>,
594 txn_dp_limit: Option<u64>,
595 spill_max_denominator: Option<u64>,
596 spill_min_denominator: Option<u64>,
597 geometry: Option<Geometry<(Option<usize>, Option<usize>)>>,
598 log_level: Option<ffi::MDBX_log_level_t>,
599 kind: EnvironmentKind,
600 handle_slow_readers: Option<HandleSlowReadersCallback>,
601 #[cfg(feature = "read-tx-timeouts")]
602 max_read_transaction_duration: Option<read_transactions::MaxReadTransactionDuration>,
605}
606
607impl EnvironmentBuilder {
608 pub fn open(&self, path: &Path) -> Result<Environment> {
612 self.open_with_permissions(path, 0o644)
613 }
614
615 pub fn open_with_permissions(
619 &self,
620 path: &Path,
621 mode: ffi::mdbx_mode_t,
622 ) -> Result<Environment> {
623 let mut env: *mut ffi::MDBX_env = ptr::null_mut();
624 unsafe {
625 if let Some(log_level) = self.log_level {
626 ffi::mdbx_setup_debug(log_level, ffi::MDBX_DBG_DONTCHANGE, None);
629 }
630
631 mdbx_result(ffi::mdbx_env_create(&mut env))?;
632
633 if let Err(e) = (|| {
634 if let Some(geometry) = &self.geometry {
635 let mut min_size = -1;
636 let mut max_size = -1;
637
638 if let Some(size) = geometry.size {
639 if let Some(size) = size.0 {
640 min_size = size as isize;
641 }
642
643 if let Some(size) = size.1 {
644 max_size = size as isize;
645 }
646 }
647
648 mdbx_result(ffi::mdbx_env_set_geometry(
649 env,
650 min_size,
651 -1,
652 max_size,
653 geometry.growth_step.unwrap_or(-1),
654 geometry.shrink_threshold.unwrap_or(-1),
655 match geometry.page_size {
656 None => -1,
657 Some(PageSize::MinimalAcceptable) => 0,
658 Some(PageSize::Set(size)) => size as isize,
659 },
660 ))?;
661 }
662 for (opt, v) in [
663 (ffi::MDBX_opt_max_db, self.max_dbs),
664 (ffi::MDBX_opt_rp_augment_limit, self.rp_augment_limit),
665 (ffi::MDBX_opt_loose_limit, self.loose_limit),
666 (ffi::MDBX_opt_dp_reserve_limit, self.dp_reserve_limit),
667 (ffi::MDBX_opt_txn_dp_limit, self.txn_dp_limit),
668 (ffi::MDBX_opt_spill_max_denominator, self.spill_max_denominator),
669 (ffi::MDBX_opt_spill_min_denominator, self.spill_min_denominator),
670 ] {
671 if let Some(v) = v {
672 mdbx_result(ffi::mdbx_env_set_option(env, opt, v))?;
673 }
674 }
675
676 if let Some(max_readers) = self.max_readers {
678 mdbx_result(ffi::mdbx_env_set_option(
679 env,
680 ffi::MDBX_opt_max_readers,
681 max_readers,
682 ))?;
683 }
684
685 if let Some(handle_slow_readers) = self.handle_slow_readers {
686 mdbx_result(ffi::mdbx_env_set_hsr(
687 env,
688 convert_hsr_fn(Some(handle_slow_readers)),
689 ))?;
690 }
691
692 #[cfg(unix)]
693 fn path_to_bytes<P: AsRef<Path>>(path: P) -> Vec<u8> {
694 use std::os::unix::ffi::OsStrExt;
695 path.as_ref().as_os_str().as_bytes().to_vec()
696 }
697
698 #[cfg(windows)]
699 fn path_to_bytes<P: AsRef<Path>>(path: P) -> Vec<u8> {
700 path.as_ref().to_string_lossy().to_string().into_bytes()
704 }
705
706 let path = match CString::new(path_to_bytes(path)) {
707 Ok(path) => path,
708 Err(_) => return Err(Error::Invalid),
709 };
710 mdbx_result(ffi::mdbx_env_open(
711 env,
712 path.as_ptr(),
713 self.flags.make_flags() | self.kind.extra_flags(),
714 mode,
715 ))?;
716
717 for (opt, v) in [
718 (ffi::MDBX_opt_sync_bytes, self.sync_bytes),
719 (ffi::MDBX_opt_sync_period, self.sync_period),
720 ] {
721 if let Some(v) = v {
722 mdbx_result(ffi::mdbx_env_set_option(env, opt, v))?;
723 }
724 }
725
726 Ok(())
727 })() {
728 ffi::mdbx_env_close_ex(env, false);
729
730 return Err(e)
731 }
732 }
733
734 let env_ptr = EnvPtr(env);
735
736 #[cfg(not(feature = "read-tx-timeouts"))]
737 let txn_manager = TxnManager::new(env_ptr);
738
739 #[cfg(feature = "read-tx-timeouts")]
740 let txn_manager = {
741 if let crate::MaxReadTransactionDuration::Set(duration) = self
742 .max_read_transaction_duration
743 .unwrap_or(read_transactions::MaxReadTransactionDuration::Set(
744 DEFAULT_MAX_READ_TRANSACTION_DURATION,
745 ))
746 {
747 TxnManager::new_with_max_read_transaction_duration(env_ptr, duration)
748 } else {
749 TxnManager::new(env_ptr)
750 }
751 };
752
753 let env = EnvironmentInner { env, txn_manager, env_kind: self.kind };
754
755 Ok(Environment { inner: Arc::new(env) })
756 }
757
758 pub const fn set_kind(&mut self, kind: EnvironmentKind) -> &mut Self {
760 self.kind = kind;
761 self
762 }
763
764 pub const fn write_map(&mut self) -> &mut Self {
768 self.set_kind(EnvironmentKind::WriteMap)
769 }
770
771 pub const fn set_flags(&mut self, flags: EnvironmentFlags) -> &mut Self {
773 self.flags = flags;
774 self
775 }
776
777 pub const fn set_max_readers(&mut self, max_readers: u64) -> &mut Self {
783 self.max_readers = Some(max_readers);
784 self
785 }
786
787 pub const fn set_max_dbs(&mut self, v: usize) -> &mut Self {
797 self.max_dbs = Some(v as u64);
798 self
799 }
800
801 pub const fn set_sync_bytes(&mut self, v: usize) -> &mut Self {
804 self.sync_bytes = Some(v as u64);
805 self
806 }
807
808 pub fn set_sync_period(&mut self, v: Duration) -> &mut Self {
811 let as_mdbx_units = (v.as_secs_f64() * 65536f64) as u64;
813 self.sync_period = Some(as_mdbx_units);
814 self
815 }
816
817 pub const fn set_rp_augment_limit(&mut self, v: u64) -> &mut Self {
818 self.rp_augment_limit = Some(v);
819 self
820 }
821
822 pub const fn set_loose_limit(&mut self, v: u64) -> &mut Self {
823 self.loose_limit = Some(v);
824 self
825 }
826
827 pub const fn set_dp_reserve_limit(&mut self, v: u64) -> &mut Self {
828 self.dp_reserve_limit = Some(v);
829 self
830 }
831
832 pub const fn set_txn_dp_limit(&mut self, v: u64) -> &mut Self {
833 self.txn_dp_limit = Some(v);
834 self
835 }
836
837 pub fn set_spill_max_denominator(&mut self, v: u8) -> &mut Self {
838 self.spill_max_denominator = Some(v.into());
839 self
840 }
841
842 pub fn set_spill_min_denominator(&mut self, v: u8) -> &mut Self {
843 self.spill_min_denominator = Some(v.into());
844 self
845 }
846
847 pub fn set_geometry<R: RangeBounds<usize>>(&mut self, geometry: Geometry<R>) -> &mut Self {
850 let convert_bound = |bound: Bound<&usize>| match bound {
851 Bound::Included(v) | Bound::Excluded(v) => Some(*v),
852 _ => None,
853 };
854 self.geometry = Some(Geometry {
855 size: geometry.size.map(|range| {
856 (convert_bound(range.start_bound()), convert_bound(range.end_bound()))
857 }),
858 growth_step: geometry.growth_step,
859 shrink_threshold: geometry.shrink_threshold,
860 page_size: geometry.page_size,
861 });
862 self
863 }
864
865 pub const fn set_log_level(&mut self, log_level: ffi::MDBX_log_level_t) -> &mut Self {
866 self.log_level = Some(log_level);
867 self
868 }
869
870 pub fn set_handle_slow_readers(&mut self, hsr: HandleSlowReadersCallback) -> &mut Self {
873 self.handle_slow_readers = Some(hsr);
874 self
875 }
876}
877
878#[cfg(feature = "read-tx-timeouts")]
879pub(crate) mod read_transactions {
880 use crate::EnvironmentBuilder;
881 use std::time::Duration;
882
883 #[derive(Debug, Clone, Copy)]
885 #[cfg(feature = "read-tx-timeouts")]
886 pub enum MaxReadTransactionDuration {
887 Unbounded,
889 Set(Duration),
891 }
892
893 #[cfg(feature = "read-tx-timeouts")]
894 impl MaxReadTransactionDuration {
895 pub const fn as_duration(&self) -> Option<Duration> {
896 match self {
897 Self::Unbounded => None,
898 Self::Set(duration) => Some(*duration),
899 }
900 }
901 }
902
903 impl EnvironmentBuilder {
904 pub const fn set_max_read_transaction_duration(
906 &mut self,
907 max_read_transaction_duration: MaxReadTransactionDuration,
908 ) -> &mut Self {
909 self.max_read_transaction_duration = Some(max_read_transaction_duration);
910 self
911 }
912 }
913}
914
915fn convert_hsr_fn(callback: Option<HandleSlowReadersCallback>) -> ffi::MDBX_hsr_func {
917 unsafe { std::mem::transmute(callback) }
918}
919
920#[cfg(test)]
921mod tests {
922 use crate::{Environment, Error, Geometry, HandleSlowReadersReturnCode, PageSize, WriteFlags};
923 use std::{
924 ops::RangeInclusive,
925 sync::atomic::{AtomicBool, Ordering},
926 };
927
928 #[test]
929 fn test_handle_slow_readers_callback() {
930 static CALLED: AtomicBool = AtomicBool::new(false);
931
932 extern "C" fn handle_slow_readers(
933 _env: *const ffi::MDBX_env,
934 _txn: *const ffi::MDBX_txn,
935 _pid: ffi::mdbx_pid_t,
936 _tid: ffi::mdbx_tid_t,
937 _laggard: u64,
938 _gap: std::ffi::c_uint,
939 _space: usize,
940 _retry: std::ffi::c_int,
941 ) -> HandleSlowReadersReturnCode {
942 CALLED.store(true, Ordering::Relaxed);
943 HandleSlowReadersReturnCode::ProceedWithoutKillingReader
944 }
945
946 let tempdir = tempfile::tempdir().unwrap();
947 let env = Environment::builder()
948 .set_geometry(Geometry::<RangeInclusive<usize>> {
949 size: Some(0..=1024 * 1024), page_size: Some(PageSize::MinimalAcceptable), ..Default::default()
952 })
953 .set_handle_slow_readers(handle_slow_readers)
954 .open(tempdir.path())
955 .unwrap();
956
957 {
959 let tx = env.begin_rw_txn().unwrap();
960 let db = tx.open_db(None).unwrap();
961 for i in 0usize..1_000 {
962 tx.put(db.dbi(), i.to_le_bytes(), b"0", WriteFlags::empty()).unwrap()
963 }
964 tx.commit().unwrap();
965 }
966
967 let _tx_ro = env.begin_ro_txn().unwrap();
969
970 {
972 let tx = env.begin_rw_txn().unwrap();
973 let db = tx.open_db(None).unwrap();
974 for i in 0usize..1_000 {
975 tx.put(db.dbi(), i.to_le_bytes(), b"1", WriteFlags::empty()).unwrap();
976 }
977 tx.commit().unwrap();
978 }
979
980 {
983 let tx = env.begin_rw_txn().unwrap();
984 let db = tx.open_db(None).unwrap();
985 for i in 1_000usize..1_000_000 {
986 match tx.put(db.dbi(), i.to_le_bytes(), b"0", WriteFlags::empty()) {
987 Ok(_) => {}
988 Err(Error::MapFull) => break,
989 result @ Err(_) => result.unwrap(),
990 }
991 }
992 let _ = tx.commit();
996 }
997
998 assert!(CALLED.load(Ordering::Relaxed));
1000 }
1001}