Load WAL checkpointer outside Zeitwerk reload; harden fork stop

Keep the module under lib/rails_ext, use before_worker_boot, shorten the
SQLite busy timeout under stop's join, and PASSIVE-checkpoint on exit for
short-lived writers.

Co-authored-by: Thomas Klemm <github@tklemm.eu>
This commit is contained in:
Cursor Agent
2026-10-07 19:05:44 +00:00
parent 61631ea5f9
commit 8704baa860
4 changed files with 38 additions and 4 deletions
@@ -21,6 +21,7 @@ module SqliteWalCheckpoint
return if @thread&.alive?
@stop = false
install_exit_checkpoint
@thread = Thread.new { run(interval) }
@thread.report_on_exception = false
end
@@ -33,6 +34,7 @@ module SqliteWalCheckpoint
def stop
@stop = true
thread = @mutex&.synchronize { @thread }
# Join longer than busy_handler_timeout so before_fork does not race a PRAGMA.
thread&.join(2)
@mutex&.synchronize { @thread = nil if @thread && !@thread.alive? }
end
@@ -63,6 +65,7 @@ module SqliteWalCheckpoint
@lock_path = nil
@database_path_override = nil
@stop = false
@exit_checkpoint_installed = false
end
private
@@ -113,7 +116,8 @@ module SqliteWalCheckpoint
result = nil
SQLite3::Database.new(path) do |database|
database.busy_handler_timeout = 5_000
# Keep below stop's join timeout so before_fork can finish cleanly.
database.busy_handler_timeout = 1_000
result = yield database
end
result
@@ -148,5 +152,14 @@ module SqliteWalCheckpoint
ensure
@lock_file = nil
end
# Best-effort PASSIVE for short-lived console/rake writers that exit before
# the contender acquires the flock. Does not touch lock ownership.
def install_exit_checkpoint
return if @exit_checkpoint_installed
@exit_checkpoint_installed = true
at_exit { checkpoint rescue nil }
end
end
end