Skip to content

Commit bcd6a77

Browse files
committed
Analysis Progress Controller
1 parent d3e5afd commit bcd6a77

File tree

10 files changed

+226
-84
lines changed

10 files changed

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

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: 22 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,16 @@ use std::collections::HashSet;
22
use std::iter;
33
use std::iter::zip;
44
use std::num::NonZero;
5-
use std::panic::{catch_unwind, AssertUnwindSafe};
6-
use std::sync::{Arc, Mutex};
5+
use std::panic::{AssertUnwindSafe, catch_unwind};
76

87
use cairo_lang_filesystem::ids::FileId;
9-
use lsp_types::notification::Notification;
108
use lsp_types::Url;
119
use tracing::{error, trace};
1210

1311
use self::project_diagnostics::ProjectDiagnostics;
1412
use self::refresh::{clear_old_diagnostics, refresh_diagnostics};
1513
use self::trigger::trigger;
14+
use crate::ide::analysis_progress::AnalysisProgressController;
1615
use crate::lang::diagnostics::file_batches::{batches, find_primary_files, find_secondary_files};
1716
use crate::lang::lsp::LsProtoGroup;
1817
use crate::server::client::Notifier;
@@ -39,87 +38,41 @@ pub struct DiagnosticsController {
3938
trigger: trigger::Sender<StateSnapshots>,
4039
_thread: JoinHandle,
4140
state_snapshots_props: StateSnapshotsProps,
42-
active_snapshots: Arc<Mutex<HashSet<usize>>>,
43-
notifier: Notifier,
4441
}
4542

