Skip to main content

lsp_max/
transport.rs

1//! Generic server for multiplexing bidirectional streams through a transport.
2
3#[cfg(all(feature = "runtime-agnostic", not(feature = "runtime-tokio")))]
4use async_codec_lite::{FramedRead, FramedWrite};
5#[cfg(all(feature = "runtime-agnostic", not(feature = "runtime-tokio")))]
6use futures::io::{AsyncRead, AsyncWrite};
7
8#[cfg(feature = "runtime-tokio")]
9use tokio::io::{AsyncRead, AsyncWrite};
10#[cfg(feature = "runtime-tokio")]
11use tokio_util::codec::{FramedRead, FramedWrite};
12
13use std::task::Poll;
14
15use futures::channel::mpsc;
16use futures::{future, join, stream, FutureExt, Sink, SinkExt, Stream, StreamExt, TryFutureExt};
17use tower::Service;
18use tracing::error;
19
20/// Drives `poll_ready` to completion; returns `Err` if the service has exited.
21async fn await_ready<S>(service: &mut S) -> Result<(), S::Error>
22where
23    S: Service<Request>,
24{
25    future::poll_fn(|cx| service.poll_ready(cx)).await
26}
27
28/// Probes `poll_ready` once without blocking; returns `Some(err)` if the service
29/// has exited, `None` if it is still pending or ready without error.
30async fn probe_exited<S>(service: &mut S) -> Option<S::Error>
31where
32    S: Service<Request>,
33{
34    future::poll_fn(|cx| match service.poll_ready(cx) {
35        Poll::Ready(Err(err)) => Poll::Ready(Some(err)),
36        _ => Poll::Ready(None),
37    })
38    .await
39}
40
41use crate::codec::{LanguageServerCodec, ParseError};
42use crate::jsonrpc::{Error, Id, Message, Request, Response};
43use crate::service::{ClientSocket, ExitedError, RequestStream, ResponseSink};
44
45const DEFAULT_MAX_CONCURRENCY: usize = 4;
46const MESSAGE_QUEUE_SIZE: usize = 100;
47/// Bounded re-polls after stdin EOF to observe the service's `Exited`
48/// transition (driven by an in-flight `exit` call future) before falling
49/// back to an abnormal-termination exit code.
50const MAX_EXIT_OBSERVE_POLLS: usize = 16;
51
52/// Trait implemented by client loopback sockets.
53///
54/// This socket handles the server-to-client half of the bidirectional communication stream.
55///
56/// See also: `examples/transport_utilities_explained.rs` — a run-to-exit witness that
57/// demonstrates `Loopback`, `ExitedError`, and `ClientSocket` with real `assert!`s.
58pub trait Loopback {
59    /// Yields a stream of pending server-to-client requests.
60    type RequestStream: Stream<Item = Request>;
61    /// Routes client-to-server responses back to the server.
62    type ResponseSink: Sink<Response> + Unpin;
63
64    /// Splits this socket into two halves capable of operating independently.
65    ///
66    /// The two halves returned implement the [`Stream`] and [`Sink`] traits, respectively.
67    fn split(self) -> (Self::RequestStream, Self::ResponseSink);
68}
69
70impl Loopback for ClientSocket {
71    type RequestStream = RequestStream;
72    type ResponseSink = ResponseSink;
73
74    #[inline]
75    fn split(self) -> (Self::RequestStream, Self::ResponseSink) {
76        self.split()
77    }
78}
79
80/// Server for processing requests and responses on standard I/O or TCP.
81#[derive(Debug)]
82pub struct Server<I, O, L = ClientSocket> {
83    stdin: I,
84    stdout: O,
85    loopback: L,
86    max_concurrency: usize,
87}
88
89impl<I, O, L> Server<I, O, L>
90where
91    I: AsyncRead + Unpin,
92    O: AsyncWrite,
93    L: Loopback,
94    <L::ResponseSink as Sink<Response>>::Error: std::error::Error,
95{
96    /// Creates a new `Server` with the given `stdin` and `stdout` handles.
97    pub fn new(stdin: I, stdout: O, socket: L) -> Self {
98        Server {
99            stdin,
100            stdout,
101            loopback: socket,
102            max_concurrency: DEFAULT_MAX_CONCURRENCY,
103        }
104    }
105
106    /// Sets the server concurrency limit to `max`.
107    ///
108    /// This setting specifies how many incoming requests may be processed concurrently. Setting
109    /// this value to `1` forces all requests to be processed sequentially, thereby implicitly
110    /// disabling support for the [`$/cancelRequest`] notification.
111    ///
112    /// [`$/cancelRequest`]: https://microsoft.github.io/language-server-protocol/specification#cancelRequest
113    ///
114    /// If not explicitly specified, `max` defaults to 4.
115    ///
116    /// # Preference over standard `tower` middleware
117    ///
118    /// The [`ConcurrencyLimit`] and [`Buffer`] middlewares provided by `tower` rely on
119    /// [`tokio::spawn`] in common usage, while this library aims to be executor agnostic and to
120    /// support exotic targets currently incompatible with `tokio`, such as WASM. As such, `Server`
121    /// includes its own concurrency facilities that don't require a global executor to be present.
122    ///
123    /// [`ConcurrencyLimit`]: https://docs.rs/tower/latest/tower/limit/concurrency/struct.ConcurrencyLimit.html
124    /// [`Buffer`]: https://docs.rs/tower/latest/tower/buffer/index.html
125    /// [`tokio::spawn`]: https://docs.rs/tokio/latest/tokio/fn.spawn.html
126    pub fn concurrency_level(mut self, max: usize) -> Self {
127        self.max_concurrency = max;
128        self
129    }
130
131    /// Spawns the service with messages read through `stdin` and responses written to `stdout`.
132    pub async fn serve<T>(self, mut service: T) -> Result<(), T::Error>
133    where
134        T: Service<Request, Response = Option<Response>> + Send + 'static,
135        T::Error: std::error::Error + Send + Sync + 'static + From<ExitedError>,
136        T::Future: Send,
137    {
138        let (client_requests, mut client_responses) = self.loopback.split();
139        let (client_requests, client_abort) = stream::abortable(client_requests);
140        let (mut responses_tx, responses_rx) = mpsc::channel(MESSAGE_QUEUE_SIZE);
141        let (mut server_tasks_tx, server_tasks_rx) = mpsc::channel(MESSAGE_QUEUE_SIZE);
142
143        let mut framed_stdin = FramedRead::new(self.stdin, LanguageServerCodec::default());
144        let framed_stdout = FramedWrite::new(self.stdout, LanguageServerCodec::default());
145
146        let process_server_tasks = server_tasks_rx
147            .buffer_unordered(self.max_concurrency)
148            .filter_map(future::ready)
149            .map(|res| Ok(Message::Response(res)))
150            .forward(responses_tx.clone().sink_map_err(|_| unreachable!()))
151            .map(|_| ());
152
153        let print_output = stream::select(responses_rx, client_requests.map(Message::Request))
154            .map(Ok)
155            .forward(framed_stdout.sink_map_err(|e| error!("failed to encode message: {}", e)))
156            .map(|_| ());
157
158        let read_input = async {
159            while let Some(msg) = framed_stdin.next().await {
160                if let Ok(ref m) = msg {
161                    match m {
162                        Message::Request(req) => tracing::trace!(
163                            "--- Server::serve read_input got Request: method={}, id={:?}",
164                            req.method(),
165                            req.id()
166                        ),
167                        Message::Response(res) => tracing::trace!(
168                            "--- Server::serve read_input got Response: id={:?}",
169                            res.id()
170                        ),
171                    }
172                }
173                match msg {
174                    Ok(Message::Request(req)) => {
175                        if let Err(err) = await_ready(&mut service).await {
176                            error!("{}", display_sources(&err));
177                            return Err(err);
178                        }
179
180                        let fut = service.call(req).unwrap_or_else(|err| {
181                            error!("{}", display_sources(&err));
182                            None
183                        });
184
185                        if let Err(e) = server_tasks_tx.send(fut).await {
186                            error!("transport send failed: {}", e);
187                            return Ok(());
188                        }
189                    }
190                    Ok(Message::Response(res)) => {
191                        if let Err(err) = client_responses.send(res).await {
192                            error!("{}", display_sources(&err));
193                            return Ok(());
194                        }
195                    }
196                    Err(err) => {
197                        error!("failed to decode message: {}", err);
198                        let res = Response::from_error(Id::Null, to_jsonrpc_error(err));
199                        if let Err(e) = responses_tx.send(Message::Response(res)).await {
200                            error!("transport send failed: {}", e);
201                            return Ok(());
202                        }
203                    }
204                }
205            }
206
207            server_tasks_tx.disconnect();
208            responses_tx.disconnect();
209            client_abort.abort();
210
211            // After stdin EOF, surface the state machine's STORED exit code.
212            //
213            // An `exit` notification dispatched just before EOF transitions the
214            // service to `Exited` via its `call` future, at which point
215            // `poll_ready` reports `Err(ExitedError(get_exit_code()))` carrying
216            // the lawful code (0 on the ShutDown -> Exited path, 1 otherwise).
217            // There is a window where the `exit` call future has not yet driven
218            // the transition when EOF is first observed; polling once and falling
219            // back to a hardcoded `1` would discard a lawful `0`. Yield and
220            // re-poll a bounded number of times so the stored code is propagated
221            // rather than a fixed fallback.
222            for _ in 0..MAX_EXIT_OBSERVE_POLLS {
223                if let Some(err) = probe_exited(&mut service).await {
224                    return Err(err);
225                }
226
227                // Not yet observed as Exited; let any in-flight `exit` call
228                // future make progress, then re-poll for the stored code.
229                tokio::task::yield_now().await;
230            }
231
232            // No lawful Exited transition was observed (e.g. EOF without a prior
233            // `exit`/`shutdown`): per LSP, this is an abnormal termination.
234            Err(T::Error::from(ExitedError(1)))
235        };
236
237        let (_, res, _) = join!(print_output, read_input, process_server_tasks);
238        res
239    }
240}
241
242fn display_sources(error: &dyn std::error::Error) -> String {
243    if let Some(source) = error.source() {
244        format!("{}: {}", error, display_sources(source))
245    } else {
246        error.to_string()
247    }
248}
249
250#[cfg(feature = "runtime-tokio")]
251fn to_jsonrpc_error(err: ParseError) -> Error {
252    match err {
253        ParseError::Body(err) if err.is_data() => Error::invalid_request(),
254        _ => Error::parse_error(),
255    }
256}
257
258#[cfg(all(feature = "runtime-agnostic", not(feature = "runtime-tokio")))]
259fn to_jsonrpc_error(err: impl std::error::Error) -> Error {
260    match err.source().and_then(|e| e.downcast_ref()) {
261        Some(ParseError::Body(err)) if err.is_data() => Error::invalid_request(),
262        _ => Error::parse_error(),
263    }
264}
265
266#[cfg(test)]
267mod tests {
268    use std::task::{Context, Poll};
269
270    #[cfg(all(feature = "runtime-agnostic", not(feature = "runtime-tokio")))]
271    use futures::io::Cursor;
272    #[cfg(feature = "runtime-tokio")]
273    use std::io::Cursor;
274
275    use futures::future::Ready;
276    use futures::{future, sink, stream};
277
278    use super::*;
279
280    const REQUEST: &str = r#"{"jsonrpc":"2.0","method":"initialize","params":{},"id":1}"#;
281    const RESPONSE: &str = r#"{"jsonrpc":"2.0","result":{"capabilities":{}},"id":1}"#;
282
283    #[derive(Debug)]
284    struct MockService;
285
286    impl Service<Request> for MockService {
287        type Response = Option<Response>;
288        type Error = ExitedError;
289        type Future = Ready<Result<Self::Response, Self::Error>>;
290
291        fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
292            Poll::Ready(Ok(()))
293        }
294
295        fn call(&mut self, _: Request) -> Self::Future {
296            let response = serde_json::from_str(RESPONSE).unwrap();
297            future::ok(Some(response))
298        }
299    }
300
301    struct MockLoopback(Vec<Request>);
302
303    impl Loopback for MockLoopback {
304        type RequestStream = stream::Iter<std::vec::IntoIter<Request>>;
305        type ResponseSink = sink::Drain<Response>;
306
307        fn split(self) -> (Self::RequestStream, Self::ResponseSink) {
308            (stream::iter(self.0), sink::drain())
309        }
310    }
311
312    fn mock_request() -> Vec<u8> {
313        format!("Content-Length: {}\r\n\r\n{}", REQUEST.len(), REQUEST).into_bytes()
314    }
315
316    fn mock_response() -> Vec<u8> {
317        format!("Content-Length: {}\r\n\r\n{}", RESPONSE.len(), RESPONSE).into_bytes()
318    }
319
320    fn mock_stdio() -> (Cursor<Vec<u8>>, Vec<u8>) {
321        (Cursor::new(mock_request()), Vec::new())
322    }
323
324    #[tokio::test(flavor = "current_thread")]
325    async fn serves_on_stdio() {
326        let (mut stdin, mut stdout) = mock_stdio();
327        let res = Server::new(&mut stdin, &mut stdout, MockLoopback(vec![]))
328            .serve(MockService)
329            .await;
330
331        assert_eq!(res, Err(ExitedError(1)));
332        assert_eq!(stdin.position(), 80);
333        assert_eq!(stdout, mock_response());
334    }
335
336    #[tokio::test(flavor = "current_thread")]
337    async fn interleaves_messages() {
338        let socket = MockLoopback(vec![serde_json::from_str(REQUEST).unwrap()]);
339
340        let (mut stdin, mut stdout) = mock_stdio();
341        let res = Server::new(&mut stdin, &mut stdout, socket)
342            .serve(MockService)
343            .await;
344
345        assert_eq!(res, Err(ExitedError(1)));
346        assert_eq!(stdin.position(), 80);
347        let output: Vec<_> = mock_request().into_iter().chain(mock_response()).collect();
348        assert_eq!(stdout, output);
349    }
350
351    #[tokio::test(flavor = "current_thread")]
352    async fn handles_invalid_json() {
353        let invalid = r#"{"jsonrpc":"2.0","method":"#;
354        let message = format!("Content-Length: {}\r\n\r\n{}", invalid.len(), invalid).into_bytes();
355        let (mut stdin, mut stdout) = (Cursor::new(message), Vec::new());
356
357        let res = Server::new(&mut stdin, &mut stdout, MockLoopback(vec![]))
358            .serve(MockService)
359            .await;
360
361        assert_eq!(res, Err(ExitedError(1)));
362        assert_eq!(stdin.position(), 48);
363        let err = r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":null}"#;
364        let output = format!("Content-Length: {}\r\n\r\n{}", err.len(), err).into_bytes();
365        assert_eq!(stdout, output);
366    }
367
368    /// Service that always returns None (no response), used to exercise the
369    /// server_tasks channel send path without blocking on a response.
370    #[derive(Debug)]
371    struct NoneService;
372
373    impl Service<Request> for NoneService {
374        type Response = Option<Response>;
375        type Error = ExitedError;
376        type Future = Ready<Result<Self::Response, Self::Error>>;
377
378        fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
379            Poll::Ready(Ok(()))
380        }
381
382        fn call(&mut self, _: Request) -> Self::Future {
383            future::ok(None)
384        }
385    }
386
387    /// Proves that multiple valid requests sent through the transport do NOT panic when
388    /// the server_tasks channel send is exercised — all complete without unwrap panics.
389    /// This guards against the defect where server_tasks_tx.send().await.unwrap() would
390    /// panic if the receiving half was dropped during a shutdown race.
391    #[tokio::test(flavor = "current_thread")]
392    async fn no_panic_on_server_tasks_channel_send() {
393        let mut input = Vec::new();
394        for _ in 0..5 {
395            input.extend_from_slice(&mock_request());
396        }
397        let (mut stdin, mut stdout) = (Cursor::new(input), Vec::new());
398
399        // NoneService returns None for every request so futures resolve immediately.
400        let res = Server::new(&mut stdin, &mut stdout, MockLoopback(vec![]))
401            .serve(NoneService)
402            .await;
403
404        // Server exits cleanly after EOF — no panic occurred.
405        assert_eq!(res, Err(ExitedError(1)));
406    }
407
408    /// Proves that two consecutive invalid JSON messages both go through the responses_tx
409    /// send path without panicking. This guards against the defect where
410    /// responses_tx.send().await.unwrap() would panic if the receiver was dropped.
411    #[tokio::test(flavor = "current_thread")]
412    async fn no_panic_on_responses_tx_send_two_invalid_messages() {
413        let invalid = r#"{"jsonrpc":"2.0","method":"#;
414        let framed = format!("Content-Length: {}\r\n\r\n{}", invalid.len(), invalid).into_bytes();
415        let mut input = framed.clone();
416        input.extend_from_slice(&framed);
417
418        let (mut stdin, mut stdout) = (Cursor::new(input), Vec::new());
419
420        let res = Server::new(&mut stdin, &mut stdout, MockLoopback(vec![]))
421            .serve(MockService)
422            .await;
423
424        // Both sends must complete without panic; result is Ok(()) or ExitedError.
425        assert!(res == Ok(()) || res == Err(ExitedError(1)));
426    }
427}