Skip to content

Commit 5a4d1d3

Browse files
committed
fix(diagnostics): release semantic compiler snapshots
1 parent c9e25d1 commit 5a4d1d3

1 file changed

Lines changed: 140 additions & 66 deletions

File tree

src/global_state/semantic_compiler.rs

Lines changed: 140 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::{
33
sync::Arc as StdArc,
44
};
55

6-
use anyhow::Result;
6+
use anyhow::{Context, Result};
77
use hir::base_db::project::CompilationProfileId;
88
use rustc_hash::FxHashSet;
99
use utils::{
@@ -25,12 +25,18 @@ use super::{
2525

2626
#[derive(Debug)]
2727
pub(crate) struct SemanticCompilerUpdate {
28-
snapshot: GlobalStateSnapshot,
28+
delivery: SemanticDiagnosticsDelivery,
2929
touched_files: FxHashSet<FileId>,
3030
diagnostic_count: usize,
3131
freshness: DiagnosticPublishFreshness,
3232
}
3333

34+
#[derive(Debug)]
35+
enum SemanticDiagnosticsDelivery {
36+
PullRefresh,
37+
Push(PublishDiagnosticsBatch),
38+
}
39+
3440
impl SemanticCompilerUpdate {
3541
pub(crate) fn touched_file_count(&self) -> usize {
3642
self.touched_files.len()
@@ -118,8 +124,15 @@ impl SemanticCompiler {
118124
return;
119125
}
120126

121-
let SemanticCompilerUpdate { snapshot, touched_files, .. } = update;
122-
self.publish_diagnostics(snapshot, touched_files, ctx);
127+
let SemanticCompilerUpdate { delivery, touched_files, .. } = update;
128+
match delivery {
129+
SemanticDiagnosticsDelivery::PullRefresh => {
130+
ctx.refresh_semantic_diagnostics(touched_files);
131+
}
132+
SemanticDiagnosticsDelivery::Push(batch) => {
133+
ctx.publish_semantic_diagnostics(batch);
134+
}
135+
}
123136
self.start_pending(ctx);
124137
}
125138
SemanticCompilerTask::Cancelled { run_id } => {
@@ -188,15 +201,6 @@ impl SemanticCompiler {
188201
self.start_run(profile_ids, ctx);
189202
}
190203
}
191-
192-
fn publish_diagnostics<C: SemanticCompilerCtx>(
193-
&mut self,
194-
snapshot: GlobalStateSnapshot,
195-
changed_files: FxHashSet<FileId>,
196-
ctx: &mut C,
197-
) {
198-
ctx.publish_semantic_diagnostics(snapshot, changed_files);
199-
}
200204
}
201205

202206
pub(crate) trait SemanticCompilerCtx {
@@ -206,11 +210,8 @@ pub(crate) trait SemanticCompilerCtx {
206210
where
207211
F: FnOnce(crossbeam_channel::Sender<Task>) + Send + 'static;
208212
fn task_cancel_token(&self) -> CancellationToken;
209-
fn publish_semantic_diagnostics(
210-
&mut self,
211-
snapshot: GlobalStateSnapshot,
212-
changed_files: FxHashSet<FileId>,
213-
);
213+
fn refresh_semantic_diagnostics(&mut self, changed_files: FxHashSet<FileId>);
214+
fn publish_semantic_diagnostics(&mut self, batch: PublishDiagnosticsBatch);
214215
}
215216

216217
pub(super) struct SemanticCompilerGlobalCtx<'a> {
@@ -288,52 +289,15 @@ impl SemanticCompilerCtx for SemanticCompilerGlobalCtx<'_> {
288289
self.tasks.task_pool.handle.task_token()
289290
}
290291

291-
fn publish_semantic_diagnostics(
292-
&mut self,
293-
snapshot: GlobalStateSnapshot,
294-
changed_files: FxHashSet<FileId>,
295-
) {
296-
if changed_files.is_empty() {
297-
return;
298-
}
292+
fn refresh_semantic_diagnostics(&mut self, changed_files: FxHashSet<FileId>) {
293+
self.refresh_pull_diagnostics(changed_files);
294+
}
299295

300-
if self.config_state.config.cli_pull_diagnostics_support() {
301-
self.refresh_pull_diagnostics(changed_files);
296+
fn publish_semantic_diagnostics(&mut self, batch: PublishDiagnosticsBatch) {
297+
if batch.touched_file_count() == 0 {
302298
return;
303299
}
304300

305-
let mut publish_tasks = Vec::with_capacity(changed_files.len());
306-
let mut touched_file_ids = FxHashSet::default();
307-
for file_id in changed_files.iter().copied() {
308-
let targets = match snapshot.diagnostic_publish_targets(file_id) {
309-
Ok(targets) => targets,
310-
Err(error) => {
311-
tracing::debug!(
312-
?file_id,
313-
"skipping semantic diagnostics for file without URI: {error:#}"
314-
);
315-
continue;
316-
}
317-
};
318-
let diagnostics = match snapshot.lsp_diagnostics(file_id) {
319-
Ok(diagnostics) => diagnostics,
320-
Err(error) if error.is::<ide::Cancelled>() => {
321-
tracing::debug!(?file_id, "semantic diagnostic publish cancelled");
322-
continue;
323-
}
324-
Err(error) => {
325-
tracing::debug!(?file_id, "semantic diagnostic publish failed: {error:#}");
326-
continue;
327-
}
328-
};
329-
touched_file_ids.insert(file_id);
330-
331-
publish_tasks.extend(
332-
targets
333-
.into_iter()
334-
.map(|target| PublishDiagnosticsTask::from_target(target, diagnostics.clone())),
335-
);
336-
}
337301
let current_freshness = self.diagnostic_publish_freshness();
338302
DiagnosticsPublisher::new(
339303
&self.config_state.config,
@@ -342,11 +306,7 @@ impl SemanticCompilerCtx for SemanticCompilerGlobalCtx<'_> {
342306
&self.client.sender,
343307
current_freshness,
344308
)
345-
.publish(PublishDiagnosticsBatch::for_touched_files(
346-
touched_file_ids,
347-
publish_tasks,
348-
snapshot.diagnostic_publish_freshness,
349-
));
309+
.publish(batch);
350310
}
351311
}
352312

@@ -416,7 +376,56 @@ fn collect_semantic_diagnostics(
416376
diagnostic_count,
417377
"semantic compiler prewarmed profile diagnostics"
418378
);
419-
Ok(SemanticCompilerUpdate { snapshot, touched_files, diagnostic_count, freshness })
379+
380+
let delivery = if snapshot.config.cli_pull_diagnostics_support() {
381+
SemanticDiagnosticsDelivery::PullRefresh
382+
} else {
383+
SemanticDiagnosticsDelivery::Push(materialize_semantic_publish_batch(
384+
&snapshot,
385+
&touched_files,
386+
cancellation,
387+
)?)
388+
};
389+
drop(snapshot);
390+
391+
Ok(SemanticCompilerUpdate { delivery, touched_files, diagnostic_count, freshness })
392+
}
393+
394+
fn materialize_semantic_publish_batch(
395+
snapshot: &GlobalStateSnapshot,
396+
changed_files: &FxHashSet<FileId>,
397+
cancellation: &CancellationToken,
398+
) -> Result<PublishDiagnosticsBatch> {
399+
let mut publish_tasks = Vec::with_capacity(changed_files.len());
400+
let mut touched_file_ids = FxHashSet::default();
401+
for file_id in changed_files.iter().copied() {
402+
cancellation.check()?;
403+
let targets = snapshot
404+
.diagnostic_publish_targets(file_id)
405+
.with_context(|| format!("failed to resolve diagnostic targets for {file_id:?}"))?;
406+
let diagnostics = match snapshot.lsp_diagnostics(file_id) {
407+
Ok(diagnostics) => diagnostics,
408+
Err(error) if error.is::<ide::Cancelled>() => return Err(CancellationError.into()),
409+
Err(error) => {
410+
return Err(error.context(format!(
411+
"failed to materialize semantic diagnostics for {file_id:?}"
412+
)));
413+
}
414+
};
415+
touched_file_ids.insert(file_id);
416+
publish_tasks.extend(
417+
targets
418+
.into_iter()
419+
.map(|target| PublishDiagnosticsTask::from_target(target, diagnostics.clone())),
420+
);
421+
}
422+
cancellation.check()?;
423+
424+
Ok(PublishDiagnosticsBatch::for_touched_files(
425+
touched_file_ids,
426+
publish_tasks,
427+
snapshot.diagnostic_publish_freshness,
428+
))
420429
}
421430

422431
fn normalize_profile_ids(mut profile_ids: Vec<CompilationProfileId>) -> Vec<CompilationProfileId> {
@@ -431,3 +440,68 @@ fn panic_message(panic: &(dyn std::any::Any + Send)) -> Option<&str> {
431440
.map(String::as_str)
432441
.or_else(|| panic.downcast_ref::<&str>().copied())
433442
}
443+
444+
#[cfg(test)]
445+
mod tests {
446+
use std::time::Duration;
447+
448+
use hir::base_db::change::Change;
449+
use lsp_server::Connection;
450+
use lsp_types::{ClientCapabilities, TraceValue};
451+
use utils::test_support::TestDir;
452+
453+
use super::*;
454+
use crate::{
455+
Opt,
456+
config::{self, user_config::UserConfig},
457+
i18n::I18n,
458+
};
459+
460+
#[test]
461+
fn semantic_compiler_task_does_not_retain_analysis_snapshot() {
462+
let root = TestDir::new("semantic-compiler-snapshot-lifetime");
463+
let root_path = root.path().to_path_buf();
464+
let config = config::Config::new(
465+
Opt {
466+
process_name: "vide-test".to_owned(),
467+
log: "error".to_owned(),
468+
log_filename: None,
469+
profile_trace: None,
470+
},
471+
root_path.clone(),
472+
ClientCapabilities::default(),
473+
vec![root_path],
474+
I18n::default(),
475+
UserConfig::default(),
476+
Vec::new(),
477+
);
478+
let (server, _client) = Connection::memory();
479+
let mut state = GlobalState::new(server.sender, config, TraceValue::Off);
480+
let cancellation = CancellationToken::new();
481+
let snapshot = state.make_snapshot_with_cancel(cancellation.clone());
482+
let task = run_semantic_compiler_task(
483+
snapshot,
484+
Vec::new(),
485+
SemanticCompilerRunId::default(),
486+
cancellation,
487+
);
488+
489+
let mut analysis_host = std::mem::take(&mut state.analysis.analysis_host);
490+
let (finished_tx, finished_rx) = crossbeam_channel::bounded(1);
491+
let writer = std::thread::spawn(move || {
492+
analysis_host.apply_change(Change::new());
493+
finished_tx.send(()).unwrap();
494+
analysis_host
495+
});
496+
497+
let completed_while_task_was_retained =
498+
finished_rx.recv_timeout(Duration::from_secs(1)).is_ok();
499+
drop(task);
500+
state.analysis.analysis_host = writer.join().unwrap();
501+
502+
assert!(
503+
completed_while_task_was_retained,
504+
"semantic compiler task retained an analysis snapshot and blocked the next change"
505+
);
506+
}
507+
}

0 commit comments

Comments
 (0)