[ty] Gracefully handle salsa cancellations and panics in background request handlers (#18254)

This commit is contained in:
Micha Reiser 2025-05-26 14:37:49 +02:00 committed by GitHub
parent d51f6940fe
commit d8216fa328
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 146 additions and 95 deletions

View file

@ -131,7 +131,7 @@ fn benchmark_incremental(criterion: &mut Criterion) {
fn setup() -> Case { fn setup() -> Case {
let case = setup_tomllib_case(); let case = setup_tomllib_case();
let result: Vec<_> = case.db.check().unwrap(); let result: Vec<_> = case.db.check();
assert_diagnostics(&case.db, &result, EXPECTED_TOMLLIB_DIAGNOSTICS); assert_diagnostics(&case.db, &result, EXPECTED_TOMLLIB_DIAGNOSTICS);
@ -159,7 +159,7 @@ fn benchmark_incremental(criterion: &mut Criterion) {
None, None,
); );
let result = db.check().unwrap(); let result = db.check();
assert_eq!(result.len(), EXPECTED_TOMLLIB_DIAGNOSTICS.len()); assert_eq!(result.len(), EXPECTED_TOMLLIB_DIAGNOSTICS.len());
} }
@ -179,7 +179,7 @@ fn benchmark_cold(criterion: &mut Criterion) {
setup_tomllib_case, setup_tomllib_case,
|case| { |case| {
let Case { db, .. } = case; let Case { db, .. } = case;
let result: Vec<_> = db.check().unwrap(); let result: Vec<_> = db.check();
assert_diagnostics(db, &result, EXPECTED_TOMLLIB_DIAGNOSTICS); assert_diagnostics(db, &result, EXPECTED_TOMLLIB_DIAGNOSTICS);
}, },
@ -293,7 +293,7 @@ fn benchmark_many_string_assignments(criterion: &mut Criterion) {
}, },
|case| { |case| {
let Case { db, .. } = case; let Case { db, .. } = case;
let result = db.check().unwrap(); let result = db.check();
assert_eq!(result.len(), 0); assert_eq!(result.len(), 0);
}, },
BatchSize::SmallInput, BatchSize::SmallInput,
@ -339,7 +339,7 @@ fn benchmark_many_tuple_assignments(criterion: &mut Criterion) {
}, },
|case| { |case| {
let Case { db, .. } = case; let Case { db, .. } = case;
let result = db.check().unwrap(); let result = db.check();
assert_eq!(result.len(), 0); assert_eq!(result.len(), 0);
}, },
BatchSize::SmallInput, BatchSize::SmallInput,

View file

@ -1,3 +1,4 @@
use std::any::Any;
use std::backtrace::BacktraceStatus; use std::backtrace::BacktraceStatus;
use std::cell::Cell; use std::cell::Cell;
use std::panic::Location; use std::panic::Location;
@ -24,17 +25,25 @@ impl Payload {
None None
} }
} }
pub fn downcast_ref<R: Any>(&self) -> Option<&R> {
self.0.downcast_ref::<R>()
}
} }
impl std::fmt::Display for PanicError { impl std::fmt::Display for PanicError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "panicked at")?; write!(f, "panicked at")?;
if let Some(location) = &self.location { if let Some(location) = &self.location {
write!(f, " {location}")?; write!(f, " {location}")?;
} }
if let Some(payload) = self.payload.as_str() { if let Some(payload) = self.payload.as_str() {
write!(f, ":\n{payload}")?; write!(f, ":\n{payload}")?;
} }
if let Some(query_trace) = self.salsa_backtrace.as_ref() {
let _ = writeln!(f, "{query_trace}");
}
if let Some(backtrace) = &self.backtrace { if let Some(backtrace) = &self.backtrace {
match backtrace.status() { match backtrace.status() {
BacktraceStatus::Disabled => { BacktraceStatus::Disabled => {
@ -49,6 +58,7 @@ impl std::fmt::Display for PanicError {
_ => {} _ => {}
} }
} }
Ok(()) Ok(())
} }
} }

View file

