1use {
14 std::{
15 io,
16 mem::forget,
17 num::ParseIntError,
18 path::{
19 Path,
20 PathBuf,
21 },
22 sync::Arc,
23 thread,
24 time::Duration,
25 },
26 sysinfo::{
27 Pid,
28 ProcessRefreshKind,
29 ProcessesToUpdate,
30 },
31 thiserror::Error,
32};
33#[cfg(feature = "async-std")] use async_std::{
34 fs,
35 task::sleep,
36};
37#[cfg(feature = "tokio")] use tokio::{
38 fs,
39 time::sleep,
40};
41
42#[must_use = "should call the drop_async method to unlock"]
58pub struct DirLock(PathBuf);
59
60#[derive(Debug, Error, Clone)]
62#[allow(missing_docs)]
63pub enum Error {
64 #[error("I/O error{}: {}", if let Some(path) = .1 { format!(" at {}", path.display()) } else { String::default() }, .0)] Io(#[source] Arc<io::Error>, Option<PathBuf>),
65 #[error(transparent)] ParseInt(#[from] ParseIntError),
66}
67
68trait IoResultExt {
69 type T;
70
71 fn at(self, path: impl AsRef<Path>) -> Self::T;
72}
73
74impl IoResultExt for io::Error {
75 type T = Error;
76
77 fn at(self, path: impl AsRef<Path>) -> Error {
78 Error::Io(Arc::new(self), Some(path.as_ref().to_owned()))
79 }
80}
81
82impl<T, E: IoResultExt> IoResultExt for Result<T, E> {
83 type T = Result<T, E::T>;
84
85 fn at(self, path: impl AsRef<Path>) -> Result<T, E::T> {
86 self.map_err(|e| e.at(path))
87 }
88}
89
90impl DirLock {
91 pub async fn new(path: impl AsRef<Path>) -> Result<Self, Error> {
95 let path = path.as_ref().to_owned();
96 loop {
97 match fs::create_dir(&path).await {
98 Ok(()) => {
99 let pidfile = path.join("pid");
100 fs::write(&pidfile, format!("{}\n", std::process::id())).await.at(pidfile)?;
101 return Ok(Self(path))
102 }
103 Err(e) => match e.kind() {
104 io::ErrorKind::AlreadyExists => {
105 let pidfile = path.join("pid");
106 if match fs::read_to_string(&pidfile).await {
107 Ok(buf) => {
108 !buf.is_empty() && !pid_exists(buf.trim().parse()?)
110 }
111 Err(e) => if e.kind() == io::ErrorKind::NotFound {
112 false
113 } else {
114 return Err(e.at(path.join("pid")))
115 },
116 } {
117 clean_up_path(&path).await?;
118 }
119 sleep(Duration::from_secs(1)).await;
120 continue
121 }
122 _ => return Err(e.at(path)),
123 },
124 }
125 }
126 }
127
128 pub fn new_sync(path: &impl AsRef<Path>) -> Result<Self, Error> {
130 let path = path.as_ref().to_owned();
131 loop {
132 match std::fs::create_dir(&path) {
133 Ok(()) => {
134 let pidfile = path.join("pid");
135 std::fs::write(&pidfile, format!("{}\n", std::process::id())).at(pidfile)?;
136 return Ok(Self(path))
137 }
138 Err(e) => match e.kind() {
139 io::ErrorKind::AlreadyExists => {
140 let pidfile = path.join("pid");
141 if match std::fs::read_to_string(&pidfile) {
142 Ok(buf) => {
143 !buf.is_empty() && !pid_exists(buf.trim().parse()?)
145 }
146 Err(e) => if e.kind() == io::ErrorKind::NotFound {
147 false
148 } else {
149 return Err(e.at(path.join("pid")))
150 },
151 } {
152 clean_up_path_sync(&path)?;
153 }
154 thread::sleep(Duration::from_secs(1));
155 continue
156 }
157 _ => return Err(e.at(path)),
158 },
159 }
160 }
161 }
162
163 pub async fn drop_async(self) -> Result<(), Error> {
165 self.clean_up().await?;
166 forget(self);
167 Ok(())
168 }
169
170 async fn clean_up(&self) -> Result<(), Error> {
171 clean_up_path(&self.0).await
172 }
173
174 fn clean_up_sync(&self) -> Result<(), Error> {
175 clean_up_path_sync(&self.0)
176 }
177}
178
179impl Drop for DirLock {
180 fn drop(&mut self) {
187 self.clean_up_sync().expect("failed to clean up dir lock");
188 }
189}
190
191async fn clean_up_path(path: &Path) -> Result<(), Error> {
192 if let Err(e) = fs::remove_file(path.join("pid")).await {
193 if e.kind() != io::ErrorKind::NotFound {
194 return Err(e.at(path.join("pid")));
195 }
196 }
197 if let Err(e) = fs::remove_dir(path).await {
198 if e.kind() != io::ErrorKind::NotFound {
199 return Err(e.at(path))
200 }
201 }
202 Ok(())
203}
204
205fn clean_up_path_sync(path: &Path) -> Result<(), Error> {
206 if let Err(e) = std::fs::remove_file(path.join("pid")) {
207 if e.kind() != io::ErrorKind::NotFound {
208 return Err(e.at(path.join("pid")))
209 }
210 }
211 if let Err(e) = std::fs::remove_dir(path) {
212 if e.kind() != io::ErrorKind::NotFound {
213 return Err(e.at(path))
214 }
215 }
216 Ok(())
217}
218
219fn pid_exists(pid: Pid) -> bool {
220 let mut system = sysinfo::System::default();
221 system.refresh_processes_specifics(ProcessesToUpdate::Some(&[pid]), true, ProcessRefreshKind::default()) > 0
222}