tinymist/crates/tinymist-project
Joseph Liu 66351570a6
Some checks are pending
tinymist::auto_tag / auto-tag (push) Waiting to run
tinymist::ci / Duplicate Actions Detection (push) Waiting to run
tinymist::ci / Check Clippy, Formatting, Completion, Documentation, and Tests (Linux) (push) Waiting to run
tinymist::ci / Check Minimum Rust version and Tests (Windows) (push) Waiting to run
tinymist::ci / prepare-build (push) Waiting to run
tinymist::ci / announce (push) Blocked by required conditions
tinymist::ci / build (push) Blocked by required conditions
tinymist::gh_pages / build-gh-pages (push) Waiting to run
fix(tinymist-project): invalidate cached snapshot after compile (#2057)
This PR fixes a latent cache invalidation issue with project snapshots.
I recommend reviewing commit-by-commit, even though this is a small
change. Only the last commit changes behavior; the rest are cleaning up
code to make it easier to see what is happening.

---

Here's what I think is the problem.

Project instance states (`ProjectInsState`) provide access to a snapshot
of the latest compilation result. This snapshot is cached for
performance:
```rust
match self.snapshot.as_ref() {
    Some(snap) if snap.world().revision() == self.verse.revision => snap.clone(),
    // otherwise recompute
}
```
Unfortunately, it turns out that the cached snapshot may be outdated
even if the revision matches. (In practice, it will usually just lag one
compilation behind, and I think this is why it hasn't caused any
observable issues--one compilation behind is not that much, especially
if the user is continuously typing.)

To see the issue, consider the following sequence of steps. Assume
initially all revisions are `0`.
1. User triggers a recompilation, say by typing into the file.
     - This triggers an `Interrupt::Memory` or similar.
2. The changes are applied to the vfs and recompilation begins.
     - `verse.revision` is incremented to `verse.revision = 1`.
3. Before compilation finishes, a project snapshot is requested.
- The project compiler checks the cached snapshot first, which still has
`snap.world().revision() == 0`.
- Since `verse.revision = 1` was already incremented before the
compilation finished, a new snapshot is created and cached--even though
nothing has actually been recompiled yet.
    - The cached snapshot now has `snap.world().revision() == 1`.
4. Compilation finishes, and some data from the resulting compilation
artifact is recorded.
5. Another project snapshot is requested.
- Since the snapshot was recomputed already in (3), the revisions appear
to match and the outdated, cached snapshot is returned.

The fix here is to just clear the cached snapshot in (4) after
recompilation finishes.

---

I would add a regression test, but it didn't seem easy to write an
automated test that can differentiate the change here. I tested this
locally by adding some logging around when the cached snapshot is used
and when it is recomputed.

As mentioned on Discord, I noticed this problem when adding some more
data to project snapshots on a local fork of Tinymist, which caused the
slightly outdated state to become a little more obvious.

---------

Co-authored-by: Myriad-Dreamin <camiyoru@gmail.com>
2025-08-27 00:45:38 +08:00
..
src fix(tinymist-project): invalidate cached snapshot after compile (#2057) 2025-08-27 00:45:38 +08:00
Cargo.toml build: bump version to 0.13.24 (#2085) 2025-08-26 16:15:03 +08:00
README.md feat: bump typst to v0.13.0-rc1 (#1342) 2025-02-21 03:18:04 +08:00

tinymist-project

Project model of typst for tinymist.