recurrent 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/.zenflow +10 -0
- data/CHANGELOG.md +7 -0
- data/README.markdown +9 -0
- data/lib/recurrent.rb +1 -0
- data/lib/recurrent/configuration.rb +1 -1
- data/lib/recurrent/scheduler.rb +23 -23
- data/lib/recurrent/task.rb +36 -7
- data/lib/recurrent/task_collection.rb +72 -0
- data/lib/recurrent/version.rb +1 -1
- data/lib/recurrent/worker.rb +5 -5
- data/spec/scheduler_spec.rb +7 -26
- data/spec/task_collection_spec.rb +172 -0
- data/spec/task_spec.rb +60 -8
- metadata +9 -4
data/.gitignore
CHANGED
data/.zenflow
ADDED
data/CHANGELOG.md
ADDED
data/README.markdown
CHANGED
@@ -121,6 +121,15 @@ How long to wait before killing tasks that are still running.
|
|
121
121
|
|
122
122
|
configure.wait_for_running_tasks_on_exit_for = 10.seconds
|
123
123
|
|
124
|
+
###Limiting the number of concurrent tasks
|
125
|
+
To limit the number of tasks that run simultaneously:
|
126
|
+
|
127
|
+
configure.maximum_concurrent_tasks = 5
|
128
|
+
|
129
|
+
This will run up to the above number of tasks simultaneously, other tasks will wait until a slot opens up before executing. Tasks that run more frequently, e.g. once a minute, will have precedence over tasks that run once an hour or day etc.
|
130
|
+
|
131
|
+
|
132
|
+
|
124
133
|
Submitting an Issue
|
125
134
|
-------------------
|
126
135
|
We use the [GitHub issue tracker](http://github.com/zencoder/recurrent/issues) to track bugs and
|
data/lib/recurrent.rb
CHANGED
data/lib/recurrent/scheduler.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
module Recurrent
|
2
2
|
class Scheduler
|
3
3
|
|
4
|
-
attr_accessor :tasks, :logger
|
4
|
+
attr_accessor :tasks, :logger, :executing_tasks, :mutex
|
5
5
|
|
6
6
|
def initialize(task_file=nil)
|
7
|
-
@tasks =
|
7
|
+
@tasks = TaskCollection.new
|
8
8
|
identifier = "host:#{Socket.gethostname} pid:#{Process.pid}" rescue "pid:#{Process.pid}"
|
9
9
|
@logger = Logger.new(identifier)
|
10
|
+
@mutex = Mutex.new
|
11
|
+
@executing_tasks = 0
|
10
12
|
eval(File.read(task_file)) if task_file
|
11
13
|
end
|
12
14
|
|
@@ -87,30 +89,16 @@ module Recurrent
|
|
87
89
|
|
88
90
|
def every(frequency, key, options={}, &block)
|
89
91
|
logger.info "Adding Task: #{key}"
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
92
|
+
task = Task.new(:name => key,
|
93
|
+
:schedule => create_schedule(key, frequency, options[:start_time]),
|
94
|
+
:action => block,
|
95
|
+
:save => options[:save],
|
96
|
+
:logger => logger,
|
97
|
+
:scheduler => self)
|
98
|
+
@tasks.add_or_update(task)
|
95
99
|
logger.info "| #{key} added to Scheduler"
|
96
100
|
end
|
97
101
|
|
98
|
-
def next_task_time
|
99
|
-
tasks.map { |task| task.next_occurrence }.sort.first
|
100
|
-
end
|
101
|
-
|
102
|
-
def running_tasks
|
103
|
-
tasks.select do |task|
|
104
|
-
task.running?
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
def tasks_at_time(time)
|
109
|
-
tasks.select do |task|
|
110
|
-
task.next_occurrence == time
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
102
|
def use_saved_schedule_if_rules_match(saved_schedule, new_schedule)
|
115
103
|
if new_schedule.has_same_rules? saved_schedule
|
116
104
|
logger.info "| Schedule matches a saved schedule, using saved schedule."
|
@@ -121,6 +109,18 @@ module Recurrent
|
|
121
109
|
end
|
122
110
|
end
|
123
111
|
|
112
|
+
def increment_executing_tasks
|
113
|
+
mutex.synchronize do
|
114
|
+
@executing_tasks += 1
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def decrement_executing_tasks
|
119
|
+
mutex.synchronize do
|
120
|
+
@executing_tasks -= 1
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
124
|
def self.define_frequencies(*frequencies)
|
125
125
|
frequencies.each do |frequency|
|
126
126
|
method_name = frequency == :day ? :daily? : :"#{frequency}ly?"
|
data/lib/recurrent/task.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
module Recurrent
|
2
|
+
class TooManyExecutingTasks < StandardError; end
|
3
|
+
|
2
4
|
class Task
|
3
|
-
attr_accessor :action, :name, :logger, :save, :schedule, :thread
|
5
|
+
attr_accessor :action, :name, :logger, :save, :schedule, :thread, :scheduler
|
4
6
|
|
5
7
|
def initialize(options={})
|
6
8
|
@name = options[:name]
|
@@ -8,6 +10,7 @@ module Recurrent
|
|
8
10
|
@action = options[:action]
|
9
11
|
@save = options[:save]
|
10
12
|
@logger = options[:logger]
|
13
|
+
@scheduler = options[:scheduler]
|
11
14
|
Configuration.save_task_schedule.call(name, schedule) if Configuration.save_task_schedule
|
12
15
|
end
|
13
16
|
|
@@ -16,13 +19,15 @@ module Recurrent
|
|
16
19
|
@thread = Thread.new do
|
17
20
|
Thread.current["execution_time"] = execution_time
|
18
21
|
begin
|
19
|
-
if Configuration.
|
20
|
-
|
21
|
-
return_value = action.call(previous_value)
|
22
|
+
if Configuration.maximum_concurrent_tasks.present?
|
23
|
+
limit_execution_to_max_concurrency
|
22
24
|
else
|
23
|
-
|
25
|
+
call_action
|
24
26
|
end
|
25
|
-
|
27
|
+
rescue TooManyExecutingTasks
|
28
|
+
scheduler.decrement_executing_tasks
|
29
|
+
sleep(0.1)
|
30
|
+
retry
|
26
31
|
rescue => e
|
27
32
|
logger.warn("#{name} - #{e.message}")
|
28
33
|
logger.warn(e.backtrace)
|
@@ -30,10 +35,34 @@ module Recurrent
|
|
30
35
|
end
|
31
36
|
end
|
32
37
|
|
38
|
+
def limit_execution_to_max_concurrency
|
39
|
+
if (scheduler.increment_executing_tasks <= Configuration.maximum_concurrent_tasks) && task_is_next_in_line?
|
40
|
+
call_action
|
41
|
+
scheduler.decrement_executing_tasks
|
42
|
+
else
|
43
|
+
raise TooManyExecutingTasks
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def task_is_next_in_line?
|
48
|
+
self == scheduler.tasks.next_for_execution_at_time(Thread.current["execution_time"])
|
49
|
+
end
|
50
|
+
|
51
|
+
def call_action
|
52
|
+
if Configuration.load_task_return_value && action.arity == 1
|
53
|
+
previous_value = Configuration.load_task_return_value.call(name)
|
54
|
+
|
55
|
+
return_value = action.call(previous_value)
|
56
|
+
else
|
57
|
+
return_value = action.call
|
58
|
+
end
|
59
|
+
save_results(return_value) if save?
|
60
|
+
end
|
61
|
+
|
33
62
|
def handle_still_running(current_time)
|
34
63
|
logger.info "#{name}: Execution from #{thread['execution_time'].to_s(:seconds)} still running, aborting this execution."
|
35
64
|
if Configuration.handle_slow_task
|
36
|
-
Configuration.handle_slow_task.call(name, current_time)
|
65
|
+
Configuration.handle_slow_task.call(name, current_time, thread['execution_time'])
|
37
66
|
end
|
38
67
|
end
|
39
68
|
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Recurrent
|
2
|
+
class TaskCollection
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@mutex = Mutex.new
|
6
|
+
@tasks = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def add_or_update(new_task)
|
10
|
+
@mutex.synchronize do
|
11
|
+
old_task_index = @tasks.index {|task| task.name == new_task.name }
|
12
|
+
if old_task_index
|
13
|
+
@tasks[old_task_index].schedule = new_task.schedule
|
14
|
+
@tasks[old_task_index].action = new_task.action
|
15
|
+
else
|
16
|
+
@tasks << new_task
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def next_for_execution_at_time(time)
|
22
|
+
@mutex.synchronize do
|
23
|
+
tasks_running_at_time = @tasks.select {|task| task.running? && task.thread['execution_time'] == time }
|
24
|
+
TaskCollection.sort_by_frequency(tasks_running_at_time).first
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def next_execution_time
|
29
|
+
@mutex.synchronize do
|
30
|
+
@tasks.map { |task| task.next_occurrence }.sort.first
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def remove(name)
|
35
|
+
@mutex.synchronize do
|
36
|
+
@tasks.reject! {|task| task.name == name }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def running
|
41
|
+
@mutex.synchronize do
|
42
|
+
@tasks.select {|task| task.running? }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def scheduled_to_execute_at(time, opts={})
|
47
|
+
@mutex.synchronize do
|
48
|
+
current_tasks = @tasks.select {|task| task.next_occurrence == time }
|
49
|
+
if opts[:sort_by_frequency]
|
50
|
+
TaskCollection.sort_by_frequency(current_tasks)
|
51
|
+
else
|
52
|
+
current_tasks
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.sort_by_frequency(task_list)
|
58
|
+
task_list.sort_by do |task|
|
59
|
+
task.schedule.rrules.sort_by do |rule|
|
60
|
+
rule.frequency_in_seconds
|
61
|
+
end.first.frequency_in_seconds
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def method_missing(id, *args, &block)
|
67
|
+
@mutex.synchronize do
|
68
|
+
@tasks.send(id, *args, &block)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/recurrent/version.rb
CHANGED
data/lib/recurrent/worker.rb
CHANGED
@@ -40,8 +40,8 @@ module Recurrent
|
|
40
40
|
|
41
41
|
def execute
|
42
42
|
loop do
|
43
|
-
execution_time = scheduler.
|
44
|
-
tasks_to_execute = scheduler.
|
43
|
+
execution_time = scheduler.tasks.next_execution_time
|
44
|
+
tasks_to_execute = scheduler.tasks.scheduled_to_execute_at(execution_time, :sort_by_frequency => !!Configuration.maximum_concurrent_tasks)
|
45
45
|
|
46
46
|
wait_for_running_tasks && break if $exit
|
47
47
|
|
@@ -80,12 +80,12 @@ module Recurrent
|
|
80
80
|
end
|
81
81
|
|
82
82
|
def wait_for_running_tasks_for(seconds)
|
83
|
-
while scheduler.
|
83
|
+
while scheduler.tasks.running.any? do
|
84
84
|
logger.info "Killing running tasks in #{seconds.inspect}."
|
85
85
|
seconds -= 1
|
86
86
|
sleep(1)
|
87
87
|
if seconds == 0
|
88
|
-
scheduler.
|
88
|
+
scheduler.tasks.running.each do |task|
|
89
89
|
logger.info "Killing #{task.name}."
|
90
90
|
task.thread = nil unless task.thread.try(:kill).try(:alive?)
|
91
91
|
end
|
@@ -95,7 +95,7 @@ module Recurrent
|
|
95
95
|
end
|
96
96
|
|
97
97
|
def wait_for_running_tasks_indefinitely
|
98
|
-
if task = scheduler.
|
98
|
+
if task = scheduler.tasks.running.first
|
99
99
|
logger.info "Waiting for #{task.name} to finish."
|
100
100
|
task.thread.try(:join)
|
101
101
|
wait_for_running_tasks_indefinitely
|
data/spec/scheduler_spec.rb
CHANGED
@@ -195,36 +195,17 @@ module Recurrent
|
|
195
195
|
describe "#next_task_time" do
|
196
196
|
context "when there are multiple tasks" do
|
197
197
|
it "should return the soonest time at which a task is scheduled" do
|
198
|
-
task1 = stub('task1')
|
198
|
+
task1 = stub('task1', :name => :task1)
|
199
199
|
task1.stub(:next_occurrence).and_return(10.minutes.from_now)
|
200
|
-
task2 = stub('task2')
|
200
|
+
task2 = stub('task2', :name => :task2)
|
201
201
|
task2.stub(:next_occurrence).and_return(5.minutes.from_now)
|
202
|
-
task3 = stub('task3')
|
202
|
+
task3 = stub('task3', :name => :task3)
|
203
203
|
task3.stub(:next_occurrence).and_return(15.minutes.from_now)
|
204
204
|
schedule = Scheduler.new
|
205
|
-
schedule.tasks
|
206
|
-
schedule.tasks
|
207
|
-
schedule.tasks
|
208
|
-
schedule.
|
209
|
-
end
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
|
-
describe "#tasks_at_time" do
|
214
|
-
context "when there are multiple tasks" do
|
215
|
-
it "should return all the tasks whose next_occurrence is at the specified time" do
|
216
|
-
in_five_minutes = 5.minutes.from_now
|
217
|
-
task1 = stub('task1')
|
218
|
-
task1.stub(:next_occurrence).and_return(in_five_minutes)
|
219
|
-
task2 = stub('task2')
|
220
|
-
task2.stub(:next_occurrence).and_return(10.minutes.from_now)
|
221
|
-
task3 = stub('task3')
|
222
|
-
task3.stub(:next_occurrence).and_return(in_five_minutes)
|
223
|
-
schedule = Scheduler.new
|
224
|
-
schedule.tasks << task1
|
225
|
-
schedule.tasks << task2
|
226
|
-
schedule.tasks << task3
|
227
|
-
schedule.tasks_at_time(in_five_minutes).should =~ [task1, task3]
|
205
|
+
schedule.tasks.add_or_update(task1)
|
206
|
+
schedule.tasks.add_or_update(task2)
|
207
|
+
schedule.tasks.add_or_update(task3)
|
208
|
+
schedule.tasks.next_execution_time.should == task2.next_occurrence
|
228
209
|
end
|
229
210
|
end
|
230
211
|
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Recurrent
|
4
|
+
describe TaskCollection do
|
5
|
+
before(:all) do
|
6
|
+
Configuration.logging = "quiet"
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#add_or_update_task" do
|
10
|
+
before(:each) do
|
11
|
+
@tasks = TaskCollection.new
|
12
|
+
end
|
13
|
+
|
14
|
+
context "when adding a new task" do
|
15
|
+
before(:each) do
|
16
|
+
@task = Task.new(:name => :new_task)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "adds the task to the list of tasks" do
|
20
|
+
@tasks.size.should == 0
|
21
|
+
@tasks.add_or_update(@task)
|
22
|
+
@tasks.size.should == 1
|
23
|
+
@tasks.first.should == @task
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "when updating a task" do
|
28
|
+
before(:each) do
|
29
|
+
@original_frequency = Scheduler.new.create_schedule(:task, 5.seconds)
|
30
|
+
@original_action = proc { "I am the original task!" }
|
31
|
+
@original_task = Task.new(:name => :task,
|
32
|
+
:frequency => @original_frequency,
|
33
|
+
:action => @original_action)
|
34
|
+
|
35
|
+
@new_frequency = Scheduler.new.create_schedule(:task, 10.seconds)
|
36
|
+
@new_action = proc { "I am the new task!" }
|
37
|
+
@new_task = Task.new(:name => :task,
|
38
|
+
:frequency => @new_frequency,
|
39
|
+
:action => @new_action)
|
40
|
+
@tasks << @original_task
|
41
|
+
end
|
42
|
+
|
43
|
+
context "before updating the task" do
|
44
|
+
it "has one task" do
|
45
|
+
@tasks.size.should == 1
|
46
|
+
end
|
47
|
+
|
48
|
+
it "has the original task's action" do
|
49
|
+
@tasks.first.action.call.should == "I am the original task!"
|
50
|
+
end
|
51
|
+
|
52
|
+
it "has the original task's frequency" do
|
53
|
+
@tasks.first.schedule.should == @original_schedule
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "after updating the task" do
|
58
|
+
before(:each) do
|
59
|
+
@tasks.add_or_update(@new_task)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "has one task" do
|
63
|
+
@tasks.size.should == 1
|
64
|
+
end
|
65
|
+
|
66
|
+
it "has the new task's action" do
|
67
|
+
@tasks.first.action.call.should == "I am the new task!"
|
68
|
+
end
|
69
|
+
|
70
|
+
it "has the new task's frequency" do
|
71
|
+
@tasks.first.schedule.should == @new_schedule
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "#remove_task" do
|
78
|
+
context "A TaskCollection with 3 tasks" do
|
79
|
+
before(:each) do
|
80
|
+
@tasks = TaskCollection.new
|
81
|
+
@task1 = Task.new(:name => :task1)
|
82
|
+
@task2 = Task.new(:name => :task2)
|
83
|
+
@task3 = Task.new(:name => :task3)
|
84
|
+
@tasks.add_or_update(@task1)
|
85
|
+
@tasks.add_or_update(@task2)
|
86
|
+
@tasks.add_or_update(@task3)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "has 3 tasks" do
|
90
|
+
@tasks.size.should == 3
|
91
|
+
(@tasks | []).should == [@task1, @task2, @task3]
|
92
|
+
end
|
93
|
+
|
94
|
+
context "that removes a task" do
|
95
|
+
before(:each) do
|
96
|
+
@tasks.remove(:task2)
|
97
|
+
end
|
98
|
+
|
99
|
+
it "has 2 tasks" do
|
100
|
+
@tasks.size.should == 2
|
101
|
+
(@tasks | []).should == [@task1, @task3]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "#scheduled_to_execute_at" do
|
108
|
+
context "when there are multiple tasks" do
|
109
|
+
it "should return all the tasks whose next_occurrence is at the specified time" do
|
110
|
+
task_1_schedule = IceCube::Schedule.new(Time.utc(2012, 1, 10))
|
111
|
+
task_1_schedule.add_recurrence_rule(IceCube::Rule.minutely(10))
|
112
|
+
|
113
|
+
task_2_schedule = IceCube::Schedule.new(Time.utc(2012, 1, 10))
|
114
|
+
task_2_schedule.add_recurrence_rule(IceCube::Rule.minutely(5))
|
115
|
+
|
116
|
+
task_3_schedule = IceCube::Schedule.new(Time.utc(2012, 1, 10))
|
117
|
+
task_3_schedule.add_recurrence_rule(IceCube::Rule.minutely(1))
|
118
|
+
|
119
|
+
current_time = Time.utc(2012, 1, 10, 14, 4)
|
120
|
+
Timecop.freeze(current_time)
|
121
|
+
|
122
|
+
task1 = Task.new(:name => 'task1',
|
123
|
+
:schedule => task_1_schedule)
|
124
|
+
task2 = Task.new(:name => 'task2',
|
125
|
+
:schedule => task_2_schedule)
|
126
|
+
task3 = Task.new(:name => 'task3',
|
127
|
+
:schedule => task_3_schedule)
|
128
|
+
tasks = TaskCollection.new
|
129
|
+
tasks.add_or_update(task1)
|
130
|
+
tasks.add_or_update(task2)
|
131
|
+
tasks.add_or_update(task3)
|
132
|
+
|
133
|
+
tasks.scheduled_to_execute_at(Time.utc(2012, 1, 10, 14, 5)).should =~ [task2, task3]
|
134
|
+
Timecop.return
|
135
|
+
end
|
136
|
+
|
137
|
+
context "when :sort_by_frequency => true is passed as an option" do
|
138
|
+
it "should return the sorted by frequency, most frequent first" do
|
139
|
+
task_1_schedule = IceCube::Schedule.new(Time.utc(2012, 1, 10))
|
140
|
+
task_1_schedule.add_recurrence_rule(IceCube::Rule.minutely(10))
|
141
|
+
|
142
|
+
task_2_schedule = IceCube::Schedule.new(Time.utc(2012, 1, 10))
|
143
|
+
task_2_schedule.add_recurrence_rule(IceCube::Rule.minutely(5))
|
144
|
+
|
145
|
+
task_3_schedule = IceCube::Schedule.new(Time.utc(2012, 1, 10))
|
146
|
+
task_3_schedule.add_recurrence_rule(IceCube::Rule.minutely(1))
|
147
|
+
|
148
|
+
current_time = Time.utc(2012, 1, 10, 14, 4)
|
149
|
+
Timecop.freeze(current_time)
|
150
|
+
|
151
|
+
task1 = Task.new(:name => 'task1',
|
152
|
+
:schedule => task_1_schedule)
|
153
|
+
task2 = Task.new(:name => 'task2',
|
154
|
+
:schedule => task_2_schedule)
|
155
|
+
task3 = Task.new(:name => 'task3',
|
156
|
+
:schedule => task_3_schedule)
|
157
|
+
tasks = TaskCollection.new
|
158
|
+
tasks.add_or_update(task1)
|
159
|
+
tasks.add_or_update(task2)
|
160
|
+
tasks.add_or_update(task3)
|
161
|
+
|
162
|
+
first_task, second_task = *tasks.scheduled_to_execute_at(Time.utc(2012, 1, 10, 14, 5), :sort_by_frequency => true)
|
163
|
+
first_task.should == task3
|
164
|
+
second_task.should == task2
|
165
|
+
Timecop.return
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
data/spec/task_spec.rb
CHANGED
@@ -72,13 +72,6 @@ module Recurrent
|
|
72
72
|
Configuration.load_task_return_value = nil
|
73
73
|
end
|
74
74
|
end
|
75
|
-
|
76
|
-
context "load_task_return_value is not configured" do
|
77
|
-
|
78
|
-
end
|
79
|
-
|
80
|
-
|
81
|
-
|
82
75
|
end
|
83
76
|
|
84
77
|
describe "#next_occurrence" do
|
@@ -129,7 +122,7 @@ module Recurrent
|
|
129
122
|
|
130
123
|
it "logs that the task is still running and calls the method" do
|
131
124
|
@task.logger.should_receive(:info).with("handle_still_running_test: Execution from #{@executing_task_time.to_s(:seconds)} still running, aborting this execution.")
|
132
|
-
Configuration.handle_slow_task.should_receive(:call).with('handle_still_running_test', @current_time)
|
125
|
+
Configuration.handle_slow_task.should_receive(:call).with('handle_still_running_test', @current_time, @executing_task_time)
|
133
126
|
@task.handle_still_running(@current_time)
|
134
127
|
end
|
135
128
|
|
@@ -219,7 +212,66 @@ module Recurrent
|
|
219
212
|
t.running?.should be_false
|
220
213
|
end
|
221
214
|
end
|
215
|
+
end
|
222
216
|
|
217
|
+
describe "Restricting to a maximum number of concurrent tasks" do
|
218
|
+
before(:each) do
|
219
|
+
scheduler = Scheduler.new
|
220
|
+
schedule = IceCube::Schedule.new(Time.now.utc.beginning_of_day)
|
221
|
+
schedule.add_recurrence_rule IceCube::Rule.minutely(1)
|
222
|
+
@task1 = Task.new(:name => 'task1',
|
223
|
+
:scheduler => scheduler,
|
224
|
+
:schedule => schedule.clone,
|
225
|
+
:action => lambda { sleep(1)})
|
226
|
+
@task2 = Task.new(:name => 'task2',
|
227
|
+
:scheduler => scheduler,
|
228
|
+
:schedule => schedule.clone,
|
229
|
+
:action => lambda { sleep(1) })
|
230
|
+
|
231
|
+
scheduler.tasks.add_or_update(@task1)
|
232
|
+
scheduler.tasks.add_or_update(@task2)
|
233
|
+
end
|
234
|
+
|
235
|
+
describe "when there is no concurrent task limit set" do
|
236
|
+
it "should run all tasks at the same time" do
|
237
|
+
current_time = Time.now
|
238
|
+
[@task1, @task2].each do |task|
|
239
|
+
task.execute(current_time)
|
240
|
+
end
|
241
|
+
|
242
|
+
[@task1, @task2].each {|task| task.thread.join }
|
243
|
+
|
244
|
+
finished_at = Time.now
|
245
|
+
elapsed = finished_at - current_time
|
246
|
+
|
247
|
+
elapsed.round.should == 1.seconds
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
describe "when there is a maximum concurrency limit set" do
|
252
|
+
before(:each) do
|
253
|
+
Configuration.maximum_concurrent_tasks = 1
|
254
|
+
end
|
255
|
+
|
256
|
+
it "should run only up to the number of tasks specified at once" do
|
257
|
+
current_time = Time.now
|
258
|
+
[@task1, @task2].each do |task|
|
259
|
+
task.execute(current_time)
|
260
|
+
end
|
261
|
+
|
262
|
+
[@task1, @task2].each {|task| task.thread.join }
|
263
|
+
|
264
|
+
finished_at = Time.now
|
265
|
+
elapsed = finished_at - current_time
|
266
|
+
|
267
|
+
elapsed.round.should == 2.seconds
|
268
|
+
end
|
269
|
+
|
270
|
+
after(:each) do
|
271
|
+
Configuration.maximum_concurrent_tasks = nil
|
272
|
+
end
|
273
|
+
end
|
223
274
|
end
|
275
|
+
|
224
276
|
end
|
225
277
|
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: 19
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 3
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.3.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:
|
18
|
+
date: 2012-01-28 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: ice_cube
|
@@ -158,6 +158,8 @@ files:
|
|
158
158
|
- .autotest
|
159
159
|
- .gitignore
|
160
160
|
- .rspec
|
161
|
+
- .zenflow
|
162
|
+
- CHANGELOG.md
|
161
163
|
- Gemfile
|
162
164
|
- LICENSE
|
163
165
|
- README.markdown
|
@@ -170,12 +172,14 @@ files:
|
|
170
172
|
- lib/recurrent/logger.rb
|
171
173
|
- lib/recurrent/scheduler.rb
|
172
174
|
- lib/recurrent/task.rb
|
175
|
+
- lib/recurrent/task_collection.rb
|
173
176
|
- lib/recurrent/version.rb
|
174
177
|
- lib/recurrent/worker.rb
|
175
178
|
- recurrent.gemspec
|
176
179
|
- spec/logger_spec.rb
|
177
180
|
- spec/scheduler_spec.rb
|
178
181
|
- spec/spec_helper.rb
|
182
|
+
- spec/task_collection_spec.rb
|
179
183
|
- spec/task_spec.rb
|
180
184
|
- spec/worker_spec.rb
|
181
185
|
homepage: http://github.com/zencoder/recurrent
|
@@ -215,5 +219,6 @@ test_files:
|
|
215
219
|
- spec/logger_spec.rb
|
216
220
|
- spec/scheduler_spec.rb
|
217
221
|
- spec/spec_helper.rb
|
222
|
+
- spec/task_collection_spec.rb
|
218
223
|
- spec/task_spec.rb
|
219
224
|
- spec/worker_spec.rb
|