mirror of
https://github.com/plashchynski/crono.git
synced 2026-03-07 14:50:03 +01:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c2f52d9bc | ||
|
|
33e9794621 | ||
|
|
c336b6d00b | ||
|
|
1cf37ee30f | ||
|
|
137dfe6d19 | ||
|
|
14c3e3162e | ||
|
|
d1e15b8537 | ||
|
|
f5d65b6cc7 | ||
|
|
64dde82342 | ||
|
|
e331490c48 | ||
|
|
8a61e65963 | ||
|
|
3f9ea8350d | ||
|
|
1d9d30ef89 | ||
|
|
72810a95ae | ||
|
|
4401c6bba4 | ||
|
|
e7df4bb067 | ||
|
|
935bdebd84 | ||
|
|
b22845cba6 | ||
|
|
77481f1143 | ||
|
|
42dc1bbc4f | ||
|
|
63c52896f7 | ||
|
|
977f49a3a4 | ||
|
|
7155ce797c | ||
|
|
d18866564a | ||
|
|
b39be015b1 | ||
|
|
51c914ea8a | ||
|
|
ac71db0d68 | ||
|
|
4e3ca885a8 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@
|
||||
/spec/reports/
|
||||
/tmp/
|
||||
log/*.log
|
||||
.byebug_history
|
||||
|
||||
10
.travis.yml
10
.travis.yml
@@ -3,14 +3,8 @@ os:
|
||||
- linux
|
||||
- osx
|
||||
rvm:
|
||||
- 2.0.0
|
||||
- 2.1.7
|
||||
- 2.2.3
|
||||
- 2.3.1
|
||||
matrix:
|
||||
exclude:
|
||||
- rvm: 2.3.1
|
||||
os: osx
|
||||
- 2.2.6
|
||||
- 2.3.3
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
|
||||
36
Changes.md
36
Changes.md
@@ -1,3 +1,39 @@
|
||||
1.1.0
|
||||
-----------
|
||||
- Rails 3 and old Rubies are not supported anymore, sorry rails 3 guys...
|
||||
- Requires Ruby 2.2.2 or newer
|
||||
- Fixed crash when no jobs defined in your cronotab
|
||||
- Some doc updates (thanks to @pachacamac)
|
||||
- Job will schedule on: today if at: time not passed (thanks to @acolyer)
|
||||
- Job log truncating (thanks to @reiz)
|
||||
|
||||
|
||||
1.0.3
|
||||
-----------
|
||||
- "every 1 week" jobs now displaying on Rails 5 as "1 week" not as "7 days"
|
||||
- Liberal gem dependencies to support both Rails 4 and Rails 5
|
||||
|
||||
|
||||
1.0.2
|
||||
-----------
|
||||
- Fix table_name_suffix/prefix issue: https://github.com/plashchynski/crono/issues/33
|
||||
|
||||
|
||||
1.0.1
|
||||
-----------
|
||||
- Fix job saving
|
||||
|
||||
|
||||
1.0.0
|
||||
-----------
|
||||
- Rails 5 support (thanks to @adamico)
|
||||
- Possibility to schedule jobs with arguments (thanks to @preisanalytics)
|
||||
- Added :within option to run only within given time interval (thanks to @lhz)
|
||||
- daemon gem support (thanks to @preisanalytics) https://github.com/plashchynski/crono/pull/37
|
||||
- Support multiple nodes (thanks to @Natural-Intelligence)
|
||||
- Fixed DB connection pool issue (thanks to @ChandravatiSG)
|
||||
|
||||
|
||||
0.9.1
|
||||
-----------
|
||||
- Add ability to define minimal time between job executions to support multiple corno nodes, so two different nodes will not execute the same job
|
||||
|
||||
69
Gemfile.lock
69
Gemfile.lock
@@ -1,62 +1,59 @@
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
crono (1.0.1)
|
||||
crono (1.1.1)
|
||||
activerecord (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activemodel (4.2.6)
|
||||
activesupport (= 4.2.6)
|
||||
builder (~> 3.1)
|
||||
activerecord (4.2.6)
|
||||
activemodel (= 4.2.6)
|
||||
activesupport (= 4.2.6)
|
||||
arel (~> 6.0)
|
||||
activesupport (4.2.6)
|
||||
activemodel (5.0.0.1)
|
||||
activesupport (= 5.0.0.1)
|
||||
activerecord (5.0.0.1)
|
||||
activemodel (= 5.0.0.1)
|
||||
activesupport (= 5.0.0.1)
|
||||
arel (~> 7.0)
|
||||
activesupport (5.0.0.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (~> 0.7)
|
||||
json (~> 1.7, >= 1.7.7)
|
||||
minitest (~> 5.1)
|
||||
thread_safe (~> 0.3, >= 0.3.4)
|
||||
tzinfo (~> 1.1)
|
||||
arel (6.0.3)
|
||||
builder (3.2.2)
|
||||
byebug (9.0.5)
|
||||
daemons (1.2.3)
|
||||
arel (7.1.4)
|
||||
byebug (9.0.6)
|
||||
concurrent-ruby (1.0.2)
|
||||
daemons (1.2.4)
|
||||
diff-lcs (1.2.5)
|
||||
haml (4.0.7)
|
||||
tilt
|
||||
i18n (0.7.0)
|
||||
json (1.8.3)
|
||||
minitest (5.9.0)
|
||||
rack (1.6.4)
|
||||
minitest (5.9.1)
|
||||
rack (1.6.5)
|
||||
rack-protection (1.5.3)
|
||||
rack
|
||||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
rake (10.5.0)
|
||||
rspec (3.4.0)
|
||||
rspec-core (~> 3.4.0)
|
||||
rspec-expectations (~> 3.4.0)
|
||||
rspec-mocks (~> 3.4.0)
|
||||
rspec-core (3.4.4)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-expectations (3.4.0)
|
||||
rake (11.3.0)
|
||||
rspec (3.5.0)
|
||||
rspec-core (~> 3.5.0)
|
||||
rspec-expectations (~> 3.5.0)
|
||||
rspec-mocks (~> 3.5.0)
|
||||
rspec-core (3.5.4)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-expectations (3.5.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-mocks (3.4.1)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-mocks (3.5.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-support (3.4.1)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-support (3.5.0)
|
||||
sinatra (1.4.7)
|
||||
rack (~> 1.5)
|
||||
rack-protection (~> 1.4)
|
||||
tilt (>= 1.3, < 3)
|
||||
sqlite3 (1.3.11)
|
||||
sqlite3 (1.3.12)
|
||||
thread_safe (0.3.5)
|
||||
tilt (2.0.2)
|
||||
tilt (2.0.5)
|
||||
timecop (0.8.1)
|
||||
tzinfo (1.2.2)
|
||||
thread_safe (~> 0.1)
|
||||
@@ -71,11 +68,11 @@ DEPENDENCIES
|
||||
daemons
|
||||
haml
|
||||
rack-test
|
||||
rake (~> 10.0)
|
||||
rspec (~> 3.0)
|
||||
rake (>= 10.0)
|
||||
rspec (>= 3.0)
|
||||
sinatra
|
||||
sqlite3
|
||||
timecop (~> 0.7)
|
||||
timecop (>= 0.7)
|
||||
|
||||
BUNDLED WITH
|
||||
1.11.2
|
||||
1.13.6
|
||||
|
||||
26
README.md
26
README.md
@@ -4,7 +4,6 @@ Crono — Job scheduler for Rails
|
||||
[](https://travis-ci.org/plashchynski/crono)
|
||||
[](https://codeclimate.com/github/plashchynski/crono)
|
||||
[](https://hakiri.io/github/plashchynski/crono/master)
|
||||
[](https://gitter.im/plashchynski/crono?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
Crono is a time-based background job scheduler daemon (just like Cron) for Ruby on Rails.
|
||||
|
||||
@@ -18,7 +17,7 @@ Currently, there is no such thing as Ruby Cron for Rails. Well, there's [Wheneve
|
||||
|
||||
## Requirements
|
||||
|
||||
Tested with latest MRI Ruby (2.3, 2.2, 2.1 and 2.0) and Rails 3.2+
|
||||
Tested with latest MRI Ruby 2.2+, 2.3+, Rails 4.\*, and Rails 5.\*.
|
||||
Other versions are untested but might work fine.
|
||||
|
||||
|
||||
@@ -77,8 +76,8 @@ Here's an example of a Rake Task within a job:
|
||||
```ruby
|
||||
# config/cronotab.rb
|
||||
require 'rake'
|
||||
# Be sure to change AppName to your application name!
|
||||
AppName::Application.load_tasks
|
||||
|
||||
Rails.app_class.load_tasks
|
||||
|
||||
class Test
|
||||
def perform
|
||||
@@ -131,6 +130,12 @@ serialized using JSON.generate
|
||||
Crono.perform(TestJob, 'some', 'args').every 1.day, at: {hour: 12, min: 15}
|
||||
```
|
||||
|
||||
You can set some options that not passed to the job but affect how the job will be treated by Crono. For example, you can set to truncate job logs (which stored in the database) to a certain number of records:
|
||||
|
||||
```ruby
|
||||
Crono.perform(TestJob).with_options(truncate_log: 100).every 1.week, on: :monday
|
||||
```
|
||||
|
||||
#### Run
|
||||
|
||||
To run Crono, in your Rails project root directory:
|
||||
@@ -184,6 +189,14 @@ Rails.application.routes.draw do
|
||||
|
||||
Access management and other questions described in the [wiki](https://github.com/plashchynski/crono/wiki/Web-UI).
|
||||
|
||||
#### Known issues
|
||||
|
||||
For Rails 5, in case of the errors:
|
||||
```
|
||||
`require': cannot load such file -- rack/showexceptions (LoadError)
|
||||
```
|
||||
See the related issue [#52](https://github.com/plashchynski/crono/issues/52)
|
||||
|
||||
|
||||
## Capistrano
|
||||
|
||||
@@ -195,6 +208,11 @@ Use the `capistrano-crono` gem ([github](https://github.com/plashchynski/capistr
|
||||
Feel free to create [issues](https://github.com/plashchynski/crono/issues)
|
||||
|
||||
|
||||
## Known Issues
|
||||
|
||||
* Is not compatible with the `protected_attributes` gem. See: [https://github.com/plashchynski/crono/issues/43](https://github.com/plashchynski/crono/issues/43)
|
||||
|
||||
|
||||
## License
|
||||
|
||||
Please see [LICENSE](https://github.com/plashchynski/crono/blob/master/LICENSE) for licensing details.
|
||||
|
||||
@@ -21,10 +21,10 @@ Gem::Specification.new do |spec|
|
||||
|
||||
spec.add_runtime_dependency 'activesupport', '>= 4.0'
|
||||
spec.add_runtime_dependency 'activerecord', '>= 4.0'
|
||||
spec.add_development_dependency 'rake', '~> 10.0'
|
||||
spec.add_development_dependency 'rake', '>= 10.0'
|
||||
spec.add_development_dependency 'bundler', '>= 1.0.0'
|
||||
spec.add_development_dependency 'rspec', '~> 3.0'
|
||||
spec.add_development_dependency 'timecop', '~> 0.7'
|
||||
spec.add_development_dependency 'rspec', '>= 3.0'
|
||||
spec.add_development_dependency 'timecop', '>= 0.7'
|
||||
spec.add_development_dependency 'sqlite3'
|
||||
spec.add_development_dependency 'byebug'
|
||||
spec.add_development_dependency 'sinatra'
|
||||
|
||||
@@ -29,7 +29,11 @@ module Crono
|
||||
Cronotab.process(File.expand_path(config.cronotab))
|
||||
print_banner
|
||||
|
||||
check_jobs
|
||||
unless have_jobs?
|
||||
logger.error "You have no jobs in you cronotab file #{config.cronotab}"
|
||||
return
|
||||
end
|
||||
|
||||
if config.daemonize
|
||||
start_working_loop_in_daemon
|
||||
else
|
||||
@@ -39,6 +43,10 @@ module Crono
|
||||
|
||||
private
|
||||
|
||||
def have_jobs?
|
||||
Crono.scheduler.jobs.present?
|
||||
end
|
||||
|
||||
def setup_log
|
||||
if config.daemonize
|
||||
self.logfile = config.logfile
|
||||
@@ -85,11 +93,6 @@ module Crono
|
||||
::Rails.application.eager_load!
|
||||
end
|
||||
|
||||
def check_jobs
|
||||
return if Crono.scheduler.jobs.present?
|
||||
logger.error "You have no jobs in you cronotab file #{config.cronotab}"
|
||||
end
|
||||
|
||||
def start_working_loop_in_daemon
|
||||
unless ENV['RAILS_ENV'] == 'test'
|
||||
begin
|
||||
|
||||
@@ -6,15 +6,16 @@ module Crono
|
||||
class Job
|
||||
include Logging
|
||||
|
||||
attr_accessor :performer, :period, :job_args, :last_performed_at,
|
||||
attr_accessor :performer, :period, :job_args, :last_performed_at, :job_options,
|
||||
:next_performed_at, :job_log, :job_logger, :healthy, :execution_interval
|
||||
|
||||
def initialize(performer, period, job_args)
|
||||
def initialize(performer, period, job_args, job_options = nil)
|
||||
self.execution_interval = 0.minutes
|
||||
self.performer, self.period = performer, period
|
||||
self.job_args = JSON.generate(job_args)
|
||||
self.job_log = StringIO.new
|
||||
self.job_logger = Logger.new(job_log)
|
||||
self.job_options = job_options || {}
|
||||
self.next_performed_at = period.next
|
||||
@semaphore = Mutex.new
|
||||
end
|
||||
@@ -61,9 +62,15 @@ module Crono
|
||||
job_log.truncate(job_log.rewind)
|
||||
end
|
||||
|
||||
def truncate_log(log)
|
||||
return log.lines.last(job_options[:truncate_log]).join if job_options[:truncate_log]
|
||||
return log
|
||||
end
|
||||
|
||||
def update_model
|
||||
saved_log = model.reload.log || ''
|
||||
log_to_save = saved_log + job_log.string
|
||||
log_to_save = truncate_log(log_to_save)
|
||||
model.update(last_performed_at: last_performed_at, log: log_to_save,
|
||||
healthy: healthy)
|
||||
end
|
||||
@@ -98,7 +105,7 @@ module Crono
|
||||
|
||||
def log(message, severity = Logger::INFO)
|
||||
@semaphore.synchronize do
|
||||
logger.log severity, message
|
||||
logger.log(severity, message) if logger
|
||||
job_logger.log severity, message
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,7 +3,6 @@ require 'active_record'
|
||||
module Crono
|
||||
# Crono::CronoJob is a ActiveRecord model to store job state
|
||||
class CronoJob < ActiveRecord::Base
|
||||
self.table_name = 'crono_jobs'
|
||||
validates :job_id, presence: true, uniqueness: true
|
||||
|
||||
def self.outdated
|
||||
|
||||
@@ -8,7 +8,7 @@ module Crono
|
||||
end
|
||||
|
||||
def every(period, *args)
|
||||
@job = Job.new(@performer, Period.new(period, *args), @job_args)
|
||||
@job = Job.new(@performer, Period.new(period, *args), @job_args, @options)
|
||||
@scheduler.add_job(@job)
|
||||
self
|
||||
end
|
||||
@@ -17,6 +17,11 @@ module Crono
|
||||
@job.execution_interval = execution_interval if @job
|
||||
self
|
||||
end
|
||||
|
||||
def with_options(options)
|
||||
@options = options
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
def self.perform(performer, *job_args)
|
||||
|
||||
@@ -23,6 +23,7 @@ module Crono
|
||||
return initial_next unless since
|
||||
@next = @period.since(since)
|
||||
end
|
||||
|
||||
@next = @next.beginning_of_week.advance(days: @on) if @on
|
||||
@next = @next.change(time_atts)
|
||||
return @next if @next.future?
|
||||
@@ -48,6 +49,7 @@ module Crono
|
||||
def initial_day
|
||||
return Time.now unless @on
|
||||
day = Time.now.beginning_of_week.advance(days: @on)
|
||||
day = day.change(time_atts)
|
||||
return day if day.future?
|
||||
@period.from_now.beginning_of_week.advance(days: @on)
|
||||
end
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module Crono
|
||||
VERSION = '1.0.1'
|
||||
VERSION = '1.1.1'
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@ class CreateCronoJobs < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :crono_jobs do |t|
|
||||
t.string :job_id, null: false
|
||||
t.text :log
|
||||
t.text :log, limit: 4294967295 # LONGTEXT for MySQL
|
||||
t.datetime :last_performed_at
|
||||
t.boolean :healthy
|
||||
t.timestamps null: false
|
||||
|
||||
@@ -7,6 +7,7 @@ describe Crono::CLI do
|
||||
describe '#run' do
|
||||
it 'should initialize rails with #load_rails and start working loop' do
|
||||
expect(cli).to receive(:load_rails)
|
||||
expect(cli).to receive(:have_jobs?).and_return(true)
|
||||
expect(cli).to receive(:start_working_loop)
|
||||
expect(cli).to receive(:parse_options)
|
||||
expect(cli).to receive(:parse_command)
|
||||
@@ -14,12 +15,13 @@ describe Crono::CLI do
|
||||
expect(Crono::Cronotab).to receive(:process)
|
||||
cli.run
|
||||
end
|
||||
context 'should run as daemon' do
|
||||
|
||||
before {cli.config.daemonize = true}
|
||||
context 'should run as daemon' do
|
||||
before { cli.config.daemonize = true }
|
||||
|
||||
it 'should initialize rails with #load_rails and start working loop' do
|
||||
expect(cli).to receive(:load_rails)
|
||||
expect(cli).to receive(:have_jobs?).and_return(true)
|
||||
expect(cli).to receive(:start_working_loop_in_daemon)
|
||||
expect(cli).to receive(:parse_options)
|
||||
expect(cli).to receive(:parse_command)
|
||||
|
||||
@@ -107,13 +107,29 @@ describe Crono::Job do
|
||||
expect(@crono_job.healthy).to be true
|
||||
end
|
||||
|
||||
it 'should save and truncate job log' do
|
||||
it 'should save log' do
|
||||
message = 'test message'
|
||||
job.send(:log, message)
|
||||
job.save
|
||||
expect(job.send(:model).reload.log).to include message
|
||||
expect(job.job_log.string).to be_empty
|
||||
end
|
||||
|
||||
it 'should not truncate log if not specified' do
|
||||
log = (1..100).map {|n| "line #{n}" }.join("\n")
|
||||
job = Crono::Job.new(TestJob, period, [])
|
||||
job.send(:log, log)
|
||||
job.save
|
||||
expect(job.send(:model).reload.log.lines.size).to be >= log.lines.size
|
||||
end
|
||||
|
||||
it 'should truncate log if specified' do
|
||||
log = (1..100).map {|n| "line #{n}" }.join("\n")
|
||||
job = Crono::Job.new(TestJob, period, [], truncate_log: 50)
|
||||
job.send(:log, log)
|
||||
job.save
|
||||
expect(job.send(:model).reload.log.lines.size).to be 50
|
||||
end
|
||||
end
|
||||
|
||||
describe '#load' do
|
||||
@@ -132,6 +148,7 @@ describe Crono::Job do
|
||||
describe '#log' do
|
||||
it 'should write log messages to both common and job log' do
|
||||
message = 'Test message'
|
||||
job.logfile = "/dev/null"
|
||||
expect(job.logger).to receive(:log).with(Logger::INFO, message)
|
||||
expect(job.job_logger).to receive(:log).with(Logger::INFO, message)
|
||||
job.send(:log, message)
|
||||
|
||||
@@ -20,8 +20,14 @@ describe Crono::PerformerProxy do
|
||||
end
|
||||
|
||||
it 'should add job with args to schedule' do
|
||||
expect(Crono::Job).to receive(:new).with(TestJob, kind_of(Crono::Period), [:some, {some: 'data'}])
|
||||
expect(Crono::Job).to receive(:new).with(TestJob, kind_of(Crono::Period), [:some, {some: 'data'}], nil)
|
||||
allow(Crono.scheduler).to receive(:add_job)
|
||||
Crono.perform(TestJob, :some, {some: 'data'}).every(2.days, at: '15:30')
|
||||
end
|
||||
|
||||
it 'should add job with options to schedule' do
|
||||
expect(Crono::Job).to receive(:new).with(TestJob, kind_of(Crono::Period), [], {some_option: true})
|
||||
allow(Crono.scheduler).to receive(:add_job)
|
||||
Crono.perform(TestJob).with_options(some_option: true).every(2.days, at: '15:30')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,7 +4,12 @@ describe Crono::Period do
|
||||
describe '#description' do
|
||||
it 'should return period description' do
|
||||
@period = Crono::Period.new(1.week, on: :monday, at: '15:20')
|
||||
expect(@period.description).to be_eql('every 7 days at 15:20 on Monday')
|
||||
expected_description = if ActiveSupport::VERSION::MAJOR >= 5
|
||||
'every 1 week at 15:20 on Monday'
|
||||
else
|
||||
'every 7 days at 15:20 on Monday'
|
||||
end
|
||||
expect(@period.description).to be_eql(expected_description)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -44,6 +49,16 @@ describe Crono::Period do
|
||||
expect(@period.next).to be_eql(tuesday)
|
||||
end
|
||||
end
|
||||
|
||||
it 'should return today on the first run if not too late' do
|
||||
@period = Crono::Period.new(1.week, on: :sunday, at: '22:00')
|
||||
Timecop.freeze(Time.now.beginning_of_week.advance(days: 6)
|
||||
.change(hour: 21, min: 0)) do
|
||||
expect(@period.next).to be_eql(
|
||||
Time.now.beginning_of_week.advance(days: 6).change(hour: 22, min: 0)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'in daily basis' do
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
%a{ href: url('/') }
|
||||
%i.mdi-navigation-chevron-left
|
||||
Back to Home
|
||||
%h4 "#{@job.job_id}" Log:
|
||||
%h4 "#{@job.job_id}" Last log:
|
||||
|
||||
%main.container.blue-grey.lighten-4.grey-text.text-darken-4
|
||||
- if @job.healthy == false
|
||||
|
||||
Reference in New Issue
Block a user