From 13e05fc54879d60d0f47fbd59af622eec5be091a Mon Sep 17 00:00:00 2001 From: Marco Bambini Date: Sun, 11 Oct 2026 17:38:58 +0200 Subject: [PATCH] mw_procs: the victim of a kill is killed after it has inserted, not after a fixed time The test killed a process 400 ms (one process) and 600 ms (all four) after it started it. On a slow runner the process had not inserted anything yet: "one process killed after 0 of 1000000 inserts", "FAIL mw_procs.c:126: killed > 0 && killed < 1000000" (linux-arm64 of main, 15:21 UTC). The data were whole: only the time did not hold. wait_acked() waits until the process has acknowledged n inserts (a byte in its .ack file for each), at most a minute, and the kill follows: 200 inserts for the one of phase 2, 100 for each of the four of phase 3. The size is read from an open handle (lseek): on Windows stat() of a file that another process holds open lags behind. Checked with the children of the test started 700 ms late: the old test fails as in CI (killed after 0 of 1000000 inserts), the new one passes (258 and 201 inserts before the kill). The checks of the end are the same. Co-Authored-By: Claude Sonnet 5.5 --- test/mw_procs.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/test/mw_procs.c b/test/mw_procs.c index f7f4419..817c7fe 100644 --- a/test/mw_procs.c +++ b/test/mw_procs.c @@ -75,6 +75,22 @@ static void pause_ms (int ms) { #endif } +// Waits until the process `id` has acknowledged `n` inserts (its file of acknowledgements has a byte for each), for at most `limit_ms`: the victim of a kill must be in the middle of its inserts, and +// how long a process takes to start and to get going is not the same on every machine (a sleep of a fixed time killed one of them before its first insert on a slow runner). The size is read from an +// open handle: on Windows the size that stat() gives for a file that another process holds open lags behind. Returns the number of acknowledgements seen. +static long long wait_acked (const char *path, int id, long long n, int limit_ms) { + char ackp[700]; snprintf(ackp, sizeof ackp, "%s.ack%d", path, id); + long long seen = 0; + for (int waited = 0; waited <= limit_ms; waited += 5) { + int fd = open(ackp, O_RDONLY | O_BINARY); + if (fd >= 0) { long sz = (long)lseek(fd, 0, SEEK_END); close(fd); if (sz > 0) seen = sz; } + if (seen >= n) return seen; + pause_ms(5); + } + fprintf(stderr, "mw_procs: process %d had %lld acknowledged inserts after %d ms (wanted %lld)\n", id, seen, limit_ms, n); + return seen; +} + static int integrity_ok (sqlite3 *db) { sqlite3_stmt *st; int ok = 0; if (sqlite3_prepare_v2(db, "PRAGMA integrity_check", -1, &st, NULL) == SQLITE_OK && sqlite3_step(st) == SQLITE_ROW) ok = strcmp((const char *)sqlite3_column_text(st, 0), "ok") == 0; @@ -114,7 +130,7 @@ int main (int argc, char **argv) { // 2. one is killed in the middle of its inserts: the others finish, nothing is torn, and a new process can write for (int i = 0; i < KIDS; i++) { p[i] = spawn(self, path, 10 + i, i == 1 ? 1000000 : ROWS * 4, rebase); CHECK(p[i] != 0 && p[i] != (proc_t)-1); } - pause_ms(400); kill_it(p[1]); wait_for(p[1]); + wait_acked(path, 11, 200, 60000); kill_it(p[1]); wait_for(p[1]); // (killed after it has inserted: not after a fixed time) bad = 0; for (int i = 0; i < KIDS; i++) if (i != 1) bad += wait_for(p[i]) != 0; CHECK(bad == 0); proc_t again = spawn(self, path, 20, 50, rebase); CHECK(wait_for(again) == 0); @@ -131,7 +147,7 @@ int main (int argc, char **argv) { // 3. all of them are killed in the middle of their inserts, and the database is opened at once: what each had acknowledged is there for (int k = 0; k < 40; k++) { char ap[700]; snprintf(ap, sizeof ap, "%s.ack%d", path, k); unlink(ap); } for (int i = 0; i < KIDS; i++) { p[i] = spawn(self, path, 30 + i, 1000000, rebase); CHECK(p[i] != 0 && p[i] != (proc_t)-1); } - pause_ms(600); + for (int i = 0; i < KIDS; i++) wait_acked(path, 30 + i, 100, 60000); // (each one is in the middle of its inserts) for (int i = 0; i < KIDS; i++) { kill_it(p[i]); wait_for(p[i]); } { const char *tf = getenv("MW_OS_TRACE"); FILE *f = tf && tf[0] != '1' ? fopen(tf, "a") : NULL; if (f) { fprintf(f, "MARK all killed, the database is opened now\n"); fclose(f); } } CHECK_RC(sqlite3_open_v2(uri, &c, SQLITE_OPEN_READWRITE | SQLITE_OPEN_URI, NULL), SQLITE_OK);