@ -243,12 +243,14 @@ impl MainLoop {
MainLoopMessage::CheckWorkspace => { MainLoopMessage::CheckWorkspace => {
let db = db.clone(); let db = db.clone();
let sender = self.sender.clone(); let sender = self.sender.clone();
let mut reporter = R::default();
// Spawn a new task that checks the project. This needs to be done in a separate thread // Spawn a new task that checks the project. This needs to be done in a separate thread
// to prevent blocking the main loop here. // to prevent blocking the main loop here.
rayon::spawn(move || { rayon::spawn(move || {
match db.check_with_reporter(&mut reporter) { match salsa::Cancelled::catch(|| {
let mut reporter = R::default();
db.check_with_reporter(&mut reporter)
}) {
Ok(result) => { Ok(result) => {
// Send the result back to the main loop for printing. // Send the result back to the main loop for printing.
sender sender

View file

@ -1135,7 +1135,7 @@ print(sys.last_exc, os.getegid())
Ok(()) Ok(())
})?; })?;
let diagnostics = case.db.check().context("Failed to check project.")?; let diagnostics = case.db.check();
assert_eq!(diagnostics.len(), 2); assert_eq!(diagnostics.len(), 2);
assert_eq!( assert_eq!(
@ -1160,7 +1160,7 @@ print(sys.last_exc, os.getegid())
}) })
.expect("Search path settings to be valid"); .expect("Search path settings to be valid");
let diagnostics = case.db.check().context("Failed to check project.")?; let diagnostics = case.db.check();
assert!(diagnostics.is_empty()); assert!(diagnostics.is_empty());
Ok(()) Ok(())
@ -1763,10 +1763,7 @@ fn changes_to_user_configuration() -> anyhow::Result<()> {
let foo = case let foo = case
.system_file(case.project_path("foo.py")) .system_file(case.project_path("foo.py"))
.expect("foo.py to exist"); .expect("foo.py to exist");
let diagnostics = case let diagnostics = case.db().check_file(foo);
.db()
.check_file(foo)
.context("Failed to check project.")?;
assert!( assert!(
diagnostics.is_empty(), diagnostics.is_empty(),
@ -1786,10 +1783,7 @@ fn changes_to_user_configuration() -> anyhow::Result<()> {
case.apply_changes(changes); case.apply_changes(changes);
let diagnostics = case let diagnostics = case.db().check_file(foo);
.db()
.check_file(foo)
.context("Failed to check project.")?;
assert!( assert!(
diagnostics.len() == 1, diagnostics.len() == 1,

View file

@ -8,8 +8,8 @@ use ruff_db::files::{File, Files};
use ruff_db::system::System; use ruff_db::system::System;
use ruff_db::vendored::VendoredFileSystem; use ruff_db::vendored::VendoredFileSystem;
use ruff_db::{Db as SourceDb, Upcast}; use ruff_db::{Db as SourceDb, Upcast};
use salsa::Event;
use salsa::plumbing::ZalsaDatabase; use salsa::plumbing::ZalsaDatabase;
use salsa::{Cancelled, Event};
use ty_ide::Db as IdeDb; use ty_ide::Db as IdeDb;
use ty_python_semantic::lint::{LintRegistry, RuleSelection}; use ty_python_semantic::lint::{LintRegistry, RuleSelection};
use ty_python_semantic::{Db as SemanticDb, Program}; use ty_python_semantic::{Db as SemanticDb, Program};
@ -76,24 +76,21 @@ impl ProjectDatabase {
} }
/// Checks all open files in the project and its dependencies. /// Checks all open files in the project and its dependencies.
pub fn check(&self) -> Result<Vec<Diagnostic>, Cancelled> { pub fn check(&self) -> Vec<Diagnostic> {
let mut reporter = DummyReporter; let mut reporter = DummyReporter;
let reporter = AssertUnwindSafe(&mut reporter as &mut dyn Reporter); let reporter = AssertUnwindSafe(&mut reporter as &mut dyn Reporter);
self.with_db(|db| db.project().check(db, reporter)) self.project().check(self, reporter)
} }
/// Checks all open files in the project and its dependencies, using the given reporter. /// Checks all open files in the project and its dependencies, using the given reporter.
pub fn check_with_reporter( pub fn check_with_reporter(&self, reporter: &mut dyn Reporter) -> Vec<Diagnostic> {
&self,
reporter: &mut dyn Reporter,
) -> Result<Vec<Diagnostic>, Cancelled> {
let reporter = AssertUnwindSafe(reporter); let reporter = AssertUnwindSafe(reporter);
self.with_db(|db| db.project().check(db, reporter)) self.project().check(self, reporter)
} }
#[tracing::instrument(level = "debug", skip(self))] #[tracing::instrument(level = "debug", skip(self))]
pub fn check_file(&self, file: File) -> Result<Vec<Diagnostic>, Cancelled> { pub fn check_file(&self, file: File) -> Vec<Diagnostic> {
self.with_db(|db| self.project().check_file(db, file)) self.project().check_file(self, file)
} }
/// Returns a mutable reference to the system. /// Returns a mutable reference to the system.
@ -107,13 +104,6 @@ impl ProjectDatabase {
Arc::get_mut(&mut self.system) Arc::get_mut(&mut self.system)
.expect("ref count should be 1 because `zalsa_mut` drops all other DB references.") .expect("ref count should be 1 because `zalsa_mut` drops all other DB references.")
} }
pub(crate) fn with_db<F, T>(&self, f: F) -> Result<T, Cancelled>
where
F: FnOnce(&ProjectDatabase) -> T + std::panic::UnwindSafe,
{
Cancelled::catch(|| f(self))
}
} }
impl Upcast<dyn SemanticDb> for ProjectDatabase { impl Upcast<dyn SemanticDb> for ProjectDatabase {

View file

@ -1,10 +1,5 @@
//! Scheduling, I/O, and API endpoints. //! 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_server::Message;
use lsp_types::{ use lsp_types::{
ClientCapabilities, DiagnosticOptions, DiagnosticServerCapabilities, ClientCapabilities, DiagnosticOptions, DiagnosticServerCapabilities,
@ -13,6 +8,8 @@ use lsp_types::{
TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions,
TypeDefinitionProviderCapability, Url, TypeDefinitionProviderCapability, Url,
}; };
use std::num::NonZeroUsize;
use std::panic::PanicHookInfo;
use self::connection::{Connection, ConnectionInitializer}; use self::connection::{Connection, ConnectionInitializer};
use self::schedule::event_loop_thread; use self::schedule::event_loop_thread;
@ -125,9 +122,7 @@ impl Server {
} }
pub(crate) fn run(self) -> crate::Result<()> { pub(crate) fn run(self) -> crate::Result<()> {
// The new PanicInfoHook name requires MSRV >= 1.82 type PanicHook = Box<dyn Fn(&PanicHookInfo<'_>) + 'static + Sync + Send>;
#[expect(deprecated)]
type PanicHook = Box<dyn Fn(&PanicInfo<'_>) + 'static + Sync + Send>;
struct RestorePanicHook { struct RestorePanicHook {
hook: Option<PanicHook>, hook: Option<PanicHook>,
} }

View file

@ -3,42 +3,41 @@ use crate::session::Session;
use crate::system::{AnySystemPath, url_to_any_system_path}; use crate::system::{AnySystemPath, url_to_any_system_path};
use anyhow::anyhow; use anyhow::anyhow;
use lsp_server as server; use lsp_server as server;
use lsp_server::RequestId;
use lsp_types::notification::Notification; use lsp_types::notification::Notification;
use ruff_db::panic::PanicError;
use std::panic::UnwindSafe;
mod diagnostics; mod diagnostics;
mod notifications; mod notifications;
mod requests; mod requests;
mod traits; mod traits;
use notifications as notification;
use requests as request;
use self::traits::{NotificationHandler, RequestHandler}; use self::traits::{NotificationHandler, RequestHandler};
use super::{Result, client::Responder, schedule::BackgroundSchedule}; use super::{Result, client::Responder, schedule::BackgroundSchedule};
pub(super) fn request<'a>(req: server::Request) -> Task<'a> { pub(super) fn request<'a>(req: server::Request) -> Task<'a> {
let id = req.id.clone(); let id = req.id.clone();
match req.method.as_str() { match req.method.as_str() {
request::DocumentDiagnosticRequestHandler::METHOD => background_request_task::< requests::DocumentDiagnosticRequestHandler::METHOD => background_request_task::<
request::DocumentDiagnosticRequestHandler, requests::DocumentDiagnosticRequestHandler,
>( >(
req, BackgroundSchedule::Worker req, BackgroundSchedule::Worker
), ),
request::GotoTypeDefinitionRequestHandler::METHOD => background_request_task::< requests::GotoTypeDefinitionRequestHandler::METHOD => background_request_task::<
request::GotoTypeDefinitionRequestHandler, requests::GotoTypeDefinitionRequestHandler,
>( >(
req, BackgroundSchedule::Worker req, BackgroundSchedule::Worker
), ),
request::HoverRequestHandler::METHOD => { requests::HoverRequestHandler::METHOD => background_request_task::<
background_request_task::<request::HoverRequestHandler>(req, BackgroundSchedule::Worker) requests::HoverRequestHandler,
}
request::InlayHintRequestHandler::METHOD => background_request_task::<
request::InlayHintRequestHandler,
>(req, BackgroundSchedule::Worker), >(req, BackgroundSchedule::Worker),
request::CompletionRequestHandler::METHOD => background_request_task::< requests::InlayHintRequestHandler::METHOD => background_request_task::<
request::CompletionRequestHandler, requests::InlayHintRequestHandler,
>(req, BackgroundSchedule::Worker),
requests::CompletionRequestHandler::METHOD => background_request_task::<
requests::CompletionRequestHandler,
>( >(
req, BackgroundSchedule::LatencySensitive 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> { pub(super) fn notification<'a>(notif: server::Notification) -> Task<'a> {
match notif.method.as_str() { match notif.method.as_str() {
notification::DidCloseTextDocumentHandler::METHOD => { notifications::DidCloseTextDocumentHandler::METHOD => {
local_notification_task::<notification::DidCloseTextDocumentHandler>(notif) local_notification_task::<notifications::DidCloseTextDocumentHandler>(notif)
} }
notification::DidOpenTextDocumentHandler::METHOD => { notifications::DidOpenTextDocumentHandler::METHOD => {
local_notification_task::<notification::DidOpenTextDocumentHandler>(notif) local_notification_task::<notifications::DidOpenTextDocumentHandler>(notif)
} }
notification::DidChangeTextDocumentHandler::METHOD => { notifications::DidChangeTextDocumentHandler::METHOD => {
local_notification_task::<notification::DidChangeTextDocumentHandler>(notif) local_notification_task::<notifications::DidChangeTextDocumentHandler>(notif)
} }
notification::DidOpenNotebookHandler::METHOD => { notifications::DidOpenNotebookHandler::METHOD => {
local_notification_task::<notification::DidOpenNotebookHandler>(notif) local_notification_task::<notifications::DidOpenNotebookHandler>(notif)
} }
notification::DidCloseNotebookHandler::METHOD => { notifications::DidCloseNotebookHandler::METHOD => {
local_notification_task::<notification::DidCloseNotebookHandler>(notif) local_notification_task::<notifications::DidCloseNotebookHandler>(notif)
} }
notification::DidChangeWatchedFiles::METHOD => { notifications::DidChangeWatchedFiles::METHOD => {
local_notification_task::<notification::DidChangeWatchedFiles>(notif) local_notification_task::<notifications::DidChangeWatchedFiles>(notif)
} }
lsp_types::notification::SetTrace::METHOD => { lsp_types::notification::SetTrace::METHOD => {
tracing::trace!("Ignoring `setTrace` notification"); 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>( fn _local_request_task<'a, R: traits::SyncRequestHandler>(
req: server::Request, 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)?; let (id, params) = cast_request::<R>(req)?;
Ok(Task::local(|session, notifier, requester, responder| { 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); let result = R::run(session, notifier, requester, params);
respond::<R>(id, result, &responder); 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>( fn background_request_task<'a, R: traits::BackgroundDocumentRequestHandler>(
req: server::Request, req: server::Request,
schedule: BackgroundSchedule, 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)?; let (id, params) = cast_request::<R>(req)?;
Ok(Task::background(schedule, move |session: &Session| { Ok(Task::background(schedule, move |session: &Session| {
let url = R::document_url(&params).into_owned(); let url = R::document_url(&params).into_owned();
@ -128,6 +129,7 @@ fn background_request_task<'a, R: traits::BackgroundDocumentRequestHandler>(
tracing::warn!("Ignoring request for invalid `{url}`"); tracing::warn!("Ignoring request for invalid `{url}`");
return Box::new(|_, _| {}); return Box::new(|_, _| {});
}; };
let db = match &path { let db = match &path {
AnySystemPath::System(path) => match session.project_db_for_path(path.as_std_path()) { AnySystemPath::System(path) => match session.project_db_for_path(path.as_std_path()) {
Some(db) => db.clone(), Some(db) => db.clone(),
@ -142,19 +144,55 @@ fn background_request_task<'a, R: traits::BackgroundDocumentRequestHandler>(
}; };
Box::new(move |notifier, responder| { Box::new(move |notifier, 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_with_snapshot(&db, snapshot, notifier, params); let result = ruff_db::panic::catch_unwind(|| {
respond::<R>(id, result, &responder); 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>( fn local_notification_task<'a, N: traits::SyncNotificationHandler>(
notif: server::Notification, notif: server::Notification,
) -> super::Result<Task<'a>> { ) -> super::Result<Task<'a>> {
let (id, params) = cast_notification::<N>(notif)?; let (id, params) = cast_notification::<N>(notif)?;
Ok(Task::local(move |session, notifier, requester, _| { 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) { if let Err(err) = N::run(session, notifier, requester, params) {
tracing::error!("An error occurred while running {id}: {err}"); tracing::error!("An error occurred while running {id}: {err}");
show_err_msg!("ty encountered a problem. Check the logs for more details."); 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)] #[expect(dead_code)]
fn background_notification_thread<'a, N: traits::BackgroundDocumentNotificationHandler>( fn background_notification_thread<'a, N>(
req: server::Notification, req: server::Notification,
schedule: BackgroundSchedule, 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)?; let (id, params) = cast_notification::<N>(req)?;
Ok(Task::background(schedule, move |session: &Session| { Ok(Task::background(schedule, move |session: &Session| {
let url = N::document_url(&params); let url = N::document_url(&params);
@ -177,8 +220,20 @@ fn background_notification_thread<'a, N: traits::BackgroundDocumentNotificationH
return Box::new(|_, _| {}); return Box::new(|_, _| {});
}; };
Box::new(move |notifier, _| { Box::new(move |notifier, _| {
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_with_snapshot(snapshot, notifier, params) {
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}"); tracing::error!("An error occurred while running {id}: {err}");
show_err_msg!("ty encountered a problem. Check the logs for more details."); show_err_msg!("ty encountered a problem. Check the logs for more details.");
} }
@ -198,6 +253,7 @@ fn cast_request<Req>(
)> )>
where where
Req: traits::RequestHandler, Req: traits::RequestHandler,
<<Req as RequestHandler>::RequestType as lsp_types::request::Request>::Params: UnwindSafe,
{ {
request request
.extract(Req::METHOD) .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 /// Tries to cast a serialized request from the server into
/// a parameter type for a specific request handler. /// a parameter type for a specific request handler.
fn cast_notification<N>( fn cast_notification<N>(
@ -240,7 +304,9 @@ fn cast_notification<N>(
( (
&'static str, &'static str,
<<N as traits::NotificationHandler>::NotificationType as lsp_types::notification::Notification>::Params, <<N as traits::NotificationHandler>::NotificationType as lsp_types::notification::Notification>::Params,
)> where N: traits::NotificationHandler{ )> where
N: traits::NotificationHandler,
{
Ok(( Ok((
N::METHOD, N::METHOD,
notification notification

View file

@ -41,13 +41,7 @@ pub(super) fn compute_diagnostics(
return vec![]; return vec![];
}; };
let diagnostics = match db.check_file(file) { let diagnostics = db.check_file(file);
Ok(diagnostics) => diagnostics,
Err(cancelled) => {
tracing::info!("Diagnostics computation {cancelled}");
return vec![];
}
};
diagnostics diagnostics
.as_slice() .as_slice()

View file

@ -187,14 +187,14 @@ impl Workspace {
/// Checks a single file. /// Checks a single file.
#[wasm_bindgen(js_name = "checkFile")] #[wasm_bindgen(js_name = "checkFile")]
pub fn check_file(&self, file_id: &FileHandle) -> Result<Vec<Diagnostic>, Error> { pub fn check_file(&self, file_id: &FileHandle) -> Result<Vec<Diagnostic>, Error> {
let result = self.db.check_file(file_id.file).map_err(into_error)?; let result = self.db.check_file(file_id.file);
Ok(result.into_iter().map(Diagnostic::wrap).collect()) Ok(result.into_iter().map(Diagnostic::wrap).collect())
} }
/// Checks all open files /// Checks all open files
pub fn check(&self) -> Result<Vec<Diagnostic>, Error> { pub fn check(&self) -> Result<Vec<Diagnostic>, Error> {
let result = self.db.check().map_err(into_error)?; let result = self.db.check();
Ok(result.into_iter().map(Diagnostic::wrap).collect()) Ok(result.into_iter().map(Diagnostic::wrap).collect())
} }