1use std::collections::HashMap;
2use std::sync::Arc;
3use std::sync::atomic::{AtomicBool, Ordering};
4
5use muxio_core::rpc::rpc_internals::RpcStreamEvent;
6use muxio_rpc_service::prebuffered::RpcMethodPrebuffered;
7use muxio_rpc_service_caller::prebuffered::RpcCallPrebuffered;
8use muxio_rpc_service_endpoint::{RpcServiceEndpointInterface, StreamResponder};
9use muxio_tokio_rpc_ipc_server::{RpcIpcConnectionContextHandle, RpcIpcServer, RpcIpcServerEvent};
10use portable_pty::PtySize;
11use tokio::sync::{Mutex, Notify, RwLock, mpsc, oneshot};
12
13use term_session_muxio_service_definitions::{
14 Attach, ChannelInfo, ChannelName, ClientInfo, CloseSession, KillChannel, KillClient,
15 ListChannels, ListChannelsResponse, OnPtyResized, RPC_ERROR_SHUTTING_DOWN,
16 RPC_ERROR_UNATTACHED, ResizePty, STREAM_INPUT_METHOD_ID, SUBSCRIBE_OUTPUT_METHOD_ID,
17 SessionInfo, ShutdownGateway, Spawn, WriteInput,
18};
19use term_wm_pty_engine::PtyStatus;
20
21use crate::session::Session;
22
23const SESSION_ID: u64 = 1;
25const INPUT_CHANNEL_CAPACITY: usize = 128;
27
28const SESSION_EXIT_FLUSH_GRACE: std::time::Duration = std::time::Duration::from_millis(100);
31
32const SESSION_EXIT_POLL_INTERVAL: std::time::Duration = std::time::Duration::from_millis(100);
35
36const SIGKILL_GRACE: std::time::Duration = std::time::Duration::from_millis(500);
38
39#[cfg(unix)]
43const SIGTERM: i32 = libc::SIGTERM;
44#[cfg(not(unix))]
45const SIGTERM: i32 = 15;
46#[cfg(unix)]
48const SIGKILL: i32 = libc::SIGKILL;
49#[cfg(not(unix))]
50#[allow(dead_code)]
51const SIGKILL: i32 = 9;
52
53const SHUTDOWN_FLUSH_GRACE_MS: u64 = 50;
58
59#[derive(Clone)]
62enum ConnState {
63 Unattached,
64 Attached(ChannelName),
65}
66
67#[derive(Clone)]
68struct ConnEntry {
69 handle: RpcIpcConnectionContextHandle,
70 state: ConnState,
71 hostname: String,
72 connected_at_unix: u64,
73 pid: u64,
75}
76
77#[derive(Clone)]
78struct ClientEntry {
79 caller: Option<RpcIpcConnectionContextHandle>,
80 hostname: String,
81 connected_at_unix: u64,
82 pid: u64,
83 cols: u16,
84 rows: u16,
85}
86
87struct SubscriberEntry {
88 conn_id: usize,
89 respond: StreamResponder,
90}
91
92struct ChannelState {
95 session: Option<Session>,
96 clients: HashMap<usize, ClientEntry>,
97 subscribers: Vec<SubscriberEntry>,
98 notify: Arc<Notify>,
99 created_at_unix: u64,
101 cmd: Vec<String>,
103 input_tx: mpsc::Sender<Vec<u8>>,
104 kill_pending: bool,
107 is_reaped: bool,
110}
111
112struct ServerState {
118 conns: RwLock<HashMap<usize, ConnEntry>>,
119 channels: RwLock<HashMap<ChannelName, Arc<Mutex<ChannelState>>>>,
120 is_shutting_down: AtomicBool,
121}
122
123type SharedState = Arc<ServerState>;
124
125fn rpc_err(message: &str) -> Box<dyn std::error::Error + Send + Sync> {
126 Box::new(std::io::Error::other(message.to_string()))
127}
128
129fn boxed_io(e: std::io::Error) -> Box<dyn std::error::Error + Send + Sync> {
131 Box::new(e)
132}
133
134fn now_unix() -> u64 {
136 std::time::SystemTime::now()
137 .duration_since(std::time::UNIX_EPOCH)
138 .map(|d| d.as_secs())
139 .unwrap_or(0)
140}
141
142impl ChannelState {
143 fn new(cmd: Vec<String>, input_tx: mpsc::Sender<Vec<u8>>, notify: Arc<Notify>) -> Self {
144 Self {
145 session: None,
146 clients: HashMap::new(),
147 subscribers: Vec::new(),
148 notify,
149 created_at_unix: now_unix(),
150 cmd,
151 input_tx,
152 kill_pending: false,
153 is_reaped: false,
154 }
155 }
156
157 fn set_session(&mut self, mut session: Session) {
160 let n = self.notify.clone();
161 session.set_status_callback(Some(Box::new(move |status| {
162 if matches!(status, PtyStatus::Wakeup | PtyStatus::Exited) {
163 n.notify_one();
164 }
165 })));
166 self.session = Some(session);
167 self.notify.notify_one();
170 }
171
172 fn request_session_kill(&mut self, signal: i32) {
177 let _ = &signal;
178 if let Some(session) = self.session.as_mut() {
179 #[cfg(unix)]
180 let _ = session.pty.signal_process_group(signal);
181 #[cfg(not(unix))]
182 let _ = session.pty.kill_child();
183 }
184 self.kill_pending = true;
185 self.notify.notify_one();
186 }
187
188 fn finalize_subscribers(&mut self) {
191 if let Some(session) = self.session.as_mut() {
192 let raw = session.read_output();
193 if !raw.is_empty() {
194 for sub in &self.subscribers {
195 sub.respond.respond(raw.clone(), false);
196 }
197 }
198 }
199 for sub in &self.subscribers {
200 sub.respond.respond(Vec::new(), true);
201 }
202 self.subscribers.clear();
203 }
204
205 fn recalculate_pty_size(&mut self) {
213 let Some(session) = self.session.as_mut() else {
214 return;
215 };
216 let Some(min_cols) = self
217 .clients
218 .values()
219 .map(|c| c.cols)
220 .filter(|&c| c != u16::MAX)
221 .min()
222 else {
223 return;
224 };
225 let Some(min_rows) = self
226 .clients
227 .values()
228 .map(|c| c.rows)
229 .filter(|&r| r != u16::MAX)
230 .min()
231 else {
232 return;
233 };
234 let size = PtySize {
235 rows: min_rows,
236 cols: min_cols,
237 pixel_width: 0,
238 pixel_height: 0,
239 };
240 let _ = session.pty.resize(size);
241 session.cols = min_cols;
242 session.rows = min_rows;
243 }
244
245 fn notify_clients(&self, clients: &[ClientEntry], cols: u16, rows: u16) {
248 for client in clients {
249 let Some(caller) = client.caller.clone() else {
250 continue;
251 };
252 tokio::spawn(async move {
253 if let Err(e) = OnPtyResized::call(&caller, (cols, rows)).await {
254 tracing::debug!(error = ?e, "Failed to deliver OnPtyResized notification");
255 }
256 });
257 }
258 }
259
260 fn to_info(&self, name: &ChannelName) -> ChannelInfo {
261 let session = self.session.as_ref().map(|s| SessionInfo {
262 id: s.id,
263 cols: s.cols,
264 rows: s.rows,
265 exited: s.exited,
266 exit_code: s.exit_code,
267 title: s.title.clone().unwrap_or_default(),
268 });
269 let clients = self
270 .clients
271 .iter()
272 .map(|(conn_id, c)| ClientInfo {
273 conn_id: *conn_id,
274 pid: c.pid,
275 hostname: c.hostname.clone(),
276 connected_at_unix: c.connected_at_unix,
277 cols: c.cols,
278 rows: c.rows,
279 })
280 .collect();
281 ChannelInfo {
282 name: name.to_string(),
283 created_at_unix: self.created_at_unix,
284 session,
285 clients,
286 }
287 }
288}
289
290async fn bound_channel(state: &ServerState, conn_id: usize) -> Option<ChannelName> {
292 let conns = state.conns.read().await;
293 match conns.get(&conn_id)?.state {
294 ConnState::Attached(ref name) => Some(name.clone()),
295 ConnState::Unattached => None,
296 }
297}
298
299async fn resolve_channel(
302 state: &ServerState,
303 name: &ChannelName,
304) -> Option<Arc<Mutex<ChannelState>>> {
305 let channels = state.channels.read().await;
306 channels.get(name).cloned()
307}
308
309async fn get_or_create_channel(
313 state: &SharedState,
314 name: &ChannelName,
315) -> Arc<Mutex<ChannelState>> {
316 {
317 let channels = state.channels.read().await;
318 if let Some(existing) = channels.get(name) {
319 let arc = existing.clone();
320 drop(channels);
321 let is_reaped = arc.lock().await.is_reaped;
322 if !is_reaped {
323 return arc;
324 }
325 }
326 }
327
328 let mut channels = state.channels.write().await;
329 if let Some(existing) = channels.get(name) {
333 let arc = existing.clone();
334 let is_reaped = arc.lock().await.is_reaped;
335 if !is_reaped {
336 return arc;
337 }
338 }
339 let (input_tx, input_rx) = mpsc::channel::<Vec<u8>>(INPUT_CHANNEL_CAPACITY);
340 let notify = Arc::new(Notify::new());
341 let channel = Arc::new(Mutex::new(ChannelState::new(Vec::new(), input_tx, notify)));
342 let ch = Arc::clone(&channel);
343 tokio::spawn(async move {
344 let mut input_rx = input_rx;
345 while let Some(data) = input_rx.recv().await {
346 let writer = {
347 let guard = ch.lock().await;
348 guard.session.as_ref().map(|s| s.pty.writer_handle())
349 };
350 if let Some(writer) = writer {
351 let _ = tokio::task::spawn_blocking(move || writer.write_bytes(&data)).await;
352 }
353 }
354 });
357
358 {
362 let st = Arc::clone(state);
363 let ch = Arc::clone(&channel);
364 let notify = {
365 let locked = ch.lock().await;
366 locked.notify.clone()
367 };
368 let name_for_task = name.clone();
369 tokio::spawn(async move {
370 loop {
371 tokio::select! {
372 _ = notify.notified() => {}
373 _ = tokio::time::sleep(SESSION_EXIT_POLL_INTERVAL) => {}
374 }
375 let mut guard = ch.lock().await;
376 if guard.is_reaped {
377 break;
378 }
379 if guard.subscribers.is_empty() {
380 if let Some(session) = guard.session.as_mut() {
381 session.sync_screen();
382 if session.check_exited() {
383 tracing::info!(channel = %name_for_task, "Session exited");
384 guard.session = None;
385 guard.kill_pending = false;
386 }
387 }
388 } else {
389 let (raw, exited, code) = {
390 let Some(session) = guard.session.as_mut() else {
391 for sub in &guard.subscribers {
393 sub.respond.respond(Vec::new(), true);
394 }
395 guard.subscribers.clear();
396 guard.notify.notify_one();
397 continue;
398 };
399 let raw = session.read_output();
400 let exited = session.check_exited();
401 let code = session.exit_code;
402 (raw, exited, code)
403 };
404 if !raw.is_empty() {
405 for sub in &guard.subscribers {
406 sub.respond.respond(raw.clone(), false);
407 }
408 }
409 if exited {
410 tracing::info!(channel = %name_for_task, "Session exited with code {:?}", code);
411 for sub in &guard.subscribers {
412 sub.respond.respond(Vec::new(), true);
413 }
414 guard.subscribers.clear();
415 guard.session = None;
416 guard.kill_pending = false;
417 guard.notify.notify_one();
418 }
419 }
420 let should_reap = guard.session.is_none() && guard.clients.is_empty();
421 drop(guard);
422
423 if should_reap {
424 let mut channels = st.channels.write().await;
427 if let Some(arc) = channels.get(&name_for_task) {
428 let mut locked = arc.lock().await;
429 if locked.session.is_none() && locked.clients.is_empty() {
430 locked.is_reaped = true;
431 drop(locked);
432 channels.remove(&name_for_task);
433 tracing::info!(channel = %name_for_task, "Reaped idle channel");
434 }
435 }
436 }
437 }
442 });
443 }
444
445 channels.insert(name.clone(), Arc::clone(&channel));
446 channel
447}
448
449async fn evict_conn(state: &ServerState, conn_id: usize) {
452 let channel = {
453 let mut conns = state.conns.write().await;
454 let entry = conns.remove(&conn_id);
455 entry.and_then(|e| match e.state {
456 ConnState::Attached(name) => Some(name),
457 ConnState::Unattached => None,
458 })
459 };
460 let Some(channel) = channel else {
461 return;
462 };
463 let Some(ch) = resolve_channel(state, &channel).await else {
464 return;
465 };
466 let mut guard = ch.lock().await;
467 guard.clients.remove(&conn_id);
468 guard.subscribers.retain(|s| s.conn_id != conn_id);
469 guard.recalculate_pty_size();
470 let session_size = guard.session.as_ref().map(|s| (s.cols, s.rows));
473 let targets: Vec<ClientEntry> = guard.clients.values().cloned().collect();
474 drop(guard);
475 let Some((ncols, nrows)) = session_size else {
476 return;
477 };
478 if let Some(ch) = resolve_channel(state, &channel).await {
481 let guard = ch.lock().await;
482 guard.notify_clients(&targets, ncols, nrows);
483 }
484}
485
486async fn spawn_kill_escalation(
495 state: &SharedState,
496 name: &ChannelName,
497) -> tokio::task::JoinHandle<()> {
498 let state = Arc::clone(state);
499 let name = name.clone();
500 tokio::spawn(async move {
501 tokio::time::sleep(SIGKILL_GRACE).await;
502 let Some(ch) = resolve_channel(&state, &name).await else {
503 return;
504 };
505 let mut guard = ch.lock().await;
506 if !guard.kill_pending {
507 return;
508 }
509 let alive = guard
511 .session
512 .as_ref()
513 .is_some_and(|s| !s.exited && s.pty.reader_is_alive());
514 guard.kill_pending = false;
515 if !alive {
516 return;
517 }
518 if let Some(session) = guard.session.as_mut() {
519 #[cfg(unix)]
520 let _ = session.pty.signal_process_group(SIGKILL);
521 #[cfg(not(unix))]
522 let _ = session.pty.kill_child();
523 }
524 })
525}
526
527pub async fn run_gateway(
530 gateway: ChannelName,
531) -> Result<i32, Box<dyn std::error::Error + Send + Sync>> {
532 let socket_name = gateway.to_string();
533 let state: SharedState = Arc::new(ServerState {
534 conns: RwLock::new(HashMap::new()),
535 channels: RwLock::new(HashMap::new()),
536 is_shutting_down: AtomicBool::new(false),
537 });
538
539 let (event_tx, mut event_rx) = mpsc::unbounded_channel();
540 let server = RpcIpcServer::new(Some(event_tx));
541 let endpoint = server.endpoint();
542
543 let st = Arc::clone(&state);
545 endpoint
546 .register_prebuffered(Attach::METHOD_ID, move |payload, ctx| {
547 let state = Arc::clone(&st);
548 async move {
549 if state.is_shutting_down.load(Ordering::SeqCst) {
550 return Err(rpc_err(RPC_ERROR_SHUTTING_DOWN));
551 }
552 let (channel_str, hostname, pid) = Attach::decode_request(&payload)?;
553 let name = ChannelName::parse(&channel_str).map_err(|e| rpc_err(&e))?;
554 let _channel = get_or_create_channel(&state, &name).await;
555 let conn_id = ctx.conn_id;
556 let mut conns = state.conns.write().await;
557 let entry = conns.entry(conn_id).or_insert_with(|| ConnEntry {
561 handle: RpcIpcConnectionContextHandle(ctx.clone()),
562 state: ConnState::Unattached,
563 hostname: String::new(),
564 connected_at_unix: now_unix(),
565 pid: 0,
566 });
567 entry.state = ConnState::Attached(name);
568 entry.hostname = hostname;
569 entry.connected_at_unix = now_unix();
570 entry.pid = pid;
571 Attach::encode_response(conn_id).map_err(boxed_io)
572 }
573 })
574 .await
575 .map_err(|e| format!("register Attach: {e:?}"))?;
576
577 let st = Arc::clone(&state);
579 endpoint
580 .register_prebuffered(Spawn::METHOD_ID, move |payload, ctx| {
581 let state = Arc::clone(&st);
582 async move {
583 if state.is_shutting_down.load(Ordering::SeqCst) {
584 return Err(rpc_err(RPC_ERROR_SHUTTING_DOWN));
585 }
586 let (cmd, cols, rows) = Spawn::decode_request(&payload)?;
587 let channel = bound_channel(state.as_ref(), ctx.conn_id).await;
588 let Some(channel) = channel else {
589 return Err(rpc_err(RPC_ERROR_UNATTACHED));
590 };
591 let ch = get_or_create_channel(&state, &channel).await;
592 let conn_meta = {
595 let conns = state.conns.read().await;
596 conns.get(&ctx.conn_id).map(|c| {
597 (
598 c.handle.clone(),
599 c.hostname.clone(),
600 c.connected_at_unix,
601 c.pid,
602 )
603 })
604 };
605 let mut guard = ch.lock().await;
606 let entry = guard
607 .clients
608 .entry(ctx.conn_id)
609 .or_insert_with(|| ClientEntry {
610 caller: conn_meta.as_ref().map(|(h, _, _, _)| h.clone()),
611 hostname: conn_meta
612 .as_ref()
613 .map(|(_, n, _, _)| n.clone())
614 .unwrap_or_default(),
615 connected_at_unix: conn_meta.as_ref().map(|(_, _, t, _)| *t).unwrap_or(0),
616 pid: conn_meta.as_ref().map(|(_, _, _, p)| *p).unwrap_or(0),
617 cols,
618 rows,
619 });
620 entry.cols = cols;
621 entry.rows = rows;
622
623 if guard.session.as_ref().is_some_and(|s| !s.exited) {
625 guard.recalculate_pty_size();
626 let session = guard.session.as_ref().unwrap();
627 let (ncols, nrows) = (session.cols, session.rows);
628 let targets: Vec<ClientEntry> = guard.clients.values().cloned().collect();
629 let id = session.id;
630 let cols = session.cols;
631 let rows = session.rows;
632 drop(guard);
633 if let Some(ch) = resolve_channel(state.as_ref(), &channel).await {
634 let g = ch.lock().await;
635 g.notify_clients(&targets, ncols, nrows);
636 }
637 return Spawn::encode_response((id, cols, rows)).map_err(boxed_io);
638 }
639
640 let effective_cmd = if let Some(c) = cmd
643 && !c.is_empty()
644 {
645 guard.cmd = c.clone();
646 Some(c)
647 } else if !guard.cmd.is_empty() {
648 Some(guard.cmd.clone())
649 } else {
650 None
651 };
652 let id = SESSION_ID;
653 let session = Session::spawn(id, effective_cmd, cols, rows, Some(&channel))?;
654 guard.set_session(session);
655 guard.recalculate_pty_size();
656 let targets: Vec<ClientEntry> = guard.clients.values().cloned().collect();
657 let session = guard.session.as_ref().unwrap();
658 let (sid, scol, srow) = (session.id, session.cols, session.rows);
659 let (ncols, nrows) = (scol, srow);
660 drop(guard);
661 if let Some(ch) = resolve_channel(state.as_ref(), &channel).await {
662 let g = ch.lock().await;
663 g.notify_clients(&targets, ncols, nrows);
664 }
665 Spawn::encode_response((sid, scol, srow)).map_err(boxed_io)
666 }
667 })
668 .await
669 .map_err(|e| format!("register Spawn: {e:?}"))?;
670
671 let st = Arc::clone(&state);
673 endpoint
674 .register_prebuffered(ResizePty::METHOD_ID, move |payload, ctx| {
675 let state = Arc::clone(&st);
676 async move {
677 if state.is_shutting_down.load(Ordering::SeqCst) {
678 return Err(rpc_err(RPC_ERROR_SHUTTING_DOWN));
679 }
680 let (_id, cols, rows) = ResizePty::decode_request(&payload)?;
681 let channel = bound_channel(state.as_ref(), ctx.conn_id).await;
682 let Some(channel) = channel else {
683 return Err(rpc_err(RPC_ERROR_UNATTACHED));
684 };
685 let ch = resolve_channel(state.as_ref(), &channel)
686 .await
687 .ok_or_else(|| rpc_err("channel not found"))?;
688 let mut guard = ch.lock().await;
689 if let Some(client) = guard.clients.get_mut(&ctx.conn_id) {
690 client.cols = cols;
691 client.rows = rows;
692 }
693 guard.recalculate_pty_size();
694 let (ncols, nrows) = guard
695 .session
696 .as_ref()
697 .map(|s| (s.cols, s.rows))
698 .unwrap_or((cols, rows));
699 let targets: Vec<ClientEntry> = guard.clients.values().cloned().collect();
700 drop(guard);
701 if let Some(ch) = resolve_channel(state.as_ref(), &channel).await {
702 let g = ch.lock().await;
703 g.notify_clients(&targets, ncols, nrows);
704 }
705 ResizePty::encode_response((ncols, nrows)).map_err(boxed_io)
706 }
707 })
708 .await
709 .map_err(|e| format!("register ResizePty: {e:?}"))?;
710
711 let st = Arc::clone(&state);
713 endpoint
714 .register_prebuffered(CloseSession::METHOD_ID, move |payload, ctx| {
715 let state = Arc::clone(&st);
716 async move {
717 if state.is_shutting_down.load(Ordering::SeqCst) {
718 return Err(rpc_err(RPC_ERROR_SHUTTING_DOWN));
719 }
720 let _id = CloseSession::decode_request(&payload)?;
721 let channel = bound_channel(state.as_ref(), ctx.conn_id).await;
722 let Some(channel) = channel else {
723 return Err(rpc_err(RPC_ERROR_UNATTACHED));
724 };
725 let ch = resolve_channel(state.as_ref(), &channel)
726 .await
727 .ok_or_else(|| rpc_err("channel not found"))?;
728 let mut guard = ch.lock().await;
729 guard.request_session_kill(SIGTERM);
730 guard.finalize_subscribers();
731 drop(guard);
732 spawn_kill_escalation(&state, &channel).await;
734 CloseSession::encode_response(()).map_err(boxed_io)
735 }
736 })
737 .await
738 .map_err(|e| format!("register CloseSession: {e:?}"))?;
739
740 let st = Arc::clone(&state);
742 endpoint
743 .register_prebuffered(WriteInput::METHOD_ID, move |payload, ctx| {
744 let state = Arc::clone(&st);
745 async move {
746 if state.is_shutting_down.load(Ordering::SeqCst) {
747 return Err(rpc_err(RPC_ERROR_SHUTTING_DOWN));
748 }
749 let (id, data) = WriteInput::decode_request(&payload)?;
750 let channel = bound_channel(state.as_ref(), ctx.conn_id).await;
751 let Some(channel) = channel else {
752 return Err(rpc_err(RPC_ERROR_UNATTACHED));
753 };
754 let ch = resolve_channel(state.as_ref(), &channel)
755 .await
756 .ok_or_else(|| rpc_err("channel not found"))?;
757 let writer = {
758 let guard = ch.lock().await;
759 guard
760 .session
761 .as_ref()
762 .filter(|s| s.id == id)
763 .map(|s| s.pty.writer_handle())
764 };
765 if let Some(writer) = writer {
769 let _ = tokio::task::spawn_blocking(move || writer.write_bytes(&data)).await;
770 }
771 WriteInput::encode_response(()).map_err(boxed_io)
772 }
773 })
774 .await
775 .map_err(|e| format!("register WriteInput: {e:?}"))?;
776
777 let st = Arc::clone(&state);
779 endpoint
780 .register_stream_handler(STREAM_INPUT_METHOD_ID, move |event, _responder, ctx| {
781 if let RpcStreamEvent::PayloadChunk { bytes, .. } = event {
782 let state = Arc::clone(&st);
783 let conn_id = ctx.conn_id;
784 tokio::spawn(async move {
785 let channel = bound_channel(state.as_ref(), conn_id).await;
786 let Some(channel) = channel else {
787 return;
788 };
789 let ch = resolve_channel(state.as_ref(), &channel).await;
790 let Some(ch) = ch else {
791 return;
792 };
793 let tx = {
794 let guard = ch.lock().await;
795 guard.input_tx.clone()
796 };
797 if let Err(e) = tx.try_send(bytes) {
798 tracing::warn!(error = %e, "gateway input buffer full; dropping input chunk");
799 }
800 });
801 }
802 })
804 .await
805 .map_err(|e| format!("register stream handler STREAM_INPUT: {e:?}"))?;
806
807 let st = Arc::clone(&state);
809 endpoint
810 .register_stream_handler(SUBSCRIBE_OUTPUT_METHOD_ID, move |event, respond, ctx| {
811 let is_new = matches!(&event, RpcStreamEvent::Header { .. });
812 if is_new {
813 let st = Arc::clone(&st);
814 let conn_id = ctx.conn_id;
815 tokio::spawn(async move {
816 let channel = bound_channel(&st, conn_id).await;
817 let Some(channel) = channel else {
818 return;
819 };
820 let ch = resolve_channel(&st, &channel).await;
821 let Some(ch) = ch else {
822 return;
823 };
824 let mut guard = ch.lock().await;
825 let early = guard.session.as_mut().and_then(|s| {
828 let data = s.read_output();
829 if data.is_empty() { None } else { Some(data) }
830 });
831 let snapshot = guard.session.as_mut().map(|s| s.generate_snapshot());
832 guard.subscribers.push(SubscriberEntry {
833 conn_id,
834 respond: respond.clone(),
835 });
836 guard.notify.notify_one();
837 let is_dead = guard.session.is_none();
838 drop(guard);
839 if let Some(data) = snapshot
840 && !data.is_empty()
841 {
842 respond.respond(data, false);
843 }
844 if let Some(data) = early {
845 respond.respond(data, false);
846 }
847 if is_dead {
848 respond.respond(Vec::new(), true);
849 }
850 });
851 }
852 })
853 .await
854 .map_err(|e| format!("register SubscribeOutput: {e:?}"))?;
855
856 let st = Arc::clone(&state);
858 let list_socket = socket_name.clone();
859 endpoint
860 .register_prebuffered(ListChannels::METHOD_ID, move |_payload, _ctx| {
861 let state = Arc::clone(&st);
862 let socket = list_socket.clone();
863 async move {
864 let channels = {
865 let chans = state.channels.read().await;
866 let mut v: Vec<(ChannelName, Arc<Mutex<ChannelState>>)> =
867 chans.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
868 drop(chans);
869 v.sort_by_key(|a| a.0.to_string());
870 v
871 };
872 let mut out = Vec::with_capacity(channels.len());
873 for (name, ch) in channels {
874 let guard = ch.lock().await;
875 out.push(guard.to_info(&name));
876 }
877 ListChannels::encode_response(ListChannelsResponse {
878 gateway_pid: std::process::id() as u64,
879 socket,
880 channels: out,
881 })
882 .map_err(boxed_io)
883 }
884 })
885 .await
886 .map_err(|e| format!("register ListChannels: {e:?}"))?;
887
888 let st = Arc::clone(&state);
890 endpoint
891 .register_prebuffered(KillChannel::METHOD_ID, move |payload, _ctx| {
892 let state = Arc::clone(&st);
893 async move {
894 if state.is_shutting_down.load(Ordering::SeqCst) {
895 return Err(rpc_err(RPC_ERROR_SHUTTING_DOWN));
896 }
897 let channel_str = KillChannel::decode_request(&payload)?;
898 let name = ChannelName::parse(&channel_str).map_err(|e| rpc_err(&e))?;
899 let target_conns: Vec<usize> = {
901 let conns = state.conns.read().await;
902 conns
903 .iter()
904 .filter(|(_, entry)| matches!(entry.state, ConnState::Attached(ref n) if n == &name))
905 .map(|(conn_id, _)| *conn_id)
906 .collect()
907 };
908 if let Some(ch) = resolve_channel(state.as_ref(), &name).await {
910 let mut guard = ch.lock().await;
911 guard.request_session_kill(SIGTERM);
912 guard.finalize_subscribers();
913 for conn_id in &target_conns {
914 guard.clients.remove(conn_id);
915 guard.subscribers.retain(|s| s.conn_id != *conn_id);
916 }
917 drop(guard);
918 spawn_kill_escalation(&state, &name).await;
919 }
920 let mut conns = state.conns.write().await;
922 for conn_id in &target_conns {
923 conns.remove(conn_id);
924 }
925 KillChannel::encode_response(()).map_err(boxed_io)
926 }
927 })
928 .await
929 .map_err(|e| format!("register KillChannel: {e:?}"))?;
930
931 let st = Arc::clone(&state);
933 endpoint
934 .register_prebuffered(KillClient::METHOD_ID, move |payload, _ctx| {
935 let state = Arc::clone(&st);
936 async move {
937 if state.is_shutting_down.load(Ordering::SeqCst) {
938 return Err(rpc_err(RPC_ERROR_SHUTTING_DOWN));
939 }
940 let (channel_str, conn_id) = KillClient::decode_request(&payload)?;
941 let name = ChannelName::parse(&channel_str).map_err(|e| rpc_err(&e))?;
942 let bound_channel = {
945 let conns = state.conns.read().await;
946 conns.get(&conn_id).and_then(|c| match &c.state {
947 ConnState::Attached(n) if n == &name => Some(n.clone()),
948 _ => None,
949 })
950 };
951 let Some(bound) = bound_channel else {
952 return Err(rpc_err(&format!(
953 "client {conn_id} is not attached to channel '{name}'"
954 )));
955 };
956 {
958 let mut conns = state.conns.write().await;
959 conns.remove(&conn_id);
960 }
961 if let Some(ch) = resolve_channel(state.as_ref(), &bound).await {
962 let mut guard = ch.lock().await;
963 let mut evicted: Vec<StreamResponder> = Vec::new();
965 let mut keep = Vec::with_capacity(guard.subscribers.len());
966 for sub in guard.subscribers.drain(..) {
967 if sub.conn_id == conn_id {
968 evicted.push(sub.respond);
969 } else {
970 keep.push(sub);
971 }
972 }
973 guard.subscribers = keep;
974 for respond in evicted {
975 respond.respond(Vec::new(), true);
976 }
977 guard.clients.remove(&conn_id);
978 guard.recalculate_pty_size();
979 let session_size = guard.session.as_ref().map(|s| (s.cols, s.rows));
980 let targets: Vec<ClientEntry> = guard.clients.values().cloned().collect();
981 drop(guard);
982 if let (Some((ncols, nrows)), Some(ch)) =
983 (session_size, resolve_channel(state.as_ref(), &bound).await)
984 {
985 let g = ch.lock().await;
986 g.notify_clients(&targets, ncols, nrows);
987 }
988 }
989 KillClient::encode_response(()).map_err(boxed_io)
990 }
991 })
992 .await
993 .map_err(|e| format!("register KillClient: {e:?}"))?;
994
995 let st = Arc::clone(&state);
997 let (shutdown_tx, mut shutdown_rx) = oneshot::channel::<()>();
998 let shutdown_tx = Arc::new(Mutex::new(Some(shutdown_tx)));
999 endpoint
1000 .register_prebuffered(ShutdownGateway::METHOD_ID, move |_payload, _ctx| {
1001 let state = Arc::clone(&st);
1002 let shutdown_tx = Arc::clone(&shutdown_tx);
1003 async move {
1004 state.is_shutting_down.store(true, Ordering::SeqCst);
1006 let channels: Vec<(ChannelName, Arc<Mutex<ChannelState>>)> = {
1008 let chans = state.channels.read().await;
1009 chans.iter().map(|(k, v)| (k.clone(), v.clone())).collect()
1010 };
1011 let mut escalations = Vec::new();
1015 for (name, ch) in channels {
1016 let mut guard = ch.lock().await;
1017 tracing::info!(channel = %name, "Shutdown: signaling session tree");
1018 guard.request_session_kill(SIGTERM);
1019 guard.finalize_subscribers();
1020 drop(guard);
1021 escalations.push(spawn_kill_escalation(&state, &name).await);
1022 }
1023 tokio::spawn(async move {
1029 for handle in escalations {
1030 let _ = handle.await;
1031 }
1032 tokio::time::sleep(std::time::Duration::from_millis(SHUTDOWN_FLUSH_GRACE_MS))
1033 .await;
1034 let mut tx_guard = shutdown_tx.lock().await;
1035 if let Some(tx) = tx_guard.take() {
1036 let _ = tx.send(());
1037 }
1038 });
1039 ShutdownGateway::encode_response(()).map_err(boxed_io)
1040 }
1041 })
1042 .await
1043 .map_err(|e| format!("register ShutdownGateway: {e:?}"))?;
1044
1045 let st = Arc::clone(&state);
1047 tokio::spawn(async move {
1048 while let Some(event) = event_rx.recv().await {
1049 match event {
1050 RpcIpcServerEvent::ClientConnected(handle) => {
1051 tracing::info!("Client {} connected", handle.0.conn_id);
1052 let mut conns = st.conns.write().await;
1053 conns.entry(handle.0.conn_id).or_insert_with(|| ConnEntry {
1059 handle: handle.clone(),
1060 state: ConnState::Unattached,
1061 hostname: String::new(),
1062 connected_at_unix: now_unix(),
1063 pid: 0,
1064 });
1065 }
1066 RpcIpcServerEvent::ClientDisconnected(conn_id) => {
1067 tracing::info!("Client {conn_id} disconnected");
1068 evict_conn(st.as_ref(), conn_id).await;
1069 }
1070 }
1071 }
1072 });
1073
1074 tracing::info!("Gateway listening on channel {gateway}");
1075
1076 let exit_code = tokio::select! {
1078 result = async {
1079 server.serve(&socket_name).await.map_err(|e| format!("serve: {e:?}"))
1080 } => {
1081 result?;
1082 0
1083 }
1084 _ = &mut shutdown_rx => {
1085 tokio::time::sleep(SESSION_EXIT_FLUSH_GRACE).await;
1087 0
1088 }
1089 };
1090
1091 Ok(exit_code)
1092}