crono 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changes.md +19 -8
- data/Gemfile.lock +5 -2
- data/README.md +11 -9
- data/bin/crono +2 -2
- data/crono.gemspec +21 -20
- data/examples/cronotab.rb +3 -4
- data/lib/crono.rb +12 -9
- data/lib/crono/cli.rb +21 -15
- data/lib/crono/config.rb +6 -9
- data/lib/crono/job.rb +49 -25
- data/lib/crono/logging.rb +2 -1
- data/lib/crono/orm/active_record/crono_job.rb +2 -1
- data/lib/crono/performer_proxy.rb +1 -0
- data/lib/crono/period.rb +37 -12
- data/lib/crono/scheduler.rb +3 -1
- data/lib/crono/version.rb +1 -1
- data/lib/crono/web.rb +6 -4
- data/lib/generators/crono/install/install_generator.rb +5 -3
- data/lib/generators/crono/install/templates/cronotab.rb.erb +2 -2
- data/lib/generators/crono/install/templates/migrations/create_crono_jobs.rb +1 -0
- data/spec/cli_spec.rb +20 -23
- data/spec/config_spec.rb +5 -5
- data/spec/job_spec.rb +49 -27
- data/spec/orm/active_record/crono_job_spec.rb +8 -8
- data/spec/performer_proxy_spec.rb +5 -4
- data/spec/period_spec.rb +59 -15
- data/spec/scheduler_spec.rb +11 -10
- data/spec/spec_helper.rb +5 -4
- data/spec/web_spec.rb +48 -0
- data/web/views/dashboard.haml +8 -3
- data/web/views/job.haml +7 -1
- data/web/views/layout.haml +9 -9
- metadata +18 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6935e065a4c161dafada3370230b5f00ee0321e3
|
4
|
+
data.tar.gz: d1667f7a5efc7f356bab0ae8b867dd9f6d65e612
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f727c28c3d2d909fe1a8f12fb26074ca0ce7f574cc4323da62ec638f92514b4e4256b882f81b8685496f6c87ffe0e280e94d8204642c1ca54b6e3d035036819b
|
7
|
+
data.tar.gz: 54aadbd50534a87e59396c020da59ca8efeb04b708a209cf0cf891409034669781ec0ff54899ee0ae7432ecaba96a37a06d061c2942287a07e7e672170e75c63
|
data/Changes.md
CHANGED
@@ -1,24 +1,35 @@
|
|
1
|
-
0.
|
1
|
+
0.8.0
|
2
2
|
-----------
|
3
3
|
|
4
|
-
-
|
4
|
+
- Added `on` (day of week) option to cronotab.rb semantic
|
5
|
+
- Added job health check and job health indicator to the Web UI
|
5
6
|
|
6
|
-
|
7
|
+
|
8
|
+
0.7.0
|
7
9
|
-----------
|
8
10
|
|
9
|
-
- Added
|
11
|
+
- Added simple Web UI
|
12
|
+
|
13
|
+
|
14
|
+
0.6.1
|
15
|
+
-----------
|
16
|
+
|
17
|
+
- Persist job state to your database.
|
18
|
+
|
10
19
|
|
11
20
|
0.5.2
|
12
21
|
-----------
|
13
22
|
|
14
23
|
- Fix: Scheduled time now related to the last performing time.
|
15
24
|
|
16
|
-
|
25
|
+
|
26
|
+
0.5.1
|
17
27
|
-----------
|
18
28
|
|
19
|
-
-
|
29
|
+
- Added -e/--environment ENV option to set the daemon rails environment.
|
20
30
|
|
21
|
-
|
31
|
+
|
32
|
+
0.5.0
|
22
33
|
-----------
|
23
34
|
|
24
|
-
-
|
35
|
+
- Initial release!
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
crono (0.
|
4
|
+
crono (0.8.0)
|
5
5
|
activejob (~> 4.0)
|
6
6
|
activerecord (~> 4.0)
|
7
7
|
activesupport (~> 4.0)
|
@@ -44,6 +44,8 @@ GEM
|
|
44
44
|
rack (1.6.0)
|
45
45
|
rack-protection (1.5.3)
|
46
46
|
rack
|
47
|
+
rack-test (0.6.3)
|
48
|
+
rack (>= 1.0)
|
47
49
|
rake (10.4.2)
|
48
50
|
rspec (3.2.0)
|
49
51
|
rspec-core (~> 3.2.0)
|
@@ -64,7 +66,7 @@ GEM
|
|
64
66
|
tilt (~> 1.3, >= 1.3.4)
|
65
67
|
slop (3.6.0)
|
66
68
|
sqlite3 (1.3.10)
|
67
|
-
thread_safe (0.3.
|
69
|
+
thread_safe (0.3.5)
|
68
70
|
tilt (1.4.1)
|
69
71
|
timecop (0.7.3)
|
70
72
|
tzinfo (1.2.2)
|
@@ -78,6 +80,7 @@ DEPENDENCIES
|
|
78
80
|
byebug
|
79
81
|
crono!
|
80
82
|
haml
|
83
|
+
rack-test
|
81
84
|
rake (~> 10.0)
|
82
85
|
rspec (~> 3.0)
|
83
86
|
sinatra
|
data/README.md
CHANGED
@@ -9,12 +9,13 @@ Crono — Job scheduler for Rails
|
|
9
9
|
Crono is a time-based background job scheduler daemon (just like Cron) for Ruby on Rails.
|
10
10
|
|
11
11
|
|
12
|
-
## The
|
12
|
+
## The Purpose
|
13
13
|
|
14
|
-
Currently there is no such thing as Cron
|
14
|
+
Currently there is no such thing as Ruby Cron for Rails. Well, there's [Whenever](https://github.com/javan/whenever) but it works on top of Unix Cron, so you haven't control of it from Ruby. Crono is pure Ruby. It doesn't use Unix Cron and other platform-dependent things. So you can use it on all platforms supported by Ruby. It persists job states to your database using Active Record. You have full control of jobs performing process. It's Ruby, so you can understand and modify it to fit your needs.
|
15
15
|
|
16
16
|
![Web UI](https://github.com/plashchynski/crono/raw/master/examples/crono_web_ui.png)
|
17
17
|
|
18
|
+
|
18
19
|
## Requirements
|
19
20
|
|
20
21
|
Tested with latest MRI Ruby (2.2, 2.1 and 2.0) and Rails 3.2+
|
@@ -73,18 +74,19 @@ end
|
|
73
74
|
|
74
75
|
#### Job Schedule
|
75
76
|
|
76
|
-
|
77
|
+
Schedule list is defined in the file `config/cronotab.rb`, that created using `crono:install`. The semantic is pretty straightforward:
|
77
78
|
|
78
79
|
```ruby
|
79
80
|
# config/cronotab.rb
|
80
|
-
Crono.perform(TestJob).every 2.days, at:
|
81
|
+
Crono.perform(TestJob).every 2.days, at: {hour: 15, min: 30}
|
82
|
+
Crono.perform(TestJob).every 1.week, on: :monday, at: "15:30"
|
81
83
|
```
|
82
84
|
|
83
|
-
You can schedule one job a few times, if you want
|
85
|
+
You can schedule one job a few times, if you want the job to be performed a few times a day or a week:
|
84
86
|
|
85
87
|
```ruby
|
86
|
-
Crono.perform(TestJob).every 1.
|
87
|
-
Crono.perform(TestJob).every 1.
|
88
|
+
Crono.perform(TestJob).every 1.week, on: :monday
|
89
|
+
Crono.perform(TestJob).every 1.week, on: :thursday
|
88
90
|
```
|
89
91
|
|
90
92
|
The `at` can be a Hash:
|
@@ -109,6 +111,7 @@ Usage: crono [options]
|
|
109
111
|
-e, --environment ENV Application environment (Default: development)
|
110
112
|
```
|
111
113
|
|
114
|
+
|
112
115
|
## Web UI
|
113
116
|
|
114
117
|
Crono comes with a Sinatra application that can display the current state of Crono jobs.
|
@@ -122,8 +125,6 @@ gem 'sinatra', require: nil
|
|
122
125
|
Add the following to your `config/routes.rb`:
|
123
126
|
|
124
127
|
```ruby
|
125
|
-
require 'crono/web'
|
126
|
-
|
127
128
|
Rails.application.routes.draw do
|
128
129
|
mount Crono::Web, at: '/crono'
|
129
130
|
...
|
@@ -131,6 +132,7 @@ Rails.application.routes.draw do
|
|
131
132
|
|
132
133
|
Access management and other questions described in the [wiki](https://github.com/plashchynski/crono/wiki/Web-UI).
|
133
134
|
|
135
|
+
|
134
136
|
## Capistrano
|
135
137
|
|
136
138
|
Use the `capistrano-crono` gem ([github](https://github.com/plashchynski/capistrano-crono/)).
|
data/bin/crono
CHANGED
data/crono.gemspec
CHANGED
@@ -2,30 +2,31 @@
|
|
2
2
|
require File.expand_path('../lib/crono/version', __FILE__)
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
|
-
s.name =
|
5
|
+
s.name = 'crono'
|
6
6
|
s.version = Crono::VERSION
|
7
|
-
s.authors = [
|
8
|
-
s.email = [
|
9
|
-
s.homepage =
|
10
|
-
s.description = s.summary =
|
11
|
-
s.license =
|
7
|
+
s.authors = ['Dzmitry Plashchynski']
|
8
|
+
s.email = ['plashchynski@gmail.com']
|
9
|
+
s.homepage = 'https://github.com/plashchynski/crono'
|
10
|
+
s.description = s.summary = 'Job scheduler for Rails'
|
11
|
+
s.license = 'Apache-2.0'
|
12
12
|
|
13
|
-
s.required_rubygems_version =
|
14
|
-
s.rubyforge_project =
|
13
|
+
s.required_rubygems_version = '>= 1.3.6'
|
14
|
+
s.rubyforge_project = 'crono'
|
15
15
|
|
16
|
-
s.add_runtime_dependency
|
17
|
-
s.add_runtime_dependency
|
18
|
-
s.add_runtime_dependency
|
19
|
-
s.add_development_dependency
|
20
|
-
s.add_development_dependency
|
21
|
-
s.add_development_dependency
|
22
|
-
s.add_development_dependency
|
23
|
-
s.add_development_dependency
|
24
|
-
s.add_development_dependency
|
25
|
-
s.add_development_dependency
|
26
|
-
s.add_development_dependency
|
16
|
+
s.add_runtime_dependency 'activejob', '~> 4.0'
|
17
|
+
s.add_runtime_dependency 'activesupport', '~> 4.0'
|
18
|
+
s.add_runtime_dependency 'activerecord', '~> 4.0'
|
19
|
+
s.add_development_dependency 'rake', '~> 10.0'
|
20
|
+
s.add_development_dependency 'bundler', '>= 1.0.0'
|
21
|
+
s.add_development_dependency 'rspec', '~> 3.0'
|
22
|
+
s.add_development_dependency 'timecop', '~> 0.7'
|
23
|
+
s.add_development_dependency 'sqlite3'
|
24
|
+
s.add_development_dependency 'byebug'
|
25
|
+
s.add_development_dependency 'sinatra'
|
26
|
+
s.add_development_dependency 'haml'
|
27
|
+
s.add_development_dependency 'rack-test'
|
27
28
|
|
28
29
|
s.files = `git ls-files`.split("\n")
|
29
|
-
s.executables =
|
30
|
+
s.executables = ['crono']
|
30
31
|
s.require_path = 'lib'
|
31
32
|
end
|
data/examples/cronotab.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# cronotab.rb
|
1
|
+
# cronotab.rb - Crono configuration file
|
2
2
|
#
|
3
3
|
# Here you can specify periodic jobs and schedule.
|
4
4
|
# You can use ActiveJob's jobs from `app/jobs/`
|
@@ -7,9 +7,8 @@
|
|
7
7
|
#
|
8
8
|
class TestJob
|
9
9
|
def perform
|
10
|
-
puts
|
10
|
+
puts 'Test!'
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
Crono.perform(TestJob).every 2.days, at:
|
15
|
-
|
14
|
+
Crono.perform(TestJob).every 2.days, at: '15:30'
|
data/lib/crono.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
|
+
# Crono main module
|
1
2
|
module Crono
|
2
3
|
end
|
3
4
|
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
5
|
+
require 'active_support/all'
|
6
|
+
require 'crono/version'
|
7
|
+
require 'crono/logging'
|
8
|
+
require 'crono/period'
|
9
|
+
require 'crono/job'
|
10
|
+
require 'crono/scheduler'
|
11
|
+
require 'crono/config'
|
12
|
+
require 'crono/performer_proxy'
|
13
|
+
require 'crono/orm/active_record/crono_job'
|
14
|
+
|
15
|
+
Crono.autoload :Web, 'crono/web'
|
data/lib/crono/cli.rb
CHANGED
@@ -4,6 +4,7 @@ require 'optparse'
|
|
4
4
|
module Crono
|
5
5
|
mattr_accessor :scheduler
|
6
6
|
|
7
|
+
# Crono::CLI - The main class for the crono daemon exacutable `bin/crono`
|
7
8
|
class CLI
|
8
9
|
include Singleton
|
9
10
|
include Logging
|
@@ -18,12 +19,7 @@ module Crono
|
|
18
19
|
def run
|
19
20
|
parse_options(ARGV)
|
20
21
|
|
21
|
-
|
22
|
-
set_log_to(config.logfile)
|
23
|
-
daemonize
|
24
|
-
else
|
25
|
-
set_log_to(STDOUT)
|
26
|
-
end
|
22
|
+
setup_log
|
27
23
|
|
28
24
|
write_pid
|
29
25
|
load_rails
|
@@ -33,7 +29,17 @@ module Crono
|
|
33
29
|
start_working_loop
|
34
30
|
end
|
35
31
|
|
36
|
-
|
32
|
+
private
|
33
|
+
|
34
|
+
def setup_log
|
35
|
+
if config.daemonize
|
36
|
+
self.logifile = config.logfile
|
37
|
+
daemonize
|
38
|
+
else
|
39
|
+
self.logfile = STDOUT
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
37
43
|
def daemonize
|
38
44
|
::Process.daemon(true, true)
|
39
45
|
|
@@ -42,7 +48,7 @@ module Crono
|
|
42
48
|
io.sync = true
|
43
49
|
end
|
44
50
|
|
45
|
-
$stdin.reopen(
|
51
|
+
$stdin.reopen('/dev/null')
|
46
52
|
end
|
47
53
|
|
48
54
|
def write_pid
|
@@ -54,28 +60,28 @@ module Crono
|
|
54
60
|
logger.info "Loading Crono #{Crono::VERSION}"
|
55
61
|
logger.info "Running in #{RUBY_DESCRIPTION}"
|
56
62
|
|
57
|
-
logger.info
|
63
|
+
logger.info 'Jobs:'
|
58
64
|
Crono.scheduler.jobs.each do |job|
|
59
|
-
logger.info
|
65
|
+
logger.info "'#{job.performer}' with rule '#{job.period.description}'"\
|
66
|
+
"next time will perform at #{job.next}"
|
60
67
|
end
|
61
68
|
end
|
62
69
|
|
63
70
|
def load_rails
|
64
71
|
ENV['RACK_ENV'] = ENV['RAILS_ENV'] = config.environment
|
65
72
|
require 'rails'
|
66
|
-
require File.expand_path(
|
73
|
+
require File.expand_path('config/environment.rb')
|
67
74
|
::Rails.application.eager_load!
|
68
75
|
require File.expand_path(config.cronotab)
|
69
76
|
end
|
70
77
|
|
71
78
|
def check_jobs
|
72
|
-
if Crono.scheduler.jobs.
|
73
|
-
|
74
|
-
end
|
79
|
+
return if Crono.scheduler.jobs.present?
|
80
|
+
logger.error "You have no jobs in you cronotab file #{config.cronotab}"
|
75
81
|
end
|
76
82
|
|
77
83
|
def start_working_loop
|
78
|
-
while job = Crono.scheduler.next
|
84
|
+
while (job = Crono.scheduler.next)
|
79
85
|
sleep(job.next - Time.now)
|
80
86
|
job.perform
|
81
87
|
end
|
data/lib/crono/config.rb
CHANGED
@@ -1,21 +1,18 @@
|
|
1
1
|
module Crono
|
2
|
+
# Crono::Config stores Crono configuration
|
2
3
|
class Config
|
3
|
-
CRONOTAB =
|
4
|
-
LOGFILE =
|
5
|
-
PIDFILE =
|
4
|
+
CRONOTAB = 'config/cronotab.rb'
|
5
|
+
LOGFILE = 'log/crono.log'
|
6
|
+
PIDFILE = 'tmp/pids/crono.pid'
|
6
7
|
|
7
|
-
attr_accessor :cronotab
|
8
|
-
attr_accessor :logfile
|
9
|
-
attr_accessor :pidfile
|
10
|
-
attr_accessor :daemonize
|
11
|
-
attr_accessor :environment
|
8
|
+
attr_accessor :cronotab, :logfile, :pidfile, :daemonize, :environment
|
12
9
|
|
13
10
|
def initialize
|
14
11
|
self.cronotab = CRONOTAB
|
15
12
|
self.logfile = LOGFILE
|
16
13
|
self.pidfile = PIDFILE
|
17
14
|
self.daemonize = false
|
18
|
-
self.environment = ENV['RAILS_ENV'] || ENV['RACK_ENV'] ||
|
15
|
+
self.environment = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
19
16
|
end
|
20
17
|
end
|
21
18
|
end
|
data/lib/crono/job.rb
CHANGED
@@ -2,14 +2,12 @@ require 'stringio'
|
|
2
2
|
require 'logger'
|
3
3
|
|
4
4
|
module Crono
|
5
|
+
# Crono::Job represents a Crono job
|
5
6
|
class Job
|
6
7
|
include Logging
|
7
8
|
|
8
|
-
attr_accessor :performer
|
9
|
-
|
10
|
-
attr_accessor :last_performed_at
|
11
|
-
attr_accessor :job_log
|
12
|
-
attr_accessor :job_logger
|
9
|
+
attr_accessor :performer, :period, :last_performed_at, :job_log,
|
10
|
+
:job_logger, :healthy
|
13
11
|
|
14
12
|
def initialize(performer, period)
|
15
13
|
self.performer, self.period = performer, period
|
@@ -35,26 +33,13 @@ module Crono
|
|
35
33
|
log "Perform #{performer}"
|
36
34
|
self.last_performed_at = Time.now
|
37
35
|
|
38
|
-
Thread.new
|
39
|
-
begin
|
40
|
-
performer.new.perform
|
41
|
-
rescue Exception => e
|
42
|
-
log "Finished #{performer} in %.2f seconds with error: #{e.message}" % (Time.now - last_performed_at)
|
43
|
-
log e.backtrace.join("\n")
|
44
|
-
else
|
45
|
-
log "Finished #{performer} in %.2f seconds" % (Time.now - last_performed_at)
|
46
|
-
ensure
|
47
|
-
save
|
48
|
-
end
|
49
|
-
end
|
36
|
+
Thread.new { perform_job }
|
50
37
|
end
|
51
38
|
|
52
39
|
def save
|
53
40
|
@semaphore.synchronize do
|
54
|
-
|
55
|
-
|
56
|
-
job_log.truncate(job_log.rewind)
|
57
|
-
model.update(last_performed_at: last_performed_at, log: log)
|
41
|
+
update_model
|
42
|
+
clear_job_log
|
58
43
|
end
|
59
44
|
end
|
60
45
|
|
@@ -62,11 +47,50 @@ module Crono
|
|
62
47
|
self.last_performed_at = model.last_performed_at
|
63
48
|
end
|
64
49
|
|
65
|
-
|
66
|
-
|
50
|
+
private
|
51
|
+
|
52
|
+
def clear_job_log
|
53
|
+
job_log.truncate(job_log.rewind)
|
54
|
+
end
|
55
|
+
|
56
|
+
def update_model
|
57
|
+
saved_log = model.reload.log || ''
|
58
|
+
log_to_save = saved_log + job_log.string
|
59
|
+
model.update(last_performed_at: last_performed_at, log: log_to_save,
|
60
|
+
healthy: healthy)
|
61
|
+
end
|
62
|
+
|
63
|
+
def perform_job
|
64
|
+
performer.new.perform
|
65
|
+
finished_time_sec = format('%.2f', Time.now - last_performed_at)
|
66
|
+
rescue StandardError => e
|
67
|
+
handle_job_fail(e, finished_time_sec)
|
68
|
+
else
|
69
|
+
handle_job_success(finished_time_sec)
|
70
|
+
ensure
|
71
|
+
save
|
72
|
+
end
|
73
|
+
|
74
|
+
def handle_job_fail(exception, finished_time_sec)
|
75
|
+
self.healthy = false
|
76
|
+
log_error "Finished #{performer} in #{finished_time_sec} seconds"\
|
77
|
+
"with error: #{exception.message}"
|
78
|
+
log_error exception.backtrace.join("\n")
|
79
|
+
end
|
80
|
+
|
81
|
+
def handle_job_success(finished_time_sec)
|
82
|
+
self.healthy = true
|
83
|
+
log "Finished #{performer} in #{finished_time_sec} seconds"
|
84
|
+
end
|
85
|
+
|
86
|
+
def log_error(message)
|
87
|
+
log(message, Logger::ERROR)
|
88
|
+
end
|
89
|
+
|
90
|
+
def log(message, severity = Logger::INFO)
|
67
91
|
@semaphore.synchronize do
|
68
|
-
logger.
|
69
|
-
job_logger.
|
92
|
+
logger.log severity, message
|
93
|
+
job_logger.log severity, message
|
70
94
|
end
|
71
95
|
end
|
72
96
|
|