1use crate::error::{ClientResultExt, ZeroFsError};
2use crate::failover::{
3 FailoverClient, MAX_ATTEMPTS, OP_TIMEOUT, RETRY_DELAY, is_transport_failure,
4};
5use crate::session::{FidGuard, Session};
6use crate::types::{Metadata, OpenOptions, SetAttrs};
7use arc_swap::ArcSwap;
8use std::future::Future;
9use std::path::PathBuf;
10use std::sync::Arc;
11use std::sync::atomic::{AtomicBool, Ordering};
12
13struct Bound {
17 session: Arc<Session>,
18 guard: FidGuard,
19}
20
21struct Reconnect {
23 fc: Arc<FailoverClient>,
24 path: PathBuf,
26 opts: OpenOptions,
29 relock: tokio::sync::Mutex<()>,
31}
32
33pub struct File {
41 bound: ArcSwap<Bound>,
42 reconnect: Option<Reconnect>,
44 closed: AtomicBool,
45 path: String,
46}
47
48impl std::fmt::Debug for File {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 f.debug_struct("File")
51 .field("path", &self.path)
52 .field("closed", &self.closed.load(Ordering::Relaxed))
53 .field("failover", &self.reconnect.is_some())
54 .finish_non_exhaustive()
55 }
56}
57
58impl File {
59 pub(crate) fn new(session: Arc<Session>, guard: FidGuard, path: String) -> Arc<Self> {
61 Arc::new(Self {
62 bound: ArcSwap::from_pointee(Bound { session, guard }),
63 reconnect: None,
64 closed: AtomicBool::new(false),
65 path,
66 })
67 }
68
69 pub(crate) fn new_failover(
72 fc: Arc<FailoverClient>,
73 session: Arc<Session>,
74 guard: FidGuard,
75 path: PathBuf,
76 reopen_opts: OpenOptions,
77 ) -> Arc<Self> {
78 let display = crate::path::display(&path);
79 Arc::new(Self {
80 bound: ArcSwap::from_pointee(Bound { session, guard }),
81 reconnect: Some(Reconnect {
82 fc,
83 path,
84 opts: reopen_opts,
85 relock: tokio::sync::Mutex::new(()),
86 }),
87 closed: AtomicBool::new(false),
88 path: display,
89 })
90 }
91
92 async fn with_binding<T, F, Fut>(&self, op: F) -> Result<T, ZeroFsError>
97 where
98 F: Fn(Arc<Session>, u32) -> Fut,
99 Fut: Future<Output = Result<T, ZeroFsError>>,
100 {
101 if self.closed.load(Ordering::Acquire) {
102 return Err(ZeroFsError::Closed);
103 }
104 let Some(rc) = &self.reconnect else {
105 let b = self.bound.load_full();
106 return op(Arc::clone(&b.session), b.guard.fid()).await;
107 };
108 let mut last: Option<ZeroFsError> = None;
109 for _ in 0..MAX_ATTEMPTS {
110 if self.closed.load(Ordering::Acquire) {
111 return Err(ZeroFsError::Closed);
112 }
113 let b = self.bound.load_full();
114 match tokio::time::timeout(OP_TIMEOUT, op(Arc::clone(&b.session), b.guard.fid())).await
115 {
116 Ok(Ok(v)) => return Ok(v),
117 Ok(Err(e)) if is_transport_failure(&e) => {
118 last = Some(e);
119 self.rebind(rc, &b).await?;
120 tokio::time::sleep(RETRY_DELAY).await;
121 }
122 Ok(Err(e)) => return Err(e),
123 Err(_) => {
124 last = Some(ZeroFsError::ConnectFailed {
125 message: format!("{}: file op timed out; leader unreachable", self.path),
126 });
127 self.rebind(rc, &b).await?;
128 }
129 }
130 }
131 Err(last.unwrap_or(ZeroFsError::ConnectFailed {
132 message: format!("{}: file failover attempts exhausted", self.path),
133 }))
134 }
135
136 async fn rebind(&self, rc: &Reconnect, failed: &Arc<Bound>) -> Result<(), ZeroFsError> {
140 let _g = rc.relock.lock().await;
141 if !Arc::ptr_eq(&self.bound.load_full(), failed) {
142 return Ok(());
143 }
144 let (session, guard) = rc.fc.reopen_handle(&rc.path, &rc.opts).await?;
145 self.bound.store(Arc::new(Bound { session, guard }));
146 Ok(())
147 }
148
149 #[cfg(feature = "tokio-io")]
152 pub(crate) fn max_read_chunk(&self) -> u32 {
153 self.bound.load().session.client.max_io()
154 }
155
156 pub async fn read_at(&self, offset: u64, len: u32) -> Result<bytes::Bytes, ZeroFsError> {
159 let path = self.path.clone();
160 self.with_binding(move |session, fid| {
161 let path = path.clone();
162 async move { session.client.read_bytes(fid, offset, len).await.ctx(&path) }
163 })
164 .await
165 }
166
167 pub async fn write_at(&self, offset: u64, data: &[u8]) -> Result<(), ZeroFsError> {
170 let data = data.to_vec();
171 let path = self.path.clone();
172 self.with_binding(move |session, fid| {
173 let data = data.clone();
174 let path = path.clone();
175 async move { session.write_all(fid, offset, &data, &path).await }
176 })
177 .await
178 }
179
180 pub async fn metadata(&self) -> Result<Metadata, ZeroFsError> {
182 let path = self.path.clone();
183 let stat = self
184 .with_binding(move |session, fid| {
185 let path = path.clone();
186 async move { session.stat_fid(fid, &path).await }
187 })
188 .await?;
189 Ok(Metadata::from_stat(&stat))
190 }
191
192 pub async fn set_len(&self, size: u64) -> Result<(), ZeroFsError> {
194 self.set_attr(SetAttrs {
195 size: Some(size),
196 ..Default::default()
197 })
198 .await?;
199 Ok(())
200 }
201
202 pub async fn set_attr(&self, attrs: SetAttrs) -> Result<Metadata, ZeroFsError> {
204 let path = self.path.clone();
205 let stat = self
206 .with_binding(move |session, fid| {
207 let path = path.clone();
208 async move { session.setattr_fid(fid, &attrs, &path).await }
209 })
210 .await?;
211 Ok(Metadata::from_stat(&stat))
212 }
213
214 pub async fn sync_all(&self) -> Result<(), ZeroFsError> {
216 let path = self.path.clone();
217 self.with_binding(move |session, fid| {
218 let path = path.clone();
219 async move { session.client.fsync(fid, 0).await.ctx(&path) }
220 })
221 .await
222 }
223
224 pub async fn sync_data(&self) -> Result<(), ZeroFsError> {
226 let path = self.path.clone();
227 self.with_binding(move |session, fid| {
228 let path = path.clone();
229 async move { session.client.fsync(fid, 1).await.ctx(&path) }
230 })
231 .await
232 }
233
234 pub async fn close(&self) {
238 self.closed.store(true, Ordering::Release);
239 }
240
241 #[cfg(feature = "tokio-io")]
246 pub fn cursor(self: &Arc<File>) -> crate::io::FileCursor {
247 crate::io::FileCursor::new(Arc::clone(self))
248 }
249}