mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 06:11:21 +00:00
[ty] Gracefully handle salsa cancellations and panics in background request handlers (#18254)
This commit is contained in:
parent
d51f6940fe
commit
d8216fa328
9 changed files with 146 additions and 95 deletions
|
@ -1,10 +1,5 @@
|
|||
//! Scheduling, I/O, and API endpoints.
|
||||
|
||||
use std::num::NonZeroUsize;
|
||||
// The new PanicInfoHook name requires MSRV >= 1.82
|
||||
#[expect(deprecated)]
|
||||
use std::panic::PanicInfo;
|
||||
|
||||
use lsp_server::Message;
|
||||
use lsp_types::{
|
||||
ClientCapabilities, DiagnosticOptions, DiagnosticServerCapabilities,
|
||||
|
@ -13,6 +8,8 @@ use lsp_types::{
|
|||
TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions,
|
||||
TypeDefinitionProviderCapability, Url,
|
||||
};
|
||||
use std::num::NonZeroUsize;
|
||||
use std::panic::PanicHookInfo;
|
||||
|
||||
use self::connection::{Connection, ConnectionInitializer};
|
||||
use self::schedule::event_loop_thread;
|
||||
|
@ -125,9 +122,7 @@ impl Server {
|
|||
}
|
||||
|
||||
pub(crate) fn run(self) -> crate::Result<()> {
|
||||
// The new PanicInfoHook name requires MSRV >= 1.82
|
||||
#[expect(deprecated)]
|
||||
type PanicHook = Box<dyn Fn(&PanicInfo<'_>) + 'static + Sync + Send>;
|
||||
type PanicHook = Box<dyn Fn(&PanicHookInfo<'_>) + 'static + Sync + Send>;
|
||||
struct RestorePanicHook {
|
||||
hook: Option<PanicHook>,
|
||||
}
|
||||
|
|
|
@ -3,42 +3,41 @@ use crate::session::Session;
|
|||
use crate::system::{AnySystemPath, url_to_any_system_path};
|
||||
use anyhow::anyhow;
|
||||
use lsp_server as server;
|
||||
use lsp_server::RequestId;
|
||||
use lsp_types::notification::Notification;
|
||||
use ruff_db::panic::PanicError;
|
||||
use std::panic::UnwindSafe;
|
||||
|
||||
mod diagnostics;
|
||||
mod notifications;
|
||||
mod requests;
|
||||
mod traits;
|
||||
|
||||
use notifications as notification;
|
||||
use requests as request;
|
||||
|
||||
use self::traits::{NotificationHandler, RequestHandler};
|
||||
|
||||
use super::{Result, client::Responder, schedule::BackgroundSchedule};
|
||||
|
||||
pub(super) fn request<'a>(req: server::Request) -> Task<'a> {
|
||||
let id = req.id.clone();
|
||||
|
||||
match req.method.as_str() {
|
||||
request::DocumentDiagnosticRequestHandler::METHOD => background_request_task::<
|
||||
request::DocumentDiagnosticRequestHandler,
|
||||
requests::DocumentDiagnosticRequestHandler::METHOD => background_request_task::<
|
||||
requests::DocumentDiagnosticRequestHandler,
|
||||
>(
|
||||
req, BackgroundSchedule::Worker
|
||||
),
|
||||
request::GotoTypeDefinitionRequestHandler::METHOD => background_request_task::<
|
||||
request::GotoTypeDefinitionRequestHandler,
|
||||
requests::GotoTypeDefinitionRequestHandler::METHOD => background_request_task::<
|
||||
requests::GotoTypeDefinitionRequestHandler,
|
||||
>(
|
||||
req, BackgroundSchedule::Worker
|
||||
),
|
||||
request::HoverRequestHandler::METHOD => {
|
||||
background_request_task::<request::HoverRequestHandler>(req, BackgroundSchedule::Worker)
|
||||
}
|
||||
request::InlayHintRequestHandler::METHOD => background_request_task::<
|
||||
request::InlayHintRequestHandler,
|
||||
requests::HoverRequestHandler::METHOD => background_request_task::<
|
||||
requests::HoverRequestHandler,
|
||||
>(req, BackgroundSchedule::Worker),
|
||||
request::CompletionRequestHandler::METHOD => background_request_task::<
|
||||
request::CompletionRequestHandler,
|
||||
requests::InlayHintRequestHandler::METHOD => background_request_task::<
|
||||
requests::InlayHintRequestHandler,
|
||||
>(req, BackgroundSchedule::Worker),
|
||||
requests::CompletionRequestHandler::METHOD => background_request_task::<
|
||||
requests::CompletionRequestHandler,
|
||||
>(
|
||||
req, BackgroundSchedule::LatencySensitive
|
||||
),
|
||||
|
@ -64,23 +63,23 @@ pub(super) fn request<'a>(req: server::Request) -> Task<'a> {
|
|||
|
||||
pub(super) fn notification<'a>(notif: server::Notification) -> Task<'a> {
|
||||
match notif.method.as_str() {
|
||||
notification::DidCloseTextDocumentHandler::METHOD => {
|
||||
local_notification_task::<notification::DidCloseTextDocumentHandler>(notif)
|
||||
notifications::DidCloseTextDocumentHandler::METHOD => {
|
||||
local_notification_task::<notifications::DidCloseTextDocumentHandler>(notif)
|
||||
}
|
||||
notification::DidOpenTextDocumentHandler::METHOD => {
|
||||
local_notification_task::<notification::DidOpenTextDocumentHandler>(notif)
|
||||
notifications::DidOpenTextDocumentHandler::METHOD => {
|
||||
local_notification_task::<notifications::DidOpenTextDocumentHandler>(notif)
|
||||
}
|
||||
notification::DidChangeTextDocumentHandler::METHOD => {
|
||||
local_notification_task::<notification::DidChangeTextDocumentHandler>(notif)
|
||||
notifications::DidChangeTextDocumentHandler::METHOD => {
|
||||
local_notification_task::<notifications::DidChangeTextDocumentHandler>(notif)
|
||||
}
|
||||
notification::DidOpenNotebookHandler::METHOD => {
|
||||
local_notification_task::<notification::DidOpenNotebookHandler>(notif)
|
||||
notifications::DidOpenNotebookHandler::METHOD => {
|
||||
local_notification_task::<notifications::DidOpenNotebookHandler>(notif)
|
||||
}
|
||||
notification::DidCloseNotebookHandler::METHOD => {
|
||||
local_notification_task::<notification::DidCloseNotebookHandler>(notif)
|
||||
notifications::DidCloseNotebookHandler::METHOD => {
|
||||
local_notification_task::<notifications::DidCloseNotebookHandler>(notif)
|
||||
}
|
||||
notification::DidChangeWatchedFiles::METHOD => {
|
||||
local_notification_task::<notification::DidChangeWatchedFiles>(notif)
|
||||
notifications::DidChangeWatchedFiles::METHOD => {
|
||||
local_notification_task::<notifications::DidChangeWatchedFiles>(notif)
|
||||
}
|
||||
lsp_types::notification::SetTrace::METHOD => {
|
||||
tracing::trace!("Ignoring `setTrace` notification");
|
||||
|
@ -103,23 +102,25 @@ pub(super) fn notification<'a>(notif: server::Notification) -> Task<'a> {
|
|||
|
||||
fn _local_request_task<'a, R: traits::SyncRequestHandler>(
|
||||
req: server::Request,
|
||||
) -> super::Result<Task<'a>> {
|
||||
) -> super::Result<Task<'a>>
|
||||
where
|
||||
<<R as RequestHandler>::RequestType as lsp_types::request::Request>::Params: UnwindSafe,
|
||||
{
|
||||
let (id, params) = cast_request::<R>(req)?;
|
||||
Ok(Task::local(|session, notifier, requester, responder| {
|
||||
let _span = tracing::trace_span!("request", %id, method = R::METHOD).entered();
|
||||
let _span = tracing::debug_span!("request", %id, method = R::METHOD).entered();
|
||||
let result = R::run(session, notifier, requester, params);
|
||||
respond::<R>(id, result, &responder);
|
||||
}))
|
||||
}
|
||||
|
||||
// TODO(micha): Calls to `db` could panic if the db gets mutated while this task is running.
|
||||
// We should either wrap `R::run_with_snapshot` with a salsa catch cancellation handler or
|
||||
// use `SemanticModel` instead of passing `db` which uses a Result for all it's methods
|
||||
// that propagate cancellations.
|
||||
fn background_request_task<'a, R: traits::BackgroundDocumentRequestHandler>(
|
||||
req: server::Request,
|
||||
schedule: BackgroundSchedule,
|
||||
) -> super::Result<Task<'a>> {
|
||||
) -> super::Result<Task<'a>>
|
||||
where
|
||||
<<R as RequestHandler>::RequestType as lsp_types::request::Request>::Params: UnwindSafe,
|
||||
{
|
||||
let (id, params) = cast_request::<R>(req)?;
|
||||
Ok(Task::background(schedule, move |session: &Session| {
|
||||
let url = R::document_url(¶ms).into_owned();
|
||||
|
@ -128,6 +129,7 @@ fn background_request_task<'a, R: traits::BackgroundDocumentRequestHandler>(
|
|||
tracing::warn!("Ignoring request for invalid `{url}`");
|
||||
return Box::new(|_, _| {});
|
||||
};
|
||||
|
||||
let db = match &path {
|
||||
AnySystemPath::System(path) => match session.project_db_for_path(path.as_std_path()) {
|
||||
Some(db) => db.clone(),
|
||||
|
@ -142,19 +144,55 @@ fn background_request_task<'a, R: traits::BackgroundDocumentRequestHandler>(
|
|||
};
|
||||
|
||||
Box::new(move |notifier, responder| {
|
||||
let _span = tracing::trace_span!("request", %id, method = R::METHOD).entered();
|
||||
let result = R::run_with_snapshot(&db, snapshot, notifier, params);
|
||||
respond::<R>(id, result, &responder);
|
||||
let _span = tracing::debug_span!("request", %id, method = R::METHOD).entered();
|
||||
let result = ruff_db::panic::catch_unwind(|| {
|
||||
R::run_with_snapshot(&db, snapshot, notifier, params)
|
||||
});
|
||||
|
||||
if let Some(response) = request_result_to_response(&id, &responder, result) {
|
||||
respond::<R>(id, response, &responder);
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
fn request_result_to_response<R>(
|
||||
id: &RequestId,
|
||||
responder: &Responder,
|
||||
result: std::result::Result<Result<R>, PanicError>,
|
||||
) -> Option<Result<R>> {
|
||||
match result {
|
||||
Ok(response) => Some(response),
|
||||
Err(error) => {
|
||||
if error.payload.downcast_ref::<salsa::Cancelled>().is_some() {
|
||||
// Request was cancelled by Salsa. TODO: Retry
|
||||
respond_silent_error(
|
||||
id.clone(),
|
||||
responder,
|
||||
Error {
|
||||
code: lsp_server::ErrorCode::ContentModified,
|
||||
error: anyhow!("content modified"),
|
||||
},
|
||||
);
|
||||
None
|
||||
} else {
|
||||
let message = format!("request handler {error}");
|
||||
|
||||
Some(Err(Error {
|
||||
code: lsp_server::ErrorCode::InternalError,
|
||||
error: anyhow!(message),
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn local_notification_task<'a, N: traits::SyncNotificationHandler>(
|
||||
notif: server::Notification,
|
||||
) -> super::Result<Task<'a>> {
|
||||
let (id, params) = cast_notification::<N>(notif)?;
|
||||
Ok(Task::local(move |session, notifier, requester, _| {
|
||||
let _span = tracing::trace_span!("notification", method = N::METHOD).entered();
|
||||
let _span = tracing::debug_span!("notification", method = N::METHOD).entered();
|
||||
if let Err(err) = N::run(session, notifier, requester, params) {
|
||||
tracing::error!("An error occurred while running {id}: {err}");
|
||||
show_err_msg!("ty encountered a problem. Check the logs for more details.");
|
||||
|
@ -163,10 +201,15 @@ fn local_notification_task<'a, N: traits::SyncNotificationHandler>(
|
|||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
fn background_notification_thread<'a, N: traits::BackgroundDocumentNotificationHandler>(
|
||||
fn background_notification_thread<'a, N>(
|
||||
req: server::Notification,
|
||||
schedule: BackgroundSchedule,
|
||||
) -> super::Result<Task<'a>> {
|
||||
) -> super::Result<Task<'a>>
|
||||
where
|
||||
N: traits::BackgroundDocumentNotificationHandler,
|
||||
<<N as NotificationHandler>::NotificationType as lsp_types::notification::Notification>::Params:
|
||||
UnwindSafe,
|
||||
{
|
||||
let (id, params) = cast_notification::<N>(req)?;
|
||||
Ok(Task::background(schedule, move |session: &Session| {
|
||||
let url = N::document_url(¶ms);
|
||||
|
@ -177,8 +220,20 @@ fn background_notification_thread<'a, N: traits::BackgroundDocumentNotificationH
|
|||
return Box::new(|_, _| {});
|
||||
};
|
||||
Box::new(move |notifier, _| {
|
||||
let _span = tracing::trace_span!("notification", method = N::METHOD).entered();
|
||||
if let Err(err) = N::run_with_snapshot(snapshot, notifier, params) {
|
||||
let _span = tracing::debug_span!("notification", method = N::METHOD).entered();
|
||||
|
||||
let result = match ruff_db::panic::catch_unwind(|| {
|
||||
N::run_with_snapshot(snapshot, notifier, params)
|
||||
}) {
|
||||
Ok(result) => result,
|
||||
Err(panic) => {
|
||||
tracing::error!("An error occurred while running {id}: {panic}");
|
||||
show_err_msg!("ty encountered a panic. Check the logs for more details.");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = result {
|
||||
tracing::error!("An error occurred while running {id}: {err}");
|
||||
show_err_msg!("ty encountered a problem. Check the logs for more details.");
|
||||
}
|
||||
|
@ -198,6 +253,7 @@ fn cast_request<Req>(
|
|||
)>
|
||||
where
|
||||
Req: traits::RequestHandler,
|
||||
<<Req as RequestHandler>::RequestType as lsp_types::request::Request>::Params: UnwindSafe,
|
||||
{
|
||||
request
|
||||
.extract(Req::METHOD)
|
||||
|
@ -232,6 +288,14 @@ fn respond<Req>(
|
|||
}
|
||||
}
|
||||
|
||||
/// Sends back an error response to the server using a [`Responder`] without showing a warning
|
||||
/// to the user.
|
||||
fn respond_silent_error(id: server::RequestId, responder: &Responder, error: Error) {
|
||||
if let Err(err) = responder.respond::<()>(id, Err(error)) {
|
||||
tracing::error!("Failed to send response: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to cast a serialized request from the server into
|
||||
/// a parameter type for a specific request handler.
|
||||
fn cast_notification<N>(
|
||||
|
@ -240,7 +304,9 @@ fn cast_notification<N>(
|
|||
(
|
||||
&'static str,
|
||||
<<N as traits::NotificationHandler>::NotificationType as lsp_types::notification::Notification>::Params,
|
||||
)> where N: traits::NotificationHandler{
|
||||
)> where
|
||||
N: traits::NotificationHandler,
|
||||
{
|
||||
Ok((
|
||||
N::METHOD,
|
||||
notification
|
||||
|
|
|
@ -41,13 +41,7 @@ pub(super) fn compute_diagnostics(
|
|||
return vec![];
|
||||
};
|
||||
|
||||
let diagnostics = match db.check_file(file) {
|
||||
Ok(diagnostics) => diagnostics,
|
||||
Err(cancelled) => {
|
||||
tracing::info!("Diagnostics computation {cancelled}");
|
||||
return vec![];
|
||||
}
|
||||
};
|
||||
let diagnostics = db.check_file(file);
|
||||
|
||||
diagnostics
|
||||
.as_slice()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue