Skip to content

Commit 47351fd

Browse files
fix(query): avoid cyclic package docs deadlock
1 parent b111924 commit 47351fd

8 files changed

Lines changed: 470 additions & 52 deletions

File tree

crates/tinymist-query/src/analysis/global.rs

Lines changed: 246 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::{collections::HashSet, ops::Deref};
88
use comemo::{Track, Tracked};
99
use ecow::EcoString;
1010
use lsp_types::Url;
11-
use parking_lot::Mutex;
11+
use parking_lot::{Condvar, Mutex};
1212
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
1313
use rustc_hash::FxHashMap;
1414
use tinymist_analysis::docs::DocString;
@@ -821,15 +821,26 @@ impl SharedContext {
821821
}
822822
}
823823

824-
/// Gets the expression information of a source file.
825-
pub(crate) fn expr_stage_by_id(self: &Arc<Self>, fid: TypstFileId) -> Option<ExprInfo> {
826-
Some(self.expr_stage(&self.source_by_id(fid).ok()?))
824+
/// Gets expression information for a recursive dependency without waiting
825+
/// on another worker's cache initializer.
826+
pub(crate) fn expr_stage_recursive_by_id(
827+
self: &Arc<Self>,
828+
fid: TypstFileId,
829+
) -> Option<ExprInfo> {
830+
let source = self.source_by_id(fid).ok()?;
831+
let mut route = ExprRoute::default();
832+
Some(self.expr_stage_(&source, &mut route))
827833
}
828834

829835
/// Gets the expression information of a source file.
830836
pub(crate) fn expr_stage(self: &Arc<Self>, source: &Source) -> ExprInfo {
831837
let mut route = ExprRoute::default();
832-
self.expr_stage_(source, &mut route)
838+
use crate::syntax::expr_of;
839+
840+
let guard = self.query_stat(source.id(), "expr_stage");
841+
self.slot.expr_stage.compute(hash128(&source), |prev| {
842+
expr_of(self.clone(), source.clone(), &mut route, guard, prev)
843+
})
833844
}
834845

835846
/// Gets the expression information of a source file.
@@ -839,17 +850,30 @@ impl SharedContext {
839850
route: &mut ExprRoute,
840851
) -> ExprInfo {
841852
use crate::syntax::expr_of;
853+
854+
// Recursive expression traversals must not wait for another worker's
855+
// initializer; their route-local result is intentionally unpublished.
856+
if let Some(cached) = route.completed(&source.id()) {
857+
return cached.clone();
858+
}
859+
860+
if let Some(cached) = self.slot.expr_stage.get(&hash128(&source)) {
861+
return cached;
862+
}
863+
842864
let guard = self.query_stat(source.id(), "expr_stage");
843-
self.slot.expr_stage.compute(hash128(&source), |prev| {
844-
expr_of(self.clone(), source.clone(), route, guard, prev)
845-
})
865+
expr_of(self.clone(), source.clone(), route, guard, None)
846866
}
847867

848868
pub(crate) fn exports_of(
849869
self: &Arc<Self>,
850870
source: &Source,
851871
route: &mut ExprRoute,
852872
) -> Option<Arc<LazyHash<LexicalScope>>> {
873+
if let Some(ei) = route.completed(&source.id()) {
874+
return Some(ei.exports.clone());
875+
}
876+
853877
if let Some(s) = route.get(&source.id()) {
854878
return s.clone();
855879
}
@@ -859,18 +883,9 @@ impl SharedContext {
859883

860884
/// Gets the type check information of a source file.
861885
pub(crate) fn type_check(self: &Arc<Self>, source: &Source) -> Arc<TypeInfo> {
862-
let mut route = TypeEnv::default();
863-
self.type_check_(source, &mut route)
864-
}
865-
866-
/// Gets the type check information of a source file.
867-
pub(crate) fn type_check_(
868-
self: &Arc<Self>,
869-
source: &Source,
870-
route: &mut TypeEnv,
871-
) -> Arc<TypeInfo> {
872886
use crate::analysis::type_check;
873887

888+
let mut route = TypeEnv::default();
874889
let ei = self.expr_stage(source);
875890
let guard = self.query_stat(source.id(), "type_check");
876891
self.slot.type_check.compute(hash128(&ei), |prev| {
@@ -880,10 +895,40 @@ impl SharedContext {
880895
}
881896

882897
guard.miss();
883-
type_check(self.clone(), ei, route)
898+
type_check(self.clone(), ei, &mut route)
884899
})
885900
}
886901

902+
/// Gets type check information for a recursive dependency.
903+
///
904+
/// A dependency already running on another worker is recomputed in this
905+
/// traversal instead of waited on, because that worker may be waiting on
906+
/// this traversal. Only the owner of the shared cache slot publishes its
907+
/// result.
908+
pub(crate) fn type_check_recursive(
909+
self: &Arc<Self>,
910+
ei: ExprInfo,
911+
route: &mut TypeEnv,
912+
) -> Arc<TypeInfo> {
913+
use crate::analysis::type_check;
914+
915+
if let Some(cached) = route.type_infos.get(&ei.fid) {
916+
return cached.clone();
917+
}
918+
919+
let guard = self.query_stat(ei.fid, "type_check");
920+
if let Some(cached) = self.slot.type_check.get(&hash128(&ei)) {
921+
route.type_infos.insert(ei.fid, cached.clone());
922+
return cached;
923+
}
924+
925+
guard.miss();
926+
let fid = ei.fid;
927+
let type_info = type_check(self.clone(), ei, route);
928+
route.type_infos.insert(fid, type_info.clone());
929+
type_info
930+
}
931+
887932
/// Gets the lint result of a source file.
888933
#[typst_macros::time(span = source.root().span())]
889934
pub(crate) fn lint(self: &Arc<Self>, source: &Source, issues: &KnownIssues) -> LintInfo {
@@ -1044,7 +1089,7 @@ impl SharedContext {
10441089
Some(DefDocs::Variable(docs))
10451090
}
10461091
DefKind::Module => {
1047-
let ei = self.expr_stage_by_id(def.decl.file_id()?)?;
1092+
let ei = self.expr_stage_recursive_by_id(def.decl.file_id()?)?;
10481093
Some(DefDocs::Module(TidyModuleDocs {
10491094
docs: ei.module_docstring.docs.clone().unwrap_or_default(),
10501095
}))
@@ -1249,7 +1294,7 @@ impl SharedContext {
12491294

12501295
let preloader = Preloader {
12511296
shared: self,
1252-
analyzed: Arc::default(),
1297+
analyzed: Arc::new(Mutex::new(HashSet::from([entry_point]))),
12531298
};
12541299

12551300
preloader.work(entry_point);
@@ -1258,13 +1303,88 @@ impl SharedContext {
12581303

12591304
// Needed by recursive computation
12601305
type DeferredCompute<T> = Arc<OnceLock<T>>;
1306+
type IncrDeferredCompute<T> = Arc<DeferredSlot<T>>;
1307+
1308+
#[derive(Default)]
1309+
enum DeferredState<T> {
1310+
#[default]
1311+
Fresh,
1312+
Running,
1313+
Done(T),
1314+
}
1315+
1316+
struct DeferredSlot<T> {
1317+
state: Mutex<DeferredState<T>>,
1318+
ready: Condvar,
1319+
}
1320+
1321+
impl<T> Default for DeferredSlot<T> {
1322+
fn default() -> Self {
1323+
Self {
1324+
state: Mutex::new(DeferredState::Fresh),
1325+
ready: Condvar::new(),
1326+
}
1327+
}
1328+
}
1329+
1330+
impl<T: Clone> DeferredSlot<T> {
1331+
fn get(&self) -> Option<T> {
1332+
let state = self.state.lock();
1333+
match &*state {
1334+
DeferredState::Done(value) => Some(value.clone()),
1335+
DeferredState::Fresh | DeferredState::Running => None,
1336+
}
1337+
}
1338+
1339+
fn compute(&self, compute: impl FnOnce() -> T) -> T {
1340+
let mut compute = Some(compute);
1341+
loop {
1342+
let mut state = self.state.lock();
1343+
match &*state {
1344+
DeferredState::Done(value) => return value.clone(),
1345+
DeferredState::Fresh => {
1346+
*state = DeferredState::Running;
1347+
drop(state);
1348+
return self.finish(compute.take().expect("compute closure used once"));
1349+
}
1350+
DeferredState::Running => self.ready.wait(&mut state),
1351+
}
1352+
}
1353+
}
1354+
1355+
fn finish(&self, compute: impl FnOnce() -> T) -> T {
1356+
let mut reset = ResetDeferredSlot {
1357+
slot: self,
1358+
armed: true,
1359+
};
1360+
let value = compute();
1361+
*self.state.lock() = DeferredState::Done(value.clone());
1362+
reset.armed = false;
1363+
self.ready.notify_all();
1364+
value
1365+
}
1366+
}
1367+
1368+
struct ResetDeferredSlot<'a, T> {
1369+
slot: &'a DeferredSlot<T>,
1370+
armed: bool,
1371+
}
1372+
1373+
impl<T> Drop for ResetDeferredSlot<'_, T> {
1374+
fn drop(&mut self) {
1375+
if self.armed {
1376+
*self.slot.state.lock() = DeferredState::Fresh;
1377+
self.slot.ready.notify_all();
1378+
}
1379+
}
1380+
}
12611381

12621382
#[derive(Clone)]
12631383
struct IncrCacheMap<K, V> {
12641384
revision: usize,
12651385
global: Arc<Mutex<FxDashMap<K, (usize, V)>>>,
1266-
prev: Arc<Mutex<FxHashMap<K, DeferredCompute<V>>>>,
1267-
next: Arc<Mutex<FxHashMap<K, DeferredCompute<V>>>>,
1386+
prev: Arc<Mutex<FxHashMap<K, IncrDeferredCompute<V>>>>,
1387+
next: Arc<Mutex<FxHashMap<K, IncrDeferredCompute<V>>>>,
12681388
}
12691389

12701390
impl<K: Eq + Hash, V> Default for IncrCacheMap<K, V> {
@@ -1279,41 +1399,60 @@ impl<K: Eq + Hash, V> Default for IncrCacheMap<K, V> {
12791399
}
12801400

12811401
impl<K, V> IncrCacheMap<K, V> {
1402+
fn get(&self, key: &K) -> Option<V>
1403+
where
1404+
K: Eq + Hash,
1405+
V: Clone,
1406+
{
1407+
// Only reuse a completed slot from this revision. Global values are
1408+
// consumed by the blocking owner so recursive routes stay isolated.
1409+
let next = self.next.lock().get(key).cloned();
1410+
next.and_then(|slot| slot.get())
1411+
}
1412+
12821413
fn compute(&self, key: K, compute: impl FnOnce(Option<V>) -> V) -> V
12831414
where
12841415
K: Clone + Eq + Hash,
12851416
V: Clone,
12861417
{
12871418
let next = self.next.lock().entry(key.clone()).or_default().clone();
12881419

1289-
next.get_or_init(|| {
1290-
let prev = self.prev.lock().get(&key).cloned();
1291-
let prev = prev.and_then(|prev| prev.get().cloned());
1292-
let prev = prev.or_else(|| {
1293-
let global = self.global.lock();
1294-
global.get(&key).map(|global| global.1.clone())
1295-
});
1296-
1297-
let res = compute(prev);
1420+
next.compute(|| self.compute_and_publish(key, compute))
1421+
}
12981422

1423+
fn compute_and_publish(&self, key: K, compute: impl FnOnce(Option<V>) -> V) -> V
1424+
where
1425+
K: Clone + Eq + Hash,
1426+
V: Clone,
1427+
{
1428+
let prev = self.prev.lock().get(&key).cloned();
1429+
let prev = prev.and_then(|prev| prev.get());
1430+
let prev = prev.or_else(|| {
12991431
let global = self.global.lock();
1300-
let entry = global.entry(key.clone());
1301-
use dashmap::mapref::entry::Entry;
1302-
match entry {
1303-
Entry::Occupied(mut entry) => {
1304-
let (revision, _) = entry.get();
1305-
if *revision < self.revision {
1306-
entry.insert((self.revision, res.clone()));
1307-
}
1308-
}
1309-
Entry::Vacant(entry) => {
1432+
global
1433+
.get(&key)
1434+
.filter(|global| global.0 <= self.revision)
1435+
.map(|global| global.1.clone())
1436+
});
1437+
1438+
let res = compute(prev);
1439+
1440+
let global = self.global.lock();
1441+
let entry = global.entry(key.clone());
1442+
use dashmap::mapref::entry::Entry;
1443+
match entry {
1444+
Entry::Occupied(mut entry) => {
1445+
let (revision, _) = entry.get();
1446+
if *revision < self.revision {
13101447
entry.insert((self.revision, res.clone()));
13111448
}
13121449
}
1450+
Entry::Vacant(entry) => {
1451+
entry.insert((self.revision, res.clone()));
1452+
}
1453+
}
13131454

1314-
res
1315-
})
1316-
.clone()
1455+
res
13171456
}
13181457

13191458
fn crawl(&self, revision: usize) -> Self {
@@ -1326,6 +1465,68 @@ impl<K, V> IncrCacheMap<K, V> {
13261465
}
13271466
}
13281467

1468+
#[cfg(test)]
1469+
mod incr_cache_tests {
1470+
use std::sync::{Arc, Barrier, mpsc::sync_channel};
1471+
1472+
use super::IncrCacheMap;
1473+
1474+
#[test]
1475+
fn recursive_lookup_does_not_wait_on_running_slot() {
1476+
let cache = IncrCacheMap::<u8, u8>::default();
1477+
let started = Arc::new(Barrier::new(2));
1478+
let (release, wait) = sync_channel(0);
1479+
1480+
std::thread::scope(|scope| {
1481+
let owner_cache = cache.clone();
1482+
let owner_started = started.clone();
1483+
let owner = scope.spawn(move || {
1484+
owner_cache.compute(1, |_| {
1485+
owner_started.wait();
1486+
wait.recv().unwrap();
1487+
1
1488+
})
1489+
});
1490+
1491+
started.wait();
1492+
assert_eq!(cache.get(&1), None);
1493+
release.send(()).unwrap();
1494+
assert_eq!(owner.join().unwrap(), 1);
1495+
});
1496+
1497+
assert_eq!(cache.get(&1), Some(1));
1498+
}
1499+
1500+
#[test]
1501+
fn panicked_owner_releases_deferred_slot() {
1502+
let cache = IncrCacheMap::<u8, u8>::default();
1503+
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1504+
cache.compute(1, |_| panic!("test panic"))
1505+
}));
1506+
1507+
assert!(result.is_err());
1508+
assert_eq!(cache.get(&1), None);
1509+
assert_eq!(cache.compute(1, |_| 2), 2);
1510+
}
1511+
1512+
#[test]
1513+
fn older_revision_does_not_read_newer_global_value() {
1514+
let cache = IncrCacheMap::<u8, u8>::default();
1515+
let newer = cache.crawl(2);
1516+
assert_eq!(newer.compute(1, |_| 20), 20);
1517+
1518+
let older = cache.crawl(1);
1519+
assert_eq!(
1520+
older.compute(1, |prev| {
1521+
assert_eq!(prev, None);
1522+
10
1523+
}),
1524+
10
1525+
);
1526+
assert_eq!(older.get(&1), Some(10));
1527+
}
1528+
}
1529+
13291530
#[derive(Clone)]
13301531
struct CacheMap<T> {
13311532
m: Arc<FxDashMap<u128, (u64, T)>>,

0 commit comments

Comments
 (0)