-
Notifications
You must be signed in to change notification settings - Fork 1.7k
[ty] Fix server hang after shutdown request #18414
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
268adf2
8a2a4fb
cb324ed
0ff0094
72a80d7
4abba69
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,6 +49,15 @@ pub(super) fn request(req: server::Request) -> Task { | |
| >( | ||
| req, BackgroundSchedule::LatencySensitive | ||
| ), | ||
| lsp_types::request::Shutdown::METHOD => { | ||
|
||
| tracing::debug!("Received shutdown request, waiting for shutdown notification."); | ||
MichaReiser marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| Ok(Task::local(move |session, client| { | ||
| session.set_shutdown_requested(true); | ||
| if let Err(error) = client.respond(&req.id, Ok(())) { | ||
| tracing::debug!("Failed to send shutdown response: {error}"); | ||
| } | ||
| })) | ||
| } | ||
|
|
||
| method => { | ||
| tracing::warn!("Received request {method} which does not have a handler"); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ use crate::server::{Server, api}; | |
| use crate::session::client::Client; | ||
| use crossbeam::select; | ||
| use lsp_server::Message; | ||
| use lsp_types::notification::Notification; | ||
| use lsp_types::{DidChangeWatchedFilesRegistrationOptions, FileSystemWatcher}; | ||
|
|
||
| pub(crate) type MainLoopSender = crossbeam::channel::Sender<Event>; | ||
|
|
@@ -13,7 +14,7 @@ impl Server { | |
| pub(super) fn main_loop(&mut self) -> crate::Result<()> { | ||
| self.initialize(&Client::new( | ||
| self.main_loop_sender.clone(), | ||
| self.connection.sender(), | ||
| self.connection.sender.clone(), | ||
| )); | ||
|
|
||
| let mut scheduler = Scheduler::new(self.worker_threads); | ||
|
|
@@ -25,19 +26,48 @@ impl Server { | |
|
|
||
| match next_event { | ||
| Event::Message(msg) => { | ||
| if self.connection.handle_shutdown(&msg)? { | ||
| break; | ||
| } | ||
| let client = Client::new( | ||
| self.main_loop_sender.clone(), | ||
| self.connection.sender.clone(), | ||
| ); | ||
|
|
||
| let task = match msg { | ||
| Message::Request(req) => { | ||
| self.session | ||
| .request_queue_mut() | ||
| .incoming_mut() | ||
| .register(req.id.clone(), req.method.clone()); | ||
|
|
||
| if self.session.is_shutdown_requested() { | ||
| tracing::warn!( | ||
| "Received request after server shutdown was requested, discarding" | ||
| ); | ||
| client.respond_err( | ||
| req.id, | ||
| lsp_server::ResponseError { | ||
| code: lsp_server::ErrorCode::InvalidRequest as i32, | ||
| message: "Shutdown already requested.".to_owned(), | ||
MichaReiser marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| data: None, | ||
| }, | ||
| )?; | ||
| continue; | ||
| } | ||
|
|
||
| api::request(req) | ||
MichaReiser marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| Message::Notification(notification) => api::notification(notification), | ||
| Message::Notification(notification) => { | ||
| if notification.method == lsp_types::notification::Exit::METHOD { | ||
| tracing::debug!("Received exit notification, exiting"); | ||
| if !self.session.is_shutdown_requested() { | ||
| tracing::warn!( | ||
| "Received exit notification before shutdown request" | ||
| ); | ||
| } | ||
MichaReiser marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return Ok(()); | ||
| } | ||
|
|
||
| api::notification(notification) | ||
| } | ||
|
|
||
| // Handle the response from the client to a server request | ||
| Message::Response(response) => { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm assuming we don't need to modify anything in this branch mainly because the server would stop handling any request / notification from the client when shutdown has been initiated and so the server wouldn't try to send any request to the client which means there shouldn't be any client responses to handle during the shutdown process.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could add the handling to notifications, because they're explicitly listed in the LSP specification. It's less clear to me if client responses are excluded too and it's quiet possible that the server might send a request from a background task when the shutdown was already initialized.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll leave it as is. The LSP specification only mentions:
Processing notifications and responses is a bit wastefull but shouldn't harm too much (they are all very short) |
||
|
|
@@ -59,8 +89,6 @@ impl Server { | |
| } | ||
| }; | ||
|
|
||
| let client = | ||
| Client::new(self.main_loop_sender.clone(), self.connection.sender()); | ||
| scheduler.dispatch(task, &mut self.session, client); | ||
| } | ||
| Event::Action(action) => match action { | ||
|
|
@@ -75,7 +103,7 @@ impl Server { | |
| let duration = start_time.elapsed(); | ||
| tracing::trace!(name: "message response", method, %response.id, duration = format_args!("{:0.2?}", duration)); | ||
|
|
||
| self.connection.send(Message::Response(response))?; | ||
| self.connection.sender.send(Message::Response(response))?; | ||
| } else { | ||
| tracing::trace!( | ||
| "Ignoring response for canceled request id={}", | ||
|
|
@@ -113,8 +141,8 @@ impl Server { | |
| /// Returns `Ok(None)` if the client connection is closed. | ||
| fn next_event(&self) -> Result<Option<Event>, crossbeam::channel::RecvError> { | ||
| let next = select!( | ||
MichaReiser marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| recv(self.connection.incoming()) -> msg => msg.map(Event::Message), | ||
| recv(self.main_loop_receiver) -> event => return Ok(event.ok()), | ||
| recv(self.connection.receiver) -> msg => return Ok(msg.ok().map(Event::Message)), | ||
MichaReiser marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| recv(self.main_loop_receiver) -> event => event, | ||
| ); | ||
|
|
||
| next.map(Some) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the main fix. Values assigned to
_are dropped immediately. That means, the panic hook was always restored immediately (and then overridden again).It looks like Rust 1.88 will add a lint for this source
The fix here is to assign the panic hook to a variable other than
_. TheDrophandler then restores the original panic hook, which in turn, drops our custom panic hook handler that holds on to the client