@@ -8,7 +8,7 @@ use std::{collections::HashSet, ops::Deref};
88use comemo::{Track, Tracked};
99use ecow::EcoString;
1010use lsp_types::Url;
11- use parking_lot::Mutex;
11+ use parking_lot::{Condvar, Mutex} ;
1212use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
1313use rustc_hash::FxHashMap;
1414use 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
12601305type 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)]
12631383struct 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
12701390impl<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
12811401impl<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)]
13301531struct CacheMap<T> {
13311532 m: Arc<FxDashMap<u128, (u64, T)>>,
0 commit comments