mirror of
https://github.com/plashchynski/crono.git
synced 2026-03-07 06:40:07 +01:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1cf37ee30f | ||
|
|
137dfe6d19 | ||
|
|
14c3e3162e | ||
|
|
d1e15b8537 | ||
|
|
f5d65b6cc7 | ||
|
|
64dde82342 | ||
|
|
e331490c48 | ||
|
|
8a61e65963 | ||
|
|
3f9ea8350d | ||
|
|
1d9d30ef89 | ||
|
|
72810a95ae | ||
|
|
4401c6bba4 | ||
|
|
e7df4bb067 | ||
|
|
935bdebd84 | ||
|
|
b22845cba6 | ||
|
|
77481f1143 | ||
|
|
42dc1bbc4f | ||
|
|
63c52896f7 | ||
|
|
977f49a3a4 |
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:
|
||||
|
||||
10
Changes.md
10
Changes.md
@@ -1,3 +1,13 @@
|
||||
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"
|
||||
|
||||
52
Gemfile.lock
52
Gemfile.lock
@@ -1,57 +1,57 @@
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
crono (1.0.3)
|
||||
crono (1.1.0)
|
||||
activerecord (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activemodel (5.0.0)
|
||||
activesupport (= 5.0.0)
|
||||
activerecord (5.0.0)
|
||||
activemodel (= 5.0.0)
|
||||
activesupport (= 5.0.0)
|
||||
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)
|
||||
activesupport (5.0.0.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (~> 0.7)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
arel (7.0.0)
|
||||
byebug (9.0.5)
|
||||
arel (7.1.4)
|
||||
byebug (9.0.6)
|
||||
concurrent-ruby (1.0.2)
|
||||
daemons (1.2.3)
|
||||
daemons (1.2.4)
|
||||
diff-lcs (1.2.5)
|
||||
haml (4.0.7)
|
||||
tilt
|
||||
i18n (0.7.0)
|
||||
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 (11.2.2)
|
||||
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.5)
|
||||
timecop (0.8.1)
|
||||
@@ -75,4 +75,4 @@ DEPENDENCIES
|
||||
timecop (>= 0.7)
|
||||
|
||||
BUNDLED WITH
|
||||
1.11.2
|
||||
1.13.6
|
||||
|
||||
21
README.md
21
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+ (including Rails 5).
|
||||
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
|
||||
|
||||
|
||||
@@ -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 = {})
|
||||
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
|
||||
|
||||
@@ -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.3'
|
||||
VERSION = '1.1.0'
|
||||
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
|
||||
|
||||
@@ -49,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
|
||||
|
||||
Reference in New Issue
Block a user