use std::collections::HashMap;
use std::future::Future;
use std::sync::{Arc, Mutex};
use bytes::Bytes;
use futures_util::future::{Either, select};
use lsp_types::{
DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
InitializeParams, InitializeResult, TextDocumentSyncCapability, TextDocumentSyncKind,
};
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
use tokio_util::sync::CancellationToken;
use tracing::{Instrument, Span, debug, info_span, warn};
use crate::builder::{
ConfigureInitialize, DocumentSync, InitializeRegistrar, OnInitialize, Registrations, Server,
};
use crate::client::{Client, OutboundRegistry};
use crate::codec::{decode_params, decode_value, encode_body};
use crate::context::Context;
use crate::documents::Documents;
use crate::error::Error;
use crate::raw::{JsonRpcError, RawMessage, RequestId};
use crate::runtime::{Runtime, TaskHandle, TaskSend, default_runtime};
use crate::service::{IncomingCall, ServiceResult, UserLayer, UserService, build_service_stack};
use crate::transport::{Transport, TransportError, TransportReader, TransportWriter};
use crate::workspace::Workspace;
use crate::{LspError, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Outcome {
Exit { code: i32 },
TransportClosed,
WriterFailed,
InitializeFailed,
}
impl Outcome {
pub fn code(&self) -> i32 {
match self {
Self::Exit { code } => *code,
Self::TransportClosed | Self::WriterFailed | Self::InitializeFailed => 1,
}
}
}
#[derive(Debug)]
enum CloseCause {
Exit { code: i32 },
ReaderEof,
ReaderFailed(TransportError),
WriterFailed,
InitializeFailed,
}
impl CloseCause {
fn into_result(self) -> Result<Outcome> {
match self {
Self::Exit { code } => Ok(Outcome::Exit { code }),
Self::ReaderEof => Ok(Outcome::TransportClosed),
Self::ReaderFailed(error) => Err(Error::Transport(error)),
Self::WriterFailed => Ok(Outcome::WriterFailed),
Self::InitializeFailed => Ok(Outcome::InitializeFailed),
}
}
}
#[derive(Clone)]
struct CloseSignal {
inner: Arc<CloseInner>,
}
struct CloseInner {
cause: Mutex<Option<CloseCause>>,
requested: CancellationToken,
}
impl CloseSignal {
fn new() -> Self {
Self {
inner: Arc::new(CloseInner {
cause: Mutex::new(None),
requested: CancellationToken::new(),
}),
}
}
fn request(&self, cause: CloseCause) {
{
let mut recorded = self.inner.cause.lock().unwrap();
if recorded.is_none() {
*recorded = Some(cause);
}
}
self.inner.requested.cancel();
}
fn requested(&self) -> CancellationToken {
self.inner.requested.clone()
}
fn take_cause(&self) -> Option<CloseCause> {
self.inner.cause.lock().unwrap().take()
}
}
pub(crate) async fn run<S, T>(server: Server<S>, transport: T) -> Result<Outcome>
where
S: Send + Sync + 'static,
T: Transport,
{
let (reader, writer) = transport.split();
let (out_tx, out_rx) = mpsc::unbounded_channel::<RawMessage>();
let client = Client::new(out_tx.clone(), OutboundRegistry::default());
let close = CloseSignal::new();
let runtime = default_runtime();
let send_task = runtime.spawn(send_loop(writer, out_rx, client.clone(), close.clone()));
ProtocolEngine::new(server, runtime, out_tx, client, close, send_task)
.serve(reader)
.await
}
struct TaskGroup<R> {
runtime: R,
handles: Vec<TaskHandle>,
}
impl<R: Runtime> TaskGroup<R> {
fn new(runtime: R) -> Self {
Self {
runtime,
handles: Vec::new(),
}
}
fn spawn<F>(&mut self, future: F)
where
F: Future<Output = ()> + TaskSend + 'static,
{
self.handles.push(self.runtime.spawn(future));
}
async fn reap_finished(&mut self) {
let mut running = Vec::with_capacity(self.handles.len());
for handle in std::mem::take(&mut self.handles) {
if handle.is_finished() {
handle.join().await;
} else {
running.push(handle);
}
}
self.handles = running;
}
async fn abort_and_join(&mut self) {
for handle in &self.handles {
handle.abort();
}
self.join_all().await;
}
async fn join_all(&mut self) {
for handle in std::mem::take(&mut self.handles) {
handle.join().await;
}
}
}
#[derive(Clone)]
struct Reservation {
id: RequestId,
generation: u64,
}
struct InboundEntry {
generation: u64,
cancellation: Option<CancellationToken>,
}
#[derive(Default)]
struct InboundInner {
entries: HashMap<RequestId, InboundEntry>,
next_generation: u64,
}
#[derive(Clone, Default)]
struct InboundRegistry {
inner: Arc<Mutex<InboundInner>>,
}
impl InboundRegistry {
fn reserve(
&self,
id: RequestId,
cancellation: Option<CancellationToken>,
) -> Option<Reservation> {
let mut inner = self.inner.lock().unwrap();
if inner.entries.contains_key(&id) {
return None;
}
let generation = inner.next_generation;
inner.next_generation += 1;
inner.entries.insert(
id.clone(),
InboundEntry {
generation,
cancellation,
},
);
Some(Reservation { id, generation })
}
fn complete(
&self,
out_tx: &UnboundedSender<RawMessage>,
reservation: Reservation,
result: std::result::Result<Bytes, LspError>,
) {
let claimed = {
let mut inner = self.inner.lock().unwrap();
match inner.entries.get(&reservation.id) {
Some(entry) if entry.generation == reservation.generation => {
inner.entries.remove(&reservation.id).is_some()
}
_ => false,
}
};
if claimed {
enqueue_encoded(out_tx, reservation.id, result);
}
}
fn complete_cancellation(&self, out_tx: &UnboundedSender<RawMessage>, id: &RequestId) {
let token = {
let mut inner = self.inner.lock().unwrap();
match inner.entries.get(id) {
Some(entry) if entry.cancellation.is_some() => inner
.entries
.remove(id)
.and_then(|entry| entry.cancellation),
_ => None,
}
};
if let Some(token) = token {
token.cancel();
enqueue_encoded(out_tx, id.clone(), Err(LspError::RequestCancelled));
}
}
fn cancel_all_with_response(&self, out_tx: &UnboundedSender<RawMessage>) {
let entries = std::mem::take(&mut self.inner.lock().unwrap().entries);
for (id, entry) in entries {
if let Some(cancellation) = entry.cancellation {
cancellation.cancel();
}
enqueue_encoded(out_tx, id, Err(LspError::RequestCancelled));
}
}
fn close_all(&self) {
let entries = std::mem::take(&mut self.inner.lock().unwrap().entries);
for cancellation in entries.into_values().filter_map(|entry| entry.cancellation) {
cancellation.cancel();
}
}
}
#[derive(serde::Deserialize)]
struct CancelParams {
id: RequestId,
}
async fn send_loop<W: TransportWriter>(
mut writer: W,
mut out_rx: UnboundedReceiver<RawMessage>,
client: Client,
close: CloseSignal,
) {
let outbound_closing = client.outbound_closing();
loop {
let msg = tokio::select! {
biased;
msg = out_rx.recv() => msg,
() = outbound_closing.cancelled() => {
out_rx.close();
break;
}
};
let Some(msg) = msg else {
client.close_outbound();
break;
};
if let Err(e) = writer.send(msg).await {
warn!(error = %e, "send_loop: transport write failed");
close.request(CloseCause::WriterFailed);
return;
}
}
while let Some(msg) = out_rx.recv().await {
if let Err(e) = writer.send(msg).await {
warn!(error = %e, "send_loop: transport write failed while draining");
close.request(CloseCause::WriterFailed);
return;
}
}
if let Err(e) = writer.shutdown().await {
warn!(error = %e, "send_loop: transport shutdown failed");
close.request(CloseCause::WriterFailed);
}
}
struct Pending<S> {
registrations: Registrations<S>,
configure_initialize: Option<ConfigureInitialize<S>>,
on_initialize: Option<OnInitialize<S>>,
layers: Vec<UserLayer<S>>,
concurrency_limit: usize,
}
enum Lifecycle<S> {
Uninitialized(Box<Pending<S>>),
Initializing,
Running(UserService<S>),
ShuttingDown,
Exited,
}
struct ProtocolEngine<S, R> {
state: Arc<S>,
documents: Documents,
workspace: Option<Workspace>,
lifecycle: Lifecycle<S>,
inbound: InboundRegistry,
tasks: TaskGroup<R>,
out_tx: UnboundedSender<RawMessage>,
client: Client,
session: CancellationToken,
close: CloseSignal,
send_task: Option<TaskHandle>,
}
impl<S, R> ProtocolEngine<S, R>
where
S: Send + Sync + 'static,
R: Runtime,
{
fn new(
server: Server<S>,
runtime: R,
out_tx: UnboundedSender<RawMessage>,
client: Client,
close: CloseSignal,
send_task: TaskHandle,
) -> Self {
Self {
state: server.state,
documents: Documents::new(),
workspace: None,
lifecycle: Lifecycle::Uninitialized(Box::new(Pending {
registrations: server.registrations,
configure_initialize: server.configure_initialize,
on_initialize: server.on_initialize,
layers: server.layers,
concurrency_limit: server.concurrency_limit,
})),
inbound: InboundRegistry::default(),
tasks: TaskGroup::new(runtime),
out_tx,
client,
session: CancellationToken::new(),
close,
send_task: Some(send_task),
}
}
async fn serve<Rd>(mut self, mut reader: Rd) -> Result<Outcome>
where
Rd: TransportReader,
{
let requested = self.close.requested();
loop {
self.tasks.reap_finished().await;
let msg = tokio::select! {
biased;
() = requested.cancelled() => break,
msg = reader.recv() => msg,
};
match msg {
Ok(msg) => match self.dispatch(msg).await {
Flow::Continue => {}
Flow::Close(cause) => {
self.close.request(cause);
break;
}
},
Err(TransportError::Closed) => {
warn!("transport closed by peer before exit notification");
self.close.request(CloseCause::ReaderEof);
break;
}
Err(error) => {
self.close.request(CloseCause::ReaderFailed(error));
break;
}
}
}
self.close().await;
self.close
.take_cause()
.expect("every path out of the read-loop records its close cause")
.into_result()
}
async fn dispatch(&mut self, msg: RawMessage) -> Flow {
match msg {
RawMessage::Request { id, method, params } => {
let span = info_span!("request", method = %method, id = ?id);
let cancellation = (method != "initialize").then(|| self.session.child_token());
let Some(reservation) = self.inbound.reserve(id.clone(), cancellation.clone())
else {
enqueue_error(
&self.out_tx,
id,
LspError::invalid_request("duplicate request id"),
);
return Flow::Continue;
};
if method != "initialize"
&& matches!(
self.lifecycle,
Lifecycle::Uninitialized(_) | Lifecycle::Initializing
)
{
self.inbound.complete(
&self.out_tx,
reservation,
Err(LspError::ServerNotInitialized),
);
return Flow::Continue;
}
if matches!(self.lifecycle, Lifecycle::ShuttingDown | Lifecycle::Exited) {
self.inbound.complete(
&self.out_tx,
reservation,
Err(LspError::invalid_request("invalid request")),
);
return Flow::Continue;
}
match method.as_ref() {
"initialize" => return self.initialize(&span, reservation, params).await,
"shutdown" => {
self.inbound.complete(
&self.out_tx,
reservation,
encode_body(&serde_json::Value::Null),
);
self.inbound.cancel_all_with_response(&self.out_tx);
self.lifecycle = Lifecycle::ShuttingDown;
}
_other => {
let service = match &self.lifecycle {
Lifecycle::Running(service) => Arc::clone(service),
_ => {
self.inbound.complete(
&self.out_tx,
reservation,
Err(LspError::ServerNotInitialized),
);
return Flow::Continue;
}
};
let params = match decode_value(¶ms) {
Ok(params) => params,
Err(error) => {
self.inbound.complete(&self.out_tx, reservation, Err(error));
return Flow::Continue;
}
};
self.spawn_service_request(
service,
span,
reservation,
method.into_owned(),
params,
cancellation.expect("non-initialize requests are cancellable"),
);
}
}
}
RawMessage::Notification { method, params } => match method.as_ref() {
"exit" => {
let code = match self.lifecycle {
Lifecycle::ShuttingDown => 0,
_ => 1,
};
return Flow::Close(CloseCause::Exit { code });
}
"$/cancelRequest" => {
let bytes: &[u8] = if params.is_empty() { b"{}" } else { ¶ms };
match serde_json::from_slice::<CancelParams>(bytes) {
Ok(cancel) => self.inbound.complete_cancellation(&self.out_tx, &cancel.id),
Err(error) => {
debug!(%error, "ignoring malformed $/cancelRequest");
}
}
}
other => {
let Lifecycle::Running(service) = &self.lifecycle else {
debug!(method = other, "notification outside running state ignored");
return Flow::Continue;
};
let service = Arc::clone(service);
if let Some(built_in) = DocumentSync::from_method(other)
&& let Err(error) = self.apply_document_mutation(built_in, ¶ms)
{
warn!(method = other, %error, "document notification skipped its hook");
return Flow::Continue;
}
let params = match decode_value(¶ms) {
Ok(params) => params,
Err(error) => {
debug!(method = other, %error, "notification params ignored");
return Flow::Continue;
}
};
self.dispatch_notification(service, other, params).await;
}
},
RawMessage::Response { id, result } => {
let id_num = match &id {
RequestId::Number(n) if *n > 0 => Some(*n as u32),
_ => None,
};
let delivered =
id_num.is_some_and(|n| self.client.outbound_registry().complete(n, result));
if !delivered {
debug!(?id, "ignoring response with unknown or non-numeric id");
}
}
RawMessage::ProtocolError { error } => {
let _ = self.out_tx.send(RawMessage::ProtocolError { error });
}
}
Flow::Continue
}
async fn dispatch_notification(
&mut self,
service: UserService<S>,
method: &str,
params: serde_json::Value,
) {
let span = info_span!("notification", method = %method);
let ctx = attach_workspace(
Context::for_notification(span, self.client.clone(), self.documents.clone()),
&self.workspace,
);
let result = service
.call(IncomingCall::notification(
method.to_string(),
params,
ctx,
Arc::clone(&self.state),
))
.await;
if !matches!(result, ServiceResult::NoResponse) {
warn!("notification service attempted to produce a response");
}
}
fn apply_document_mutation(
&self,
built_in: DocumentSync,
raw_params: &Bytes,
) -> std::result::Result<(), LspError> {
match built_in {
DocumentSync::Open => {
let params: DidOpenTextDocumentParams = decode_params(raw_params)?;
self.documents.open(params.text_document);
}
DocumentSync::Change => {
let params: DidChangeTextDocumentParams = decode_params(raw_params)?;
self.documents.apply_changes(
¶ms.text_document.uri,
params.text_document.version,
params.content_changes,
)?;
}
DocumentSync::Close => {
let params: DidCloseTextDocumentParams = decode_params(raw_params)?;
if self.documents.close(¶ms.text_document.uri).is_none() {
debug!(
uri = ?params.text_document.uri,
"closing a document that was not open"
);
}
}
}
Ok(())
}
async fn initialize(&mut self, span: &Span, reservation: Reservation, params: Bytes) -> Flow {
if !matches!(self.lifecycle, Lifecycle::Uninitialized(_)) {
self.inbound.complete(
&self.out_tx,
reservation,
Err(LspError::ServerError {
code: -32600,
message: "server already initialized".into(),
data: None,
}),
);
return Flow::Continue;
}
let params = match decode_params::<InitializeParams>(¶ms) {
Ok(params) => params,
Err(err) => {
self.inbound.complete(&self.out_tx, reservation, Err(err));
return Flow::Continue;
}
};
let pending = match std::mem::replace(&mut self.lifecycle, Lifecycle::Initializing) {
Lifecycle::Uninitialized(pending) => *pending,
_ => unreachable!("initialize runs only while uninitialized"),
};
let Pending {
registrations,
configure_initialize,
on_initialize,
layers,
concurrency_limit,
} = pending;
let mut registrar = InitializeRegistrar::new(registrations);
let committed = match configure_initialize {
Some(callback) => callback(¶ms, &mut registrar),
None => Ok(()),
}
.and_then(|()| registrar.commit().map_err(LspError::internal));
let registrations = match committed {
Ok(registrations) => registrations,
Err(_err) => {
self.inbound.complete(
&self.out_tx,
reservation,
Err(LspError::internal("initialization failed")),
);
return Flow::Close(CloseCause::InitializeFailed);
}
};
let router = Arc::new(registrations.freeze());
let established = Workspace::from_params(¶ms);
self.workspace = Some(established.clone());
let position_encoding = self.documents.negotiate_position_encoding(¶ms);
let mut capabilities = router.capabilities();
capabilities.position_encoding = Some(position_encoding);
capabilities.text_document_sync = Some(TextDocumentSyncCapability::Kind(
TextDocumentSyncKind::INCREMENTAL,
));
let server_info = match on_initialize {
Some(hook) => {
let ctx = Context::for_request(
reservation.id.clone(),
span.clone(),
self.client.clone(),
self.documents.clone(),
)
.with_workspace(established);
match hook(
Arc::clone(&self.state),
ctx,
params,
self.session.child_token(),
)
.instrument(span.clone())
.await
{
Ok(server_info) => server_info,
Err(err) => {
self.inbound.complete(&self.out_tx, reservation, Err(err));
return Flow::Close(CloseCause::InitializeFailed);
}
}
}
None => None,
};
self.inbound.complete(
&self.out_tx,
reservation,
encode_body(&InitializeResult {
capabilities,
server_info,
}),
);
self.lifecycle = Lifecycle::Running(build_service_stack(router, layers, concurrency_limit));
Flow::Continue
}
fn spawn_service_request(
&mut self,
service: UserService<S>,
span: Span,
reservation: Reservation,
method: String,
params: serde_json::Value,
cancellation: CancellationToken,
) {
let state = Arc::clone(&self.state);
let documents = self.documents.clone();
let workspace = self.workspace.clone();
let out_tx = self.out_tx.clone();
let client = self.client.clone();
let inbound = self.inbound.clone();
self.tasks.spawn(async move {
let id = reservation.id.clone();
let ctx = attach_workspace(
Context::for_request(id.clone(), span, client, documents),
&workspace,
)
.with_cancellation(cancellation.clone());
let call = IncomingCall::request(method, id, params, ctx, state);
let result = match select(
Box::pin(service.call(call)),
Box::pin(cancellation.cancelled()),
)
.await
{
Either::Left((result, _)) => result,
Either::Right(((), _)) => ServiceResult::Error(LspError::RequestCancelled),
};
let result = match result {
ServiceResult::Response(value) => encode_body(&value),
ServiceResult::Error(error) => Err(error),
ServiceResult::NoResponse => {
Err(LspError::internal("request service returned no response"))
}
};
inbound.complete(&out_tx, reservation, result);
});
}
async fn close(&mut self) {
if matches!(self.lifecycle, Lifecycle::Exited) {
return;
}
self.lifecycle = Lifecycle::Exited;
self.client.close_connection();
self.session.cancel();
self.client.outbound_registry().close_all();
self.inbound.close_all();
self.tasks.abort_and_join().await;
self.client.close_outbound();
if let Some(send_task) = self.send_task.take() {
send_task.join().await;
}
}
}
impl<S, R> Drop for ProtocolEngine<S, R> {
fn drop(&mut self) {
for handle in self.tasks.handles.iter().chain(self.send_task.iter()) {
handle.abort();
}
}
}
enum Flow {
Continue,
Close(CloseCause),
}
fn attach_workspace(ctx: Context, workspace: &Option<Workspace>) -> Context {
match workspace {
Some(ws) => ctx.with_workspace(ws.clone()),
None => ctx,
}
}
fn enqueue_encoded(
out_tx: &UnboundedSender<RawMessage>,
id: RequestId,
result: std::result::Result<Bytes, LspError>,
) {
let response = match result {
Ok(bytes) => RawMessage::Response {
id,
result: Ok(bytes),
},
Err(err) => error_response(id, &err),
};
let _ = out_tx.send(response);
}
fn error_response(id: RequestId, err: &LspError) -> RawMessage {
RawMessage::Response {
id,
result: Err(JsonRpcError {
code: err.code(),
message: err.message(),
data: err.data().cloned(),
}),
}
}
fn enqueue_error(out_tx: &UnboundedSender<RawMessage>, id: RequestId, err: LspError) {
let _ = out_tx.send(error_response(id, &err));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_first_requester_records_the_cause_and_later_ones_do_not_replace_it() {
let close = CloseSignal::new();
assert!(!close.requested().is_cancelled());
close.request(CloseCause::WriterFailed);
close.request(CloseCause::Exit { code: 0 });
close.request(CloseCause::ReaderEof);
assert!(
close.requested().is_cancelled(),
"requesting close wakes the read-loop"
);
assert!(
matches!(close.take_cause(), Some(CloseCause::WriterFailed)),
"the first cause requested is the one reported"
);
assert!(
close.take_cause().is_none(),
"the cause is taken once, by the read-loop that ran the close"
);
}
#[test]
fn every_cause_maps_to_one_outcome_or_a_transport_error() {
assert_eq!(
CloseCause::Exit { code: 0 }.into_result().unwrap(),
Outcome::Exit { code: 0 }
);
assert_eq!(
CloseCause::ReaderEof.into_result().unwrap(),
Outcome::TransportClosed
);
assert_eq!(
CloseCause::WriterFailed.into_result().unwrap(),
Outcome::WriterFailed
);
assert_eq!(
CloseCause::InitializeFailed.into_result().unwrap(),
Outcome::InitializeFailed
);
assert!(matches!(
CloseCause::ReaderFailed(TransportError::Malformed("bad".into())).into_result(),
Err(Error::Transport(_))
));
}
#[test]
fn a_stale_reservation_cannot_claim_a_reused_request_id() {
let (out_tx, mut out_rx) = mpsc::unbounded_channel();
let registry = InboundRegistry::default();
let id = RequestId::Number(2);
let first = registry
.reserve(id.clone(), Some(CancellationToken::new()))
.expect("the id is free");
assert!(
registry
.reserve(id.clone(), Some(CancellationToken::new()))
.is_none(),
"an in-flight id is not reserved twice"
);
registry.complete_cancellation(&out_tx, &id);
let second = registry
.reserve(id.clone(), Some(CancellationToken::new()))
.expect("the id is free once the first request is answered");
registry.complete(&out_tx, first, encode_body(&"race"));
registry.complete(&out_tx, second, encode_body(&"reused"));
assert_eq!(
out_rx.try_recv().unwrap().id(),
Some(&id),
"the cancellation answers the first request"
);
let answer = out_rx.try_recv().expect("the second request is answered");
match answer {
RawMessage::Response {
result: Ok(body), ..
} => assert_eq!(
serde_json::from_slice::<String>(&body).unwrap(),
"reused",
"the second request gets its own result, not the stale one"
),
other => panic!("expected a success response, got {other:?}"),
}
assert!(
out_rx.try_recv().is_err(),
"the stale reservation enqueued nothing"
);
}
#[test]
fn only_a_shutdown_exit_reports_code_zero() {
assert_eq!(Outcome::Exit { code: 0 }.code(), 0);
assert_eq!(Outcome::Exit { code: 1 }.code(), 1);
assert_eq!(Outcome::TransportClosed.code(), 1);
assert_eq!(Outcome::WriterFailed.code(), 1);
assert_eq!(Outcome::InitializeFailed.code(), 1);
}
}