recurrent 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.markdown +1 -1
- data/bin/recurrent +1 -2
- data/lib/recurrent/configuration.rb +1 -1
- data/lib/recurrent/ice_cube_extensions.rb +16 -1
- data/lib/recurrent/scheduler.rb +23 -37
- data/lib/recurrent/task.rb +14 -4
- data/lib/recurrent/version.rb +1 -1
- data/lib/recurrent/worker.rb +2 -1
- data/spec/scheduler_spec.rb +22 -27
- data/spec/task_spec.rb +49 -15
- data/spec/worker_spec.rb +18 -0
- metadata +4 -4
data/README.markdown
CHANGED
@@ -35,7 +35,7 @@ Use the every method to create tasks.
|
|
35
35
|
end
|
36
36
|
|
37
37
|
####Frequency
|
38
|
-
The first argument is the frequency, it can be an integer in seconds, including ActiveSupport style 2.hours, 3.weeks etc, or an [IceCube::
|
38
|
+
The first argument is the frequency, it can be an integer in seconds, including ActiveSupport style 2.hours, 3.weeks etc, or an [IceCube::Schedule](http://seejohncode.com/ice_cube/)
|
39
39
|
####Name
|
40
40
|
The second argument is the name of the task
|
41
41
|
####Options
|
data/bin/recurrent
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
$: << File.expand_path("#{File.dirname(__FILE__)}/../lib")
|
3
2
|
require 'rubygems'
|
4
3
|
require 'trollop'
|
5
4
|
|
@@ -16,5 +15,5 @@ begin
|
|
16
15
|
rescue LoadError
|
17
16
|
require 'recurrent'
|
18
17
|
end
|
19
|
-
|
18
|
+
$0 = "recurrent:worker:started-#{Time.now.to_s(:logging)}"
|
20
19
|
Recurrent::Worker.new(opts).start
|
@@ -19,7 +19,7 @@ module Recurrent
|
|
19
19
|
")
|
20
20
|
end
|
21
21
|
end
|
22
|
-
block_accessor :logger, :save_task_schedule, :load_task_schedule, :save_task_return_value, :process_locking, :handle_slow_task
|
22
|
+
block_accessor :logger, :save_task_schedule, :load_task_schedule, :save_task_return_value, :load_task_return_value, :process_locking, :handle_slow_task, :setup
|
23
23
|
|
24
24
|
end
|
25
25
|
end
|
@@ -1,5 +1,12 @@
|
|
1
1
|
module IceCube
|
2
2
|
class Rule
|
3
|
+
|
4
|
+
def ==(another_rule)
|
5
|
+
['@interval', '@validation_types', '@validations'].all? do |variable|
|
6
|
+
self.instance_variable_get(variable) == another_rule.instance_variable_get(variable)
|
7
|
+
end && self.class.name == another_rule.class.name
|
8
|
+
end
|
9
|
+
|
3
10
|
def frequency_in_seconds
|
4
11
|
rule_type = self.class
|
5
12
|
if rule_type == IceCube::YearlyRule
|
@@ -19,4 +26,12 @@ module IceCube
|
|
19
26
|
end
|
20
27
|
end
|
21
28
|
end
|
22
|
-
|
29
|
+
|
30
|
+
class Schedule
|
31
|
+
attr_writer :start_date
|
32
|
+
|
33
|
+
def has_same_rules?(other_schedule)
|
34
|
+
self.rrules == other_schedule.rrules
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/recurrent/scheduler.rb
CHANGED
@@ -41,50 +41,26 @@ module Recurrent
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def create_schedule(name, frequency, start_time=nil)
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
frequency_in_seconds = rule.frequency_in_seconds
|
44
|
+
saved_schedule = Configuration.load_task_schedule.call(name) if Configuration.load_task_schedule
|
45
|
+
new_schedule = frequency.is_a?(IceCube::Schedule) ? frequency : create_schedule_from_frequency(frequency, start_time)
|
46
|
+
if saved_schedule
|
47
|
+
use_saved_schedule_if_rules_match(saved_schedule, new_schedule)
|
49
48
|
else
|
50
|
-
|
51
|
-
rule = create_rule_from_frequency(frequency)
|
52
|
-
logger.info "| IceCube Rule created: #{rule.to_s}"
|
53
|
-
frequency_in_seconds = frequency
|
49
|
+
new_schedule
|
54
50
|
end
|
55
|
-
|
51
|
+
end
|
52
|
+
|
53
|
+
def create_schedule_from_frequency(frequency, start_time=nil)
|
54
|
+
logger.info "| Frequency is an integer: #{frequency}"
|
55
|
+
rule = create_rule_from_frequency(frequency)
|
56
|
+
logger.info "| IceCube Rule created: #{rule.to_s}"
|
57
|
+
frequency_in_seconds = frequency
|
58
|
+
start_time ||= derive_start_time_from_frequency(frequency_in_seconds)
|
56
59
|
schedule = IceCube::Schedule.new(start_time)
|
57
60
|
schedule.add_recurrence_rule rule
|
58
|
-
logger.info "| schedule created"
|
59
61
|
schedule
|
60
62
|
end
|
61
63
|
|
62
|
-
def derive_start_time(name, frequency)
|
63
|
-
logger.info "| No start time provided, deriving one."
|
64
|
-
if Configuration.load_task_schedule
|
65
|
-
logger.info "| Attempting to derive from saved schedule"
|
66
|
-
derive_start_time_from_saved_schedule(name, frequency)
|
67
|
-
else
|
68
|
-
derive_start_time_from_frequency(frequency)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def derive_start_time_from_saved_schedule(name, frequency)
|
73
|
-
saved_schedule = Configuration.load_task_schedule.call(name)
|
74
|
-
if saved_schedule
|
75
|
-
logger.info "| Saved schedule found"
|
76
|
-
if saved_schedule.rrules.first.frequency_in_seconds == frequency
|
77
|
-
logger.info "| Saved schedule frequency matches, setting start time to saved schedules next occurrence: #{saved_schedule.next_occurrence.to_s(:seconds)}"
|
78
|
-
saved_schedule.next_occurrence
|
79
|
-
else
|
80
|
-
logger.info "| Schedule frequency does not match saved schedule frequency"
|
81
|
-
derive_start_time_from_frequency(frequency)
|
82
|
-
end
|
83
|
-
else
|
84
|
-
derive_start_time_from_frequency(frequency)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
64
|
def derive_start_time_from_frequency(frequency)
|
89
65
|
logger.info "| Deriving start time from frequency"
|
90
66
|
current_time = Time.now
|
@@ -135,6 +111,16 @@ module Recurrent
|
|
135
111
|
end
|
136
112
|
end
|
137
113
|
|
114
|
+
def use_saved_schedule_if_rules_match(saved_schedule, new_schedule)
|
115
|
+
if new_schedule.has_same_rules? saved_schedule
|
116
|
+
logger.info "| Schedule matches a saved schedule, using saved schedule."
|
117
|
+
saved_schedule.start_date = saved_schedule.next_occurrence
|
118
|
+
saved_schedule
|
119
|
+
else
|
120
|
+
new_schedule
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
138
124
|
def self.define_frequencies(*frequencies)
|
139
125
|
frequencies.each do |frequency|
|
140
126
|
method_name = frequency == :day ? :daily? : :"#{frequency}ly?"
|
data/lib/recurrent/task.rb
CHANGED
@@ -15,8 +15,18 @@ module Recurrent
|
|
15
15
|
return handle_still_running(execution_time) if running?
|
16
16
|
@thread = Thread.new do
|
17
17
|
Thread.current["execution_time"] = execution_time
|
18
|
-
|
19
|
-
|
18
|
+
begin
|
19
|
+
if Configuration.load_task_return_value && action.arity == 1
|
20
|
+
previous_value = Configuration.load_task_return_value.call(name)
|
21
|
+
return_value = action.call(previous_value)
|
22
|
+
else
|
23
|
+
return_value = action.call
|
24
|
+
end
|
25
|
+
save_results(return_value) if save?
|
26
|
+
rescue => e
|
27
|
+
logger.warn("#{name} - #{e.message}")
|
28
|
+
logger.warn(e.backtrace)
|
29
|
+
end
|
20
30
|
end
|
21
31
|
end
|
22
32
|
|
@@ -28,8 +38,8 @@ module Recurrent
|
|
28
38
|
end
|
29
39
|
|
30
40
|
def next_occurrence
|
31
|
-
|
32
|
-
|
41
|
+
occurrence = schedule.next_occurrence
|
42
|
+
schedule.start_date = occurrence
|
33
43
|
end
|
34
44
|
|
35
45
|
def save?
|
data/lib/recurrent/version.rb
CHANGED
data/lib/recurrent/worker.rb
CHANGED
@@ -4,6 +4,7 @@ module Recurrent
|
|
4
4
|
attr_accessor :scheduler, :logger
|
5
5
|
|
6
6
|
def initialize(options={})
|
7
|
+
Configuration.setup.call if Configuration.setup
|
7
8
|
file = options[:file]
|
8
9
|
@scheduler = Scheduler.new(file)
|
9
10
|
if options[:every]
|
@@ -61,7 +62,7 @@ module Recurrent
|
|
61
62
|
lock_established = nil
|
62
63
|
until lock_established
|
63
64
|
break if $exit
|
64
|
-
lock_established = Configuration.process_locking.call do
|
65
|
+
lock_established = Configuration.process_locking.call(*scheduler.tasks.map(&:name)) do
|
65
66
|
execute
|
66
67
|
end
|
67
68
|
break if $exit
|
data/spec/scheduler_spec.rb
CHANGED
@@ -55,17 +55,16 @@ module Recurrent
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
-
describe "create_schedule" do
|
59
|
-
context "when frequency is an IceCube
|
60
|
-
|
58
|
+
describe "#create_schedule" do
|
59
|
+
context "when frequency is an IceCube::Schedule" do
|
60
|
+
before :each do
|
61
61
|
rule = IceCube::Rule.daily(1)
|
62
|
-
@
|
63
|
-
|
64
|
-
it "should be a schedule" do
|
65
|
-
subject.class.should == IceCube::Schedule
|
62
|
+
@schedule = IceCube::Schedule.new(Time.now)
|
63
|
+
@schedule.add_recurrence_rule rule
|
66
64
|
end
|
67
|
-
|
68
|
-
|
65
|
+
|
66
|
+
it "returns the schedule" do
|
67
|
+
@scheduler.create_schedule(:test, @schedule).should == @schedule
|
69
68
|
end
|
70
69
|
end
|
71
70
|
|
@@ -83,7 +82,7 @@ module Recurrent
|
|
83
82
|
|
84
83
|
context "when start time is not provided" do
|
85
84
|
it "should derive its own start time" do
|
86
|
-
@scheduler.should_receive(:
|
85
|
+
@scheduler.should_receive(:derive_start_time_from_frequency).with(1.day)
|
87
86
|
@scheduler.create_schedule(:test, 1.day)
|
88
87
|
end
|
89
88
|
end
|
@@ -96,7 +95,7 @@ module Recurrent
|
|
96
95
|
end
|
97
96
|
end
|
98
97
|
|
99
|
-
describe "derive_start_time_from_frequency" do
|
98
|
+
describe "#derive_start_time_from_frequency" do
|
100
99
|
context "when the current time is 11:35:12 am on July 26th, 2011" do
|
101
100
|
before(:all) do
|
102
101
|
Timecop.freeze(Time.local(2011, 7, 26, 11, 35, 12))
|
@@ -150,30 +149,26 @@ module Recurrent
|
|
150
149
|
end
|
151
150
|
end
|
152
151
|
|
153
|
-
describe "
|
152
|
+
describe "A schedule has a saved schedule" do
|
154
153
|
before(:all) do
|
155
154
|
@scheduler = Scheduler.new
|
156
155
|
Configuration.load_task_schedule do |name|
|
157
|
-
|
158
|
-
|
159
|
-
|
156
|
+
if name == :test
|
157
|
+
current_time = Time.new
|
158
|
+
current_time.change(:sec => 0, :usec => 0)
|
159
|
+
schedule = IceCube::Schedule.new(current_time)
|
160
|
+
schedule.add_recurrence_rule IceCube::SecondlyRule.new(10)
|
161
|
+
schedule
|
162
|
+
end
|
160
163
|
end
|
161
164
|
end
|
162
165
|
|
163
166
|
describe "a schedule being created with a saved schedule with the same name and frequency" do
|
164
|
-
it "
|
165
|
-
|
166
|
-
@scheduler.create_schedule(:test, 10.seconds)
|
167
|
-
|
168
|
-
|
169
|
-
describe "the created schedule's start time" do
|
170
|
-
it "should be the next occurrence of the saved schedule" do
|
171
|
-
saved_schedule = Configuration.load_task_schedule.call(:test)
|
172
|
-
created_schedule = @scheduler.create_schedule(:test, 10.seconds)
|
173
|
-
created_schedule.start_date.to_s(:seconds).should == saved_schedule.next_occurrence.to_s(:seconds)
|
174
|
-
end
|
167
|
+
it "should return the saved schedule with its start time updated to be its next_occurrence" do
|
168
|
+
saved_schedule = Configuration.load_task_schedule.call(:test)
|
169
|
+
created_schedule = @scheduler.create_schedule(:test, 10.seconds)
|
170
|
+
created_schedule.start_date.to_s(:seconds).should == saved_schedule.next_occurrence.to_s(:seconds)
|
175
171
|
end
|
176
|
-
|
177
172
|
end
|
178
173
|
|
179
174
|
describe "a schedule being created with a saved schedule with the same name and different frequency" do
|
data/spec/task_spec.rb
CHANGED
@@ -10,7 +10,7 @@ module Recurrent
|
|
10
10
|
before :each do
|
11
11
|
@executing_task_time = 5.minutes.ago
|
12
12
|
@current_time = Time.now
|
13
|
-
@task = Task.new :name => 'execute test', :logger => Logger.new('some identifier')
|
13
|
+
@task = Task.new :name => 'execute test', :logger => Logger.new('some identifier'), :action => proc { 'blah' }
|
14
14
|
end
|
15
15
|
|
16
16
|
context "The task is still running a previous execution" do
|
@@ -40,30 +40,64 @@ module Recurrent
|
|
40
40
|
@task.thread['execution_time'].should == @current_time
|
41
41
|
end
|
42
42
|
|
43
|
-
|
44
|
-
|
45
|
-
|
43
|
+
context "load_task_return_value is configured" do
|
44
|
+
before :each do
|
45
|
+
Configuration.load_task_return_value do
|
46
|
+
"testing"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "the action doesn't take an argument" do
|
51
|
+
it "calls the action with a nil argument" do
|
52
|
+
@task.action.should_receive(:call)
|
53
|
+
@task.execute(@current_time)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "the action takes an argument" do
|
58
|
+
before :each do
|
59
|
+
@task.action = proc { |previous_value| "derp" }
|
60
|
+
end
|
61
|
+
|
62
|
+
it "loads the task return value and calls the action with it as an argument" do
|
63
|
+
@task.action.should_receive(:call).with("testing")
|
64
|
+
@task.execute(@current_time)
|
65
|
+
end
|
66
|
+
|
67
|
+
after :each do
|
68
|
+
@task.action = proc { "derp" }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
after :each do
|
72
|
+
Configuration.load_task_return_value = nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context "load_task_return_value is not configured" do
|
77
|
+
|
46
78
|
end
|
79
|
+
|
80
|
+
|
81
|
+
|
47
82
|
end
|
48
83
|
|
49
84
|
describe "#next_occurrence" do
|
50
85
|
context "a task that occurs ever 10 seconds and has just occurred" do
|
51
|
-
|
52
|
-
current_time = Time.new
|
53
|
-
current_time.change(:sec => 0, :usec => 0)
|
54
|
-
Timecop.freeze(current_time)
|
55
|
-
Task.new(:name => :test, :schedule => Scheduler.new.create_schedule(:test, 10.seconds, current_time))
|
86
|
+
before :each do
|
87
|
+
@current_time = Time.new
|
88
|
+
@current_time.change(:sec => 0, :usec => 0)
|
89
|
+
Timecop.freeze(@current_time)
|
90
|
+
@task = Task.new(:name => :test, :schedule => Scheduler.new.create_schedule(:test, 10.seconds, @current_time))
|
56
91
|
end
|
57
92
|
|
58
93
|
it "should occur 10 seconds from now" do
|
59
|
-
|
94
|
+
@task.next_occurrence.should == 10.seconds.from_now
|
60
95
|
end
|
61
96
|
|
62
|
-
it "
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
subject.next_occurrence
|
97
|
+
it "update the start time of the task to the time of this occurrence because IceCube::Schedule#next_occurrence gets progressively slower the farther back the start time is" do
|
98
|
+
@task.schedule.start_date.should == @current_time
|
99
|
+
@task.next_occurrence.should == 10.seconds.from_now
|
100
|
+
@task.schedule.start_date.should == 10.seconds.from_now
|
67
101
|
end
|
68
102
|
|
69
103
|
after(:each) do
|
data/spec/worker_spec.rb
CHANGED
@@ -41,5 +41,23 @@ module Recurrent
|
|
41
41
|
Timecop.return
|
42
42
|
end
|
43
43
|
end
|
44
|
+
|
45
|
+
describe "setup" do
|
46
|
+
before :each do
|
47
|
+
Configuration.setup do
|
48
|
+
@setup = true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it "runs any configured setup when a worker is created" do
|
53
|
+
@setup.should == nil
|
54
|
+
Worker.new
|
55
|
+
@setup.should == true
|
56
|
+
end
|
57
|
+
|
58
|
+
after :each do
|
59
|
+
Configuration.setup = nil
|
60
|
+
end
|
61
|
+
end
|
44
62
|
end
|
45
63
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: recurrent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 2
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Adam Kittelson
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-08-
|
18
|
+
date: 2011-08-13 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: ice_cube
|