Skip to content

fix(vector): fix 4 bugs causing Windows segfault + silent index corruption (HNSW)#21

Closed
chiangchenghsin-hash wants to merge 3 commits into
LadybugDB:mainfrom
chiangchenghsin-hash:fix/vector-windows-crash
Closed

fix(vector): fix 4 bugs causing Windows segfault + silent index corruption (HNSW)#21
chiangchenghsin-hash wants to merge 3 commits into
LadybugDB:mainfrom
chiangchenghsin-hash:fix/vector-windows-crash

Conversation

@chiangchenghsin-hash

Copy link
Copy Markdown
Contributor

Summary

Fixes 4 bugs in the VECTOR extension that cause SEGFAULT (0xC0000005) on Windows and silent index corruption on all platforms. Verified on Windows 11 x64 with MSVC 19.50, LadybugDB 0.18.0.

Supersedes #499 (closed without fix — bug still present in 0.18.0 source).

Bugs Fixed

Bug 1: MSVC const-correctness — create_hnsw_index.cpp:60

NodeTable::getNumTotalRows() is non-const. MSVC rejects const auto& table calling it (error C2662). Clang/GCC tolerate it, so this only blocks Windows source builds.

-    const auto& table =
+    auto& table =
         storage::StorageManager::Get(*context)->getTable(tableID)->cast<storage::NodeTable>();

Bug 2: Stale table cardinality — create_hnsw_index.cpp:64

getStats(transaction).getTableCard() returns a statistics snapshot that can be 0 or stale during uncommitted transactions / WAL replay. This caused numNodes=0 → HNSW graph allocated with wrong size.

-    auto numNodes = table.getStats(transaction).getTableCard();
+    auto numNodes = table.getNumTotalRows(transaction);

Bug 3: DUMMY_CHECKPOINT_TRANSACTION in finalize()hnsw_index.cpp:672

OnDiskHNSWIndex::finalize() queried row count via the static dummy transaction, which returns 0 in non-checkpoint contexts. This made finalize() silently skip incremental writes — newly inserted nodes were never indexed. This is a platform-agnostic correctness bug (Linux silently loses index coverage; Windows additionally crashes downstream).

 void OnDiskHNSWIndex::finalize(main::ClientContext* context) {
     auto& hnswStorageInfo = storageInfo->cast<HNSWStorageInfo>();
-    const auto numTotalRows = nodeTable.getNumTotalRows(&DUMMY_CHECKPOINT_TRANSACTION);
+    auto transaction = Transaction::Get(*context);
+    const auto numTotalRows = nodeTable.getNumTotalRows(transaction);
     if (numTotalRows == hnswStorageInfo.numCheckpointedNodes) {
         return;
     }
-    auto transaction = Transaction::Get(*context);

Bug 4: INVALID_OFFSET out-of-bounds — hnsw_rel_batch_insert.cpp:127

Uninitialized CSR neighbor slots are marked INVALID_OFFSET. The batch insert loop passed them to graphToNodeOffset() without filtering → out-of-bounds access → 0xC0000005 on Windows (Linux silently writes garbage).

     for (const auto neighbourGraphOffset : neighbours) {
+        if (neighbourGraphOffset == common::INVALID_OFFSET) {
+            continue;
+        }
         const auto neighbourNodeOffset = selectionMap.graphToNodeOffset(neighbourGraphOffset);

Why these matter beyond Windows

Bugs 2, 3, and 4 are platform-agnostic correctness bugs. On Linux they don't crash (glibc's tolerant malloc) but silently produce:

  • incomplete HNSW indexes (bug 3: finalize skips new nodes)
  • corrupted graph edges (bug 4: garbage offsets written)
  • wrong recall (cf. #351 — 18% recall vs Kuzu's 47%, likely caused by incomplete indexes)

Verification

Built from source on Windows 11 with MSVC 19.50 + CMake Ninja (BUILD_EXTENSIONS=vector). End-to-end test:

LOAD EXTENSION 'libvector.lbug_extension';
CREATE NODE TABLE v (id INT64, emb DOUBLE[3], PRIMARY KEY(id));
CREATE (v {id:1, emb:[0.1,0.2,0.3]});
CREATE (v {id:2, emb:[0.4,0.5,0.6]});
CREATE (v {id:3, emb:[0.7,0.8,0.9]});
CALL CREATE_VECTOR_INDEX('v','vi','emb',metric:='cosine');
CALL QUERY_VECTOR_INDEX('v','vi',CAST([0.15,0.25,0.35],'DOUBLE[3]'),2) RETURN node.id, distance;
-- Returns: id=1 dist=0.002585, id=2 dist=0.011805

Before the fix: LOAD VECTORSTATUS_ACCESS_VIOLATION (deterministic crash, exit -1073741819).

Related

  • Fixes #499 (SEGFAULT on LOAD VECTOR — Windows x64)
  • Possibly related to #351 (poor vector recall — incomplete indexes)
  • Possibly related to #431 (LOAD EXTENSION fts segfault — same loading path)

@adsharma

adsharma commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Moved to #23 with CI

@adsharma adsharma closed this Jul 4, 2026
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