From 3c91c8e5109d32db1c67884935cec1cda68fe226 Mon Sep 17 00:00:00 2001 From: Diogo Vernier <10981831+diogovernier@users.noreply.github.com> Date: Fri, 27 Mar 2026 10:46:38 -0300 Subject: [PATCH 1/2] Generalize silence_polling to silence_queries Replace `silence_polling` with a unified `silence_queries` config that silences all of Solid Queue's internal SQL logging: polling, heartbeats, concurrency maintenance, process pruning, and scheduler dynamic task reloading. This implements the approach approved by @rosa in #198 and addresses the long standing request in #210: a single toggle instead of accumulating per feature silencing flags (#389). `silence_polling` remains as a backward compatible alias. --- README.md | 4 +- lib/solid_queue.rb | 17 ++++- lib/solid_queue/app_executor.rb | 8 +++ .../dispatcher/concurrency_maintenance.rb | 8 ++- lib/solid_queue/processes/poller.rb | 6 +- lib/solid_queue/processes/registrable.rb | 4 +- .../scheduler/recurring_schedule.rb | 8 ++- lib/solid_queue/supervisor/maintenance.rb | 2 +- test/unit/dispatcher_test.rb | 14 ++--- test/unit/worker_test.rb | 62 ++++++++++++++++--- 10 files changed, 102 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index f77c4d5a1..8ce987bcd 100644 --- a/README.md +++ b/README.md @@ -385,7 +385,7 @@ All the options available to Active Record for multiple databases can be used he ### Other configuration settings -_Note_: The settings in this section should be set in your `config/application.rb` or your environment config like this: `config.solid_queue.silence_polling = true` +_Note_: The settings in this section should be set in your `config/application.rb` or your environment config like this: `config.solid_queue.silence_queries = true` There are several settings that control how Solid Queue works that you can set as well: - `logger`: the logger you want Solid Queue to use. Defaults to the app logger. @@ -402,7 +402,7 @@ There are several settings that control how Solid Queue works that you can set a - `process_heartbeat_interval`: the heartbeat interval that all processes will follow—defaults to 60 seconds. - `process_alive_threshold`: how long to wait until a process is considered dead after its last heartbeat—defaults to 5 minutes. - `shutdown_timeout`: time the supervisor will wait since it sent the `TERM` signal to its supervised processes before sending a `QUIT` version to them requesting immediate termination—defaults to 5 seconds. -- `silence_polling`: whether to silence Active Record logs emitted when polling for both workers and dispatchers—defaults to `true`. +- `silence_queries`: whether to silence Active Record logs emitted by Solid Queue's internal queries—including polling, heartbeats, concurrency maintenance, and process pruning—defaults to `true`. For backward compatibility, `silence_polling` is still supported as an alias. - `supervisor_pidfile`: path to a pidfile that the supervisor will create when booting to prevent running more than one supervisor in the same host, or in case you want to use it for a health check. It's `nil` by default. - `preserve_finished_jobs`: whether to keep finished jobs in the `solid_queue_jobs` table—defaults to `true`. - `clear_finished_jobs_after`: period to keep finished jobs around, in case `preserve_finished_jobs` is true — defaults to 1 day. When installing Solid Queue, [a recurring job](#recurring-tasks) is automatically configured to clear finished jobs every hour on the 12th minute in batches. You can edit the `recurring.yml` configuration to change this as you see fit. diff --git a/lib/solid_queue.rb b/lib/solid_queue.rb index bd2269e5f..ccc338ea9 100644 --- a/lib/solid_queue.rb +++ b/lib/solid_queue.rb @@ -32,7 +32,7 @@ module SolidQueue mattr_accessor :shutdown_timeout, default: 5.seconds - mattr_accessor :silence_polling, default: true + mattr_accessor :silence_queries, default: true mattr_accessor :supervisor_pidfile mattr_accessor :supervisor, default: false @@ -69,8 +69,21 @@ def supervisor? supervisor end + def silence_queries? + silence_queries + end + + # Backward compatibility: silence_polling is now an alias for silence_queries + def silence_polling=(value) + self.silence_queries = value + end + + def silence_polling + silence_queries + end + def silence_polling? - silence_polling + silence_queries? end def preserve_finished_jobs? diff --git a/lib/solid_queue/app_executor.rb b/lib/solid_queue/app_executor.rb index f315f61a2..0746a6e31 100644 --- a/lib/solid_queue/app_executor.rb +++ b/lib/solid_queue/app_executor.rb @@ -10,6 +10,14 @@ def wrap_in_app_executor(&block) end end + def with_silenced_queries + if SolidQueue.silence_queries? && ActiveRecord::Base.logger + ActiveRecord::Base.logger.silence { yield } + else + yield + end + end + def handle_thread_error(error) SolidQueue.instrument(:thread_error, error: error) diff --git a/lib/solid_queue/dispatcher/concurrency_maintenance.rb b/lib/solid_queue/dispatcher/concurrency_maintenance.rb index 81cf770cc..ffd6078e3 100644 --- a/lib/solid_queue/dispatcher/concurrency_maintenance.rb +++ b/lib/solid_queue/dispatcher/concurrency_maintenance.rb @@ -31,13 +31,17 @@ def stop private def expire_semaphores wrap_in_app_executor do - Semaphore.expired.in_batches(of: batch_size, &:delete_all) + with_silenced_queries do + Semaphore.expired.in_batches(of: batch_size, &:delete_all) + end end end def unblock_blocked_executions wrap_in_app_executor do - BlockedExecution.unblock(batch_size) + with_silenced_queries do + BlockedExecution.unblock(batch_size) + end end end end diff --git a/lib/solid_queue/processes/poller.rb b/lib/solid_queue/processes/poller.rb index 75df61045..58ee200f5 100644 --- a/lib/solid_queue/processes/poller.rb +++ b/lib/solid_queue/processes/poller.rb @@ -43,11 +43,7 @@ def poll def with_polling_volume SolidQueue.instrument(:polling) do - if SolidQueue.silence_polling? && ActiveRecord::Base.logger - ActiveRecord::Base.logger.silence { yield } - else - yield - end + with_silenced_queries { yield } end end end diff --git a/lib/solid_queue/processes/registrable.rb b/lib/solid_queue/processes/registrable.rb index 35b4e01bd..e40413b00 100644 --- a/lib/solid_queue/processes/registrable.rb +++ b/lib/solid_queue/processes/registrable.rb @@ -39,7 +39,7 @@ def registered? def launch_heartbeat @heartbeat_task = Concurrent::TimerTask.new(execution_interval: SolidQueue.process_heartbeat_interval) do - wrap_in_app_executor { heartbeat } + wrap_in_app_executor { with_silenced_queries { heartbeat } } end @heartbeat_task.add_observer do |_, _, error| @@ -61,7 +61,7 @@ def heartbeat end def reload_metadata - wrap_in_app_executor { process&.update(metadata: metadata.compact) } + wrap_in_app_executor { with_silenced_queries { process&.update(metadata: metadata.compact) } } end end end diff --git a/lib/solid_queue/scheduler/recurring_schedule.rb b/lib/solid_queue/scheduler/recurring_schedule.rb index a1e2409e0..f538e3e81 100644 --- a/lib/solid_queue/scheduler/recurring_schedule.rb +++ b/lib/solid_queue/scheduler/recurring_schedule.rb @@ -48,9 +48,11 @@ def task_keys def reschedule_dynamic_tasks wrap_in_app_executor do - reload_dynamic_tasks - schedule_created_dynamic_tasks - unschedule_deleted_dynamic_tasks + with_silenced_queries do + reload_dynamic_tasks + schedule_created_dynamic_tasks + unschedule_deleted_dynamic_tasks + end end end diff --git a/lib/solid_queue/supervisor/maintenance.rb b/lib/solid_queue/supervisor/maintenance.rb index d92569d58..2c29e6b56 100644 --- a/lib/solid_queue/supervisor/maintenance.rb +++ b/lib/solid_queue/supervisor/maintenance.rb @@ -24,7 +24,7 @@ def stop_maintenance_task end def prune_dead_processes - wrap_in_app_executor { SolidQueue::Process.prune(excluding: process) } + wrap_in_app_executor { with_silenced_queries { SolidQueue::Process.prune(excluding: process) } } end def fail_orphaned_executions diff --git a/test/unit/dispatcher_test.rb b/test/unit/dispatcher_test.rb index 7df0591f5..a5f0f49a9 100644 --- a/test/unit/dispatcher_test.rb +++ b/test/unit/dispatcher_test.rb @@ -39,7 +39,7 @@ class DispatcherTest < ActiveSupport::TestCase test "polling queries are logged" do log = StringIO.new with_active_record_logger(ActiveSupport::Logger.new(log)) do - with_polling(silence: false) do + with_silence_queries(false) do rewind_io(log) @dispatcher.start sleep 0.5.second @@ -52,7 +52,7 @@ class DispatcherTest < ActiveSupport::TestCase test "polling queries can be silenced" do log = StringIO.new with_active_record_logger(ActiveSupport::Logger.new(log)) do - with_polling(silence: true) do + with_silence_queries(true) do rewind_io(log) @dispatcher.start sleep 0.5.second @@ -62,9 +62,9 @@ class DispatcherTest < ActiveSupport::TestCase assert_no_match /SELECT .* FROM .solid_queue_scheduled_executions. WHERE/, log.string end - test "silencing polling queries when there's no Active Record logger" do + test "silencing queries when there's no Active Record logger" do with_active_record_logger(nil) do - with_polling(silence: true) do + with_silence_queries(true) do @dispatcher.start sleep 0.5.second end @@ -131,11 +131,11 @@ class DispatcherTest < ActiveSupport::TestCase end private - def with_polling(silence:) - old_silence_polling, SolidQueue.silence_polling = SolidQueue.silence_polling, silence + def with_silence_queries(silence) + old_silence_queries, SolidQueue.silence_queries = SolidQueue.silence_queries, silence yield ensure - SolidQueue.silence_polling = old_silence_polling + SolidQueue.silence_queries = old_silence_queries end def with_active_record_logger(logger) diff --git a/test/unit/worker_test.rb b/test/unit/worker_test.rb index 3d692404b..3e994fd37 100644 --- a/test/unit/worker_test.rb +++ b/test/unit/worker_test.rb @@ -106,7 +106,7 @@ class WorkerTest < ActiveSupport::TestCase test "polling queries are logged" do log = StringIO.new with_active_record_logger(ActiveSupport::Logger.new(log)) do - with_polling(silence: false) do + with_silence_queries(false) do @worker.start sleep 0.2 end @@ -118,7 +118,7 @@ class WorkerTest < ActiveSupport::TestCase test "polling queries can be silenced" do log = StringIO.new with_active_record_logger(ActiveSupport::Logger.new(log)) do - with_polling(silence: true) do + with_silence_queries(true) do @worker.start sleep 0.2 end @@ -127,9 +127,9 @@ class WorkerTest < ActiveSupport::TestCase assert_no_match /SELECT .* FROM .solid_queue_ready_executions. WHERE .solid_queue_ready_executions...queue_name./, log.string end - test "silencing polling queries when there's no Active Record logger" do + test "silencing queries when there's no Active Record logger" do with_active_record_logger(nil) do - with_polling(silence: true) do + with_silence_queries(true) do @worker.start sleep 0.2 end @@ -140,6 +140,54 @@ class WorkerTest < ActiveSupport::TestCase assert_no_registered_processes end + test "heartbeat queries can be silenced" do + old_heartbeat_interval, SolidQueue.process_heartbeat_interval = SolidQueue.process_heartbeat_interval, 0.2.second + + log = StringIO.new + with_active_record_logger(ActiveSupport::Logger.new(log)) do + with_silence_queries(true) do + @worker.start + wait_for_registered_processes(1, timeout: 1.second) + sleep 0.5 + end + end + + assert_no_match /UPDATE .solid_queue_processes. SET .last_heartbeat_at./, log.string + ensure + SolidQueue.process_heartbeat_interval = old_heartbeat_interval + end + + test "heartbeat queries are logged when silence_queries is false" do + old_heartbeat_interval, SolidQueue.process_heartbeat_interval = SolidQueue.process_heartbeat_interval, 0.2.second + + log = StringIO.new + with_active_record_logger(ActiveSupport::Logger.new(log)) do + with_silence_queries(false) do + @worker.start + wait_for_registered_processes(1, timeout: 1.second) + sleep 0.5 + end + end + + assert_match /UPDATE .solid_queue_processes. SET .last_heartbeat_at./, log.string + ensure + SolidQueue.process_heartbeat_interval = old_heartbeat_interval + end + + test "silence_polling is backward compatible with silence_queries" do + old_value = SolidQueue.silence_queries + + SolidQueue.silence_polling = false + assert_not SolidQueue.silence_queries? + assert_not SolidQueue.silence_polling? + + SolidQueue.silence_polling = true + assert SolidQueue.silence_queries? + assert SolidQueue.silence_polling? + ensure + SolidQueue.silence_queries = old_value + end + test "run inline" do worker = SolidQueue::Worker.new(queues: "*", threads: 3, polling_interval: 0.2) worker.mode = :inline @@ -195,11 +243,11 @@ class WorkerTest < ActiveSupport::TestCase end private - def with_polling(silence:) - old_silence_polling, SolidQueue.silence_polling = SolidQueue.silence_polling, silence + def with_silence_queries(silence) + old_silence_queries, SolidQueue.silence_queries = SolidQueue.silence_queries, silence yield ensure - SolidQueue.silence_polling = old_silence_polling + SolidQueue.silence_queries = old_silence_queries end def with_active_record_logger(logger) From 43d615e57a5f1c1afa1f32ee83553ff3f19f6f6e Mon Sep 17 00:00:00 2001 From: Diogo Vernier <10981831+diogovernier@users.noreply.github.com> Date: Thu, 9 Apr 2026 09:45:16 -0300 Subject: [PATCH 2/2] Fix heartbeat test regex to match MySQL column quoting MySQL fully qualifies column names in UPDATE statements as `table`.`column`, so the regex needs .+ instead of . between SET and last_heartbeat_at. --- test/unit/worker_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/worker_test.rb b/test/unit/worker_test.rb index 3e994fd37..7c6debbbb 100644 --- a/test/unit/worker_test.rb +++ b/test/unit/worker_test.rb @@ -152,7 +152,7 @@ class WorkerTest < ActiveSupport::TestCase end end - assert_no_match /UPDATE .solid_queue_processes. SET .last_heartbeat_at./, log.string + assert_no_match /UPDATE .solid_queue_processes. SET .+last_heartbeat_at/, log.string ensure SolidQueue.process_heartbeat_interval = old_heartbeat_interval end @@ -169,7 +169,7 @@ class WorkerTest < ActiveSupport::TestCase end end - assert_match /UPDATE .solid_queue_processes. SET .last_heartbeat_at./, log.string + assert_match /UPDATE .solid_queue_processes. SET .+last_heartbeat_at/, log.string ensure SolidQueue.process_heartbeat_interval = old_heartbeat_interval end