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#[derive(Clone)]
22#[non_exhaustive]
23pub struct Options {
24 pub pool_size: usize,
26 pub client_name: String,
27 pub logger: Option<Logger>,
31 pub call_timeout: Duration,
38 pub max_inflight: usize,
42}
43
44pub const DEFAULT_MAX_INFLIGHT: usize = 32;
46const MAX_INFLIGHT_CEILING: usize = 1024;
47
48pub 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 pub fn with_pool_size(mut self, pool_size: usize) -> Options {
66 self.pool_size = pool_size;
67 self
68 }
69
70 pub fn with_logger(mut self, logger: Logger) -> Options {
72 self.logger = Some(logger);
73 self
74 }
75
76 pub fn with_call_timeout(mut self, call_timeout: Duration) -> Options {
78 self.call_timeout = call_timeout;
79 self
80 }
81
82 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
105fn join_other(h: JoinHandle<()>) {
107 if h.thread().id() != std::thread::current().id() {
108 let _ = h.join();
109 }
110}
111
112fn 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
126fn 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
138struct Waiter {
140 method: u16,
141 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 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 abandoned: HashMap<u64, u16>,
180 next_id: u64,
181 end: Option<Error>,
183}
184
185struct Info {
187 max_payload: usize,
188 limit: usize,
190 caps: Option<HashSet<String>>,
193 instance: u64,
195}
196
197pub(crate) struct Sock {
201 id: u64,
202 hub: Arc<Hub>,
203 timeout: Duration,
205 writer: Mutex<UnixStream>,
206 killer: UnixStream,
208 core: Mutex<Core>,
209 dead: AtomicBool,
210 closing: AtomicBool,
212 saw_closing: AtomicBool,
214 info: OnceLock<Info>,
215 reader: Mutex<Option<JoinHandle<()>>>,
216 releasers: Mutex<Vec<JoinHandle<()>>>,
218}
219
220impl Sock {
221 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 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 pub(crate) fn kill(&self) {
329 self.closing.store(true, Ordering::SeqCst);
330 self.fail(aborted("connection closed"), false);
331 }
332
333 fn kill_with(&self, e: &Error) {
335 self.closing.store(true, Ordering::SeqCst);
336 self.fail(e.dup(), false);
337 }
338
339 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 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 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 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 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 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 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 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 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
557fn 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 epoch: u64,
604}
605
606#[derive(Default)]
609pub(crate) struct Gate {
610 generation: Mutex<u64>,
611 cv: Condvar,
612 waiting: AtomicUsize,
614}
615
616struct 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 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
661struct 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
671pub(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 outstanding: AtomicUsize,
682 limit: AtomicUsize,
684 closed: AtomicBool,
685 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 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 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 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 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 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 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#[derive(Clone)]
823pub struct Client {
824 pub(crate) inner: Arc<Inner>,
825}
826
827impl Client {
828 pub fn connect(path: impl AsRef<Path>) -> Result<Client> {
830 Client::connect_with(path, Options::default())
831 }
832
833 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 inner.slots[0].run(None, |_| Ok(()))?;
872 Ok(Client { inner: Arc::new(inner) })
873 }
874
875 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 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 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 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 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 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 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 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}