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