@@ -2,6 +2,7 @@ use std::fs::{canonicalize, create_dir_all, OpenOptions};
22use std:: mem;
33use std:: os:: unix:: io:: AsRawFd ;
44use std:: path:: { Path , PathBuf } ;
5+ use std:: time:: Duration ;
56#[ cfg( feature = "v1" ) ]
67use std:: { borrow:: Cow , collections:: HashMap } ;
78
@@ -24,7 +25,16 @@ use super::symlink::SymlinkError;
2425use super :: utils:: { parse_mount, MountOptionConfig } ;
2526use crate :: syscall:: syscall:: create_syscall;
2627use crate :: syscall:: { linux, Syscall , SyscallError } ;
27- use crate :: utils:: PathBufExt ;
28+ use crate :: utils:: { retry, PathBufExt } ;
29+
30+ const MAX_EBUSY_MOUNT_ATTEMPTS : u32 = 3 ;
31+ // runc has a retry interval of 100ms. We are following this.
32+ // https://github.com/opencontainers/runc/blob/v1.3.0/libcontainer/rootfs_linux.go#L1235
33+ #[ cfg( not( test) ) ]
34+ const MOUNT_RETRY_DELAY_MS : u64 = 100 ;
35+ // In tests, there is no need to delay, so set it to 0ms.
36+ #[ cfg( test) ]
37+ const MOUNT_RETRY_DELAY_MS : u64 = 0 ;
2838
2939#[ derive( Debug , thiserror:: Error ) ]
3040pub enum MountError {
@@ -551,24 +561,35 @@ impl Mount {
551561 . mount ( Some ( & * src) , dest, typ, mount_option_config. flags , Some ( & * d) )
552562 {
553563 if let SyscallError :: Nix ( errno) = err {
554- if !matches ! ( errno, Errno :: EINVAL ) {
555- tracing:: error!( "mount of {:?} failed. {}" , m. destination( ) , errno) ;
564+ if matches ! ( errno, Errno :: EINVAL ) {
565+ self . syscall . mount (
566+ Some ( & * src) ,
567+ dest,
568+ typ,
569+ mount_option_config. flags ,
570+ Some ( & mount_option_config. data ) ,
571+ ) ?;
572+ } else if matches ! ( errno, Errno :: EBUSY ) {
573+ let mount_op = || -> std:: result:: Result < ( ) , SyscallError > {
574+ self . syscall . mount (
575+ Some ( & * src) ,
576+ dest,
577+ typ,
578+ mount_option_config. flags ,
579+ Some ( & * d) ,
580+ )
581+ } ;
582+ let delay = Duration :: from_millis ( MOUNT_RETRY_DELAY_MS ) ;
583+ let retry_policy = |err : & SyscallError | -> bool {
584+ matches ! ( err, SyscallError :: Nix ( Errno :: EBUSY ) )
585+ } ;
586+ retry ( mount_op, MAX_EBUSY_MOUNT_ATTEMPTS - 1 , delay, retry_policy) ?;
587+ } else {
556588 return Err ( err. into ( ) ) ;
557589 }
590+ } else {
591+ return Err ( err. into ( ) ) ;
558592 }
559-
560- self . syscall
561- . mount (
562- Some ( & * src) ,
563- dest,
564- typ,
565- mount_option_config. flags ,
566- Some ( & mount_option_config. data ) ,
567- )
568- . map_err ( |err| {
569- tracing:: error!( "failed to mount {src:?} to {dest:?}" ) ;
570- err
571- } ) ?;
572593 }
573594
574595 if typ == Some ( "bind" )
@@ -635,7 +656,7 @@ mod tests {
635656 use anyhow:: { Context , Ok , Result } ;
636657
637658 use super :: * ;
638- use crate :: syscall:: test:: { MountArgs , TestHelperSyscall } ;
659+ use crate :: syscall:: test:: { ArgName , MountArgs , TestHelperSyscall } ;
639660
640661 #[ test]
641662 fn test_mount_into_container ( ) -> Result < ( ) > {
@@ -729,6 +750,102 @@ mod tests {
729750 assert_eq ! ( want, * got) ;
730751 assert_eq ! ( got. len( ) , 2 ) ;
731752 }
753+ {
754+ let m = Mount :: new ( ) ;
755+ let mount = & SpecMountBuilder :: default ( )
756+ . destination ( PathBuf :: from ( "/tmp/retry" ) )
757+ . typ ( "tmpfs" )
758+ . source ( PathBuf :: from ( "tmpfs" ) )
759+ . build ( ) ?;
760+ let mount_option_config = parse_mount ( mount) ?;
761+
762+ let syscall = m
763+ . syscall
764+ . as_any ( )
765+ . downcast_ref :: < TestHelperSyscall > ( )
766+ . unwrap ( ) ;
767+ syscall. set_ret_err ( ArgName :: Mount , || {
768+ Err ( crate :: syscall:: SyscallError :: Nix ( nix:: errno:: Errno :: EINVAL ) )
769+ } ) ;
770+ syscall. set_ret_err_times ( ArgName :: Mount , 1 ) ;
771+
772+ assert ! ( m
773+ . mount_into_container( mount, tmp_dir. path( ) , & mount_option_config, None )
774+ . is_ok( ) ) ;
775+ assert_eq ! ( syscall. get_mount_args( ) . len( ) , 1 ) ;
776+ }
777+ {
778+ let m = Mount :: new ( ) ;
779+ let mount = & SpecMountBuilder :: default ( )
780+ . destination ( PathBuf :: from ( "/tmp/retry" ) )
781+ . typ ( "tmpfs" )
782+ . source ( PathBuf :: from ( "tmpfs" ) )
783+ . build ( ) ?;
784+ let mount_option_config = parse_mount ( mount) ?;
785+
786+ let syscall = m
787+ . syscall
788+ . as_any ( )
789+ . downcast_ref :: < TestHelperSyscall > ( )
790+ . unwrap ( ) ;
791+ syscall. set_ret_err ( ArgName :: Mount , || {
792+ Err ( crate :: syscall:: SyscallError :: Nix ( nix:: errno:: Errno :: EINVAL ) )
793+ } ) ;
794+ syscall. set_ret_err_times ( ArgName :: Mount , 2 ) ;
795+
796+ assert ! ( m
797+ . mount_into_container( mount, tmp_dir. path( ) , & mount_option_config, None )
798+ . is_err( ) ) ;
799+ assert_eq ! ( syscall. get_mount_args( ) . len( ) , 0 ) ;
800+ }
801+ {
802+ let m = Mount :: new ( ) ;
803+ let mount = & SpecMountBuilder :: default ( )
804+ . destination ( PathBuf :: from ( "/tmp/retry" ) )
805+ . typ ( "tmpfs" )
806+ . source ( PathBuf :: from ( "tmpfs" ) )
807+ . build ( ) ?;
808+ let mount_option_config = parse_mount ( mount) ?;
809+
810+ let syscall = m
811+ . syscall
812+ . as_any ( )
813+ . downcast_ref :: < TestHelperSyscall > ( )
814+ . unwrap ( ) ;
815+ syscall. set_ret_err ( ArgName :: Mount , || {
816+ Err ( crate :: syscall:: SyscallError :: Nix ( nix:: errno:: Errno :: EBUSY ) )
817+ } ) ;
818+ syscall. set_ret_err_times ( ArgName :: Mount , MAX_EBUSY_MOUNT_ATTEMPTS as usize - 1 ) ;
819+
820+ assert ! ( m
821+ . mount_into_container( mount, tmp_dir. path( ) , & mount_option_config, None )
822+ . is_ok( ) ) ;
823+ assert_eq ! ( syscall. get_mount_args( ) . len( ) , 1 ) ;
824+ }
825+ {
826+ let m = Mount :: new ( ) ;
827+ let mount = & SpecMountBuilder :: default ( )
828+ . destination ( PathBuf :: from ( "/tmp/retry" ) )
829+ . typ ( "tmpfs" )
830+ . source ( PathBuf :: from ( "tmpfs" ) )
831+ . build ( ) ?;
832+ let mount_option_config = parse_mount ( mount) ?;
833+
834+ let syscall = m
835+ . syscall
836+ . as_any ( )
837+ . downcast_ref :: < TestHelperSyscall > ( )
838+ . unwrap ( ) ;
839+ syscall. set_ret_err ( ArgName :: Mount , || {
840+ Err ( crate :: syscall:: SyscallError :: Nix ( nix:: errno:: Errno :: EBUSY ) )
841+ } ) ;
842+ syscall. set_ret_err_times ( ArgName :: Mount , MAX_EBUSY_MOUNT_ATTEMPTS as usize ) ;
843+
844+ assert ! ( m
845+ . mount_into_container( mount, tmp_dir. path( ) , & mount_option_config, None )
846+ . is_err( ) ) ;
847+ assert_eq ! ( syscall. get_mount_args( ) . len( ) , 0 ) ;
848+ }
732849
733850 Ok ( ( ) )
734851 }
0 commit comments