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)?;
215
216 for result in cursor.iter_slices() {
217 let (_key, value) = result?;
218 if value.len() < size_of::<usize>() {
219 return Err(Error::Corrupted)
220 }
221
222 let s = &value[..size_of::<usize>()];
223 freelist += NativeEndian::read_u32(s) as usize;
224 }
225
226 Ok(freelist)
227 }
228}
229
230struct EnvironmentInner {
235 env: *mut ffi::MDBX_env,
239 env_kind: EnvironmentKind,
241 txn_manager: TxnManager,
243}
244
245impl Drop for EnvironmentInner {
246 fn drop(&mut self) {
247 unsafe {
249 ffi::mdbx_env_close_ex(self.env, false);
250 }
251 }
252}
253
254unsafe impl Send for EnvironmentInner {}
257unsafe impl Sync for EnvironmentInner {}
258
259#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
263pub enum EnvironmentKind {
264 #[default]
266 Default,
267 WriteMap,
278}
279
280impl EnvironmentKind {
281 #[inline]
283 pub const fn is_write_map(&self) -> bool {
284 matches!(self, Self::WriteMap)
285 }
286
287 pub(crate) const fn extra_flags(&self) -> ffi::MDBX_env_flags_t {
289 match self {
290 Self::Default => ffi::MDBX_ENV_DEFAULTS,
291 Self::WriteMap => ffi::MDBX_WRITEMAP,
292 }
293 }
294}
295
296#[derive(Copy, Clone, Debug)]
297pub(crate) struct EnvPtr(pub(crate) *mut ffi::MDBX_env);
298unsafe impl Send for EnvPtr {}
299unsafe impl Sync for EnvPtr {}
300
301#[derive(Debug)]
305#[repr(transparent)]
306pub struct Stat(ffi::MDBX_stat);
307
308impl Stat {
309 pub(crate) const fn new() -> Self {
311 unsafe { Self(mem::zeroed()) }
312 }
313
314 pub(crate) const fn mdb_stat(&mut self) -> *mut ffi::MDBX_stat {
316 &mut self.0
317 }
318}
319
320impl Stat {
321 #[inline]
323 pub const fn page_size(&self) -> u32 {
324 self.0.ms_psize
325 }
326
327 #[inline]
329 pub const fn depth(&self) -> u32 {
330 self.0.ms_depth
331 }
332
333 #[inline]
335 pub const fn branch_pages(&self) -> usize {
336 self.0.ms_branch_pages as usize
337 }
338
339 #[inline]
341 pub const fn leaf_pages(&self) -> usize {
342 self.0.ms_leaf_pages as usize
343 }
344
345 #[inline]
347 pub const fn overflow_pages(&self) -> usize {
348 self.0.ms_overflow_pages as usize
349 }
350
351 #[inline]
353 pub const fn entries(&self) -> usize {
354 self.0.ms_entries as usize
355 }
356}
357
358#[derive(Debug)]
359#[repr(transparent)]
360pub struct GeometryInfo(ffi::MDBX_envinfo__bindgen_ty_1);
361
362impl GeometryInfo {
363 pub const fn min(&self) -> u64 {
364 self.0.lower
365 }
366}
367
368#[derive(Debug)]
372#[repr(transparent)]
373pub struct Info(ffi::MDBX_envinfo);
374
375impl Info {
376 pub const fn geometry(&self) -> GeometryInfo {
377 GeometryInfo(self.0.mi_geo)
378 }
379
380 #[inline]
382 pub const fn map_size(&self) -> usize {
383 self.0.mi_mapsize as usize
384 }
385
386 #[inline]
388 pub const fn last_pgno(&self) -> usize {
389 self.0.mi_last_pgno as usize
390 }
391
392 #[inline]
394 pub const fn last_txnid(&self) -> usize {
395 self.0.mi_recent_txnid as usize
396 }
397
398 #[inline]
400 pub const fn max_readers(&self) -> usize {
401 self.0.mi_maxreaders as usize
402 }
403
404 #[inline]
406 pub const fn num_readers(&self) -> usize {
407 self.0.mi_numreaders as usize
408 }
409
410 #[inline]
412 pub const fn page_ops(&self) -> PageOps {
413 PageOps {
414 newly: self.0.mi_pgop_stat.newly,
415 cow: self.0.mi_pgop_stat.cow,
416 clone: self.0.mi_pgop_stat.clone,
417 split: self.0.mi_pgop_stat.split,
418 merge: self.0.mi_pgop_stat.merge,
419 spill: self.0.mi_pgop_stat.spill,
420 unspill: self.0.mi_pgop_stat.unspill,
421 wops: self.0.mi_pgop_stat.wops,
422 prefault: self.0.mi_pgop_stat.prefault,
423 mincore: self.0.mi_pgop_stat.mincore,
424 msync: self.0.mi_pgop_stat.msync,
425 fsync: self.0.mi_pgop_stat.fsync,
426 }
427 }
428
429 #[inline]
431 pub const fn mode(&self) -> Mode {
432 let mode = self.0.mi_mode as ffi::MDBX_env_flags_t;
433 if (mode & ffi::MDBX_RDONLY) != 0 {
434 Mode::ReadOnly
435 } else if (mode & ffi::MDBX_UTTERLY_NOSYNC) != 0 {
436 Mode::ReadWrite { sync_mode: SyncMode::UtterlyNoSync }
437 } else if (mode & ffi::MDBX_NOMETASYNC) != 0 {
438 Mode::ReadWrite { sync_mode: SyncMode::NoMetaSync }
439 } else if (mode & ffi::MDBX_SAFE_NOSYNC) != 0 {
440 Mode::ReadWrite { sync_mode: SyncMode::SafeNoSync }
441 } else {
442 Mode::ReadWrite { sync_mode: SyncMode::Durable }
443 }
444 }
445}
446
447impl fmt::Debug for Environment {
448 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
449 f.debug_struct("Environment").field("kind", &self.inner.env_kind).finish_non_exhaustive()
450 }
451}
452
453#[derive(Clone, Debug, PartialEq, Eq)]
458pub enum PageSize {
459 MinimalAcceptable,
460 Set(usize),
461}
462
463#[derive(Clone, Debug, PartialEq, Eq)]
465pub struct PageOps {
466 pub newly: u64,
468 pub cow: u64,
470 pub clone: u64,
472 pub split: u64,
474 pub merge: u64,
476 pub spill: u64,
478 pub unspill: u64,
480 pub wops: u64,
482 pub msync: u64,
484 pub fsync: u64,
486 pub prefault: u64,
488 pub mincore: u64,
490}
491
492#[derive(Clone, Debug, PartialEq, Eq)]
494pub struct Geometry<R> {
495 pub size: Option<R>,
497 pub growth_step: Option<isize>,
498 pub shrink_threshold: Option<isize>,
499 pub page_size: Option<PageSize>,
500}
501
502impl<R> Default for Geometry<R> {
503 fn default() -> Self {
504 Self { size: None, growth_step: None, shrink_threshold: None, page_size: None }
505 }
506}
507
508pub type HandleSlowReadersCallback = extern "C" fn(
552 env: *const ffi::MDBX_env,
553 txn: *const ffi::MDBX_txn,
554 pid: ffi::mdbx_pid_t,
555 tid: ffi::mdbx_tid_t,
556 laggard: u64,
557 gap: std::ffi::c_uint,
558 space: usize,
559 retry: std::ffi::c_int,
560) -> HandleSlowReadersReturnCode;
561
562#[derive(Debug)]
563#[repr(i32)]
564pub enum HandleSlowReadersReturnCode {
565 Error = -2,
567 ProceedWithoutKillingReader = -1,
570 Success = 0,
575 ClearReaderSlot = 1,
579 ReaderProcessTerminated = 2,
582}
583
584#[derive(Debug, Clone)]
586pub struct EnvironmentBuilder {
587 flags: EnvironmentFlags,
588 max_readers: Option<u64>,
589 max_dbs: Option<u64>,
590 sync_bytes: Option<u64>,
591 sync_period: Option<u64>,
592 rp_augment_limit: Option<u64>,
593 loose_limit: Option<u64>,
594 dp_reserve_limit: Option<u64>,
595 txn_dp_limit: Option<u64>,
596 spill_max_denominator: Option<u64>,
597 spill_min_denominator: Option<u64>,
598 geometry: Option<Geometry<(Option<usize>, Option<usize>)>>,
599 log_level: Option<ffi::MDBX_log_level_t>,
600 kind: EnvironmentKind,
601 handle_slow_readers: Option<HandleSlowReadersCallback>,
602 #[cfg(feature = "read-tx-timeouts")]
603 max_read_transaction_duration: Option<read_transactions::MaxReadTransactionDuration>,
606}
607
608impl EnvironmentBuilder {
609 pub fn open(&self, path: &Path) -> Result<Environment> {
613 self.open_with_permissions(path, 0o644)
614 }
615
616 pub fn open_with_permissions(
620 &self,
621 path: &Path,
622 mode: ffi::mdbx_mode_t,
623 ) -> Result<Environment> {
624 let mut env: *mut ffi::MDBX_env = ptr::null_mut();
625 unsafe {
626 if let Some(log_level) = self.log_level {
627 ffi::mdbx_setup_debug(log_level, ffi::MDBX_DBG_DONTCHANGE, None);
630 }
631
632 mdbx_result(ffi::mdbx_env_create(&mut env))?;
633
634 if let Err(e) = (|| {
635 if let Some(geometry) = &self.geometry {
636 let mut min_size = -1;
637 let mut max_size = -1;
638
639 if let Some(size) = geometry.size {
640 if let Some(size) = size.0 {
641 min_size = size as isize;
642 }
643
644 if let Some(size) = size.1 {
645 max_size = size as isize;
646 }
647 }
648
649 mdbx_result(ffi::mdbx_env_set_geometry(
650 env,
651 min_size,
652 -1,
653 max_size,
654 geometry.growth_step.unwrap_or(-1),
655 geometry.shrink_threshold.unwrap_or(-1),
656 match geometry.page_size {
657 None => -1,
658 Some(PageSize::MinimalAcceptable) => 0,
659 Some(PageSize::Set(size)) => size as isize,
660 },
661 ))?;
662 }
663 for (opt, v) in [
664 (ffi::MDBX_opt_max_db, self.max_dbs),
665 (ffi::MDBX_opt_rp_augment_limit, self.rp_augment_limit),
666 (ffi::MDBX_opt_loose_limit, self.loose_limit),
667 (ffi::MDBX_opt_dp_reserve_limit, self.dp_reserve_limit),
668 (ffi::MDBX_opt_txn_dp_limit, self.txn_dp_limit),
669 (ffi::MDBX_opt_spill_max_denominator, self.spill_max_denominator),
670 (ffi::MDBX_opt_spill_min_denominator, self.spill_min_denominator),
671 ] {
672 if let Some(v) = v {
673 mdbx_result(ffi::mdbx_env_set_option(env, opt, v))?;
674 }
675 }
676
677 if let Some(max_readers) = self.max_readers {
679 mdbx_result(ffi::mdbx_env_set_option(
680 env,
681 ffi::MDBX_opt_max_readers,
682 max_readers,
683 ))?;
684 }
685
686 if let Some(handle_slow_readers) = self.handle_slow_readers {
687 mdbx_result(ffi::mdbx_env_set_hsr(
688 env,
689 convert_hsr_fn(Some(handle_slow_readers)),
690 ))?;
691 }
692
693 #[cfg(unix)]
694 fn path_to_bytes<P: AsRef<Path>>(path: P) -> Vec<u8> {
695 use std::os::unix::ffi::OsStrExt;
696 path.as_ref().as_os_str().as_bytes().to_vec()
697 }
698
699 #[cfg(windows)]
700 fn path_to_bytes<P: AsRef<Path>>(path: P) -> Vec<u8> {
701 path.as_ref().to_string_lossy().to_string().into_bytes()
705 }
706
707 let path = match CString::new(path_to_bytes(path)) {
708 Ok(path) => path,
709 Err(_) => return Err(Error::Invalid),
710 };
711 mdbx_result(ffi::mdbx_env_open(
712 env,
713 path.as_ptr(),
714 self.flags.make_flags() | self.kind.extra_flags(),
715 mode,
716 ))?;
717
718 for (opt, v) in [
719 (ffi::MDBX_opt_sync_bytes, self.sync_bytes),
720 (ffi::MDBX_opt_sync_period, self.sync_period),
721 ] {
722 if let Some(v) = v {
723 mdbx_result(ffi::mdbx_env_set_option(env, opt, v))?;
724 }
725 }
726
727 Ok(())
728 })() {
729 ffi::mdbx_env_close_ex(env, false);
730
731 return Err(e)
732 }
733 }
734
735 let env_ptr = EnvPtr(env);
736
737 #[cfg(not(feature = "read-tx-timeouts"))]
738 let txn_manager = TxnManager::new(env_ptr);
739
740 #[cfg(feature = "read-tx-timeouts")]
741 let txn_manager = {
742 if let crate::MaxReadTransactionDuration::Set(duration) = self
743 .max_read_transaction_duration
744 .unwrap_or(read_transactions::MaxReadTransactionDuration::Set(
745 DEFAULT_MAX_READ_TRANSACTION_DURATION,
746 ))
747 {
748 TxnManager::new_with_max_read_transaction_duration(env_ptr, duration)
749 } else {
750 TxnManager::new(env_ptr)
751 }
752 };
753
754 let env = EnvironmentInner { env, txn_manager, env_kind: self.kind };
755
756 Ok(Environment { inner: Arc::new(env) })
757 }
758
759 pub const fn set_kind(&mut self, kind: EnvironmentKind) -> &mut Self {
761 self.kind = kind;
762 self
763 }
764
765 pub const fn write_map(&mut self) -> &mut Self {
769 self.set_kind(EnvironmentKind::WriteMap)
770 }
771
772 pub const fn set_flags(&mut self, flags: EnvironmentFlags) -> &mut Self {
774 self.flags = flags;
775 self
776 }
777
778 pub const fn set_max_readers(&mut self, max_readers: u64) -> &mut Self {
784 self.max_readers = Some(max_readers);
785 self
786 }
787
788 pub const fn set_max_dbs(&mut self, v: usize) -> &mut Self {
798 self.max_dbs = Some(v as u64);
799 self
800 }
801
802 pub const fn set_sync_bytes(&mut self, v: usize) -> &mut Self {
805 self.sync_bytes = Some(v as u64);
806 self
807 }
808
809 pub fn set_sync_period(&mut self, v: Duration) -> &mut Self {
812 let as_mdbx_units = (v.as_secs_f64() * 65536f64) as u64;
814 self.sync_period = Some(as_mdbx_units);
815 self
816 }
817
818 pub const fn set_rp_augment_limit(&mut self, v: u64) -> &mut Self {
819 self.rp_augment_limit = Some(v);
820 self
821 }
822
823 pub const fn set_loose_limit(&mut self, v: u64) -> &mut Self {
824 self.loose_limit = Some(v);
825 self
826 }
827
828 pub const fn set_dp_reserve_limit(&mut self, v: u64) -> &mut Self {
829 self.dp_reserve_limit = Some(v);
830 self
831 }
832
833 pub const fn set_txn_dp_limit(&mut self, v: u64) -> &mut Self {
834 self.txn_dp_limit = Some(v);
835 self
836 }
837
838 pub fn set_spill_max_denominator(&mut self, v: u8) -> &mut Self {
839 self.spill_max_denominator = Some(v.into());
840 self
841 }
842
843 pub fn set_spill_min_denominator(&mut self, v: u8) -> &mut Self {
844 self.spill_min_denominator = Some(v.into());
845 self
846 }
847
848 pub fn set_geometry<R: RangeBounds<usize>>(&mut self, geometry: Geometry<R>) -> &mut Self {
851 let convert_bound = |bound: Bound<&usize>| match bound {
852 Bound::Included(v) | Bound::Excluded(v) => Some(*v),
853 _ => None,
854 };
855 self.geometry = Some(Geometry {
856 size: geometry.size.map(|range| {
857 (convert_bound(range.start_bound()), convert_bound(range.end_bound()))
858 }),
859 growth_step: geometry.growth_step,
860 shrink_threshold: geometry.shrink_threshold,
861 page_size: geometry.page_size,
862 });
863 self
864 }
865
866 pub const fn set_log_level(&mut self, log_level: ffi::MDBX_log_level_t) -> &mut Self {
867 self.log_level = Some(log_level);
868 self
869 }
870
871 pub fn set_handle_slow_readers(&mut self, hsr: HandleSlowReadersCallback) -> &mut Self {
874 self.handle_slow_readers = Some(hsr);
875 self
876 }
877}
878
879#[cfg(feature = "read-tx-timeouts")]
880pub(crate) mod read_transactions {
881 use crate::EnvironmentBuilder;
882 use std::time::Duration;
883
884 #[derive(Debug, Clone, Copy)]
886 #[cfg(feature = "read-tx-timeouts")]
887 pub enum MaxReadTransactionDuration {
888 Unbounded,
890 Set(Duration),
892 }
893
894 #[cfg(feature = "read-tx-timeouts")]
895 impl MaxReadTransactionDuration {
896 pub const fn as_duration(&self) -> Option<Duration> {
897 match self {
898 Self::Unbounded => None,
899 Self::Set(duration) => Some(*duration),
900 }
901 }
902 }
903
904 impl EnvironmentBuilder {
905 pub const fn set_max_read_transaction_duration(
907 &mut self,
908 max_read_transaction_duration: MaxReadTransactionDuration,
909 ) -> &mut Self {
910 self.max_read_transaction_duration = Some(max_read_transaction_duration);
911 self
912 }
913 }
914}
915
916fn convert_hsr_fn(callback: Option<HandleSlowReadersCallback>) -> ffi::MDBX_hsr_func {
918 unsafe { std::mem::transmute(callback) }
919}
920
921#[cfg(test)]
922mod tests {
923 use crate::{Environment, Error, Geometry, HandleSlowReadersReturnCode, PageSize, WriteFlags};
924 use std::{
925 ops::RangeInclusive,
926 sync::atomic::{AtomicBool, Ordering},
927 };
928
929 #[test]
930 fn test_handle_slow_readers_callback() {
931 static CALLED: AtomicBool = AtomicBool::new(false);
932
933 extern "C" fn handle_slow_readers(
934 _env: *const ffi::MDBX_env,
935 _txn: *const ffi::MDBX_txn,
936 _pid: ffi::mdbx_pid_t,
937 _tid: ffi::mdbx_tid_t,
938 _laggard: u64,
939 _gap: std::ffi::c_uint,
940 _space: usize,
941 _retry: std::ffi::c_int,
942 ) -> HandleSlowReadersReturnCode {
943 CALLED.store(true, Ordering::Relaxed);
944 HandleSlowReadersReturnCode::ProceedWithoutKillingReader
945 }
946
947 let tempdir = tempfile::tempdir().unwrap();
948 let env = Environment::builder()
949 .set_geometry(Geometry::<RangeInclusive<usize>> {
950 size: Some(0..=1024 * 1024), page_size: Some(PageSize::MinimalAcceptable), ..Default::default()
953 })
954 .set_handle_slow_readers(handle_slow_readers)
955 .open(tempdir.path())
956 .unwrap();
957
958 {
960 let tx = env.begin_rw_txn().unwrap();
961 let db = tx.open_db(None).unwrap();
962 for i in 0usize..1_000 {
963 tx.put(db.dbi(), i.to_le_bytes(), b"0", WriteFlags::empty()).unwrap()
964 }
965 tx.commit().unwrap();
966 }
967
968 let _tx_ro = env.begin_ro_txn().unwrap();
970
971 {
973 let tx = env.begin_rw_txn().unwrap();
974 let db = tx.open_db(None).unwrap();
975 for i in 0usize..1_000 {
976 tx.put(db.dbi(), i.to_le_bytes(), b"1", WriteFlags::empty()).unwrap();
977 }
978 tx.commit().unwrap();
979 }
980
981 {
984 let tx = env.begin_rw_txn().unwrap();
985 let db = tx.open_db(None).unwrap();
986 for i in 1_000usize..1_000_000 {
987 match tx.put(db.dbi(), i.to_le_bytes(), b"0", WriteFlags::empty()) {
988 Ok(_) => {}
989 Err(Error::MapFull) => break,
990 result @ Err(_) => result.unwrap(),
991 }
992 }
993 tx.commit().unwrap();
994 }
995
996 assert!(CALLED.load(Ordering::Relaxed));
998 }
999}