Skip to content

Commit 40d3863

Browse files
committed
Track analysis progress with notifications
1 parent 62ad275 commit 40d3863

File tree

10 files changed

+283
-16
lines changed

10 files changed

+283
-16
lines changed

src/config.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ impl Config {
9797
response.pop_front().as_ref().and_then(Value::as_bool).unwrap_or_default();
9898
state.config.enable_proc_macros =
9999
response.pop_front().as_ref().and_then(Value::as_bool).unwrap_or(false);
100+
state
101+
.analysis_progress_controller
102+
.set_procmacros_enabled(state.config.enable_proc_macros);
100103

101104
debug!("reloaded configuration: {:#?}", state.config);
102105

File renamed without changes.

src/ide/analysis_progress.rs

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
use std::collections::HashSet;
2+
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
3+
use std::sync::{Arc, Mutex};
4+
5+
use lsp_types::notification::Notification;
6+
7+
use crate::id_generator::IdGenerator;
8+
use crate::server::client::Notifier;
9+
use crate::state::Beacon;
10+
11+
/// A facade for `AnalysisProgressController` that allows to track progress of diagnostics
12+
/// generation and procmacro requests.
13+
#[derive(Clone)]
14+
pub struct AnalysisProgressTracker {
15+
controller: AnalysisProgressController,
16+
}
17+
18+
impl AnalysisProgressTracker {
19+
/// Signals that a request to proc macro server was made during the current generation of
20+
/// diagnostics.
21+
pub fn register_procmacro_request(&self) {
22+
self.controller.set_did_submit_procmacro_request(true);
23+
}
24+
25+
/// Sets handlers for tracking beacons sent to threads.
26+
/// The beacons are wrapping snapshots, which are signalling when diagnostics finished
27+
/// calculating for a given snapshot (used for calculating files diagnostics or removing
28+
/// stale ones)
29+
pub fn track_analysis<'a>(&self, beacons: impl Iterator<Item = &'a mut Beacon>) {
30+
let gen_id = self.controller.next_generation_id();
31+
32+
self.controller.clear_active_snapshots();
33+
34+
beacons.enumerate().for_each(|(i, beacon)| {
35+
self.controller.insert_active_snapshot(i);
36+
37+
let controller_ref: AnalysisProgressController = self.controller.clone();
38+
beacon.on_signal(move || controller_ref.on_snapshot_deactivate(gen_id, i));
39+
});
40+
41+
self.controller.start_analysis();
42+
}
43+
}
44+
45+
/// Controller used to send notifications to the client about analysis progress.
46+
/// Uses information provided from other controllers (diagnostics controller, procmacro controller)
47+
/// to assess if diagnostics are in fact calculated.
48+
#[derive(Debug, Clone)]
49+
pub struct AnalysisProgressController {
50+
notifier: Notifier,
51+
/// ID of the diagnostics "generation" - the scheduled diagnostics jobs set.
52+
/// Used to filter out stale threads finishing when new ones (from newer "generation")
53+
/// are already in progress and being tracked by the controller.
54+
generation_id: Arc<AtomicU64>,
55+
/// Sequential IDs of state snapshots from the current generation, used to track their status
56+
/// (present meaning it's still being used)
57+
active_snapshots: Arc<Mutex<HashSet<usize>>>,
58+
id_generator: Arc<IdGenerator>,
59+
/// If `true` - a request to procmacro server was submitted, meaning that analysis will extend
60+
/// beyond the current generation of diagnostics.
61+
did_submit_procmacro_request: Arc<AtomicBool>,
62+
/// Indicates that a notification was sent and analysis (i.e. macro expansion) is taking place.
63+
analysis_in_progress: Arc<AtomicBool>,
64+
/// Loaded asynchronously from config - unset if config was not loaded yet.
65+
/// Has to be set in order for analysis to finish.
66+
procmacros_enabled: Arc<Mutex<Option<bool>>>,
67+
}
68+
69+
impl AnalysisProgressController {
70+
pub fn tracker(&self) -> AnalysisProgressTracker {
71+
AnalysisProgressTracker { controller: self.clone() }
72+
}
73+
74+
pub fn new(notifier: Notifier) -> Self {
75+
let id_generator = Arc::new(IdGenerator::default());
76+
Self {
77+
notifier,
78+
id_generator: id_generator.clone(),
79+
active_snapshots: Arc::new(Mutex::new(HashSet::default())),
80+
did_submit_procmacro_request: Arc::new(AtomicBool::new(false)),
81+
analysis_in_progress: Arc::new(AtomicBool::new(false)),
82+
procmacros_enabled: Arc::new(Mutex::new(None)),
83+
generation_id: Arc::new(AtomicU64::new(id_generator.unique_id())),
84+
}
85+
}
86+
87+
pub fn set_did_submit_procmacro_request(&self, value: bool) {
88+
self.did_submit_procmacro_request.store(value, Ordering::SeqCst);
89+
}
90+
91+
/// Allows to set the procmacro configuration to whatever is in the config, upon loading it.
92+
pub fn set_procmacros_enabled(&self, value: bool) {
93+
let mut guard = self.procmacros_enabled.lock().unwrap();
94+
*guard = Some(value);
95+
}
96+
97+
pub fn insert_active_snapshot(&self, snapshot_id: usize) {
98+
let mut active_snapshots = self.active_snapshots.lock().unwrap();
99+
active_snapshots.insert(snapshot_id);
100+
}
101+
102+
pub fn on_snapshot_deactivate(&self, snapshot_gen_id: u64, snapshot_id: usize) {
103+
let current_gen = self.get_generation_id();
104+
if current_gen == snapshot_gen_id {
105+
self.remove_active_snapshot(snapshot_id);
106+
self.try_stop_analysis();
107+
}
108+
}
109+
110+
pub fn next_generation_id(&self) -> u64 {
111+
let new_gen_id = self.id_generator.unique_id();
112+
self.generation_id.store(new_gen_id, Ordering::SeqCst);
113+
new_gen_id
114+
}
115+
116+
pub fn get_generation_id(&self) -> u64 {
117+
self.generation_id.load(Ordering::SeqCst)
118+
}
119+
120+
pub fn remove_active_snapshot(&self, snapshot_id: usize) {
121+
let mut active_snapshots = self.active_snapshots.lock().unwrap();
122+
active_snapshots.remove(&snapshot_id);
123+
}
124+
125+
pub fn clear_active_snapshots(&self) {
126+
let active_snapshots_ref = self.active_snapshots.clone();
127+
active_snapshots_ref.lock().unwrap().clear();
128+
}
129+
130+
/// Starts a next generation of diagnostics, sends a notification
131+
fn start_analysis(&self) {
132+
let analysis_in_progress = self.analysis_in_progress.load(Ordering::SeqCst);
133+
let config_loaded = self.procmacros_enabled.lock().unwrap().is_some();
134+
// We want to clear this flag always when starting a new generation to track the requests
135+
// properly
136+
self.did_submit_procmacro_request.store(false, Ordering::SeqCst);
137+
138+
if !analysis_in_progress && config_loaded {
139+
self.analysis_in_progress.store(true, Ordering::SeqCst);
140+
self.notifier.notify::<DiagnosticsCalculationStart>(());
141+
}
142+
}
143+
144+
/// Checks a bunch of conditions and if they are fulfilled, sends stop notification
145+
/// and resets the state back to start of generation defaults.
146+
fn try_stop_analysis(&self) {
147+
let did_submit_procmacro_request = self.did_submit_procmacro_request.load(Ordering::SeqCst);
148+
let snapshots_empty = self.active_snapshots.lock().unwrap().is_empty();
149+
let analysis_in_progress = self.analysis_in_progress.load(Ordering::SeqCst);
150+
let procmacros_enabled = *self.procmacros_enabled.lock().unwrap();
151+
152+
if snapshots_empty
153+
&& (!did_submit_procmacro_request || (procmacros_enabled == Some(false)))
154+
&& analysis_in_progress
155+
{
156+
self.did_submit_procmacro_request.store(false, Ordering::SeqCst);
157+
self.analysis_in_progress.store(false, Ordering::SeqCst);
158+
159+
self.notifier.notify::<DiagnosticsCalculationFinish>(());
160+
}
161+
}
162+
}
163+
164+
/// Notifies about diagnostics generation which is beginning to calculate
165+
#[derive(Debug)]
166+
pub struct DiagnosticsCalculationStart;
167+
168+
impl Notification for DiagnosticsCalculationStart {
169+
type Params = ();
170+
const METHOD: &'static str = "cairo/diagnosticsCalculationStart";
171+
}
172+
173+
/// Notifies about diagnostics generation which ended calculating
174+
#[derive(Debug)]
175+
pub struct DiagnosticsCalculationFinish;
176+
177+
impl Notification for DiagnosticsCalculationFinish {
178+
type Params = ();
179+
const METHOD: &'static str = "cairo/diagnosticsCalculationFinish";
180+
}

