Skip to content

fix: release Python GIL and isolate concurrent index searches - #137

Merged
gouyt13 merged 2 commits into
mainfrom
fix/python-gil-concurrent-search
Oct 9, 2026
Merged

gouyt13 merged 2 commits into
mainfrom
fix/python-gil-concurrent-search

Conversation

@gouyt13

@gouyt13 gouyt13 commented Oct 9, 2026

Copy link
Copy Markdown
Member

Summary

Python index calls hold the GIL during native work, while HNSW and SymphonyQG searches update shared search-window state. Release the GIL around native index search, construction, updates, and file I/O, and keep search parameters local to each call so independent Python threads can safely search an unchanged index.

Add per-index access guards: searches, saves, and property reads may coexist; conflicting mutations immediately raise RuntimeError("Index is busy: conflicting operation in progress"). Retain input array owners and reacquire the GIL before Python object handling and guard destruction. Add QuantizedGraph::search_batch_with_ef while preserving existing default-window APIs.

Python signatures/defaults and saved-index formats remain unchanged. Downstream callers must synchronize conflicting updates and must not mutate input arrays during calls. C++ callers still synchronize mutations themselves; dependent C++ binaries should be rebuilt with matching headers/library. Document the concurrency contract and add Python/C++ regression tests. No release is included.

Testing

Reviewed and tested 46160f1 against main (0973bef) on Linux x86-64, GCC 14.3, Python 3.13:

  • Python: 968 passed, 2 platform-specific repaired-wheel checks skipped, including the separately run 18 C++ example integration tests.
  • Portable C++ (RABITQ_ENABLE_NATIVE_OPTIMIZATION=OFF, GoogleTest 1.14.0): 320 passed, 1 conditional skip; independent OpenMPBinding passed. Available generic, AVX2, and AVX-512 HNSW backend checks passed.
  • Supplementary concurrency stress: 20 L2/IP/storage/initializer configurations and 600 operations, including 300 save/load/search round trips; outputs match serial references. Access guards recover after native save errors.
  • Full core/binding clang-tidy, C++ formatting, Python formatting/lint, strict MkDocs build, and git diff --check: passed.
  • Controlled GIST1M QGKMeans A/B (4,096 clusters, 48 threads, 25 iterations, seed 42, exact final assignment; matched Release/native builds; 3 runs per configuration): contiguous-input medians 14.42 s on main and 14.38 s on this branch; strided-input medians 15.26 s and 15.19 s. No training regression observed. All output centroids/assignments match bitwise; WCSS 1,034,787.870122 and recall@10 0.8196 at nprobe=40. This is not a general speedup claim.

Windows, macOS, native ARM64, and ThreadSanitizer were not run locally.

@gouyt13
gouyt13 merged commit 53dd904 into main Oct 9, 2026
37 checks passed
@gouyt13
gouyt13 deleted the fix/python-gil-concurrent-search branch October 9, 2026 06:22
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