1use std::sync::Arc;
22#[cfg(test)]
23use std::sync::atomic::{AtomicUsize, Ordering};
24
25use async_trait::async_trait;
26use bytes::{Bytes, BytesMut};
27use futures::stream::FuturesUnordered;
28use futures::{StreamExt, stream};
29use tokio::sync::Mutex;
30
31use crate::error::{NfsError, Result};
32use crate::mount::{Mount, WriteOutcome};
33
34pub(crate) async fn file_gate(
37 identity: usize,
38 fh: Bytes,
39 kind: u8,
40) -> tokio::sync::OwnedMutexGuard<()> {
41 type Gates = std::collections::HashMap<(usize, Bytes, u8), std::sync::Weak<Mutex<()>>>;
42 static GATES: std::sync::OnceLock<std::sync::Mutex<Gates>> = std::sync::OnceLock::new();
43 let gate = {
44 let mut gates = GATES
45 .get_or_init(Default::default)
46 .lock()
47 .unwrap_or_else(|e| e.into_inner());
48 let key = (identity, fh, kind);
49 if let Some(gate) = gates.get(&key).and_then(std::sync::Weak::upgrade) {
50 gate
51 } else {
52 gates.retain(|_, gate| gate.strong_count() > 0);
53 let gate = Arc::new(Mutex::new(()));
54 gates.insert(key, Arc::downgrade(&gate));
55 gate
56 }
57 };
58 gate.lock_owned().await
59}
60
61#[async_trait]
62pub(crate) trait WriteIo: Send + Sync {
63 async fn begin_batch(&self, _fh: Bytes) -> Option<tokio::sync::OwnedMutexGuard<()>> {
64 None
65 }
66 fn write_chunk_size(&self) -> u32;
67 fn protocol(&self) -> crate::NFSVersion;
68 async fn write_unstable(&self, fh: Bytes, offset: u64, data: Bytes) -> Result<WriteOutcome>;
69 async fn commit_batch(
70 &self,
71 fh: Bytes,
72 offset: u64,
73 count: u32,
74 writes: &[WriteOutcome],
75 ) -> Result<()>;
76}
77
78#[async_trait]
79pub(crate) trait ReadIo: Send + Sync {
80 fn read_chunk_size(&self) -> u32;
81 async fn read(&self, fh: Bytes, offset: u64, count: u32) -> Result<Bytes>;
82}
83
84#[async_trait]
85pub(crate) trait ChunkIo: WriteIo + ReadIo + 'static {
86 async fn close(&self, fh: Bytes) -> Result<()>;
87}
88
89struct MountWriter<'a, M: Mount + ?Sized>(&'a M);
90#[async_trait]
91impl<M: Mount + ?Sized> WriteIo for MountWriter<'_, M> {
92 async fn begin_batch(&self, fh: Bytes) -> Option<tokio::sync::OwnedMutexGuard<()>> {
93 Some(file_gate(self.0 as *const M as *const () as usize, fh, 0).await)
94 }
95 fn write_chunk_size(&self) -> u32 {
96 self.0.get_max_write_size().max(1)
97 }
98 fn protocol(&self) -> crate::NFSVersion {
99 self.0.version()
100 }
101 async fn write_unstable(&self, fh: Bytes, offset: u64, data: Bytes) -> Result<WriteOutcome> {
102 self.0.write(fh, offset, data).await
103 }
104 async fn commit_batch(
105 &self,
106 fh: Bytes,
107 offset: u64,
108 count: u32,
109 writes: &[WriteOutcome],
110 ) -> Result<()> {
111 self.0.commit_write_batch(fh, offset, count, writes).await
112 }
113}
114
115#[async_trait]
116impl WriteIo for Arc<dyn Mount> {
117 async fn begin_batch(&self, fh: Bytes) -> Option<tokio::sync::OwnedMutexGuard<()>> {
118 Some(file_gate(Arc::as_ptr(self) as *const () as usize, fh, 0).await)
119 }
120 fn write_chunk_size(&self) -> u32 {
121 self.get_max_write_size().max(1)
122 }
123 fn protocol(&self) -> crate::NFSVersion {
124 self.version()
125 }
126 async fn write_unstable(&self, fh: Bytes, offset: u64, data: Bytes) -> Result<WriteOutcome> {
127 self.write(fh, offset, data).await
128 }
129 async fn commit_batch(
130 &self,
131 fh: Bytes,
132 offset: u64,
133 count: u32,
134 writes: &[WriteOutcome],
135 ) -> Result<()> {
136 self.commit_write_batch(fh, offset, count, writes).await
137 }
138}
139
140#[async_trait]
141impl ReadIo for Arc<dyn Mount> {
142 fn read_chunk_size(&self) -> u32 {
143 self.get_max_read_size().max(1)
144 }
145 async fn read(&self, fh: Bytes, offset: u64, count: u32) -> Result<Bytes> {
146 Mount::read(self.as_ref(), fh, offset, count).await
147 }
148}
149
150#[async_trait]
151impl ChunkIo for Arc<dyn Mount> {
152 async fn close(&self, fh: Bytes) -> Result<()> {
153 Mount::close(self.as_ref(), fh).await
154 }
155}
156
157pub async fn write_all<M: Mount + ?Sized>(
165 mount: &M,
166 fh: Bytes,
167 offset: u64,
168 data: Bytes,
169) -> Result<u64> {
170 write_all_with(&MountWriter(mount), fh, offset, data).await
171}
172
173const WRITE_CONCURRENCY: usize = 8;
174
175async fn write_chunk_with<I: WriteIo + ?Sized>(
178 io: &I,
179 fh: Bytes,
180 offset: u64,
181 data: Bytes,
182) -> (u64, usize, Vec<WriteOutcome>, Option<NfsError>) {
183 let mut done = 0;
184 let mut receipts = Vec::new();
185 while done < data.len() {
186 let outcome = match io
187 .write_unstable(fh.clone(), offset + done as u64, data.slice(done..))
188 .await
189 {
190 Ok(outcome) => outcome,
191 Err(error) => return (offset, done, receipts, Some(error)),
192 };
193 let n = outcome.count as usize;
194 if n == 0 || n > data.len() - done {
195 let error = write_failure(
196 NfsError::Rpc("server returned an invalid write count".into()),
197 io.protocol(),
198 done as u64,
199 true,
200 false,
201 );
202 return (offset, done, receipts, Some(error));
203 }
204 done += n;
205 receipts.push(outcome);
206 }
207 (offset, done, receipts, None)
208}
209
210pub(crate) async fn write_all_with<I: WriteIo + ?Sized>(
211 io: &I,
212 fh: Bytes,
213 offset: u64,
214 data: Bytes,
215) -> Result<u64> {
216 let len = data.len() as u64;
217 offset
218 .checked_add(len)
219 .ok_or_else(|| NfsError::InvalidInput("write range overflows u64".into()))?;
220 if data.is_empty() {
221 return Ok(0);
222 }
223 let _batch = io.begin_batch(fh.clone()).await;
224 for attempt in 0..3 {
225 let chunk = io.write_chunk_size().max(1) as usize;
226 let mut ranges = (0..data.len()).step_by(chunk);
227 let mut active = FuturesUnordered::new();
228 let write_range = |start: usize| {
229 let end = data.len().min(start.saturating_add(chunk));
230 write_chunk_with(
231 io,
232 fh.clone(),
233 offset + start as u64,
234 data.slice(start..end),
235 )
236 };
237 for start in ranges.by_ref().take(WRITE_CONCURRENCY) {
238 active.push(write_range(start));
239 }
240 let mut accepted = 0usize;
241 let mut receipts = Vec::new();
242 let mut failure: Option<(u64, NfsError)> = None;
243 let mut uncertain = false;
244 while let Some((start, done, chunk_receipts, error)) = active.next().await {
245 accepted += done;
246 receipts.extend(chunk_receipts);
247 if let Some(error) = error {
248 uncertain |= error
249 .operation_outcome()
250 .is_some_and(|outcome| outcome.outcome == crate::OperationOutcome::Uncertain);
251 if failure.as_ref().is_none_or(|(at, _)| start < *at) {
254 failure = Some((start, error));
255 }
256 }
257 if failure.is_none()
258 && let Some(start) = ranges.next()
259 {
260 active.push(write_range(start));
261 }
262 }
263 if let Some((_, error)) = failure {
264 return Err(write_failure(
265 error,
266 io.protocol(),
267 accepted as u64,
268 false,
269 uncertain,
270 ));
271 }
272 let (commit_offset, count) = u32::try_from(len).map_or((0, 0), |n| (offset, n));
273 match io
274 .commit_batch(fh.clone(), commit_offset, count, &receipts)
275 .await
276 {
277 Ok(()) => return Ok(len),
278 Err(e)
279 if attempt < 2
280 && e.operation_outcome()
281 .is_some_and(|o| o.context().operation == "write_verifier") =>
282 {
283 continue;
284 }
285 Err(e) => return Err(write_failure(e, io.protocol(), len, true, false)),
286 }
287 }
288 unreachable!("last commit attempt always returns")
289}
290
291fn write_failure(
292 error: NfsError,
293 protocol: crate::NFSVersion,
294 accepted: u64,
295 commit: bool,
296 uncertain: bool,
297) -> NfsError {
298 if accepted == 0 && !commit && !uncertain {
305 return error;
306 }
307 NfsError::OperationOutcome(Box::new(
308 crate::OperationOutcomeError::new(
309 crate::OperationOutcome::Uncertain,
310 crate::OperationClass::ReplaySensitive,
311 crate::RecoveryAction::VerifyThenResume,
312 crate::RequestContext {
313 operation: if commit { "commit" } else { "write" }.into(),
314 protocol,
315 request_id: None,
316 },
317 error,
318 )
319 .with_completed_bytes(accepted),
320 ))
321}
322
323pub(crate) const READ_CONCURRENCY: usize = 8;
325
326pub(crate) async fn read_into_with<I, F>(
330 io: &I,
331 fh: Bytes,
332 offset: u64,
333 len: usize,
334 mut fill: F,
335) -> Result<usize>
336where
337 I: ReadIo + ?Sized,
338 F: FnMut(usize, &[u8]) -> Result<()> + Send,
339{
340 read_chunks_with(io, fh, offset, len, |at, piece| fill(at, &piece)).await
341}
342
343pub(crate) async fn read_chunks_with<I, F>(
345 io: &I,
346 fh: Bytes,
347 offset: u64,
348 len: usize,
349 mut fill: F,
350) -> Result<usize>
351where
352 I: ReadIo + ?Sized,
353 F: FnMut(usize, Bytes) -> Result<()> + Send,
354{
355 offset
356 .checked_add(
357 u64::try_from(len)
358 .map_err(|_| NfsError::InvalidInput("read buffer is too large".into()))?,
359 )
360 .ok_or_else(|| NfsError::InvalidInput("read range overflows u64".into()))?;
361 let chunk = io.read_chunk_size().max(1) as usize;
362 let mut reads = stream::iter((0..len).step_by(chunk))
363 .map(|start| {
364 let fh = fh.clone();
365 async move {
366 let want = chunk.min(len - start);
367 let mut got = 0;
368 let mut pieces = Vec::new();
369 while got < want {
370 let data = io
371 .read(
372 fh.clone(),
373 offset + start as u64 + got as u64,
374 (want - got) as u32,
375 )
376 .await?;
377 if data.len() > want - got {
378 return Err(NfsError::Rpc(
379 "server returned more READ data than requested".into(),
380 ));
381 }
382 if data.is_empty() {
383 break;
384 }
385 got += data.len();
386 pieces.push(data);
387 }
388 Ok::<_, NfsError>((start, want, got, pieces))
389 }
390 })
391 .buffered(READ_CONCURRENCY);
392 let mut completed = 0;
393 while let Some(result) = reads.next().await {
394 let (start, want, got, pieces) = result?;
395 let mut at = start;
396 for piece in pieces {
397 let len = piece.len();
398 fill(at, piece)?;
399 at += len;
400 }
401 completed += got;
402 if got < want {
403 break;
404 }
405 }
406 Ok(completed)
407}
408
409pub struct BufferedFile {
412 io: Arc<dyn ChunkIo>,
413 fh: Bytes,
414 writes: tokio::sync::RwLock<FileState>,
415}
416
417#[derive(Clone, Copy, PartialEq, Eq)]
418enum FileState {
419 Open,
420 Closing,
421 Closed,
422}
423
424impl FileState {
425 fn require_open(self) -> Result<()> {
426 if self == Self::Open {
427 Ok(())
428 } else {
429 Err(NfsError::ClosedResource(
430 "BufferedFile is closing or closed".into(),
431 ))
432 }
433 }
434}
435
436impl std::fmt::Debug for BufferedFile {
437 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
438 f.debug_struct("BufferedFile").finish_non_exhaustive()
439 }
440}
441
442impl BufferedFile {
443 pub fn new(mount: Arc<dyn Mount>, fh: Bytes) -> Self {
446 Self::with_io(Arc::new(mount), fh)
447 }
448
449 pub(crate) fn with_io(io: Arc<dyn ChunkIo>, fh: Bytes) -> Self {
450 Self {
451 io,
452 fh,
453 writes: tokio::sync::RwLock::new(FileState::Open),
454 }
455 }
456
457 pub async fn read_at(&self, offset: u64, len: u32) -> Result<Bytes> {
458 let guard = self.writes.read().await;
459 guard.require_open()?;
460 let mut data = BytesMut::new();
461 read_into_with(
462 self.io.as_ref(),
463 self.fh.clone(),
464 offset,
465 len as usize,
466 |_, piece| {
467 data.extend_from_slice(piece);
468 Ok(())
469 },
470 )
471 .await?;
472 Ok(data.freeze())
473 }
474
475 pub async fn write_at(&self, offset: u64, data: Bytes) -> Result<()> {
476 let guard = self.writes.write().await;
477 guard.require_open()?;
478 write_all_with(self.io.as_ref(), self.fh.clone(), offset, data)
479 .await
480 .map(|_| ())
481 }
482
483 pub async fn flush(&self) -> Result<()> {
484 let guard = self.writes.write().await;
485 guard.require_open()?;
486 Ok(())
487 }
488
489 pub async fn close(&self) -> Result<()> {
490 let mut state = self.writes.write().await;
491 match *state {
492 FileState::Closed => return Ok(()),
493 FileState::Closing => {
494 return Err(NfsError::ClosedResource(
495 "BufferedFile close did not settle successfully; clean up the mount".into(),
496 ));
497 }
498 FileState::Open => {}
499 }
500 *state = FileState::Closing;
504 self.io.close(self.fh.clone()).await?;
505 *state = FileState::Closed;
506 Ok(())
507 }
508}
509
510#[cfg(test)]
511mod tests {
512 use super::*;
513 use std::sync::atomic::AtomicU32;
514 use tokio::sync::Mutex as AsyncMutex;
515
516 #[derive(Default)]
517 struct Fake {
518 data: AsyncMutex<Vec<u8>>,
519 reads: AtomicUsize,
520 unstable_writes: AtomicUsize,
521 commits: AtomicUsize,
522 closes: AtomicUsize,
523 max_concurrent_reads: AtomicUsize,
524 concurrent_reads: AtomicUsize,
525 max_concurrent_writes: AtomicUsize,
526 concurrent_writes: AtomicUsize,
527 verifier: AtomicU32,
528 fail_unstable_at: Option<u64>,
529 fail_write_offset: Option<u64>,
530 write_error: Option<fn(u64) -> NfsError>,
531 write_delay: Option<fn(u64) -> u64>,
532 report_stable: bool,
533 commit_verifier_bump: bool,
534 change_once: bool,
535 chunk_size: u32,
536 short_write: usize,
537 short_read: usize,
538 fail_read_at: Option<u64>,
539 fail_close: bool,
540 close_delay: Option<std::time::Duration>,
541 read_delay: Option<fn(u64) -> u64>,
543 }
544
545 #[async_trait]
546 impl ReadIo for Fake {
547 fn read_chunk_size(&self) -> u32 {
548 4
549 }
550 async fn read(&self, _fh: Bytes, offset: u64, count: u32) -> Result<Bytes> {
551 let now = self.concurrent_reads.fetch_add(1, Ordering::SeqCst) + 1;
552 self.max_concurrent_reads.fetch_max(now, Ordering::SeqCst);
553 let delay = self.read_delay.map_or(5, |f| f(offset));
554 tokio::time::sleep(std::time::Duration::from_millis(delay)).await;
555 self.reads.fetch_add(1, Ordering::SeqCst);
556 if self.fail_read_at == Some(offset) {
557 self.concurrent_reads.fetch_sub(1, Ordering::SeqCst);
558 return Err(NfsError::Rpc("scripted read failure".into()));
559 }
560 let count = if self.short_read > 0 {
561 count.min(self.short_read as u32)
562 } else {
563 count
564 };
565 let data = self.data.lock().await;
566 let start = (offset as usize).min(data.len());
567 let end = (start + count as usize).min(data.len());
568 self.concurrent_reads.fetch_sub(1, Ordering::SeqCst);
569 Ok(Bytes::copy_from_slice(&data[start..end]))
570 }
571 }
572 #[async_trait]
573 impl ChunkIo for Fake {
574 async fn close(&self, _fh: Bytes) -> Result<()> {
575 self.closes.fetch_add(1, Ordering::SeqCst);
576 if let Some(delay) = self.close_delay {
577 tokio::time::sleep(delay).await;
578 }
579 if self.fail_close {
580 return Err(NfsError::Rpc("scripted close failure".into()));
581 }
582 Ok(())
583 }
584 }
585 #[async_trait]
586 impl WriteIo for Fake {
587 fn write_chunk_size(&self) -> u32 {
588 if self.chunk_size == 0 {
589 4
590 } else {
591 self.chunk_size
592 }
593 }
594 fn protocol(&self) -> crate::NFSVersion {
595 crate::NFSVersion::NFSv3
596 }
597 async fn write_unstable(
598 &self,
599 _fh: Bytes,
600 offset: u64,
601 data: Bytes,
602 ) -> Result<WriteOutcome> {
603 if self.fail_unstable_at.is_some_and(|at| offset >= at) {
604 return Err(NfsError::Rpc("scripted write failure".to_string()));
605 }
606 let data = if self.short_write > 0 {
607 data.slice(..data.len().min(self.short_write))
608 } else {
609 data
610 };
611 let now = self.concurrent_writes.fetch_add(1, Ordering::SeqCst) + 1;
612 self.max_concurrent_writes.fetch_max(now, Ordering::SeqCst);
613 let delay = self.write_delay.map_or(5, |f| f(offset));
614 tokio::time::sleep(std::time::Duration::from_millis(delay)).await;
615 if let Some(error) = self.write_error {
616 self.concurrent_writes.fetch_sub(1, Ordering::SeqCst);
617 return Err(error(offset));
618 }
619 if self.fail_write_offset == Some(offset) {
620 self.concurrent_writes.fetch_sub(1, Ordering::SeqCst);
621 return Err(NfsError::Rpc("scripted write failure".into()));
622 }
623 self.unstable_writes.fetch_add(1, Ordering::SeqCst);
624 self.store(offset, &data).await;
625 self.concurrent_writes.fetch_sub(1, Ordering::SeqCst);
626 let v = self.verifier.load(Ordering::SeqCst);
627 Ok(WriteOutcome {
628 pnfs: None,
629 count: data.len() as u32,
630 committed: if self.report_stable {
631 crate::WriteCommitted::FileSync
632 } else {
633 crate::WriteCommitted::Unstable
634 },
635 verifier: Some([v as u8; 8]),
636 })
637 }
638 async fn commit_batch(
639 &self,
640 _fh: Bytes,
641 _offset: u64,
642 _count: u32,
643 writes: &[WriteOutcome],
644 ) -> Result<()> {
645 assert_eq!(
646 self.concurrent_writes.load(Ordering::SeqCst),
647 0,
648 "COMMIT raced an active WRITE"
649 );
650 if writes
651 .iter()
652 .all(|w| w.committed == crate::WriteCommitted::FileSync)
653 {
654 return Ok(());
655 }
656 let call = self.commits.fetch_add(1, Ordering::SeqCst);
657 let v = if self.commit_verifier_bump || (self.change_once && call == 0) {
658 self.verifier.fetch_add(1, Ordering::SeqCst) + 1
659 } else {
660 self.verifier.load(Ordering::SeqCst)
661 };
662 crate::mount::verify_write_batch(self.protocol(), writes, Some([v as u8; 8]))
663 }
664 }
665
666 impl Fake {
667 async fn store(&self, offset: u64, data: &[u8]) {
668 let mut file = self.data.lock().await;
669 let end = offset as usize + data.len();
670 if file.len() < end {
671 file.resize(end, 0);
672 }
673 file[offset as usize..end].copy_from_slice(data);
674 }
675 }
676
677 fn file(fake: Arc<Fake>) -> BufferedFile {
678 BufferedFile::with_io(fake, Bytes::from_static(b"fh"))
679 }
680
681 #[tokio::test]
682 async fn concurrent_reads_fill_requested_range_without_prefetch() {
683 let fake = Fake {
684 data: AsyncMutex::new((0..200u8).collect()),
685 read_delay: Some(|offset| if offset == 0 { 20 } else { 1 }),
686 ..Default::default()
687 };
688 let mut target = [255; 100];
689 let n = read_into_with(&fake, Bytes::new(), 0, target.len(), |at, data| {
690 target[at..at + data.len()].copy_from_slice(data);
691 Ok(())
692 })
693 .await
694 .unwrap();
695 assert_eq!(n, 100);
696 assert_eq!(target.as_slice(), &(0..100u8).collect::<Vec<_>>());
697 assert_eq!(fake.reads.load(Ordering::SeqCst), 25);
698 let peak = fake.max_concurrent_reads.load(Ordering::SeqCst);
699 assert!(peak > 1 && peak <= READ_CONCURRENCY);
700 }
701
702 #[tokio::test]
703 async fn owned_read_chunks_are_ordered_with_actual_concurrency() {
704 for (len, expected_peak) in [(3, 1), (12, 3), (40, 8)] {
705 let fake = Fake {
706 data: AsyncMutex::new((0..40u8).collect()),
707 read_delay: Some(|offset| if offset == 0 { 20 } else { 1 }),
708 short_read: 2,
709 ..Default::default()
710 };
711 let mut pieces = Vec::new();
712 let mut next = 0;
713 let count = read_chunks_with(&fake, Bytes::new(), 0, len, |at, piece| {
714 assert_eq!(at, next);
715 next += piece.len();
716 pieces.push(piece);
717 Ok(())
718 })
719 .await
720 .unwrap();
721 assert_eq!(count, len);
722 assert_eq!(pieces.concat(), (0..len as u8).collect::<Vec<_>>());
723 assert_eq!(
724 fake.max_concurrent_reads.load(Ordering::SeqCst),
725 expected_peak
726 );
727 }
728 }
729
730 #[tokio::test]
731 async fn short_reads_are_completed_and_eof_leaves_buffer_tail_untouched() {
732 let fake = Fake {
733 data: AsyncMutex::new((0..25u8).collect()),
734 short_read: 2,
735 ..Default::default()
736 };
737 let mut target = [255; 32];
738 let n = read_into_with(&fake, Bytes::new(), 3, target.len(), |at, data| {
739 target[at..at + data.len()].copy_from_slice(data);
740 Ok(())
741 })
742 .await
743 .unwrap();
744 assert_eq!(n, 22);
745 assert_eq!(&target[..n], &(3..25u8).collect::<Vec<_>>());
746 assert_eq!(&target[n..], &[255; 10]);
747 }
748
749 #[tokio::test]
750 async fn read_error_never_copies_data_beyond_a_hole() {
751 let fake = Fake {
752 data: AsyncMutex::new((0..40u8).collect()),
753 fail_read_at: Some(4),
754 ..Default::default()
755 };
756 let mut target = [255; 20];
757 assert!(
758 read_into_with(&fake, Bytes::new(), 0, target.len(), |at, data| {
759 target[at..at + data.len()].copy_from_slice(data);
760 Ok(())
761 })
762 .await
763 .is_err()
764 );
765 assert_eq!(&target[..4], &[0, 1, 2, 3]);
766 assert_eq!(&target[4..], &[255; 16]);
767 }
768
769 #[tokio::test]
770 async fn empty_and_overflow_reads_send_no_requests() {
771 let fake = Fake::default();
772 assert_eq!(
773 read_into_with(&fake, Bytes::new(), 0, 0, |_, _| panic!("no data"))
774 .await
775 .unwrap(),
776 0
777 );
778 assert!(
779 read_into_with(&fake, Bytes::new(), u64::MAX, 1, |_, _| panic!("no data"))
780 .await
781 .is_err()
782 );
783 assert_eq!(fake.reads.load(Ordering::SeqCst), 0);
784 }
785
786 #[tokio::test]
787 async fn large_read_spans_chunks_and_stops_at_eof() {
788 let fake = Arc::new(Fake {
789 data: AsyncMutex::new((0..10u8).collect()),
790 ..Default::default()
791 });
792 let f = file(fake);
793 let all = f.read_at(0, 64).await.unwrap();
794 assert_eq!(&all[..], &(0..10u8).collect::<Vec<_>>()[..]);
795 assert!(f.read_at(10, 4).await.unwrap().is_empty());
796 }
797
798 #[tokio::test]
799 async fn writes_use_at_most_eight_chunks_and_commit_after_all_short_writes() {
800 let fake = Fake {
801 short_write: 2,
802 write_delay: Some(|offset| if offset == 7 { 20 } else { 2 }),
803 ..Default::default()
804 };
805 let payload = Bytes::from((0..200u8).collect::<Vec<_>>());
806 assert_eq!(
807 write_all_with(&fake, Bytes::new(), 7, payload.clone())
808 .await
809 .unwrap(),
810 200
811 );
812 assert_eq!(&fake.data.lock().await[7..], payload.as_ref());
813 assert_eq!(fake.unstable_writes.load(Ordering::SeqCst), 100);
814 assert_eq!(
815 fake.max_concurrent_writes.load(Ordering::SeqCst),
816 WRITE_CONCURRENCY
817 );
818 assert_eq!(fake.commits.load(Ordering::SeqCst), 1);
819 }
820
821 #[tokio::test]
822 async fn sibling_uncertainty_survives_lowest_offset_definite_error_without_acknowledgements() {
823 for delay in [
824 (|offset| if offset == 0 { 1 } else { 10 }) as fn(u64) -> u64,
825 (|offset| if offset == 0 { 10 } else { 1 }) as fn(u64) -> u64,
826 ] {
827 let fake = Fake {
828 write_delay: Some(delay),
829 write_error: Some(|offset| {
830 if offset == 0 {
831 return NfsError::Nfs3(crate::nfs3::ErrorCode::NFS3ERR_NOSPC);
832 }
833 NfsError::OperationOutcome(Box::new(crate::OperationOutcomeError::new(
834 crate::OperationOutcome::Uncertain,
835 crate::OperationClass::ReplaySensitive,
836 crate::RecoveryAction::VerifyThenResume,
837 crate::RequestContext {
838 operation: "write".into(),
839 protocol: crate::NFSVersion::NFSv3,
840 request_id: None,
841 },
842 NfsError::Rpc("reply lost after transmission".into()),
843 )))
844 }),
845 ..Default::default()
846 };
847 let error = write_all_with(&fake, Bytes::new(), 0, Bytes::from(vec![9; 32]))
848 .await
849 .unwrap_err();
850 let outcome = error
851 .operation_outcome()
852 .expect("sibling may have modified the file");
853 assert_eq!(outcome.outcome, crate::OperationOutcome::Uncertain);
854 assert_eq!(outcome.completed_bytes, Some(0));
855 assert!(matches!(
856 *outcome.source,
857 NfsError::Nfs3(crate::nfs3::ErrorCode::NFS3ERR_NOSPC)
858 ));
859 assert_eq!(fake.concurrent_writes.load(Ordering::SeqCst), 0);
860 assert_eq!(fake.commits.load(Ordering::SeqCst), 0);
861 }
862 }
863
864 #[tokio::test]
865 async fn failed_chunk_stops_admission_settles_siblings_and_skips_commit() {
866 let fake = Fake {
867 fail_write_offset: Some(0),
868 write_delay: Some(|offset| if offset == 0 { 1 } else { 20 }),
869 ..Default::default()
870 };
871 let error = write_all_with(&fake, Bytes::new(), 0, Bytes::from(vec![9; 200]))
872 .await
873 .unwrap_err();
874 assert_eq!(fake.concurrent_writes.load(Ordering::SeqCst), 0);
875 assert_eq!(
876 fake.unstable_writes.load(Ordering::SeqCst),
877 WRITE_CONCURRENCY - 1
878 );
879 assert_eq!(fake.commits.load(Ordering::SeqCst), 0);
880 let outcome = error.operation_outcome().unwrap();
881 assert_eq!(outcome.outcome, crate::OperationOutcome::Uncertain);
882 assert_eq!(
883 outcome.completed_bytes,
884 Some(((WRITE_CONCURRENCY - 1) * 4) as u64)
885 );
886 let data = fake.data.lock().await;
887 assert_eq!(&data[..4], &[0; 4]);
888 assert_eq!(&data[4..], &[9; (WRITE_CONCURRENCY - 1) * 4]);
889 }
890
891 #[tokio::test]
892 async fn small_writes_each_commit_before_return() {
893 let fake = Arc::new(Fake::default());
894 let f = file(fake.clone());
895 f.write_at(0, Bytes::from_static(b"a")).await.unwrap();
896 f.write_at(1, Bytes::from_static(b"b")).await.unwrap();
897 f.write_at(2, Bytes::from_static(b"c")).await.unwrap();
898 f.flush().await.unwrap();
899 assert_eq!(fake.unstable_writes.load(Ordering::SeqCst), 3);
900 assert_eq!(fake.commits.load(Ordering::SeqCst), 3);
901 assert_eq!(&fake.data.lock().await[..], b"abc");
902 }
903
904 #[tokio::test]
905 async fn each_write_commits_before_return_and_flush_does_not_repeat() {
906 let fake = Arc::new(Fake::default());
907 let f = file(fake.clone());
908 f.write_at(0, Bytes::from(vec![42; 32])).await.unwrap();
909 assert_eq!(fake.unstable_writes.load(Ordering::SeqCst), 8);
910 assert_eq!(fake.commits.load(Ordering::SeqCst), 1);
911 f.write_at(32, Bytes::from_static(b"tail")).await.unwrap();
912 assert_eq!(fake.commits.load(Ordering::SeqCst), 2);
913 f.flush().await.unwrap();
914 f.close().await.unwrap();
915 assert_eq!(fake.commits.load(Ordering::SeqCst), 2);
916 assert_eq!(fake.closes.load(Ordering::SeqCst), 1);
917 }
918
919 #[tokio::test]
920 async fn stable_replies_skip_commit() {
921 let fake = Arc::new(Fake {
922 report_stable: true,
923 ..Default::default()
924 });
925 file(fake.clone())
926 .write_at(0, Bytes::from_static(b"abcdefgh"))
927 .await
928 .unwrap();
929 assert_eq!(fake.commits.load(Ordering::SeqCst), 0);
930 }
931
932 #[tokio::test]
933 async fn verifier_recovery_is_bounded_and_never_uses_file_sync() {
934 let fake = Arc::new(Fake {
935 commit_verifier_bump: true,
936 ..Default::default()
937 });
938 assert!(
939 file(fake.clone())
940 .write_at(0, Bytes::from_static(b"abcdefgh"))
941 .await
942 .is_err()
943 );
944 assert_eq!(fake.commits.load(Ordering::SeqCst), 3);
945 assert_eq!(fake.unstable_writes.load(Ordering::SeqCst), 6);
946 }
947
948 #[tokio::test]
949 async fn partial_failure_is_reported_by_write_as_uncertain() {
950 let fake = Arc::new(Fake {
951 fail_unstable_at: Some(4),
952 ..Default::default()
953 });
954 let error = file(fake.clone())
955 .write_at(0, Bytes::from_static(b"abcdefgh"))
956 .await
957 .unwrap_err();
958 assert_eq!(
959 error.operation_outcome().unwrap().outcome,
960 crate::OperationOutcome::Uncertain
961 );
962 assert_eq!(fake.commits.load(Ordering::SeqCst), 0);
963 }
964
965 #[tokio::test]
966 async fn reads_after_write_observe_new_data() {
967 let fake = Arc::new(Fake {
968 data: AsyncMutex::new(vec![0; 16]),
969 ..Default::default()
970 });
971 let f = file(fake.clone());
972 f.read_at(0, 4).await.unwrap();
973 f.write_at(4, Bytes::from_static(b"changed!"))
974 .await
975 .unwrap();
976 assert_eq!(&f.read_at(4, 8).await.unwrap()[..], b"changed!");
977 assert_eq!(fake.commits.load(Ordering::SeqCst), 1);
978 }
979 #[tokio::test]
980 async fn large_call_has_one_commit_and_short_writes_are_completed() {
981 let fake = Arc::new(Fake {
982 chunk_size: 1024 * 1024,
983 ..Default::default()
984 });
985 let data = Bytes::from(vec![7; 17 * 1024 * 1024]);
986 file(fake.clone()).write_at(0, data.clone()).await.unwrap();
987 assert_eq!(fake.unstable_writes.load(Ordering::SeqCst), 17);
988 assert_eq!(fake.commits.load(Ordering::SeqCst), 1);
989 assert_eq!(fake.data.lock().await.as_slice(), data.as_ref());
990
991 let fake = Arc::new(Fake {
992 short_write: 2,
993 ..Default::default()
994 });
995 file(fake.clone())
996 .write_at(0, Bytes::from_static(b"abcdefghij"))
997 .await
998 .unwrap();
999 assert_eq!(fake.unstable_writes.load(Ordering::SeqCst), 5);
1000 assert_eq!(fake.commits.load(Ordering::SeqCst), 1);
1001 assert_eq!(fake.data.lock().await.as_slice(), b"abcdefghij");
1002 }
1003
1004 #[tokio::test]
1005 async fn verifier_change_rewrites_retained_data_and_recommits() {
1006 let fake = Arc::new(Fake {
1007 change_once: true,
1008 ..Default::default()
1009 });
1010 file(fake.clone())
1011 .write_at(0, Bytes::from_static(b"abcdefgh"))
1012 .await
1013 .unwrap();
1014 assert_eq!(fake.unstable_writes.load(Ordering::SeqCst), 4);
1015 assert_eq!(fake.commits.load(Ordering::SeqCst), 2);
1016 }
1017
1018 #[tokio::test]
1019 async fn overflow_is_rejected_before_any_write() {
1020 let fake = Arc::new(Fake::default());
1021 assert!(
1022 file(fake.clone())
1023 .write_at(u64::MAX, Bytes::from_static(b"a"))
1024 .await
1025 .is_err()
1026 );
1027 assert_eq!(fake.unstable_writes.load(Ordering::SeqCst), 0);
1028 }
1029 #[tokio::test]
1030 async fn close_is_idempotent_and_rejects_later_io() {
1031 let fake = Arc::new(Fake::default());
1032 let first = file(fake.clone());
1033 let second = file(fake.clone());
1034 first.close().await.unwrap();
1035 first.close().await.unwrap();
1036 assert_eq!(fake.closes.load(Ordering::SeqCst), 1);
1037 assert!(matches!(
1038 first.read_at(0, 1).await,
1039 Err(NfsError::ClosedResource(_))
1040 ));
1041 assert!(matches!(
1042 first.write_at(0, Bytes::from_static(b"x")).await,
1043 Err(NfsError::ClosedResource(_))
1044 ));
1045 assert!(matches!(
1046 first.flush().await,
1047 Err(NfsError::ClosedResource(_))
1048 ));
1049 second.write_at(0, Bytes::from_static(b"y")).await.unwrap();
1050 second.close().await.unwrap();
1051 assert_eq!(fake.closes.load(Ordering::SeqCst), 2);
1052 }
1053 #[tokio::test]
1054 async fn failed_or_cancelled_close_never_releases_a_second_reference() {
1055 for cancel in [false, true] {
1056 let fake = Arc::new(Fake {
1057 fail_close: !cancel,
1058 close_delay: cancel.then_some(std::time::Duration::from_secs(60)),
1059 ..Default::default()
1060 });
1061 let file = file(fake.clone());
1062 if cancel {
1063 assert!(
1064 tokio::time::timeout(std::time::Duration::from_millis(10), file.close())
1065 .await
1066 .is_err()
1067 );
1068 } else {
1069 assert!(file.close().await.is_err());
1070 }
1071 assert!(matches!(
1072 file.read_at(0, 1).await,
1073 Err(NfsError::ClosedResource(_))
1074 ));
1075 assert!(matches!(
1076 file.close().await,
1077 Err(NfsError::ClosedResource(_))
1078 ));
1079 assert_eq!(fake.closes.load(Ordering::SeqCst), 1);
1080 }
1081 }
1082}