evobench_tools/io_utils/
owning_lockable_file.rs

1//! Variant of `lockable_file.rs` that does not have lifetimes in its
2//! locks, but instead relies on Arc.
3
4use std::{fs::File, ops::Deref, path::Path, sync::Arc};
5
6use fs2::{FileExt, lock_contended_error};
7use lazy_static::lazy_static;
8
9use crate::io_utils::lockable_file::{HELD_LOCKS, LockStatus};
10
11// -----------------------------------------------------------------------------
12
13pub struct OwningSharedFileLock<F: FileExt> {
14    debug: Option<Arc<Path>>,
15    // XX joke: need DerefMut anyway, even reading requires mut
16    // access. So the two locks are identical now. TODO: eliminate or
17    // ? Parameterize instead?
18    file: Arc<F>,
19}
20
21impl<F: FileExt> Drop for OwningSharedFileLock<F> {
22    fn drop(&mut self) {
23        self.file
24            .unlock()
25            .expect("no way another path to unlock exists");
26        if let Some(path) = &self.debug {
27            eprintln!("dropped SharedFileLock on {path:?}");
28            HELD_LOCKS.with_borrow_mut(|set| {
29                set.remove(&**path);
30            });
31        }
32    }
33}
34
35impl<F: FileExt> Deref for OwningSharedFileLock<F> {
36    type Target = Arc<F>;
37
38    fn deref(&self) -> &Self::Target {
39        &self.file
40    }
41}
42
43// -----------------------------------------------------------------------------
44
45#[derive(Debug)]
46pub struct OwningExclusiveFileLock<F: FileExt> {
47    debug: Option<Arc<Path>>,
48    file: Arc<F>,
49}
50
51impl<F: FileExt> PartialEq for OwningExclusiveFileLock<F> {
52    fn eq(&self, other: &Self) -> bool {
53        self.debug == other.debug
54    }
55}
56
57impl<F: FileExt> Drop for OwningExclusiveFileLock<F> {
58    fn drop(&mut self) {
59        self.file
60            .unlock()
61            .expect("no way another path to unlock exists");
62        if let Some(path) = &self.debug {
63            eprintln!("dropped ExclusiveFileLock on {path:?}");
64            HELD_LOCKS.with_borrow_mut(|set| {
65                set.remove(&**path);
66            });
67        }
68    }
69}
70
71impl<F: FileExt> Deref for OwningExclusiveFileLock<F> {
72    type Target = Arc<F>;
73
74    fn deref(&self) -> &Self::Target {
75        &self.file
76    }
77}
78
79// -----------------------------------------------------------------------------
80
81#[derive(Debug)]
82pub struct OwningLockableFile<F: FileExt> {
83    /// Path for, and only if, debugging is set via
84    /// `DEBUG_LOCKABLE_FILE` env var
85    debug: Option<Arc<Path>>,
86    file: Arc<F>,
87}
88
89impl<F: FileExt> OwningLockableFile<F> {
90    /// Determines lock status by temporarily getting locks in
91    /// nonblocking manner, thus not very performant! Also, may
92    /// erroneously return `LockStatus::SharedLock` if during testing
93    /// an exclusive lock is released.
94    pub fn get_lock_status(&mut self) -> std::io::Result<LockStatus> {
95        use LockStatus::*;
96        Ok(if self.try_lock_exclusive()?.is_some() {
97            Unlocked
98        } else if self.try_lock_shared()?.is_some() {
99            SharedLock
100        } else {
101            ExclusiveLock
102        })
103    }
104
105    pub fn lock_shared(&self) -> std::io::Result<OwningSharedFileLock<F>> {
106        if let Some(path) = self.debug.as_ref() {
107            eprintln!("getting SharedFileLock on {path:?}");
108            HELD_LOCKS.with_borrow_mut(|set| -> std::io::Result<()> {
109                // XXX: ah, todo: allow multiple shared
110                if set.contains(&**path) {
111                    panic!("{path:?} is already locked by this thread")
112                }
113                FileExt::lock_shared(&*self.file)?;
114                eprintln!("got SharedFileLock on {path:?}");
115                set.insert((&**path).to_owned());
116                Ok(())
117            })?;
118        } else {
119            FileExt::lock_shared(&*self.file)?;
120        }
121        Ok(OwningSharedFileLock {
122            debug: self.debug.as_ref().map(Arc::clone),
123            file: self.file.clone(),
124        })
125    }
126
127    pub fn lock_exclusive(&self) -> std::io::Result<OwningExclusiveFileLock<F>> {
128        if let Some(path) = self.debug.as_ref() {
129            eprintln!("getting ExclusiveFileLock on {path:?}");
130            HELD_LOCKS.with_borrow_mut(|set| -> std::io::Result<()> {
131                if set.contains(&**path) {
132                    panic!("{path:?} is already locked by this thread")
133                }
134                FileExt::lock_exclusive(&*self.file)?;
135                eprintln!("got ExclusiveFileLock on {path:?}");
136                set.insert((&**path).to_owned());
137                Ok(())
138            })?;
139        } else {
140            FileExt::lock_exclusive(&*self.file)?;
141        }
142        Ok(OwningExclusiveFileLock {
143            debug: self.debug.as_ref().map(Arc::clone),
144            file: self.file.clone(),
145        })
146    }
147
148    pub fn try_lock_shared(&self) -> std::io::Result<Option<OwningSharedFileLock<F>>> {
149        match FileExt::try_lock_shared(&*self.file) {
150            Ok(()) => {
151                if let Some(path) = self.debug.as_ref() {
152                    eprintln!("got SharedFileLock on {path:?}");
153                    HELD_LOCKS.with_borrow_mut(|set| {
154                        set.insert((&**path).to_owned());
155                    });
156                }
157                Ok(Some(OwningSharedFileLock {
158                    debug: self.debug.as_ref().map(Arc::clone),
159                    file: self.file.clone(),
160                }))
161            }
162            Err(e) => {
163                if e.kind() == lock_contended_error().kind() {
164                    Ok(None)
165                } else {
166                    Err(e)
167                }
168            }
169        }
170    }
171
172    pub fn try_lock_exclusive(&self) -> std::io::Result<Option<OwningExclusiveFileLock<F>>> {
173        match FileExt::try_lock_exclusive(&*self.file) {
174            Ok(()) => {
175                if let Some(path) = self.debug.as_ref() {
176                    eprintln!("got ExclusiveFileLock on {path:?}");
177                    HELD_LOCKS.with_borrow_mut(|set| {
178                        set.insert((&**path).to_owned());
179                    });
180                }
181                Ok(Some(OwningExclusiveFileLock {
182                    debug: self.debug.as_ref().map(Arc::clone),
183                    file: self.file.clone(),
184                }))
185            }
186            Err(e) => {
187                if e.kind() == lock_contended_error().kind() {
188                    Ok(None)
189                } else {
190                    Err(e)
191                }
192            }
193        }
194    }
195}
196
197lazy_static! {
198    static ref DEBUGGING: bool = if let Some(val) = std::env::var_os("DEBUG_LOCKABLE_FILE") {
199        match val
200            .into_string()
201            .expect("utf-8 for env var DEBUG_LOCKABLE_FILE")
202            .as_str()
203        {
204            "0" => false,
205            "1" | "" => true,
206            _ => panic!("need 1|0 or empty string for DEBUG_LOCKABLE_FILE"),
207        }
208    } else {
209        false
210    };
211}
212
213impl OwningLockableFile<File> {
214    pub fn open(path: Arc<Path>) -> std::io::Result<Self> {
215        File::open(path.as_ref()).and_then(|file| {
216            let path = if *DEBUGGING { Some(path) } else { None };
217
218            Ok(OwningLockableFile {
219                debug: path,
220                file: Arc::new(file),
221            })
222        })
223    }
224}