recurrent 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +0 -1
- data/.rvmrc +1 -0
- data/README.markdown +1 -1
- data/bin/recurrent +3 -0
- data/lib/recurrent/configuration.rb +2 -2
- data/lib/recurrent/scheduler.rb +2 -1
- data/lib/recurrent/task.rb +29 -27
- data/lib/recurrent/version.rb +1 -1
- data/lib/recurrent/worker.rb +4 -1
- data/spec/task_spec.rb +8 -12
- metadata +6 -5
data/.gitignore
CHANGED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm 1.8.7@recurrent
|
data/README.markdown
CHANGED
@@ -126,7 +126,7 @@ To limit the number of tasks that run simultaneously:
|
|
126
126
|
|
127
127
|
configure.maximum_concurrent_tasks = 5
|
128
128
|
|
129
|
-
This will run up to the above number of tasks simultaneously,
|
129
|
+
This will run up to the above number of tasks simultaneously, if there are too many tasks running it will skip any extra tasks for that run. Tasks that run less frequently are prioritized. For example if you can run a maximum of 5 tasks, 4 of which are currently running, and you are now scheduled to run two tasks, one that runs once an hour, and one that runs once a minute, the hourly task will get the slot and the minutely task will not run until its next scheduled run.
|
130
130
|
|
131
131
|
|
132
132
|
|
data/bin/recurrent
CHANGED
@@ -8,6 +8,9 @@ opts = Trollop::options do
|
|
8
8
|
opt :ruby, "Ruby code to execute", :type => :string
|
9
9
|
opt :system, "Command to send to the command line", :type => :string
|
10
10
|
opt :name, "Name of the task", :default => 'command_line'
|
11
|
+
opt :maximum_concurrent_tasks, "Maximum number of concurrent tasks to run", :type => :integer
|
12
|
+
opt :pool_size, "Size of database connection pool", :type => :integer
|
13
|
+
opt :locker_pool_size, "Size of locker database connection pool (should be >= maximum_concurrent_tasks)", :type => :integer
|
11
14
|
end
|
12
15
|
|
13
16
|
begin
|
@@ -3,7 +3,7 @@ module Recurrent
|
|
3
3
|
|
4
4
|
class << self
|
5
5
|
|
6
|
-
attr_accessor :logging, :wait_for_running_tasks_on_exit_for, :maximum_concurrent_tasks
|
6
|
+
attr_accessor :logging, :wait_for_running_tasks_on_exit_for, :maximum_concurrent_tasks, :pool_size, :locker_pool_size
|
7
7
|
|
8
8
|
def self.block_accessor(*fields)
|
9
9
|
fields.each do |field|
|
@@ -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, :load_task_return_value, :process_locking, :handle_slow_task, :setup
|
22
|
+
block_accessor :logger, :save_task_schedule, :load_task_schedule, :save_task_return_value, :load_task_return_value, :process_locking, :task_locking, :handle_slow_task, :setup
|
23
23
|
|
24
24
|
end
|
25
25
|
end
|
data/lib/recurrent/scheduler.rb
CHANGED
@@ -94,7 +94,8 @@ module Recurrent
|
|
94
94
|
:action => block,
|
95
95
|
:save => options[:save],
|
96
96
|
:logger => logger,
|
97
|
-
:scheduler => self
|
97
|
+
:scheduler => self,
|
98
|
+
:disable_task_locking => options[:disable_task_locking])
|
98
99
|
@tasks.add_or_update(task)
|
99
100
|
logger.info "| #{key} added to Scheduler"
|
100
101
|
end
|
data/lib/recurrent/task.rb
CHANGED
@@ -11,52 +11,54 @@ module Recurrent
|
|
11
11
|
@save = options[:save]
|
12
12
|
@logger = options[:logger]
|
13
13
|
@scheduler = options[:scheduler]
|
14
|
+
@disable_task_locking = options[:disable_task_locking]
|
14
15
|
Configuration.save_task_schedule.call(name, schedule) if Configuration.save_task_schedule
|
15
16
|
end
|
16
17
|
|
17
18
|
def execute(execution_time)
|
18
19
|
return handle_still_running(execution_time) if running?
|
20
|
+
return if Configuration.maximum_concurrent_tasks.present? && (scheduler.executing_tasks >= Configuration.maximum_concurrent_tasks)
|
19
21
|
@thread = Thread.new do
|
20
22
|
Thread.current["execution_time"] = execution_time
|
23
|
+
scheduler && scheduler.increment_executing_tasks
|
21
24
|
begin
|
22
|
-
|
23
|
-
limit_execution_to_max_concurrency
|
24
|
-
else
|
25
|
-
call_action
|
26
|
-
end
|
27
|
-
rescue TooManyExecutingTasks
|
28
|
-
scheduler.decrement_executing_tasks
|
29
|
-
sleep(0.1)
|
30
|
-
retry
|
25
|
+
call_action(execution_time)
|
31
26
|
rescue => e
|
32
27
|
logger.warn("#{name} - #{e.message}")
|
33
28
|
logger.warn(e.backtrace)
|
29
|
+
ensure
|
30
|
+
scheduler && scheduler.decrement_executing_tasks
|
34
31
|
end
|
35
32
|
end
|
36
33
|
end
|
37
34
|
|
38
|
-
def
|
39
|
-
if
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
35
|
+
def call_action(execution_time=nil)
|
36
|
+
if Configuration.task_locking && !@disable_task_locking
|
37
|
+
logger.info "#{name} - #{execution_time.to_s(:seconds)}: attempting to establish lock"
|
38
|
+
lock_established = Configuration.task_locking.call(name) do
|
39
|
+
if Configuration.load_task_return_value && action.arity == 1
|
40
|
+
previous_value = Configuration.load_task_return_value.call(name)
|
50
41
|
|
51
|
-
|
52
|
-
|
53
|
-
|
42
|
+
return_value = action.call(previous_value)
|
43
|
+
else
|
44
|
+
return_value = action.call
|
45
|
+
end
|
46
|
+
save_results(return_value) if save?
|
54
47
|
|
55
|
-
|
48
|
+
# If a task finishes quickly hold the lock for a few seconds to avoid releasing it before other processes try to pick up the task
|
49
|
+
sleep(1) until Time.now - execution_time > 5 if execution_time
|
50
|
+
end
|
51
|
+
logger.info "#{name} - #{execution_time.to_s(:seconds)}: locked by another process" unless lock_established
|
56
52
|
else
|
57
|
-
|
53
|
+
if Configuration.load_task_return_value && action.arity == 1
|
54
|
+
previous_value = Configuration.load_task_return_value.call(name)
|
55
|
+
|
56
|
+
return_value = action.call(previous_value)
|
57
|
+
else
|
58
|
+
return_value = action.call
|
59
|
+
end
|
60
|
+
save_results(return_value) if save?
|
58
61
|
end
|
59
|
-
save_results(return_value) if save?
|
60
62
|
end
|
61
63
|
|
62
64
|
def handle_still_running(current_time)
|
data/lib/recurrent/version.rb
CHANGED
data/lib/recurrent/worker.rb
CHANGED
@@ -4,6 +4,9 @@ module Recurrent
|
|
4
4
|
attr_accessor :scheduler, :logger
|
5
5
|
|
6
6
|
def initialize(options={})
|
7
|
+
Configuration.maximum_concurrent_tasks = options[:maximum_concurrent_tasks]
|
8
|
+
Configuration.pool_size = options[:pool_size]
|
9
|
+
Configuration.locker_pool_size = options[:locker_pool_size]
|
7
10
|
Configuration.setup.call if Configuration.setup
|
8
11
|
file = options[:file]
|
9
12
|
@scheduler = Scheduler.new(file)
|
@@ -41,7 +44,7 @@ module Recurrent
|
|
41
44
|
def execute
|
42
45
|
loop do
|
43
46
|
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)
|
47
|
+
tasks_to_execute = scheduler.tasks.scheduled_to_execute_at(execution_time, :sort_by_frequency => !!Configuration.maximum_concurrent_tasks).reverse
|
45
48
|
|
46
49
|
wait_for_running_tasks && break if $exit
|
47
50
|
|
data/spec/task_spec.rb
CHANGED
@@ -222,14 +222,15 @@ module Recurrent
|
|
222
222
|
@task1 = Task.new(:name => 'task1',
|
223
223
|
:scheduler => scheduler,
|
224
224
|
:schedule => schedule.clone,
|
225
|
-
:action => lambda { sleep(1)})
|
225
|
+
:action => lambda { sleep(1); $tasks_executed += 1 })
|
226
226
|
@task2 = Task.new(:name => 'task2',
|
227
227
|
:scheduler => scheduler,
|
228
228
|
:schedule => schedule.clone,
|
229
|
-
:action => lambda { sleep(1) })
|
229
|
+
:action => lambda { sleep(1); $tasks_executed += 1 })
|
230
230
|
|
231
231
|
scheduler.tasks.add_or_update(@task1)
|
232
232
|
scheduler.tasks.add_or_update(@task2)
|
233
|
+
$tasks_executed = 0
|
233
234
|
end
|
234
235
|
|
235
236
|
describe "when there is no concurrent task limit set" do
|
@@ -241,10 +242,7 @@ module Recurrent
|
|
241
242
|
|
242
243
|
[@task1, @task2].each {|task| task.thread.join }
|
243
244
|
|
244
|
-
|
245
|
-
elapsed = finished_at - current_time
|
246
|
-
|
247
|
-
elapsed.round.should == 1.seconds
|
245
|
+
$tasks_executed.should == 2
|
248
246
|
end
|
249
247
|
end
|
250
248
|
|
@@ -253,18 +251,16 @@ module Recurrent
|
|
253
251
|
Configuration.maximum_concurrent_tasks = 1
|
254
252
|
end
|
255
253
|
|
256
|
-
it "
|
254
|
+
it "throw away tasks if there are too many tasks running" do
|
257
255
|
current_time = Time.now
|
256
|
+
|
258
257
|
[@task1, @task2].each do |task|
|
259
258
|
task.execute(current_time)
|
260
259
|
end
|
261
260
|
|
262
|
-
[@task1, @task2].each {|task| task.thread.join }
|
263
|
-
|
264
|
-
finished_at = Time.now
|
265
|
-
elapsed = finished_at - current_time
|
261
|
+
[@task1, @task2].each {|task| task.thread.try(:join) }
|
266
262
|
|
267
|
-
|
263
|
+
$tasks_executed.should == 1
|
268
264
|
end
|
269
265
|
|
270
266
|
after(:each) do
|
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: 15
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 4
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.4.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: 2012-
|
18
|
+
date: 2012-06-14 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: ice_cube
|
@@ -158,6 +158,7 @@ files:
|
|
158
158
|
- .autotest
|
159
159
|
- .gitignore
|
160
160
|
- .rspec
|
161
|
+
- .rvmrc
|
161
162
|
- .zenflow
|
162
163
|
- CHANGELOG.md
|
163
164
|
- Gemfile
|
@@ -211,7 +212,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
211
212
|
requirements: []
|
212
213
|
|
213
214
|
rubyforge_project:
|
214
|
-
rubygems_version: 1.8.
|
215
|
+
rubygems_version: 1.8.24
|
215
216
|
signing_key:
|
216
217
|
specification_version: 3
|
217
218
|
summary: Task scheduler that doesn't need to bootstrap your Rails environment every time it executes a task the way running a rake task via cron does.
|