Files
deusdata--codebase-memory-mcp/tests/repro/repro_parallel_determinism.c
T
wehub-resource-sync 41cb1c0170
OpenSSF Scorecard / scorecard (push) Failing after 0s
DCO / dco (push) Failing after 0s
CodeQL SAST / analyze (push) Failing after 1s
Deploy Pages / deploy (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:28:05 +08:00

207 lines
8.2 KiB
C

/*
* repro_parallel_determinism.c — RED reproduction for the parallel-indexing
* edge-loss / non-determinism bug (REAL-REPO TIER).
*
* DISCOVERED: while verifying F6 (2026-07), re-indexing the SAME corpus with the
* correct cache cleared produced DIFFERENT edge counts run-to-run when
* multi-threaded (kernel fs/xfs: 58548 / 58552 / 58557 / 58573; kernel-rust:
* 44445 / 44775 / 44857) and the multi-threaded counts trend BELOW the
* single-threaded reference (xfs ST 58573). I.e. parallel indexing both flickers
* AND drops edges vs the single-threaded graph.
*
* INVARIANT (green <=> fixed): indexing a fixed corpus is DETERMINISTIC and the
* multi-threaded graph equals the single-threaded graph exactly. This test indexes
* the corpus single-threaded (reference) then K times multi-threaded (fresh store
* each run) and asserts every MT sorted edge set equals the ST one. Comparison is
* over the SORTED (source_qn, type, target_qn) triples — NOT raw counts.
*
* WHY A REAL-REPO TIER (honest calibration record): a self-contained synthetic C
* corpus could NOT trigger the divergence in three escalating attempts —
* (1) 300 files, dense cross-file CALLS only;
* (2) 500 files, big per-function bodies (fingerprinted, nodes_with_fp=500);
* (3) 600 files, TOKEN-IDENTICAL clustered bodies (to force SIMILAR_TO edges);
* all produced a fully DETERMINISTIC graph across ST + 6 MT runs, and the
* similarity pass emitted 0 edges on synthetic C (`pass.similarity edges=0`). The
* race clearly needs real-code edge diversity/volume (real SIMILAR_TO / semantic /
* cross-file type edges) that synthetic C does not produce. Rather than ship a
* false guard (green on buggy code), this uses the smallest REAL corpus on which
* the flicker was directly observed — the kernel's fs/xfs subtree — and SKIPs when
* that corpus is absent (e.g. CI). It is RED on the dev machine where the race
* lives, which is where the fix (deferred) will be developed and guarded.
*
* SUSPECTED ROOT CAUSE (for the fixer): a race in parallel edge production/merge —
* per-worker edge-buffer merge (pass_parallel 2_merge_edge_bufs_seq), graph-buffer
* edge dedup under concurrent append (edge_by_key check-then-insert), similarity
* edge emission (symmetric SIMILAR_TO from both sides), or resolve_worker emission
* ordering. The single-threaded edge set is the reference semantics. Fix DEFERRED —
* this stays on the known-red board until the race is fixed.
*/
#include "test_framework.h"
#include "repro_harness.h"
#include <store/store.h>
#include <pipeline/pipeline.h>
#include <sqlite3.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#define RPD_MT_RUNS 4 /* K multi-threaded runs compared against the ST reference */
/* Smallest real corpus on which the flicker was directly observed. */
#define RPD_CORPUS "/Users/martinvogel/perf-bench/linux/fs/xfs"
/* Sorted (source_qn|type|target_qn) fingerprint of the whole project graph.
* Heap string (caller frees) or NULL on error. */
static char *rpd_edge_fingerprint(cbm_store_t *s, const char *project) {
struct sqlite3 *db = cbm_store_get_db(s);
if (!db)
return NULL;
const char *sql = "SELECT s.qualified_name, e.type, t.qualified_name "
"FROM edges e "
"JOIN nodes s ON e.source_id = s.id "
"JOIN nodes t ON e.target_id = t.id "
"WHERE e.project = ?1 "
"ORDER BY 1, 2, 3;";
sqlite3_stmt *stmt = NULL;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK)
return NULL;
sqlite3_bind_text(stmt, 1, project, -1, SQLITE_TRANSIENT);
size_t cap = 1 << 20, len = 0;
char *buf = (char *)malloc(cap);
if (!buf) {
sqlite3_finalize(stmt);
return NULL;
}
buf[0] = '\0';
while (sqlite3_step(stmt) == SQLITE_ROW) {
const char *src = (const char *)sqlite3_column_text(stmt, 0);
const char *typ = (const char *)sqlite3_column_text(stmt, 1);
const char *tgt = (const char *)sqlite3_column_text(stmt, 2);
src = src ? src : "";
typ = typ ? typ : "";
tgt = tgt ? tgt : "";
size_t need = strlen(src) + strlen(typ) + strlen(tgt) + 4;
while (len + need >= cap) {
cap *= 2;
char *nb = (char *)realloc(buf, cap);
if (!nb) {
free(buf);
sqlite3_finalize(stmt);
return NULL;
}
buf = nb;
}
len += (size_t)snprintf(buf + len, cap - len, "%s|%s|%s\n", src, typ, tgt);
}
sqlite3_finalize(stmt);
return buf;
}
/* Index `repo` into a freshly-unlinked isolated store and return the edge
* fingerprint. Honors CBM_INDEX_SINGLE_THREAD from the environment. */
static char *rpd_index_and_fingerprint(const char *repo, const char *dbpath) {
unlink(dbpath);
char wal[600], shm[600];
snprintf(wal, sizeof(wal), "%s-wal", dbpath);
snprintf(shm, sizeof(shm), "%s-shm", dbpath);
unlink(wal);
unlink(shm);
cbm_pipeline_t *p = cbm_pipeline_new(repo, dbpath, CBM_MODE_FULL);
if (!p)
return NULL;
int rc = cbm_pipeline_run(p);
cbm_pipeline_free(p);
if (rc != 0)
return NULL;
char *project = cbm_project_name_from_path(repo);
if (!project)
return NULL;
cbm_store_t *s = cbm_store_open_path(dbpath);
char *fp = s ? rpd_edge_fingerprint(s, project) : NULL;
if (s)
cbm_store_close(s);
free(project);
return fp;
}
/* GUARD (green <=> the parallel-indexing races are fixed): repeated
* multi-threaded runs over a fixed corpus must produce the IDENTICAL sorted
* node+edge set. Root causes fixed (all order/scheduling dependence):
* semantic-pass admission + canonical funcs order, import target first-match,
* similarity ordering + id-based pair ownership, call-neighbor truncation
* subsets, and the QN-collision last-wins overwrite in gbuf upsert AND merge
* (a C struct/function/macro sharing one name flipped label by merge order). */
TEST(repro_parallel_edge_determinism) {
struct stat st;
if (stat(RPD_CORPUS, &st) != 0 || !S_ISDIR(st.st_mode)) {
SKIP("real-repo tier: corpus " RPD_CORPUS
" absent (synthetic C could not trigger the parallel edge race — see header)");
}
char dbpath[512];
snprintf(dbpath, sizeof(dbpath), "%s/cbm_rpd_par_det.db", cbm_tmpdir());
/* First multi-threaded run = reference; every further MT run must match. */
char *fp_ref = rpd_index_and_fingerprint(RPD_CORPUS, dbpath);
ASSERT_NOT_NULL(fp_ref);
ASSERT_TRUE(strlen(fp_ref) > 0);
int diverged = 0;
for (int k = 1; k < RPD_MT_RUNS && !diverged; k++) {
char *fp_mt = rpd_index_and_fingerprint(RPD_CORPUS, dbpath);
ASSERT_NOT_NULL(fp_mt);
if (strcmp(fp_mt, fp_ref) != 0)
diverged = 1;
free(fp_mt);
}
unlink(dbpath);
free(fp_ref);
ASSERT_EQ(diverged, 0);
PASS();
}
/* RED (open bug, fix deferred): the SEQUENTIAL pipeline and the PARALLEL
* pipeline are different code paths that produce SYSTEMATICALLY different
* graphs — on the xfs corpus they disagree on ~3459 USAGE, ~1666 WRITES and
* ~60 CALLS sorted-edge lines, consistently (each mode is internally
* deterministic after the race fixes above; the modes just don't agree).
* GREEN when both pipelines emit the same graph for the same corpus. */
TEST(repro_seq_parallel_equivalence) {
struct stat st;
if (stat(RPD_CORPUS, &st) != 0 || !S_ISDIR(st.st_mode)) {
SKIP("real-repo tier: corpus " RPD_CORPUS " absent");
}
char dbpath[512];
snprintf(dbpath, sizeof(dbpath), "%s/cbm_rpd_seq_par.db", cbm_tmpdir());
setenv("CBM_INDEX_SINGLE_THREAD", "1", 1);
char *fp_st = rpd_index_and_fingerprint(RPD_CORPUS, dbpath);
unsetenv("CBM_INDEX_SINGLE_THREAD");
ASSERT_NOT_NULL(fp_st);
char *fp_mt = rpd_index_and_fingerprint(RPD_CORPUS, dbpath);
ASSERT_NOT_NULL(fp_mt);
int equal = strcmp(fp_st, fp_mt) == 0;
unlink(dbpath);
free(fp_st);
free(fp_mt);
ASSERT_EQ(equal, 1);
PASS();
}
void suite_repro_parallel_determinism(void) {
RUN_TEST(repro_parallel_edge_determinism);
RUN_TEST(repro_seq_parallel_equivalence);
}