runtime: harden autocompile polling and result capture

This commit is contained in:
Matthew Stanley
2026-07-20 13:42:06 -07:00
parent a6ba6b3cfd
commit c37fc2d60c
2 changed files with 57 additions and 5 deletions
+37 -4
View File
@@ -101,7 +101,31 @@ static char s_child_line[1024];
static int s_child_line_len = 0;
static int s_child_line_overflow = 0;
static void publish_line_locked(void) {
/* Parse result markers while stdout is streaming, not from s_out after exit.
* The configured child command may chain post-processing after
* compile_overlays.py (coverage_vault.py is the production example). That
* output can exceed AC_OUT_CAP and evict the shard result from the diagnostic
* tail even though the marker was complete and valid when it arrived. Keep
* the last valid result here; poll_main accounts it exactly once after both
* child-output workers have joined. */
static int shard_result_line_locked(const char *line) {
unsigned ok = 0, failed = 0, skipped = 0;
int consumed = 0;
if (sscanf(line, "PSX_SHARD_RESULT ok=%u failed=%u skipped=%u %n",
&ok, &failed, &skipped, &consumed) != 3)
return 0;
for (const char *tail = line + consumed; *tail; tail++) {
if (*tail != ' ' && *tail != '\t' && *tail != '\r') return 0;
}
s_shard_ok = ok;
s_shard_fail = failed;
s_shard_skipped = skipped;
s_shard_result_seen = 1;
return 1;
}
static void child_line_locked(void) {
if (shard_result_line_locked(s_child_line)) return;
static const char marker[] = "PSX_SHARD_PUBLISHED ";
if (strncmp(s_child_line, marker, sizeof(marker) - 1) != 0) return;
const char *path = s_child_line + sizeof(marker) - 1;
@@ -131,7 +155,7 @@ static void publish_parse_locked(const char *buf, int n) {
if (s_child_line_overflow)
s_publish_parse_fail_run++;
else
publish_line_locked();
child_line_locked();
s_child_line_len = 0;
s_child_line_overflow = 0;
} else if (s_child_line_len < (int)sizeof(s_child_line) - 1) {
@@ -691,7 +715,6 @@ static int parse_shard_result(void) {
s_shard_ok = ok;
s_shard_fail = failed;
s_shard_skipped = skipped;
if (failed) s_shard_fail_total += failed;
return 1;
#else
return 0;
@@ -703,6 +726,10 @@ void autocompile_poll_main(void) {
* mapped-but-uninitialized images; callback wiring, validation, and
* candidate registration remain single-threaded here. */
#ifdef _WIN32
/* Titles without overlay autocompile never call autocompile_configure(),
* so their publication lock does not exist. Poll is still called by the
* shared maintenance path; keep that unconfigured path a strict no-op. */
if (!s_out_lock_init) return;
PublishItem *published = publish_ready_pop();
if (published) {
if (overlay_loader_commit_published(published->image) <= 0)
@@ -737,8 +764,14 @@ void autocompile_poll_main(void) {
s_job = NULL;
}
#endif
/* Streaming parsing survives arbitrarily large chained post-processing
* output. Retain the tail scan as a compatibility fallback for output
* injected by older/test paths that bypass the line parser. */
if (!s_shard_result_seen)
s_shard_result_seen = parse_shard_result();
if (s_shard_result_seen && s_shard_fail)
s_shard_fail_total += s_shard_fail;
ac_state_store(AC_IDLE);
s_shard_result_seen = parse_shard_result();
/* Direct markers minimize handoff latency, then one idempotent batch-end
* rescan makes every successfully published artifact visible to additive
* cache queries and the lazy manifest index. This is intentionally once per
+20 -1
View File
@@ -70,12 +70,16 @@ static DWORD WINAPI feed_thread(void *opaque) {
int main(void) {
enum { ITEM_COUNT = 300 };
s_main_thread = GetCurrentThreadId();
/* The shared runtime polls every title. A title with overlay autocompile
* disabled must not touch the publication lock before configuration. */
autocompile_poll_main();
autocompile_configure("unused", ".");
size_t capacity = (size_t)ITEM_COUNT * 96u;
char *text = (char *)malloc(capacity);
if (!text) return 1;
int length = 0;
int length = snprintf(text, capacity,
"PSX_SHARD_RESULT ok=17 failed=0 skipped=4\n");
for (unsigned i = 0; i < ITEM_COUNT; ++i) {
length += snprintf(text + length, capacity - (size_t)length,
"PSX_SHARD_PUBLISHED C:\\cache\\00010000_DEADBEEF_%08X.dll\n", i);
@@ -99,8 +103,23 @@ int main(void) {
fprintf(stderr, "FAIL: preparer did not exit\n");
return 1;
}
/* Finalize the synthetic run. The 300 publication lines are well over
* AC_OUT_CAP, so the result marker at the beginning is no longer present
* in output_tail. Its counters must nevertheless survive streaming. */
autocompile_poll_main();
free(text);
char status[2048];
autocompile_status_json(status, sizeof(status));
if (!strstr(status, "\"shard_ok\":17") ||
!strstr(status, "\"shard_fail\":0") ||
!strstr(status, "\"shard_skipped\":4") ||
!strstr(status, "\"shard_result_seen\":1")) {
fprintf(stderr, "FAIL: streamed shard result was lost after tail eviction: %s\n",
status);
return 1;
}
if (s_prepared != ITEM_COUNT || s_committed != ITEM_COUNT ||
autocompile_test_ready_count() != 0 ||
autocompile_test_preparing_count() != 0 ||