-
-
Notifications
You must be signed in to change notification settings - Fork 80.3k
[Bug]: SQLite WAL TRUNCATE checkpoint runs synchronously on main event loop every 30 min — blocks all gateways using task registry or plugin-state stores #81715
Copy link
Copy link
Closed
Labels
P1High-priority user-facing bug, regression, or broken workflow.High-priority user-facing bug, regression, or broken workflow.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.ClawSweeper found a clear likely implementation shape for this issue.clawsweeper:linked-pr-openClawSweeper found an open linked pull request for this issue.ClawSweeper found an open linked pull request for this issue.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.ClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.ClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.ClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.ClawSweeper found a high-confidence source-level issue reproduction.impact:crash-loopCrash, hang, restart loop, or process-level availability failure.Crash, hang, restart loop, or process-level availability failure.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.Very strong issue quality with high-confidence source-level or clear reproduction.
Description
Metadata
Metadata
Assignees
Labels
P1High-priority user-facing bug, regression, or broken workflow.High-priority user-facing bug, regression, or broken workflow.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.ClawSweeper found a clear likely implementation shape for this issue.clawsweeper:linked-pr-openClawSweeper found an open linked pull request for this issue.ClawSweeper found an open linked pull request for this issue.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.ClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.ClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.ClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.ClawSweeper found a high-confidence source-level issue reproduction.impact:crash-loopCrash, hang, restart loop, or process-level availability failure.Crash, hang, restart loop, or process-level availability failure.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.Very strong issue quality with high-confidence source-level or clear reproduction.
Type
Fields
No fields configured for issues without a type.
Bug type
Behavior bug (recurring event-loop block on main thread; no crash)
Beta release blocker
No
Summary
configureSqliteWalMaintenanceinsrc/infra/sqlite-wal.tsschedulesPRAGMA wal_checkpoint(TRUNCATE)— a synchronous, blocking disk I/O operation — on the Node.js main event loop every 30 minutes viasetInterval. Becausenode:sqlite'sDatabaseSync.exec()runs synchronously on the calling thread, this operation blocks the event loop for the full duration of the WAL checkpoint. The stall fires regardless of user traffic (active=0, waiting=0, queued=0) and affects every deployment using any SQLite-backed store: task registry, plugin state, memory host SDK, and proxy capture — not only deployments with the Hindsight memory plugin.Steps to reproduce
eventLoopDelayMaxMsspikes to 8,000–12,000 ms at a 30-minute cadence.The stall fires even when the gateway is fully idle because
setIntervalfires the checkpoint callback on the main event loop thread regardless of session activity.Expected behavior
WAL maintenance should not block the event loop. A periodic WAL checkpoint should incur ≤ a few milliseconds of overhead on the main thread regardless of WAL size.
Actual behavior
PRAGMA wal_checkpoint(TRUNCATE)on anode:sqliteDatabaseSyncconnection blocks the main thread for the entire duration of the WAL I/O — up to 8–12 seconds on slow disks or after a high-write period when the WAL has grown large. All WebSocket events, HTTP responses, and gateway timers are delayed for the duration.Code location
src/infra/sqlite-wal.ts:4:DEFAULT_SQLITE_WAL_TRUNCATE_INTERVAL_MS = 30 * 60 * 1000— 30-minute periodic intervalsrc/infra/sqlite-wal.ts:50:db.exec(\PRAGMA wal_checkpoint(${checkpointMode});`)— synchronous call onDatabaseSync`; blocks the calling threadsrc/infra/sqlite-wal.ts:60:timer = setInterval(checkpoint, checkpointIntervalMs)— fires the synchronous checkpoint on the main event loop threadsrc/infra/sqlite-wal.ts:61:timer.unref?.()— prevents the timer from keeping the process alive at shutdown, but does not make the callback non-blocking when it firesAffected production call sites (7 total):
src/tasks/task-registry.store.sqlite.tssrc/tasks/task-flow-registry.store.sqlite.tssrc/plugin-state/plugin-state-store.sqlite.tssrc/proxy-capture/store.sqlite.tspackages/memory-host-sdk/src/host/sqlite.tspackages/memory-host-sdk/src/host/openclaw-runtime.tspackages/memory-host-sdk/src/host/openclaw-runtime-io.tsAll gateways with a task registry (the default) are affected, not only Hindsight/memory-host deployments.
Hypothesis (analysis, not observed in production)
The 30-minute event-loop stall pattern reported in #80820 (
active=0, waiting=0, queued=0at 30-minute cadence) matches this timer precisely.TRUNCATEmode blocks until all WAL frames are checkpointed into the main database file and the WAL is truncated to zero bytes — a full read-then-write I/O pass over the WAL. On deployments where the task registry or plugin-state store accumulates writes between checkpoints (e.g., during heavy cron or heartbeat periods), the WAL can grow large enough to cause multi-second stalls.The
timer.unref?.()call is a common pattern to prevent process hang-at-exit, but it is unrelated to event-loop blocking: when an unref'd timer fires, its callback still runs synchronously on the main thread.Proposed fix options
Option A — Change default checkpoint mode from
TRUNCATEtoPASSIVE(minimal change, non-blocking):PASSIVEmode writes as many WAL frames as possible without waiting for active readers; it never blocks the event loop. The trade-off is thatPASSIVEdoes not guarantee WAL truncation, but the existing autocheckpoint threshold (1,000 pages,PRAGMA wal_autocheckpoint = 1000) already limits WAL growth. Change insrc/infra/sqlite-wal.ts:43:Option B — Hybrid: PASSIVE timer + TRUNCATE on close (recommended):
Use
PASSIVEfor the 30-minute periodic timer (non-blocking, controls WAL size) and keepTRUNCATEonly in theclose()path where blocking is acceptable (gateway shutdown). This avoids WAL accumulation while eliminating the main-thread stall.Option C — Move periodic checkpoint to a
node:worker_threadsworker (complete isolation):Open a separate
DatabaseSyncconnection in a worker thread dedicated to WAL maintenance. The worker can block freely without affecting the main event loop. RequiresMessageChannelfor coordination and a second SQLite connection. Most correct, most complex.OpenClaw version
Confirmed on current
mainbranch (commit81b239dc984f, inspectedsrc/infra/sqlite-wal.tsdirectly).Operating system
N/A — analysis is code-level; reproducible on any OS where SQLite I/O takes >1ms.
Install method
N/A
Model
N/A
Provider / routing chain
N/A
Additional information
Related symptom report: #80820 ("Gateway event loop stalls 8–12s every ~30 minutes at idle") — that report attributed the stall to the Hindsight memory plugin, but the root cause is in the core
configureSqliteWalMaintenancecall used by all SQLite-backed stores, including the task registry that is active in every default gateway deployment.