Skip to content

fix(try_reserve): skip slot lookup growth when capacity already fits#765

Merged
ashvardanian merged 1 commit into
unum-cloud:main-devfrom
desertfury:fix/fuzz-try-reserve-runaway-growth
May 24, 2026
Merged

fix(try_reserve): skip slot lookup growth when capacity already fits#765
ashvardanian merged 1 commit into
unum-cloud:main-devfrom
desertfury:fix/fuzz-try-reserve-runaway-growth

Conversation

@desertfury
Copy link
Copy Markdown
Contributor

`index_dense_gt::try_reserve` unconditionally called `slot_lookup_.try_reserve(limits.members)`. The hash table's load-factor check `capacity * 3 <= capacity_slots * 2` always failed when `capacity == capacity_slots`, so the table grew at 1.5× per call. Every `usearch_change_threads_search` / `usearch_change_threads_add` (which reuses the current `limits.members`) then doubled the slot lookup. The fuzzer found this after enough iterations: members 9 → 64 → 128 → 256 → ... → 67 M, triggering a 1 GiB allocation that breached libFuzzer's rss limit.

Only forwards to the lookup's grow path when the requested members truly exceed the current capacity; otherwise the lookup stays as-is.

@ashvardanian ashvardanian force-pushed the fix/fuzz-try-reserve-runaway-growth branch from 3e13223 to eb5889c Compare May 24, 2026 12:06
`index_dense_gt::try_reserve` reused the keyed lookup capacity as the member limit after reserving. The lookup reported physical hash slots, so later thread-only reserves fed that larger number back into the hash-table growth path and kept expanding the table.

Make the hash table report logical element capacity under its load factor, while keeping the physical slot count available through an explicit `slots_capacity()` accessor. Repeated reserves that only change thread counts now leave the keyed lookup unchanged.

Co-authored-by: Mikhail Chichvarin <6496186+desertfury@users.noreply.github.com>

Co-authored-by: Mikhail Chichvarin <desertfury@nebius.com>

Co-authored-by: Ash Vardanian <1983160+ashvardanian@users.noreply.github.com>
@ashvardanian ashvardanian force-pushed the fix/fuzz-try-reserve-runaway-growth branch from eb5889c to 459e53f Compare May 24, 2026 12:07
@ashvardanian ashvardanian changed the base branch from main to main-dev May 24, 2026 12:08
@ashvardanian ashvardanian merged commit 3cf843b into unum-cloud:main-dev May 24, 2026
ashvardanian pushed a commit that referenced this pull request May 24, 2026
### Patch

- Fix: Refuse operations without reserved thread contexts (#757) (b8d3403)
- Fix: Checked arithmetic for allocation sizes (#763) (89da7c5)
- Fix: Preserve hash lookup capacity across thread reserves (#765) (3cf843b)
- Fix: Guard quantized casts against zero-magnitude inputs (#758) (0c903f4)
- Fix: Refuse missing metrics in C change-metric API (#760) (490c1b2)
- Fix: Short-circuit self-renames (#761) (830f31a)
- Fix: Keep `vectors_lookup_` capacity after `clear()` #759 (9d77be5)
- Improve: Serialize concurrent same-`Index` Python access with a mutex (47528b5)
- Improve: Test GIL-release contract and progress-callback path (a598493)
- Improve: Release Python GIL during long index operations (d8be67d)
- Fix: Restore `ring_gt::try_push` return value (18c44ee)
- Fix: Stop JavaScript `Remove` loop after exception throw (f3e1052)
- Fix: Stop `usearch_init` on `make` failure (2aa0070)
- Fix: Stop C-ABI metadata readers on failure (34889ee)
- Fix: Report OOM from C-ABI thread-limit changers (ad24056)
- Fix: Bounded probe in `equal_iterator_gt::operator++` (b779c47)
- Fix: Resize cast buffer in `change_metric` for new bytes-per-vector (d544745)
- Fix: Eager-reserve thread contexts in `index_dense_gt::make` (#755) (b296566)
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.

2 participants