|
2 | 2 | //! sometimes is counter productive when, for example, the first goto definition |
3 | 3 | //! request takes longer to compute. This modules implemented prepopulation of |
4 | 4 | //! various caches, it's not really advanced at the moment. |
| 5 | +mod topologic_sort; |
| 6 | + |
| 7 | +use std::time::Duration; |
5 | 8 |
|
6 | 9 | use hir::db::DefDatabase; |
7 | | -use ide_db::base_db::{SourceDatabase, SourceDatabaseExt}; |
| 10 | +use ide_db::{ |
| 11 | + base_db::{ |
| 12 | + salsa::{Database, ParallelDatabase, Snapshot}, |
| 13 | + Cancelled, CrateGraph, CrateId, SourceDatabase, SourceDatabaseExt, |
| 14 | + }, |
| 15 | + FxIndexMap, |
| 16 | +}; |
8 | 17 | use rustc_hash::FxHashSet; |
9 | 18 |
|
10 | 19 | use crate::RootDatabase; |
11 | 20 |
|
12 | | -/// We started indexing a crate. |
| 21 | +/// We're indexing many crates. |
13 | 22 | #[derive(Debug)] |
14 | | -pub struct PrimeCachesProgress { |
15 | | - pub on_crate: String, |
16 | | - pub n_done: usize, |
17 | | - pub n_total: usize, |
| 23 | +pub struct ParallelPrimeCachesProgress { |
| 24 | + /// the crates that we are currently priming. |
| 25 | + pub crates_currently_indexing: Vec<String>, |
| 26 | + /// the total number of crates we want to prime. |
| 27 | + pub crates_total: usize, |
| 28 | + /// the total number of crates that have finished priming |
| 29 | + pub crates_done: usize, |
18 | 30 | } |
19 | 31 |
|
20 | | -pub(crate) fn prime_caches(db: &RootDatabase, cb: &(dyn Fn(PrimeCachesProgress) + Sync)) { |
| 32 | +pub(crate) fn parallel_prime_caches( |
| 33 | + db: &RootDatabase, |
| 34 | + num_worker_threads: u8, |
| 35 | + cb: &(dyn Fn(ParallelPrimeCachesProgress) + Sync), |
| 36 | +) { |
21 | 37 | let _p = profile::span("prime_caches"); |
| 38 | + |
22 | 39 | let graph = db.crate_graph(); |
| 40 | + let mut crates_to_prime = { |
| 41 | + let crate_ids = compute_crates_to_prime(db, &graph); |
| 42 | + |
| 43 | + let mut builder = topologic_sort::TopologicalSortIter::builder(); |
| 44 | + |
| 45 | + for &crate_id in &crate_ids { |
| 46 | + let crate_data = &graph[crate_id]; |
| 47 | + let dependencies = crate_data |
| 48 | + .dependencies |
| 49 | + .iter() |
| 50 | + .map(|d| d.crate_id) |
| 51 | + .filter(|i| crate_ids.contains(i)); |
| 52 | + |
| 53 | + builder.add(crate_id, dependencies); |
| 54 | + } |
| 55 | + |
| 56 | + builder.build() |
| 57 | + }; |
| 58 | + |
| 59 | + enum ParallelPrimeCacheWorkerProgress { |
| 60 | + BeginCrate { crate_id: CrateId, crate_name: String }, |
| 61 | + EndCrate { crate_id: CrateId }, |
| 62 | + } |
| 63 | + |
| 64 | + let (work_sender, progress_receiver) = { |
| 65 | + let (progress_sender, progress_receiver) = crossbeam_channel::unbounded(); |
| 66 | + let (work_sender, work_receiver) = crossbeam_channel::unbounded(); |
| 67 | + let prime_caches_worker = move |db: Snapshot<RootDatabase>| { |
| 68 | + while let Ok((crate_id, crate_name)) = work_receiver.recv() { |
| 69 | + progress_sender |
| 70 | + .send(ParallelPrimeCacheWorkerProgress::BeginCrate { crate_id, crate_name })?; |
| 71 | + |
| 72 | + // This also computes the DefMap |
| 73 | + db.import_map(crate_id); |
| 74 | + |
| 75 | + progress_sender.send(ParallelPrimeCacheWorkerProgress::EndCrate { crate_id })?; |
| 76 | + } |
| 77 | + |
| 78 | + Ok::<_, crossbeam_channel::SendError<_>>(()) |
| 79 | + }; |
| 80 | + |
| 81 | + for _ in 0..num_worker_threads { |
| 82 | + let worker = prime_caches_worker.clone(); |
| 83 | + let db = db.snapshot(); |
| 84 | + std::thread::spawn(move || Cancelled::catch(|| worker(db))); |
| 85 | + } |
| 86 | + |
| 87 | + (work_sender, progress_receiver) |
| 88 | + }; |
| 89 | + |
| 90 | + let crates_total = crates_to_prime.pending(); |
| 91 | + let mut crates_done = 0; |
| 92 | + |
| 93 | + // an index map is used to preserve ordering so we can sort the progress report in order of |
| 94 | + // "longest crate to index" first |
| 95 | + let mut crates_currently_indexing = |
| 96 | + FxIndexMap::with_capacity_and_hasher(num_worker_threads as _, Default::default()); |
| 97 | + |
| 98 | + while crates_done < crates_total { |
| 99 | + db.unwind_if_cancelled(); |
| 100 | + |
| 101 | + for crate_id in &mut crates_to_prime { |
| 102 | + work_sender |
| 103 | + .send(( |
| 104 | + crate_id, |
| 105 | + graph[crate_id].display_name.as_deref().unwrap_or_default().to_string(), |
| 106 | + )) |
| 107 | + .ok(); |
| 108 | + } |
| 109 | + |
| 110 | + // recv_timeout is somewhat a hack, we need a way to from this thread check to see if the current salsa revision |
| 111 | + // is cancelled on a regular basis. workers will only exit if they are processing a task that is cancelled, or |
| 112 | + // if this thread exits, and closes the work channel. |
| 113 | + let worker_progress = match progress_receiver.recv_timeout(Duration::from_millis(10)) { |
| 114 | + Ok(p) => p, |
| 115 | + Err(crossbeam_channel::RecvTimeoutError::Timeout) => { |
| 116 | + continue; |
| 117 | + } |
| 118 | + Err(crossbeam_channel::RecvTimeoutError::Disconnected) => { |
| 119 | + // our workers may have died from a cancelled task, so we'll check and re-raise here. |
| 120 | + db.unwind_if_cancelled(); |
| 121 | + break; |
| 122 | + } |
| 123 | + }; |
| 124 | + match worker_progress { |
| 125 | + ParallelPrimeCacheWorkerProgress::BeginCrate { crate_id, crate_name } => { |
| 126 | + crates_currently_indexing.insert(crate_id, crate_name); |
| 127 | + } |
| 128 | + ParallelPrimeCacheWorkerProgress::EndCrate { crate_id } => { |
| 129 | + crates_currently_indexing.remove(&crate_id); |
| 130 | + crates_to_prime.mark_done(crate_id); |
| 131 | + crates_done += 1; |
| 132 | + } |
| 133 | + }; |
| 134 | + |
| 135 | + let progress = ParallelPrimeCachesProgress { |
| 136 | + crates_currently_indexing: crates_currently_indexing.values().cloned().collect(), |
| 137 | + crates_done, |
| 138 | + crates_total, |
| 139 | + }; |
| 140 | + |
| 141 | + cb(progress); |
| 142 | + } |
| 143 | +} |
| 144 | + |
| 145 | +fn compute_crates_to_prime(db: &RootDatabase, graph: &CrateGraph) -> FxHashSet<CrateId> { |
23 | 146 | // We're only interested in the workspace crates and the `ImportMap`s of their direct |
24 | 147 | // dependencies, though in practice the latter also compute the `DefMap`s. |
25 | 148 | // We don't prime transitive dependencies because they're generally not visible in |
26 | 149 | // the current workspace. |
27 | | - let to_prime: FxHashSet<_> = graph |
| 150 | + graph |
28 | 151 | .iter() |
29 | 152 | .filter(|&id| { |
30 | 153 | let file_id = graph[id].root_file_id; |
31 | 154 | let root_id = db.file_source_root(file_id); |
32 | 155 | !db.source_root(root_id).is_library |
33 | 156 | }) |
34 | 157 | .flat_map(|id| graph[id].dependencies.iter().map(|krate| krate.crate_id)) |
35 | | - .collect(); |
36 | | - |
37 | | - // FIXME: This would be easy to parallelize, since it's in the ideal ordering for that. |
38 | | - // Unfortunately rayon prevents panics from propagation out of a `scope`, which breaks |
39 | | - // cancellation, so we cannot use rayon. |
40 | | - let n_total = to_prime.len(); |
41 | | - for (n_done, &crate_id) in to_prime.iter().enumerate() { |
42 | | - let crate_name = graph[crate_id].display_name.as_deref().unwrap_or_default().to_string(); |
43 | | - |
44 | | - cb(PrimeCachesProgress { on_crate: crate_name, n_done, n_total }); |
45 | | - // This also computes the DefMap |
46 | | - db.import_map(crate_id); |
47 | | - } |
| 158 | + .collect() |
48 | 159 | } |
0 commit comments