src/ide/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod analysis_progress;
12
pub mod code_actions;
23
pub mod completion;
34
pub mod formatter;

src/lang/diagnostics/mod.rs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@ use tracing::{error, trace};
1010

1111
use self::project_diagnostics::ProjectDiagnostics;
1212
use self::refresh::{clear_old_diagnostics, refresh_diagnostics};
13+
use self::trigger::trigger;
14+
use crate::ide::analysis_progress::AnalysisProgressTracker;
1315
use crate::lang::diagnostics::file_batches::{batches, find_primary_files, find_secondary_files};
1416
use crate::lang::lsp::LsProtoGroup;
1517
use crate::server::client::Notifier;
1618
use crate::server::panic::cancelled_anyhow;
1719
use crate::server::schedule::thread::task_progress_monitor::TaskHandle;
1820
use crate::server::schedule::thread::{self, JoinHandle, ThreadPriority};
1921
use crate::server::trigger;
20-
use crate::state::{State, StateSnapshot};
22+
use crate::state::{Beacon, State, StateSnapshot};
2123

2224
mod file_batches;
2325
mod file_diagnostics;
@@ -41,9 +43,10 @@ pub struct DiagnosticsController {
4143

4244
impl DiagnosticsController {
4345
/// Creates a new diagnostics controller.
44-
pub fn new(notifier: Notifier) -> Self {
46+
pub fn new(notifier: Notifier, analysis_progress_tracker: AnalysisProgressTracker) -> Self {
4547
let (trigger, receiver) = trigger::trigger();
46-
let (thread, parallelism) = DiagnosticsControllerThread::spawn(receiver, notifier);
48+
let (thread, parallelism) =
49+
DiagnosticsControllerThread::spawn(receiver, notifier, analysis_progress_tracker);
4750
Self {
4851
trigger,
4952
_thread: thread,
@@ -53,8 +56,7 @@ impl DiagnosticsController {
5356

5457
/// Schedules diagnostics refreshing on snapshot(s) of the current state.
5558
pub fn refresh(&self, state: &State) {
56-
let state_snapshots = StateSnapshots::new(state, &self.state_snapshots_props);
57-
self.trigger.activate(state_snapshots);
59+
self.trigger.activate(StateSnapshots::new(state, &self.state_snapshots_props));
5860
}
5961
}
6062

@@ -64,6 +66,7 @@ struct DiagnosticsControllerThread {
6466
notifier: Notifier,
6567
pool: thread::Pool,
6668
project_diagnostics: ProjectDiagnostics,
69+
analysis_progress_tracker: AnalysisProgressTracker,
6770
worker_handles: Vec<TaskHandle>,
6871
}
6972

@@ -73,10 +76,12 @@ impl DiagnosticsControllerThread {
7376
fn spawn(
7477
receiver: trigger::Receiver<StateSnapshots>,
7578
notifier: Notifier,
79+
analysis_progress_tracker: AnalysisProgressTracker,
7680
) -> (JoinHandle, NonZero<usize>) {
7781
let mut this = Self {
7882
receiver,
7983
notifier,
84+
analysis_progress_tracker,
8085
pool: thread::Pool::new(),
8186
project_diagnostics: ProjectDiagnostics::new(),
8287
worker_handles: Vec::new(),
@@ -96,6 +101,7 @@ impl DiagnosticsControllerThread {
96101
fn event_loop(&mut self) {
97102
while let Some(state_snapshots) = self.receiver.wait() {
98103
assert!(self.worker_handles.is_empty());
104+
self.analysis_progress_tracker.track_analysis(&mut state_snapshots.beacons());
99105
if let Err(err) = catch_unwind(AssertUnwindSafe(|| {
100106
self.diagnostics_controller_tick(state_snapshots);
101107
})) {
@@ -114,7 +120,7 @@ impl DiagnosticsControllerThread {
114120
/// Runs a single tick of the diagnostics controller's event loop.
115121
#[tracing::instrument(skip_all)]
116122
fn diagnostics_controller_tick(&mut self, state_snapshots: StateSnapshots) {
117-
let (state, primary_snapshots, secondary_snapshots) = state_snapshots.split();
123+
let (mut state, primary_snapshots, secondary_snapshots) = state_snapshots.split();
118124

119125
let primary_set = find_primary_files(&state.db, &state.open_files);
120126
let primary: Vec<_> = primary_set.iter().copied().collect();
@@ -131,6 +137,7 @@ impl DiagnosticsControllerThread {
131137

132138
self.spawn_worker(move |project_diagnostics, notifier| {
133139
clear_old_diagnostics(files_to_preserve, project_diagnostics, notifier);
140+
state.signal_finish();
134141
});
135142
}
136143

@@ -156,7 +163,7 @@ impl DiagnosticsControllerThread {
156163
fn spawn_refresh_workers(&mut self, files: &[FileId], state_snapshots: Vec<StateSnapshot>) {
157164
let files_batches = batches(files, self.pool.parallelism());
158165
assert_eq!(files_batches.len(), state_snapshots.len());
159-
for (batch, state) in zip(files_batches, state_snapshots) {
166+
for (batch, mut state) in zip(files_batches, state_snapshots) {
160167
self.spawn_worker(move |project_diagnostics, notifier| {
161168
refresh_diagnostics(
162169
&state.db,
@@ -165,6 +172,7 @@ impl DiagnosticsControllerThread {
165172
project_diagnostics,
166173
notifier,
167174
);
175+
state.signal_finish();
168176
});
169177
}
170178
}
@@ -203,6 +211,10 @@ impl StateSnapshots {
203211
let secondary = snapshots.split_off(snapshots.len() / 2);
204212
(control, snapshots, secondary)
205213
}
214+
215+
fn beacons(&mut self) -> impl Iterator<Item = &mut Beacon> {
216+
self.0.iter_mut().map(|snapshot| &mut snapshot.beacon)
217+
}
206218
}
207219

208220
/// Stores necessary properties for creating [`StateSnapshots`].

src/lang/proc_macros/client/mod.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::collections::{HashMap, VecDeque};
2+
use std::fmt::{Debug, Formatter};
23
use std::sync::{Mutex, MutexGuard};
34

45
use anyhow::{Context, Result, anyhow, ensure};
@@ -16,8 +17,10 @@ use scarb_proc_macro_server_types::methods::expand::{
1617
pub use status::ClientStatus;
1718
use tracing::error;
1819

20+
use crate::id_generator;
21+
use crate::ide::analysis_progress::AnalysisProgressTracker;
22+
1923
pub mod connection;
20-
mod id_generator;
2124
pub mod status;
2225

2326
#[derive(Debug)]
@@ -27,21 +30,26 @@ pub enum RequestParams {
2730
Inline(ExpandInlineMacroParams),
2831
}
2932

30-
#[derive(Debug)]
3133
pub struct ProcMacroClient {
3234
connection: ProcMacroServerConnection,
3335
id_generator: id_generator::IdGenerator,
3436
requests_params: Mutex<HashMap<RequestId, RequestParams>>,
3537
error_channel: Sender<()>,
38+
analysis_progress_tracker: AnalysisProgressTracker,
3639
}
3740

3841
impl ProcMacroClient {
39-
pub fn new(connection: ProcMacroServerConnection, error_channel: Sender<()>) -> Self {
42+
pub fn new(
43+
connection: ProcMacroServerConnection,
44+
error_channel: Sender<()>,
45+
analysis_progress_tracker: AnalysisProgressTracker,
46+
) -> Self {
4047
Self {
4148
connection,
4249
id_generator: Default::default(),
4350
requests_params: Default::default(),
4451
error_channel,
52+
analysis_progress_tracker,
4553
}
4654
}
4755

@@ -147,6 +155,7 @@ impl ProcMacroClient {
147155
match self.send_request_untracked::<M>(id, &params) {
148156
Ok(()) => {
149157
requests_params.insert(id, map(params));
158+
self.analysis_progress_tracker.register_procmacro_request();
150159
}
151160
Err(err) => {
152161
error!("Sending request to proc-macro-server failed: {err:?}");
@@ -162,6 +171,17 @@ impl ProcMacroClient {
162171
}
163172
}
164173

174+
impl Debug for ProcMacroClient {
175+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
176+
f.debug_struct("ProcMacroClient")
177+
.field("connection", &self.connection)
178+
.field("id_generator", &self.id_generator)
179+
.field("requests_params", &self.requests_params)
180+
.field("error_channel", &self.error_channel)
181+
.finish()
182+
}
183+
}
184+
165185
pub struct Responses<'a> {
166186
responses: MutexGuard<'a, VecDeque<RpcResponse>>,
167187
requests: MutexGuard<'a, HashMap<RequestId, RequestParams>>,

0 commit comments

Comments
 (0)