| 1247 | /* ── Checkpoint ─────────────────────────────────────────────────── */ |
| 1248 | |
| 1249 | int cbm_store_checkpoint(cbm_store_t *s) { |
| 1250 | if (!s) { |
| 1251 | return CBM_STORE_ERR; |
| 1252 | } |
| 1253 | /* PASSIVE never blocks readers and never ftruncate()s either file. |
| 1254 | * SQLite recommends PASSIVE for shared databases — TRUNCATE shrinks |
| 1255 | * the WAL via ftruncate(fd, 0) on success, which on macOS can raise |
| 1256 | * SIGBUS in a sibling process that has the DB mmap'd through SQLite |
| 1257 | * when it next faults a page in the now-shorter region. |
| 1258 | * See https://www.sqlite.org/c3ref/c_checkpoint_full.html */ |
| 1259 | int wal_frames = 0; |
| 1260 | int checkpointed = 0; |
| 1261 | int rc = sqlite3_wal_checkpoint_v2(s->db, NULL, SQLITE_CHECKPOINT_PASSIVE, &wal_frames, |
| 1262 | &checkpointed); |
| 1263 | if (rc != SQLITE_OK) { |
| 1264 | store_set_error_sqlite(s, "checkpoint"); |
| 1265 | return CBM_STORE_ERR; |
| 1266 | } |
| 1267 | /* #1083: a large WAL that a PASSIVE checkpoint can't fully reset — because |
| 1268 | * concurrent readers hold marks — is the checkpoint-starvation signal. Warn |
| 1269 | * so an operator can see it (and the driving processes) before the -wal file |
| 1270 | * fills the disk; journal_size_limit only reclaims once a reset succeeds. |
| 1271 | * wal_frames = frames in the WAL, checkpointed = frames reclaimed this pass. */ |
| 1272 | if (wal_frames >= ST_WAL_STARVE_WARN_FRAMES && checkpointed < wal_frames) { |
| 1273 | char frames_buf[ST_BUF_16]; |
| 1274 | char ckpt_buf[ST_BUF_16]; |
| 1275 | snprintf(frames_buf, sizeof(frames_buf), "%d", wal_frames); |
| 1276 | snprintf(ckpt_buf, sizeof(ckpt_buf), "%d", checkpointed); |
| 1277 | cbm_log_warn("store.wal.starving", "wal_frames", frames_buf, "checkpointed", ckpt_buf, |
| 1278 | "hint", |
| 1279 | "concurrent readers block the WAL reset — the -wal file keeps growing"); |
| 1280 | } |
| 1281 | return exec_sql(s, "PRAGMA optimize;"); |
| 1282 | } |
| 1283 | |
| 1284 | static int prepare_sqlite_for_publish(sqlite3 *db) { |
| 1285 | if (!db) { |
no test coverage detected