@@ -29,14 +29,12 @@ pub struct Env {
29
29
}
30
30
31
31
impl Env {
32
- pub ( crate ) fn new ( env_ptr : NonNull < MDB_env > , path : PathBuf ) -> Env {
33
- Env {
34
- inner : Arc :: new ( EnvInner {
35
- env_ptr,
36
- path,
37
- signal_event : Arc :: new ( SignalEvent :: manual ( false ) ) ,
38
- } ) ,
39
- }
32
+ pub ( crate ) fn new (
33
+ env_ptr : NonNull < MDB_env > ,
34
+ path : PathBuf ,
35
+ signal_event : Arc < SignalEvent > ,
36
+ ) -> Env {
37
+ Env { inner : Arc :: new ( EnvInner { env_ptr, path, signal_event } ) }
40
38
}
41
39
42
40
pub ( crate ) fn env_mut_ptr ( & self ) -> NonNull < ffi:: MDB_env > {
@@ -492,7 +490,6 @@ impl fmt::Debug for Env {
492
490
}
493
491
}
494
492
495
- #[ derive( Clone ) ]
496
493
pub ( crate ) struct EnvInner {
497
494
env_ptr : NonNull < MDB_env > ,
498
495
signal_event : Arc < SignalEvent > ,
@@ -501,9 +498,332 @@ pub(crate) struct EnvInner {
501
498
502
499
impl Drop for EnvInner {
503
500
fn drop ( & mut self ) {
504
- unsafe { ffi:: mdb_env_close ( self . env_ptr . as_mut ( ) ) } ;
505
501
let mut lock = OPENED_ENV . write ( ) . unwrap ( ) ;
506
502
let removed = lock. remove ( & self . path ) ;
507
- debug_assert ! ( removed) ;
503
+ debug_assert ! ( removed. is_some( ) ) ;
504
+ unsafe { ffi:: mdb_env_close ( self . env_ptr . as_mut ( ) ) } ;
505
+ self . signal_event . signal ( ) ;
506
+ }
507
+ }
508
+
509
+ #[ cfg( test) ]
510
+ mod tests {
511
+ use std:: io:: ErrorKind ;
512
+ use std:: time:: Duration ;
513
+ use std:: { fs, thread} ;
514
+
515
+ use crate :: types:: * ;
516
+ use crate :: { env_closing_event, EnvOpenOptions , Error } ;
517
+
518
+ #[ test]
519
+ fn close_env ( ) {
520
+ let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
521
+ let env = unsafe {
522
+ EnvOpenOptions :: new ( )
523
+ . map_size ( 10 * 1024 * 1024 ) // 10MB
524
+ . max_dbs ( 30 )
525
+ . open ( dir. path ( ) )
526
+ . unwrap ( )
527
+ } ;
528
+
529
+ // Force a thread to keep the env for 1 second.
530
+ let env_cloned = env. clone ( ) ;
531
+ thread:: spawn ( move || {
532
+ let _env = env_cloned;
533
+ thread:: sleep ( Duration :: from_secs ( 1 ) ) ;
534
+ } ) ;
535
+
536
+ let mut wtxn = env. write_txn ( ) . unwrap ( ) ;
537
+ let db = env. create_database :: < Str , Str > ( & mut wtxn, None ) . unwrap ( ) ;
538
+ wtxn. commit ( ) . unwrap ( ) ;
539
+
540
+ // Create an ordered list of keys...
541
+ let mut wtxn = env. write_txn ( ) . unwrap ( ) ;
542
+ db. put ( & mut wtxn, "hello" , "hello" ) . unwrap ( ) ;
543
+ db. put ( & mut wtxn, "world" , "world" ) . unwrap ( ) ;
544
+
545
+ let mut iter = db. iter ( & wtxn) . unwrap ( ) ;
546
+ assert_eq ! ( iter. next( ) . transpose( ) . unwrap( ) , Some ( ( "hello" , "hello" ) ) ) ;
547
+ assert_eq ! ( iter. next( ) . transpose( ) . unwrap( ) , Some ( ( "world" , "world" ) ) ) ;
548
+ assert_eq ! ( iter. next( ) . transpose( ) . unwrap( ) , None ) ;
549
+ drop ( iter) ;
550
+
551
+ wtxn. commit ( ) . unwrap ( ) ;
552
+
553
+ let signal_event = env. prepare_for_closing ( ) ;
554
+
555
+ eprintln ! ( "waiting for the env to be closed" ) ;
556
+ signal_event. wait ( ) ;
557
+ eprintln ! ( "env closed successfully" ) ;
558
+
559
+ // Make sure we don't have a reference to the env
560
+ assert ! ( env_closing_event( dir. path( ) ) . is_none( ) ) ;
561
+ }
562
+
563
+ #[ test]
564
+ fn reopen_env_with_different_options_is_err ( ) {
565
+ let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
566
+ let _env = unsafe {
567
+ EnvOpenOptions :: new ( )
568
+ . map_size ( 10 * 1024 * 1024 ) // 10MB
569
+ . open ( dir. path ( ) )
570
+ . unwrap ( )
571
+ } ;
572
+
573
+ let result = unsafe {
574
+ EnvOpenOptions :: new ( )
575
+ . map_size ( 12 * 1024 * 1024 ) // 12MB
576
+ . open ( dir. path ( ) )
577
+ } ;
578
+
579
+ assert ! ( matches!( result, Err ( Error :: EnvAlreadyOpened ) ) ) ;
580
+ }
581
+
582
+ #[ test]
583
+ fn open_env_with_named_path ( ) {
584
+ let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
585
+ fs:: create_dir_all ( dir. path ( ) . join ( "babar.mdb" ) ) . unwrap ( ) ;
586
+ let _env = unsafe {
587
+ EnvOpenOptions :: new ( )
588
+ . map_size ( 10 * 1024 * 1024 ) // 10MB
589
+ . open ( dir. path ( ) . join ( "babar.mdb" ) )
590
+ . unwrap ( )
591
+ } ;
592
+
593
+ let error = unsafe {
594
+ EnvOpenOptions :: new ( )
595
+ . map_size ( 10 * 1024 * 1024 ) // 10MB
596
+ . open ( dir. path ( ) . join ( "babar.mdb" ) )
597
+ . unwrap_err ( )
598
+ } ;
599
+
600
+ assert ! ( matches!( error, Error :: EnvAlreadyOpened ) ) ;
601
+ }
602
+
603
+ #[ test]
604
+ #[ cfg( not( windows) ) ]
605
+ fn open_database_with_writemap_flag ( ) {
606
+ let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
607
+ let mut envbuilder = EnvOpenOptions :: new ( ) ;
608
+ envbuilder. map_size ( 10 * 1024 * 1024 ) ; // 10MB
609
+ envbuilder. max_dbs ( 10 ) ;
610
+ unsafe { envbuilder. flags ( crate :: EnvFlags :: WRITE_MAP ) } ;
611
+ let env = unsafe { envbuilder. open ( dir. path ( ) ) . unwrap ( ) } ;
612
+
613
+ let mut wtxn = env. write_txn ( ) . unwrap ( ) ;
614
+ let _db = env. create_database :: < Str , Str > ( & mut wtxn, Some ( "my-super-db" ) ) . unwrap ( ) ;
615
+ wtxn. commit ( ) . unwrap ( ) ;
616
+ }
617
+
618
+ #[ test]
619
+ fn open_database_with_nosubdir ( ) {
620
+ let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
621
+ let mut envbuilder = EnvOpenOptions :: new ( ) ;
622
+ unsafe { envbuilder. flags ( crate :: EnvFlags :: NO_SUB_DIR ) } ;
623
+ let _env = unsafe { envbuilder. open ( dir. path ( ) . join ( "data.mdb" ) ) . unwrap ( ) } ;
624
+ }
625
+
626
+ #[ test]
627
+ fn create_database_without_commit ( ) {
628
+ let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
629
+ let env = unsafe {
630
+ EnvOpenOptions :: new ( )
631
+ . map_size ( 10 * 1024 * 1024 ) // 10MB
632
+ . max_dbs ( 10 )
633
+ . open ( dir. path ( ) )
634
+ . unwrap ( )
635
+ } ;
636
+
637
+ let mut wtxn = env. write_txn ( ) . unwrap ( ) ;
638
+ let _db = env. create_database :: < Str , Str > ( & mut wtxn, Some ( "my-super-db" ) ) . unwrap ( ) ;
639
+ wtxn. abort ( ) ;
640
+
641
+ let rtxn = env. read_txn ( ) . unwrap ( ) ;
642
+ let option = env. open_database :: < Str , Str > ( & rtxn, Some ( "my-super-db" ) ) . unwrap ( ) ;
643
+ assert ! ( option. is_none( ) ) ;
644
+ }
645
+
646
+ #[ test]
647
+ fn open_already_existing_database ( ) {
648
+ let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
649
+ let env = unsafe {
650
+ EnvOpenOptions :: new ( )
651
+ . map_size ( 10 * 1024 * 1024 ) // 10MB
652
+ . max_dbs ( 10 )
653
+ . open ( dir. path ( ) )
654
+ . unwrap ( )
655
+ } ;
656
+
657
+ // we first create a database
658
+ let mut wtxn = env. write_txn ( ) . unwrap ( ) ;
659
+ let _db = env. create_database :: < Str , Str > ( & mut wtxn, Some ( "my-super-db" ) ) . unwrap ( ) ;
660
+ wtxn. commit ( ) . unwrap ( ) ;
661
+
662
+ // Close the environement and reopen it, databases must not be loaded in memory.
663
+ env. prepare_for_closing ( ) . wait ( ) ;
664
+ let env = unsafe {
665
+ EnvOpenOptions :: new ( )
666
+ . map_size ( 10 * 1024 * 1024 ) // 10MB
667
+ . max_dbs ( 10 )
668
+ . open ( dir. path ( ) )
669
+ . unwrap ( )
670
+ } ;
671
+
672
+ let rtxn = env. read_txn ( ) . unwrap ( ) ;
673
+ let option = env. open_database :: < Str , Str > ( & rtxn, Some ( "my-super-db" ) ) . unwrap ( ) ;
674
+ assert ! ( option. is_some( ) ) ;
675
+ }
676
+
677
+ #[ test]
678
+ fn resize_database ( ) {
679
+ let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
680
+ let page_size = page_size:: get ( ) ;
681
+ let env = unsafe {
682
+ EnvOpenOptions :: new ( ) . map_size ( 9 * page_size) . max_dbs ( 1 ) . open ( dir. path ( ) ) . unwrap ( )
683
+ } ;
684
+
685
+ let mut wtxn = env. write_txn ( ) . unwrap ( ) ;
686
+ let db = env. create_database :: < Str , Str > ( & mut wtxn, Some ( "my-super-db" ) ) . unwrap ( ) ;
687
+ wtxn. commit ( ) . unwrap ( ) ;
688
+
689
+ let mut wtxn = env. write_txn ( ) . unwrap ( ) ;
690
+ for i in 0 ..64 {
691
+ db. put ( & mut wtxn, & i. to_string ( ) , "world" ) . unwrap ( ) ;
692
+ }
693
+ wtxn. commit ( ) . unwrap ( ) ;
694
+
695
+ let mut wtxn = env. write_txn ( ) . unwrap ( ) ;
696
+ for i in 64 ..128 {
697
+ db. put ( & mut wtxn, & i. to_string ( ) , "world" ) . unwrap ( ) ;
698
+ }
699
+ wtxn. commit ( ) . expect_err ( "cannot commit a transaction that would reach the map size limit" ) ;
700
+
701
+ unsafe {
702
+ env. resize ( 10 * page_size) . unwrap ( ) ;
703
+ }
704
+ let mut wtxn = env. write_txn ( ) . unwrap ( ) ;
705
+ for i in 64 ..128 {
706
+ db. put ( & mut wtxn, & i. to_string ( ) , "world" ) . unwrap ( ) ;
707
+ }
708
+ wtxn. commit ( ) . expect ( "transaction should commit after resizing the map size" ) ;
709
+
710
+ assert_eq ! ( 10 * page_size, env. info( ) . map_size) ;
711
+ }
712
+
713
+ /// Non-regression test for
714
+ /// <https://github.com/meilisearch/heed/issues/183>
715
+ ///
716
+ /// We should be able to open database Read-Only Env with
717
+ /// no prior Read-Write Env opening. And query data.
718
+ #[ test]
719
+ fn open_read_only_without_no_env_opened_before ( ) {
720
+ let expected_data0 = "Data Expected db0" ;
721
+ let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
722
+
723
+ {
724
+ // We really need this env to be dropped before the read-only access.
725
+ let env = unsafe {
726
+ EnvOpenOptions :: new ( )
727
+ . map_size ( 16 * 1024 * 1024 * 1024 ) // 10MB
728
+ . max_dbs ( 32 )
729
+ . open ( dir. path ( ) )
730
+ . unwrap ( )
731
+ } ;
732
+ let mut wtxn = env. write_txn ( ) . unwrap ( ) ;
733
+ let database0 = env. create_database :: < Str , Str > ( & mut wtxn, Some ( "shared0" ) ) . unwrap ( ) ;
734
+
735
+ wtxn. commit ( ) . unwrap ( ) ;
736
+ let mut wtxn = env. write_txn ( ) . unwrap ( ) ;
737
+ database0. put ( & mut wtxn, "shared0" , expected_data0) . unwrap ( ) ;
738
+ wtxn. commit ( ) . unwrap ( ) ;
739
+ // We also really need that no other env reside in memory in other thread doing tests.
740
+ env. prepare_for_closing ( ) . wait ( ) ;
741
+ }
742
+
743
+ {
744
+ // Open now we do a read-only opening
745
+ let env = unsafe {
746
+ EnvOpenOptions :: new ( )
747
+ . map_size ( 16 * 1024 * 1024 * 1024 ) // 10MB
748
+ . max_dbs ( 32 )
749
+ . open ( dir. path ( ) )
750
+ . unwrap ( )
751
+ } ;
752
+ let database0 = {
753
+ let rtxn = env. read_txn ( ) . unwrap ( ) ;
754
+ let database0 =
755
+ env. open_database :: < Str , Str > ( & rtxn, Some ( "shared0" ) ) . unwrap ( ) . unwrap ( ) ;
756
+ // This commit is mandatory if not committed you might get
757
+ // Io(Os { code: 22, kind: InvalidInput, message: "Invalid argument" })
758
+ rtxn. commit ( ) . unwrap ( ) ;
759
+ database0
760
+ } ;
761
+
762
+ {
763
+ // If we didn't committed the opening it might fail with EINVAL.
764
+ let rtxn = env. read_txn ( ) . unwrap ( ) ;
765
+ let value = database0. get ( & rtxn, "shared0" ) . unwrap ( ) . unwrap ( ) ;
766
+ assert_eq ! ( value, expected_data0) ;
767
+ }
768
+
769
+ env. prepare_for_closing ( ) . wait ( ) ;
770
+ }
771
+
772
+ // To avoid reintroducing the bug let's try to open again but without the commit
773
+ {
774
+ // Open now we do a read-only opening
775
+ let env = unsafe {
776
+ EnvOpenOptions :: new ( )
777
+ . map_size ( 16 * 1024 * 1024 * 1024 ) // 10MB
778
+ . max_dbs ( 32 )
779
+ . open ( dir. path ( ) )
780
+ . unwrap ( )
781
+ } ;
782
+ let database0 = {
783
+ let rtxn = env. read_txn ( ) . unwrap ( ) ;
784
+ let database0 =
785
+ env. open_database :: < Str , Str > ( & rtxn, Some ( "shared0" ) ) . unwrap ( ) . unwrap ( ) ;
786
+ // No commit it's important, dropping explicitly
787
+ drop ( rtxn) ;
788
+ database0
789
+ } ;
790
+
791
+ {
792
+ // We didn't committed the opening we will get EINVAL.
793
+ let rtxn = env. read_txn ( ) . unwrap ( ) ;
794
+ // The dbg!() is intentional in case of a change in rust-std or in lmdb related
795
+ // to the windows error.
796
+ let err = dbg ! ( database0. get( & rtxn, "shared0" ) ) ;
797
+
798
+ // The error kind is still ErrorKind Uncategorized on windows.
799
+ // Behind it's a ERROR_BAD_COMMAND code 22 like EINVAL.
800
+ if cfg ! ( windows) {
801
+ assert ! ( err. is_err( ) ) ;
802
+ } else {
803
+ assert ! (
804
+ matches!( err, Err ( Error :: Io ( ref e) ) if e. kind( ) == ErrorKind :: InvalidInput )
805
+ ) ;
806
+ }
807
+ }
808
+
809
+ env. prepare_for_closing ( ) . wait ( ) ;
810
+ }
811
+ }
812
+
813
+ #[ test]
814
+ fn max_key_size ( ) {
815
+ let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
816
+ let env = unsafe { EnvOpenOptions :: new ( ) . open ( dir. path ( ) . join ( dir. path ( ) ) ) . unwrap ( ) } ;
817
+ let maxkeysize = env. max_key_size ( ) ;
818
+
819
+ eprintln ! ( "maxkeysize: {}" , maxkeysize) ;
820
+
821
+ if cfg ! ( feature = "longer-keys" ) {
822
+ // Should be larger than the default of 511
823
+ assert ! ( maxkeysize > 511 ) ;
824
+ } else {
825
+ // Should be the default of 511
826
+ assert_eq ! ( maxkeysize, 511 ) ;
827
+ }
508
828
}
509
829
}
0 commit comments