crabshell_services/
socket.rs1#[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;
15
16use calloop::{Interest, RegistrationToken};
17use crabshell_reactive::{ReadSignal, Signal, untrack};
18use crabshell_ui::Cx;
19
20use crate::Shared;
21use crate::lines::{Lines, panic_message};
22
23pub(crate) const MAX_OUTBOX: usize = 4 * 1024 * 1024;
26
27pub(crate) struct Sockets(pub(crate) Rc<Shared>);
29
30#[derive(Clone)]
40pub struct Socket {
41 inner: Rc<Inner>,
42}
43
44struct Inner {
45 shared: Option<Rc<Shared>>,
47 path: PathBuf,
48 connected: Signal<bool>,
51 state: RefCell<State>,
52}
53
54type OnLine = Rc<dyn Fn(&str)>;
56
57#[derive(Default)]
58struct State {
59 connection: Option<Connection>,
60 on_line: Option<OnLine>,
61 at: Option<&'static Location<'static>>,
63 lines: Lines,
64 serial: u64,
67}
68
69struct Connection {
70 stream: UnixStream,
71 reader: RegistrationToken,
73 writer: Option<RegistrationToken>,
76 outbox: Vec<u8>,
77 write_failed: Option<String>,
79}
80
81pub(crate) fn socket(cx: &Cx, path: PathBuf) -> Socket {
83 let shared = cx.service::<Sockets>().map(|sockets| Rc::clone(&sockets.0));
84 if shared.is_none() {
85 static LOGGED: Once = Once::new();
86 LOGGED.call_once(|| {
87 tracing::warn!("cx.socket() needs the services the shell provides; it never connects");
88 });
89 }
90 let inner = Rc::new(Inner {
91 shared,
92 path,
93 connected: cx.signal(false),
94 state: RefCell::default(),
95 });
96 cx.owner().signal(CloseOnDrop(Rc::clone(&inner)));
98 inner.connect();
99 Socket { inner }
100}
101
102struct CloseOnDrop(Rc<Inner>);
104
105impl Drop for CloseOnDrop {
106 fn drop(&mut self) {
107 let on_line = self.0.state.borrow_mut().on_line.take();
110 drop(on_line);
111 self.0.close();
112 }
113}
114
115impl Socket {
116 pub fn connected(&self) -> ReadSignal<bool> {
119 self.inner.connected.read_only()
120 }
121
122 #[track_caller]
130 pub fn on_line(self, on_line: impl Fn(&str) + 'static) -> Socket {
131 let mut state = self.inner.state.borrow_mut();
132 state.on_line = Some(Rc::new(on_line));
133 state.at = Some(Location::caller());
134 drop(state);
135 self
136 }
137
138 pub fn write(&self, data: impl AsRef<[u8]>) {
142 self.inner.write(data.as_ref());
143 }
144
145 pub fn reconnect(&self) {
149 self.inner.close();
150 self.inner.connect();
151 }
152
153 pub fn close(&self) {
156 self.inner.close();
157 }
158}
159
160impl Inner {
161 fn alive(&self) -> bool {
164 untrack(|| self.connected.try_with(|_| ()).is_some())
165 }
166
167 fn connect(self: &Rc<Self>) {
168 let Some(event_loop) = self.shared.as_ref().and_then(|shared| shared.event_loop()) else {
169 return;
170 };
171 if !self.alive() {
172 return;
173 }
174 let opened = UnixStream::connect(&self.path).and_then(|stream| {
175 stream.set_nonblocking(true)?;
176 let fd = stream.as_fd().try_clone_to_owned()?;
177 Ok((stream, fd))
178 });
179 let (stream, fd) = match opened {
180 Ok(opened) => opened,
181 Err(error) => {
182 let path = self.path.display();
183 return tracing::warn!(%path, %error, "can't connect to the socket");
184 }
185 };
186 let inner = Rc::clone(self);
187 let read = Box::new(move || inner.on_readable());
188 let Some(reader) = event_loop.insert_fd(fd, Interest::READ, read) else {
189 return;
190 };
191 let mut state = self.state.borrow_mut();
192 state.lines = Lines::default();
193 state.serial += 1;
194 state.connection = Some(Connection {
195 stream,
196 reader,
197 writer: None,
198 outbox: Vec::new(),
199 write_failed: None,
200 });
201 drop(state);
202 self.connected.try_set(true);
203 }
204
205 fn on_readable(self: &Rc<Self>) {
207 let mut chunk = [0; 64 * 1024];
208 let mut state = self.state.borrow_mut();
209 let Some(connection) = state.connection.as_mut() else {
210 return;
211 };
212 let (lines, closed) = match connection.stream.read(&mut chunk) {
213 Ok(0) => {
214 let last = state.lines.finish();
215 (
216 last.into_iter().collect(),
217 Some(String::from("the other end closed it")),
218 )
219 }
220 Ok(read) => (state.lines.push(&chunk[..read], &self.path.display()), None),
221 Err(error) if matches!(error.kind(), ErrorKind::WouldBlock | ErrorKind::Interrupted) => {
222 return;
223 }
224 Err(error) => {
226 let last = state.lines.finish();
227 (last.into_iter().collect(), Some(error.to_string()))
228 }
229 };
230 let serial = state.serial;
231 drop(state);
232 if let Some(why) = closed {
234 self.drop_connection(&why);
235 }
236 self.deliver(lines, serial);
237 }
238
239 fn deliver(&self, lines: Vec<String>, serial: u64) {
242 let (on_line, at) = {
243 let state = self.state.borrow();
244 (state.on_line.clone(), state.at)
245 };
246 let Some(on_line) = on_line else {
247 return;
248 };
249 for line in lines {
250 if self.state.borrow().serial != serial {
251 return;
252 }
253 if let Err(payload) = panic::catch_unwind(AssertUnwindSafe(|| on_line(&line))) {
254 let path = self.path.display();
255 let at = at.map_or_else(String::new, |at| format!(" (called at {at})"));
256 let message = panic_message(payload.as_ref());
257 tracing::error!("socket {path} on_line{at} panicked: {message}");
258 let mut state = self.state.borrow_mut();
259 if state
261 .on_line
262 .as_ref()
263 .is_some_and(|now| Rc::ptr_eq(now, &on_line))
264 {
265 state.on_line = None;
266 }
267 return;
268 }
269 }
270 }
271
272 fn write(self: &Rc<Self>, data: &[u8]) {
273 let mut state = self.state.borrow_mut();
274 let Some(connection) = state
275 .connection
276 .as_mut()
277 .filter(|connection| connection.write_failed.is_none())
278 else {
279 let path = self.path.display();
280 return tracing::warn!(%path, "the socket takes no writes now, so a write was dropped");
281 };
282 connection.outbox.extend_from_slice(data);
283 drop(state);
284 self.flush();
285 }
286
287 fn flush(self: &Rc<Self>) {
289 let Some(event_loop) = self.shared.as_ref().and_then(|shared| shared.event_loop()) else {
290 return;
291 };
292 let mut state = self.state.borrow_mut();
293 let Some(connection) = state.connection.as_mut() else {
294 return;
295 };
296 let mut broken = None;
297 while !connection.outbox.is_empty() {
298 match connection.stream.write(&connection.outbox) {
299 Ok(written) => {
300 connection.outbox.drain(..written);
301 }
302 Err(error) if error.kind() == ErrorKind::Interrupted => {}
303 Err(error) if error.kind() == ErrorKind::WouldBlock => break,
304 Err(error) => {
305 let path = self.path.display();
308 tracing::info!(%path, %error, "the socket takes no more writes");
309 connection.write_failed = Some(error.to_string());
310 connection.outbox = Vec::new();
311 break;
312 }
313 }
314 }
315 if connection.outbox.len() > MAX_OUTBOX {
316 broken = Some(String::from("the other end reads nothing it is sent"));
317 }
318 if broken.is_none() && connection.outbox.is_empty() {
319 connection.outbox.shrink_to(64 * 1024);
321 if let Some(writer) = connection.writer.take() {
322 event_loop.remove(writer);
323 }
324 } else if broken.is_none() && connection.writer.is_none() {
325 match connection.stream.as_fd().try_clone_to_owned() {
326 Ok(fd) => {
327 let inner = Rc::clone(self);
328 let write = Box::new(move || inner.flush());
329 connection.writer = event_loop.insert_fd(fd, Interest::WRITE, write);
330 }
331 Err(error) => broken = Some(error.to_string()),
332 }
333 }
334 drop(state);
335 if let Some(why) = broken {
336 self.drop_connection(&why);
337 }
338 }
339
340 fn drop_connection(&self, why: &str) {
343 if self.state.borrow().connection.is_some() {
344 let path = self.path.display();
345 tracing::info!(%path, why, "the socket closed");
346 }
347 self.end(false);
348 }
349
350 fn close(&self) {
352 self.end(true);
353 }
354
355 fn end(&self, asked: bool) {
356 let connection = {
357 let mut state = self.state.borrow_mut();
358 state.lines = Lines::default();
359 if asked {
360 state.serial += 1;
361 }
362 state.connection.take()
363 };
364 if let (Some(connection), Some(event_loop)) = (
365 connection,
366 self.shared.as_ref().and_then(|shared| shared.event_loop()),
367 ) {
368 event_loop.remove(connection.reader);
369 if let Some(writer) = connection.writer {
370 event_loop.remove(writer);
371 }
372 }
373 if untrack(|| self.connected.try_with(|connected| *connected)) == Some(true) {
374 self.connected.try_set(false);
375 }
376 }
377}