mirror of
https://github.com/plashchynski/crono.git
synced 2026-01-15 23:03:29 +01:00
Compare commits
9 Commits
v0.8.6.pre
...
v0.8.8.pre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d31aaaffbe | ||
|
|
6d41a19212 | ||
|
|
a28ec7b276 | ||
|
|
e8c7400caa | ||
|
|
260cf14e95 | ||
|
|
1900a06582 | ||
|
|
8174f86407 | ||
|
|
c8f9ff4e34 | ||
|
|
84ac08e5d4 |
@@ -1,7 +1,7 @@
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
crono (0.8.6.pre)
|
||||
crono (0.8.8.pre)
|
||||
activejob (~> 4.0)
|
||||
activerecord (~> 4.0)
|
||||
activesupport (~> 4.0)
|
||||
|
||||
@@ -10,6 +10,7 @@ require 'crono/job'
|
||||
require 'crono/scheduler'
|
||||
require 'crono/config'
|
||||
require 'crono/performer_proxy'
|
||||
require 'crono/cronotab'
|
||||
require 'crono/orm/active_record/crono_job'
|
||||
require 'crono/railtie' if defined?(Rails)
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ module Crono
|
||||
|
||||
write_pid
|
||||
load_rails
|
||||
Cronotab.process(File.expand_path(config.cronotab))
|
||||
print_banner
|
||||
|
||||
check_jobs
|
||||
@@ -71,7 +72,6 @@ module Crono
|
||||
require 'rails'
|
||||
require File.expand_path('config/environment.rb')
|
||||
::Rails.application.eager_load!
|
||||
require File.expand_path(config.cronotab)
|
||||
end
|
||||
|
||||
def check_jobs
|
||||
@@ -82,7 +82,7 @@ module Crono
|
||||
def start_working_loop
|
||||
loop do
|
||||
next_time, jobs = Crono.scheduler.next_jobs
|
||||
sleep(next_time - Time.now)
|
||||
sleep(next_time - Time.now) if next_time > Time.now
|
||||
jobs.each(&:perform)
|
||||
end
|
||||
end
|
||||
|
||||
10
lib/crono/cronotab.rb
Normal file
10
lib/crono/cronotab.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
module Crono
|
||||
class Cronotab
|
||||
def self.process(cronotab_path = nil)
|
||||
cronotab_path ||= ENV['CRONOTAB'] || (defined?(Rails) &&
|
||||
File.join(Rails.root, Config::CRONOTAB))
|
||||
fail 'No cronotab defined' unless cronotab_path
|
||||
require cronotab_path
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -6,18 +6,20 @@ module Crono
|
||||
class Job
|
||||
include Logging
|
||||
|
||||
attr_accessor :performer, :period, :last_performed_at, :job_log,
|
||||
:job_logger, :healthy
|
||||
attr_accessor :performer, :period, :last_performed_at,
|
||||
:next_performed_at, :job_log, :job_logger, :healthy
|
||||
|
||||
def initialize(performer, period)
|
||||
self.performer, self.period = performer, period
|
||||
self.job_log = StringIO.new
|
||||
self.job_logger = Logger.new(job_log)
|
||||
self.next_performed_at = period.next
|
||||
@semaphore = Mutex.new
|
||||
end
|
||||
|
||||
def next
|
||||
period.next(since: last_performed_at)
|
||||
return next_performed_at if next_performed_at.future?
|
||||
Time.now
|
||||
end
|
||||
|
||||
def description
|
||||
@@ -31,6 +33,7 @@ module Crono
|
||||
def perform
|
||||
log "Perform #{performer}"
|
||||
self.last_performed_at = Time.now
|
||||
self.next_performed_at = period.next(since: last_performed_at)
|
||||
|
||||
Thread.new { perform_job }
|
||||
end
|
||||
@@ -44,6 +47,7 @@ module Crono
|
||||
|
||||
def load
|
||||
self.last_performed_at = model.last_performed_at
|
||||
self.next_performed_at = period.next(since: last_performed_at)
|
||||
end
|
||||
|
||||
private
|
||||
@@ -61,23 +65,24 @@ module Crono
|
||||
|
||||
def perform_job
|
||||
performer.new.perform
|
||||
finished_time_sec = format('%.2f', Time.now - last_performed_at)
|
||||
rescue StandardError => e
|
||||
handle_job_fail(e, finished_time_sec)
|
||||
handle_job_fail(e)
|
||||
else
|
||||
handle_job_success(finished_time_sec)
|
||||
handle_job_success
|
||||
ensure
|
||||
save
|
||||
end
|
||||
|
||||
def handle_job_fail(exception, finished_time_sec)
|
||||
def handle_job_fail(exception)
|
||||
finished_time_sec = format('%.2f', Time.now - last_performed_at)
|
||||
self.healthy = false
|
||||
log_error "Finished #{performer} in #{finished_time_sec} seconds"\
|
||||
"with error: #{exception.message}"
|
||||
" with error: #{exception.message}"
|
||||
log_error exception.backtrace.join("\n")
|
||||
end
|
||||
|
||||
def handle_job_success(finished_time_sec)
|
||||
def handle_job_success
|
||||
finished_time_sec = format('%.2f', Time.now - last_performed_at)
|
||||
self.healthy = true
|
||||
log "Finished #{performer} in #{finished_time_sec} seconds"
|
||||
end
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module Crono
|
||||
VERSION = '0.8.6.pre'
|
||||
VERSION = '0.8.8.pre'
|
||||
end
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
module Crono
|
||||
def self.load_cronotab
|
||||
cronotab_path = ENV['CRONOTAB'] || (defined?(Rails) &&
|
||||
File.join(Rails.root, cronotab_path))
|
||||
fail 'No cronotab defined' unless cronotab_path
|
||||
puts "Load cronotab #{cronotab_path}"
|
||||
require cronotab_path
|
||||
end
|
||||
end
|
||||
|
||||
namespace :crono do
|
||||
desc 'Clean unused job stats from DB'
|
||||
task clean: :environment do
|
||||
Crono.scheduler = Crono::Scheduler.new
|
||||
Crono.load_cronotab
|
||||
Crono::Cronotab.process
|
||||
current_job_ids = Crono.scheduler.jobs.map(&:job_id)
|
||||
Crono::CronoJob.where.not(job_id: current_job_ids).destroy_all
|
||||
end
|
||||
@@ -20,7 +10,7 @@ namespace :crono do
|
||||
desc 'Check cronotab.rb syntax'
|
||||
task check: :environment do
|
||||
Crono.scheduler = Crono::Scheduler.new
|
||||
Crono.load_cronotab
|
||||
Crono::Cronotab.process
|
||||
puts 'Syntax ok'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,6 +10,7 @@ describe Crono::CLI do
|
||||
expect(cli).to receive(:start_working_loop)
|
||||
expect(cli).to receive(:parse_options)
|
||||
expect(cli).to receive(:write_pid)
|
||||
expect(Crono::Cronotab).to receive(:process)
|
||||
cli.run
|
||||
end
|
||||
end
|
||||
|
||||
20
spec/cronotab_spec.rb
Normal file
20
spec/cronotab_spec.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Crono::Cronotab do
|
||||
describe '#process' do
|
||||
it 'should load cronotab file' do
|
||||
cronotab_path = File.expand_path('../assets/good_cronotab.rb', __FILE__)
|
||||
expect(Crono.scheduler).to receive(:add_job).with(kind_of(Crono::Job))
|
||||
expect {
|
||||
Crono::Cronotab.process(cronotab_path)
|
||||
}.to_not raise_error
|
||||
end
|
||||
|
||||
it 'should raise error when cronotab is invalid' do
|
||||
cronotab_path = File.expand_path('../assets/bad_cronotab.rb', __FILE__)
|
||||
expect {
|
||||
Crono::Cronotab.process(cronotab_path)
|
||||
}.to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,12 +1,6 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Crono::Period do
|
||||
around(:each) do |example|
|
||||
Timecop.freeze do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
describe '#description' do
|
||||
it 'should return period description' do
|
||||
@period = Crono::Period.new(1.week, on: :monday, at: '15:20')
|
||||
@@ -55,12 +49,12 @@ describe Crono::Period do
|
||||
context 'in daily basis' do
|
||||
it "should return Time.now if the next time in past" do
|
||||
@period = Crono::Period.new(1.day, at: '06:00')
|
||||
expect(@period.next(since: 2.days.ago)).to be_eql(Time.now)
|
||||
expect(@period.next(since: 2.days.ago).to_s).to be_eql(Time.now.to_s)
|
||||
end
|
||||
|
||||
it 'should return the time 2 days from now' do
|
||||
@period = Crono::Period.new(2.day)
|
||||
expect(@period.next).to be_eql(2.days.from_now)
|
||||
expect(@period.next.to_s).to be_eql(2.days.from_now.to_s)
|
||||
end
|
||||
|
||||
it "should set time to 'at' time as a string" do
|
||||
@@ -91,7 +85,7 @@ describe Crono::Period do
|
||||
|
||||
it 'should return time in relation to last time' do
|
||||
@period = Crono::Period.new(2.days)
|
||||
expect(@period.next(since: 1.day.ago)).to be_eql(1.day.from_now)
|
||||
expect(@period.next(since: 1.day.ago).to_s).to be_eql(1.day.from_now.to_s)
|
||||
end
|
||||
|
||||
it 'should return today time if it is first run and not too late' do
|
||||
|
||||
@@ -23,7 +23,7 @@ describe Crono::Scheduler do
|
||||
expect(jobs).to be_eql [jobs[0]]
|
||||
end
|
||||
|
||||
it 'should return an array of jobs scheduled at same time' do
|
||||
it 'should return an array of jobs scheduled at same time with `at`' do
|
||||
time = 5.minutes.from_now
|
||||
scheduler.jobs = jobs = [
|
||||
Crono::Period.new(1.day, at: time.strftime('%H:%M')),
|
||||
@@ -34,5 +34,23 @@ describe Crono::Scheduler do
|
||||
time, jobs = scheduler.next_jobs
|
||||
expect(jobs).to be_eql [jobs[0], jobs[1]]
|
||||
end
|
||||
|
||||
it 'should handle a few jobs scheduled at same time without `at`' do
|
||||
scheduler.jobs = jobs = [
|
||||
Crono::Period.new(10.seconds),
|
||||
Crono::Period.new(10.seconds),
|
||||
Crono::Period.new(1.day, at: 10.minutes.from_now.strftime('%H:%M'))
|
||||
].map { |period| Crono::Job.new(TestJob, period) }
|
||||
|
||||
_, next_jobs = scheduler.next_jobs
|
||||
expect(next_jobs).to be_eql [jobs[0]]
|
||||
|
||||
Timecop.travel(4.seconds.from_now)
|
||||
expect(Thread).to receive(:new)
|
||||
jobs[0].perform
|
||||
|
||||
_, next_jobs = scheduler.next_jobs
|
||||
expect(next_jobs).to be_eql [jobs[1]]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user