a3s_effect/log/
fencing.rs1use super::{
4 restrict_file_permissions, same_generation, storage_error, valid_thread, validate_regular,
5 FileLog,
6};
7use crate::ActorError;
8use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt, OpenOptionsSyncExt};
9use cap_std::fs::{Metadata, OpenOptions};
10use std::fs::File;
11use std::io;
12use std::sync::{Arc, Mutex, Weak};
13
14const WRITER_LOCK: &str = "writer.lock";
15
16#[derive(Debug)]
17pub(super) struct LockedLogFile {
18 file: File,
19 metadata: Metadata,
20}
21
22impl Drop for LockedLogFile {
23 fn drop(&mut self) {
24 let _ = fs2::FileExt::unlock(&self.file);
27 }
28}
29
30#[derive(Debug)]
33pub struct FileLogWriterFence {
34 held: Option<Arc<LockedLogFile>>,
35 cache: Arc<Mutex<Weak<LockedLogFile>>>,
36}
37
38impl FileLogWriterFence {
39 pub fn is_for(&self, log: &FileLog) -> Result<bool, ActorError> {
40 let current = log
41 .writer
42 .lock()
43 .map_err(|_| storage_error("poisoned writer fence"))?;
44 Ok(self
45 .held
46 .as_ref()
47 .is_some_and(|held| Weak::ptr_eq(¤t, &Arc::downgrade(held))))
48 }
49}
50
51impl Drop for FileLogWriterFence {
52 fn drop(&mut self) {
53 let _cache = self
55 .cache
56 .lock()
57 .unwrap_or_else(std::sync::PoisonError::into_inner);
58 drop(self.held.take());
59 }
60}
61
62#[derive(Debug)]
64pub struct FileLogRunOwnerFence {
65 held: LockedLogFile,
66}
67
68impl FileLogRunOwnerFence {
69 pub fn release(&mut self) -> Result<(), ActorError> {
71 fs2::FileExt::unlock(&self.held.file).map_err(storage_error)
72 }
73}
74
75impl FileLog {
76 pub fn try_writer_fence(&self) -> Result<FileLogWriterFence, ActorError> {
78 self.writer_fence(true)?
79 .ok_or_else(|| storage_error("writer fence was not created"))
80 }
81
82 pub(super) fn existing_writer_fence(&self) -> Result<Option<FileLogWriterFence>, ActorError> {
83 self.writer_fence(false)
84 }
85
86 pub(super) fn prepare_writer_fence(
87 &self,
88 thread_id: &str,
89 ) -> Result<FileLogWriterFence, ActorError> {
90 match self.dir.symlink_metadata(WRITER_LOCK) {
91 Err(error) if error.kind() == io::ErrorKind::NotFound => {
92 self.read_opened(thread_id, false)?;
94 }
95 Err(error) => return Err(storage_error(error)),
96 Ok(_) => {}
97 }
98 self.try_writer_fence()
99 }
100
101 fn writer_fence(&self, create: bool) -> Result<Option<FileLogWriterFence>, ActorError> {
102 let mut current = self
103 .writer
104 .lock()
105 .map_err(|_| storage_error("poisoned writer fence"))?;
106 let held = match current.upgrade() {
107 Some(held) => held,
108 None => {
109 if !create {
110 match self.dir.symlink_metadata(WRITER_LOCK) {
111 Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(None),
112 Err(error) => return Err(storage_error(error)),
113 Ok(_) => {}
114 }
115 }
116 let held = Arc::new(self.lock_file(WRITER_LOCK, create)?);
117 *current = Arc::downgrade(&held);
118 held
119 }
120 };
121 same_generation(
122 &held.metadata,
123 &self
124 .dir
125 .symlink_metadata(WRITER_LOCK)
126 .map_err(storage_error)?,
127 )?;
128 Ok(Some(FileLogWriterFence {
129 held: Some(held),
130 cache: Arc::clone(&self.writer),
131 }))
132 }
133
134 pub fn try_run_owner(&self, thread_id: &str) -> Result<FileLogRunOwnerFence, ActorError> {
136 valid_thread(thread_id)?;
137 Ok(FileLogRunOwnerFence {
138 held: self.lock_file(&format!("run-owner-{thread_id}.lock"), true)?,
139 })
140 }
141
142 fn lock_file(&self, name: &str, create: bool) -> Result<LockedLogFile, ActorError> {
143 let before = match self.dir.symlink_metadata(name) {
144 Ok(metadata) => {
145 validate_lock_file(&metadata)?;
146 Some(metadata)
147 }
148 Err(error) if error.kind() == io::ErrorKind::NotFound => None,
149 Err(error) => return Err(storage_error(error)),
150 };
151 let mut options = OpenOptions::new();
152 options
153 .read(true)
154 .append(true)
155 .create(create)
156 .follow(FollowSymlinks::No)
157 .nonblock(true);
158 #[cfg(unix)]
159 {
160 use cap_std::fs::OpenOptionsExt;
161 options.mode(0o600);
162 }
163 let file = self.dir.open_with(name, &options).map_err(storage_error)?;
164 let metadata = file.metadata().map_err(storage_error)?;
165 validate_lock_file(&metadata)?;
166 if let Some(before) = before {
167 same_generation(&before, &metadata)?;
168 }
169 let file = file.into_std();
170 restrict_file_permissions(&file)?;
171 fs2::FileExt::try_lock_exclusive(&file).map_err(|error| {
172 if error.raw_os_error() == fs2::lock_contended_error().raw_os_error() {
173 ActorError::LogBusy
174 } else {
175 storage_error(error)
176 }
177 })?;
178 same_generation(
179 &metadata,
180 &self.dir.symlink_metadata(name).map_err(storage_error)?,
181 )?;
182 Ok(LockedLogFile { file, metadata })
183 }
184}
185
186fn validate_lock_file(metadata: &Metadata) -> Result<(), ActorError> {
187 validate_regular(metadata)?;
188 if metadata.len() != 0 {
189 return Err(storage_error("fact log lock file must be empty"));
190 }
191 Ok(())
192}
193
194#[cfg(test)]
195mod tests;