Class: RailsPodKit::SolidQueue::SchedulerRunner

Inherits:
Object
  • Object
show all
Defined in:
lib/rails_pod_kit/solid_queue/scheduler_runner.rb

Overview

Runs a SolidQueue scheduler — and only the scheduler — as a supervised background thread, so the process running jobs can scale to zero.

Scaling the executor to zero is otherwise a chicken-and-egg problem: with no executor there is no scheduler, so nothing enqueues the recurring or scheduled jobs that would wake one. A k8s CronJob can't take over either, because it can't own dynamic recurring tasks (created and updated at runtime through SolidQueue.schedule_recurring_task). Moving just the scheduler onto an always-on process — the web, or the exporter pod — breaks the cycle: it keeps evaluating the crons and enqueueing, and the queue depth wakes the executor.

Deliberately not the full supervisor (plugin :solid_queue): that one forks, and its Puma watchdog takes the host process down when the supervisor exits — which a transient Postgres disconnect is enough to cause (rails/solid_queue#512). Here a DB blip at worst kills the scheduler thread; the supervising timer notices on its next tick and starts a fresh one, and the host process never notices.

Running it on every replica is safe: enqueues stay exactly-once via the unique index on solid_queue_recurring_executions (task_key, run_at).

Constant Summary collapse

DEFAULT_POLLING_INTERVAL =

How often the scheduler re-reads the dynamic tasks from the DB.

5
DEFAULT_SUPERVISION_INTERVAL =

How often we check that the scheduler thread is still alive.

5
SOURCE =
'rails_pod_kit.solid_queue_scheduler'

Instance Method Summary collapse

Constructor Details

#initialize(polling_interval: DEFAULT_POLLING_INTERVAL, supervision_interval: DEFAULT_SUPERVISION_INTERVAL, recurring_schedule_file: nil) ⇒ SchedulerRunner

Returns a new instance of SchedulerRunner.



41
42
43
44
45
46
47
48
# File 'lib/rails_pod_kit/solid_queue/scheduler_runner.rb', line 41

def initialize(polling_interval: DEFAULT_POLLING_INTERVAL,
               supervision_interval: DEFAULT_SUPERVISION_INTERVAL,
               recurring_schedule_file: nil)
  @polling_interval = polling_interval
  @supervision_interval = supervision_interval
  @recurring_schedule_file = recurring_schedule_file
  @stopping = false
end

Instance Method Details

#running?Boolean

Returns:

  • (Boolean)


72
73
74
# File 'lib/rails_pod_kit/solid_queue/scheduler_runner.rb', line 72

def running?
  !!@scheduler&.alive?
end

#startObject

Starts the supervisor, which starts the scheduler on its first (immediate) tick and returns without blocking.



52
53
54
55
56
57
58
59
# File 'lib/rails_pod_kit/solid_queue/scheduler_runner.rb', line 52

def start
  @supervisor = ::Concurrent::TimerTask.new(
    execution_interval: @supervision_interval,
    run_now: true
  ) { supervise }
  @supervisor.execute
  self
end

#stopObject

Graceful stop: drop the supervisor first so it can't resurrect the scheduler, then wind the scheduler down (unschedule its timers and deregister the process, instead of leaving a row to expire).



64
65
66
67
68
69
70
# File 'lib/rails_pod_kit/solid_queue/scheduler_runner.rb', line 64

def stop
  @stopping = true
  @supervisor&.shutdown
  @supervisor = nil
  @scheduler&.stop
  @scheduler = nil
end