1#[cfg(test)]
5mod tests;
6
7use std::cell::RefCell;
8use std::io::{ErrorKind, Read, Write};
9use std::os::fd::AsFd;
10use std::os::unix::net::UnixStream;
11use std::panic::{self, AssertUnwindSafe, Location};
12use std::path::PathBuf;
13use std::rc::Rc;
14use std::sync::Once;
15use std::time::{Duration, Instant};
16
17use calloop::timer::TimeoutAction;
18use calloop::{Interest, RegistrationToken};
19use crabshell_reactive::{Owner, ReadSignal, Signal, untrack};
20use crabshell_ui::Cx;
21
22use crate::Shared;
23use crate::lines::{Lines, panic_message};
24
25pub(crate) const MAX_OUTBOX: usize = 4 * 1024 * 1024;
28
29const HALF_OPEN: Duration = if cfg!(test) {
32 Duration::from_millis(300)
33} else {
34 Duration::from_secs(5)
35};
36
37pub(crate) struct Sockets(pub(crate) Rc<Shared>);
39
40#[derive(Clone)]
53pub struct Socket {
54 inner: Rc<Inner>,
55}
56
57struct Inner {
58 shared: Option<Rc<Shared>>,
60 path: PathBuf,
61 connected: Signal<bool>,
64 state: RefCell<State>,
65 served: Option<Owner>,
68}
69
70type OnLine = Rc<dyn Fn(&str)>;
72
73#[derive(Default)]
74struct State {
75 connection: Option<Connection>,
76 on_line: Option<OnLine>,
77 at: Option<(&'static str, &'static Location<'static>)>,
79 lines: Lines,
82 later: bool,
84}
85
86struct Connection {
87 stream: UnixStream,
88 reader: RegistrationToken,
90 writer: Option<RegistrationToken>,
93 outbox: Vec<u8>,
94 write_failed: Option<String>,
96 half_open: Option<RegistrationToken>,
99}
100
101pub(crate) fn socket(cx: &Cx, path: PathBuf) -> Socket {
103 let shared = cx.service::<Sockets>().map(|sockets| Rc::clone(&sockets.0));
104 if shared.is_none() {
105 static LOGGED: Once = Once::new();
106 LOGGED.call_once(|| {
107 tracing::warn!("cx.socket() needs the services the shell provides; it never connects");
108 });
109 }
110 let inner = Rc::new(Inner {
111 shared,
112 path,
113 connected: cx.signal(false),
114 state: RefCell::default(),
115 served: None,
116 });
117 cx.owner().signal(CloseOnDrop(Rc::clone(&inner)));
119 inner.connect();
120 Socket { inner }
121}
122
123pub(crate) fn accepted(
127 cx: &Cx,
128 shared: &Rc<Shared>,
129 path: PathBuf,
130 stream: UnixStream,
131) -> Option<Socket> {
132 let inner = Rc::new(Inner {
133 shared: Some(Rc::clone(shared)),
134 path,
135 connected: cx.signal(false),
136 state: RefCell::default(),
137 served: Some(cx.owner()),
138 });
139 cx.owner().signal(CloseOnDrop(Rc::clone(&inner)));
140 if let Err(error) = inner.attach(stream) {
141 let path = inner.path.display();
142 tracing::warn!(%path, %error, "can't serve a connection");
143 return None;
144 }
145 Some(Socket { inner })
146}
147
148struct CloseOnDrop(Rc<Inner>);
150
151impl Drop for CloseOnDrop {
152 fn drop(&mut self) {
153 let on_line = self.0.state.borrow_mut().on_line.take();
156 drop(on_line);
157 self.0.close();
158 }
159}
160
161impl Socket {
162 pub fn connected(&self) -> ReadSignal<bool> {
165 self.inner.connected.read_only()
166 }
167
168 #[track_caller]
176 pub fn on_line(self, on_line: impl Fn(&str) + 'static) -> Socket {
177 self.handle("on_line", "\n", Rc::new(on_line))
178 }
179
180 #[track_caller]
189 pub fn on_split(self, marker: &str, on_split: impl Fn(&str) + 'static) -> Socket {
190 self.handle("on_split", marker, Rc::new(on_split))
191 }
192
193 #[track_caller]
194 fn handle(self, name: &'static str, marker: &str, handler: OnLine) -> Socket {
195 let mut state = self.inner.state.borrow_mut();
196 state.on_line = Some(handler);
197 state.at = Some((name, Location::caller()));
198 state.lines.set_marker(marker);
199 let waiting = state.lines.waiting();
200 drop(state);
201 if waiting {
202 self.inner.deliver_later();
203 }
204 self
205 }
206
207 pub fn write(&self, data: impl AsRef<[u8]>) {
211 self.inner.write(data.as_ref());
212 }
213
214 pub fn reconnect(&self) {
219 if self.inner.served.is_some() {
220 let path = self.inner.path.display();
221 return tracing::warn!(%path, "a connection a server accepted can't reconnect");
222 }
223 self.inner.close();
224 self.inner.connect();
225 }
226
227 pub fn close(&self) {
230 self.inner.close();
231 }
232}
233
234impl Inner {
235 fn alive(&self) -> bool {
238 untrack(|| self.connected.try_with(|_| ()).is_some())
239 }
240
241 fn connect(self: &Rc<Self>) {
242 let event_loop = self.shared.as_ref().and_then(|shared| shared.event_loop());
243 if event_loop.is_none() || !self.alive() {
244 return;
245 }
246 if let Err(error) = UnixStream::connect(&self.path).and_then(|stream| self.attach(stream)) {
247 let path = self.path.display();
248 tracing::warn!(%path, %error, "can't connect to the socket");
249 }
250 }
251
252 fn attach(self: &Rc<Self>, stream: UnixStream) -> std::io::Result<()> {
254 let event_loop = self
255 .shared
256 .as_ref()
257 .and_then(|shared| shared.event_loop())
258 .ok_or_else(|| std::io::Error::other("the loop is gone"))?;
259 stream.set_nonblocking(true)?;
260 let fd = stream.as_fd().try_clone_to_owned()?;
261 let inner = Rc::clone(self);
262 let read = Box::new(move || inner.on_readable());
263 let reader = event_loop
264 .insert_fd(fd, Interest::READ, read)
265 .ok_or_else(|| std::io::Error::other("the loop refused it"))?;
266 let mut state = self.state.borrow_mut();
267 state.lines.reset();
268 state.connection = Some(Connection {
269 stream,
270 reader,
271 writer: None,
272 outbox: Vec::new(),
273 write_failed: None,
274 half_open: None,
275 });
276 drop(state);
277 self.connected.try_set(true);
278 Ok(())
279 }
280
281 fn on_readable(self: &Rc<Self>) {
283 let mut chunk = [0; 64 * 1024];
284 let mut state = self.state.borrow_mut();
285 let Some(connection) = state.connection.as_mut() else {
286 return;
287 };
288 let closed = match connection.stream.read(&mut chunk) {
289 Ok(0) if self.served.is_some() && !hung_up(&connection.stream) => {
292 drop(state);
293 self.half_close();
294 return self.deliver(true);
295 }
296 Ok(0) => Some(String::from("the other end closed it")),
297 Ok(read) => {
298 state.lines.append(&chunk[..read]);
299 None
300 }
301 Err(error) if matches!(error.kind(), ErrorKind::WouldBlock | ErrorKind::Interrupted) => {
302 return;
303 }
304 Err(error) => Some(error.to_string()),
306 };
307 drop(state);
308 if let Some(why) = &closed {
310 self.drop_connection(why);
311 }
312 self.deliver(closed.is_some());
313 }
314
315 fn half_close(self: &Rc<Self>) {
319 let Some(event_loop) = self.shared.as_ref().and_then(|shared| shared.event_loop()) else {
320 return;
321 };
322 let mut state = self.state.borrow_mut();
323 let Some(connection) = state.connection.as_mut() else {
324 return;
325 };
326 event_loop.remove(connection.reader);
328 let inner = Rc::clone(self);
329 let expire = move || {
330 if let Some(connection) = inner.state.borrow_mut().connection.as_mut() {
332 connection.half_open = None;
333 }
334 inner.drop_connection("the client got no reply in time");
335 TimeoutAction::Drop
336 };
337 connection.half_open = event_loop.insert_timer(Instant::now() + HALF_OPEN, Box::new(expire));
338 let unbounded = connection.half_open.is_none();
339 drop(state);
340 if unbounded {
341 self.drop_connection("the client stopped sending");
342 }
343 }
344
345 fn deliver_later(self: &Rc<Self>) {
348 let Some(event_loop) = self.shared.as_ref().and_then(|shared| shared.event_loop()) else {
349 return;
350 };
351 if std::mem::replace(&mut self.state.borrow_mut().later, true) {
352 return;
353 }
354 let inner = Rc::clone(self);
355 let later = move || {
356 inner.state.borrow_mut().later = false;
357 inner.deliver(false);
358 TimeoutAction::Drop
359 };
360 event_loop.insert_timer(Instant::now(), Box::new(later));
361 }
362
363 fn deliver(&self, done: bool) {
368 loop {
369 let (line, on_line, at) = {
370 let mut state = self.state.borrow_mut();
371 let line = match state.lines.next(&self.path.display()) {
372 Some(line) => line,
373 None if done => match state.lines.rest(&self.path.display()) {
374 Some(line) => line,
375 None => return,
376 },
377 None => return,
378 };
379 (line, state.on_line.clone(), state.at)
380 };
381 let Some(on_line) = on_line else {
382 continue;
383 };
384 if let Err(payload) = panic::catch_unwind(AssertUnwindSafe(|| on_line(&line))) {
385 let path = self.path.display();
386 let (name, at) = at.map_or(("on_line", String::new()), |(name, at)| {
387 (name, format!(" (called at {at})"))
388 });
389 let message = panic_message(payload.as_ref());
390 tracing::error!("socket {path} {name}{at} panicked: {message}");
391 let mut state = self.state.borrow_mut();
392 if state
394 .on_line
395 .as_ref()
396 .is_some_and(|now| Rc::ptr_eq(now, &on_line))
397 {
398 state.on_line = None;
399 }
400 }
401 }
402 }
403
404 fn write(self: &Rc<Self>, data: &[u8]) {
405 let mut state = self.state.borrow_mut();
406 let Some(connection) = state
407 .connection
408 .as_mut()
409 .filter(|connection| connection.write_failed.is_none())
410 else {
411 let path = self.path.display();
412 return tracing::warn!(%path, "the socket takes no writes now, so a write was dropped");
413 };
414 connection.outbox.extend_from_slice(data);
415 drop(state);
416 self.flush();
417 }
418
419 fn flush(self: &Rc<Self>) {
421 let Some(event_loop) = self.shared.as_ref().and_then(|shared| shared.event_loop()) else {
422 return;
423 };
424 let mut state = self.state.borrow_mut();
425 let Some(connection) = state.connection.as_mut() else {
426 return;
427 };
428 let mut broken = None;
429 while !connection.outbox.is_empty() {
430 match connection.stream.write(&connection.outbox) {
431 Ok(written) => {
432 connection.outbox.drain(..written);
433 }
434 Err(error) if error.kind() == ErrorKind::Interrupted => {}
435 Err(error) if error.kind() == ErrorKind::WouldBlock => break,
436 Err(error) => {
437 let path = self.path.display();
440 tracing::info!(%path, %error, "the socket takes no more writes");
441 connection.write_failed = Some(error.to_string());
442 connection.outbox = Vec::new();
443 if connection.half_open.is_some() {
445 broken = Some(error.to_string());
446 }
447 break;
448 }
449 }
450 }
451 if connection.outbox.len() > MAX_OUTBOX {
452 broken = Some(String::from("the other end reads nothing it is sent"));
453 }
454 if broken.is_none() && connection.outbox.is_empty() {
455 connection.outbox.shrink_to(64 * 1024);
457 if let Some(writer) = connection.writer.take() {
458 event_loop.remove(writer);
459 }
460 } else if broken.is_none() && connection.writer.is_none() {
461 match connection.stream.as_fd().try_clone_to_owned() {
462 Ok(fd) => {
463 let inner = Rc::clone(self);
464 let write = Box::new(move || inner.flush());
465 connection.writer = event_loop.insert_fd(fd, Interest::WRITE, write);
466 }
467 Err(error) => broken = Some(error.to_string()),
468 }
469 }
470 drop(state);
471 if let Some(why) = broken {
472 self.drop_connection(&why);
473 }
474 }
475
476 fn drop_connection(&self, why: &str) {
479 if self.state.borrow().connection.is_some() {
480 let path = self.path.display();
481 tracing::info!(%path, why, "the socket closed");
482 }
483 self.end(false);
484 }
485
486 fn close(&self) {
488 self.end(true);
489 }
490
491 fn end(&self, asked: bool) {
492 let connection = {
493 let mut state = self.state.borrow_mut();
494 if asked {
495 state.lines.reset();
496 }
497 state.connection.take()
498 };
499 if let (Some(connection), Some(event_loop)) = (
500 connection,
501 self.shared.as_ref().and_then(|shared| shared.event_loop()),
502 ) {
503 event_loop.remove(connection.reader);
504 for token in [connection.writer, connection.half_open]
505 .into_iter()
506 .flatten()
507 {
508 event_loop.remove(token);
509 }
510 if let Some(owner) = self.served.filter(|_| self.alive()) {
513 let gone = move || {
514 owner.dispose();
515 TimeoutAction::Drop
516 };
517 event_loop.insert_timer(Instant::now(), Box::new(gone));
518 }
519 }
520 if untrack(|| self.connected.try_with(|connected| *connected)) == Some(true) {
521 self.connected.try_set(false);
522 }
523 }
524}
525
526fn hung_up(stream: &UnixStream) -> bool {
529 use rustix::event::{PollFd, PollFlags, Timespec};
530
531 let mut fds = [PollFd::new(stream, PollFlags::empty())];
532 let now = Timespec::default();
533 rustix::event::poll(&mut fds, Some(&now)).is_err() || fds[0].revents().contains(PollFlags::HUP)
534}