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#[derive(Clone)]
23#[non_exhaustive]
24pub struct Options {
25 pub pool_size: usize,
27 pub client_name: String,
28 pub logger: Option<Logger>,
32 pub call_timeout: Duration,
39 pub max_inflight: usize,
43}
44
45pub const DEFAULT_MAX_INFLIGHT: usize = 32;
47const MAX_INFLIGHT_CEILING: usize = 1024;
48
49pub 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 pub fn with_pool_size(mut self, pool_size: usize) -> Options {
67 self.pool_size = pool_size;
68 self
69 }
70
71 pub fn with_logger(mut self, logger: Logger) -> Options {
73 self.logger = Some(logger);
74 self
75 }
76
77 pub fn with_call_timeout(mut self, call_timeout: Duration) -> Options {
79 self.call_timeout = call_timeout;
80 self
81 }
82
83 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
106fn join_other(h: JoinHandle<()>) {
108 if h.thread().id() != std::thread::current().id() {
109 let _ = h.join();
110 }
111}
112
113fn 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
129const 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
141fn 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
153struct Waiter {
155 method: u16,
156 watch: Option<Arc<Chan>>,
158 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 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 abandoned: HashMap<u64, u16>,
197 next_id: u64,
198 end: Option<Error>,
200}
201
202struct Info {
204 max_payload: usize,
205 limit: usize,
207 caps: Option<HashSet<String>>,
210 instance: u64,
212}
213
214pub(crate) struct Sock {
218 id: u64,
219 hub: Arc<Hub>,
220 timeout: Duration,
222 writer: Mutex<UnixStream>,
223 killer: UnixStream,
225 core: Mutex<Core>,
226 dead: AtomicBool,
227 closing: AtomicBool,
229 saw_closing: AtomicBool,
231 info: OnceLock<Info>,
232 reader: Mutex<Option<JoinHandle<()>>>,
233 releasers: Mutex<Vec<JoinHandle<()>>>,
235}
236
237impl Sock {
238 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 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 pub(crate) fn kill(&self) {
347 self.closing.store(true, Ordering::SeqCst);
348 self.fail(aborted("connection closed"), false);
349 }
350
351 fn kill_with(&self, e: &Error) {
353 self.closing.store(true, Ordering::SeqCst);
354 self.fail(e.dup(), false);
355 }
356
357 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 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 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 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 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 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 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 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 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 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 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
615fn 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 epoch: u64,
662}
663
664#[derive(Default)]
667pub(crate) struct Gate {
668 generation: Mutex<u64>,
669 cv: Condvar,
670 waiting: AtomicUsize,
672}
673
674struct 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 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
719struct 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
729pub(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 outstanding: AtomicUsize,
740 limit: AtomicUsize,
742 closed: AtomicBool,
743 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 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 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 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 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 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 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#[derive(Clone)]
881pub struct Client {
882 pub(crate) inner: Arc<Inner>,
883}
884
885impl Client {
886 pub fn connect(path: impl AsRef<Path>) -> Result<Client> {
888 Client::connect_with(path, Options::default())
889 }
890
891 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 inner.slots[0].run(None, |_| Ok(()))?;
930 Ok(Client { inner: Arc::new(inner) })
931 }
932
933 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 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 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 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 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 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 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 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}