|
1 | 1 | use std::borrow::Cow; |
2 | | -use std::fmt::Display; |
| 2 | +use std::io; |
3 | 3 | use std::path::{Path, PathBuf}; |
4 | 4 |
|
5 | 5 | use tempfile::NamedTempFile; |
6 | | -use tracing::{debug, error, info, trace, warn}; |
| 6 | +use tracing::warn; |
7 | 7 |
|
| 8 | +pub use crate::locked_file::*; |
8 | 9 | pub use crate::path::*; |
9 | 10 |
|
10 | 11 | pub mod cachedir; |
| 12 | +mod locked_file; |
11 | 13 | mod path; |
12 | 14 | pub mod which; |
13 | 15 |
|
@@ -666,218 +668,6 @@ fn is_known_already_locked_error(err: &std::fs::TryLockError) -> bool { |
666 | 668 | } |
667 | 669 | } |
668 | 670 |
|
669 | | -/// A file lock that is automatically released when dropped. |
670 | | -#[derive(Debug)] |
671 | | -#[must_use] |
672 | | -pub struct LockedFile(fs_err::File); |
673 | | - |
674 | | -impl LockedFile { |
675 | | - /// Inner implementation for [`LockedFile::acquire_blocking`] and [`LockedFile::acquire`]. |
676 | | - fn lock_file_blocking(file: fs_err::File, resource: &str) -> Result<Self, std::io::Error> { |
677 | | - trace!( |
678 | | - "Checking lock for `{resource}` at `{}`", |
679 | | - file.path().user_display() |
680 | | - ); |
681 | | - match file.file().try_lock() { |
682 | | - Ok(()) => { |
683 | | - debug!("Acquired lock for `{resource}`"); |
684 | | - Ok(Self(file)) |
685 | | - } |
686 | | - Err(err) => { |
687 | | - // Log error code and enum kind to help debugging more exotic failures. |
688 | | - if !is_known_already_locked_error(&err) { |
689 | | - debug!("Try lock error: {err:?}"); |
690 | | - } |
691 | | - info!( |
692 | | - "Waiting to acquire lock for `{resource}` at `{}`", |
693 | | - file.path().user_display(), |
694 | | - ); |
695 | | - file.lock()?; |
696 | | - debug!("Acquired lock for `{resource}`"); |
697 | | - Ok(Self(file)) |
698 | | - } |
699 | | - } |
700 | | - } |
701 | | - |
702 | | - /// Inner implementation for [`LockedFile::acquire_no_wait`]. |
703 | | - fn lock_file_no_wait(file: fs_err::File, resource: &str) -> Option<Self> { |
704 | | - trace!( |
705 | | - "Checking lock for `{resource}` at `{}`", |
706 | | - file.path().user_display() |
707 | | - ); |
708 | | - match file.try_lock() { |
709 | | - Ok(()) => { |
710 | | - debug!("Acquired lock for `{resource}`"); |
711 | | - Some(Self(file)) |
712 | | - } |
713 | | - Err(err) => { |
714 | | - // Log error code and enum kind to help debugging more exotic failures. |
715 | | - if !is_known_already_locked_error(&err) { |
716 | | - debug!("Try lock error: {err:?}"); |
717 | | - } |
718 | | - debug!("Lock is busy for `{resource}`"); |
719 | | - None |
720 | | - } |
721 | | - } |
722 | | - } |
723 | | - |
724 | | - /// Inner implementation for [`LockedFile::acquire_shared_blocking`] and |
725 | | - /// [`LockedFile::acquire_blocking`]. |
726 | | - fn lock_file_shared_blocking( |
727 | | - file: fs_err::File, |
728 | | - resource: &str, |
729 | | - ) -> Result<Self, std::io::Error> { |
730 | | - trace!( |
731 | | - "Checking shared lock for `{resource}` at `{}`", |
732 | | - file.path().user_display() |
733 | | - ); |
734 | | - match file.try_lock_shared() { |
735 | | - Ok(()) => { |
736 | | - debug!("Acquired shared lock for `{resource}`"); |
737 | | - Ok(Self(file)) |
738 | | - } |
739 | | - Err(err) => { |
740 | | - // Log error code and enum kind to help debugging more exotic failures. |
741 | | - if !is_known_already_locked_error(&err) { |
742 | | - debug!("Try lock error: {err:?}"); |
743 | | - } |
744 | | - info!( |
745 | | - "Waiting to acquire shared lock for `{resource}` at `{}`", |
746 | | - file.path().user_display(), |
747 | | - ); |
748 | | - file.lock_shared()?; |
749 | | - debug!("Acquired shared lock for `{resource}`"); |
750 | | - Ok(Self(file)) |
751 | | - } |
752 | | - } |
753 | | - } |
754 | | - |
755 | | - /// The same as [`LockedFile::acquire`], but for synchronous contexts. |
756 | | - /// |
757 | | - /// Do not use from an async context, as this can block the runtime while waiting for another |
758 | | - /// process to release the lock. |
759 | | - pub fn acquire_blocking( |
760 | | - path: impl AsRef<Path>, |
761 | | - resource: impl Display, |
762 | | - ) -> Result<Self, std::io::Error> { |
763 | | - let file = Self::create(path)?; |
764 | | - let resource = resource.to_string(); |
765 | | - Self::lock_file_blocking(file, &resource) |
766 | | - } |
767 | | - |
768 | | - /// The same as [`LockedFile::acquire_blocking`], but for synchronous contexts. |
769 | | - /// |
770 | | - /// Do not use from an async context, as this can block the runtime while waiting for another |
771 | | - /// process to release the lock. |
772 | | - pub fn acquire_shared_blocking( |
773 | | - path: impl AsRef<Path>, |
774 | | - resource: impl Display, |
775 | | - ) -> Result<Self, std::io::Error> { |
776 | | - let file = Self::create(path)?; |
777 | | - let resource = resource.to_string(); |
778 | | - Self::lock_file_shared_blocking(file, &resource) |
779 | | - } |
780 | | - |
781 | | - /// Acquire a cross-process lock for a resource using a file at the provided path. |
782 | | - #[cfg(feature = "tokio")] |
783 | | - pub async fn acquire( |
784 | | - path: impl AsRef<Path>, |
785 | | - resource: impl Display, |
786 | | - ) -> Result<Self, std::io::Error> { |
787 | | - let file = Self::create(path)?; |
788 | | - let resource = resource.to_string(); |
789 | | - tokio::task::spawn_blocking(move || Self::lock_file_blocking(file, &resource)).await? |
790 | | - } |
791 | | - |
792 | | - /// Acquire a cross-process read lock for a shared resource using a file at the provided path. |
793 | | - #[cfg(feature = "tokio")] |
794 | | - pub async fn acquire_shared( |
795 | | - path: impl AsRef<Path>, |
796 | | - resource: impl Display, |
797 | | - ) -> Result<Self, std::io::Error> { |
798 | | - let file = Self::create(path)?; |
799 | | - let resource = resource.to_string(); |
800 | | - tokio::task::spawn_blocking(move || Self::lock_file_shared_blocking(file, &resource)) |
801 | | - .await? |
802 | | - } |
803 | | - |
804 | | - /// Acquire a cross-process lock for a resource using a file at the provided path |
805 | | - /// |
806 | | - /// Unlike [`LockedFile::acquire`] this function will not wait for the lock to become available. |
807 | | - /// |
808 | | - /// If the lock is not immediately available, [`None`] is returned. |
809 | | - pub fn acquire_no_wait(path: impl AsRef<Path>, resource: impl Display) -> Option<Self> { |
810 | | - let file = Self::create(path).ok()?; |
811 | | - let resource = resource.to_string(); |
812 | | - Self::lock_file_no_wait(file, &resource) |
813 | | - } |
814 | | - |
815 | | - #[cfg(unix)] |
816 | | - fn create(path: impl AsRef<Path>) -> Result<fs_err::File, std::io::Error> { |
817 | | - use std::os::unix::fs::PermissionsExt; |
818 | | - |
819 | | - // If path already exists, return it. |
820 | | - if let Ok(file) = fs_err::OpenOptions::new() |
821 | | - .read(true) |
822 | | - .write(true) |
823 | | - .open(path.as_ref()) |
824 | | - { |
825 | | - return Ok(file); |
826 | | - } |
827 | | - |
828 | | - // Otherwise, create a temporary file with 777 permissions. We must set |
829 | | - // permissions _after_ creating the file, to override the `umask`. |
830 | | - let file = if let Some(parent) = path.as_ref().parent() { |
831 | | - NamedTempFile::new_in(parent)? |
832 | | - } else { |
833 | | - NamedTempFile::new()? |
834 | | - }; |
835 | | - if let Err(err) = file |
836 | | - .as_file() |
837 | | - .set_permissions(std::fs::Permissions::from_mode(0o777)) |
838 | | - { |
839 | | - warn!("Failed to set permissions on temporary file: {err}"); |
840 | | - } |
841 | | - |
842 | | - // Try to move the file to path, but if path exists now, just open path |
843 | | - match file.persist_noclobber(path.as_ref()) { |
844 | | - Ok(file) => Ok(fs_err::File::from_parts(file, path.as_ref())), |
845 | | - Err(err) => { |
846 | | - if err.error.kind() == std::io::ErrorKind::AlreadyExists { |
847 | | - fs_err::OpenOptions::new() |
848 | | - .read(true) |
849 | | - .write(true) |
850 | | - .open(path.as_ref()) |
851 | | - } else { |
852 | | - Err(err.error) |
853 | | - } |
854 | | - } |
855 | | - } |
856 | | - } |
857 | | - |
858 | | - #[cfg(not(unix))] |
859 | | - fn create(path: impl AsRef<Path>) -> std::io::Result<fs_err::File> { |
860 | | - fs_err::OpenOptions::new() |
861 | | - .read(true) |
862 | | - .write(true) |
863 | | - .create(true) |
864 | | - .open(path.as_ref()) |
865 | | - } |
866 | | -} |
867 | | - |
868 | | -impl Drop for LockedFile { |
869 | | - fn drop(&mut self) { |
870 | | - if let Err(err) = self.0.unlock() { |
871 | | - error!( |
872 | | - "Failed to unlock resource at `{}`; program may be stuck: {err}", |
873 | | - self.0.path().display() |
874 | | - ); |
875 | | - } else { |
876 | | - debug!("Released lock at `{}`", self.0.path().display()); |
877 | | - } |
878 | | - } |
879 | | -} |
880 | | - |
881 | 671 | /// An asynchronous reader that reports progress as bytes are read. |
882 | 672 | #[cfg(feature = "tokio")] |
883 | 673 | pub struct ProgressReader<Reader: tokio::io::AsyncRead + Unpin, Callback: Fn(usize) + Unpin> { |
|
0 commit comments