4643
impl DiagnosticsController {
4744
/// Creates a new diagnostics controller.
48-
pub fn new(notifier: Notifier) -> Self {
45+
pub fn new(
46+
notifier: Notifier,
47+
analysis_progress_controller: AnalysisProgressController,
48+
) -> Self {
4949
let (trigger, receiver) = trigger();
50-
let (thread, parallelism) = DiagnosticsControllerThread::spawn(receiver, notifier.clone());
50+
let (thread, parallelism) = DiagnosticsControllerThread::spawn(
51+
receiver,
52+
notifier.clone(),
53+
analysis_progress_controller,
54+
);
55+
5156
Self {
5257
trigger,
5358
_thread: thread,
5459
state_snapshots_props: StateSnapshotsProps { parallelism },
55-
active_snapshots: Arc::new(Mutex::new(HashSet::default())),
56-
notifier,
5760
}
5861
}
5962

6063
/// Schedules diagnostics refreshing on snapshot(s) of the current state.
6164
pub fn refresh(&self, state: &State) {
62-
let mut state_snapshots = StateSnapshots::new(state, &self.state_snapshots_props);
63-
self.register_beacons(&mut state_snapshots);
64-
65-
DiagnosticsController::notify_start_analysis(self.notifier.clone());
66-
self.trigger.activate(state_snapshots);
67-
}
68-
69-
fn register_beacons(&self, state_snapshots: &mut StateSnapshots) {
70-
let active_snapshots_ref = self.active_snapshots.clone();
71-
(active_snapshots_ref.lock().unwrap()).clear();
72-
73-
state_snapshots.0.iter_mut().enumerate().for_each(|(i, beacon)| {
74-
let mut active_snapshots = active_snapshots_ref.lock().unwrap();
75-
active_snapshots.insert(i);
76-
77-
let active_snapshots_ref_2 = self.active_snapshots.clone();
78-
let notifer_ref = self.notifier.clone();
79-
beacon.on_drop(move || {
80-
let mut active_snapshots = active_snapshots_ref_2.lock().unwrap();
81-
82-
active_snapshots.remove(&i);
83-
if active_snapshots.is_empty() {
84-
DiagnosticsController::notify_stop_analysis(notifer_ref);
85-
}
86-
});
87-
});
88-
}
89-
90-
fn notify_stop_analysis(notifier: Notifier) {
91-
notifier.notify::<DiagnosticsCalculationFinish>(());
92-
}
93-
94-
fn notify_start_analysis(notifier: Notifier) {
95-
notifier.notify::<DiagnosticsCalculationStart>(());
65+
self.trigger.activate(StateSnapshots::new(state, &self.state_snapshots_props));
9666
}
9767
}
9868

99-
/// Notifies about diagnostics round which is beginning to calculate
100-
#[derive(Debug)]
101-
pub struct DiagnosticsCalculationStart;
102-
103-
impl Notification for DiagnosticsCalculationStart {
104-
type Params = ();
105-
const METHOD: &'static str = "cairo/diagnosticsCalculationStart";
106-
}
107-
108-
/// Notifies about diagnostics round which ended calulating
109-
#[derive(Debug)]
110-
pub struct DiagnosticsCalculationFinish;
111-
112-
impl Notification for DiagnosticsCalculationFinish {
113-
type Params = ();
114-
const METHOD: &'static str = "cairo/diagnosticsCalculationFinish";
115-
}
116-
11769
/// Stores entire state of diagnostics controller's worker thread.
11870
struct DiagnosticsControllerThread {
11971
receiver: trigger::Receiver<StateSnapshots>,
12072
notifier: Notifier,
12173
pool: thread::Pool,
12274
project_diagnostics: ProjectDiagnostics,
75+
analysis_progress_controller: AnalysisProgressController,
12376
}
12477

12578
impl DiagnosticsControllerThread {
@@ -128,10 +81,12 @@ impl DiagnosticsControllerThread {
12881
fn spawn(
12982
receiver: trigger::Receiver<StateSnapshots>,
13083
notifier: Notifier,
84+
analysis_progress_controller: AnalysisProgressController,
13185
) -> (JoinHandle, NonZero<usize>) {
13286
let this = Self {
13387
receiver,
13488
notifier,
89+
analysis_progress_controller,
13590
pool: thread::Pool::new(),
13691
project_diagnostics: ProjectDiagnostics::new(),
13792
};
@@ -148,7 +103,8 @@ impl DiagnosticsControllerThread {
148103

149104
/// Runs diagnostics controller's event loop.
150105
fn event_loop(&self) {
151-
while let Some(state_snapshots) = self.receiver.wait() {
106+
while let Some(mut state_snapshots) = self.receiver.wait() {
107+
self.analysis_progress_controller.track_analysis(&mut state_snapshots.0);
152108
if let Err(err) = catch_unwind(AssertUnwindSafe(|| {
153109
self.diagnostics_controller_tick(state_snapshots);
154110
})) {
@@ -165,7 +121,7 @@ impl DiagnosticsControllerThread {
165121
/// Runs a single tick of the diagnostics controller's event loop.
166122
#[tracing::instrument(skip_all)]
167123
fn diagnostics_controller_tick(&self, state_snapshots: StateSnapshots) {
168-
let (state, primary_snapshots, secondary_snapshots) = state_snapshots.split();
124+
let (mut state, primary_snapshots, secondary_snapshots) = state_snapshots.split();
169125

170126
let primary_set = find_primary_files(&state.db, &state.open_files);
171127
let primary: Vec<_> = primary_set.iter().copied().collect();
@@ -182,6 +138,7 @@ impl DiagnosticsControllerThread {
182138

183139
self.spawn_worker(move |project_diagnostics, notifier| {
184140
clear_old_diagnostics(files_to_preserve, project_diagnostics, notifier);
141+
state.signal();
185142
});
186143
}
187144

@@ -206,7 +163,7 @@ impl DiagnosticsControllerThread {
206163
fn spawn_refresh_worker(&self, files: &[FileId], state_snapshots: Vec<StateSnapshot>) {
207164
let files_batches = batches(files, self.pool.parallelism());
208165
assert_eq!(files_batches.len(), state_snapshots.len());
209-
for (batch, state) in zip(files_batches, state_snapshots) {
166+
for (batch, mut state) in zip(files_batches, state_snapshots) {
210167
self.spawn_worker(move |project_diagnostics, notifier| {
211168
refresh_diagnostics(
212169
&state.db,
@@ -215,6 +172,7 @@ impl DiagnosticsControllerThread {
215172
project_diagnostics,
216173
notifier,
217174
);
175+
state.signal();
218176
});
219177
}
220178
}

src/lang/proc_macros/client/mod.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ use scarb_proc_macro_server_types::methods::expand::{
1616
pub use status::ClientStatus;
1717
use tracing::error;
1818

19+
use crate::id_generator;
20+
use crate::ide::analysis_progress::AnalysisProgressController;
21+
1922
pub mod connection;
20-
mod id_generator;
2123
pub mod status;
2224

2325
#[derive(Debug)]
@@ -33,15 +35,21 @@ pub struct ProcMacroClient {
3335
id_generator: id_generator::IdGenerator,
3436
requests_params: Mutex<HashMap<RequestId, RequestParams>>,
3537
error_channel: Sender<()>,
38+
analysis_progress_controller: AnalysisProgressController,
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_controller: AnalysisProgressController,
46+
) -> Self {
4047
Self {
4148
connection,
4249
id_generator: Default::default(),
4350
requests_params: Default::default(),
4451
error_channel,
52+
analysis_progress_controller,
4553
}
4654
}
4755

@@ -142,6 +150,7 @@ impl ProcMacroClient {
142150
match self.send_request_untracked::<M>(id, &params) {
143151
Ok(()) => {
144152
requests_params.insert(id, map(params));
153+
self.analysis_progress_controller.register_procmacro_request();
145154
}
146155
Err(err) => {
147156
error!("Sending request to proc-macro-server failed: {err:?}");

0 commit comments

Comments
 (0)