Skip to main content

mountos_native/
client.rs

1use crate::dir::Dir;
2use crate::error::{Error, Result};
3use crate::events::{lock, Chan, Event, EventKind, Hub, Logger};
4use crate::file::File;
5use crate::frame::{self, Frame, FLAG_ERROR, FLAG_NOTIFY, FLAG_RESPONSE, MAX_PAYLOAD_BYTES};
6use crate::ops::OpState;
7use crate::wire::*;
8use prost::Message;
9use std::collections::{HashMap, HashSet};
10use std::fmt;
11use std::io;
12use std::net::Shutdown;
13use std::os::unix::net::UnixStream;
14use std::path::{Path, PathBuf};
15use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
16use std::sync::{Arc, Condvar, Mutex, OnceLock};
17use std::thread::JoinHandle;
18use std::time::{Duration, Instant};
19
20/// Client settings. Start from `Options::default()` and chain the `with_` methods. New fields
21/// can be added in a minor version, so a struct literal does not compile outside this crate.
22#[derive(Clone)]
23#[non_exhaustive]
24pub struct Options {
25  /// Number of connections. Calls on different files can run in parallel up to this number.
26  pub pool_size: usize,
27  pub client_name: String,
28  /// Receives log lines: events nobody listens for, listener panics and dropped events.
29  /// The line has no prefix and no newline. When `None`, lines go to stderr with the prefix
30  /// "mountos: ". Pass your own function to route them elsewhere.
31  pub logger: Option<Logger>,
32  /// Longest a single request may take, from the end of the send to the reply. `Duration::ZERO`
33  /// (the default) means no limit. A call that runs over fails with `Error::Timeout`. When the
34  /// connection pipelines, only that call fails: its id is marked abandoned and the late reply is
35  /// dropped. Otherwise, or when too many ids are abandoned, the connection is dropped and dialed
36  /// again on the next call, and its files and watches are gone. A call that sends several
37  /// requests (a large read or write) gets the limit for each request.
38  pub call_timeout: Duration,
39  /// Most requests outstanding on one connection when the server pipelines. The server limit
40  /// (`max_inflight` in its Hello reply) also applies. Values are held between 1 and 1024. A
41  /// server without the pipelining capability gets one request at a time.
42  pub max_inflight: usize,
43}
44
45/// Default for `Options::max_inflight`.
46pub const DEFAULT_MAX_INFLIGHT: usize = 32;
47const MAX_INFLIGHT_CEILING: usize = 1024;
48
49/// Alias of `Options`.
50pub type ClientOptions = Options;
51
52impl Default for Options {
53  fn default() -> Options {
54    Options {
55      pool_size: 4,
56      client_name: concat!("mountos-native-rust/", env!("CARGO_PKG_VERSION")).into(),
57      logger: None,
58      call_timeout: Duration::ZERO,
59      max_inflight: DEFAULT_MAX_INFLIGHT,
60    }
61  }
62}
63
64impl Options {
65  /// Sets `pool_size`.
66  pub fn with_pool_size(mut self, pool_size: usize) -> Options {
67    self.pool_size = pool_size;
68    self
69  }
70
71  /// Sets `logger`.
72  pub fn with_logger(mut self, logger: Logger) -> Options {
73    self.logger = Some(logger);
74    self
75  }
76
77  /// Sets `call_timeout`.
78  pub fn with_call_timeout(mut self, call_timeout: Duration) -> Options {
79    self.call_timeout = call_timeout;
80    self
81  }
82
83  /// Sets `max_inflight`.
84  pub fn with_max_inflight(mut self, max_inflight: usize) -> Options {
85    self.max_inflight = max_inflight;
86    self
87  }
88}
89
90impl fmt::Debug for Options {
91  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92    f.debug_struct("Options")
93      .field("pool_size", &self.pool_size)
94      .field("client_name", &self.client_name)
95      .field("logger", &self.logger.as_ref().map(|_| "<fn>"))
96      .field("call_timeout", &self.call_timeout)
97      .field("max_inflight", &self.max_inflight)
98      .finish()
99  }
100}
101
102fn aborted(msg: &str) -> Error {
103  Error::Io(io::Error::new(io::ErrorKind::ConnectionAborted, msg.to_string()))
104}
105
106/// Joins a thread unless it is the calling one, which would wait for itself.
107fn join_other(h: JoinHandle<()>) {
108  if h.thread().id() != std::thread::current().id() {
109    let _ = h.join();
110  }
111}
112
113/// Capability the server must report for a method, if any.
114fn required_capability(m: u16) -> Option<&'static str> {
115  match m {
116    method::LS => Some("ls_stat"),
117    method::STAT_BATCH | method::READ_BATCH | method::WRITE_BATCH | method::READ_FILE | method::WRITE_FILE => {
118      Some("bulk_io")
119    }
120    method::REMOVE_ALL => Some("remove_all"),
121    method::WATCH => Some("watch"),
122    method::WARM_OPEN | method::WARM_READ => Some("warm"),
123    method::CLONE => Some("clone"),
124    method::CLONE_DIR | method::REMOVE_DIR | method::OP_CANCEL => Some("ops"),
125    _ => None,
126  }
127}
128
129/// Capabilities asked for in Hello. The server answers with the ones it offers.
130const CLIENT_CAPABILITIES: [&str; 8] = [
131  "bulk_io",
132  "ls_stat",
133  "remove_all",
134  "watch",
135  "warm",
136  "pipelining",
137  "clone",
138  "ops",
139];
140
141/// Turns a socket write that hit the send timeout into `Error::Timeout`.
142fn write_timeout(e: Error) -> Error {
143  match e {
144    Error::Io(e) if matches!(e.kind(), io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut) => {
145      Error::Timeout(format!("send: {e}"))
146    }
147    e => e,
148  }
149}
150
151static CONN_SEQ: AtomicU64 = AtomicU64::new(1);
152
153/// One request in flight. The caller waits on it, the reader fills it.
154struct Waiter {
155  method: u16,
156  /// Queue that receives the notices of a watch started by this request.
157  watch: Option<Arc<Chan>>,
158  /// Record of the operation started by this request.
159  op: Option<Arc<OpState>>,
160  out: Mutex<Option<Result<Frame>>>,
161  cv: Condvar,
162}
163
164impl Waiter {
165  fn set(&self, r: Result<Frame>) {
166    *lock(&self.out) = Some(r);
167    self.cv.notify_one();
168  }
169
170  /// Waits for the result. `None` means the deadline passed first.
171  fn wait(&self, deadline: Option<Instant>) -> Option<Result<Frame>> {
172    let mut g = lock(&self.out);
173    loop {
174      if let Some(r) = g.take() {
175        return Some(r);
176      }
177      g = match deadline {
178        None => self.cv.wait(g).unwrap_or_else(|e| e.into_inner()),
179        Some(d) => {
180          let left = d.saturating_duration_since(Instant::now());
181          if left.is_zero() {
182            return None;
183          }
184          self.cv.wait_timeout(g, left).unwrap_or_else(|e| e.into_inner()).0
185        }
186      };
187    }
188  }
189}
190
191#[derive(Default)]
192struct Core {
193  pending: HashMap<u64, Arc<Waiter>>,
194  /// Ids whose caller gave up, with the method. A late reply is dropped (an Open, Opendir or
195  /// Watch reply also releases what it created).
196  abandoned: HashMap<u64, u16>,
197  next_id: u64,
198  /// Why the connection ended. Set once.
199  end: Option<Error>,
200}
201
202/// What the Hello reply settled.
203struct Info {
204  max_payload: usize,
205  /// Requests that may be outstanding at once.
206  limit: usize,
207  /// Capabilities the server reported. `None` when it reported none (an older server), which
208  /// allows every call.
209  caps: Option<HashSet<String>>,
210  /// Server instance id. 0 from an older server.
211  instance: u64,
212}
213
214/// One handshaken connection. Calls from many threads overlap up to the limit. Request frames are
215/// written under a mutex. One reader thread reads every frame: replies go to the waiting call by
216/// request id, notices go to the hub.
217pub(crate) struct Sock {
218  id: u64,
219  hub: Arc<Hub>,
220  /// Per-request limit, zero for none.
221  timeout: Duration,
222  writer: Mutex<UnixStream>,
223  /// Second handle on the socket. Closes the socket from another thread.
224  killer: UnixStream,
225  core: Mutex<Core>,
226  dead: AtomicBool,
227  /// Set when this side ends the connection on purpose.
228  closing: AtomicBool,
229  /// The server sent Closing, so the end that follows is expected.
230  saw_closing: AtomicBool,
231  info: OnceLock<Info>,
232  reader: Mutex<Option<JoinHandle<()>>>,
233  /// Threads that release a handle or watch created by a request that timed out.
234  releasers: Mutex<Vec<JoinHandle<()>>>,
235}
236
237impl Sock {
238  /// Connects and starts the reader. The handshake is separate so a caller can register the
239  /// socket for `close` before it blocks.
240  fn open(path: &Path, hub: &Arc<Hub>, timeout: Duration) -> Result<Arc<Sock>> {
241    let stream = UnixStream::connect(path)?;
242    if !timeout.is_zero() {
243      stream.set_write_timeout(Some(timeout))?;
244    }
245    let (rs, killer) = (stream.try_clone()?, stream.try_clone()?);
246    let sock = Arc::new(Sock {
247      id: CONN_SEQ.fetch_add(1, Ordering::SeqCst),
248      hub: hub.clone(),
249      timeout,
250      writer: Mutex::new(stream),
251      killer,
252      core: Mutex::new(Core::default()),
253      dead: AtomicBool::new(false),
254      closing: AtomicBool::new(false),
255      saw_closing: AtomicBool::new(false),
256      info: OnceLock::new(),
257      reader: Mutex::new(None),
258      releasers: Mutex::new(Vec::new()),
259    });
260    let (rsock, guard) = (sock.clone(), hub.thread_guard());
261    let h = std::thread::Builder::new()
262      .name("mountos-reader".into())
263      .spawn(move || {
264        let _guard = guard;
265        read_loop(rs, &rsock)
266      })?;
267    *lock(&sock.reader) = Some(h);
268    Ok(sock)
269  }
270
271  fn handshake(&self, name: &str, max_inflight: usize) -> Result<()> {
272    let req = HelloRequest {
273      client_name: name.into(),
274      min_version: 1,
275      max_version: 1,
276      capabilities: CLIENT_CAPABILITIES.iter().map(|c| (*c).into()).collect(),
277    };
278    let (resp, _): (HelloResponse, _) = self.call_msg(method::HELLO, &req, &[])?;
279    if resp.version != 1 {
280      return Err(Error::Protocol(format!("server chose version {}", resp.version)));
281    }
282    let mut max_payload = MAX_PAYLOAD_BYTES as usize;
283    if resp.max_payload_bytes > 0 {
284      max_payload = max_payload.min(resp.max_payload_bytes as usize);
285    }
286    let pipelined = resp.capabilities.iter().any(|c| c == "pipelining");
287    let limit = if pipelined {
288      max_inflight.min(resp.max_inflight as usize).max(1)
289    } else {
290      1
291    };
292    let instance = resp.instance_id;
293    let caps = (!resp.capabilities.is_empty()).then(|| resp.capabilities.into_iter().collect());
294    let _ = self.info.set(Info {
295      max_payload,
296      limit,
297      caps,
298      instance,
299    });
300    Ok(())
301  }
302
303  pub(crate) fn max_payload(&self) -> usize {
304    self.info.get().map_or(MAX_PAYLOAD_BYTES as usize, |i| i.max_payload)
305  }
306
307  fn limit(&self) -> usize {
308    self.info.get().map_or(1, |i| i.limit)
309  }
310
311  fn instance(&self) -> u64 {
312    self.info.get().map_or(0, |i| i.instance)
313  }
314
315  fn is_dead(&self) -> bool {
316    self.dead.load(Ordering::SeqCst)
317  }
318
319  /// Ends the connection once: the first reason wins. Every waiting call gets `end`.
320  fn fail(&self, end: Error, report: bool) {
321    let waiters: Vec<Arc<Waiter>> = {
322      let mut core = lock(&self.core);
323      if core.end.is_some() {
324        return;
325      }
326      core.end = Some(end.dup());
327      self.dead.store(true, Ordering::SeqCst);
328      core.abandoned.clear();
329      core.pending.drain().map(|(_, w)| w).collect()
330    };
331    let _ = self.killer.shutdown(Shutdown::Both);
332    for w in waiters {
333      w.set(Err(end.dup()));
334    }
335    self.hub.conn_lost(self.id);
336    self.hub.ops_lost(self.id, &end);
337    let expected = self.closing.load(Ordering::SeqCst) || self.saw_closing.load(Ordering::SeqCst);
338    if report && !expected {
339      self
340        .hub
341        .publish(None, Event::local(EventKind::ConnectionLost, 0, Some(end.dup())));
342    }
343  }
344
345  /// Ends the connection on purpose.
346  pub(crate) fn kill(&self) {
347    self.closing.store(true, Ordering::SeqCst);
348    self.fail(aborted("connection closed"), false);
349  }
350
351  /// Ends the connection because of a local fault. Waiting calls get a copy of `e`.
352  fn kill_with(&self, e: &Error) {
353    self.closing.store(true, Ordering::SeqCst);
354    self.fail(e.dup(), false);
355  }
356
357  /// Ends the connection and joins its threads.
358  fn finish(&self) {
359    self.kill();
360    let reader = lock(&self.reader).take();
361    if let Some(h) = reader {
362      join_other(h);
363    }
364    let releasers = std::mem::take(&mut *lock(&self.releasers));
365    for h in releasers {
366      join_other(h);
367    }
368  }
369
370  fn forget(&self, id: u64) {
371    lock(&self.core).pending.remove(&id);
372  }
373
374  /// Sends one request and waits for its response frame. The sizes and the capability are
375  /// checked before anything is sent, so those failures leave the connection intact.
376  fn call(
377    &self,
378    method: u16,
379    req: &impl Message,
380    payload: &[u8],
381    attach: (Option<Arc<Chan>>, Option<Arc<OpState>>),
382  ) -> Result<Frame> {
383    let (watch, op) = attach;
384    if let Some(cap) = required_capability(method) {
385      if self
386        .info
387        .get()
388        .and_then(|i| i.caps.as_ref())
389        .is_some_and(|c| !c.contains(cap))
390      {
391        return Err(Error::server(
392          "UNSUPPORTED_OPERATION",
393          &format!("the server does not report capability {cap}"),
394        ));
395      }
396    }
397    let meta = req.encode_to_vec();
398    frame::check_limits(meta.len(), payload.len())?;
399    let w = Arc::new(Waiter {
400      method,
401      watch,
402      op,
403      out: Mutex::new(None),
404      cv: Condvar::new(),
405    });
406    let id = {
407      let mut core = lock(&self.core);
408      if let Some(e) = &core.end {
409        return Err(e.dup());
410      }
411      core.next_id += 1;
412      let id = core.next_id;
413      core.pending.insert(id, w.clone());
414      id
415    };
416    let sent = {
417      let mut s = lock(&self.writer);
418      frame::write_parts(&mut *s, 0, method, id, &meta, payload).map_err(write_timeout)
419    };
420    if let Err(e) = sent {
421      // A failed write may leave half a frame on the wire, so the connection is unusable.
422      self.forget(id);
423      self.kill_with(&aborted(&format!("send failed: {e}")));
424      return Err(e);
425    }
426    let deadline = (!self.timeout.is_zero()).then(|| Instant::now() + self.timeout);
427    let resp = match w.wait(deadline) {
428      Some(r) => r?,
429      None => self.give_up(id, method, &w)?,
430    };
431    if resp.flags & FLAG_ERROR != 0 {
432      let e = match WireError::decode(&resp.meta[..]) {
433        Ok(e) => e,
434        Err(e) => {
435          let e = Error::Protocol(format!("bad error meta: {e}"));
436          self.kill_with(&e);
437          return Err(e);
438        }
439      };
440      return Err(Error::Server {
441        code: e.code,
442        message: e.message,
443      });
444    }
445    Ok(resp)
446  }
447
448  /// Handles a call whose wait ran out. With room in the abandoned set the connection stays up
449  /// and the late reply is dropped. Otherwise the connection is cut.
450  fn give_up(&self, id: u64, method: u16, w: &Waiter) -> Result<Frame> {
451    let limit = self.limit();
452    let kept = {
453      let mut core = lock(&self.core);
454      if core.pending.remove(&id).is_none() {
455        None
456      } else if limit > 1 && core.abandoned.len() < limit * 4 {
457        core.abandoned.insert(id, method);
458        Some(true)
459      } else {
460        Some(false)
461      }
462    };
463    match kept {
464      // The reply or the failure won the race and is being delivered.
465      None => w.wait(None).unwrap_or_else(|| Err(aborted("connection closed"))),
466      Some(keep) => {
467        if !keep {
468          self.kill();
469        }
470        Err(Error::Timeout(format!(
471          "method {method} did not finish within {:?}",
472          self.timeout
473        )))
474      }
475    }
476  }
477
478  /// Like `call`, decoding the meta as `R` and returning the reply payload.
479  pub(crate) fn call_msg<R: Message + Default>(
480    &self,
481    method: u16,
482    req: &impl Message,
483    payload: &[u8],
484  ) -> Result<(R, Vec<u8>)> {
485    self.decode(self.call(method, req, payload, (None, None))?)
486  }
487
488  /// Starts a watch. The reader routes the notices of the new watch to `chan` before the call returns.
489  pub(crate) fn call_watch(&self, req: &WatchRequest, chan: Arc<Chan>) -> Result<WatchResponse> {
490    Ok(self.decode(self.call(method::WATCH, req, &[], (Some(chan), None))?)?.0)
491  }
492
493  /// Starts an operation. The reader registers it with the hub before the call returns, so no
494  /// notice of the operation can come first. `id_of` reads the op id from the reply.
495  pub(crate) fn call_op<R: Message + Default>(
496    &self,
497    method: u16,
498    req: &impl Message,
499    st: Arc<OpState>,
500    id_of: impl Fn(&R) -> u64,
501  ) -> Result<u64> {
502    let r: R = self.decode(self.call(method, req, &[], (None, Some(st)))?)?.0;
503    Ok(id_of(&r))
504  }
505
506  fn decode<R: Message + Default>(&self, resp: Frame) -> Result<(R, Vec<u8>)> {
507    match R::decode(&resp.meta[..]) {
508      Ok(msg) => Ok((msg, resp.payload)),
509      Err(e) => {
510        let e = Error::Protocol(format!("bad response meta: {e}"));
511        self.kill_with(&e);
512        Err(e)
513      }
514    }
515  }
516
517  /// Routes one non-notice frame from the reader. An error is a protocol violation.
518  fn deliver(self: &Arc<Self>, hub: &Hub, f: Frame) -> Result<()> {
519    let unexpected = |f: &Frame| Error::Protocol(format!("unexpected frame: method {} id {}", f.method, f.request_id));
520    if f.flags & FLAG_RESPONSE == 0 {
521      return Err(unexpected(&f));
522    }
523    let waiter = {
524      let mut core = lock(&self.core);
525      match core.pending.get(&f.request_id).map(|w| w.method) {
526        Some(m) if m == f.method => core.pending.remove(&f.request_id),
527        Some(_) => return Err(unexpected(&f)),
528        None => {
529          if core.abandoned.get(&f.request_id) != Some(&f.method) {
530            return Err(unexpected(&f));
531          }
532          core.abandoned.remove(&f.request_id);
533          None
534        }
535      }
536    };
537    let ok = f.flags & (FLAG_RESPONSE | FLAG_ERROR) == FLAG_RESPONSE;
538    match waiter {
539      Some(w) => {
540        if ok && f.method == method::WATCH {
541          if let (Ok(r), Some(chan)) = (WatchResponse::decode(&f.meta[..]), &w.watch) {
542            hub.add_watch(r.watch_id, self.id, chan.clone());
543          }
544        }
545        if ok {
546          self.register_op(hub, &f, &w);
547        }
548        w.set(Ok(f));
549      }
550      None if ok => self.release_late(f),
551      None => {}
552    }
553    Ok(())
554  }
555
556  /// Hands the operation a start reply created to the hub. When the connection ended in the
557  /// meantime, the operation ends at once, because no notice can reach it.
558  fn register_op(&self, hub: &Hub, f: &Frame, w: &Waiter) {
559    let Some(st) = &w.op else { return };
560    let id = match f.method {
561      method::CLONE_DIR => CloneDirResponse::decode(&f.meta[..]).map(|r| r.op_id),
562      method::REMOVE_DIR => RemoveDirResponse::decode(&f.meta[..]).map(|r| r.op_id),
563      _ => return,
564    };
565    let Ok(id) = id else { return };
566    hub.add_op(id, self.id, st.clone());
567    if self.is_dead() {
568      hub.ops_lost(self.id, &aborted("connection lost"));
569    }
570  }
571
572  /// A reply arrived for a request nobody waits for. An Open, Opendir or Watch created a server
573  /// resource, so a thread frees it. The reader never writes: it must keep reading.
574  fn release_late(self: &Arc<Self>, f: Frame) {
575    let job: Box<dyn FnOnce(&Sock) + Send> = match f.method {
576      method::OPEN => match OpenResponse::decode(&f.meta[..]) {
577        Ok(r) => Box::new(move |s| drop(s.call_msg::<Empty>(method::CLOSE, &CloseRequest { handle: r.handle }, &[]))),
578        Err(_) => return,
579      },
580      method::OPENDIR => match OpendirResponse::decode(&f.meta[..]) {
581        Ok(r) => {
582          Box::new(move |s| drop(s.call_msg::<Empty>(method::CLOSEDIR, &ClosedirRequest { handle: r.handle }, &[])))
583        }
584        Err(_) => return,
585      },
586      method::WATCH => match WatchResponse::decode(&f.meta[..]) {
587        Ok(r) => {
588          Box::new(move |s| drop(s.call_msg::<Empty>(method::UNWATCH, &UnwatchRequest { watch_id: r.watch_id }, &[])))
589        }
590        Err(_) => return,
591      },
592      _ => return,
593    };
594    if self.is_dead() {
595      return;
596    }
597    let (sock, guard) = (self.clone(), self.hub.thread_guard());
598    let spawned = std::thread::Builder::new()
599      .name("mountos-release".into())
600      .spawn(move || {
601        let _guard = guard;
602        job(&sock)
603      });
604    match spawned {
605      Ok(h) => {
606        let mut rs = lock(&self.releasers);
607        rs.retain(|h| !h.is_finished());
608        rs.push(h);
609      }
610      Err(e) => self.hub.log(&format!("could not start release of a late reply: {e}")),
611    }
612  }
613}
614
615/// Reads frames until the stream ends. Replies go to the waiting call, notices to the hub.
616fn read_loop(mut s: UnixStream, sock: &Arc<Sock>) {
617  let hub = sock.hub.clone();
618  let end = loop {
619    match frame::read(&mut s) {
620      Ok(Some(f)) if f.flags & FLAG_NOTIFY != 0 => on_notice(f, &hub, sock),
621      Ok(Some(f)) => {
622        if let Err(e) = sock.deliver(&hub, f) {
623          break e;
624        }
625      }
626      Ok(None) => break aborted("server closed the connection"),
627      Err(e) => break e,
628    }
629  };
630  let _ = s.shutdown(Shutdown::Both);
631  sock.fail(end, true);
632}
633
634fn on_notice(f: Frame, hub: &Hub, sock: &Sock) {
635  if f.method != method::NOTICE {
636    hub.log(&format!("ignored notify frame with method {}", f.method));
637    return;
638  }
639  let ev = match Notice::decode(&f.meta[..]) {
640    Ok(m) => match Event::from_wire(m) {
641      Some(ev) => ev,
642      None => {
643        hub.log("ignored notice of unknown kind");
644        return;
645      }
646    },
647    Err(e) => {
648      hub.log(&format!("ignored undecodable notice: {e}"));
649      return;
650    }
651  };
652  if ev.kind == EventKind::Closing {
653    sock.saw_closing.store(true, Ordering::SeqCst);
654  }
655  hub.publish(Some(sock.id), ev);
656}
657
658struct SlotState {
659  sock: Option<Arc<Sock>>,
660  /// Counts connects. A file remembers it to detect that its handle died with the old connection.
661  epoch: u64,
662}
663
664/// Wakes callers that wait for a free connection. Every release bumps the generation, so a
665/// waiter that read the generation before it scanned the pool cannot miss a release.
666#[derive(Default)]
667pub(crate) struct Gate {
668  generation: Mutex<u64>,
669  cv: Condvar,
670  /// Callers that found every connection busy and wait for the first to free.
671  waiting: AtomicUsize,
672}
673
674/// Marks a caller as waiting for any free connection until dropped.
675struct Waiting<'a>(&'a Gate);
676
677impl Drop for Waiting<'_> {
678  fn drop(&mut self) {
679    self.0.waiting.fetch_sub(1, Ordering::SeqCst);
680    self.0.bump();
681  }
682}
683
684impl Gate {
685  fn generation(&self) -> u64 {
686    *lock(&self.generation)
687  }
688
689  fn bump(&self) {
690    *lock(&self.generation) += 1;
691    self.cv.notify_all();
692  }
693
694  fn wait_change(&self, seen: u64) {
695    let mut g = lock(&self.generation);
696    while *g == seen {
697      g = self.cv.wait(g).unwrap_or_else(|e| e.into_inner());
698    }
699  }
700
701  fn wait_for_free(&self) -> Waiting<'_> {
702    self.waiting.fetch_add(1, Ordering::SeqCst);
703    Waiting(self)
704  }
705
706  /// Holds back a caller bound to one connection while others wait for any free one, so a long
707  /// operation that goes request after request cannot starve a small call.
708  fn let_waiters_pass(&self) {
709    loop {
710      let seen = self.generation();
711      if self.waiting.load(Ordering::SeqCst) == 0 {
712        return;
713      }
714      self.wait_change(seen);
715    }
716  }
717}
718
719/// Holds one of a slot's request places until dropped, then wakes callers that wait for room.
720struct Permit<'a>(&'a Slot);
721
722impl Drop for Permit<'_> {
723  fn drop(&mut self) {
724    self.0.outstanding.fetch_sub(1, Ordering::SeqCst);
725    self.0.gate.bump();
726  }
727}
728
729/// A lazily connected pool entry. Up to `limit` requests run on its connection at once. A
730/// request owns a place for one round trip only, never across the chunks of a large operation.
731pub(crate) struct Slot {
732  path: PathBuf,
733  name: String,
734  timeout: Duration,
735  max_inflight: usize,
736  hub: Arc<Hub>,
737  gate: Arc<Gate>,
738  /// Requests that hold a place.
739  outstanding: AtomicUsize,
740  /// Places available. One until the Hello reply says more.
741  limit: AtomicUsize,
742  closed: AtomicBool,
743  /// The current connection, reachable without the state lock so `close` can cut a dial or a
744  /// call in flight.
745  live: Mutex<Option<Arc<Sock>>>,
746  state: Mutex<SlotState>,
747}
748
749impl Slot {
750  fn try_reserve(&self) -> Option<Permit<'_>> {
751    let limit = self.limit.load(Ordering::SeqCst);
752    let mut cur = self.outstanding.load(Ordering::SeqCst);
753    loop {
754      if cur >= limit {
755        return None;
756      }
757      match self
758        .outstanding
759        .compare_exchange_weak(cur, cur + 1, Ordering::SeqCst, Ordering::SeqCst)
760      {
761        Ok(_) => return Some(Permit(self)),
762        Err(c) => cur = c,
763      }
764    }
765  }
766
767  /// Runs `f` on the connection once a place is free. With `want`, fails if the connection is
768  /// not the one with that epoch. A broken connection is replaced on the next call.
769  pub(crate) fn run<T>(&self, want: Option<u64>, f: impl FnOnce(&Sock) -> Result<T>) -> Result<(T, u64)> {
770    self.gate.let_waiters_pass();
771    let permit = loop {
772      let seen = self.gate.generation();
773      if self.closed.load(Ordering::SeqCst) {
774        return Err(aborted("client closed"));
775      }
776      if let Some(p) = self.try_reserve() {
777        break p;
778      }
779      self.gate.wait_change(seen);
780    };
781    self.exec(permit, want, f)
782  }
783
784  /// Runs `f` for a caller that already holds a place and releases it at the end.
785  fn exec<T>(&self, _permit: Permit<'_>, want: Option<u64>, f: impl FnOnce(&Sock) -> Result<T>) -> Result<(T, u64)> {
786    let (sock, epoch) = self.sock_for(want)?;
787    let r = f(&sock);
788    if matches!(r, Err(Error::Io(_)) | Err(Error::Protocol(_))) {
789      sock.kill();
790    }
791    r.map(|v| (v, epoch))
792  }
793
794  /// The live connection, dialed when there is none.
795  fn sock_for(&self, want: Option<u64>) -> Result<(Arc<Sock>, u64)> {
796    let mut st = lock(&self.state);
797    if self.closed.load(Ordering::SeqCst) {
798      return Err(aborted("client closed"));
799    }
800    let alive = st.sock.as_ref().is_some_and(|s| !s.is_dead());
801    if want.is_some_and(|e| !alive || st.epoch != e) {
802      return Err(aborted("connection lost, file handle is gone"));
803    }
804    if !alive {
805      if let Some(old) = st.sock.take() {
806        old.finish();
807      }
808      let s = Sock::open(&self.path, &self.hub, self.timeout)?;
809      *lock(&self.live) = Some(s.clone());
810      let shaken = if self.closed.load(Ordering::SeqCst) {
811        Err(aborted("client closed"))
812      } else {
813        s.handshake(&self.name, self.max_inflight)
814      };
815      if let Err(e) = shaken {
816        s.finish();
817        return Err(e);
818      }
819      self.limit.store(s.limit(), Ordering::SeqCst);
820      self.hub.note_instance(s.instance());
821      st.sock = Some(s);
822      st.epoch += 1;
823      self.gate.bump();
824    }
825    Ok((st.sock.clone().expect("connected above"), st.epoch))
826  }
827
828  /// Refuses new calls and cuts the connection, which ends the calls in flight.
829  fn close_start(&self) {
830    self.closed.store(true, Ordering::SeqCst);
831    if let Some(s) = lock(&self.live).as_ref() {
832      s.kill();
833    }
834    self.gate.bump();
835  }
836
837  /// Stops the connection's threads.
838  fn close_finish(&self) {
839    let s = lock(&self.state).sock.take();
840    if let Some(s) = s {
841      s.finish();
842    }
843  }
844}
845
846pub(crate) struct Inner {
847  slots: Vec<Arc<Slot>>,
848  gate: Arc<Gate>,
849  next: AtomicUsize,
850  pub(crate) hub: Arc<Hub>,
851  dispatcher: Mutex<Option<JoinHandle<()>>>,
852}
853
854impl Inner {
855  /// Ends every connection, reader, watch and the dispatcher. Safe to call twice.
856  fn shutdown(&self) {
857    for s in &self.slots {
858      s.close_start();
859    }
860    for s in &self.slots {
861      s.close_finish();
862    }
863    self.hub.shutdown();
864    let d = lock(&self.dispatcher).take();
865    if let Some(h) = d {
866      join_other(h);
867    }
868    self.hub.clear_listeners();
869  }
870}
871
872impl Drop for Inner {
873  fn drop(&mut self) {
874    self.shutdown();
875  }
876}
877
878/// Client for one mount. Cheap to clone, clones share the pool, the listeners and the event
879/// dispatcher. The last clone to drop closes everything. `close` does it at once for all clones.
880#[derive(Clone)]
881pub struct Client {
882  pub(crate) inner: Arc<Inner>,
883}
884
885impl Client {
886  /// Connects with default options (4 connections).
887  pub fn connect(path: impl AsRef<Path>) -> Result<Client> {
888    Client::connect_with(path, Options::default())
889  }
890
891  /// Connects with the given options. The first connection and handshake happen now,
892  /// so a bad socket or version mismatch fails here.
893  pub fn connect_with(path: impl AsRef<Path>, opts: Options) -> Result<Client> {
894    let n = opts.pool_size.max(1);
895    let hub = Arc::new(Hub::new(opts.logger.clone()));
896    let gate = Arc::new(Gate::default());
897    let slots: Vec<_> = (0..n)
898      .map(|_| {
899        Arc::new(Slot {
900          path: path.as_ref().to_path_buf(),
901          name: opts.client_name.clone(),
902          timeout: opts.call_timeout,
903          max_inflight: opts.max_inflight.clamp(1, MAX_INFLIGHT_CEILING),
904          hub: hub.clone(),
905          gate: gate.clone(),
906          outstanding: AtomicUsize::new(0),
907          limit: AtomicUsize::new(1),
908          closed: AtomicBool::new(false),
909          live: Mutex::new(None),
910          state: Mutex::new(SlotState { sock: None, epoch: 0 }),
911        })
912      })
913      .collect();
914    let (h, guard) = (hub.clone(), hub.thread_guard());
915    let dispatcher = std::thread::Builder::new()
916      .name("mountos-dispatch".into())
917      .spawn(move || {
918        let _guard = guard;
919        h.dispatch_loop()
920      })?;
921    let inner = Inner {
922      slots,
923      gate,
924      next: AtomicUsize::new(0),
925      hub,
926      dispatcher: Mutex::new(Some(dispatcher)),
927    };
928    // On failure, dropping `inner` stops the dispatcher and any reader.
929    inner.slots[0].run(None, |_| Ok(()))?;
930    Ok(Client { inner: Arc::new(inner) })
931  }
932
933  /// Ends all connections, watches and background threads. Later calls fail with an I/O error
934  /// of kind `ConnectionAborted`. Dropping the last clone does the same.
935  pub fn close(&self) {
936    self.inner.shutdown();
937  }
938
939  pub(crate) fn pool_size(&self) -> usize {
940    self.inner.slots.len()
941  }
942
943  /// Runs `f` on the connection with the fewest requests outstanding that has room. The scan
944  /// starts at a rotating position so equal choices spread out. When all are full it waits for whichever frees first. Returns the slot
945  /// and epoch the call ran on, for callers that bind a handle to it.
946  pub(crate) fn run_any<T>(&self, f: impl FnOnce(&Sock) -> Result<T>) -> Result<(T, &Arc<Slot>, u64)> {
947    let inner = &*self.inner;
948    let n = inner.slots.len();
949    let mut waiter = None;
950    loop {
951      let seen = inner.gate.generation();
952      let start = inner.next.fetch_add(1, Ordering::Relaxed) % n;
953      let mut best: Option<(&Arc<Slot>, usize)> = None;
954      for i in 0..n {
955        let slot = &inner.slots[(start + i) % n];
956        let out = slot.outstanding.load(Ordering::SeqCst);
957        if out >= slot.limit.load(Ordering::SeqCst) {
958          continue;
959        }
960        if best.as_ref().is_none_or(|(_, k)| out < *k) {
961          best = Some((slot, out));
962        }
963      }
964      if let Some((slot, _)) = best {
965        // Losing the race for the place means another caller took it, so scan again.
966        if let Some(permit) = slot.try_reserve() {
967          drop(waiter);
968          let (v, epoch) = slot.exec(permit, None, f)?;
969          return Ok((v, slot, epoch));
970        }
971        continue;
972      }
973      if waiter.is_none() {
974        // Register, then scan once more: a place may have freed in between.
975        waiter = Some(inner.gate.wait_for_free());
976        continue;
977      }
978      inner.gate.wait_change(seen);
979    }
980  }
981
982  pub(crate) fn unary<R: Message + Default>(&self, m: u16, req: &impl Message) -> Result<R> {
983    Ok(self.run_any(|c| c.call_msg::<R>(m, req, &[]))?.0 .0)
984  }
985
986  /// Opens a file. `flags` combine `open_flag` values. The file stays bound to the connection that opened it.
987  pub fn open(&self, path: &str, flags: u32, mode: u32) -> Result<File> {
988    let ((r, _), slot, epoch) = self.run_any(|c| {
989      c.call_msg::<OpenResponse>(
990        method::OPEN,
991        &OpenRequest {
992          path: path.into(),
993          flags,
994          mode,
995        },
996        &[],
997      )
998    })?;
999    Ok(File::new(slot.clone(), r.handle, epoch))
1000  }
1001
1002  /// Sets times in nanoseconds since the epoch. `None` leaves that time unchanged.
1003  pub fn utimens(&self, path: &str, atime_ns: Option<i64>, mtime_ns: Option<i64>) -> Result<()> {
1004    self
1005      .unary::<Empty>(
1006        method::UTIMENS,
1007        &UtimensRequest {
1008          path: path.into(),
1009          atime_ns: atime_ns.unwrap_or(0),
1010          mtime_ns: mtime_ns.unwrap_or(0),
1011          set_atime: atime_ns.is_some(),
1012          set_mtime: mtime_ns.is_some(),
1013        },
1014      )
1015      .map(drop)
1016  }
1017
1018  /// Opens a directory for paged listing. The directory stays bound to the connection that opened it.
1019  pub fn opendir(&self, path: &str) -> Result<Dir> {
1020    let ((r, _), slot, epoch) =
1021      self.run_any(|c| c.call_msg::<OpendirResponse>(method::OPENDIR, &OpendirRequest { path: path.into() }, &[]))?;
1022    Ok(Dir::new(slot.clone(), r.handle, epoch))
1023  }
1024
1025  /// Lists up to `max_entries` entries with their attributes. Pass an empty `cursor` to start,
1026  /// then the returned cursor to continue. An empty returned cursor means the listing is complete.
1027  pub fn ls(&self, path: &str, max_entries: u32, cursor: &str) -> Result<(Vec<DirEntry>, String)> {
1028    let r: LsResponse = self.unary(
1029      method::LS,
1030      &LsRequest {
1031        path: path.into(),
1032        max_entries,
1033        cursor: cursor.into(),
1034      },
1035    )?;
1036    Ok((r.entries, r.next_cursor))
1037  }
1038}