Class: Philiprehberger::CronKit::Scheduler
- Inherits:
-
Object
- Object
- Philiprehberger::CronKit::Scheduler
- Includes:
- TimeoutHandler
- Defined in:
- lib/philiprehberger/cron_kit/scheduler.rb
Overview
A simple in-process cron scheduler that checks registered jobs every 60 seconds.
Defined Under Namespace
Classes: Job
Instance Method Summary collapse
-
#every(expression, name: nil, timeout: nil, overlap: true) {|time| ... } ⇒ self
Register a cron job to be executed when
expressionmatches. -
#initialize ⇒ Scheduler
constructor
A new instance of Scheduler.
-
#job?(name) ⇒ Boolean
Whether a job is registered under
name. -
#job_names ⇒ Array<Symbol, String>
Names of all registered named jobs.
-
#next_runs(from: Time.now) ⇒ Hash{Symbol, String => Time}
Upcoming run times for every named job.
-
#on_error {|job, error| ... } ⇒ Proc?
Register a callback invoked when a job raises an error.
-
#remove(name) ⇒ Boolean
Remove a registered job by name.
-
#run_now(name) ⇒ Object?
Manually trigger a registered job by name.
-
#running? ⇒ Boolean
Whether the scheduler loop is currently running.
-
#running_jobs ⇒ Integer
Number of currently executing job threads.
-
#start ⇒ self
Start the scheduler loop in a background thread.
-
#stop ⇒ self
Stop the scheduler loop and wait briefly for the background thread to exit.
Constructor Details
#initialize ⇒ Scheduler
Returns a new instance of Scheduler.
11 12 13 14 15 16 17 18 19 |
# File 'lib/philiprehberger/cron_kit/scheduler.rb', line 11 def initialize @jobs = [] @mutex = Mutex.new @thread = nil @running = false @on_error = nil @running_threads = [] @job_threads = {} end |
Instance Method Details
#every(expression, name: nil, timeout: nil, overlap: true) {|time| ... } ⇒ self
Register a cron job to be executed when expression matches.
Accepts either a cron expression string or a pre-built Expression. Raises ArgumentError immediately if no block is provided, surfacing configuration mistakes at registration time rather than at tick time.
55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/philiprehberger/cron_kit/scheduler.rb', line 55 def every(expression, name: nil, timeout: nil, overlap: true, &block) raise ArgumentError, 'block required' unless block expr = expression.is_a?(Expression) ? expression : Expression.new(expression) @mutex.synchronize do @jobs << Job.new(expression: expr, block: block, name: name, timeout: timeout, overlap: overlap) end self end |
#job?(name) ⇒ Boolean
Whether a job is registered under name.
Anonymous jobs (registered without a name:) are never matched.
82 83 84 85 86 |
# File 'lib/philiprehberger/cron_kit/scheduler.rb', line 82 def job?(name) return false if name.nil? @mutex.synchronize { @jobs.any? { |j| j.name == name } } end |
#job_names ⇒ Array<Symbol, String>
Names of all registered named jobs.
Anonymous jobs (registered without name:) are omitted.
72 73 74 |
# File 'lib/philiprehberger/cron_kit/scheduler.rb', line 72 def job_names @mutex.synchronize { @jobs.map(&:name).compact } end |
#next_runs(from: Time.now) ⇒ Hash{Symbol, String => Time}
Upcoming run times for every named job.
Anonymous jobs are omitted.
124 125 126 127 128 129 130 131 132 |
# File 'lib/philiprehberger/cron_kit/scheduler.rb', line 124 def next_runs(from: Time.now) jobs = @mutex.synchronize { @jobs.dup } jobs.each_with_object({}) do |job, hash| next unless job.name hash[job.name] = job.expression.next_at(from: from) end end |
#on_error {|job, error| ... } ⇒ Proc?
Register a callback invoked when a job raises an error.
Called with the failing Job and the StandardError raised. With no block, returns the currently registered callback (or nil).
29 30 31 32 33 |
# File 'lib/philiprehberger/cron_kit/scheduler.rb', line 29 def on_error(&block) return @on_error unless block @on_error = block end |
#remove(name) ⇒ Boolean
Remove a registered job by name.
92 93 94 95 96 97 98 |
# File 'lib/philiprehberger/cron_kit/scheduler.rb', line 92 def remove(name) @mutex.synchronize do initial_size = @jobs.size @jobs.reject! { |job| job.name == name } @jobs.size < initial_size end end |
#run_now(name) ⇒ Object?
Manually trigger a registered job by name.
Reuses the standard execution path (timeout + overlap-skip), but runs synchronously so the caller can capture the return value. Useful for testing and operator-driven re-runs.
110 111 112 113 114 115 116 |
# File 'lib/philiprehberger/cron_kit/scheduler.rb', line 110 def run_now(name) job = @mutex.synchronize { @jobs.find { |j| j.name == name } } raise KeyError, "job not registered: #{name.inspect}" unless job return nil if skip_overlapping?(job) execute_now(job) end |
#running? ⇒ Boolean
Whether the scheduler loop is currently running.
166 167 168 |
# File 'lib/philiprehberger/cron_kit/scheduler.rb', line 166 def running? @mutex.synchronize { @running } end |
#running_jobs ⇒ Integer
Number of currently executing job threads.
38 39 40 |
# File 'lib/philiprehberger/cron_kit/scheduler.rb', line 38 def running_jobs @mutex.synchronize { @running_threads.count(&:alive?) } end |
#start ⇒ self
Start the scheduler loop in a background thread.
Idempotent — calling start on an already-running scheduler is a no-op.
139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/philiprehberger/cron_kit/scheduler.rb', line 139 def start @mutex.synchronize do return self if @running @running = true end @thread = Thread.new { run_loop } @thread.abort_on_exception = true self end |
#stop ⇒ self
Stop the scheduler loop and wait briefly for the background thread to exit.
155 156 157 158 159 160 161 |
# File 'lib/philiprehberger/cron_kit/scheduler.rb', line 155 def stop @mutex.synchronize { @running = false } @thread&.join(5) @thread = nil self end |