async_lsp/
client_monitor.rs

1//! Stop the main loop when the Language Client process aborted unexpectedly.
2//!
3//! *Only applies to Language Servers.*
4//!
5//! Typically, the Language Client should send
6//! [`exit` notification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#exit)
7//! to inform the Language Server to exit. While in case of it unexpected aborted without the
8//! notification, and the communicate channel is left open somehow (eg. an FIFO or UNIX domain
9//! socket), the Language Server would wait indefinitely, wasting system resources.
10//!
11//! It is encouraged by
12//! [LSP specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initializeParams)
13//! that,
14//! > If the parent process (`processId`) is not alive then the server should exit (see exit
15//! > notification) its process.
16//!
17//! And this middleware does exactly this monitor mechanism.
18//!
19//! Implementation: See crate [`waitpid_any`].
20use std::ops::ControlFlow;
21use std::task::{Context, Poll};
22
23use lsp_types::request::{self, Request};
24use tower_layer::Layer;
25use tower_service::Service;
26
27use crate::{AnyEvent, AnyNotification, AnyRequest, ClientSocket, Error, LspService, Result};
28
29struct ClientProcessExited;
30
31/// The middleware stopping the main loop when the Language Client process aborted unexpectedly.
32///
33/// See [module level documentations](self) for details.
34pub struct ClientProcessMonitor<S> {
35    service: S,
36    client: ClientSocket,
37}
38
39impl<S: LspService> Service<AnyRequest> for ClientProcessMonitor<S> {
40    type Response = S::Response;
41    type Error = S::Error;
42    type Future = S::Future;
43
44    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
45        self.service.poll_ready(cx)
46    }
47
48    fn call(&mut self, req: AnyRequest) -> Self::Future {
49        if let Some(pid) = (|| -> Option<i32> {
50            (req.method == request::Initialize::METHOD)
51                .then_some(&req.params)?
52                .as_object()?
53                .get("processId")?
54                .as_i64()?
55                .try_into()
56                .ok()
57        })() {
58            match waitpid_any::WaitHandle::open(pid) {
59                Ok(mut handle) => {
60                    let client = self.client.clone();
61                    let spawn_ret = std::thread::Builder::new()
62                        .name("client-process-monitor".into())
63                        .spawn(move || {
64                            match handle.wait() {
65                                Ok(()) => {
66                                    // Ignore channel close.
67                                    let _: Result<_, _> = client.emit(ClientProcessExited);
68                                }
69                                #[allow(unused_variables)]
70                                Err(err) => {
71                                    #[cfg(feature = "tracing")]
72                                    ::tracing::error!(
73                                        "Failed to monitor peer process ({pid}): {err:#}"
74                                    );
75                                }
76                            }
77                        });
78                    #[allow(unused_variables)]
79                    if let Err(err) = spawn_ret {
80                        #[cfg(feature = "tracing")]
81                        ::tracing::error!("Failed to spawn client process monitor thread: {err:#}");
82                    }
83                }
84                // Already exited.
85                #[cfg(unix)]
86                Err(err) if err.raw_os_error() == Some(rustix::io::Errno::SRCH.raw_os_error()) => {
87                    // Ignore channel close.
88                    let _: Result<_, _> = self.client.emit(ClientProcessExited);
89                }
90                #[allow(unused_variables)]
91                Err(err) => {
92                    #[cfg(feature = "tracing")]
93                    ::tracing::error!("Failed to monitor peer process {pid}: {err:#}");
94                }
95            }
96        }
97
98        self.service.call(req)
99    }
100}
101
102impl<S: LspService> LspService for ClientProcessMonitor<S> {
103    fn notify(&mut self, notif: AnyNotification) -> ControlFlow<Result<()>> {
104        self.service.notify(notif)
105    }
106
107    fn emit(&mut self, event: AnyEvent) -> ControlFlow<Result<()>> {
108        match event.downcast::<ClientProcessExited>() {
109            Ok(ClientProcessExited) => {
110                ControlFlow::Break(Err(Error::Protocol("Client process exited".into())))
111            }
112            Err(event) => self.service.emit(event),
113        }
114    }
115}
116
117/// The builder of [`ClientProcessMonitor`] middleware.
118#[must_use]
119pub struct ClientProcessMonitorBuilder {
120    client: ClientSocket,
121}
122
123impl ClientProcessMonitorBuilder {
124    /// Create the middleware builder with a given [`ClientSocket`] to inject exit events.
125    pub fn new(client: ClientSocket) -> Self {
126        Self { client }
127    }
128}
129
130/// A type alias of [`ClientProcessMonitorBuilder`] conforming to the naming convention of
131/// [`tower_layer`].
132pub type ClientProcessMonitorLayer = ClientProcessMonitorBuilder;
133
134impl<S> Layer<S> for ClientProcessMonitorBuilder {
135    type Service = ClientProcessMonitor<S>;
136
137    fn layer(&self, inner: S) -> Self::Service {
138        ClientProcessMonitor {
139            service: inner,
140            client: self.client.clone(),
141        }
142    }
143}