http_cache_stream/
lock.rs1use std::fmt;
9use std::fs::File;
10use std::fs::OpenOptions;
11use std::io;
12use std::ops::Deref;
13use std::ops::DerefMut;
14use std::path::Path;
15
16#[cfg(unix)]
17pub(crate) mod unix;
18
19use tracing::debug;
20#[cfg(unix)]
21pub(crate) use unix as sys;
22
23#[cfg(windows)]
24pub(crate) mod windows;
25
26#[cfg(windows)]
27pub(crate) use windows as sys;
28
29use crate::runtime;
30
31#[derive(Debug)]
33pub struct LockedFile(File);
34
35impl Deref for LockedFile {
36 type Target = File;
37
38 fn deref(&self) -> &Self::Target {
39 &self.0
40 }
41}
42
43impl DerefMut for LockedFile {
44 fn deref_mut(&mut self) -> &mut Self::Target {
45 &mut self.0
46 }
47}
48
49impl Drop for LockedFile {
50 fn drop(&mut self) {
51 let _ = sys::unlock(&self.0);
52 }
53}
54
55pub trait OpenOptionsExt {
60 fn try_open_shared(&self, path: &Path) -> io::Result<Option<LockedFile>>;
66
67 fn open_shared(&self, path: &Path) -> impl Future<Output = io::Result<LockedFile>> + Send;
71
72 fn try_open_exclusive(&self, path: &Path) -> io::Result<Option<LockedFile>>;
78
79 fn open_exclusive(&self, path: &Path) -> impl Future<Output = io::Result<LockedFile>> + Send;
83}
84
85impl OpenOptionsExt for OpenOptions {
86 fn try_open_shared(&self, path: &Path) -> io::Result<Option<LockedFile>> {
87 let file = self.open(path)?;
88
89 if try_lock(&file, Access::Shared)? {
90 Ok(Some(LockedFile(file)))
91 } else {
92 Ok(None)
93 }
94 }
95
96 async fn open_shared(&self, path: &Path) -> io::Result<LockedFile> {
97 lock(self.open(path)?, path, Access::Shared).await
98 }
99
100 fn try_open_exclusive(&self, path: &Path) -> io::Result<Option<LockedFile>> {
101 let file = self.open(path)?;
102
103 if try_lock(&file, Access::Exclusive)? {
104 Ok(Some(LockedFile(file)))
105 } else {
106 Ok(None)
107 }
108 }
109
110 async fn open_exclusive(&self, path: &Path) -> io::Result<LockedFile> {
111 lock(self.open(path)?, path, Access::Exclusive).await
112 }
113}
114
115#[derive(Debug, Copy, Clone, Eq, PartialEq)]
117enum Access {
118 Shared,
120 Exclusive,
122}
123
124impl fmt::Display for Access {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 match self {
127 Self::Shared => write!(f, "shared"),
128 Self::Exclusive => write!(f, "exclusive"),
129 }
130 }
131}
132
133fn try_lock(file: &File, access: Access) -> io::Result<bool> {
138 let res = match access {
141 Access::Shared => sys::try_lock_shared(file),
142 Access::Exclusive => sys::try_lock_exclusive(file),
143 };
144
145 match res {
146 Ok(_) => Ok(true),
147 Err(e) if sys::error_unsupported(&e) => Ok(true),
148 Err(e) if sys::error_contended(&e) => Ok(false),
149 Err(e) => Err(e),
150 }
151}
152
153async fn lock(file: File, path: &Path, access: Access) -> io::Result<LockedFile> {
155 if try_lock(&file, access)? {
157 return Ok(LockedFile(file));
158 }
159
160 debug!(
162 "waiting to acquire {access} lock on file `{path}`",
163 path = path.display()
164 );
165 match runtime::unwrap_task_output(
166 runtime::spawn_blocking(move || {
167 let res = match access {
168 Access::Shared => sys::lock_shared(&file),
169 Access::Exclusive => sys::lock_exclusive(&file),
170 };
171
172 match res {
173 Ok(_) => Ok(LockedFile(file)),
174 Err(e) if sys::error_unsupported(&e) => Ok(LockedFile(file)),
175 Err(e) => Err(e),
176 }
177 })
178 .await,
179 ) {
180 Some(res) => res,
181 None => Err(io::Error::other("failed to wait for file lock")),
182 }
183}