Skip to content

Commit ea3cc69

Browse files
committed
fix: rebuild index when mode adds capabilities
Signed-off-by: Anton Standrik <astandrik@yandex-team.ru>
1 parent 7235a4d commit ea3cc69

6 files changed

Lines changed: 944 additions & 122 deletions

File tree

src/pipeline/pipeline.c

Lines changed: 118 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ enum { CBM_DIR_PERMS = 0755, PL_RING = 4, PL_RING_MASK = 3, PL_SEQ_PASSES = 6, P
3535
#include "foundation/compat_thread.h"
3636
#include "foundation/profile.h"
3737
#include "foundation/mem.h"
38+
#include "yyjson/yyjson.h"
3839

3940
#include <stdint.h>
4041
#include <stdio.h>
@@ -153,6 +154,83 @@ static const char *itoa_buf(int val) {
153154
return bufs[i];
154155
}
155156

157+
const char *cbm_pipeline_mode_name(cbm_index_mode_t mode) {
158+
switch (mode) {
159+
case CBM_MODE_FULL:
160+
return "full";
161+
case CBM_MODE_MODERATE:
162+
return "moderate";
163+
case CBM_MODE_FAST:
164+
return "fast";
165+
default:
166+
return "unknown";
167+
}
168+
}
169+
170+
typedef enum {
171+
INDEX_MODE_METADATA_OK = 0,
172+
INDEX_MODE_METADATA_MISSING_OR_INVALID,
173+
INDEX_MODE_METADATA_ERROR,
174+
} index_mode_metadata_status_t;
175+
176+
static index_mode_metadata_status_t parse_index_mode(const char *properties_json,
177+
size_t properties_len,
178+
cbm_index_mode_t *out_mode) {
179+
if (!properties_json || !out_mode) {
180+
return INDEX_MODE_METADATA_ERROR;
181+
}
182+
183+
yyjson_read_err read_error = {0};
184+
yyjson_doc *doc =
185+
yyjson_read_opts((char *)properties_json, properties_len, 0, NULL, &read_error);
186+
if (!doc) {
187+
return read_error.code == YYJSON_READ_ERROR_MEMORY_ALLOCATION
188+
? INDEX_MODE_METADATA_ERROR
189+
: INDEX_MODE_METADATA_MISSING_OR_INVALID;
190+
}
191+
192+
yyjson_val *root = yyjson_doc_get_root(doc);
193+
yyjson_val *mode_value = yyjson_is_obj(root) ? yyjson_obj_get(root, "index_mode") : NULL;
194+
index_mode_metadata_status_t status = INDEX_MODE_METADATA_OK;
195+
if (yyjson_equals_str(mode_value, "full")) {
196+
*out_mode = CBM_MODE_FULL;
197+
} else if (yyjson_equals_str(mode_value, "moderate")) {
198+
*out_mode = CBM_MODE_MODERATE;
199+
} else if (yyjson_equals_str(mode_value, "fast")) {
200+
*out_mode = CBM_MODE_FAST;
201+
} else {
202+
status = INDEX_MODE_METADATA_MISSING_OR_INVALID;
203+
}
204+
205+
yyjson_doc_free(doc);
206+
return status;
207+
}
208+
209+
static bool index_mode_covers(cbm_index_mode_t stored_mode, cbm_index_mode_t requested_mode) {
210+
return stored_mode >= CBM_MODE_FULL && stored_mode <= CBM_MODE_FAST &&
211+
requested_mode >= CBM_MODE_FULL && requested_mode <= CBM_MODE_FAST &&
212+
stored_mode <= requested_mode;
213+
}
214+
215+
static index_mode_metadata_status_t read_stored_index_mode(cbm_store_t *store, const char *project,
216+
cbm_index_mode_t *out_mode) {
217+
char *properties_json = NULL;
218+
size_t properties_len = 0;
219+
int rc = cbm_store_find_node_properties_by_qn(store, project, project, &properties_json,
220+
&properties_len);
221+
if (rc == CBM_STORE_NOT_FOUND) {
222+
return INDEX_MODE_METADATA_MISSING_OR_INVALID;
223+
}
224+
if (rc != CBM_STORE_OK) {
225+
return INDEX_MODE_METADATA_ERROR;
226+
}
227+
228+
index_mode_metadata_status_t status =
229+
parse_index_mode(properties_json, properties_len, out_mode);
230+
free(properties_json);
231+
return status;
232+
}
233+
156234
/* Log current + peak RSS at a pipeline phase boundary (memory profiling). */
157235
static void log_phase_mem(const char *phase) {
158236
enum { PL_BYTES_PER_MB = 1024 * 1024 };
@@ -448,7 +526,11 @@ static int pass_structure(cbm_pipeline_t *p, const cbm_file_info_t *files, int f
448526
cbm_log_info("pass.start", "pass", "structure", "files", itoa_buf(file_count));
449527

450528
/* Project node */
451-
cbm_gbuf_upsert_node(p->gbuf, "Project", p->project_name, p->project_name, NULL, 0, 0, "{}");
529+
char project_props[CBM_SZ_64];
530+
snprintf(project_props, sizeof(project_props), "{\"index_mode\":\"%s\"}",
531+
cbm_pipeline_mode_name(p->mode));
532+
cbm_gbuf_upsert_node(p->gbuf, "Project", p->project_name, p->project_name, NULL, 0, 0,
533+
project_props);
452534
const char *branch_qn = p->branch_qn ? p->branch_qn : p->project_name;
453535
const char *branch_name = p->git_ctx.branch ? p->git_ctx.branch : "working-tree";
454536
char branch_props[CBM_SZ_2K];
@@ -1116,17 +1198,37 @@ static int try_incremental_or_delete_db(cbm_pipeline_t *p, cbm_file_info_t *file
11161198
if (check_store && cbm_store_check_integrity(check_store)) {
11171199
cbm_file_hash_t *hashes = NULL;
11181200
int hash_count = 0;
1201+
cbm_index_mode_t stored_mode = CBM_MODE_FAST;
1202+
index_mode_metadata_status_t stored_mode_status =
1203+
read_stored_index_mode(check_store, p->project_name, &stored_mode);
1204+
if (stored_mode_status == INDEX_MODE_METADATA_ERROR) {
1205+
cbm_log_error("pipeline.route", "path", "metadata_read_error", "stored_mode", "error",
1206+
"requested_mode", cbm_pipeline_mode_name(p->mode), "action",
1207+
"preserve_db");
1208+
cbm_store_close(check_store);
1209+
free(db_path);
1210+
return CBM_PIPELINE_ABORT_PRESERVE_DB;
1211+
}
1212+
bool stored_mode_known = stored_mode_status == INDEX_MODE_METADATA_OK;
11191213
cbm_store_get_file_hashes(check_store, p->project_name, &hashes, &hash_count);
11201214
cbm_store_free_file_hashes(hashes, hash_count);
11211215
cbm_store_close(check_store);
1122-
if (hash_count > 0 && file_count <= hash_count + (hash_count / PAIR_LEN)) {
1216+
bool mode_covered = stored_mode_known && index_mode_covers(stored_mode, p->mode);
1217+
if (hash_count > 0 && mode_covered && file_count <= hash_count + (hash_count / PAIR_LEN)) {
11231218
cbm_log_info("pipeline.route", "path", "incremental", "stored_hashes",
1124-
itoa_buf(hash_count));
1125-
int rc = cbm_pipeline_run_incremental(p, db_path, files, file_count);
1219+
itoa_buf(hash_count), "stored_mode", cbm_pipeline_mode_name(stored_mode),
1220+
"requested_mode", cbm_pipeline_mode_name(p->mode), "effective_mode",
1221+
cbm_pipeline_mode_name(stored_mode));
1222+
int rc = cbm_pipeline_run_incremental(p, db_path, files, file_count, stored_mode,
1223+
p->persistence);
11261224
free(db_path);
11271225
return rc;
11281226
}
1129-
if (hash_count > 0) {
1227+
if (hash_count > 0 && !mode_covered) {
1228+
cbm_log_info("pipeline.route", "path", "mode_upgrade_reindex", "stored_mode",
1229+
stored_mode_known ? cbm_pipeline_mode_name(stored_mode) : "unknown",
1230+
"requested_mode", cbm_pipeline_mode_name(p->mode));
1231+
} else if (hash_count > 0) {
11301232
cbm_log_info("pipeline.route", "path", "mode_change_reindex", "stored_hashes",
11311233
itoa_buf(hash_count), "discovered", itoa_buf(file_count));
11321234
}
@@ -1173,19 +1275,6 @@ static int64_t stat_mtime_ns(const struct stat *fst) {
11731275
#endif
11741276
}
11751277

1176-
static const char *pipeline_mode_name(cbm_index_mode_t mode) {
1177-
switch (mode) {
1178-
case CBM_MODE_FULL:
1179-
return "full";
1180-
case CBM_MODE_MODERATE:
1181-
return "moderate";
1182-
case CBM_MODE_FAST:
1183-
return "fast";
1184-
default:
1185-
return "unknown";
1186-
}
1187-
}
1188-
11891278
/* Dump graph to SQLite and persist file hashes for incremental indexing. */
11901279
static int dump_and_persist_hashes(cbm_pipeline_t *p, const cbm_file_info_t *files, int file_count,
11911280
struct timespec *t) {
@@ -1334,7 +1423,7 @@ static int dump_and_persist_hashes(cbm_pipeline_t *p, const cbm_file_info_t *fil
13341423
: (p->ignored_total > p->ignored_count ? "truncated" : "complete");
13351424
cbm_coverage_meta_t coverage_meta = {
13361425
.generation = have_project_info ? project_info.indexed_at : NULL,
1337-
.index_mode = pipeline_mode_name(p->mode),
1426+
.index_mode = cbm_pipeline_mode_name(p->mode),
13381427
.recording_status = recording_status,
13391428
.ignored_files_stored = p->ignored_count,
13401429
.ignored_files_total = p->ignored_total,
@@ -1377,16 +1466,22 @@ static int dump_and_persist_hashes(cbm_pipeline_t *p, const cbm_file_info_t *fil
13771466
free(p->saved_adr);
13781467
p->saved_adr = NULL;
13791468

1380-
/* Export persistent artifact if enabled */
1381-
if (p->persistence) {
1469+
/* Export when persistence is explicitly enabled, or refresh an artifact
1470+
* created by an earlier run so a full rebuild cannot leave it stale. */
1471+
bool persistence_required = p->persistence;
1472+
if (persistence_required || cbm_artifact_exists(p->repo_path)) {
13821473
CBM_PROF_START(t_art);
13831474
int arc = cbm_artifact_export(db_path, p->repo_path, p->project_name, CBM_ARTIFACT_BEST);
13841475
CBM_PROF_END("persist", "6_artifact_export", t_art);
13851476
if (arc != 0) {
13861477
const char *err = cbm_artifact_export_last_error();
13871478
cbm_log_error("pipeline.err", "phase", "artifact_export", "err", err ? err : "unknown");
1388-
/* A failed persistence export intentionally fails the run; this used to be ignored. */
1389-
return arc;
1479+
/* An explicitly requested persistence export is part of the run's
1480+
* contract. Refreshing a pre-existing artifact remains best-effort,
1481+
* matching the incremental pipeline. */
1482+
if (persistence_required) {
1483+
return arc;
1484+
}
13901485
}
13911486
}
13921487

0 commit comments

Comments
 (0)