fix(cross-repo): no row count decides which calls are matched

Each of the cross-repo scans -- a project's HTTP_CALLS, async and channel
edges, a target's Route templates for fuzzy matching -- stopped after
4,096 rows and treated the rest as not there. A call examined after
4,096 others never got its CROSS_HTTP_CALLS edge even though the target
serves its route; which edges existed depended on how many others the
project held. No count may decide graph content.

The scans now read every row; only a SQLite error counts as failure, as
before. The project-cache guards (CR_MAX_PROJECTS,
CR_MAX_CACHE_ENTRIES) stay: they fail the run visibly instead of
dropping content.

cross_repo_scan_bound_counts_examined_rows_not_matches asserted that the
4,097th call stays unmatched; as cross_repo_matches_calls_past_4096_rows
it asserts the edge exists, and fails on the bounded scans. None of the
17 infra corpora has 4,096 rows in one of these scans, so their graphs
are unchanged.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel committed 2026-10-10 15:24:17 +02:00
1 parent 0a65d1ec80
commit 0ac529f5f4
2 files changed
+19 -32

No files matched your search

+13 -29
View File
@@ -33,7 +33,6 @@ enum {
CR_PATH_BUF = 4096,
CR_QN_BUF = 512,
CR_PROPS_BUF = 2048,
CR_MAX_EDGES = 4096,
CR_DB_EXT_LEN = 3, /* strlen(".db") */
CR_INIT_CAP = 32,
CR_MAX_PROJECTS = 4096,
@@ -481,9 +480,9 @@ static bool cr_path_matches_template(const char *concrete, const char *templ) {
* the route QN into out_qn and return the handler id; returns 0 on no match.
*
* COST: this scans every Route node of the target project once per unmatched
* HTTP_CALLS edge — O(calls × routes) per project pair. Acceptable while both
* factors stay small (calls are capped at CR_MAX_EDGES and it only runs for
* edges the exact lookup missed); revisit with a prepared template index if
* HTTP_CALLS edge — O(calls × routes) per project pair. It only runs for edges
* the exact lookup missed; no row count stops a scan (a cap decided which
* calls were matched at all); revisit with a prepared template index if
* cross-repo matching ever shows up in profiles. (#523) */
static int64_t find_route_handler_fuzzy(cbm_store_t *target_store, const char *concrete_path,
const char *method, char *out_qn, size_t out_qn_sz,
@@ -504,11 +503,8 @@ static int64_t find_route_handler_fuzzy(cbm_store_t *target_store, const char *c
return 0;
}
int64_t found = 0;
int scanned = 0;
int step_rc = SQLITE_DONE;
while (scanned < CR_MAX_EDGES && !cr_cancel_requested(ctx) &&
(step_rc = sqlite3_step(s)) == SQLITE_ROW) {
scanned++;
while (!cr_cancel_requested(ctx) && (step_rc = sqlite3_step(s)) == SQLITE_ROW) {
const char *qn = (const char *)sqlite3_column_text(s, 0);
if (!qn || strncmp(qn, "__route__", CR_ROUTE_PREFIX_LEN) != 0) {
continue;
@@ -550,7 +546,7 @@ static int64_t find_route_handler_fuzzy(cbm_store_t *target_store, const char *c
break;
}
}
if (!cr_cancel_requested(ctx) && scanned < CR_MAX_EDGES && step_rc != SQLITE_DONE) {
if (!cr_cancel_requested(ctx) && step_rc != SQLITE_DONE) {
*failed = true;
}
if (sqlite3_finalize(s) != SQLITE_OK) {
@@ -713,12 +709,9 @@ static cr_match_result_t match_http_routes(cbm_store_t *src_store, const char *s
}
int count = 0;
int scanned = 0;
int step_rc = SQLITE_DONE;
bool failed = false;
while (scanned < CR_MAX_EDGES && !cr_cancel_requested(ctx) &&
(step_rc = sqlite3_step(s)) == SQLITE_ROW) {
scanned++;
while (!cr_cancel_requested(ctx) && (step_rc = sqlite3_step(s)) == SQLITE_ROW) {
int64_t caller_id = sqlite3_column_int64(s, 0);
int64_t route_id = sqlite3_column_int64(s, SKIP_ONE);
const char *props = (const char *)sqlite3_column_text(s, PAIR_LEN);
@@ -772,7 +765,7 @@ static cr_match_result_t match_http_routes(cbm_store_t *src_store, const char *s
count++;
}
if (!failed && !cr_cancel_requested(ctx) && scanned < CR_MAX_EDGES && step_rc != SQLITE_DONE) {
if (!failed && !cr_cancel_requested(ctx) && step_rc != SQLITE_DONE) {
failed = true;
}
if (sqlite3_finalize(s) != SQLITE_OK) {
@@ -804,12 +797,9 @@ static cr_match_result_t match_async_routes(cbm_store_t *src_store, const char *
}
int count = 0;
int scanned = 0;
int step_rc = SQLITE_DONE;
bool failed = false;
while (scanned < CR_MAX_EDGES && !cr_cancel_requested(ctx) &&
(step_rc = sqlite3_step(s)) == SQLITE_ROW) {
scanned++;
while (!cr_cancel_requested(ctx) && (step_rc = sqlite3_step(s)) == SQLITE_ROW) {
int64_t caller_id = sqlite3_column_int64(s, 0);
int64_t route_id = sqlite3_column_int64(s, SKIP_ONE);
const char *props = (const char *)sqlite3_column_text(s, PAIR_LEN);
@@ -854,7 +844,7 @@ static cr_match_result_t match_async_routes(cbm_store_t *src_store, const char *
}
count++;
}
if (!failed && !cr_cancel_requested(ctx) && scanned < CR_MAX_EDGES && step_rc != SQLITE_DONE) {
if (!failed && !cr_cancel_requested(ctx) && step_rc != SQLITE_DONE) {
failed = true;
}
if (sqlite3_finalize(s) != SQLITE_OK) {
@@ -969,12 +959,9 @@ static cr_match_result_t match_channels(cbm_store_t *src_store, const char *src_
}
int count = 0;
int scanned = 0;
int step_rc = SQLITE_DONE;
bool failed = false;
while (scanned < CR_MAX_EDGES && !cr_cancel_requested(ctx) &&
(step_rc = sqlite3_step(s)) == SQLITE_ROW) {
scanned++;
while (!cr_cancel_requested(ctx) && (step_rc = sqlite3_step(s)) == SQLITE_ROW) {
const char *channel_name = (const char *)sqlite3_column_text(s, SKIP_ONE);
const char *channel_qn = (const char *)sqlite3_column_text(s, PAIR_LEN);
if (!channel_name || !channel_qn) {
@@ -1007,7 +994,7 @@ static cr_match_result_t match_channels(cbm_store_t *src_store, const char *src_
count++;
}
}
if (!failed && !cr_cancel_requested(ctx) && scanned < CR_MAX_EDGES && step_rc != SQLITE_DONE) {
if (!failed && !cr_cancel_requested(ctx) && step_rc != SQLITE_DONE) {
failed = true;
}
if (sqlite3_finalize(s) != SQLITE_OK) {
@@ -1084,12 +1071,9 @@ static cr_match_result_t match_typed_routes(cbm_store_t *src_store, const char *
}
int count = 0;
int scanned = 0;
int step_rc = SQLITE_DONE;
bool failed = false;
while (scanned < CR_MAX_EDGES && !cr_cancel_requested(ctx) &&
(step_rc = sqlite3_step(s)) == SQLITE_ROW) {
scanned++;
while (!cr_cancel_requested(ctx) && (step_rc = sqlite3_step(s)) == SQLITE_ROW) {
int64_t caller_id = sqlite3_column_int64(s, 0);
int64_t route_id = sqlite3_column_int64(s, SKIP_ONE);
const char *props = (const char *)sqlite3_column_text(s, PAIR_LEN);
@@ -1139,7 +1123,7 @@ static cr_match_result_t match_typed_routes(cbm_store_t *src_store, const char *
}
count++;
}
if (!failed && !cr_cancel_requested(ctx) && scanned < CR_MAX_EDGES && step_rc != SQLITE_DONE) {
if (!failed && !cr_cancel_requested(ctx) && step_rc != SQLITE_DONE) {
failed = true;
}
if (sqlite3_finalize(s) != SQLITE_OK) {
+6 -3
View File
@@ -574,7 +574,10 @@ static bool cross_repo_seed_bounded_scan(const cross_repo_fixture_t *fixture,
return ok;
}
TEST(cross_repo_scan_bound_counts_examined_rows_not_matches) {
/* No row count decides which calls are matched: the scans stopped after 4,096
* rows, so a call examined after 4,096 others never got its CROSS_HTTP_CALLS
* edge even though the target serves its route. */
TEST(cross_repo_matches_calls_past_4096_rows) {
cross_repo_fixture_t fixture;
bool setup = cross_repo_fixture_begin(&fixture) &&
cross_repo_seed_bounded_scan(&fixture, "bounded-source", "bounded-target");
@@ -589,7 +592,7 @@ TEST(cross_repo_scan_bound_counts_examined_rows_not_matches) {
ASSERT_FALSE(result.failed);
ASSERT_EQ(result.projects_scanned, 1);
ASSERT_EQ(result.http_edges, 0);
ASSERT_EQ(result.http_edges, 1);
PASS();
}
@@ -952,7 +955,7 @@ SUITE(cross_repo) {
RUN_TEST(cross_repo_named_pre768_target_fails_before_cleanup_issue2133);
RUN_TEST(cross_repo_status_distinguishes_never_run_from_ran_without_links);
RUN_TEST(cross_repo_wildcard_reports_skipped_pre768_store);
RUN_TEST(cross_repo_scan_bound_counts_examined_rows_not_matches);
RUN_TEST(cross_repo_matches_calls_past_4096_rows);
RUN_TEST(cross_repo_propagates_delete_failure);
RUN_TEST(cross_repo_failed_bidirectional_insert_is_not_counted);
RUN_TEST(cross_repo_cancel_mid_run_keeps_completed_target_and_stops_before_later_target);