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 CHANGED
@@ -1,6 +1,5 @@
1
1
  .DS_Store
2
2
  coverage
3
- .rvmrc
4
3
  Gemfile.lock
5
4
  .bundle
6
5
  *.gem
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm 1.8.7@recurrent
@@ -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, 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.
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
 
@@ -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
@@ -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
@@ -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
- if Configuration.maximum_concurrent_tasks.present?
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 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
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
- def call_action
52
- if Configuration.load_task_return_value && action.arity == 1
53
- previous_value = Configuration.load_task_return_value.call(name)
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
- return_value = action.call(previous_value)
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
- return_value = action.call
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)
@@ -1,3 +1,3 @@
1
1
  module Recurrent
2
- VERSION = '0.3.0'
2
+ VERSION = '0.4.0'
3
3
  end
@@ -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
 
@@ -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
- finished_at = Time.now
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 "should run only up to the number of tasks specified at once" do
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
- elapsed.round.should == 2.seconds
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: 19
4
+ hash: 15
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 3
8
+ - 4
9
9
  - 0
10
- version: 0.3.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-01-28 00:00:00 Z
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.6
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.