1use arc_swap::ArcSwap;
27use bytes::{Bytes, BytesMut};
28use dashmap::mapref::entry::Entry;
29use dashmap::{DashMap, DashSet};
30use futures::StreamExt;
31use futures::stream::FuturesUnordered;
32pub use ninep_proto::NOFID;
33use ninep_proto::retry::MUTATION_RETRY_HORIZON;
34use ninep_proto::slice_codec::{LOCK_BLOCKED, LOCK_SUCCESS};
35use ninep_proto::*;
36use std::collections::HashMap;
37#[cfg(not(target_arch = "wasm32"))]
38use std::net::{IpAddr, SocketAddr};
39#[cfg(not(target_arch = "wasm32"))]
40use std::path::{Path, PathBuf};
41use std::sync::Arc;
42use std::sync::Mutex;
43use std::sync::atomic::{AtomicBool, AtomicU16, AtomicU32, AtomicU64, Ordering};
44use std::time::Duration;
45#[cfg(not(target_arch = "wasm32"))]
46use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
47#[cfg(not(target_arch = "wasm32"))]
48use tokio::net::{TcpStream, UnixStream};
49use tokio::sync::{Notify, OwnedSemaphorePermit, Semaphore, mpsc, oneshot};
50#[cfg(not(target_arch = "wasm32"))]
51use tokio_util::codec::LengthDelimitedCodec;
52use tracing::{debug, info, warn};
53use uuid::Uuid;
54
55mod linux;
56mod runtime;
57#[cfg(target_arch = "wasm32")]
58mod web_transport;
59
60const NOTAG: u16 = 0xFFFF;
62const MAX_INFLIGHT_REQUESTS: usize = 1024;
64const _: () = assert!(MAX_INFLIGHT_REQUESTS < NOTAG as usize);
65#[cfg(not(target_arch = "wasm32"))]
67const DEFAULT_9P_PORT: u16 = 5564;
68
69const RECONNECT_BACKOFF_MIN: Duration = Duration::from_millis(50);
70const RECONNECT_BACKOFF_MAX: Duration = Duration::from_millis(500);
71const PROBE_TIMEOUT: Duration = Duration::from_secs(3);
73const REQUEST_TIMEOUT: Duration = Duration::from_secs(8);
75const NO_LIVENESS_PROBE_TAG: u32 = u32::MAX;
77
78#[derive(Default)]
80struct OpAttemptState {
81 origin_epoch: Option<u64>,
84 started: Option<runtime::Clock>,
85}
86
87impl OpAttemptState {
88 fn dispatch_frame<T>(
90 &mut self,
91 has_op_id: bool,
92 connection_epoch: u64,
93 dispatch: impl FnOnce(u8, u64) -> ClientResult<T>,
94 ) -> ClientResult<(u8, T)> {
95 let flags = if has_op_id && self.origin_epoch.is_some() {
96 P9_OP_FLAG_RETRY
97 } else {
98 0
99 };
100 let origin_epoch = if has_op_id {
101 self.origin_epoch.unwrap_or(connection_epoch)
102 } else {
103 0
104 };
105 let result = dispatch(flags, origin_epoch)?;
106 if has_op_id {
107 self.origin_epoch.get_or_insert(origin_epoch);
108 if self.started.is_none() {
109 self.started = Some(runtime::Clock::now());
110 }
111 }
112 Ok((flags, result))
113 }
114
115 fn proven_predispatch(&mut self, sent_flags: u8) {
116 if sent_flags & P9_OP_FLAG_RETRY == 0 {
117 self.origin_epoch = None;
118 self.started = None;
119 }
120 }
121
122 fn retry_budget(&self) -> ClientResult<Option<Duration>> {
123 let Some(started) = self.started.as_ref() else {
124 return Ok(None);
125 };
126 let elapsed = Duration::from_millis(started.elapsed_millis());
127 let retry_horizon = MUTATION_RETRY_HORIZON;
128 if elapsed >= retry_horizon {
129 return Err(ClientError::Disconnected);
130 }
131 Ok(Some(retry_horizon - elapsed))
132 }
133}
134
135struct StatefulCancellationGuard<'a> {
138 client: &'a NinePClient,
139 dispatched_conn: &'a Mutex<Option<Arc<Conn>>>,
140 armed: bool,
141}
142
143impl StatefulCancellationGuard<'_> {
144 fn disarm(&mut self) {
145 self.armed = false;
146 }
147}
148
149impl Drop for StatefulCancellationGuard<'_> {
150 fn drop(&mut self) {
151 let conn = self
152 .armed
153 .then(|| self.dispatched_conn.lock().unwrap().take())
154 .flatten();
155 if let Some(conn) = conn {
156 self.client.force_reprobe(&conn);
157 }
158 }
159}
160
161#[derive(Debug)]
162pub enum ClientError {
163 Errno(u32),
165 Disconnected,
167 Unexpected(&'static str),
169 Codec(CodecError),
171}
172
173impl std::fmt::Display for ClientError {
174 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175 match self {
176 ClientError::Errno(e) => write!(f, "server error: errno {e}"),
177 ClientError::Disconnected => write!(f, "9P connection lost"),
178 ClientError::Unexpected(m) => write!(f, "unexpected 9P reply to {m}"),
179 ClientError::Codec(e) => write!(f, "9P codec error: {e}"),
180 }
181 }
182}
183
184impl std::error::Error for ClientError {}
185
186impl ClientError {
187 pub fn to_errno(&self) -> i32 {
190 match self {
191 ClientError::Errno(e) => *e as i32,
192 _ => linux::EIO,
193 }
194 }
195}
196
197pub type ClientResult<T> = Result<T, ClientError>;
198
199async fn await_resend_bounded<T>(
202 attempt: &OpAttemptState,
203 future: impl std::future::Future<Output = T>,
204) -> ClientResult<T> {
205 match attempt.retry_budget()? {
206 Some(remaining) => runtime::timeout(remaining, future)
207 .await
208 .map_err(|_| ClientError::Disconnected),
209 None => Ok(future.await),
210 }
211}
212
213#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
215pub struct TrafficStats {
216 pub bytes_sent: u64,
217 pub bytes_received: u64,
218 pub operations: u64,
219}
220
221#[derive(Default)]
222struct TrafficCounters {
223 bytes_sent: AtomicU64,
224 bytes_received: AtomicU64,
225 operations: AtomicU64,
226}
227
228mod ops;
229pub use ops::{DirEntryCookie, ReaddirState, SetattrBuilder, SetattrTime};
230
231#[derive(Clone, Debug, PartialEq, Eq)]
237pub enum Target {
238 #[cfg(not(target_arch = "wasm32"))]
239 Tcp(SocketAddr),
240 #[cfg(not(target_arch = "wasm32"))]
242 TcpHost(String),
243 #[cfg(not(target_arch = "wasm32"))]
244 Unix(PathBuf),
245 #[cfg(target_arch = "wasm32")]
247 WebSocket(String),
248}
249
250impl Target {
251 pub fn parse_list(spec: &str) -> Result<Vec<Self>, String> {
254 let targets = spec
255 .split(',')
256 .map(str::trim)
257 .filter(|spec| !spec.is_empty())
258 .map(str::parse)
259 .collect::<Result<Vec<_>, _>>()?;
260 if targets.is_empty() {
261 Err("no 9P target given".into())
262 } else {
263 Ok(targets)
264 }
265 }
266}
267
268#[cfg(not(target_arch = "wasm32"))]
269fn parse_numeric_tcp_endpoint(endpoint: &str) -> Option<SocketAddr> {
270 if let Ok(addr) = endpoint.parse::<SocketAddr>() {
271 return Some(addr);
272 }
273
274 if let Some(literal) = endpoint
276 .strip_prefix('[')
277 .and_then(|literal| literal.strip_suffix(']'))
278 && let Ok(ip @ IpAddr::V6(_)) = literal.parse::<IpAddr>()
279 {
280 return Some(SocketAddr::new(ip, DEFAULT_9P_PORT));
281 }
282
283 if !endpoint.starts_with('[')
286 && let Some((host, port)) = endpoint.rsplit_once(':')
287 && let Ok(ip @ IpAddr::V6(_)) = host.parse::<IpAddr>()
288 && let Ok(port) = port.parse::<u16>()
289 {
290 return Some(SocketAddr::new(ip, port));
291 }
292
293 endpoint
294 .parse::<IpAddr>()
295 .ok()
296 .map(|ip| SocketAddr::new(ip, DEFAULT_9P_PORT))
297}
298
299impl std::str::FromStr for Target {
300 type Err = String;
301
302 fn from_str(spec: &str) -> Result<Self, Self::Err> {
303 let spec = spec.trim();
304 if spec.is_empty() {
305 return Err("empty 9P target".into());
306 }
307
308 #[cfg(target_arch = "wasm32")]
309 {
310 return if spec.starts_with("ws://") || spec.starts_with("wss://") {
311 Ok(Self::WebSocket(spec.into()))
312 } else {
313 Err(format!(
314 "browser clients require a ws:// or wss:// target, got {spec:?}"
315 ))
316 };
317 }
318
319 #[cfg(not(target_arch = "wasm32"))]
320 {
321 if let Some(path) = spec.strip_prefix("unix:") {
322 return Ok(Self::Unix(path.strip_prefix("//").unwrap_or(path).into()));
323 }
324 let endpoint = spec.strip_prefix("tcp://").unwrap_or(spec);
325 if endpoint.starts_with('/') || endpoint.starts_with('.') {
326 return Ok(Self::Unix(endpoint.into()));
327 }
328 if let Some(addr) = parse_numeric_tcp_endpoint(endpoint) {
329 return Ok(Self::Tcp(addr));
330 }
331 Ok(Self::TcpHost(if endpoint.contains(':') {
332 endpoint.into()
333 } else {
334 format!("{endpoint}:{DEFAULT_9P_PORT}")
335 }))
336 }
337 }
338}
339
340#[cfg(test)]
341mod target_parse_tests {
342 use super::*;
343
344 #[test]
345 fn target_grammar_matrix() {
346 #[cfg(not(target_arch = "wasm32"))]
347 {
348 let cases = [
349 ("unix:///run/z.sock", r#"Unix("/run/z.sock")"#),
350 ("./z.sock", r#"Unix("./z.sock")"#),
351 ("tcp://127.0.0.1:6000", "Tcp(127.0.0.1:6000)"),
352 ("127.0.0.1", "Tcp(127.0.0.1:5564)"),
353 ("::1", "Tcp([::1]:5564)"),
354 ("leader.example", r#"TcpHost("leader.example:5564")"#),
355 ("leader.example:6000", r#"TcpHost("leader.example:6000")"#),
356 ];
357 for (spec, expected) in cases {
358 assert_eq!(format!("{:?}", spec.parse::<Target>().unwrap()), expected);
359 }
360 assert_eq!(
361 format!(
362 "{:?}",
363 Target::parse_list("retired.invalid, ,leader.example:6000,").unwrap()
364 ),
365 r#"[TcpHost("retired.invalid:5564"), TcpHost("leader.example:6000")]"#
366 );
367 }
368
369 #[cfg(target_arch = "wasm32")]
370 {
371 let targets = Target::parse_list("ws://node-a:5564, ,wss://node-b/9p").unwrap();
372 assert_eq!(targets.len(), 2);
373 assert!(matches!(&targets[0], Target::WebSocket(url) if url == "ws://node-a:5564"));
374 assert!("tcp://node-a:5564".parse::<Target>().is_err());
375 }
376
377 assert!(Target::parse_list(" , ").is_err());
378 assert!(" ".parse::<Target>().is_err());
379 }
380
381 #[cfg(not(target_arch = "wasm32"))]
382 #[test]
383 fn legacy_ipv6_ambiguity_matches_documented_mapping() {
384 let cases = [
385 ("::1", "[::1]:5564"),
386 ("::1:5564", "[::1]:5564"),
387 ("fd00::10", "[fd00::10]:5564"),
388 ("fd00::10:5564", "[fd00::10]:5564"),
389 ("tcp://fd00::10:6000", "[fd00::10]:6000"),
390 ("[fd00::10]:6000", "[fd00::10]:6000"),
391 ("[fd00::10]", "[fd00::10]:5564"),
392 ("tcp://[::1]", "[::1]:5564"),
393 ("[fd00::10:5564]", "[fd00::10:5564]:5564"),
394 ];
395
396 for (spec, expected) in cases {
397 assert_eq!(
398 spec.parse::<Target>(),
399 Ok(Target::Tcp(expected.parse::<SocketAddr>().unwrap())),
400 "target {spec:?}"
401 );
402 }
403 }
404}
405
406struct Conn {
408 writer_tx: mpsc::Sender<Vec<u8>>,
409 pending: DashMap<u16, PendingRequest>,
410 tag_ctr: AtomicU16,
411 lineage_token: AtomicU64,
413 writer_epoch: AtomicU64,
415 dead: AtomicBool,
417 receive_generation: AtomicU64,
422 liveness_probe_tag: AtomicU32,
424 probe_lock: tokio::sync::Mutex<()>,
426 writer_shutdown: Notify,
428 reader_shutdown: Notify,
430 counters: Arc<TrafficCounters>,
431}
432
433type AdmissionPermit = Arc<OwnedSemaphorePermit>;
434
435struct PendingRequest {
439 reply: oneshot::Sender<Bytes>,
440 _admission: Option<AdmissionPermit>,
441}
442
443impl Conn {
444 fn shutdown(&self) {
446 self.dead.store(true, Ordering::Release);
447 self.liveness_probe_tag
448 .store(NO_LIVENESS_PROBE_TAG, Ordering::Release);
449 self.pending.clear();
450 self.reader_shutdown.notify_one();
451 self.writer_shutdown.notify_one();
452 }
453
454 fn note_received_frame(&self) {
456 self.receive_generation.fetch_add(1, Ordering::Release);
457 }
458
459 fn receive_generation(&self) -> u64 {
460 self.receive_generation.load(Ordering::Acquire)
461 }
462
463 fn deliver(&self, frame: Bytes) {
464 self.counters
465 .bytes_received
466 .fetch_add(frame.len() as u64, Ordering::Relaxed);
467 if frame.len() < P9_HEADER_SIZE {
468 warn!(
469 "9P client: response frame too short ({} bytes)",
470 frame.len()
471 );
472 return;
473 }
474 self.note_received_frame();
475 let tag = u16::from_le_bytes([frame[5], frame[6]]);
476 let _ = self.liveness_probe_tag.compare_exchange(
477 u32::from(tag),
478 NO_LIVENESS_PROBE_TAG,
479 Ordering::AcqRel,
480 Ordering::Acquire,
481 );
482 if let Some((_, pending)) = self.pending.remove(&tag) {
483 let _ = pending.reply.send(frame);
484 } else {
485 debug!("9P client: response for unknown tag {tag}");
486 }
487 }
488
489 fn connection_lost(&self, reconnect: &Notify) {
490 self.shutdown();
491 reconnect.notify_waiters();
492 }
493}
494
495struct PendingTag<C: std::ops::Deref<Target = Conn>> {
498 conn: C,
499 tag: u16,
500 dispatched: bool,
501}
502
503impl<C: std::ops::Deref<Target = Conn>> PendingTag<C> {
504 fn mark_dispatched(&mut self) {
505 self.dispatched = true;
506 }
507}
508
509impl<C: std::ops::Deref<Target = Conn>> Drop for PendingTag<C> {
510 fn drop(&mut self) {
511 if !self.dispatched {
512 self.conn.pending.remove(&self.tag);
513 }
514 }
515}
516
517#[derive(Clone)]
519struct FidRecord {
520 inode_id: u64,
521 root_inode: u64,
523 n_uname: u32,
524 uname: Vec<u8>,
526 opened: Option<u32>,
528}
529
530impl FidRecord {
531 fn replay_identity(&self) -> (u32, u64, Vec<u8>) {
532 (self.n_uname, self.root_inode, self.uname.clone())
533 }
534}
535
536#[derive(Clone)]
537struct LockRecord {
538 fid: u32,
539 lock_type: LockType,
540 start: u64,
541 length: u64,
542 proc_id: u32,
543 client_id: Vec<u8>,
544}
545
546#[derive(Default, Clone)]
548struct SessionState {
549 fids: HashMap<u32, FidRecord>,
550 locks: Vec<LockRecord>,
551 default_root: Option<u64>,
553}
554
555impl SessionState {
556 fn forget_fid(&mut self, fid: u32) {
557 self.fids.remove(&fid);
558 self.locks.retain(|lock| lock.fid != fid);
559 }
560}
561
562pub struct NinePClient {
563 targets: Vec<Target>,
565 conn: ArcSwap<Conn>,
567 live: AtomicBool,
569 terminal_errno: AtomicU32,
571 live_notify: Notify,
572 reconnect_notify: Arc<Notify>,
573 msize: u32,
576 msize_mismatch_warned: Arc<AtomicBool>,
578 fid_ctr: AtomicU32,
579 fid_free: Mutex<Vec<u32>>,
580 state: Mutex<SessionState>,
582 stale_fids: DashSet<u32>,
584 session_transition: tokio::sync::Mutex<()>,
586 request_admission: Arc<Semaphore>,
589 unsynced: DashMap<u64, Unsynced>,
591 counters: Arc<TrafficCounters>,
592}
593
594#[derive(Default)]
597struct Unsynced {
598 oldest: Option<u64>,
599 generation: u64,
600 reported: bool,
601}
602
603#[derive(Clone, Copy)]
604enum FsyncScope {
605 Inode(u64),
606 All,
607}
608
609impl FsyncScope {
610 fn wire_flag(self) -> u32 {
611 match self {
612 Self::Inode(_) => P9_FSYNC_INODE,
613 Self::All => 0,
614 }
615 }
616}
617
618impl Unsynced {
619 fn note(&mut self, token: u64) {
621 if self.reported {
622 self.oldest = Some(token);
623 self.reported = false;
624 } else {
625 self.oldest = Some(self.oldest.map_or(token, |oldest| oldest.min(token)));
626 }
627 self.generation = self.generation.wrapping_add(1);
628 }
629
630 fn snapshot(&self) -> (Option<u64>, u64) {
631 (self.oldest, self.generation)
632 }
633
634 fn clear_if_unchanged(&mut self, generation: u64) {
636 if self.generation == generation {
637 self.oldest = None;
638 self.reported = false;
639 }
640 }
641
642 fn report_if_unchanged(&mut self, generation: u64) {
644 if self.generation == generation {
645 self.reported = true;
646 }
647 }
648}
649
650impl NinePClient {
651 #[cfg(not(target_arch = "wasm32"))]
653 pub async fn connect_tcp(addr: SocketAddr, requested_msize: u32) -> std::io::Result<Arc<Self>> {
654 Self::connect(vec![Target::Tcp(addr)], requested_msize)
655 .await
656 .map_err(|e| std::io::Error::other(e.to_string()))
657 }
658
659 #[cfg(not(target_arch = "wasm32"))]
661 pub async fn connect_unix(
662 path: impl AsRef<Path>,
663 requested_msize: u32,
664 ) -> std::io::Result<Arc<Self>> {
665 Self::connect(
666 vec![Target::Unix(path.as_ref().to_path_buf())],
667 requested_msize,
668 )
669 .await
670 .map_err(|e| std::io::Error::other(e.to_string()))
671 }
672
673 pub async fn connect_multi(
675 targets: Vec<Target>,
676 requested_msize: u32,
677 ) -> std::io::Result<Arc<Self>> {
678 Self::connect(targets, requested_msize)
679 .await
680 .map_err(|e| std::io::Error::other(e.to_string()))
681 }
682
683 #[cfg(target_arch = "wasm32")]
685 pub async fn connect_websocket(url: &str, requested_msize: u32) -> ClientResult<Arc<Self>> {
686 Self::connect(vec![Target::WebSocket(url.to_string())], requested_msize).await
687 }
688
689 async fn connect(targets: Vec<Target>, requested_msize: u32) -> ClientResult<Arc<Self>> {
690 let reconnect_notify = Arc::new(Notify::new());
691 let counters = Arc::new(TrafficCounters::default());
692 let msize_mismatch_warned = Arc::new(AtomicBool::new(false));
693 let (conn, msize) = Self::probe(
694 &targets,
695 requested_msize,
696 None,
697 Arc::clone(&reconnect_notify),
698 Arc::clone(&counters),
699 Arc::clone(&msize_mismatch_warned),
700 )
701 .await?;
702
703 let client = Arc::new(Self {
704 targets,
705 conn: ArcSwap::new(conn),
706 live: AtomicBool::new(true),
707 terminal_errno: AtomicU32::new(0),
708 live_notify: Notify::new(),
709 reconnect_notify,
710 msize,
711 msize_mismatch_warned,
712 fid_ctr: AtomicU32::new(1),
713 fid_free: Mutex::new(Vec::new()),
714 state: Mutex::new(SessionState::default()),
715 stale_fids: DashSet::new(),
716 session_transition: tokio::sync::Mutex::new(()),
717 request_admission: Arc::new(Semaphore::new(MAX_INFLIGHT_REQUESTS)),
718 unsynced: DashMap::new(),
719 counters,
720 });
721 client.spawn_supervisor();
722 Ok(client)
723 }
724
725 async fn connect_once(
727 target: &Target,
728 requested_msize: u32,
729 required_msize: Option<u32>,
730 reconnect_notify: Arc<Notify>,
731 counters: Arc<TrafficCounters>,
732 msize_mismatch_warned: Arc<AtomicBool>,
733 ) -> ClientResult<(Arc<Conn>, u32)> {
734 let transport = dial(target).await?;
735 let (writer_tx, writer_rx) = mpsc::channel::<Vec<u8>>(P9_CHANNEL_SIZE);
736 let conn = Arc::new(Conn {
737 writer_tx,
738 pending: DashMap::new(),
739 tag_ctr: AtomicU16::new(0),
740 lineage_token: AtomicU64::new(0),
741 writer_epoch: AtomicU64::new(0),
742 dead: AtomicBool::new(false),
743 receive_generation: AtomicU64::new(0),
744 liveness_probe_tag: AtomicU32::new(NO_LIVENESS_PROBE_TAG),
745 probe_lock: tokio::sync::Mutex::new(()),
746 writer_shutdown: Notify::new(),
747 reader_shutdown: Notify::new(),
748 counters,
749 });
750
751 match transport {
752 #[cfg(not(target_arch = "wasm32"))]
753 DialedTransport::Native { read, write } => {
754 spawn_writer(
755 write,
756 writer_rx,
757 Arc::clone(&conn),
758 Arc::clone(&reconnect_notify),
759 );
760 spawn_reader(read, Arc::clone(&conn), reconnect_notify);
761 }
762 #[cfg(target_arch = "wasm32")]
763 DialedTransport::WebSocket(io) => {
764 web_transport::spawn(io, writer_rx, Arc::clone(&conn), reconnect_notify);
765 }
766 }
767
768 match runtime::timeout(PROBE_TIMEOUT, negotiate_on(&conn, requested_msize)).await {
769 Ok(Ok(msize)) => {
770 if let Some(required) = required_msize
771 && msize != required
772 {
773 if !msize_mismatch_warned.swap(true, Ordering::AcqRel) {
774 warn!(
775 "9P reconnect candidate negotiated msize {msize}; logical session requires {required}"
776 );
777 } else {
778 debug!(
779 "9P reconnect candidate still negotiates msize {msize}; logical session requires {required}"
780 );
781 }
782 conn.shutdown();
783 return Err(ClientError::Unexpected("version"));
784 }
785 Ok((conn, msize))
786 }
787 Ok(Err(e)) => {
789 conn.shutdown();
790 Err(e)
791 }
792 Err(_) => {
793 conn.shutdown();
794 Err(ClientError::Disconnected)
795 }
796 }
797 }
798
799 async fn probe(
801 targets: &[Target],
802 requested_msize: u32,
803 required_msize: Option<u32>,
804 reconnect_notify: Arc<Notify>,
805 counters: Arc<TrafficCounters>,
806 msize_mismatch_warned: Arc<AtomicBool>,
807 ) -> ClientResult<(Arc<Conn>, u32)> {
808 let mut probes = FuturesUnordered::new();
809 for target in targets {
810 let target = target.clone();
811 let notify = Arc::clone(&reconnect_notify);
812 let counters = Arc::clone(&counters);
813 let warned = Arc::clone(&msize_mismatch_warned);
814 probes.push(async move {
815 Self::connect_once(
816 &target,
817 requested_msize,
818 required_msize,
819 notify,
820 counters,
821 warned,
822 )
823 .await
824 });
825 }
826
827 let mut last_err = None;
828 let mut winner = None;
829 while let Some(res) = probes.next().await {
830 match res {
831 Ok(triple) => {
832 winner = Some(triple);
833 break;
834 }
835 Err(e) => last_err = Some(e),
836 }
837 }
838
839 if !probes.is_empty() {
841 runtime::spawn(async move {
842 while let Some(res) = probes.next().await {
843 if let Ok((conn, _)) = res {
844 conn.shutdown();
845 }
846 }
847 });
848 }
849
850 winner.ok_or_else(|| last_err.unwrap_or(ClientError::Disconnected))
851 }
852
853 fn spawn_supervisor(self: &Arc<Self>) {
855 let weak = Arc::downgrade(self);
856 let notify = Arc::clone(&self.reconnect_notify);
857 runtime::spawn(async move {
858 loop {
859 loop {
861 let notified = notify.notified();
862 tokio::pin!(notified);
863 notified.as_mut().enable();
864 let this = match weak.upgrade() {
865 Some(t) => t,
866 None => return,
867 };
868 if this.conn.load().dead.load(Ordering::Acquire) {
869 this.live.store(false, Ordering::Release);
870 break;
871 }
872 drop(this);
873 notified.await;
874 }
875
876 warn!("9P connection lost; reconnecting and replaying session…");
877 let mut backoff = RECONNECT_BACKOFF_MIN;
878 loop {
879 let this = match weak.upgrade() {
880 Some(t) => t,
881 None => return,
882 };
883 match this.reconnect_once().await {
884 Ok(()) => {
885 this.live.store(true, Ordering::Release);
886 this.live_notify.notify_waiters();
887 info!("9P session reconnected; replay complete");
888 break;
889 }
890 Err(e) => {
891 if this.terminal_errno.load(Ordering::Acquire) != 0 {
892 warn!("9P session replay failed permanently: {e}");
893 return;
894 }
895 debug!("9P reconnect failed ({e}); retrying in {backoff:?}");
896 drop(this);
897 runtime::sleep(backoff).await;
898 backoff = (backoff * 2).min(RECONNECT_BACKOFF_MAX);
899 }
900 }
901 }
902 }
903 });
904 }
905
906 async fn reconnect_once(self: &Arc<Self>) -> ClientResult<()> {
908 let msize = self.msize();
909 let (conn, msize) = Self::probe(
910 &self.targets,
911 msize,
912 Some(msize),
913 Arc::clone(&self.reconnect_notify),
914 Arc::clone(&self.counters),
915 Arc::clone(&self.msize_mismatch_warned),
916 )
917 .await?;
918
919 let _transition = self.session_transition.lock().await;
921 debug_assert_eq!(msize, self.msize);
922 if let Err(error) = self.replay_supervised(&conn).await {
923 self.discard_replay_candidate(&conn).await;
924 return Err(error);
925 }
926 let old = self.conn.swap(conn);
927 old.dead.store(true, Ordering::Release);
928 old.writer_shutdown.notify_one();
929
930 Ok(())
931 }
932
933 async fn discard_replay_candidate(&self, conn: &Conn) {
936 match runtime::timeout(PROBE_TIMEOUT, version_on(conn, self.msize)).await {
937 Ok(Ok(_)) => {}
938 Ok(Err(error)) => {
939 debug!("9P replay candidate reset failed ({error}); closing connection");
940 }
941 Err(_) => {
942 debug!("9P replay candidate reset timed out; closing connection");
943 }
944 }
945 conn.shutdown();
946 }
947
948 async fn replay_supervised(self: &Arc<Self>, conn: &Conn) -> ClientResult<()> {
950 self.replay_inner(conn, REQUEST_TIMEOUT, Some(self)).await
951 }
952
953 #[cfg(test)]
954 async fn replay(&self, conn: &Conn) -> ClientResult<()> {
955 self.replay_inner(conn, REQUEST_TIMEOUT, None).await
956 }
957
958 #[cfg(test)]
959 async fn replay_with_rpc_timeout(
960 &self,
961 conn: &Conn,
962 rpc_timeout: Duration,
963 ) -> ClientResult<()> {
964 self.replay_inner(conn, rpc_timeout, None).await
965 }
966
967 async fn replay_inner(
969 &self,
970 conn: &Conn,
971 rpc_timeout: Duration,
972 supervisor_owner: Option<&Arc<Self>>,
973 ) -> ClientResult<()> {
974 let snapshot = self.state.lock().unwrap().clone();
975 let mut gone_unopened = Vec::new();
976 let mut stale_opened = Vec::new();
977
978 let (roots, descendants): (Vec<_>, Vec<_>) = snapshot
979 .fids
980 .iter()
981 .partition(|(_, rec)| rec.inode_id == rec.root_inode);
982 for (&fid, rec) in roots.into_iter().chain(descendants) {
983 let has_lock = snapshot.locks.iter().any(|lock| lock.fid == fid);
984 let restored = self.replay_fid(conn, fid, rec, rpc_timeout).await?;
985 if !restored {
986 if has_lock {
987 return Err(self.replay_state_lost("fid with a held lock could not be rebound"));
988 }
989 if rec.opened.is_some() {
990 stale_opened.push(fid);
991 } else {
992 gone_unopened.push(fid);
993 }
994 continue;
995 }
996 if let Some(flags) = rec.opened {
997 match Self::send_replay_rpc(
998 conn,
999 Message::Tlopen(Tlopen { fid, flags }),
1000 rpc_timeout,
1001 )
1002 .await
1003 {
1004 Ok(Message::Rlopen(_)) => {}
1005 Ok(_) => {
1006 return Err(
1007 self.replay_state_lost("opened fid returned a non-Rlopen reply")
1008 );
1009 }
1010 Err(ClientError::Errno(errno)) if Self::replay_state_lost_errno(errno) => {
1011 if has_lock {
1012 return Err(self.replay_state_lost("locked fid could not be reopened"));
1013 }
1014 match Self::send_replay_rpc(
1015 conn,
1016 Message::Tclunk(Tclunk { fid }),
1017 rpc_timeout,
1018 )
1019 .await
1020 {
1021 Ok(Message::Rclunk(_)) => {}
1022 Ok(_) => return Err(ClientError::Unexpected("replay clunk")),
1023 Err(error) => return Err(error),
1024 }
1025 stale_opened.push(fid);
1026 }
1027 Err(e) => return Err(e),
1028 }
1029 }
1030 }
1031
1032 self.replay_locks(conn, &snapshot.locks, rpc_timeout, supervisor_owner)
1033 .await?;
1034
1035 if !gone_unopened.is_empty() || !stale_opened.is_empty() {
1036 let mut state = self.state.lock().unwrap();
1037 for fid in gone_unopened.iter().chain(&stale_opened) {
1038 state.fids.remove(fid);
1039 }
1040 state.locks.retain(|lock| {
1041 !gone_unopened.contains(&lock.fid) && !stale_opened.contains(&lock.fid)
1042 });
1043 drop(state);
1044 for fid in &stale_opened {
1045 self.stale_fids.insert(*fid);
1046 }
1047 }
1048 Ok(())
1049 }
1050
1051 async fn replay_locks(
1053 &self,
1054 conn: &Conn,
1055 locks: &[LockRecord],
1056 rpc_timeout: Duration,
1057 supervisor_owner: Option<&Arc<Self>>,
1058 ) -> ClientResult<()> {
1059 let mut conflict_backoff = RECONNECT_BACKOFF_MIN;
1060 'lock_replay: loop {
1061 if supervisor_owner.is_some_and(|owner| Arc::strong_count(owner) == 1) {
1065 conn.shutdown();
1066 return Err(ClientError::Disconnected);
1067 }
1068 let mut acquired = Vec::new();
1070 for lk in locks {
1071 let body = Message::Tlock(Tlock {
1072 fid: lk.fid,
1073 lock_type: lk.lock_type,
1074 flags: 0,
1075 start: lk.start,
1076 length: lk.length,
1077 proc_id: lk.proc_id,
1078 client_id: P9String::new(lk.client_id.clone()),
1079 });
1080 match Self::send_replay_rpc(conn, body, rpc_timeout).await {
1081 Ok(Message::Rlock(r)) if r.status == LockStatus::Success.as_wire() => {
1082 acquired.push(lk);
1083 }
1084 Ok(Message::Rlock(Rlock {
1085 status: LOCK_BLOCKED,
1086 }))
1087 | Err(ClientError::Errno(linux::EAGAIN)) => {
1088 let rolled_back = Self::rollback_replayed_locks(conn, &acquired).await;
1089 if !rolled_back {
1090 return Err(ClientError::Errno(linux::EAGAIN));
1091 }
1092 runtime::sleep(conflict_backoff).await;
1093 conflict_backoff = (conflict_backoff * 2).min(RECONNECT_BACKOFF_MAX);
1094 continue 'lock_replay;
1095 }
1096 Ok(Message::Rlock(_)) => {
1097 Self::rollback_replayed_locks(conn, &acquired).await;
1098 return Err(self.replay_state_lost("recorded lock was not reacquired"));
1099 }
1100 Ok(_) => {
1101 Self::rollback_replayed_locks(conn, &acquired).await;
1102 return Err(
1103 self.replay_state_lost("lock replay returned a non-Rlock reply")
1104 );
1105 }
1106 Err(ClientError::Errno(errno)) if Self::replay_state_lost_errno(errno) => {
1107 Self::rollback_replayed_locks(conn, &acquired).await;
1108 return Err(self.replay_state_lost("recorded lock was refused"));
1109 }
1110 Err(e) => {
1111 Self::rollback_replayed_locks(conn, &acquired).await;
1112 return Err(e);
1113 }
1114 }
1115 }
1116 return Ok(());
1117 }
1118 }
1119
1120 async fn rollback_replayed_locks(conn: &Conn, acquired: &[&LockRecord]) -> bool {
1122 if acquired.is_empty() {
1123 return true;
1124 }
1125 let rollback = async {
1126 let mut requests = FuturesUnordered::new();
1127 for lk in acquired.iter().rev() {
1128 requests.push(Self::send_raw_rpc(
1129 conn,
1130 Message::Tlock(Tlock {
1131 fid: lk.fid,
1132 lock_type: LockType::Unlock,
1133 flags: 0,
1134 start: lk.start,
1135 length: lk.length,
1136 proc_id: lk.proc_id,
1137 client_id: P9String::new(lk.client_id.clone()),
1138 }),
1139 ));
1140 }
1141 let mut acknowledged = true;
1142 while let Some(result) = requests.next().await {
1143 if !matches!(
1144 result,
1145 Ok(Message::Rlock(Rlock {
1146 status: LOCK_SUCCESS
1147 }))
1148 ) {
1149 debug!("9P lock replay rollback was not acknowledged");
1150 acknowledged = false;
1151 }
1152 }
1153 acknowledged
1154 };
1155 match runtime::timeout(PROBE_TIMEOUT, rollback).await {
1156 Ok(acknowledged) => acknowledged,
1157 Err(_) => {
1158 warn!("9P lock replay rollback timed out; discarding candidate session");
1159 false
1160 }
1161 }
1162 }
1163
1164 async fn replay_fid(
1166 &self,
1167 conn: &Conn,
1168 fid: u32,
1169 rec: &FidRecord,
1170 rpc_timeout: Duration,
1171 ) -> ClientResult<bool> {
1172 let body = Message::Trebind(Trebind {
1173 fid,
1174 inode_id: rec.inode_id,
1175 root_inode: rec.root_inode,
1176 flags: P9_REBIND_REPLAY
1177 | if rec.opened.is_some() {
1178 P9_REBIND_OPENED
1179 } else {
1180 0
1181 },
1182 uname: P9String::new(rec.uname.clone()),
1183 n_uname: rec.n_uname,
1184 });
1185 match Self::send_replay_rpc(conn, body, rpc_timeout).await {
1186 Ok(Message::Rrebind(_)) => Ok(true),
1187 Ok(_) => Err(ClientError::Unexpected("replay fid")),
1188 Err(ClientError::Errno(errno)) if Self::replay_state_lost_errno(errno) => Ok(false),
1189 Err(e) => Err(e),
1190 }
1191 }
1192
1193 fn replay_state_lost_errno(errno: u32) -> bool {
1194 matches!(errno, linux::ENOENT | linux::ESTALE)
1195 }
1196
1197 fn validate_fids(&self, fids: impl IntoIterator<Item = u32>) -> ClientResult<()> {
1198 if fids.into_iter().any(|fid| self.stale_fids.contains(&fid)) {
1199 return Err(ClientError::Errno(linux::ESTALE));
1200 }
1201 Ok(())
1202 }
1203
1204 pub fn msize(&self) -> u32 {
1206 self.msize
1207 }
1208
1209 pub fn is_connected(&self) -> bool {
1212 self.live.load(Ordering::Acquire)
1213 }
1214
1215 pub fn traffic_stats(&self) -> TrafficStats {
1217 TrafficStats {
1218 bytes_sent: self.counters.bytes_sent.load(Ordering::Relaxed),
1219 bytes_received: self.counters.bytes_received.load(Ordering::Relaxed),
1220 operations: self.counters.operations.load(Ordering::Relaxed),
1221 }
1222 }
1223
1224 fn mutation_inodes(&self, body: &Message) -> ClientResult<Vec<u64>> {
1230 if !body.is_mutation() {
1231 return Ok(Vec::new());
1232 }
1233 self.validate_fids(body.durability_fids())?;
1234 let state = self.state.lock().unwrap();
1235 let mut inodes = Vec::with_capacity(2);
1236 for fid in body.durability_fids() {
1237 let inode = state
1238 .fids
1239 .get(&fid)
1240 .map(|record| record.inode_id)
1241 .ok_or(ClientError::Errno(linux::EBADF))?;
1242 if !inodes.contains(&inode) {
1243 inodes.push(inode);
1244 }
1245 }
1246 Ok(inodes)
1247 }
1248
1249 fn note_unsynced(&self, inode: u64, token: u64) {
1251 self.unsynced.entry(inode).or_default().note(token);
1252 }
1253
1254 fn fid_inode(&self, fid: u32) -> ClientResult<u64> {
1256 self.validate_fids([fid])?;
1257 self.state
1258 .lock()
1259 .unwrap()
1260 .fids
1261 .get(&fid)
1262 .map(|record| record.inode_id)
1263 .ok_or(ClientError::Errno(linux::EBADF))
1264 }
1265
1266 fn snapshot_unsynced(&self, inode: u64) -> (Option<u64>, u64) {
1268 self.unsynced
1269 .get(&inode)
1270 .map_or((None, 0), |u| u.snapshot())
1271 }
1272
1273 fn clear_unsynced_if_unchanged(&self, inode: u64, generation: u64) {
1276 if let Some(mut u) = self.unsynced.get_mut(&inode) {
1277 u.clear_if_unchanged(generation);
1278 }
1279 self.unsynced.remove_if(&inode, |_, u| u.oldest.is_none());
1280 }
1281
1282 fn report_unsynced_if_unchanged(&self, inode: u64, generation: u64) {
1284 if let Some(mut u) = self.unsynced.get_mut(&inode) {
1285 u.report_if_unchanged(generation);
1286 }
1287 }
1288
1289 pub fn max_io(&self) -> u32 {
1292 self.msize().saturating_sub(P9_IOHDRSZ)
1293 }
1294
1295 pub fn max_write_payload(&self) -> u32 {
1300 self.msize()
1301 .saturating_sub(P9_TWRITE_HDR)
1302 .saturating_sub(P9_OP_ENVELOPE_LEN as u32)
1303 }
1304
1305 pub fn alloc_fid(&self) -> u32 {
1307 if let Some(fid) = self.fid_free.lock().unwrap().pop() {
1308 return fid;
1309 }
1310 self.fid_ctr.fetch_add(1, Ordering::Relaxed)
1311 }
1312
1313 pub fn free_fid(&self, fid: u32) {
1315 self.stale_fids.remove(&fid);
1316 self.fid_free.lock().unwrap().push(fid);
1317 }
1318
1319 pub fn outstanding_fids(&self) -> usize {
1321 let allocated = self.fid_ctr.load(Ordering::Relaxed).saturating_sub(1) as usize;
1322 allocated.saturating_sub(self.fid_free.lock().unwrap().len())
1323 }
1324
1325 fn fail_session(&self, errno: u32) {
1327 let _ = self
1328 .terminal_errno
1329 .compare_exchange(0, errno, Ordering::AcqRel, Ordering::Acquire);
1330 self.live.store(false, Ordering::Release);
1331 self.live_notify.notify_waiters();
1332 }
1333
1334 fn replay_state_lost(&self, reason: &str) -> ClientError {
1335 warn!("9P session replay cannot preserve observed state: {reason}");
1336 self.fail_session(linux::ESTALE);
1337 ClientError::Errno(linux::ESTALE)
1338 }
1339
1340 async fn wait_until_live(&self) -> ClientResult<()> {
1342 let terminal_errno = self.terminal_errno.load(Ordering::Acquire);
1343 if terminal_errno != 0 {
1344 return Err(ClientError::Errno(terminal_errno));
1345 }
1346 if self.live.load(Ordering::Acquire) {
1347 return Ok(());
1348 }
1349 loop {
1350 let notified = self.live_notify.notified();
1351 tokio::pin!(notified);
1352 notified.as_mut().enable();
1354 let terminal_errno = self.terminal_errno.load(Ordering::Acquire);
1355 if terminal_errno != 0 {
1356 return Err(ClientError::Errno(terminal_errno));
1357 }
1358 if self.live.load(Ordering::Acquire) {
1359 return Ok(());
1360 }
1361 notified.await;
1362 }
1363 }
1364
1365 fn register_tag(
1368 conn: &Conn,
1369 tag: u16,
1370 tx: oneshot::Sender<Bytes>,
1371 admission: Option<AdmissionPermit>,
1372 ) -> Result<u16, oneshot::Sender<Bytes>> {
1373 match conn.pending.entry(tag) {
1374 Entry::Vacant(slot) => {
1375 slot.insert(PendingRequest {
1376 reply: tx,
1377 _admission: admission,
1378 });
1379 Ok(tag)
1380 }
1381 Entry::Occupied(_) => Err(tx),
1382 }
1383 }
1384
1385 fn alloc_tag(
1387 conn: &Conn,
1388 mut tx: oneshot::Sender<Bytes>,
1389 admission: Option<&AdmissionPermit>,
1390 ) -> Result<u16, oneshot::Sender<Bytes>> {
1391 for _ in 0..=usize::from(NOTAG) {
1393 let candidate = conn.tag_ctr.fetch_add(1, Ordering::Relaxed);
1394 if candidate == NOTAG {
1395 continue;
1396 }
1397 match Self::register_tag(conn, candidate, tx, admission.cloned()) {
1398 Ok(tag) => return Ok(tag),
1399 Err(returned) => tx = returned,
1400 }
1401 }
1402 Err(tx)
1403 }
1404
1405 async fn send_request_on_current(
1407 &self,
1408 op_id: [u8; 16],
1409 body: &Message,
1410 attempt: &mut OpAttemptState,
1411 stateful_dispatched_conn: Option<&Mutex<Option<Arc<Conn>>>>,
1412 admission: &AdmissionPermit,
1413 ) -> ClientResult<(Message, Arc<Conn>)> {
1414 let mutation_inodes = self.mutation_inodes(body)?;
1418 'resend: loop {
1419 self.validate_fids(body.request_fids())?;
1420 await_resend_bounded(attempt, self.wait_until_live()).await??;
1421 let conn = self.conn.load_full();
1422 self.validate_fids(body.request_fids())?;
1423
1424 let permit = match await_resend_bounded(attempt, conn.writer_tx.reserve()).await? {
1426 Ok(permit) => permit,
1427 Err(_) => {
1428 runtime::yield_now().await;
1429 continue;
1430 }
1431 };
1432
1433 if conn.dead.load(Ordering::Acquire) {
1435 drop(permit);
1436 self.reconnect_notify.notify_waiters();
1437 runtime::yield_now().await;
1438 continue;
1439 }
1440
1441 let (otx, mut orx) = oneshot::channel();
1442 let tag = match Self::alloc_tag(&conn, otx, Some(admission)) {
1443 Ok(tag) => tag,
1444 Err(_) => {
1445 drop(permit);
1447 self.force_reprobe(&conn);
1448 runtime::yield_now().await;
1449 continue;
1450 }
1451 };
1452 let mut pending = PendingTag {
1453 conn: Arc::clone(&conn),
1454 tag,
1455 dispatched: false,
1456 };
1457
1458 if conn.dead.load(Ordering::Acquire) {
1461 drop(permit);
1462 runtime::yield_now().await;
1463 continue;
1464 }
1465
1466 let has_op_id = op_id != [0u8; 16];
1468 let connection_epoch = conn.writer_epoch.load(Ordering::Relaxed);
1469 let (op_flags, ()) =
1470 attempt.dispatch_frame(has_op_id, connection_epoch, |op_flags, origin_epoch| {
1471 let bytes = P9Message::encode_body(
1472 tag,
1473 MutationEnvelope {
1474 op_id,
1475 flags: op_flags,
1476 origin_writer_epoch: origin_epoch,
1477 },
1478 body,
1479 true,
1480 )
1481 .map_err(ClientError::Codec)?;
1482
1483 pending.mark_dispatched();
1485 if let Some(dispatched_conn) = stateful_dispatched_conn {
1486 *dispatched_conn.lock().unwrap() = Some(Arc::clone(&conn));
1487 }
1488 permit.send(bytes);
1489 Ok(())
1490 })?;
1491 let mut observed_receive_generation = conn.receive_generation();
1494 let frame = loop {
1495 match runtime::timeout(REQUEST_TIMEOUT, &mut orx).await {
1496 Ok(Ok(frame)) => break frame,
1497 Ok(Err(_)) => {
1498 runtime::yield_now().await;
1500 continue 'resend;
1501 }
1502 Err(_) => {
1503 let current_generation = conn.receive_generation();
1504 if current_generation != observed_receive_generation {
1505 observed_receive_generation = current_generation;
1506 continue;
1507 }
1508 if Self::probe_connection(&conn, current_generation).await {
1509 observed_receive_generation = conn.receive_generation();
1510 continue;
1511 }
1512 self.force_reprobe(&conn);
1513 runtime::yield_now().await;
1514 continue 'resend;
1515 }
1516 }
1517 };
1518
1519 let msg = P9Message::from_owned_bytes_ctx(frame, false).map_err(ClientError::Codec)?;
1522 if let Message::Rlerror(ref e) = msg.body
1523 && matches!(e.ecode, P9_ENOTLEADER | P9_ENOTLEADER_CLEAN)
1524 {
1525 if e.ecode == P9_ENOTLEADER_CLEAN {
1526 attempt.proven_predispatch(op_flags);
1527 if let Some(dispatched_conn) = stateful_dispatched_conn {
1528 dispatched_conn.lock().unwrap().take();
1529 }
1530 }
1531 self.force_reprobe(&conn);
1532 runtime::yield_now().await;
1533 continue;
1534 }
1535 if !matches!(msg.body, Message::Rlerror(_)) {
1537 let token = conn.lineage_token.load(Ordering::Relaxed);
1538 for &inode in &mutation_inodes {
1539 self.note_unsynced(inode, token);
1540 }
1541 }
1542 return Ok((msg.body, conn));
1543 }
1544 }
1545
1546 async fn acquire_request_admission(&self) -> ClientResult<AdmissionPermit> {
1547 Ok(Arc::new(
1548 Arc::clone(&self.request_admission)
1549 .acquire_owned()
1550 .await
1551 .map_err(|_| ClientError::Disconnected)?,
1552 ))
1553 }
1554
1555 async fn send_request(&self, body: Message) -> ClientResult<Message> {
1557 let admission = self.acquire_request_admission().await?;
1558 let op_id = if body.is_mutation() {
1559 Uuid::new_v4().into_bytes()
1560 } else {
1561 [0u8; 16]
1562 };
1563 let mut attempt = OpAttemptState::default();
1564 self.send_request_on_current(op_id, &body, &mut attempt, None, &admission)
1565 .await
1566 .map(|(response, _)| response)
1567 }
1568
1569 async fn accept_stateful_response(
1571 &self,
1572 response_conn: &Arc<Conn>,
1573 ) -> Option<tokio::sync::MutexGuard<'_, ()>> {
1574 let transition = self.session_transition.lock().await;
1575 let current = self.conn.load_full();
1576 if self.live.load(Ordering::Acquire)
1577 && !response_conn.dead.load(Ordering::Acquire)
1578 && Arc::ptr_eq(response_conn, ¤t)
1579 {
1580 Some(transition)
1581 } else {
1582 None
1583 }
1584 }
1585
1586 async fn send_stateful_request(
1589 &self,
1590 body: Message,
1591 ) -> ClientResult<(Message, tokio::sync::MutexGuard<'_, ()>)> {
1592 let admission = self.acquire_request_admission().await?;
1593 let op_id = if body.is_mutation() {
1594 Uuid::new_v4().into_bytes()
1595 } else {
1596 [0u8; 16]
1597 };
1598 let mut attempt = OpAttemptState::default();
1599 let dispatched_conn = Mutex::new(None);
1600 let mut cancellation = StatefulCancellationGuard {
1601 client: self,
1602 dispatched_conn: &dispatched_conn,
1603 armed: true,
1604 };
1605 let result = loop {
1606 let (response, response_conn) = match self
1607 .send_request_on_current(
1608 op_id,
1609 &body,
1610 &mut attempt,
1611 Some(&dispatched_conn),
1612 &admission,
1613 )
1614 .await
1615 {
1616 Ok(response) => response,
1617 Err(error) => break Err(error),
1618 };
1619 if let Some(transition) = self.accept_stateful_response(&response_conn).await {
1620 break Ok((response, transition));
1621 }
1622 };
1623 if !matches!(
1625 &result,
1626 Err(_)
1627 | Ok((
1628 Message::Rlerror(Rlerror {
1629 ecode: P9_EOPIDSTALE
1630 }),
1631 _
1632 ))
1633 ) {
1634 cancellation.disarm();
1635 }
1636 result
1637 }
1638
1639 async fn probe_connection(conn: &Conn, observed_generation: u64) -> bool {
1641 Self::probe_connection_with_timeout(conn, observed_generation, PROBE_TIMEOUT).await
1642 }
1643
1644 async fn probe_connection_with_timeout(
1645 conn: &Conn,
1646 observed_generation: u64,
1647 probe_timeout: Duration,
1648 ) -> bool {
1649 if conn.dead.load(Ordering::Acquire) {
1650 return false;
1651 }
1652 if conn.receive_generation() != observed_generation {
1653 return true;
1654 }
1655 let _guard = conn.probe_lock.lock().await;
1656 if conn.dead.load(Ordering::Acquire) {
1657 return false;
1658 }
1659 if conn.receive_generation() != observed_generation {
1660 return true;
1661 }
1662 if conn.liveness_probe_tag.load(Ordering::Acquire) != NO_LIVENESS_PROBE_TAG {
1666 return false;
1667 }
1668 match runtime::timeout(probe_timeout, query_lineage_token(conn)).await {
1669 Ok(Ok(())) => true,
1670 Err(_) => {
1677 !conn.dead.load(Ordering::Acquire)
1678 && conn.receive_generation() != observed_generation
1679 }
1680 Ok(Err(_)) => false,
1681 }
1682 }
1683
1684 fn force_reprobe(&self, conn: &Arc<Conn>) {
1686 conn.shutdown();
1687 self.reconnect_notify.notify_waiters();
1688 }
1689
1690 async fn send_raw(conn: &Conn, body: Message) -> ClientResult<Message> {
1693 let track_liveness_probe = matches!(&body, Message::Tgetlineage(_));
1694 Self::send_raw_at_tag(conn, None, body, track_liveness_probe).await
1695 }
1696
1697 async fn send_raw_at_tag(
1699 conn: &Conn,
1700 exact_tag: Option<u16>,
1701 body: Message,
1702 track_liveness_probe: bool,
1703 ) -> ClientResult<Message> {
1704 let permit = conn
1705 .writer_tx
1706 .reserve()
1707 .await
1708 .map_err(|_| ClientError::Disconnected)?;
1709 if conn.dead.load(Ordering::Acquire) {
1710 drop(permit);
1711 return Err(ClientError::Disconnected);
1712 }
1713 let (otx, orx) = oneshot::channel();
1714 let tag = match exact_tag {
1715 Some(tag) => Self::register_tag(conn, tag, otx, None)
1716 .map_err(|_| ClientError::Unexpected("raw tag already registered"))?,
1717 None => match Self::alloc_tag(conn, otx, None) {
1718 Ok(tag) => tag,
1719 Err(_) => {
1720 drop(permit);
1721 conn.shutdown();
1722 return Err(ClientError::Disconnected);
1723 }
1724 },
1725 };
1726 let mut pending = PendingTag {
1727 conn,
1728 tag,
1729 dispatched: false,
1730 };
1731 if conn.dead.load(Ordering::Acquire) {
1732 drop(permit);
1733 return Err(ClientError::Disconnected);
1734 }
1735 let bytes = match P9Message::new(tag, body).to_bytes() {
1736 Ok(b) => b,
1737 Err(e) => return Err(ClientError::Codec(e)),
1738 };
1739 if track_liveness_probe
1740 && conn
1741 .liveness_probe_tag
1742 .compare_exchange(
1743 NO_LIVENESS_PROBE_TAG,
1744 u32::from(tag),
1745 Ordering::AcqRel,
1746 Ordering::Acquire,
1747 )
1748 .is_err()
1749 {
1750 return Err(ClientError::Unexpected("liveness probe already pending"));
1751 }
1752 pending.mark_dispatched();
1753 permit.send(bytes);
1754 let frame = orx.await.map_err(|_| ClientError::Disconnected)?;
1755 let msg = P9Message::from_owned_bytes_ctx(frame, false).map_err(ClientError::Codec)?;
1756 if msg.tag != tag {
1757 return Err(ClientError::Unexpected("response tag"));
1758 }
1759 Ok(msg.body)
1760 }
1761
1762 async fn send_raw_rpc(conn: &Conn, body: Message) -> ClientResult<Message> {
1765 match Self::send_raw(conn, body).await? {
1766 Message::Rlerror(e) => Err(ClientError::Errno(e.ecode)),
1767 other => Ok(other),
1768 }
1769 }
1770
1771 async fn send_replay_rpc(
1773 conn: &Conn,
1774 body: Message,
1775 timeout: Duration,
1776 ) -> ClientResult<Message> {
1777 match runtime::timeout(timeout, Self::send_raw_rpc(conn, body)).await {
1778 Ok(result) => result,
1779 Err(_) => {
1780 conn.shutdown();
1784 Err(ClientError::Disconnected)
1785 }
1786 }
1787 }
1788
1789 async fn rpc(&self, body: Message) -> ClientResult<Message> {
1791 match self.send_request(body).await? {
1792 Message::Rlerror(e) => Err(ClientError::Errno(e.ecode)),
1793 other => Ok(other),
1794 }
1795 }
1796
1797 async fn rpc_stateful(
1799 &self,
1800 body: Message,
1801 ) -> ClientResult<(Message, tokio::sync::MutexGuard<'_, ()>)> {
1802 let (response, transition) = self.send_stateful_request(body).await?;
1803 match response {
1804 Message::Rlerror(e) => Err(ClientError::Errno(e.ecode)),
1805 other => Ok((other, transition)),
1806 }
1807 }
1808
1809 pub async fn attach(
1810 &self,
1811 fid: u32,
1812 afid: u32,
1813 uname: &str,
1814 aname: &str,
1815 n_uname: u32,
1816 ) -> ClientResult<Qid> {
1817 let (resp, _transition) = self
1818 .rpc_stateful(Message::Tattach(Tattach {
1819 fid,
1820 afid,
1821 uname: P9String::new(uname.as_bytes().to_vec()),
1822 aname: P9String::new(aname.as_bytes().to_vec()),
1823 n_uname,
1824 }))
1825 .await?;
1826 match resp {
1827 Message::Rattach(r) => {
1828 let mut st = self.state.lock().unwrap();
1829 st.fids.insert(
1830 fid,
1831 FidRecord {
1832 inode_id: r.qid.path,
1833 root_inode: r.qid.path,
1834 n_uname,
1835 uname: uname.as_bytes().to_vec(),
1836 opened: None,
1837 },
1838 );
1839 st.default_root = Some(r.qid.path);
1840 Ok(r.qid)
1841 }
1842 _ => Err(ClientError::Unexpected("attach")),
1843 }
1844 }
1845
1846 pub async fn rebind(&self, fid: u32, inode_id: u64, n_uname: u32) -> ClientResult<Qid> {
1850 self.rebind_inner(fid, inode_id, n_uname, Vec::new()).await
1851 }
1852
1853 pub async fn rebind_with_credentials(
1859 &self,
1860 fid: u32,
1861 inode_id: u64,
1862 uid: u32,
1863 gid: u32,
1864 groups: &[u32],
1865 ) -> ClientResult<Qid> {
1866 if groups.len() > P9_MAX_GROUPS {
1867 return Err(ClientError::Errno(linux::E2BIG));
1868 }
1869 self.rebind_with_credentials_inner(fid, inode_id, uid, gid, groups, true)
1870 .await
1871 }
1872
1873 pub async fn rebind_with_primary_group(
1878 &self,
1879 fid: u32,
1880 inode_id: u64,
1881 uid: u32,
1882 gid: u32,
1883 ) -> ClientResult<Qid> {
1884 self.rebind_with_credentials_inner(fid, inode_id, uid, gid, &[], false)
1885 .await
1886 }
1887
1888 async fn rebind_with_credentials_inner(
1889 &self,
1890 fid: u32,
1891 inode_id: u64,
1892 uid: u32,
1893 gid: u32,
1894 groups: &[u32],
1895 groups_complete: bool,
1896 ) -> ClientResult<Qid> {
1897 let group_count = groups.len().min(P9_MAX_GROUPS);
1898 let mut payload = Vec::with_capacity(
1899 P9_REBIND_CREDENTIAL_HEADER_SIZE + group_count * std::mem::size_of::<u32>(),
1900 );
1901 payload.push(P9_REBIND_CREDENTIAL_SENTINEL);
1902 payload.push(P9_REBIND_CREDENTIAL_VERSION);
1903 payload.extend_from_slice(&gid.to_le_bytes());
1904 payload.push(
1905 group_count as u8
1906 | if groups_complete {
1907 0
1908 } else {
1909 P9_REBIND_CREDENTIAL_GROUPS_INCOMPLETE
1910 },
1911 );
1912 for group in &groups[..group_count] {
1913 payload.extend_from_slice(&group.to_le_bytes());
1914 }
1915 self.rebind_inner(fid, inode_id, uid, payload).await
1916 }
1917
1918 async fn rebind_inner(
1919 &self,
1920 fid: u32,
1921 inode_id: u64,
1922 n_uname: u32,
1923 uname: Vec<u8>,
1924 ) -> ClientResult<Qid> {
1925 let root_inode = self.state.lock().unwrap().default_root.unwrap_or(0);
1926 let (resp, _transition) = self
1927 .rpc_stateful(Message::Trebind(Trebind {
1928 fid,
1929 inode_id,
1930 root_inode,
1931 flags: 0,
1932 uname: P9String::new(uname.clone()),
1933 n_uname,
1934 }))
1935 .await?;
1936 match resp {
1937 Message::Rrebind(r) => {
1938 self.state.lock().unwrap().fids.insert(
1939 fid,
1940 FidRecord {
1941 inode_id,
1942 root_inode,
1943 n_uname,
1944 uname,
1945 opened: None,
1946 },
1947 );
1948 Ok(r.qid)
1949 }
1950 _ => Err(ClientError::Unexpected("rebind")),
1951 }
1952 }
1953
1954 pub async fn walk(&self, fid: u32, newfid: u32, names: &[&[u8]]) -> ClientResult<Vec<Qid>> {
1955 let wnames = names
1956 .iter()
1957 .map(|n| P9String::new(n.to_vec()))
1958 .collect::<Vec<_>>();
1959 let (resp, _transition) = self
1960 .rpc_stateful(Message::Twalk(Twalk {
1961 fid,
1962 newfid,
1963 nwname: wnames.len() as u16,
1964 wnames,
1965 }))
1966 .await?;
1967 match resp {
1968 Message::Rwalk(r) => {
1969 if names.is_empty() || r.wqids.len() == names.len() {
1971 let mut st = self.state.lock().unwrap();
1972 let identity = st.fids.get(&fid).map(FidRecord::replay_identity);
1973 let inode_id = if names.is_empty() {
1974 st.fids.get(&fid).map(|rec| rec.inode_id)
1975 } else {
1976 r.wqids.last().map(|q| q.path)
1977 };
1978 if let (Some(inode_id), Some((n_uname, root_inode, uname))) =
1979 (inode_id, identity)
1980 {
1981 st.fids.insert(
1982 newfid,
1983 FidRecord {
1984 inode_id,
1985 root_inode,
1986 n_uname,
1987 uname,
1988 opened: None,
1989 },
1990 );
1991 }
1992 }
1993 Ok(r.wqids)
1994 }
1995 _ => Err(ClientError::Unexpected("walk")),
1996 }
1997 }
1998
1999 pub async fn walk_getattr(
2002 &self,
2003 fid: u32,
2004 newfid: u32,
2005 names: &[&[u8]],
2006 ) -> ClientResult<(Vec<Qid>, Stat)> {
2007 let wnames = names
2008 .iter()
2009 .map(|n| P9String::new(n.to_vec()))
2010 .collect::<Vec<_>>();
2011 let (resp, _transition) = self
2012 .rpc_stateful(Message::Twalkgetattr(Twalkgetattr {
2013 fid,
2014 newfid,
2015 nwname: wnames.len() as u16,
2016 wnames,
2017 }))
2018 .await?;
2019 match resp {
2020 Message::Rwalkgetattr(r) => {
2021 {
2022 let mut st = self.state.lock().unwrap();
2023 let identity = st.fids.get(&fid).map(FidRecord::replay_identity);
2024 let inode_id = if names.is_empty() {
2025 st.fids.get(&fid).map(|rec| rec.inode_id)
2026 } else {
2027 r.wqids.last().map(|q| q.path)
2028 };
2029 if let (Some(inode_id), Some((n_uname, root_inode, uname))) =
2030 (inode_id, identity)
2031 {
2032 st.fids.insert(
2033 newfid,
2034 FidRecord {
2035 inode_id,
2036 root_inode,
2037 n_uname,
2038 uname,
2039 opened: None,
2040 },
2041 );
2042 }
2043 }
2044 Ok((r.wqids, r.stat))
2045 }
2046 _ => Err(ClientError::Unexpected("walk_getattr")),
2047 }
2048 }
2049
2050 pub async fn clunk(&self, fid: u32) -> ClientResult<()> {
2051 if self.stale_fids.contains(&fid) && self.clear_stale_fid(fid).await {
2052 return Ok(());
2053 }
2054 let (resp, _transition) = match self
2055 .send_stateful_request(Message::Tclunk(Tclunk { fid }))
2056 .await
2057 {
2058 Ok(result) => result,
2059 Err(error) => {
2060 if matches!(&error, ClientError::Errno(errno) if *errno == linux::ESTALE)
2061 && self.clear_stale_fid(fid).await
2062 {
2063 return Ok(());
2064 }
2065 return Err(error);
2066 }
2067 };
2068 let mut st = self.state.lock().unwrap();
2070 st.forget_fid(fid);
2071 drop(st);
2072 self.stale_fids.remove(&fid);
2073 match resp {
2074 Message::Rclunk(_) => Ok(()),
2075 Message::Rlerror(e) => Err(ClientError::Errno(e.ecode)),
2076 _ => Err(ClientError::Unexpected("clunk")),
2077 }
2078 }
2079
2080 async fn clear_stale_fid(&self, fid: u32) -> bool {
2081 let _transition = self.session_transition.lock().await;
2082 if self.terminal_errno.load(Ordering::Acquire) != 0 {
2083 return false;
2084 }
2085 if self.stale_fids.remove(&fid).is_none() {
2086 return false;
2087 }
2088 let mut state = self.state.lock().unwrap();
2089 state.forget_fid(fid);
2090 true
2091 }
2092
2093 pub async fn getattr(&self, fid: u32, mask: u64) -> ClientResult<Stat> {
2094 let resp = self
2095 .rpc(Message::Tgetattr(Tgetattr {
2096 fid,
2097 request_mask: mask,
2098 }))
2099 .await?;
2100 match resp {
2101 Message::Rgetattr(r) => Ok(r.stat),
2102 _ => Err(ClientError::Unexpected("getattr")),
2103 }
2104 }
2105
2106 pub async fn setattr(&self, ts: Tsetattr) -> ClientResult<()> {
2107 match self.rpc(Message::Tsetattr(ts)).await? {
2108 Message::Rsetattr(_) => Ok(()),
2109 _ => Err(ClientError::Unexpected("setattr")),
2110 }
2111 }
2112
2113 pub async fn fallocate(
2116 &self,
2117 fid: u32,
2118 offset: u64,
2119 length: u64,
2120 mode: u32,
2121 ) -> ClientResult<()> {
2122 match self
2123 .rpc(Message::Tfallocate(Tfallocate {
2124 fid,
2125 offset,
2126 length,
2127 mode,
2128 }))
2129 .await?
2130 {
2131 Message::Rfallocate(_) => Ok(()),
2132 _ => Err(ClientError::Unexpected("fallocate")),
2133 }
2134 }
2135
2136 pub async fn setattr_attr(&self, ts: Tsetattr) -> ClientResult<Stat> {
2138 match self.rpc(Message::Tsetattrattr(ts)).await? {
2139 Message::Rsetattrattr(r) => Ok(r.stat),
2140 _ => Err(ClientError::Unexpected("setattr_attr")),
2141 }
2142 }
2143
2144 pub async fn lopen(&self, fid: u32, flags: u32) -> ClientResult<(Qid, u32)> {
2145 let (response, _transition) = self
2146 .rpc_stateful(Message::Tlopen(Tlopen { fid, flags }))
2147 .await?;
2148 match response {
2149 Message::Rlopen(r) => {
2150 if let Some(rec) = self.state.lock().unwrap().fids.get_mut(&fid) {
2151 rec.opened = Some(flags);
2152 }
2153 Ok((r.qid, r.iounit))
2154 }
2155 _ => Err(ClientError::Unexpected("lopen")),
2156 }
2157 }
2158
2159 pub async fn lopenat(&self, fid: u32, newfid: u32, flags: u32) -> ClientResult<(Qid, u32)> {
2162 let (resp, _transition) = self
2163 .rpc_stateful(Message::Tlopenat(Tlopenat { fid, newfid, flags }))
2164 .await?;
2165 match resp {
2166 Message::Rlopenat(r) => {
2167 let mut st = self.state.lock().unwrap();
2168 if let Some((n_uname, root_inode, uname)) =
2169 st.fids.get(&fid).map(FidRecord::replay_identity)
2170 {
2171 st.fids.insert(
2172 newfid,
2173 FidRecord {
2174 inode_id: r.qid.path,
2175 root_inode,
2176 n_uname,
2177 uname,
2178 opened: Some(flags),
2179 },
2180 );
2181 }
2182 Ok((r.qid, r.iounit))
2183 }
2184 _ => Err(ClientError::Unexpected("lopenat")),
2185 }
2186 }
2187
2188 pub async fn lopenatread(
2194 &self,
2195 fid: u32,
2196 newfid: u32,
2197 flags: u32,
2198 count: u32,
2199 ) -> ClientResult<(Qid, u32, Bytes, bool)> {
2200 let (resp, _transition) = self
2201 .rpc_stateful(Message::Tlopenatread(Tlopenatread {
2202 fid,
2203 newfid,
2204 flags,
2205 count,
2206 }))
2207 .await?;
2208 match resp {
2209 Message::Rlopenatread(r) => {
2210 let mut st = self.state.lock().unwrap();
2211 if let Some((n_uname, root_inode, uname)) =
2212 st.fids.get(&fid).map(FidRecord::replay_identity)
2213 {
2214 st.fids.insert(
2215 newfid,
2216 FidRecord {
2217 inode_id: r.qid.path,
2218 root_inode,
2219 n_uname,
2220 uname,
2221 opened: Some(flags),
2222 },
2223 );
2224 }
2225 drop(st);
2226 Ok((r.qid, r.iounit, r.data.0, r.eof != 0))
2227 }
2228 _ => Err(ClientError::Unexpected("lopenatread")),
2229 }
2230 }
2231
2232 pub async fn lcreate(
2233 &self,
2234 fid: u32,
2235 name: &[u8],
2236 flags: u32,
2237 mode: u32,
2238 gid: u32,
2239 ) -> ClientResult<(Qid, u32)> {
2240 let (resp, _transition) = self
2241 .rpc_stateful(Message::Tlcreate(Tlcreate {
2242 fid,
2243 name: P9String::new(name.to_vec()),
2244 flags,
2245 mode,
2246 gid,
2247 }))
2248 .await?;
2249 match resp {
2250 Message::Rlcreate(r) => {
2251 let reopen = flags & !(linux::O_CREAT | linux::O_EXCL | linux::O_TRUNC);
2253 let mut st = self.state.lock().unwrap();
2254 if let Some(rec) = st.fids.get_mut(&fid) {
2255 rec.inode_id = r.qid.path;
2256 rec.opened = Some(reopen);
2257 }
2258 Ok((r.qid, r.iounit))
2259 }
2260 _ => Err(ClientError::Unexpected("lcreate")),
2261 }
2262 }
2263
2264 pub async fn lcreateattr(
2269 &self,
2270 dfid: u32,
2271 newfid: u32,
2272 name: &[u8],
2273 flags: u32,
2274 mode: u32,
2275 gid: u32,
2276 ) -> ClientResult<(Stat, u32)> {
2277 let (resp, _transition) = self
2278 .rpc_stateful(Message::Tlcreateattr(Tlcreateattr {
2279 dfid,
2280 newfid,
2281 name: P9String::new(name.to_vec()),
2282 flags,
2283 mode,
2284 gid,
2285 }))
2286 .await?;
2287 match resp {
2288 Message::Rlcreateattr(r) => {
2289 let reopen = flags & !(linux::O_CREAT | linux::O_EXCL | linux::O_TRUNC);
2290 let mut st = self.state.lock().unwrap();
2291 if let Some((n_uname, root_inode, uname)) =
2292 st.fids.get(&dfid).map(FidRecord::replay_identity)
2293 {
2294 st.fids.insert(
2295 newfid,
2296 FidRecord {
2297 inode_id: r.stat.qid.path,
2298 root_inode,
2299 n_uname,
2300 uname,
2301 opened: Some(reopen),
2302 },
2303 );
2304 }
2305 Ok((r.stat, r.iounit))
2306 }
2307 _ => Err(ClientError::Unexpected("lcreateattr")),
2308 }
2309 }
2310
2311 pub async fn read(&self, fid: u32, offset: u64, size: u32) -> ClientResult<Vec<u8>> {
2314 Ok(self.read_bytes(fid, offset, size).await?.into())
2315 }
2316
2317 pub async fn read_bytes(&self, fid: u32, offset: u64, size: u32) -> ClientResult<Bytes> {
2321 if size == 0 {
2322 return Ok(Bytes::new());
2323 }
2324 let size = size as usize;
2325 let max = self.max_io().max(1) as usize;
2326 let first_count = size.min(max);
2327 let first = self.read_once(fid, offset, first_count as u32).await?;
2328 if size <= max || first.len() < first_count {
2329 return Ok(first);
2330 }
2331 let mut out = BytesMut::with_capacity(size.min(max.saturating_mul(2)));
2333 let mut off = offset + first.len() as u64;
2334 out.extend_from_slice(&first);
2335 while out.len() < size {
2336 let want = (size - out.len()).min(max);
2337 let data = self.read_once(fid, off, want as u32).await?;
2338 let got = data.len();
2339 out.extend_from_slice(&data);
2340 off += got as u64;
2341 if got < want {
2342 break;
2343 }
2344 }
2345 Ok(out.freeze())
2346 }
2347
2348 async fn read_once(&self, fid: u32, offset: u64, count: u32) -> ClientResult<Bytes> {
2349 let resp = self
2350 .rpc(Message::Tread(Tread { fid, offset, count }))
2351 .await?;
2352 match resp {
2353 Message::Rread(r) if r.data.len() <= count as usize => Ok(r.data.0),
2354 Message::Rread(_) => Err(ClientError::Unexpected("read count")),
2355 _ => Err(ClientError::Unexpected("read")),
2356 }
2357 }
2358
2359 pub async fn write(&self, fid: u32, offset: u64, data: &[u8]) -> ClientResult<u64> {
2362 let len = data.len();
2363 self.write_chunks(fid, offset, len, move |start, end| {
2364 Bytes::copy_from_slice(&data[start..end])
2365 })
2366 .await
2367 }
2368
2369 pub async fn write_bytes(&self, fid: u32, offset: u64, data: Bytes) -> ClientResult<u64> {
2373 let len = data.len();
2374 self.write_chunks(fid, offset, len, move |start, end| data.slice(start..end))
2375 .await
2376 }
2377
2378 async fn write_chunks<F>(
2379 &self,
2380 fid: u32,
2381 offset: u64,
2382 len: usize,
2383 mut chunk: F,
2384 ) -> ClientResult<u64>
2385 where
2386 F: FnMut(usize, usize) -> Bytes,
2387 {
2388 let mut written = 0usize;
2389 let max = self.max_write_payload().max(1) as usize;
2390 while written < len {
2391 let end = written + (len - written).min(max);
2392 let data = chunk(written, end);
2393 debug_assert_eq!(data.len(), end - written);
2394 let attempted = data.len();
2395 let n = self.write_chunk(fid, offset + written as u64, data).await? as usize;
2396 written += n;
2397 if n < attempted {
2398 break;
2399 }
2400 }
2401 Ok(written as u64)
2402 }
2403
2404 async fn write_chunk(&self, fid: u32, offset: u64, data: Bytes) -> ClientResult<u32> {
2405 let attempted = u32::try_from(data.len())
2406 .expect("write chunks are bounded by the negotiated u32 msize");
2407 let resp = self
2408 .rpc(Message::Twrite(Twrite {
2409 fid,
2410 offset,
2411 count: attempted,
2412 data: P9Bytes::from(data),
2413 }))
2414 .await?;
2415 match resp {
2416 Message::Rwrite(r) if r.count <= attempted => Ok(r.count),
2417 Message::Rwrite(_) => Err(ClientError::Unexpected("write count")),
2418 _ => Err(ClientError::Unexpected("write")),
2419 }
2420 }
2421
2422 pub async fn readdir(&self, fid: u32, offset: u64, count: u32) -> ClientResult<Vec<DirEntry>> {
2423 let resp = self
2424 .rpc(Message::Treaddir(Treaddir { fid, offset, count }))
2425 .await?;
2426 match resp {
2427 Message::Rreaddir(r) => r.to_entries().map_err(ClientError::Codec),
2428 _ => Err(ClientError::Unexpected("readdir")),
2429 }
2430 }
2431
2432 pub async fn readdirplus(
2435 &self,
2436 fid: u32,
2437 offset: u64,
2438 count: u32,
2439 ) -> ClientResult<Vec<DirEntryPlus>> {
2440 let resp = self
2441 .rpc(Message::Treaddirattr(Treaddirattr { fid, offset, count }))
2442 .await?;
2443 match resp {
2444 Message::Rreaddirattr(r) => r.to_entries().map_err(ClientError::Codec),
2445 _ => Err(ClientError::Unexpected("readdirplus")),
2446 }
2447 }
2448
2449 pub async fn mkdir(&self, dfid: u32, name: &[u8], mode: u32, gid: u32) -> ClientResult<Qid> {
2450 let resp = self
2451 .rpc(Message::Tmkdir(Tmkdir {
2452 dfid,
2453 name: P9String::new(name.to_vec()),
2454 mode,
2455 gid,
2456 }))
2457 .await?;
2458 match resp {
2459 Message::Rmkdir(r) => Ok(r.qid),
2460 _ => Err(ClientError::Unexpected("mkdir")),
2461 }
2462 }
2463
2464 pub async fn mkdir_attr(
2466 &self,
2467 dfid: u32,
2468 name: &[u8],
2469 mode: u32,
2470 gid: u32,
2471 ) -> ClientResult<Stat> {
2472 let resp = self
2473 .rpc(Message::Tmkdirattr(Tmkdir {
2474 dfid,
2475 name: P9String::new(name.to_vec()),
2476 mode,
2477 gid,
2478 }))
2479 .await?;
2480 match resp {
2481 Message::Rmkdirattr(r) => Ok(r.stat),
2482 _ => Err(ClientError::Unexpected("mkdir_attr")),
2483 }
2484 }
2485
2486 pub async fn symlink(
2487 &self,
2488 dfid: u32,
2489 name: &[u8],
2490 target: &[u8],
2491 gid: u32,
2492 ) -> ClientResult<Qid> {
2493 let resp = self
2494 .rpc(Message::Tsymlink(Tsymlink {
2495 dfid,
2496 name: P9String::new(name.to_vec()),
2497 symtgt: P9String::new(target.to_vec()),
2498 gid,
2499 }))
2500 .await?;
2501 match resp {
2502 Message::Rsymlink(r) => Ok(r.qid),
2503 _ => Err(ClientError::Unexpected("symlink")),
2504 }
2505 }
2506
2507 pub async fn symlink_attr(
2509 &self,
2510 dfid: u32,
2511 name: &[u8],
2512 target: &[u8],
2513 gid: u32,
2514 ) -> ClientResult<Stat> {
2515 let resp = self
2516 .rpc(Message::Tsymlinkattr(Tsymlink {
2517 dfid,
2518 name: P9String::new(name.to_vec()),
2519 symtgt: P9String::new(target.to_vec()),
2520 gid,
2521 }))
2522 .await?;
2523 match resp {
2524 Message::Rsymlinkattr(r) => Ok(r.stat),
2525 _ => Err(ClientError::Unexpected("symlink_attr")),
2526 }
2527 }
2528
2529 pub async fn mknod(
2530 &self,
2531 dfid: u32,
2532 name: &[u8],
2533 mode: u32,
2534 major: u32,
2535 minor: u32,
2536 gid: u32,
2537 ) -> ClientResult<Qid> {
2538 let resp = self
2539 .rpc(Message::Tmknod(Tmknod {
2540 dfid,
2541 name: P9String::new(name.to_vec()),
2542 mode,
2543 major,
2544 minor,
2545 gid,
2546 }))
2547 .await?;
2548 match resp {
2549 Message::Rmknod(r) => Ok(r.qid),
2550 _ => Err(ClientError::Unexpected("mknod")),
2551 }
2552 }
2553
2554 pub async fn mknod_attr(
2556 &self,
2557 dfid: u32,
2558 name: &[u8],
2559 mode: u32,
2560 major: u32,
2561 minor: u32,
2562 gid: u32,
2563 ) -> ClientResult<Stat> {
2564 let resp = self
2565 .rpc(Message::Tmknodattr(Tmknod {
2566 dfid,
2567 name: P9String::new(name.to_vec()),
2568 mode,
2569 major,
2570 minor,
2571 gid,
2572 }))
2573 .await?;
2574 match resp {
2575 Message::Rmknodattr(r) => Ok(r.stat),
2576 _ => Err(ClientError::Unexpected("mknod_attr")),
2577 }
2578 }
2579
2580 pub async fn readlink(&self, fid: u32) -> ClientResult<Vec<u8>> {
2581 match self.rpc(Message::Treadlink(Treadlink { fid })).await? {
2582 Message::Rreadlink(r) => Ok(r.target.data.to_vec()),
2583 _ => Err(ClientError::Unexpected("readlink")),
2584 }
2585 }
2586
2587 pub async fn link(&self, dfid: u32, fid: u32, name: &[u8]) -> ClientResult<()> {
2588 let resp = self
2589 .rpc(Message::Tlink(Tlink {
2590 dfid,
2591 fid,
2592 name: P9String::new(name.to_vec()),
2593 }))
2594 .await?;
2595 match resp {
2596 Message::Rlink(_) => Ok(()),
2597 _ => Err(ClientError::Unexpected("link")),
2598 }
2599 }
2600
2601 pub async fn link_attr(&self, dfid: u32, fid: u32, name: &[u8]) -> ClientResult<Stat> {
2604 let resp = self
2605 .rpc(Message::Tlinkattr(Tlink {
2606 dfid,
2607 fid,
2608 name: P9String::new(name.to_vec()),
2609 }))
2610 .await?;
2611 match resp {
2612 Message::Rlinkattr(r) => Ok(r.stat),
2613 _ => Err(ClientError::Unexpected("link_attr")),
2614 }
2615 }
2616
2617 pub async fn renameat(
2618 &self,
2619 olddirfid: u32,
2620 oldname: &[u8],
2621 newdirfid: u32,
2622 newname: &[u8],
2623 ) -> ClientResult<()> {
2624 let resp = self
2625 .rpc(Message::Trenameat(Trenameat {
2626 olddirfid,
2627 oldname: P9String::new(oldname.to_vec()),
2628 newdirfid,
2629 newname: P9String::new(newname.to_vec()),
2630 }))
2631 .await?;
2632 match resp {
2633 Message::Rrenameat(_) => Ok(()),
2634 _ => Err(ClientError::Unexpected("renameat")),
2635 }
2636 }
2637
2638 pub async fn unlinkat(&self, dirfid: u32, name: &[u8], flags: u32) -> ClientResult<()> {
2639 let resp = self
2640 .rpc(Message::Tunlinkat(Tunlinkat {
2641 dirfid,
2642 name: P9String::new(name.to_vec()),
2643 flags,
2644 }))
2645 .await?;
2646 match resp {
2647 Message::Runlinkat(_) => Ok(()),
2648 _ => Err(ClientError::Unexpected("unlinkat")),
2649 }
2650 }
2651
2652 async fn fsync_scope(&self, primary: u32, scope: FsyncScope) -> ClientResult<()> {
2653 self.validate_fids([primary])?;
2654 let mut token: Option<u64> = None;
2656 let mut snaps: Vec<(u64, u64)> = Vec::new();
2657 match scope {
2658 FsyncScope::Inode(inode) => {
2659 let (oldest, generation) = self.snapshot_unsynced(inode);
2660 token = oldest;
2661 snaps.push((inode, generation));
2662 }
2663 FsyncScope::All => {
2664 for entry in &self.unsynced {
2665 let inode = *entry.key();
2666 let (oldest, generation) = entry.snapshot();
2667 if let Some(t) = oldest {
2668 token = Some(token.map_or(t, |current| current.min(t)));
2669 }
2670 snaps.push((inode, generation));
2671 }
2672 }
2673 }
2674 match self
2675 .rpc(Message::Tfsyncdur(Tfsyncdur {
2676 fid: primary,
2677 datasync: scope.wire_flag(),
2678 token: token.unwrap_or(0),
2679 }))
2680 .await
2681 {
2682 Ok(Message::Rfsync(_)) => {
2683 for (inode, generation) in snaps {
2684 self.clear_unsynced_if_unchanged(inode, generation);
2685 }
2686 Ok(())
2687 }
2688 Ok(_) => Err(ClientError::Unexpected("fsync")),
2689 Err(ClientError::Errno(e)) if e == linux::ESTALE => {
2690 for (inode, generation) in snaps {
2692 self.report_unsynced_if_unchanged(inode, generation);
2693 }
2694 Err(ClientError::Errno(e))
2695 }
2696 Err(e) => Err(e),
2697 }
2698 }
2699
2700 pub async fn fsync(&self, fid: u32) -> ClientResult<()> {
2702 let inode = self.fid_inode(fid)?;
2703 self.fsync_scope(fid, FsyncScope::Inode(inode)).await
2704 }
2705
2706 pub async fn fsync_all(&self, primary: u32) -> ClientResult<()> {
2708 self.fsync_scope(primary, FsyncScope::All).await
2709 }
2710
2711 pub async fn statfs(&self, fid: u32) -> ClientResult<Rstatfs> {
2712 match self.rpc(Message::Tstatfs(Tstatfs { fid })).await? {
2713 Message::Rstatfs(r) => Ok(r),
2714 _ => Err(ClientError::Unexpected("statfs")),
2715 }
2716 }
2717
2718 #[allow(clippy::too_many_arguments)]
2723 pub async fn lock(
2724 &self,
2725 fid: u32,
2726 lock_type: LockType,
2727 flags: u32,
2728 start: u64,
2729 length: u64,
2730 proc_id: u32,
2731 client_id: &[u8],
2732 ) -> ClientResult<LockStatus> {
2733 let (resp, _transition) = self
2734 .rpc_stateful(Message::Tlock(Tlock {
2735 fid,
2736 lock_type,
2737 flags,
2738 start,
2739 length,
2740 proc_id,
2741 client_id: P9String::new(client_id.to_vec()),
2742 }))
2743 .await?;
2744 match resp {
2745 Message::Rlock(r) => {
2746 let status = LockStatus::from_wire(r.status)
2747 .ok_or(ClientError::Unexpected("lock status"))?;
2748 let mut st = self.state.lock().unwrap();
2749 match lock_type {
2750 LockType::Unlock if status == LockStatus::Success => {
2751 unlock_recorded_range(&mut st.locks, fid, start, length);
2752 }
2753 _ if status == LockStatus::Success => {
2754 replace_recorded_lock(
2755 &mut st.locks,
2756 LockRecord {
2757 fid,
2758 lock_type,
2759 start,
2760 length,
2761 proc_id,
2762 client_id: client_id.to_vec(),
2763 },
2764 );
2765 }
2766 _ => {}
2767 }
2768 Ok(status)
2769 }
2770 _ => Err(ClientError::Unexpected("lock")),
2771 }
2772 }
2773
2774 pub async fn getlock(
2776 &self,
2777 fid: u32,
2778 lock_type: LockType,
2779 start: u64,
2780 length: u64,
2781 proc_id: u32,
2782 client_id: &[u8],
2783 ) -> ClientResult<Rgetlock> {
2784 let resp = self
2785 .rpc(Message::Tgetlock(Tgetlock {
2786 fid,
2787 lock_type,
2788 start,
2789 length,
2790 proc_id,
2791 client_id: P9String::new(client_id.to_vec()),
2792 }))
2793 .await?;
2794 match resp {
2795 Message::Rgetlock(r) => Ok(r),
2796 _ => Err(ClientError::Unexpected("getlock")),
2797 }
2798 }
2799}
2800
2801impl Drop for NinePClient {
2802 fn drop(&mut self) {
2803 self.conn.load().shutdown();
2805 self.reconnect_notify.notify_waiters();
2806 }
2807}
2808
2809fn subtract_lock_record(
2811 held: LockRecord,
2812 unlock_start: u64,
2813 unlock_length: u64,
2814) -> Vec<LockRecord> {
2815 ninep_proto::subtract_lock_range(held.start, held.length, unlock_start, unlock_length)
2816 .into_iter()
2817 .map(|(start, length)| LockRecord {
2818 start,
2819 length,
2820 ..held.clone()
2821 })
2822 .collect()
2823}
2824
2825fn unlock_recorded_range(locks: &mut Vec<LockRecord>, fid: u32, start: u64, length: u64) {
2826 let prior = std::mem::take(locks);
2827 for held in prior {
2828 if held.fid == fid {
2829 locks.extend(subtract_lock_record(held, start, length));
2830 } else {
2831 locks.push(held);
2832 }
2833 }
2834}
2835
2836fn replace_recorded_lock(locks: &mut Vec<LockRecord>, replacement: LockRecord) {
2838 unlock_recorded_range(
2839 locks,
2840 replacement.fid,
2841 replacement.start,
2842 replacement.length,
2843 );
2844 locks.push(replacement);
2845}
2846
2847enum DialedTransport {
2848 #[cfg(not(target_arch = "wasm32"))]
2849 Native {
2850 read: Box<dyn AsyncRead + Unpin + Send>,
2851 write: Box<dyn AsyncWrite + Unpin + Send>,
2852 },
2853 #[cfg(target_arch = "wasm32")]
2854 WebSocket(web_transport::WebSocketIo),
2855}
2856
2857async fn dial(target: &Target) -> ClientResult<DialedTransport> {
2860 match target {
2861 #[cfg(not(target_arch = "wasm32"))]
2862 Target::Tcp(addr) => {
2863 let stream = runtime::timeout(PROBE_TIMEOUT, TcpStream::connect(addr))
2864 .await
2865 .map_err(|_| ClientError::Disconnected)?
2866 .map_err(|_| ClientError::Disconnected)?;
2867 configure_tcp(stream)
2868 }
2869 #[cfg(not(target_arch = "wasm32"))]
2870 Target::TcpHost(endpoint) => {
2871 let stream = runtime::timeout(PROBE_TIMEOUT, TcpStream::connect(endpoint.as_str()))
2873 .await
2874 .map_err(|_| ClientError::Disconnected)?
2875 .map_err(|_| ClientError::Disconnected)?;
2876 configure_tcp(stream)
2877 }
2878 #[cfg(not(target_arch = "wasm32"))]
2879 Target::Unix(path) => {
2880 let stream = runtime::timeout(PROBE_TIMEOUT, UnixStream::connect(path))
2881 .await
2882 .map_err(|_| ClientError::Disconnected)?
2883 .map_err(|_| ClientError::Disconnected)?;
2884 let (r, w) = stream.into_split();
2885 Ok(DialedTransport::Native {
2886 read: Box::new(r),
2887 write: Box::new(w),
2888 })
2889 }
2890 #[cfg(target_arch = "wasm32")]
2891 Target::WebSocket(url) => web_transport::connect(url)
2892 .await
2893 .map(DialedTransport::WebSocket),
2894 }
2895}
2896
2897#[cfg(not(target_arch = "wasm32"))]
2898fn configure_tcp(stream: TcpStream) -> ClientResult<DialedTransport> {
2899 stream.set_nodelay(true).ok();
2900 let keepalive = socket2::TcpKeepalive::new()
2901 .with_time(Duration::from_secs(45))
2902 .with_interval(Duration::from_secs(15))
2903 .with_retries(4);
2904 let _ = socket2::SockRef::from(&stream).set_tcp_keepalive(&keepalive);
2905 let (r, w) = stream.into_split();
2906 Ok(DialedTransport::Native {
2907 read: Box::new(r),
2908 write: Box::new(w),
2909 })
2910}
2911
2912#[cfg(all(test, not(target_arch = "wasm32")))]
2913mod target_dial_tests {
2914 use super::*;
2915 use tokio::io::AsyncReadExt;
2916 use tokio::net::TcpListener;
2917
2918 async fn recv_message(stream: &mut TcpStream) -> P9Message {
2919 let mut size = [0u8; P9_SIZE_FIELD_LEN];
2920 stream.read_exact(&mut size).await.unwrap();
2921 let frame_len = u32::from_le_bytes(size) as usize;
2922 let mut frame = Vec::with_capacity(frame_len);
2923 frame.extend_from_slice(&size);
2924 frame.resize(frame_len, 0);
2925 stream
2926 .read_exact(&mut frame[P9_SIZE_FIELD_LEN..])
2927 .await
2928 .unwrap();
2929 P9Message::from_bytes_ctx(&frame, false).unwrap()
2930 }
2931
2932 async fn send_message(stream: &mut TcpStream, tag: u16, body: Message) {
2933 stream
2934 .write_all(&P9Message::new(tag, body).to_bytes().unwrap())
2935 .await
2936 .unwrap();
2937 }
2938
2939 #[tokio::test]
2940 async fn hostname_lookup_failure_is_isolated_from_healthy_target_dials() {
2941 let listener = TcpListener::bind(("127.0.0.1", 0)).await.unwrap();
2942 let healthy_addr = listener.local_addr().unwrap();
2943 let accept = tokio::spawn(async move { listener.accept().await.unwrap() });
2944
2945 let targets = [
2946 Target::TcpHost("not a valid hostname:5564".to_string()),
2947 Target::Tcp(healthy_addr),
2948 ];
2949 let mut dials = FuturesUnordered::new();
2950 for target in targets {
2951 dials.push(async move { dial(&target).await });
2952 }
2953
2954 let mut successes = 0;
2955 let mut failures = 0;
2956 while let Some(result) = dials.next().await {
2957 match result {
2958 Ok(transport) => {
2959 successes += 1;
2960 drop(transport);
2961 }
2962 Err(_) => failures += 1,
2963 }
2964 }
2965 assert_eq!(successes, 1);
2966 assert_eq!(failures, 1);
2967 accept.await.unwrap();
2968 }
2969
2970 #[tokio::test]
2971 async fn reconnect_rejects_a_smaller_negotiated_msize() {
2972 let listener = TcpListener::bind(("127.0.0.1", 0)).await.unwrap();
2973 let addr = listener.local_addr().unwrap();
2974 let server = tokio::spawn(async move {
2975 let (mut stream, _) = listener.accept().await.unwrap();
2976 let version = recv_message(&mut stream).await;
2977 assert!(matches!(version.body, Message::Tversion(_)));
2978 send_message(
2979 &mut stream,
2980 version.tag,
2981 Message::Rversion(Rversion {
2982 msize: 4096,
2983 version: P9String::new(VERSION_9P2000L_ZEROFS.to_vec()),
2984 }),
2985 )
2986 .await;
2987
2988 let lineage = recv_message(&mut stream).await;
2989 assert!(matches!(lineage.body, Message::Tgetlineage(_)));
2990 send_message(
2991 &mut stream,
2992 lineage.tag,
2993 Message::Rgetlineage(Rgetlineage {
2994 token: 1,
2995 writer_epoch: 1,
2996 }),
2997 )
2998 .await;
2999 });
3000
3001 let result = NinePClient::connect_once(
3002 &Target::Tcp(addr),
3003 8192,
3004 Some(8192),
3005 Arc::new(Notify::new()),
3006 Arc::new(TrafficCounters::default()),
3007 Arc::new(AtomicBool::new(false)),
3008 )
3009 .await;
3010 assert!(matches!(result, Err(ClientError::Unexpected("version"))));
3011 server.await.unwrap();
3012 }
3013}
3014
3015async fn negotiate_on(conn: &Conn, requested: u32) -> ClientResult<u32> {
3017 let negotiated = version_on(conn, requested).await?;
3018 query_lineage_token(conn).await?;
3019 debug!("ZeroFS 9P dialect negotiated, msize={negotiated}");
3020 Ok(negotiated)
3021}
3022
3023async fn version_on(conn: &Conn, requested: u32) -> ClientResult<u32> {
3025 match NinePClient::send_raw_at_tag(
3027 conn,
3028 Some(NOTAG),
3029 Message::Tversion(Tversion {
3030 msize: requested,
3031 version: P9String::new(VERSION_9P2000L_ZEROFS.to_vec()),
3032 }),
3033 false,
3034 )
3035 .await?
3036 {
3037 Message::Rlerror(e) => Err(ClientError::Errno(e.ecode)),
3038 Message::Rversion(rv) => {
3039 if rv.version.data != VERSION_9P2000L_ZEROFS {
3040 warn!(
3041 "server did not accept the required ZeroFS dialect: {:?}",
3042 rv.version.as_str().unwrap_or("<non-UTF-8>")
3043 );
3044 return Err(ClientError::Unexpected("version"));
3045 }
3046 let negotiated = rv.msize.min(requested);
3048 if negotiated < 4096 {
3049 warn!("server negotiated msize {negotiated} below minimum 4096");
3050 return Err(ClientError::Unexpected("version"));
3051 }
3052 Ok(negotiated)
3053 }
3054 _ => Err(ClientError::Unexpected("version")),
3055 }
3056}
3057
3058async fn query_lineage_token(conn: &Conn) -> ClientResult<()> {
3060 match NinePClient::send_raw(conn, Message::Tgetlineage(Tgetlineage)).await? {
3061 Message::Rgetlineage(r) => {
3062 conn.lineage_token.store(r.token, Ordering::Relaxed);
3063 conn.writer_epoch.store(r.writer_epoch, Ordering::Relaxed);
3064 Ok(())
3065 }
3066 Message::Rlerror(e) => Err(ClientError::Errno(e.ecode)),
3067 _ => Err(ClientError::Unexpected("getlineage")),
3068 }
3069}
3070
3071#[cfg(not(target_arch = "wasm32"))]
3072fn spawn_writer(
3073 write: Box<dyn AsyncWrite + Unpin + Send>,
3074 mut rx: mpsc::Receiver<Vec<u8>>,
3075 conn: Arc<Conn>,
3076 reconnect: Arc<Notify>,
3077) {
3078 runtime::spawn(async move {
3079 let mut writer = tokio::io::BufWriter::with_capacity(64 * 1024, write);
3080 loop {
3081 tokio::select! {
3082 biased;
3083 _ = conn.writer_shutdown.notified() => break,
3085 maybe = rx.recv() => {
3086 let Some(frame) = maybe else { break };
3087 conn.counters.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed);
3088 conn.counters.operations.fetch_add(1, Ordering::Relaxed);
3089 if writer.write_all(&frame).await.is_err() {
3090 conn.shutdown();
3091 break;
3092 }
3093 let mut failed = false;
3094 while let Ok(more) = rx.try_recv() {
3095 conn.counters.bytes_sent.fetch_add(more.len() as u64, Ordering::Relaxed);
3096 conn.counters.operations.fetch_add(1, Ordering::Relaxed);
3097 if writer.write_all(&more).await.is_err() {
3098 failed = true;
3099 break;
3100 }
3101 }
3102 if failed {
3103 conn.shutdown();
3104 break;
3105 }
3106 if writer.flush().await.is_err() {
3107 conn.shutdown();
3108 break;
3109 }
3110 }
3111 }
3112 }
3113 conn.shutdown();
3114 reconnect.notify_waiters();
3115 });
3116}
3117
3118#[cfg(not(target_arch = "wasm32"))]
3119fn spawn_reader(read: Box<dyn AsyncRead + Unpin + Send>, conn: Arc<Conn>, reconnect: Arc<Notify>) {
3120 runtime::spawn(async move {
3121 let mut framed = LengthDelimitedCodec::builder()
3122 .little_endian()
3123 .length_field_offset(0)
3124 .length_field_length(P9_SIZE_FIELD_LEN)
3125 .length_adjustment(0)
3126 .num_skip(0)
3127 .max_frame_length(P9_MAX_MSIZE as usize)
3128 .new_read(read);
3129
3130 loop {
3131 let next = tokio::select! {
3132 biased;
3133 _ = conn.reader_shutdown.notified() => break,
3135 next = framed.next() => next,
3136 };
3137 let frame = match next {
3138 Some(Ok(buf)) => buf.freeze(),
3139 Some(Err(e)) => {
3140 warn!("9P client read failed: {e}");
3141 break;
3142 }
3143 None => break,
3144 };
3145 conn.deliver(frame);
3146 }
3147
3148 conn.connection_lost(&reconnect);
3149 });
3150}
3151
3152#[cfg(test)]
3153mod lock_range_tests {
3154 use super::*;
3155
3156 fn held(start: u64, length: u64) -> LockRecord {
3157 LockRecord {
3158 fid: 9,
3159 lock_type: LockType::WriteLock,
3160 start,
3161 length,
3162 proc_id: 42,
3163 client_id: b"client".to_vec(),
3164 }
3165 }
3166
3167 #[test]
3168 fn lock_record_subtraction_preserves_metadata() {
3169 let survivors = subtract_lock_record(held(10, 90), 30, 20);
3170 assert_eq!(
3171 survivors
3172 .iter()
3173 .map(|record| (record.start, record.length))
3174 .collect::<Vec<_>>(),
3175 vec![(10, 20), (50, 50)]
3176 );
3177 assert!(survivors.iter().all(|record| {
3178 record.fid == 9
3179 && matches!(record.lock_type, LockType::WriteLock)
3180 && record.proc_id == 42
3181 && record.client_id == b"client"
3182 }));
3183 }
3184
3185 #[test]
3186 fn subrange_relock_preserves_tails_for_replay() {
3187 let mut locks = vec![held(0, 100)];
3188 replace_recorded_lock(
3189 &mut locks,
3190 LockRecord {
3191 lock_type: LockType::ReadLock,
3192 start: 20,
3193 length: 30,
3194 ..held(0, 0)
3195 },
3196 );
3197 locks.sort_by_key(|lock| lock.start);
3198 let shape: Vec<_> = locks
3199 .iter()
3200 .map(|lock| {
3201 (
3202 lock.start,
3203 lock.length,
3204 match lock.lock_type {
3205 LockType::ReadLock => "read",
3206 LockType::WriteLock => "write",
3207 LockType::Unlock => "unlock",
3208 },
3209 )
3210 })
3211 .collect();
3212 assert_eq!(
3213 shape,
3214 vec![(0, 20, "write"), (20, 30, "read"), (50, 50, "write")]
3215 );
3216 }
3217}
3218
3219#[cfg(test)]
3220mod durability_tracking_tests {
3221 use super::{FsyncScope, P9_FSYNC_INODE, Unsynced};
3222
3223 #[test]
3224 fn fsync_scope_owns_the_wire_scope_bit() {
3225 assert_eq!(FsyncScope::All.wire_flag(), 0);
3226 assert_eq!(FsyncScope::Inode(7).wire_flag(), P9_FSYNC_INODE);
3227 }
3228
3229 #[test]
3230 fn fsync_clears_the_obligation_when_quiescent() {
3231 let mut u = Unsynced::default();
3232 u.note(7);
3233 let (oldest, generation) = u.snapshot();
3234 assert_eq!(oldest, Some(7));
3235 u.clear_if_unchanged(generation);
3236 assert_eq!(u.snapshot().0, None);
3237 }
3238
3239 #[test]
3240 fn a_repeated_fsync_on_one_inode_keeps_failing_until_a_redo() {
3241 let mut u = Unsynced::default();
3242 u.note(1);
3243 let (oldest, generation) = u.snapshot();
3244 assert_eq!(oldest, Some(1));
3245 u.report_if_unchanged(generation);
3246 assert_eq!(
3247 u.snapshot().0,
3248 Some(1),
3249 "a reported loss must persist so a repeated fsync still fails"
3250 );
3251 u.note(2);
3252 assert_eq!(
3253 u.snapshot().0,
3254 Some(2),
3255 "a redo after a report advances the obligation (no livelock)"
3256 );
3257 let (_, gen2) = u.snapshot();
3258 u.clear_if_unchanged(gen2);
3259 assert_eq!(u.snapshot().0, None);
3260 }
3261
3262 #[test]
3263 fn an_estale_and_redo_on_one_inode_does_not_discharge_a_sibling_inode() {
3264 use std::collections::HashMap;
3265 let mut map: HashMap<u32, Unsynced> = HashMap::new();
3266 map.entry(10).or_default().note(1);
3267 map.entry(20).or_default().note(1);
3268 let (_, g) = map.get(&10).unwrap().snapshot();
3269 map.get_mut(&10).unwrap().report_if_unchanged(g);
3270 map.get_mut(&10).unwrap().note(2);
3271 let (_, g2) = map.get(&10).unwrap().snapshot();
3272 map.get_mut(&10).unwrap().clear_if_unchanged(g2);
3273 if map.get(&10).unwrap().snapshot().0.is_none() {
3274 map.remove(&10);
3275 }
3276 assert_eq!(
3277 map.get(&20).unwrap().snapshot().0,
3278 Some(1),
3279 "inode 10's ESTALE+redo+success cycle must not touch inode 20"
3280 );
3281 }
3282
3283 #[test]
3284 fn fsync_does_not_erase_a_write_from_its_window() {
3285 let mut u = Unsynced::default();
3286 u.note(7);
3287 let (_, generation) = u.snapshot();
3288 u.note(7);
3289 u.clear_if_unchanged(generation);
3290 assert_eq!(
3291 u.snapshot().0,
3292 Some(7),
3293 "a write racing the fsync must stay tracked"
3294 );
3295 }
3296
3297 #[test]
3298 fn oldest_token_is_kept_across_a_lineage_change() {
3299 let mut u = Unsynced::default();
3300 u.note(9);
3301 u.note(5);
3302 assert_eq!(u.snapshot().0, Some(5), "the oldest (riskiest) token wins");
3303 }
3304
3305 #[test]
3306 fn nothing_tracked_reports_none() {
3307 let u = Unsynced::default();
3308 assert_eq!(u.snapshot(), (None, 0));
3309 }
3310}
3311
3312#[cfg(test)]
3313fn test_conn_with_receiver() -> (Arc<Conn>, mpsc::Receiver<Vec<u8>>) {
3314 let (writer_tx, rx) = mpsc::channel(1);
3315 let conn = Arc::new(Conn {
3316 writer_tx,
3317 pending: DashMap::new(),
3318 tag_ctr: AtomicU16::new(0),
3319 lineage_token: AtomicU64::new(0),
3320 writer_epoch: AtomicU64::new(0),
3321 dead: AtomicBool::new(false),
3322 receive_generation: AtomicU64::new(0),
3323 liveness_probe_tag: AtomicU32::new(NO_LIVENESS_PROBE_TAG),
3324 probe_lock: tokio::sync::Mutex::new(()),
3325 writer_shutdown: Notify::new(),
3326 reader_shutdown: Notify::new(),
3327 counters: Arc::new(TrafficCounters::default()),
3328 });
3329 (conn, rx)
3330}
3331
3332#[cfg(test)]
3333mod session_transition_tests {
3334 use super::*;
3335
3336 type TestRequests = mpsc::Receiver<Vec<u8>>;
3337 const REPLAY: u8 = P9_REBIND_REPLAY;
3338 const OPEN_REPLAY: u8 = REPLAY | P9_REBIND_OPENED;
3339
3340 fn test_conn() -> Arc<Conn> {
3341 test_conn_with_receiver().0
3342 }
3343
3344 fn test_client(conn: Arc<Conn>) -> Arc<NinePClient> {
3345 test_client_with_limit(conn, MAX_INFLIGHT_REQUESTS)
3346 }
3347
3348 fn test_client_with_limit(conn: Arc<Conn>, max_inflight_requests: usize) -> Arc<NinePClient> {
3349 Arc::new(NinePClient {
3350 targets: Vec::new(),
3351 conn: ArcSwap::new(conn),
3352 live: AtomicBool::new(true),
3353 terminal_errno: AtomicU32::new(0),
3354 live_notify: Notify::new(),
3355 reconnect_notify: Arc::new(Notify::new()),
3356 msize: 8192,
3357 msize_mismatch_warned: Arc::new(AtomicBool::new(false)),
3358 fid_ctr: AtomicU32::new(1),
3359 fid_free: Mutex::new(Vec::new()),
3360 state: Mutex::new(SessionState::default()),
3361 stale_fids: DashSet::new(),
3362 session_transition: tokio::sync::Mutex::new(()),
3363 request_admission: Arc::new(Semaphore::new(max_inflight_requests)),
3364 unsynced: DashMap::new(),
3365 counters: Arc::new(TrafficCounters::default()),
3366 })
3367 }
3368
3369 fn track_test_fid(client: &NinePClient, fid: u32) {
3370 client
3371 .state
3372 .lock()
3373 .unwrap()
3374 .fids
3375 .insert(fid, inode_fid(u64::from(fid), 0, 0, None));
3376 }
3377
3378 #[test]
3379 fn mutation_obligations_are_derived_from_stable_inode_ids() {
3380 let client = test_client(test_conn());
3381 {
3382 let mut state = client.state.lock().unwrap();
3383 state.fids.insert(7, inode_fid(70, 0, 0, None));
3384 state.fids.insert(8, inode_fid(80, 0, 0, None));
3385 }
3386 let rename = Message::Trenameat(Trenameat {
3387 olddirfid: 7,
3388 oldname: P9String::new(b"old".to_vec()),
3389 newdirfid: 8,
3390 newname: P9String::new(b"new".to_vec()),
3391 });
3392 assert_eq!(client.mutation_inodes(&rename).unwrap(), vec![80, 70]);
3393
3394 client
3395 .state
3396 .lock()
3397 .unwrap()
3398 .fids
3399 .insert(8, inode_fid(70, 0, 0, None));
3400 assert_eq!(
3401 client.mutation_inodes(&rename).unwrap(),
3402 vec![70],
3403 "aliases of one inode must create one durability obligation"
3404 );
3405 }
3406
3407 #[test]
3408 fn recycling_a_fid_does_not_discard_its_inode_obligation() {
3409 let client = test_client(test_conn());
3410 client.note_unsynced(70, 5);
3411 client.free_fid(7);
3412 assert_eq!(client.snapshot_unsynced(70).0, Some(5));
3413 }
3414
3415 async fn next_request(requests: &mut TestRequests) -> Option<P9Message> {
3416 requests
3417 .recv()
3418 .await
3419 .map(|frame| P9Message::from_bytes_ctx(&frame, false).unwrap())
3420 }
3421
3422 async fn recv_request(requests: &mut TestRequests, description: &str) -> P9Message {
3423 next_request(requests).await.expect(description)
3424 }
3425
3426 async fn recv_op_request(requests: &mut TestRequests, description: &str) -> P9Message {
3427 let frame = requests.recv().await.expect(description);
3428 P9Message::from_bytes_ctx(&frame, true).unwrap()
3429 }
3430
3431 fn reply(conn: &Conn, tag: u16, body: Message) {
3432 conn.deliver(Bytes::from(P9Message::new(tag, body).to_bytes().unwrap()));
3433 }
3434
3435 fn qid(path: u64) -> Qid {
3436 Qid {
3437 type_: 0,
3438 version: 0,
3439 path,
3440 }
3441 }
3442
3443 fn inode_fid(inode_id: u64, root_inode: u64, n_uname: u32, opened: Option<u32>) -> FidRecord {
3444 FidRecord {
3445 inode_id,
3446 root_inode,
3447 n_uname,
3448 uname: Vec::new(),
3449 opened,
3450 }
3451 }
3452
3453 fn attach_fid(root_inode: u64, n_uname: u32) -> FidRecord {
3454 FidRecord {
3455 inode_id: root_inode,
3456 root_inode,
3457 n_uname,
3458 uname: Vec::new(),
3459 opened: None,
3460 }
3461 }
3462
3463 fn write_lock(fid: u32, start: u64, length: u64) -> LockRecord {
3464 LockRecord {
3465 fid,
3466 lock_type: LockType::WriteLock,
3467 start,
3468 length,
3469 proc_id: 1,
3470 client_id: b"owner".to_vec(),
3471 }
3472 }
3473
3474 #[test]
3475 fn forgetting_a_fid_removes_only_its_locks() {
3476 let mut state = SessionState::default();
3477 state.fids.insert(7, inode_fid(70, 0, 0, None));
3478 state.fids.insert(8, inode_fid(80, 0, 0, None));
3479 state.locks.extend([
3480 write_lock(7, 0, 10),
3481 write_lock(8, 0, 10),
3482 write_lock(7, 20, 10),
3483 ]);
3484
3485 state.forget_fid(7);
3486
3487 assert!(!state.fids.contains_key(&7));
3488 assert!(state.fids.contains_key(&8));
3489 assert_eq!(state.locks.len(), 1);
3490 assert_eq!(state.locks[0].fid, 8);
3491 }
3492
3493 async fn replay_rebind_error(
3494 client: &NinePClient,
3495 conn: &Arc<Conn>,
3496 mut requests: TestRequests,
3497 expected: (u32, u64, u64, u8),
3498 ecode: u32,
3499 ) -> ClientResult<()> {
3500 let responder_conn = Arc::clone(conn);
3501 let responder = tokio::spawn(async move {
3502 let request = recv_request(&mut requests, "rebind request").await;
3503 let Message::Trebind(rebind) = request.body else {
3504 panic!("expected Trebind");
3505 };
3506 let actual = (rebind.fid, rebind.inode_id, rebind.root_inode, rebind.flags);
3507 assert_eq!(actual, expected);
3508 reply(
3509 &responder_conn,
3510 request.tag,
3511 Message::Rlerror(Rlerror { ecode }),
3512 );
3513 });
3514 let result = client.replay(conn).await;
3515 responder.await.unwrap();
3516 result
3517 }
3518
3519 #[tokio::test]
3520 async fn progressing_fid_replay_has_no_aggregate_deadline() {
3521 let (conn, mut requests) = test_conn_with_receiver();
3522 let client = test_client(Arc::clone(&conn));
3523 {
3524 let mut state = client.state.lock().unwrap();
3525 state.fids.insert(1, attach_fid(10, 0));
3526 state.fids.insert(2, inode_fid(11, 10, 0, None));
3527 }
3528
3529 let responder_conn = Arc::clone(&conn);
3530 let responder = tokio::spawn(async move {
3531 for _ in 0..2 {
3532 let request = recv_request(&mut requests, "replay rebind").await;
3533 let Message::Trebind(rebind) = request.body else {
3534 panic!("expected Trebind");
3535 };
3536 runtime::sleep(Duration::from_millis(20)).await;
3539 reply(
3540 &responder_conn,
3541 request.tag,
3542 Message::Rrebind(Rrebind {
3543 qid: qid(rebind.inode_id),
3544 }),
3545 );
3546 }
3547 });
3548
3549 client
3550 .replay_with_rpc_timeout(&conn, Duration::from_secs(1))
3551 .await
3552 .unwrap();
3553 responder.await.unwrap();
3554 }
3555
3556 #[tokio::test]
3557 async fn stalled_fid_replay_is_bounded_per_exchange() {
3558 let (conn, _requests) = test_conn_with_receiver();
3559 let client = test_client(Arc::clone(&conn));
3560 client
3561 .state
3562 .lock()
3563 .unwrap()
3564 .fids
3565 .insert(1, attach_fid(10, 0));
3566
3567 assert!(matches!(
3568 client
3569 .replay_with_rpc_timeout(&conn, Duration::from_millis(5))
3570 .await,
3571 Err(ClientError::Disconnected)
3572 ));
3573 assert!(conn.dead.load(Ordering::Acquire));
3574 assert!(conn.pending.is_empty());
3575 }
3576
3577 #[tokio::test]
3578 async fn reconnect_winner_rejects_an_old_stateful_response() {
3579 let old = test_conn();
3580 let client = test_client(Arc::clone(&old));
3581 let waiter_client = Arc::clone(&client);
3582 let waiter_old = Arc::clone(&old);
3583
3584 let reconnect = client.session_transition.lock().await;
3585 let waiter = tokio::spawn(async move {
3586 waiter_client
3587 .accept_stateful_response(&waiter_old)
3588 .await
3589 .is_some()
3590 });
3591 tokio::task::yield_now().await;
3592 assert!(
3593 !waiter.is_finished(),
3594 "response settlement must wait for reconnect's transition"
3595 );
3596
3597 old.dead.store(true, Ordering::Release);
3598 client.live.store(false, Ordering::Release);
3599 client.conn.store(test_conn());
3600 client.live.store(true, Ordering::Release);
3601 drop(reconnect);
3602
3603 assert!(
3604 !waiter.await.unwrap(),
3605 "a response from the replaced connection must be resent, not settled"
3606 );
3607 }
3608
3609 #[tokio::test]
3610 async fn settled_state_is_visible_to_the_next_replay_snapshot() {
3611 let conn = test_conn();
3612 let client = test_client(Arc::clone(&conn));
3613 let settlement = client
3614 .accept_stateful_response(&conn)
3615 .await
3616 .expect("current response should settle");
3617
3618 let replay_client = Arc::clone(&client);
3619 let snapshot = tokio::spawn(async move {
3620 let _reconnect = replay_client.session_transition.lock().await;
3621 replay_client.state.lock().unwrap().fids.contains_key(&7)
3622 });
3623 tokio::task::yield_now().await;
3624 assert!(
3625 !snapshot.is_finished(),
3626 "replay must wait until response bookkeeping completes"
3627 );
3628
3629 client
3630 .state
3631 .lock()
3632 .unwrap()
3633 .fids
3634 .insert(7, inode_fid(70, 0, 0, Some(0)));
3635 drop(settlement);
3636
3637 assert!(
3638 snapshot.await.unwrap(),
3639 "the replay snapshot must include the settled fid transition"
3640 );
3641 }
3642
3643 #[tokio::test]
3644 async fn permanent_replay_loss_wakes_waiters_with_estale() {
3645 let client = test_client(test_conn());
3646 client.live.store(false, Ordering::Release);
3647 let waiter_client = Arc::clone(&client);
3648 let waiter = tokio::spawn(async move { waiter_client.wait_until_live().await });
3649 tokio::task::yield_now().await;
3650 assert!(!waiter.is_finished());
3651
3652 client.fail_session(linux::ESTALE);
3653 assert!(matches!(
3654 waiter.await.unwrap(),
3655 Err(ClientError::Errno(errno)) if errno == linux::ESTALE
3656 ));
3657 assert!(matches!(
3658 client.wait_until_live().await,
3659 Err(ClientError::Errno(errno)) if errno == linux::ESTALE
3660 ));
3661 }
3662
3663 #[tokio::test]
3664 async fn discarded_replay_candidate_resets_session_before_shutdown() {
3665 let (conn, mut requests) = test_conn_with_receiver();
3666 let client = test_client(Arc::clone(&conn));
3667 let responder_conn = Arc::clone(&conn);
3668 let responder = tokio::spawn(async move {
3669 let request = recv_request(&mut requests, "candidate reset").await;
3670 assert_eq!(request.tag, NOTAG);
3671 assert!(matches!(request.body, Message::Tversion(_)));
3672 reply(
3673 &responder_conn,
3674 request.tag,
3675 Message::Rversion(Rversion {
3676 msize: 8192,
3677 version: P9String::new(VERSION_9P2000L_ZEROFS.to_vec()),
3678 }),
3679 );
3680 requests
3681 });
3682
3683 client.discard_replay_candidate(&conn).await;
3684 let mut requests = responder.await.unwrap();
3685 assert!(
3686 requests.try_recv().is_err(),
3687 "candidate reset must not issue a throwaway lineage query"
3688 );
3689 assert!(conn.dead.load(Ordering::Acquire));
3690 }
3691
3692 #[tokio::test]
3693 async fn replay_establishes_each_attach_root_before_its_explicit_descendants() {
3694 let (conn, mut requests) = test_conn_with_receiver();
3695 let client = test_client(Arc::clone(&conn));
3696 {
3697 let mut state = client.state.lock().unwrap();
3698 state.fids.insert(1, attach_fid(10, 1000));
3699 state.fids.insert(2, attach_fid(20, 2000));
3700 state.fids.insert(3, inode_fid(11, 10, 1000, None));
3701 state.fids.insert(4, inode_fid(21, 20, 2000, None));
3702 }
3703
3704 let responder_conn = Arc::clone(&conn);
3705 let responder = tokio::spawn(async move {
3706 let mut roots = std::collections::BTreeSet::new();
3707 let mut descendants = std::collections::BTreeSet::new();
3708 for index in 0..4 {
3709 let request = recv_request(&mut requests, "replay request").await;
3710 let Message::Trebind(rebind) = request.body else {
3711 panic!("expected Trebind");
3712 };
3713 assert_eq!(
3714 rebind.flags, P9_REBIND_REPLAY,
3715 "every automatic fid replay is marked independently of open state"
3716 );
3717 if index < 2 {
3718 assert_eq!(rebind.inode_id, rebind.root_inode);
3719 roots.insert(rebind.root_inode);
3720 } else {
3721 assert_ne!(rebind.inode_id, rebind.root_inode);
3722 descendants.insert((rebind.inode_id, rebind.root_inode));
3723 }
3724 reply(
3725 &responder_conn,
3726 request.tag,
3727 Message::Rrebind(Rrebind {
3728 qid: qid(rebind.inode_id),
3729 }),
3730 );
3731 }
3732 assert_eq!(roots, [10, 20].into_iter().collect());
3733 assert_eq!(descendants, [(11, 10), (21, 20)].into_iter().collect());
3734 });
3735
3736 client.replay(&conn).await.unwrap();
3737 responder.await.unwrap();
3738 }
3739
3740 #[tokio::test]
3741 async fn name_derived_credentials_survive_descendant_replay() {
3742 let (old_conn, mut old_requests) = test_conn_with_receiver();
3743 let client = test_client(Arc::clone(&old_conn));
3744 let responder_conn = Arc::clone(&old_conn);
3745 let responder = tokio::spawn(async move {
3746 let request = recv_request(&mut old_requests, "attach request").await;
3747 let Message::Tattach(attach) = request.body else {
3748 panic!("expected Tattach");
3749 };
3750 assert_eq!(attach.uname.as_str().unwrap(), "root");
3751 assert_eq!(attach.n_uname, u32::MAX);
3752 reply(
3753 &responder_conn,
3754 request.tag,
3755 Message::Rattach(Rattach {
3756 qid: Qid {
3757 type_: 0x80,
3758 ..qid(10)
3759 },
3760 }),
3761 );
3762
3763 let request = recv_request(&mut old_requests, "walk request").await;
3764 assert!(matches!(request.body, Message::Twalk(_)));
3765 reply(
3766 &responder_conn,
3767 request.tag,
3768 Message::Rwalk(Rwalk {
3769 nwqid: 1,
3770 wqids: vec![qid(11)],
3771 }),
3772 );
3773 });
3774
3775 client.attach(1, NOFID, "root", "", u32::MAX).await.unwrap();
3776 client.walk(1, 2, &[b"child"]).await.unwrap();
3777 responder.await.unwrap();
3778
3779 client.state.lock().unwrap().fids.remove(&1);
3780
3781 let (replay_conn, mut replay_requests) = test_conn_with_receiver();
3782 let responder_conn = Arc::clone(&replay_conn);
3783 let responder = tokio::spawn(async move {
3784 let request = recv_request(&mut replay_requests, "descendant rebind").await;
3785 let Message::Trebind(rebind) = request.body else {
3786 panic!("expected Trebind");
3787 };
3788 assert_eq!(rebind.fid, 2);
3789 assert_eq!((rebind.inode_id, rebind.root_inode), (11, 10));
3790 assert_eq!(rebind.uname.as_str().unwrap(), "root");
3791 assert_eq!(rebind.n_uname, u32::MAX);
3792 reply(
3793 &responder_conn,
3794 request.tag,
3795 Message::Rrebind(Rrebind { qid: qid(11) }),
3796 );
3797 });
3798
3799 client.replay(&replay_conn).await.unwrap();
3800 responder.await.unwrap();
3801 }
3802
3803 #[tokio::test]
3804 async fn binary_rebind_credentials_survive_replay_exactly() {
3805 let (old_conn, mut old_requests) = test_conn_with_receiver();
3806 let client = test_client(Arc::clone(&old_conn));
3807 let expected = vec![
3808 P9_REBIND_CREDENTIAL_SENTINEL,
3809 P9_REBIND_CREDENTIAL_VERSION,
3810 0xd0,
3811 0x07,
3812 0,
3813 0,
3814 2,
3815 0xd1,
3816 0x07,
3817 0,
3818 0,
3819 0xd2,
3820 0x07,
3821 0,
3822 0,
3823 ];
3824
3825 let responder_conn = Arc::clone(&old_conn);
3826 let expected_initial = expected.clone();
3827 let responder = tokio::spawn(async move {
3828 let request = recv_request(&mut old_requests, "credential rebind").await;
3829 let Message::Trebind(rebind) = request.body else {
3830 panic!("expected Trebind");
3831 };
3832 assert_eq!((rebind.fid, rebind.inode_id, rebind.n_uname), (7, 70, 1000));
3833 assert_eq!(rebind.flags, 0);
3834 assert_eq!(rebind.uname.data, expected_initial);
3835 reply(
3836 &responder_conn,
3837 request.tag,
3838 Message::Rrebind(Rrebind { qid: qid(70) }),
3839 );
3840 });
3841
3842 client
3843 .rebind_with_credentials(7, 70, 1000, 2000, &[2001, 2002])
3844 .await
3845 .unwrap();
3846 responder.await.unwrap();
3847
3848 let (replay_conn, mut replay_requests) = test_conn_with_receiver();
3849 let responder_conn = Arc::clone(&replay_conn);
3850 let responder = tokio::spawn(async move {
3851 let request = recv_request(&mut replay_requests, "credential replay").await;
3852 let Message::Trebind(rebind) = request.body else {
3853 panic!("expected Trebind");
3854 };
3855 assert_eq!((rebind.fid, rebind.inode_id, rebind.n_uname), (7, 70, 1000));
3856 assert_eq!(rebind.flags, P9_REBIND_REPLAY);
3857 assert_eq!(rebind.uname.data, expected);
3858 reply(
3859 &responder_conn,
3860 request.tag,
3861 Message::Rrebind(Rrebind { qid: qid(70) }),
3862 );
3863 });
3864
3865 client.replay(&replay_conn).await.unwrap();
3866 responder.await.unwrap();
3867 }
3868
3869 #[tokio::test]
3870 async fn primary_group_rebind_marks_incomplete_groups_through_replay() {
3871 let (old_conn, mut old_requests) = test_conn_with_receiver();
3872 let client = test_client(Arc::clone(&old_conn));
3873 let expected = vec![
3874 P9_REBIND_CREDENTIAL_SENTINEL,
3875 P9_REBIND_CREDENTIAL_VERSION,
3876 0xd0,
3877 0x07,
3878 0,
3879 0,
3880 P9_REBIND_CREDENTIAL_GROUPS_INCOMPLETE,
3881 ];
3882
3883 let responder_conn = Arc::clone(&old_conn);
3884 let expected_initial = expected.clone();
3885 let responder = tokio::spawn(async move {
3886 let request = recv_request(&mut old_requests, "primary-group rebind").await;
3887 let Message::Trebind(rebind) = request.body else {
3888 panic!("expected Trebind");
3889 };
3890 assert_eq!((rebind.fid, rebind.inode_id, rebind.n_uname), (7, 70, 1000));
3891 assert_eq!(rebind.flags, 0);
3892 assert_eq!(rebind.uname.data, expected_initial);
3893 reply(
3894 &responder_conn,
3895 request.tag,
3896 Message::Rrebind(Rrebind { qid: qid(70) }),
3897 );
3898 });
3899
3900 client
3901 .rebind_with_primary_group(7, 70, 1000, 2000)
3902 .await
3903 .unwrap();
3904 responder.await.unwrap();
3905
3906 let (replay_conn, mut replay_requests) = test_conn_with_receiver();
3907 let responder_conn = Arc::clone(&replay_conn);
3908 let responder = tokio::spawn(async move {
3909 let request = recv_request(&mut replay_requests, "primary-group replay").await;
3910 let Message::Trebind(rebind) = request.body else {
3911 panic!("expected Trebind");
3912 };
3913 assert_eq!(rebind.flags, P9_REBIND_REPLAY);
3914 assert_eq!(rebind.uname.data, expected);
3915 reply(
3916 &responder_conn,
3917 request.tag,
3918 Message::Rrebind(Rrebind { qid: qid(70) }),
3919 );
3920 });
3921
3922 client.replay(&replay_conn).await.unwrap();
3923 responder.await.unwrap();
3924 }
3925
3926 #[tokio::test]
3927 async fn credential_rebind_rejects_unrepresentable_group_lists() {
3928 let (conn, _requests) = test_conn_with_receiver();
3929 let client = test_client(conn);
3930 let groups = [2000; P9_MAX_GROUPS + 1];
3931
3932 assert!(matches!(
3933 client
3934 .rebind_with_credentials(7, 70, 1000, 2000, &groups)
3935 .await,
3936 Err(ClientError::Errno(code)) if code == linux::E2BIG
3937 ));
3938 }
3939
3940 #[tokio::test]
3941 async fn rooted_descendant_replays_after_its_attach_fid_was_clunked() {
3942 let (conn, mut requests) = test_conn_with_receiver();
3943 let client = test_client(Arc::clone(&conn));
3944 client
3945 .state
3946 .lock()
3947 .unwrap()
3948 .fids
3949 .insert(7, inode_fid(70, 10, 1000, None));
3950
3951 let responder_conn = Arc::clone(&conn);
3952 let responder = tokio::spawn(async move {
3953 let request = recv_request(&mut requests, "descendant rebind").await;
3954 let Message::Trebind(rebind) = request.body else {
3955 panic!("expected Trebind");
3956 };
3957 assert_eq!((rebind.inode_id, rebind.root_inode), (70, 10));
3958 reply(
3959 &responder_conn,
3960 request.tag,
3961 Message::Rrebind(Rrebind { qid: qid(70) }),
3962 );
3963 });
3964
3965 client.replay(&conn).await.unwrap();
3966 responder.await.unwrap();
3967 assert!(client.state.lock().unwrap().fids.contains_key(&7));
3968 }
3969
3970 #[tokio::test]
3971 async fn missing_opened_fid_is_quarantined_without_terminating_the_session() {
3972 let (conn, mut requests) = test_conn_with_receiver();
3973 let client = test_client(Arc::clone(&conn));
3974 client
3975 .state
3976 .lock()
3977 .unwrap()
3978 .fids
3979 .insert(7, inode_fid(70, 0, 0, Some(0)));
3980
3981 let responder_conn = Arc::clone(&conn);
3982 let responder = tokio::spawn(async move {
3983 let request = recv_request(&mut requests, "opened fid rebind").await;
3984 assert!(matches!(
3985 request.body,
3986 Message::Trebind(Trebind {
3987 fid: 7,
3988 inode_id: 70,
3989 flags: OPEN_REPLAY,
3990 ..
3991 })
3992 ));
3993 reply(
3994 &responder_conn,
3995 request.tag,
3996 Message::Rlerror(Rlerror {
3997 ecode: linux::ESTALE,
3998 }),
3999 );
4000
4001 let request = recv_request(&mut requests, "unrelated read").await;
4002 assert!(matches!(
4003 request.body,
4004 Message::Tread(Tread {
4005 fid: 8,
4006 count: 1,
4007 ..
4008 })
4009 ));
4010 reply(
4011 &responder_conn,
4012 request.tag,
4013 Message::Rread(Rread {
4014 count: 1,
4015 data: P9Bytes::from(vec![b'x']),
4016 }),
4017 );
4018 });
4019
4020 client.replay(&conn).await.unwrap();
4021 assert!(!client.state.lock().unwrap().fids.contains_key(&7));
4022 assert!(client.stale_fids.contains(&7));
4023 assert_eq!(client.terminal_errno.load(Ordering::Acquire), 0);
4024 assert!(matches!(
4025 client.read(7, 0, 1).await,
4026 Err(ClientError::Errno(errno)) if errno == linux::ESTALE
4027 ));
4028 assert_eq!(client.read(8, 0, 1).await.unwrap(), vec![b'x']);
4029 tokio::time::timeout(Duration::from_secs(1), client.clunk(7))
4030 .await
4031 .expect("stale clunk must not reach the server")
4032 .unwrap();
4033 assert!(!client.stale_fids.contains(&7));
4034 responder.await.unwrap();
4035 }
4036
4037 #[tokio::test]
4038 async fn read_bytes_retains_the_response_frame_allocation() {
4039 let (conn, mut requests) = test_conn_with_receiver();
4040 let client = test_client(Arc::clone(&conn));
4041 let request_client = Arc::clone(&client);
4042 let read = tokio::spawn(async move { request_client.read_bytes(7, 0, 7).await });
4043
4044 let request = recv_request(&mut requests, "read request").await;
4045 assert!(matches!(request.body, Message::Tread(_)));
4046 let frame = Bytes::from(
4047 P9Message::new(
4048 request.tag,
4049 Message::Rread(Rread {
4050 count: 7,
4051 data: P9Bytes::from(Bytes::from_static(b"payload")),
4052 }),
4053 )
4054 .to_bytes()
4055 .unwrap(),
4056 );
4057 let expected_payload_ptr = frame.as_ptr() as usize + P9_IOHDRSZ as usize;
4058 conn.deliver(frame);
4059
4060 let payload = read.await.unwrap().unwrap();
4061 assert_eq!(payload.as_ref(), b"payload");
4062 assert_eq!(payload.as_ptr() as usize, expected_payload_ptr);
4063 }
4064
4065 #[test]
4066 fn freeing_a_fid_clears_its_stale_tombstone() {
4067 let client = test_client(test_conn());
4068 client.stale_fids.insert(7);
4069 client.free_fid(7);
4070 assert!(!client.stale_fids.contains(&7));
4071 }
4072
4073 #[test]
4074 fn stale_tombstones_cover_source_destination_and_clunk_fids() {
4075 let client = test_client(test_conn());
4076 client.stale_fids.insert(7);
4077
4078 for request in [
4079 Message::Tlink(Tlink {
4080 dfid: 8,
4081 fid: 7,
4082 name: P9String::new(b"link".to_vec()),
4083 }),
4084 Message::Twalk(Twalk {
4085 fid: 8,
4086 newfid: 7,
4087 nwname: 0,
4088 wnames: Vec::new(),
4089 }),
4090 Message::Tclunk(Tclunk { fid: 7 }),
4091 ] {
4092 assert!(matches!(
4093 client.validate_fids(request.request_fids()),
4094 Err(ClientError::Errno(errno)) if errno == linux::ESTALE
4095 ));
4096 }
4097 assert!(
4098 client
4099 .validate_fids(
4100 Message::Tread(Tread {
4101 fid: 8,
4102 offset: 0,
4103 count: 1,
4104 })
4105 .request_fids()
4106 )
4107 .is_ok()
4108 );
4109 }
4110
4111 #[tokio::test]
4112 async fn clunk_does_not_mask_a_terminal_session_estale() {
4113 let client = test_client(test_conn());
4114 client.stale_fids.insert(7);
4115 client.fail_session(linux::ESTALE);
4116
4117 assert!(matches!(
4118 client.clunk(7).await,
4119 Err(ClientError::Errno(errno)) if errno == linux::ESTALE
4120 ));
4121 assert!(client.stale_fids.contains(&7));
4122 }
4123
4124 #[tokio::test]
4125 async fn failed_reopen_clunks_the_pending_rebind_fid() {
4126 let (conn, mut requests) = test_conn_with_receiver();
4127 let client = test_client(Arc::clone(&conn));
4128 client
4129 .state
4130 .lock()
4131 .unwrap()
4132 .fids
4133 .insert(7, inode_fid(70, 0, 0, Some(0)));
4134
4135 let responder_conn = Arc::clone(&conn);
4136 let responder = tokio::spawn(async move {
4137 let request = recv_request(&mut requests, "opened fid rebind").await;
4138 assert!(matches!(
4139 request.body,
4140 Message::Trebind(Trebind { fid: 7, .. })
4141 ));
4142 reply(
4143 &responder_conn,
4144 request.tag,
4145 Message::Rrebind(Rrebind { qid: qid(70) }),
4146 );
4147
4148 let request = recv_request(&mut requests, "fid reopen").await;
4149 assert!(matches!(
4150 request.body,
4151 Message::Tlopen(Tlopen { fid: 7, .. })
4152 ));
4153 reply(
4154 &responder_conn,
4155 request.tag,
4156 Message::Rlerror(Rlerror {
4157 ecode: linux::ESTALE,
4158 }),
4159 );
4160
4161 let request = recv_request(&mut requests, "pending rebind fid clunk").await;
4162 assert!(matches!(request.body, Message::Tclunk(Tclunk { fid: 7 })));
4163 reply(&responder_conn, request.tag, Message::Rclunk(Rclunk));
4164 });
4165
4166 client.replay(&conn).await.unwrap();
4167 responder.await.unwrap();
4168 let state = client.state.lock().unwrap();
4169 assert!(!state.fids.contains_key(&7));
4170 drop(state);
4171 assert!(client.stale_fids.contains(&7));
4172 assert_eq!(client.terminal_errno.load(Ordering::Acquire), 0);
4173 }
4174
4175 #[tokio::test]
4176 async fn missing_locked_fid_is_a_terminal_replay_error() {
4177 let (conn, requests) = test_conn_with_receiver();
4178 let client = test_client(Arc::clone(&conn));
4179 {
4180 let mut state = client.state.lock().unwrap();
4181 state.fids.insert(7, inode_fid(70, 0, 0, None));
4182 state.locks.push(write_lock(7, 0, 0));
4183 }
4184
4185 assert!(matches!(
4186 replay_rebind_error(&client, &conn, requests, (7, 70, 0, REPLAY), linux::ENOENT)
4187 .await,
4188 Err(ClientError::Errno(errno)) if errno == linux::ESTALE
4189 ));
4190 {
4191 let state = client.state.lock().unwrap();
4192 assert!(state.fids.contains_key(&7));
4193 assert_eq!(state.locks.len(), 1);
4194 }
4195 assert_eq!(client.terminal_errno.load(Ordering::Acquire), linux::ESTALE);
4196 }
4197
4198 #[tokio::test]
4199 async fn missing_unopened_rebind_is_dropped() {
4200 let (conn, requests) = test_conn_with_receiver();
4201 let client = test_client(Arc::clone(&conn));
4202 client
4203 .state
4204 .lock()
4205 .unwrap()
4206 .fids
4207 .insert(7, inode_fid(70, 0, 0, None));
4208
4209 replay_rebind_error(&client, &conn, requests, (7, 70, 0, REPLAY), linux::ENOENT)
4210 .await
4211 .unwrap();
4212 assert!(!client.state.lock().unwrap().fids.contains_key(&7));
4213 assert_eq!(client.terminal_errno.load(Ordering::Acquire), 0);
4214 }
4215
4216 #[tokio::test]
4217 async fn operational_rebind_error_is_transient_and_preserves_fid() {
4218 let (conn, requests) = test_conn_with_receiver();
4219 let client = test_client(Arc::clone(&conn));
4220 client
4221 .state
4222 .lock()
4223 .unwrap()
4224 .fids
4225 .insert(7, inode_fid(70, 0, 0, None));
4226
4227 assert!(matches!(
4228 replay_rebind_error(
4229 &client,
4230 &conn,
4231 requests,
4232 (7, 70, 0, REPLAY),
4233 linux::EIO as u32,
4234 )
4235 .await,
4236 Err(ClientError::Errno(errno)) if errno == linux::EIO as u32
4237 ));
4238 assert!(client.state.lock().unwrap().fids.contains_key(&7));
4239 assert_eq!(client.terminal_errno.load(Ordering::Acquire), 0);
4240 }
4241
4242 #[tokio::test]
4243 async fn missing_unopened_attach_root_is_dropped_while_open_child_replays() {
4244 let (conn, mut requests) = test_conn_with_receiver();
4245 let client = test_client(Arc::clone(&conn));
4246 {
4247 let mut state = client.state.lock().unwrap();
4248 state.fids.insert(1, attach_fid(1, 0));
4249 state.fids.insert(2, inode_fid(70, 1, 0, Some(0)));
4250 }
4251
4252 let responder_conn = Arc::clone(&conn);
4253 let responder = tokio::spawn(async move {
4254 let request = recv_request(&mut requests, "root rebind request").await;
4255 assert!(matches!(
4256 request.body,
4257 Message::Trebind(Trebind {
4258 inode_id: 1,
4259 root_inode: 1,
4260 flags: P9_REBIND_REPLAY,
4261 ..
4262 })
4263 ));
4264 reply(
4265 &responder_conn,
4266 request.tag,
4267 Message::Rlerror(Rlerror {
4268 ecode: linux::ENOENT,
4269 }),
4270 );
4271
4272 let request = recv_request(&mut requests, "opened child rebind").await;
4273 assert!(matches!(
4274 request.body,
4275 Message::Trebind(Trebind {
4276 inode_id: 70,
4277 root_inode: 1,
4278 flags: P9_REBIND_KNOWN_FLAGS,
4279 ..
4280 })
4281 ));
4282 reply(
4283 &responder_conn,
4284 request.tag,
4285 Message::Rrebind(Rrebind { qid: qid(70) }),
4286 );
4287
4288 let request = recv_request(&mut requests, "opened child reopen").await;
4289 assert!(matches!(
4290 request.body,
4291 Message::Tlopen(Tlopen { fid: 2, flags: 0 })
4292 ));
4293 reply(
4294 &responder_conn,
4295 request.tag,
4296 Message::Rlopen(Rlopen {
4297 qid: qid(70),
4298 iounit: 0,
4299 }),
4300 );
4301 });
4302
4303 client.replay(&conn).await.unwrap();
4304 responder.await.unwrap();
4305 let state = client.state.lock().unwrap();
4306 assert!(!state.fids.contains_key(&1));
4307 assert!(state.fids.contains_key(&2));
4308 drop(state);
4309 assert_eq!(client.terminal_errno.load(Ordering::Acquire), 0);
4310 }
4311
4312 #[tokio::test]
4313 async fn operational_attach_error_is_transient() {
4314 let (conn, requests) = test_conn_with_receiver();
4315 let client = test_client(Arc::clone(&conn));
4316 client
4317 .state
4318 .lock()
4319 .unwrap()
4320 .fids
4321 .insert(1, attach_fid(2, 0));
4322
4323 assert!(matches!(
4324 replay_rebind_error(&client, &conn, requests, (1, 2, 2, REPLAY), linux::EIO as u32)
4325 .await,
4326 Err(ClientError::Errno(errno)) if errno == linux::EIO as u32
4327 ));
4328 assert!(client.state.lock().unwrap().fids.contains_key(&1));
4329 assert_eq!(client.terminal_errno.load(Ordering::Acquire), 0);
4330 }
4331
4332 #[tokio::test]
4333 async fn persistent_lock_conflict_keeps_the_candidate_without_terminating_the_session() {
4334 let (conn, mut requests) = test_conn_with_receiver();
4335 let client = test_client(Arc::clone(&conn));
4336 client.state.lock().unwrap().locks.push(write_lock(7, 0, 0));
4337
4338 let responder_conn = Arc::clone(&conn);
4339 let responder = tokio::spawn(async move {
4340 while let Some(request) = next_request(&mut requests).await {
4341 assert!(matches!(request.body, Message::Tlock(_)));
4342 reply(
4343 &responder_conn,
4344 request.tag,
4345 Message::Rlock(Rlock {
4346 status: LockStatus::Blocked.as_wire(),
4347 }),
4348 );
4349 }
4350 });
4351
4352 assert!(
4353 runtime::timeout(Duration::from_millis(100), client.replay(&conn))
4354 .await
4355 .is_err(),
4356 "a lock conflict should keep waiting on the restored candidate"
4357 );
4358 assert_eq!(client.terminal_errno.load(Ordering::Acquire), 0);
4359 responder.abort();
4360 let _ = responder.await;
4361 }
4362
4363 #[tokio::test]
4364 async fn supervised_lock_replay_stops_after_the_last_external_owner_drops() {
4365 let (conn, mut requests) = test_conn_with_receiver();
4366 let client = test_client(Arc::clone(&conn));
4367 client.state.lock().unwrap().locks.push(write_lock(7, 0, 0));
4368
4369 let replay_client = Arc::clone(&client);
4370 let replay_conn = Arc::clone(&conn);
4371 let replay =
4372 tokio::spawn(async move { replay_client.replay_supervised(&replay_conn).await });
4373
4374 let request = recv_request(&mut requests, "conflicting replay lock").await;
4375 assert!(matches!(request.body, Message::Tlock(_)));
4376 reply(
4377 &conn,
4378 request.tag,
4379 Message::Rlock(Rlock {
4380 status: LockStatus::Blocked.as_wire(),
4381 }),
4382 );
4383
4384 drop(client);
4385 assert!(matches!(
4386 runtime::timeout(Duration::from_secs(1), replay).await,
4387 Ok(Ok(Err(ClientError::Disconnected)))
4388 ));
4389 assert!(conn.dead.load(Ordering::Acquire));
4390 }
4391
4392 #[tokio::test]
4393 async fn refused_lock_replay_terminates_the_session() {
4394 let (conn, mut requests) = test_conn_with_receiver();
4395 let client = test_client(Arc::clone(&conn));
4396 client.state.lock().unwrap().locks.push(write_lock(7, 0, 0));
4397
4398 let responder_conn = Arc::clone(&conn);
4399 let responder = tokio::spawn(async move {
4400 let request = recv_request(&mut requests, "lock replay").await;
4401 assert!(matches!(request.body, Message::Tlock(_)));
4402 reply(
4403 &responder_conn,
4404 request.tag,
4405 Message::Rlock(Rlock {
4406 status: LockStatus::LockError.as_wire(),
4407 }),
4408 );
4409 });
4410
4411 assert!(matches!(
4412 client.replay(&conn).await,
4413 Err(ClientError::Errno(errno)) if errno == linux::ESTALE
4414 ));
4415 responder.await.unwrap();
4416 assert_eq!(client.terminal_errno.load(Ordering::Acquire), linux::ESTALE);
4417 }
4418
4419 #[tokio::test]
4420 async fn lock_replay_retries_through_old_session_teardown() {
4421 let (conn, mut requests) = test_conn_with_receiver();
4422 let client = test_client(Arc::clone(&conn));
4423 client.state.lock().unwrap().locks.push(write_lock(7, 0, 0));
4424
4425 let responder_conn = Arc::clone(&conn);
4426 let responder = tokio::spawn(async move {
4427 let request = recv_request(&mut requests, "blocked lock acquisition").await;
4428 assert!(matches!(request.body, Message::Tlock(_)));
4429 reply(
4430 &responder_conn,
4431 request.tag,
4432 Message::Rlock(Rlock {
4433 status: LockStatus::Blocked.as_wire(),
4434 }),
4435 );
4436
4437 let request = recv_request(&mut requests, "retried lock acquisition").await;
4438 assert!(matches!(
4439 request.body,
4440 Message::Tlock(Tlock {
4441 lock_type: LockType::WriteLock,
4442 ..
4443 })
4444 ));
4445 reply(
4446 &responder_conn,
4447 request.tag,
4448 Message::Rlock(Rlock {
4449 status: LockStatus::Success.as_wire(),
4450 }),
4451 );
4452 });
4453
4454 client.replay(&conn).await.unwrap();
4455 responder.await.unwrap();
4456 assert_eq!(client.terminal_errno.load(Ordering::Acquire), 0);
4457 }
4458
4459 #[tokio::test]
4460 async fn raced_lock_conflict_rolls_back_the_acquired_prefix() {
4461 let (conn, mut requests) = test_conn_with_receiver();
4462 let client = test_client(Arc::clone(&conn));
4463 client
4464 .state
4465 .lock()
4466 .unwrap()
4467 .locks
4468 .extend([write_lock(7, 0, 10), write_lock(8, 20, 10)]);
4469
4470 let responder_conn = Arc::clone(&conn);
4471 let responder = tokio::spawn(async move {
4472 let request = recv_request(&mut requests, "first lock acquisition").await;
4473 assert!(matches!(
4474 request.body,
4475 Message::Tlock(Tlock {
4476 fid: 7,
4477 lock_type: LockType::WriteLock,
4478 ..
4479 })
4480 ));
4481 reply(
4482 &responder_conn,
4483 request.tag,
4484 Message::Rlock(Rlock {
4485 status: LockStatus::Success.as_wire(),
4486 }),
4487 );
4488
4489 let request = recv_request(&mut requests, "raced lock acquisition").await;
4490 assert!(matches!(
4491 request.body,
4492 Message::Tlock(Tlock {
4493 fid: 8,
4494 lock_type: LockType::WriteLock,
4495 ..
4496 })
4497 ));
4498 reply(
4499 &responder_conn,
4500 request.tag,
4501 Message::Rlerror(Rlerror {
4502 ecode: linux::EAGAIN,
4503 }),
4504 );
4505
4506 let request = recv_request(&mut requests, "prefix rollback").await;
4507 assert!(matches!(
4508 request.body,
4509 Message::Tlock(Tlock {
4510 fid: 7,
4511 lock_type: LockType::Unlock,
4512 ..
4513 })
4514 ));
4515 reply(
4516 &responder_conn,
4517 request.tag,
4518 Message::Rlock(Rlock {
4519 status: LockStatus::Success.as_wire(),
4520 }),
4521 );
4522
4523 for expected_fid in [7, 8] {
4524 let request = recv_request(&mut requests, "retry lock acquisition").await;
4525 let Message::Tlock(lock) = request.body else {
4526 panic!("expected Tlock");
4527 };
4528 assert_eq!(lock.fid, expected_fid);
4529 assert!(matches!(lock.lock_type, LockType::WriteLock));
4530 reply(
4531 &responder_conn,
4532 request.tag,
4533 Message::Rlock(Rlock {
4534 status: LockStatus::Success.as_wire(),
4535 }),
4536 );
4537 }
4538 });
4539
4540 client.replay(&conn).await.unwrap();
4541 responder.await.unwrap();
4542 assert_eq!(client.terminal_errno.load(Ordering::Acquire), 0);
4543 }
4544
4545 #[test]
4546 fn clean_rejection_of_first_frame_restarts_the_retry_horizon() {
4547 let mut attempt = OpAttemptState::default();
4548 let (first_flags, first_origin) = attempt
4549 .dispatch_frame(true, 7, |_, origin| Ok(origin))
4550 .unwrap();
4551 assert_eq!((first_flags, first_origin), (0, 7));
4552 assert!(attempt.started.is_some());
4553 attempt.proven_predispatch(first_flags);
4554 assert!(attempt.started.is_none());
4555 let (next_flags, next_origin) = attempt
4556 .dispatch_frame(true, 8, |_, origin| Ok(origin))
4557 .unwrap();
4558 assert_eq!(
4559 (next_flags, next_origin),
4560 (0, 8),
4561 "a definitive rejection of the sole FIRST may be routed as FIRST again"
4562 );
4563 }
4564
4565 #[test]
4566 fn clean_rejection_of_retry_does_not_erase_older_ambiguity() {
4567 let mut attempt = OpAttemptState::default();
4568 let (first_flags, first_origin) = attempt
4569 .dispatch_frame(true, 7, |_, origin| Ok(origin))
4570 .unwrap();
4571 assert_eq!((first_flags, first_origin), (0, 7));
4572
4573 let (retry_flags, retry_origin) = attempt
4574 .dispatch_frame(true, 8, |_, origin| Ok(origin))
4575 .unwrap();
4576 assert_eq!((retry_flags, retry_origin), (P9_OP_FLAG_RETRY, 7));
4577 assert!(attempt.started.is_some());
4578 attempt.proven_predispatch(retry_flags);
4579 assert!(attempt.started.is_some());
4580 let (next_flags, next_origin) = attempt
4581 .dispatch_frame(true, 9, |_, origin| Ok(origin))
4582 .unwrap();
4583 assert_eq!(
4584 (next_flags, next_origin),
4585 (P9_OP_FLAG_RETRY, 7),
4586 "a rejected RETRY cannot make the older lost FIRST unambiguous"
4587 );
4588 }
4589
4590 async fn assert_notleader_reroute(ecode: u32, expected_flags: u8, expected_epoch: u64) {
4591 let (old_conn, mut old_requests) = test_conn_with_receiver();
4592 old_conn.writer_epoch.store(7, Ordering::Relaxed);
4593 let client = test_client(Arc::clone(&old_conn));
4594 track_test_fid(&client, 7);
4595
4596 let request_client = Arc::clone(&client);
4597 let request = tokio::spawn(async move { request_client.write(7, 0, b"x").await });
4598 let first_frame = tokio::time::timeout(Duration::from_secs(1), old_requests.recv())
4599 .await
4600 .expect("FIRST request was not queued")
4601 .expect("old request channel closed");
4602 let first = P9Message::from_bytes_ctx(&first_frame, true).unwrap();
4603 let op_id = first.op_id;
4604 assert_ne!(op_id, [0u8; 16]);
4605 assert_eq!(first.op_flags, 0);
4606 assert_eq!(first.op_origin_epoch, 7);
4607 assert!(matches!(first.body, Message::Twrite(_)));
4608
4609 client.live.store(false, Ordering::Release);
4610 reply(&old_conn, first.tag, Message::Rlerror(Rlerror { ecode }));
4611 tokio::time::timeout(Duration::from_secs(1), async {
4612 while !old_conn.dead.load(Ordering::Acquire) {
4613 tokio::task::yield_now().await;
4614 }
4615 })
4616 .await
4617 .expect("not-leader response did not force a re-probe");
4618
4619 let (new_conn, mut new_requests) = test_conn_with_receiver();
4620 new_conn.writer_epoch.store(8, Ordering::Relaxed);
4621 client.conn.store(Arc::clone(&new_conn));
4622 client.live.store(true, Ordering::Release);
4623 client.live_notify.notify_waiters();
4624
4625 let rerouted_frame = tokio::time::timeout(Duration::from_secs(1), new_requests.recv())
4626 .await
4627 .expect("request was not rerouted")
4628 .expect("replacement request channel closed");
4629 let rerouted = P9Message::from_bytes_ctx(&rerouted_frame, true).unwrap();
4630 assert_eq!(rerouted.op_id, op_id);
4631 assert_eq!(
4632 rerouted.op_flags, expected_flags,
4633 "CLEAN must restore FIRST; an ambiguous rejection must retain RETRY"
4634 );
4635 assert_eq!(
4636 rerouted.op_origin_epoch, expected_epoch,
4637 "FIRST adopts the successor epoch; RETRY retains its origin epoch"
4638 );
4639 assert!(matches!(rerouted.body, Message::Twrite(_)));
4640
4641 reply(
4642 &new_conn,
4643 rerouted.tag,
4644 Message::Rwrite(Rwrite { count: 1 }),
4645 );
4646 assert_eq!(request.await.unwrap().unwrap(), 1);
4647 }
4648
4649 #[tokio::test]
4650 async fn clean_notleader_after_first_reroutes_same_op_id_as_first() {
4651 assert_notleader_reroute(P9_ENOTLEADER_CLEAN, 0, 8).await;
4652 }
4653
4654 #[tokio::test]
4655 async fn generic_notleader_after_first_reroutes_same_op_id_as_retry() {
4656 assert_notleader_reroute(P9_ENOTLEADER, P9_OP_FLAG_RETRY, 7).await;
4657 }
4658
4659 #[tokio::test]
4660 async fn write_bytes_chunks_at_the_msize_boundary() {
4661 let (conn, mut requests) = test_conn_with_receiver();
4662 let client = test_client(Arc::clone(&conn));
4663 track_test_fid(&client, 7);
4664 let max = client.max_write_payload();
4665 assert_eq!(
4666 max,
4667 client.msize() - P9_TWRITE_HDR - P9_OP_ENVELOPE_LEN as u32
4668 );
4669 let offset = 37;
4670 let mut payload = vec![b'x'; max as usize + 1];
4671 payload[max as usize] = b'y';
4672 let payload = Bytes::from(payload);
4673
4674 let request_client = Arc::clone(&client);
4675 let request =
4676 tokio::spawn(async move { request_client.write_bytes(7, offset, payload).await });
4677
4678 let first = recv_op_request(&mut requests, "first write request").await;
4679 let Message::Twrite(first_write) = first.body else {
4680 panic!("expected Twrite");
4681 };
4682 assert_eq!(first_write.offset, offset);
4683 assert_eq!(first_write.count, max);
4684 assert_eq!(first_write.data.len(), max as usize);
4685 assert!(first_write.data.iter().all(|byte| *byte == b'x'));
4686 reply(&conn, first.tag, Message::Rwrite(Rwrite { count: max }));
4687
4688 let second = recv_op_request(&mut requests, "second write request").await;
4689 let Message::Twrite(second_write) = second.body else {
4690 panic!("expected Twrite");
4691 };
4692 assert_eq!(second_write.offset, offset + u64::from(max));
4693 assert_eq!(second_write.count, 1);
4694 assert_eq!(second_write.data.as_ref(), b"y");
4695 reply(&conn, second.tag, Message::Rwrite(Rwrite { count: 1 }));
4696
4697 assert_eq!(request.await.unwrap().unwrap(), u64::from(max) + 1);
4698 }
4699
4700 #[tokio::test]
4701 async fn read_once_rejects_a_payload_larger_than_requested() {
4702 let (conn, mut requests) = test_conn_with_receiver();
4703 let client = test_client(Arc::clone(&conn));
4704
4705 let request_client = Arc::clone(&client);
4706 let request = tokio::spawn(async move { request_client.read_once(7, 0, 1).await });
4707 let sent = recv_request(&mut requests, "read request").await;
4708 assert!(matches!(sent.body, Message::Tread(Tread { count: 1, .. })));
4709 reply(
4710 &conn,
4711 sent.tag,
4712 Message::Rread(Rread {
4713 count: 2,
4714 data: P9Bytes::from(vec![b'x', b'y']),
4715 }),
4716 );
4717
4718 assert!(matches!(
4719 request.await.unwrap(),
4720 Err(ClientError::Unexpected("read count"))
4721 ));
4722 }
4723
4724 #[tokio::test]
4725 async fn write_chunk_rejects_a_count_larger_than_attempted() {
4726 let (conn, mut requests) = test_conn_with_receiver();
4727 let client = test_client(Arc::clone(&conn));
4728 track_test_fid(&client, 7);
4729
4730 let request_client = Arc::clone(&client);
4731 let request = tokio::spawn(async move {
4732 request_client
4733 .write_chunk(7, 0, Bytes::from_static(b"x"))
4734 .await
4735 });
4736 let sent = recv_op_request(&mut requests, "write request").await;
4737 assert!(matches!(
4738 sent.body,
4739 Message::Twrite(Twrite { count: 1, .. })
4740 ));
4741 reply(&conn, sent.tag, Message::Rwrite(Rwrite { count: 2 }));
4742
4743 assert!(matches!(
4744 request.await.unwrap(),
4745 Err(ClientError::Unexpected("write count"))
4746 ));
4747 }
4748
4749 #[tokio::test]
4750 async fn ambiguous_reply_loss_marks_the_next_frame_as_retry() {
4751 let (conn, mut requests) = test_conn_with_receiver();
4752 let client = test_client(Arc::clone(&conn));
4753 track_test_fid(&client, 7);
4754
4755 let responder_conn = Arc::clone(&conn);
4756 let responder = tokio::spawn(async move {
4757 let first = recv_op_request(&mut requests, "initial write request").await;
4758 let op_id = first.op_id;
4759 assert_ne!(op_id, [0u8; 16]);
4760 assert_eq!(first.op_flags, 0);
4761
4762 let (_, pending) = responder_conn
4763 .pending
4764 .remove(&first.tag)
4765 .expect("initial response slot");
4766 drop(pending);
4767
4768 let retry = recv_op_request(&mut requests, "retried write request").await;
4769 assert_eq!(retry.op_id, op_id);
4770 assert_eq!(retry.op_flags, P9_OP_FLAG_RETRY);
4771 reply(
4772 &responder_conn,
4773 retry.tag,
4774 Message::Rlerror(Rlerror {
4775 ecode: P9_EOPIDSTALE,
4776 }),
4777 );
4778 });
4779
4780 assert!(matches!(
4781 client.write(7, 0, b"x").await,
4782 Err(ClientError::Errno(P9_EOPIDSTALE))
4783 ));
4784 responder.await.unwrap();
4785 assert!(conn.pending.is_empty());
4786 }
4787
4788 #[tokio::test]
4789 async fn cancelled_stateful_create_retires_its_session() {
4790 let (old_conn, mut old_requests) = test_conn_with_receiver();
4791 old_conn.writer_epoch.store(7, Ordering::Relaxed);
4792 let client = test_client(Arc::clone(&old_conn));
4793 client
4794 .state
4795 .lock()
4796 .unwrap()
4797 .fids
4798 .insert(7, inode_fid(1, 1, 0, None));
4799 let first_client = Arc::clone(&client);
4800 let first_task = tokio::spawn(async move {
4801 first_client
4802 .lcreate(7, b"child", linux::O_CREAT, 0o644, 0)
4803 .await
4804 });
4805 let first = recv_op_request(&mut old_requests, "FIRST create request").await;
4806 assert_eq!(first.op_flags, 0);
4807 assert_eq!(first.op_origin_epoch, 7);
4808 assert!(matches!(first.body, Message::Tlcreate(_)));
4809
4810 first_task.abort();
4811 let _ = first_task.await;
4812 assert!(
4813 old_conn.dead.load(Ordering::Acquire),
4814 "an unobserved stateful transition must retire its connection"
4815 );
4816 assert_eq!(client.state.lock().unwrap().fids[&7].inode_id, 1);
4817 old_conn.connection_lost(&client.reconnect_notify);
4818 }
4819
4820 #[tokio::test]
4821 async fn cancelled_stateful_request_does_not_retire_an_unseen_successor() {
4822 let (old_conn, mut old_requests) = test_conn_with_receiver();
4823 let client = test_client(Arc::clone(&old_conn));
4824 client
4825 .state
4826 .lock()
4827 .unwrap()
4828 .fids
4829 .insert(7, inode_fid(1, 1, 0, None));
4830
4831 let request_client = Arc::clone(&client);
4832 let request = tokio::spawn(async move {
4833 request_client
4834 .lcreate(7, b"child", linux::O_CREAT, 0o644, 0)
4835 .await
4836 });
4837 let sent = recv_op_request(&mut old_requests, "create request").await;
4838 assert!(matches!(sent.body, Message::Tlcreate(_)));
4839
4840 client.live.store(false, Ordering::Release);
4841 old_conn.connection_lost(&client.reconnect_notify);
4842 let (successor, _successor_requests) = test_conn_with_receiver();
4843 client.conn.store(Arc::clone(&successor));
4844
4845 request.abort();
4846 let _ = request.await;
4847 assert!(old_conn.dead.load(Ordering::Acquire));
4848 assert!(
4849 !successor.dead.load(Ordering::Acquire),
4850 "cancellation must not retire a connection that did not receive the request"
4851 );
4852 }
4853
4854 #[tokio::test]
4855 async fn cancelled_zero_id_lopen_retires_its_session() {
4856 let (conn, mut requests) = test_conn_with_receiver();
4857 let client = test_client(Arc::clone(&conn));
4858 client
4859 .state
4860 .lock()
4861 .unwrap()
4862 .fids
4863 .insert(7, inode_fid(42, 1, 0, None));
4864
4865 let request_client = Arc::clone(&client);
4866 let request = tokio::spawn(async move { request_client.lopen(7, 0).await });
4867 let sent = recv_request(&mut requests, "Tlopen request").await;
4868 assert!(matches!(sent.body, Message::Tlopen(Tlopen { fid: 7, .. })));
4869
4870 request.abort();
4871 let _ = request.await;
4872 assert!(
4873 conn.dead.load(Ordering::Acquire),
4874 "zero-id stateful cancellation must normalize the session"
4875 );
4876 assert_eq!(client.state.lock().unwrap().fids[&7].opened, None);
4877 }
4878
4879 #[tokio::test]
4880 async fn cancelling_stateful_create_before_enqueue_keeps_connection_live() {
4881 let (conn, _requests) = test_conn_with_receiver();
4882 conn.writer_tx
4883 .send(vec![0])
4884 .await
4885 .expect("test writer queue should accept its first frame");
4886 let client = test_client(Arc::clone(&conn));
4887 track_test_fid(&client, 7);
4888 let request_client = Arc::clone(&client);
4889 let request = tokio::spawn(async move {
4890 request_client
4891 .lcreate(7, b"child", linux::O_CREAT, 0o644, 0)
4892 .await
4893 });
4894 for _ in 0..4 {
4895 tokio::task::yield_now().await;
4896 }
4897 assert!(!request.is_finished());
4898 assert!(conn.pending.is_empty());
4899
4900 request.abort();
4901 let _ = request.await;
4902 assert!(
4903 !conn.dead.load(Ordering::Acquire),
4904 "pre-enqueue cancellation must not churn the live connection"
4905 );
4906 }
4907
4908 #[tokio::test]
4909 async fn cancelling_a_dispatched_request_quarantines_its_tag() {
4910 let (conn, mut requests) = test_conn_with_receiver();
4911 let client = test_client(Arc::clone(&conn));
4912 track_test_fid(&client, 7);
4913 let request_client = Arc::clone(&client);
4914 let request = tokio::spawn(async move { request_client.write(7, 0, b"x").await });
4915
4916 let first_frame = tokio::time::timeout(Duration::from_secs(1), requests.recv())
4917 .await
4918 .expect("request was not queued")
4919 .expect("request channel closed");
4920 let first = P9Message::from_bytes_ctx(&first_frame, true).unwrap();
4921 assert_eq!(conn.pending.len(), 1);
4922 request.abort();
4923 let _ = request.await;
4924 assert_eq!(
4925 conn.pending.len(),
4926 1,
4927 "a dispatched request's tag must remain quarantined"
4928 );
4929
4930 conn.tag_ctr.store(first.tag, Ordering::Relaxed);
4931 let second_client = Arc::clone(&client);
4932 let second_request = tokio::spawn(async move { second_client.write(7, 0, b"y").await });
4933 let second_frame = tokio::time::timeout(Duration::from_secs(1), requests.recv())
4934 .await
4935 .expect("second request was not queued")
4936 .expect("request channel closed");
4937 let second = P9Message::from_bytes_ctx(&second_frame, true).unwrap();
4938 assert_ne!(second.tag, first.tag);
4939
4940 reply(&conn, first.tag, Message::Rwrite(Rwrite { count: 1 }));
4941 tokio::task::yield_now().await;
4942 assert!(
4943 !second_request.is_finished(),
4944 "the late response must not complete the new request"
4945 );
4946
4947 reply(&conn, second.tag, Message::Rwrite(Rwrite { count: 1 }));
4948 assert_eq!(second_request.await.unwrap().unwrap(), 1);
4949 assert!(conn.pending.is_empty());
4950 }
4951
4952 #[tokio::test]
4953 async fn cancelling_before_writer_capacity_does_not_register_a_tag() {
4954 let (conn, _requests) = test_conn_with_receiver();
4955 conn.writer_tx
4956 .send(vec![0])
4957 .await
4958 .expect("test writer queue should accept its first frame");
4959
4960 let client = test_client(Arc::clone(&conn));
4961 track_test_fid(&client, 7);
4962 let request_client = Arc::clone(&client);
4963 let request = tokio::spawn(async move { request_client.write(7, 0, b"x").await });
4964 for _ in 0..4 {
4965 tokio::task::yield_now().await;
4966 }
4967 assert!(!request.is_finished());
4968 assert!(
4969 conn.pending.is_empty(),
4970 "waiting for writer capacity must happen before tag registration"
4971 );
4972 request.abort();
4973 let _ = request.await;
4974 assert!(conn.pending.is_empty());
4975 }
4976
4977 #[tokio::test]
4978 async fn admission_waits_locally_until_an_inflight_request_settles() {
4979 let (conn, mut requests) = test_conn_with_receiver();
4980 let client = test_client_with_limit(Arc::clone(&conn), 1);
4981 track_test_fid(&client, 7);
4982
4983 let first_client = Arc::clone(&client);
4984 let first = tokio::spawn(async move { first_client.write(7, 0, b"a").await });
4985 let first_request = recv_op_request(&mut requests, "first admitted request").await;
4986 assert_eq!(client.request_admission.available_permits(), 0);
4987
4988 let second_client = Arc::clone(&client);
4989 let second = tokio::spawn(async move { second_client.write(7, 1, b"b").await });
4990 for _ in 0..4 {
4991 tokio::task::yield_now().await;
4992 }
4993 assert!(
4994 requests.try_recv().is_err(),
4995 "the next request must remain local while the permit is held"
4996 );
4997
4998 reply(
4999 &conn,
5000 first_request.tag,
5001 Message::Rwrite(Rwrite { count: 1 }),
5002 );
5003 assert_eq!(first.await.unwrap().unwrap(), 1);
5004
5005 let second_request = recv_op_request(&mut requests, "second admitted request").await;
5006 reply(
5007 &conn,
5008 second_request.tag,
5009 Message::Rwrite(Rwrite { count: 1 }),
5010 );
5011 assert_eq!(second.await.unwrap().unwrap(), 1);
5012 assert_eq!(client.request_admission.available_permits(), 1);
5013 }
5014
5015 #[tokio::test]
5016 async fn cancelled_request_retains_admission_while_its_tag_is_quarantined() {
5017 let (conn, mut requests) = test_conn_with_receiver();
5018 let client = test_client_with_limit(Arc::clone(&conn), 1);
5019 track_test_fid(&client, 7);
5020
5021 let first_client = Arc::clone(&client);
5022 let first = tokio::spawn(async move { first_client.write(7, 0, b"a").await });
5023 let first_request = recv_op_request(&mut requests, "first request").await;
5024 first.abort();
5025 let _ = first.await;
5026 assert_eq!(client.request_admission.available_permits(), 0);
5027
5028 let second_client = Arc::clone(&client);
5029 let second = tokio::spawn(async move { second_client.write(7, 1, b"b").await });
5030 for _ in 0..4 {
5031 tokio::task::yield_now().await;
5032 }
5033 assert!(
5034 requests.try_recv().is_err(),
5035 "cancellation must not release capacity while the reply remains unresolved"
5036 );
5037
5038 reply(
5041 &conn,
5042 first_request.tag,
5043 Message::Rwrite(Rwrite { count: 1 }),
5044 );
5045 let second_request = recv_op_request(&mut requests, "second request").await;
5046 reply(
5047 &conn,
5048 second_request.tag,
5049 Message::Rwrite(Rwrite { count: 1 }),
5050 );
5051 assert_eq!(second.await.unwrap().unwrap(), 1);
5052 }
5053
5054 #[tokio::test]
5055 async fn reconnect_resend_keeps_the_original_admission_permit() {
5056 let (old_conn, mut old_requests) = test_conn_with_receiver();
5057 let client = test_client_with_limit(Arc::clone(&old_conn), 1);
5058 track_test_fid(&client, 7);
5059
5060 let first_client = Arc::clone(&client);
5061 let first = tokio::spawn(async move { first_client.write(7, 0, b"a").await });
5062 let first_request = recv_op_request(&mut old_requests, "first request").await;
5063 assert!(matches!(
5064 first_request.body,
5065 Message::Twrite(Twrite { offset: 0, .. })
5066 ));
5067
5068 let second_client = Arc::clone(&client);
5069 let second = tokio::spawn(async move { second_client.write(7, 1, b"b").await });
5070 for _ in 0..4 {
5071 tokio::task::yield_now().await;
5072 }
5073 assert!(old_requests.try_recv().is_err());
5074
5075 client.live.store(false, Ordering::Release);
5076 old_conn.connection_lost(&client.reconnect_notify);
5077 let (new_conn, mut new_requests) = test_conn_with_receiver();
5078 client.conn.store(Arc::clone(&new_conn));
5079 client.live.store(true, Ordering::Release);
5080 client.live_notify.notify_waiters();
5081
5082 let resent = recv_op_request(&mut new_requests, "resent first request").await;
5083 assert!(matches!(
5084 resent.body,
5085 Message::Twrite(Twrite { offset: 0, .. })
5086 ));
5087 assert!(
5088 new_requests.try_recv().is_err(),
5089 "a resend must retain its permit instead of admitting another operation"
5090 );
5091
5092 reply(&new_conn, resent.tag, Message::Rwrite(Rwrite { count: 1 }));
5093 assert_eq!(first.await.unwrap().unwrap(), 1);
5094
5095 let second_request = recv_op_request(&mut new_requests, "second request").await;
5096 assert!(matches!(
5097 second_request.body,
5098 Message::Twrite(Twrite { offset: 1, .. })
5099 ));
5100 reply(
5101 &new_conn,
5102 second_request.tag,
5103 Message::Rwrite(Rwrite { count: 1 }),
5104 );
5105 assert_eq!(second.await.unwrap().unwrap(), 1);
5106 }
5107
5108 #[test]
5109 fn undispatched_guard_releases_its_tag() {
5110 let conn = test_conn();
5111
5112 let (tx, _rx) = oneshot::channel();
5113 let tag = NinePClient::alloc_tag(&conn, tx, None).expect("tag allocation failed");
5114 let guard = PendingTag {
5115 conn: Arc::clone(&conn),
5116 tag,
5117 dispatched: false,
5118 };
5119 assert!(conn.pending.contains_key(&tag));
5120 drop(guard);
5121 assert!(conn.pending.is_empty());
5122 }
5123
5124 #[test]
5125 fn allocator_checks_every_wire_tag_after_skipping_notag() {
5126 let conn = test_conn();
5127
5128 for tag in 0..(NOTAG - 1) {
5129 let (tx, _rx) = oneshot::channel();
5130 assert!(NinePClient::register_tag(&conn, tag, tx, None).is_ok());
5131 }
5132 conn.tag_ctr.store(NOTAG, Ordering::Relaxed);
5133
5134 let (tx, _rx) = oneshot::channel();
5135 let tag =
5136 NinePClient::alloc_tag(&conn, tx, None).expect("allocator missed the last usable tag");
5137 assert_eq!(tag, NOTAG - 1);
5138 }
5139}
5140
5141#[cfg(test)]
5142mod liveness_tests {
5143 use super::*;
5144
5145 fn test_conn() -> Arc<Conn> {
5146 test_conn_with_receiver().0
5147 }
5148
5149 #[test]
5150 fn valid_frames_advance_connection_progress() {
5151 let conn = test_conn();
5152 assert_eq!(conn.receive_generation(), 0);
5153 conn.deliver(
5154 P9Message::new(17, Message::Rflush(Rflush))
5155 .to_bytes()
5156 .unwrap()
5157 .into(),
5158 );
5159 assert_eq!(conn.receive_generation(), 1);
5160 }
5161
5162 #[test]
5163 fn malformed_frames_do_not_prove_liveness() {
5164 let conn = test_conn();
5165 conn.deliver(Bytes::from_static(&[0; P9_HEADER_SIZE - 1]));
5166 assert_eq!(conn.receive_generation(), 0);
5167 }
5168
5169 fn response(tag: u16, body: Message) -> Bytes {
5170 P9Message::new(tag, body).to_bytes().unwrap().into()
5171 }
5172
5173 fn request_tag(frame: &[u8]) -> u16 {
5174 u16::from_le_bytes([frame[5], frame[6]])
5175 }
5176
5177 #[tokio::test]
5178 async fn a_quiet_probe_timeout_does_not_claim_liveness() {
5179 let (conn, mut requests) = test_conn_with_receiver();
5180 let observed = conn.receive_generation();
5181 let probe =
5182 NinePClient::probe_connection_with_timeout(&conn, observed, Duration::from_millis(5));
5183 let (alive, request) = tokio::join!(probe, requests.recv());
5184 let request = request.expect("probe request");
5185 let tag = request_tag(&request);
5186
5187 assert!(!alive);
5188 assert!(conn.pending.contains_key(&tag));
5189 assert!(
5190 !NinePClient::probe_connection_with_timeout(
5191 &conn,
5192 conn.receive_generation(),
5193 Duration::from_millis(5),
5194 )
5195 .await
5196 );
5197 assert!(
5198 runtime::timeout(Duration::from_millis(1), requests.recv())
5199 .await
5200 .is_err(),
5201 "an outstanding probe must suppress another probe"
5202 );
5203
5204 conn.deliver(response(
5205 tag,
5206 Message::Rgetlineage(Rgetlineage {
5207 token: 1,
5208 writer_epoch: 1,
5209 }),
5210 ));
5211 assert!(conn.pending.is_empty());
5212 assert_eq!(
5213 conn.liveness_probe_tag.load(Ordering::Acquire),
5214 NO_LIVENESS_PROBE_TAG
5215 );
5216 }
5217
5218 #[tokio::test]
5219 async fn unrelated_reply_proves_a_probe_connection_is_moving() {
5220 let (conn, mut requests) = test_conn_with_receiver();
5221 let observed = conn.receive_generation();
5222 let probe =
5223 NinePClient::probe_connection_with_timeout(&conn, observed, Duration::from_millis(5));
5224 let server = async {
5225 let request = requests.recv().await.expect("probe request");
5226 let tag = request_tag(&request);
5227 let other_tag = if tag == 0 { 1 } else { 0 };
5228 conn.deliver(response(other_tag, Message::Rflush(Rflush)));
5229 tag
5230 };
5231 let (alive, tag) = tokio::join!(probe, server);
5232
5233 assert!(alive);
5234 assert!(conn.pending.contains_key(&tag));
5235
5236 conn.deliver(response(
5237 tag,
5238 Message::Rgetlineage(Rgetlineage {
5239 token: 1,
5240 writer_epoch: 1,
5241 }),
5242 ));
5243 assert!(conn.pending.is_empty());
5244 assert_eq!(
5245 conn.liveness_probe_tag.load(Ordering::Acquire),
5246 NO_LIVENESS_PROBE_TAG
5247 );
5248 }
5249}