Skip to content

Fix CI index abort: bound open-buffer indexing, pre-size the symbol map - #547

Open
metif12 wants to merge 4 commits into
vlang:masterfrom
metif12:fix/index-memory-ci
Open

metif12 wants to merge 4 commits into
vlang:masterfrom
metif12:fix/index-memory-ci

Conversation

@metif12

@metif12 metif12 commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Fixes Windows CI abort Too many retries in GC_alloc_large in
test_large_vlang_v_workspace_from_env: indexing the whole vlang/v
tree inserts thousands of rich entries one by one, and a late doubling
of the map backing store fails under a fragmented heap (backtrace:
map__set → DenseArray__expand via reindex_uri).

  • Pre-size the symbol index once from the walk's file count
    (map.reserve), so bulk walks allocate up front instead of
    doubling repeatedly.
  • Apply the 2MB index size gate to open buffers too; previously
    only disk files were capped, so one huge buffer could force an
    unbounded index build.

Both changes are behavior-preserving for normal sizes; oversized
entries are dropped and marked skipped exactly like oversized disk
files. Verified with targeted tests (one proves teeth by failing
with the gate disabled) plus the existing large-project suites.

reindex_uri capped disk files at 2MB but indexed open buffers of any size, allowing one huge buffer to force an unbounded index build. The gate now covers both paths; oversized entries are dropped and marked skipped exactly like oversized disk files.
Inserting thousands of entries one by one doubles the map backing store repeatedly; a late doubling can abort under a fragmented heap (GC_alloc_large on big trees). Reserve once from the walk's file count.
@JalonSolov

Copy link
Copy Markdown
Contributor

Local AI Finding

P2: Pre-sizing is skipped for incomplete walks. When collect_v_files reaches its 20,000-file or directory-entry limit, index.v:638 skips the reservation branch, and entries are then inserted one by one until the global cap at index.v:672. Large workspaces therefore retain the repeated-growth and allocation-failure behavior this PR intends to fix. Reserve a bounded target for partial walks as well.

A walk that hits the 20k file or entry limit used to insert what it found one by one until the global cap, keeping the repeated-growth and allocation-failure behavior. Both branches now share one bounded target.
@metif12

metif12 commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

Good catch — agreed, and fixed in d94fbc9.

The reservation no longer depends on the walk being complete. Both branches now share one bounded target:

if !walk_complete {
    app.index_incomplete_scopes[scope] = true
}
target := index_reserve_target(app.symbol_index.len, files.len)
if target > 0 {
    app.symbol_index.reserve(u32(target))
}

index_reserve_target returns 0 when the index is already at or past the global cap, so the bound is unchanged there; otherwise a partial walk reserves what it found, and the growth toward the cap is bounded from the start instead of doubling repeatedly. The target is capped at index_max_files, so files.len from a partial walk (which is already limited) cannot push it over.

Tests: test_index_reserve_target_covers_partial_walks pins the target for empty, small, mid and at-cap cases, and test_partial_walk_still_reserves_what_it_found walks a real directory with an incomplete scope marked by an earlier walk and asserts all entries land.

@medvednikov medvednikov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the latest head (d94fbc9), focusing on code behavior and ignoring CI as requested. The earlier incomplete-walk reservation concern has been addressed, but I found three remaining edge cases:

  1. [P2] Clamp the reservation instead of dropping it when the projected count exceeds the cap (index.v, index_reserve_target). For example, index_reserve_target(12_000, 10_000) returns 0, although ensure_dirs_indexed may still insert another 8,000 entries before reaching index_max_files. In multi-root/partially indexed workspaces, that brings back the incremental map growth this change is meant to prevent. Consider returning min(indexed + found, index_max_files) when indexed < index_max_files, and test the crossing-cap case. Also, test_partial_walk_still_reserves_what_it_found currently performs a complete 20-file walk, so it doesn't actually exercise the partial-walk branch.

  2. [P2] Mark oversized unsaved open buffers as incomplete for rename (reindex_uri / index_is_complete_for_scope). The new open-buffer size check sets index_skipped_uris[uri], but index_is_complete_for_scope only rejects a skipped URI if os.is_file(uri_to_path(uri)) is true. An unsaved new file (>2 MB) in a project has no on-disk file, so rename can proceed as if indexing were complete while silently omitting its references. The completeness check should also recognize skipped URIs that are still in app.open_files.

  3. [P2] Apply the oversized-buffer guard to the occurrence cache too (occurrences_for). reindex_uri drops ref_occurrences for large buffers, but occurrences_for reads the same open buffer without any size gate and rebuilds the full token-occurrence map. The normal local-binding hover path calls it, so hovering an identifier in a large file can still trigger the unbounded index allocation that this change is trying to avoid. Short-circuit occurrence indexing for oversized sources and cover that call path with a test.

These are independent of CI status.

@JalonSolov

JalonSolov commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Can you fix the 3 items from Alex's review?

@metif12

metif12 commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor Author

Can you fix the 3 items from Alex's review?

On it

… as incomplete

index_reserve_target clamps to index_max_files instead of dropping the reservation when the projection crosses the cap; occurrences_for applies the same oversized-source gate as reindex_uri; index_is_complete_for_scope and index_is_complete treat a skipped open buffer with no file on disk as not indexed, so rename refuses instead of silently omitting references.
@metif12

metif12 commented Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

Can you fix the 3 items from Alex's review?

All three addressed

metif12 added a commit to metif12/vls that referenced this pull request Oct 11, 2026
@medvednikov's review of the index-memory change asked for three things, and this branch predates those fixes. Ported across, adapted to this tree: index_reserve_target clamps to index_max_files instead of dropping the reservation when the projection crosses the cap; occurrences_for applies the same oversized-source gate reindex_uri uses; index_is_complete_for_scope and index_is_complete treat a skipped URI still in open_files as not indexed, so an unsaved oversized buffer stops a rename.

Verified: v -check, v fmt -verify, and the full index_test.v green on Windows.
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.

3 participants