Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 526507f

Browse files
committed
Auto merge of rust-lang#14888 - lunacookies:multi-qos, r=Veykril
Prioritize threads affected by user typing To this end I’ve introduced a new custom thread pool type which can spawn threads using each QoS class. This way we can run latency-sensitive requests under one QoS class and everything else under another QoS class. The implementation is very similar to that of the `threadpool` crate (which is currently used by rust-analyzer) but with unused functionality stripped out. I’ll have to rebase on master once rust-lang#14859 is merged but I think everything else is alright :D
2 parents d2b3caa + 6b46095 commit 526507f

File tree

16 files changed

+507
-350
lines changed

16 files changed

+507
-350
lines changed

Cargo.lock

Lines changed: 1 addition & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/flycheck/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ impl FlycheckHandle {
9090
) -> FlycheckHandle {
9191
let actor = FlycheckActor::new(id, sender, config, workspace_root);
9292
let (sender, receiver) = unbounded::<StateChange>();
93-
let thread = stdx::thread::Builder::new(stdx::thread::QoSClass::Utility)
93+
let thread = stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker)
9494
.name("Flycheck".to_owned())
9595
.spawn(move || actor.run(receiver))
9696
.expect("failed to spawn thread");
@@ -409,7 +409,7 @@ impl CargoHandle {
409409

410410
let (sender, receiver) = unbounded();
411411
let actor = CargoActor::new(sender, stdout, stderr);
412-
let thread = stdx::thread::Builder::new(stdx::thread::QoSClass::Utility)
412+
let thread = stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker)
413413
.name("CargoHandle".to_owned())
414414
.spawn(move || actor.run())
415415
.expect("failed to spawn thread");

crates/ide/src/prime_caches.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ pub(crate) fn parallel_prime_caches(
8181
let worker = prime_caches_worker.clone();
8282
let db = db.snapshot();
8383

84-
stdx::thread::Builder::new(stdx::thread::QoSClass::Utility)
84+
stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker)
8585
.allow_leak(true)
8686
.spawn(move || Cancelled::catch(|| worker(db)))
8787
.expect("failed to spawn thread");

crates/rust-analyzer/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ oorandom = "11.1.3"
3131
rustc-hash = "1.1.0"
3232
serde_json = { workspace = true, features = ["preserve_order"] }
3333
serde.workspace = true
34-
threadpool = "1.8.1"
3534
rayon = "1.6.1"
3635
num_cpus = "1.15.0"
3736
mimalloc = { version = "0.1.30", default-features = false, optional = true }

crates/rust-analyzer/src/bin/main.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,15 @@ fn try_main(flags: flags::RustAnalyzer) -> Result<()> {
7979
return Ok(());
8080
}
8181

