Skip to content

test(workspace): the document caches follow a long editing session - #956

Open
psteinroe wants to merge 1 commit into
perf/bound-document-cachesfrom
test/edit-churn-memory
Open

psteinroe wants to merge 1 commit into
perf/bound-document-cachesfrom
test/edit-churn-memory

Conversation

@psteinroe

@psteinroe psteinroe commented Oct 24, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #951.

The memory test from #951 covers open, requests, close. It doesn't cover a long editing session, which is where a leak in the caches keyed by statement text would show: the parse results, trees and annotations of each statement (#913), the analysis (#929), and the hover snapshot (#913). This adds that test. It found no leak.

The test

tests/edit_churn.rs opens a document of 1,000 statements: tables, indexes, SQL functions with a body, and queries. It then applies 2,000 statement edits at random positions, using a seeded xorshift so failures reproduce. The edits rewrite, insert or delete a statement, or change the body of a SQL function, and keep the document within 10% of its length. They are applied in 500 change_file calls of 4 edits each. Hover and inlay hints run every 5 changes; diagnostics and code actions every 50. After a final full pass it asserts:

  • the document has a parse result and a tree for exactly its current distinct statements, function bodies included, and no more. The test compares parse_results and trees against statements from a new #[doc(hidden)] WorkspaceServer::document_cache_stats;
  • the analysis is cached;
  • the memory retained after the edits is at most 1.5× what it was after the first pass over the freshly opened document.

To check that it catches a leak, I disabled the pruning in Document::update_content locally. The test then failed with 3,440 parse results and 3,209 trees for 1,235 statements, and 85.9 MiB retained against 32.8 MiB.

CHURN_STATEMENTS and CHURN_EDITS change the workload. The counting allocator moves from memory_bounds.rs to tests/common/mod.rs, so both memory tests use it. Each stays in its own test binary, so nothing else allocates while it counts.

Numbers

run retained after the first pass retained after the edits statements / parse results / trees at the end
debug, 1,000 statements, 2,000 edits (the CI default, 40 s) 32.8 MiB 36.0 MiB 1,205 / 1,205 / 1,205
release, same 32.8 MiB 36.0 MiB 1,205 / 1,205 / 1,205
release, 5,000 statements, 20,000 edits 134.8 MiB 139.9 MiB 6,196 / 6,196 / 6,196

The small growth is the document's length and the analysis of the final pass, not the edits. The 20,000-edit run retains as much more as the 2,000-edit one.

Found along the way, not changed here

Every change_file re-splits and re-scans the whole document for suppressions. For 1,000 statements in a debug build that is 33 ms of the 40 ms per change: about 32 ms for the statement splitter and 5 ms for suppressions. It is why the test applies several edits per change to stay fast in CI. It is linear in the file and pre-existing; an incremental split would be a separate change.

Opens a document of 1,000 statements and applies 2,000 statement edits
(rewrites, inserts, deletes, and changes of SQL function bodies), with
diagnostics, hover, inlay hints and code actions in between. Afterwards
the caches hold a parse result and a tree for exactly the statements of
the document, and the memory retained is what it was after the first
pass. The counting allocator moves to tests/common for both memory tests.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant