diff options
Diffstat (limited to 'crates/ra_lsp_server/src')
-rw-r--r-- | crates/ra_lsp_server/src/main_loop.rs | 133 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/handlers.rs | 5 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/world.rs | 4 |
3 files changed, 89 insertions, 53 deletions
diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs index 7822be2e2..d850ded37 100644 --- a/crates/ra_lsp_server/src/main_loop.rs +++ b/crates/ra_lsp_server/src/main_loop.rs | |||
@@ -5,11 +5,18 @@ mod handlers; | |||
5 | mod subscriptions; | 5 | mod subscriptions; |
6 | pub(crate) mod pending_requests; | 6 | pub(crate) mod pending_requests; |
7 | 7 | ||
8 | use std::{error::Error, fmt, panic, path::PathBuf, sync::Arc, time::Instant}; | 8 | use std::{ |
9 | env, | ||
10 | error::Error, | ||
11 | fmt, panic, | ||
12 | path::PathBuf, | ||
13 | sync::Arc, | ||
14 | time::{Duration, Instant}, | ||
15 | }; | ||
9 | 16 | ||
10 | use crossbeam_channel::{select, unbounded, RecvError, Sender}; | 17 | use crossbeam_channel::{select, unbounded, RecvError, Sender}; |
11 | use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; | 18 | use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; |
12 | use lsp_types::{ClientCapabilities, NumberOrString, Url}; | 19 | use lsp_types::{ClientCapabilities, NumberOrString}; |
13 | use ra_cargo_watch::{CheckOptions, CheckTask}; | 20 | use ra_cargo_watch::{CheckOptions, CheckTask}; |
14 | use ra_ide::{Canceled, FeatureFlags, FileId, LibraryData, SourceRootId}; | 21 | use ra_ide::{Canceled, FeatureFlags, FileId, LibraryData, SourceRootId}; |
15 | use ra_prof::profile; | 22 | use ra_prof::profile; |
@@ -29,9 +36,6 @@ use crate::{ | |||
29 | Result, ServerConfig, | 36 | Result, ServerConfig, |
30 | }; | 37 | }; |
31 | 38 | ||
32 | const THREADPOOL_SIZE: usize = 8; | ||
33 | const MAX_IN_FLIGHT_LIBS: usize = THREADPOOL_SIZE - 3; | ||
34 | |||
35 | #[derive(Debug)] | 39 | #[derive(Debug)] |
36 | pub struct LspError { | 40 | pub struct LspError { |
37 | pub code: i32, | 41 | pub code: i32, |
@@ -60,6 +64,25 @@ pub fn main_loop( | |||
60 | ) -> Result<()> { | 64 | ) -> Result<()> { |
61 | log::info!("server_config: {:#?}", config); | 65 | log::info!("server_config: {:#?}", config); |
62 | 66 | ||
67 | // Windows scheduler implements priority boosts: if thread waits for an | ||
68 | // event (like a condvar), and event fires, priority of the thread is | ||
69 | // temporary bumped. This optimization backfires in our case: each time the | ||
70 | // `main_loop` schedules a task to run on a threadpool, the worker threads | ||
71 | // gets a higher priority, and (on a machine with fewer cores) displaces the | ||
72 | // main loop! We work-around this by marking the main loop as a | ||
73 | // higher-priority thread. | ||
74 | // | ||
75 | // https://docs.microsoft.com/en-us/windows/win32/procthread/scheduling-priorities | ||
76 | // https://docs.microsoft.com/en-us/windows/win32/procthread/priority-boosts | ||
77 | // https://github.com/rust-analyzer/rust-analyzer/issues/2835 | ||
78 | #[cfg(windows)] | ||
79 | unsafe { | ||
80 | use winapi::um::processthreadsapi::*; | ||
81 | let thread = GetCurrentThread(); | ||
82 | let thread_priority_above_normal = 1; | ||
83 | SetThreadPriority(thread, thread_priority_above_normal); | ||
84 | } | ||
85 | |||
63 | let mut loop_state = LoopState::default(); | 86 | let mut loop_state = LoopState::default(); |
64 | let mut world_state = { | 87 | let mut world_state = { |
65 | let feature_flags = { | 88 | let feature_flags = { |
@@ -168,7 +191,7 @@ pub fn main_loop( | |||
168 | ) | 191 | ) |
169 | }; | 192 | }; |
170 | 193 | ||
171 | let pool = ThreadPool::new(THREADPOOL_SIZE); | 194 | let pool = ThreadPool::default(); |
172 | let (task_sender, task_receiver) = unbounded::<Task>(); | 195 | let (task_sender, task_receiver) = unbounded::<Task>(); |
173 | let (libdata_sender, libdata_receiver) = unbounded::<LibraryData>(); | 196 | let (libdata_sender, libdata_receiver) = unbounded::<LibraryData>(); |
174 | 197 | ||
@@ -210,7 +233,7 @@ pub fn main_loop( | |||
210 | )?; | 233 | )?; |
211 | } | 234 | } |
212 | } | 235 | } |
213 | 236 | world_state.analysis_host.request_cancellation(); | |
214 | log::info!("waiting for tasks to finish..."); | 237 | log::info!("waiting for tasks to finish..."); |
215 | task_receiver.into_iter().for_each(|task| { | 238 | task_receiver.into_iter().for_each(|task| { |
216 | on_task(task, &connection.sender, &mut loop_state.pending_requests, &mut world_state) | 239 | on_task(task, &connection.sender, &mut loop_state.pending_requests, &mut world_state) |
@@ -336,7 +359,7 @@ fn loop_turn( | |||
336 | world_state.maybe_collect_garbage(); | 359 | world_state.maybe_collect_garbage(); |
337 | loop_state.in_flight_libraries -= 1; | 360 | loop_state.in_flight_libraries -= 1; |
338 | } | 361 | } |
339 | Event::CheckWatcher(task) => on_check_task(task, world_state, task_sender)?, | 362 | Event::CheckWatcher(task) => on_check_task(pool, task, world_state, task_sender)?, |
340 | Event::Msg(msg) => match msg { | 363 | Event::Msg(msg) => match msg { |
341 | Message::Request(req) => on_request( | 364 | Message::Request(req) => on_request( |
342 | world_state, | 365 | world_state, |
@@ -371,7 +394,8 @@ fn loop_turn( | |||
371 | loop_state.pending_libraries.extend(changes); | 394 | loop_state.pending_libraries.extend(changes); |
372 | } | 395 | } |
373 | 396 | ||
374 | while loop_state.in_flight_libraries < MAX_IN_FLIGHT_LIBS | 397 | let max_in_flight_libs = pool.max_count().saturating_sub(2).max(1); |
398 | while loop_state.in_flight_libraries < max_in_flight_libs | ||
375 | && !loop_state.pending_libraries.is_empty() | 399 | && !loop_state.pending_libraries.is_empty() |
376 | { | 400 | { |
377 | let (root, files) = loop_state.pending_libraries.pop().unwrap(); | 401 | let (root, files) = loop_state.pending_libraries.pop().unwrap(); |
@@ -408,6 +432,19 @@ fn loop_turn( | |||
408 | loop_state.subscriptions.subscriptions(), | 432 | loop_state.subscriptions.subscriptions(), |
409 | ) | 433 | ) |
410 | } | 434 | } |
435 | |||
436 | let loop_duration = loop_start.elapsed(); | ||
437 | if loop_duration > Duration::from_millis(100) { | ||
438 | log::error!("overly long loop turn: {:?}", loop_duration); | ||
439 | if env::var("RA_PROFILE").is_ok() { | ||
440 | show_message( | ||
441 | req::MessageType::Error, | ||
442 | format!("overly long loop turn: {:?}", loop_duration), | ||
443 | &connection.sender, | ||
444 | ); | ||
445 | } | ||
446 | } | ||
447 | |||
411 | Ok(()) | 448 | Ok(()) |
412 | } | 449 | } |
413 | 450 | ||
@@ -435,7 +472,7 @@ fn on_request( | |||
435 | world: &mut WorldState, | 472 | world: &mut WorldState, |
436 | pending_requests: &mut PendingRequests, | 473 | pending_requests: &mut PendingRequests, |
437 | pool: &ThreadPool, | 474 | pool: &ThreadPool, |
438 | sender: &Sender<Task>, | 475 | task_sender: &Sender<Task>, |
439 | msg_sender: &Sender<Message>, | 476 | msg_sender: &Sender<Message>, |
440 | request_received: Instant, | 477 | request_received: Instant, |
441 | req: Request, | 478 | req: Request, |
@@ -444,7 +481,7 @@ fn on_request( | |||
444 | req: Some(req), | 481 | req: Some(req), |
445 | pool, | 482 | pool, |
446 | world, | 483 | world, |
447 | sender, | 484 | task_sender, |
448 | msg_sender, | 485 | msg_sender, |
449 | pending_requests, | 486 | pending_requests, |
450 | request_received, | 487 | request_received, |
@@ -585,31 +622,23 @@ fn on_notification( | |||
585 | } | 622 | } |
586 | 623 | ||
587 | fn on_check_task( | 624 | fn on_check_task( |
625 | pool: &ThreadPool, | ||
588 | task: CheckTask, | 626 | task: CheckTask, |
589 | world_state: &WorldState, | 627 | world_state: &mut WorldState, |
590 | task_sender: &Sender<Task>, | 628 | task_sender: &Sender<Task>, |
591 | ) -> Result<()> { | 629 | ) -> Result<()> { |
592 | match task { | 630 | let urls = match task { |
593 | CheckTask::ClearDiagnostics => { | 631 | CheckTask::ClearDiagnostics => { |
594 | let cleared_files = world_state.check_watcher.state.write().clear(); | 632 | let state = Arc::get_mut(&mut world_state.check_watcher.state) |
595 | 633 | .expect("couldn't get check watcher state as mutable"); | |
596 | // Send updated diagnostics for each cleared file | 634 | state.clear() |
597 | for url in cleared_files { | ||
598 | publish_diagnostics_for_url(&url, world_state, task_sender)?; | ||
599 | } | ||
600 | } | 635 | } |
601 | 636 | ||
602 | CheckTask::AddDiagnostic(url, diagnostic) => { | 637 | CheckTask::AddDiagnostic(url, diagnostic) => { |
603 | world_state | 638 | let state = Arc::get_mut(&mut world_state.check_watcher.state) |
604 | .check_watcher | 639 | .expect("couldn't get check watcher state as mutable"); |
605 | .state | 640 | state.add_diagnostic_with_fixes(url.clone(), diagnostic); |
606 | .write() | 641 | vec![url] |
607 | .add_diagnostic_with_fixes(url.clone(), diagnostic); | ||
608 | |||
609 | // We manually send a diagnostic update when the watcher asks | ||
610 | // us to, to avoid the issue of having to change the file to | ||
611 | // receive updated diagnostics. | ||
612 | publish_diagnostics_for_url(&url, world_state, task_sender)?; | ||
613 | } | 642 | } |
614 | 643 | ||
615 | CheckTask::Status(progress) => { | 644 | CheckTask::Status(progress) => { |
@@ -619,22 +648,30 @@ fn on_check_task( | |||
619 | }; | 648 | }; |
620 | let not = notification_new::<req::Progress>(params); | 649 | let not = notification_new::<req::Progress>(params); |
621 | task_sender.send(Task::Notify(not)).unwrap(); | 650 | task_sender.send(Task::Notify(not)).unwrap(); |
651 | Vec::new() | ||
622 | } | 652 | } |
623 | } | 653 | }; |
624 | Ok(()) | 654 | |
625 | } | 655 | let subscriptions = urls |
656 | .into_iter() | ||
657 | .map(|url| { | ||
658 | let path = url.to_file_path().map_err(|()| format!("invalid uri: {}", url))?; | ||
659 | Ok(world_state.vfs.read().path2file(&path).map(|it| FileId(it.0))) | ||
660 | }) | ||
661 | .filter_map(|res| res.transpose()) | ||
662 | .collect::<Result<Vec<_>>>()?; | ||
663 | |||
664 | // We manually send a diagnostic update when the watcher asks | ||
665 | // us to, to avoid the issue of having to change the file to | ||
666 | // receive updated diagnostics. | ||
667 | update_file_notifications_on_threadpool( | ||
668 | pool, | ||
669 | world_state.snapshot(), | ||
670 | false, | ||
671 | task_sender.clone(), | ||
672 | subscriptions, | ||
673 | ); | ||
626 | 674 | ||
627 | fn publish_diagnostics_for_url( | ||
628 | url: &Url, | ||
629 | world_state: &WorldState, | ||
630 | task_sender: &Sender<Task>, | ||
631 | ) -> Result<()> { | ||
632 | let path = url.to_file_path().map_err(|()| format!("invalid uri: {}", url))?; | ||
633 | if let Some(file_id) = world_state.vfs.read().path2file(&path) { | ||
634 | let params = handlers::publish_diagnostics(&world_state.snapshot(), FileId(file_id.0))?; | ||
635 | let not = notification_new::<req::PublishDiagnostics>(params); | ||
636 | task_sender.send(Task::Notify(not)).unwrap(); | ||
637 | } | ||
638 | Ok(()) | 675 | Ok(()) |
639 | } | 676 | } |
640 | 677 | ||
@@ -644,7 +681,7 @@ struct PoolDispatcher<'a> { | |||
644 | world: &'a mut WorldState, | 681 | world: &'a mut WorldState, |
645 | pending_requests: &'a mut PendingRequests, | 682 | pending_requests: &'a mut PendingRequests, |
646 | msg_sender: &'a Sender<Message>, | 683 | msg_sender: &'a Sender<Message>, |
647 | sender: &'a Sender<Task>, | 684 | task_sender: &'a Sender<Task>, |
648 | request_received: Instant, | 685 | request_received: Instant, |
649 | } | 686 | } |
650 | 687 | ||
@@ -691,7 +728,7 @@ impl<'a> PoolDispatcher<'a> { | |||
691 | 728 | ||
692 | self.pool.execute({ | 729 | self.pool.execute({ |
693 | let world = self.world.snapshot(); | 730 | let world = self.world.snapshot(); |
694 | let sender = self.sender.clone(); | 731 | let sender = self.task_sender.clone(); |
695 | move || { | 732 | move || { |
696 | let result = f(world, params); | 733 | let result = f(world, params); |
697 | let task = result_to_task::<R>(id, result); | 734 | let task = result_to_task::<R>(id, result); |
@@ -769,7 +806,7 @@ fn update_file_notifications_on_threadpool( | |||
769 | pool: &ThreadPool, | 806 | pool: &ThreadPool, |
770 | world: WorldSnapshot, | 807 | world: WorldSnapshot, |
771 | publish_decorations: bool, | 808 | publish_decorations: bool, |
772 | sender: Sender<Task>, | 809 | task_sender: Sender<Task>, |
773 | subscriptions: Vec<FileId>, | 810 | subscriptions: Vec<FileId>, |
774 | ) { | 811 | ) { |
775 | log::trace!("updating notifications for {:?}", subscriptions); | 812 | log::trace!("updating notifications for {:?}", subscriptions); |
@@ -785,7 +822,7 @@ fn update_file_notifications_on_threadpool( | |||
785 | } | 822 | } |
786 | Ok(params) => { | 823 | Ok(params) => { |
787 | let not = notification_new::<req::PublishDiagnostics>(params); | 824 | let not = notification_new::<req::PublishDiagnostics>(params); |
788 | sender.send(Task::Notify(not)).unwrap(); | 825 | task_sender.send(Task::Notify(not)).unwrap(); |
789 | } | 826 | } |
790 | } | 827 | } |
791 | } | 828 | } |
@@ -798,7 +835,7 @@ fn update_file_notifications_on_threadpool( | |||
798 | } | 835 | } |
799 | Ok(params) => { | 836 | Ok(params) => { |
800 | let not = notification_new::<req::PublishDecorations>(params); | 837 | let not = notification_new::<req::PublishDecorations>(params); |
801 | sender.send(Task::Notify(not)).unwrap(); | 838 | task_sender.send(Task::Notify(not)).unwrap(); |
802 | } | 839 | } |
803 | } | 840 | } |
804 | } | 841 | } |
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index 8e43f0575..666f2ee29 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs | |||
@@ -674,8 +674,7 @@ pub fn handle_code_action( | |||
674 | res.push(action.into()); | 674 | res.push(action.into()); |
675 | } | 675 | } |
676 | 676 | ||
677 | for fix in world.check_watcher.read().fixes_for(¶ms.text_document.uri).into_iter().flatten() | 677 | for fix in world.check_watcher.fixes_for(¶ms.text_document.uri).into_iter().flatten() { |
678 | { | ||
679 | let fix_range = fix.location.range.conv_with(&line_index); | 678 | let fix_range = fix.location.range.conv_with(&line_index); |
680 | if fix_range.intersection(&range).is_none() { | 679 | if fix_range.intersection(&range).is_none() { |
681 | continue; | 680 | continue; |
@@ -895,7 +894,7 @@ pub fn publish_diagnostics( | |||
895 | tags: None, | 894 | tags: None, |
896 | }) | 895 | }) |
897 | .collect(); | 896 | .collect(); |
898 | if let Some(check_diags) = world.check_watcher.read().diagnostics_for(&uri) { | 897 | if let Some(check_diags) = world.check_watcher.diagnostics_for(&uri) { |
899 | diagnostics.extend(check_diags.iter().cloned()); | 898 | diagnostics.extend(check_diags.iter().cloned()); |
900 | } | 899 | } |
901 | Ok(req::PublishDiagnosticsParams { uri, diagnostics, version: None }) | 900 | Ok(req::PublishDiagnosticsParams { uri, diagnostics, version: None }) |
diff --git a/crates/ra_lsp_server/src/world.rs b/crates/ra_lsp_server/src/world.rs index e7a0acfc7..3059ef9ec 100644 --- a/crates/ra_lsp_server/src/world.rs +++ b/crates/ra_lsp_server/src/world.rs | |||
@@ -63,7 +63,7 @@ pub struct WorldSnapshot { | |||
63 | pub workspaces: Arc<Vec<ProjectWorkspace>>, | 63 | pub workspaces: Arc<Vec<ProjectWorkspace>>, |
64 | pub analysis: Analysis, | 64 | pub analysis: Analysis, |
65 | pub latest_requests: Arc<RwLock<LatestRequests>>, | 65 | pub latest_requests: Arc<RwLock<LatestRequests>>, |
66 | pub check_watcher: Arc<RwLock<CheckState>>, | 66 | pub check_watcher: CheckState, |
67 | vfs: Arc<RwLock<Vfs>>, | 67 | vfs: Arc<RwLock<Vfs>>, |
68 | } | 68 | } |
69 | 69 | ||
@@ -220,7 +220,7 @@ impl WorldState { | |||
220 | analysis: self.analysis_host.analysis(), | 220 | analysis: self.analysis_host.analysis(), |
221 | vfs: Arc::clone(&self.vfs), | 221 | vfs: Arc::clone(&self.vfs), |
222 | latest_requests: Arc::clone(&self.latest_requests), | 222 | latest_requests: Arc::clone(&self.latest_requests), |
223 | check_watcher: self.check_watcher.state.clone(), | 223 | check_watcher: (*self.check_watcher.state).clone(), |
224 | } | 224 | } |
225 | } | 225 | } |
226 | 226 | ||