82-
// rust-analyzer’s “main thread” is actually a secondary thread
83-
// with an increased stack size at the User Initiated QoS class.
84-
// We use this QoS class because any delay in the main loop
82+
// rust-analyzer’s “main thread” is actually
83+
// a secondary latency-sensitive thread with an increased stack size.
84+
// We use this thread intent because any delay in the main loop
8585
// will make actions like hitting enter in the editor slow.
86-
// rust-analyzer does not block the editor’s render loop,
87-
// so we don’t use User Interactive.
88-
with_extra_thread("LspServer", stdx::thread::QoSClass::UserInitiated, run_server)?;
86+
with_extra_thread(
87+
"LspServer",
88+
stdx::thread::ThreadIntent::LatencySensitive,
89+
run_server,
90+
)?;
8991
}
9092
flags::RustAnalyzerCmd::Parse(cmd) => cmd.run()?,
9193
flags::RustAnalyzerCmd::Symbols(cmd) => cmd.run()?,
@@ -143,10 +145,10 @@ const STACK_SIZE: usize = 1024 * 1024 * 8;
143145
/// space.
144146
fn with_extra_thread(
145147
thread_name: impl Into<String>,
146-
qos_class: stdx::thread::QoSClass,
148+
thread_intent: stdx::thread::ThreadIntent,
147149
f: impl FnOnce() -> Result<()> + Send + 'static,
148150
) -> Result<()> {
149-
let handle = stdx::thread::Builder::new(qos_class)
151+
let handle = stdx::thread::Builder::new(thread_intent)
150152
.name(thread_name.into())
151153
.stack_size(STACK_SIZE)
152154
.spawn(f)?;

crates/rust-analyzer/src/dispatch.rs

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::{fmt, panic, thread};
44
use ide::Cancelled;
55
use lsp_server::ExtractError;
66
use serde::{de::DeserializeOwned, Serialize};
7+
use stdx::thread::ThreadIntent;
78

89
use crate::{
910
global_state::{GlobalState, GlobalStateSnapshot},
@@ -87,7 +88,8 @@ impl<'a> RequestDispatcher<'a> {
8788
self
8889
}
8990

90-
/// Dispatches the request onto thread pool
91+
/// Dispatches a non-latency-sensitive request onto the thread pool
92+
/// without retrying it if it panics.
9193
pub(crate) fn on_no_retry<R>(
9294
&mut self,
9395
f: fn(GlobalStateSnapshot, R::Params) -> Result<R::Result>,
@@ -102,7 +104,7 @@ impl<'a> RequestDispatcher<'a> {
102104
None => return self,
103105
};
104106

105-
self.global_state.task_pool.handle.spawn({
107+
self.global_state.task_pool.handle.spawn(ThreadIntent::Worker, {
106108
let world = self.global_state.snapshot();
107109
move || {
108110
let result = panic::catch_unwind(move || {
@@ -123,11 +125,49 @@ impl<'a> RequestDispatcher<'a> {
123125
self
124126
}
125127

126-
/// Dispatches the request onto thread pool
128+
/// Dispatches a non-latency-sensitive request onto the thread pool.
127129
pub(crate) fn on<R>(
128130
&mut self,
129131
f: fn(GlobalStateSnapshot, R::Params) -> Result<R::Result>,
130132
) -> &mut Self
133+
where
134+
R: lsp_types::request::Request + 'static,
135+
R::Params: DeserializeOwned + panic::UnwindSafe + Send + fmt::Debug,
136+
R::Result: Serialize,
137+
{
138+
self.on_with_thread_intent::<R>(ThreadIntent::Worker, f)
139+
}
140+
141+
/// Dispatches a latency-sensitive request onto the thread pool.
142+
pub(crate) fn on_latency_sensitive<R>(
143+
&mut self,
144+
f: fn(GlobalStateSnapshot, R::Params) -> Result<R::Result>,
145+
) -> &mut Self
146+
where
147+
R: lsp_types::request::Request + 'static,
148+
R::Params: DeserializeOwned + panic::UnwindSafe + Send + fmt::Debug,
149+
R::Result: Serialize,
150+
{
151+
self.on_with_thread_intent::<R>(ThreadIntent::LatencySensitive, f)
152+
}
153+
154+
pub(crate) fn finish(&mut self) {
155+
if let Some(req) = self.req.take() {
156+
tracing::error!("unknown request: {:?}", req);
157+
let response = lsp_server::Response::new_err(
158+
req.id,
159+
lsp_server::ErrorCode::MethodNotFound as i32,
160+
"unknown request".to_string(),
161+
);
162+
self.global_state.respond(response);
163+
}
164+
}
165+
166+
fn on_with_thread_intent<R>(
167+
&mut self,
168+
intent: ThreadIntent,
169+
f: fn(GlobalStateSnapshot, R::Params) -> Result<R::Result>,
170+
) -> &mut Self
131171
where
132172
R: lsp_types::request::Request + 'static,
133173
R::Params: DeserializeOwned + panic::UnwindSafe + Send + fmt::Debug,
@@ -138,7 +178,7 @@ impl<'a> RequestDispatcher<'a> {
138178
None => return self,
139179
};
140180

141-
self.global_state.task_pool.handle.spawn({
181+
self.global_state.task_pool.handle.spawn(intent, {
142182
let world = self.global_state.snapshot();
143183
move || {
144184
let result = panic::catch_unwind(move || {
@@ -155,18 +195,6 @@ impl<'a> RequestDispatcher<'a> {
155195
self
156196
}
157197

158-
pub(crate) fn finish(&mut self) {
159-
if let Some(req) = self.req.take() {
160-
tracing::error!("unknown request: {:?}", req);
161-
let response = lsp_server::Response::new_err(
162-
req.id,
163-
lsp_server::ErrorCode::MethodNotFound as i32,
164-
"unknown request".to_string(),
165-
);
166-
self.global_state.respond(response);
167-
}
168-
}
169-
170198
fn parse<R>(&mut self) -> Option<(lsp_server::Request, R::Params, String)>
171199
where
172200
R: lsp_types::request::Request,

crates/rust-analyzer/src/handlers/notification.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool {
291291
}
292292
Ok(())
293293
};
294-
state.task_pool.handle.spawn_with_sender(move |_| {
294+
state.task_pool.handle.spawn_with_sender(stdx::thread::ThreadIntent::Worker, move |_| {
295295
if let Err(e) = std::panic::catch_unwind(task) {
296296
tracing::error!("flycheck task panicked: {e:?}")
297297
}

crates/rust-analyzer/src/main_loop.rs

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ impl GlobalState {
397397
tracing::debug!(%cause, "will prime caches");
398398
let num_worker_threads = self.config.prime_caches_num_threads();
399399

400-
self.task_pool.handle.spawn_with_sender({
400+
self.task_pool.handle.spawn_with_sender(stdx::thread::ThreadIntent::Worker, {
401401
let analysis = self.snapshot().analysis;
402402
move |sender| {
403403
sender.send(Task::PrimeCaches(PrimeCachesProgress::Begin)).unwrap();
@@ -678,7 +678,32 @@ impl GlobalState {
678678
.on_sync::<lsp_types::request::SelectionRangeRequest>(handlers::handle_selection_range)
679679
.on_sync::<lsp_ext::MatchingBrace>(handlers::handle_matching_brace)
680680
.on_sync::<lsp_ext::OnTypeFormatting>(handlers::handle_on_type_formatting)
681-
// All other request handlers:
681+
// We can’t run latency-sensitive request handlers which do semantic
682+
// analysis on the main thread because that would block other
683+
// requests. Instead, we run these request handlers on higher priority
684+
// threads in the threadpool.
685+
.on_latency_sensitive::<lsp_types::request::Completion>(handlers::handle_completion)
686+
.on_latency_sensitive::<lsp_types::request::ResolveCompletionItem>(
687+
handlers::handle_completion_resolve,
688+
)
689+
.on_latency_sensitive::<lsp_types::request::SemanticTokensFullRequest>(
690+
handlers::handle_semantic_tokens_full,
691+
)
692+
.on_latency_sensitive::<lsp_types::request::SemanticTokensFullDeltaRequest>(
693+
handlers::handle_semantic_tokens_full_delta,
694+
)
695+
.on_latency_sensitive::<lsp_types::request::SemanticTokensRangeRequest>(
696+
handlers::handle_semantic_tokens_range,
697+
)
698+
// Formatting is not caused by the user typing,
699+
// but it does qualify as latency-sensitive
700+
// because a delay before formatting is applied
701+
// can be confusing for the user.
702+
.on_latency_sensitive::<lsp_types::request::Formatting>(handlers::handle_formatting)
703+
.on_latency_sensitive::<lsp_types::request::RangeFormatting>(
704+
handlers::handle_range_formatting,
705+
)
706+
// All other request handlers
682707
.on::<lsp_ext::FetchDependencyList>(handlers::fetch_dependency_list)
683708
.on::<lsp_ext::AnalyzerStatus>(handlers::handle_analyzer_status)
684709
.on::<lsp_ext::SyntaxTree>(handlers::handle_syntax_tree)
@@ -706,17 +731,13 @@ impl GlobalState {
706731
.on::<lsp_types::request::GotoTypeDefinition>(handlers::handle_goto_type_definition)
707732
.on_no_retry::<lsp_types::request::InlayHintRequest>(handlers::handle_inlay_hints)
708733
.on::<lsp_types::request::InlayHintResolveRequest>(handlers::handle_inlay_hints_resolve)
709-
.on::<lsp_types::request::Completion>(handlers::handle_completion)
710-
.on::<lsp_types::request::ResolveCompletionItem>(handlers::handle_completion_resolve)
711734
.on::<lsp_types::request::CodeLensRequest>(handlers::handle_code_lens)
712735
.on::<lsp_types::request::CodeLensResolve>(handlers::handle_code_lens_resolve)
713736
.on::<lsp_types::request::FoldingRangeRequest>(handlers::handle_folding_range)
714737
.on::<lsp_types::request::SignatureHelpRequest>(handlers::handle_signature_help)
715738
.on::<lsp_types::request::PrepareRenameRequest>(handlers::handle_prepare_rename)
716739
.on::<lsp_types::request::Rename>(handlers::handle_rename)
717740
.on::<lsp_types::request::References>(handlers::handle_references)
718-
.on::<lsp_types::request::Formatting>(handlers::handle_formatting)
719-
.on::<lsp_types::request::RangeFormatting>(handlers::handle_range_formatting)
720741
.on::<lsp_types::request::DocumentHighlightRequest>(handlers::handle_document_highlight)
721742
.on::<lsp_types::request::CallHierarchyPrepare>(handlers::handle_call_hierarchy_prepare)
722743
.on::<lsp_types::request::CallHierarchyIncomingCalls>(
@@ -725,15 +746,6 @@ impl GlobalState {
725746
.on::<lsp_types::request::CallHierarchyOutgoingCalls>(
726747
handlers::handle_call_hierarchy_outgoing,
727748
)
728-
.on::<lsp_types::request::SemanticTokensFullRequest>(
729-
handlers::handle_semantic_tokens_full,
730-
)
731-
.on::<lsp_types::request::SemanticTokensFullDeltaRequest>(
732-
handlers::handle_semantic_tokens_full_delta,
733-
)
734-
.on::<lsp_types::request::SemanticTokensRangeRequest>(
735-
handlers::handle_semantic_tokens_range,
736-
)
737749
.on::<lsp_types::request::WillRenameFiles>(handlers::handle_will_rename_files)
738750
.on::<lsp_ext::Ssr>(handlers::handle_ssr)
739751
.finish();
@@ -781,7 +793,10 @@ impl GlobalState {
781793
tracing::trace!("updating notifications for {:?}", subscriptions);
782794

783795
let snapshot = self.snapshot();
784-
self.task_pool.handle.spawn(move || {
796+
797+
// Diagnostics are triggered by the user typing
798+
// so we run them on a latency sensitive thread.
799+
self.task_pool.handle.spawn(stdx::thread::ThreadIntent::LatencySensitive, move || {
785800
let _p = profile::span("publish_diagnostics");
786801
let diagnostics = subscriptions
787802
.into_iter()

crates/rust-analyzer/src/reload.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use ide_db::{
2727
use itertools::Itertools;
2828
use proc_macro_api::{MacroDylib, ProcMacroServer};
2929
use project_model::{PackageRoot, ProjectWorkspace, WorkspaceBuildScripts};
30-
use stdx::format_to;
30+
use stdx::{format_to, thread::ThreadIntent};
3131
use syntax::SmolStr;
3232
use triomphe::Arc;
3333
use vfs::{file_set::FileSetConfig, AbsPath, AbsPathBuf, ChangeKind};
@@ -185,7 +185,7 @@ impl GlobalState {
185185
pub(crate) fn fetch_workspaces(&mut self, cause: Cause) {
186186
tracing::info!(%cause, "will fetch workspaces");
187187

188-
self.task_pool.handle.spawn_with_sender({
188+
self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, {
189189
let linked_projects = self.config.linked_projects();
190190
let detached_files = self.config.detached_files().to_vec();
191191
let cargo_config = self.config.cargo();
@@ -260,7 +260,7 @@ impl GlobalState {
260260
tracing::info!(%cause, "will fetch build data");
261261
let workspaces = Arc::clone(&self.workspaces);
262262
let config = self.config.cargo();
263-
self.task_pool.handle.spawn_with_sender(move |sender| {
263+
self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, move |sender| {
264264
sender.send(Task::FetchBuildData(BuildDataProgress::Begin)).unwrap();
265265

266266
let progress = {
@@ -280,7 +280,7 @@ impl GlobalState {
280280
let dummy_replacements = self.config.dummy_replacements().clone();
281281
let proc_macro_clients = self.proc_macro_clients.clone();
282282

283-
self.task_pool.handle.spawn_with_sender(move |sender| {
283+
self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, move |sender| {
284284
sender.send(Task::LoadProcMacros(ProcMacroProgress::Begin)).unwrap();
285285

286286
let dummy_replacements = &dummy_replacements;

0 commit comments

Comments
 (0)