acts_as_executor 1.0.0.beta2 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/acts_as_executor.gemspec +8 -1
  2. data/lib/acts_as_executor.rb +18 -31
  3. data/lib/acts_as_executor/common/future_task.rb +2 -2
  4. data/lib/acts_as_executor/common/instance_support_methods.rb +15 -0
  5. data/lib/acts_as_executor/executor/factory.rb +18 -22
  6. data/lib/acts_as_executor/executor/model/class_methods.rb +33 -0
  7. data/lib/acts_as_executor/executor/model/instance_methods.rb +87 -0
  8. data/lib/acts_as_executor/executor/model/instance_support_methods.rb +36 -0
  9. data/lib/acts_as_executor/task/clazz.rb +26 -0
  10. data/lib/acts_as_executor/task/model/class_methods.rb +13 -0
  11. data/lib/acts_as_executor/task/model/instance_methods.rb +64 -0
  12. data/lib/acts_as_executor/task/model/instance_support_methods.rb +32 -0
  13. data/lib/acts_as_executor/validators/class_exists_validator.rb +1 -1
  14. data/lib/acts_as_executor/validators/class_includes_validator.rb +10 -0
  15. data/lib/acts_as_executor/version.rb +1 -1
  16. data/lib/generators/{executor_generator.rb → acts_as_executor_generator.rb} +1 -5
  17. data/lib/generators/templates/migration/create_executors.rb +2 -2
  18. data/spec/acts_as_executor_spec.rb +22 -0
  19. data/spec/common/future_task_spec.rb +21 -0
  20. data/spec/common/instance_support_methods_spec.rb +25 -0
  21. data/spec/common/units_spec.rb +19 -0
  22. data/spec/executor/factory_spec.rb +31 -0
  23. data/spec/executor/model/class_methods_spec.rb +49 -0
  24. data/spec/executor/model/instance_methods_spec.rb +150 -0
  25. data/spec/executor/model/instance_support_methods_spec.rb +70 -0
  26. data/spec/spec_helper.rb +8 -0
  27. data/spec/support/blueprints.rb +20 -0
  28. data/spec/support/classes.rb +55 -0
  29. data/spec/support/custom_matchers.rb +26 -0
  30. data/spec/support/database_connection.rb +4 -0
  31. data/spec/task/clazz_spec.rb +42 -0
  32. data/spec/task/model/class_methods_spec.rb +13 -0
  33. data/spec/task/model/instance_methods_spec.rb +80 -0
  34. data/spec/task/model/instance_support_methods_spec.rb +56 -0
  35. data/spec/task/schedules_spec.rb +11 -0
  36. data/spec/validators/class_exists_validator_spec.rb +21 -0
  37. data/spec/validators/class_includes_validator_spec.rb +21 -0
  38. metadata +88 -21
  39. data/README.rdoc +0 -67
  40. data/lib/acts_as_executor/executor/kinds.rb +0 -16
  41. data/lib/acts_as_executor/executor/model/actions.rb +0 -65
  42. data/lib/acts_as_executor/executor/model/associations.rb +0 -11
  43. data/lib/acts_as_executor/executor/model/attributes.rb +0 -16
  44. data/lib/acts_as_executor/executor/model/base.rb +0 -24
  45. data/lib/acts_as_executor/executor/model/helpers.rb +0 -25
  46. data/lib/acts_as_executor/executor/model/validations.rb +0 -13
  47. data/lib/acts_as_executor/task/model/actions.rb +0 -34
  48. data/lib/acts_as_executor/task/model/associations.rb +0 -11
  49. data/lib/acts_as_executor/task/model/attributes.rb +0 -16
  50. data/lib/acts_as_executor/task/model/base.rb +0 -15
  51. data/lib/acts_as_executor/task/model/helpers.rb +0 -14
  52. data/lib/acts_as_executor/task/model/validations.rb +0 -16
  53. data/lib/generators/templates/initializer/acts_as_executor.rb +0 -4
@@ -10,8 +10,15 @@ Gem::Specification.new do |s|
10
10
  s.email = ["philostler@gmail.com"]
11
11
  s.homepage = "https://github.com/philostler/acts_as_executor"
12
12
  s.summary = %q{Java Executor framework integration for Rails}
13
- s.description = %q{Seamless integration of Java's Executor framework with JRuby on Rails}
13
+ s.description = %q{Seamlessly integrates Java's Executor framework with JRuby on Rails}
14
14
 
15
15
  s.files = Dir["**/*.rb"] + Dir["*.rdoc"] + Dir["LICENSE"] + Dir["*.gemspec"]
16
16
  s.require_paths = ["lib"]
17
+
18
+ s.add_dependency "activemodel", ">= 3.0"
19
+ s.add_dependency "activerecord", ">= 3.0"
20
+
21
+ s.add_development_dependency "activerecord-jdbcsqlite3-adapter", "~> 1.1"
22
+ s.add_development_dependency "machinist", "2.0.0.beta2"
23
+ s.add_development_dependency "rspec", "~> 2.6"
17
24
  end
@@ -1,46 +1,33 @@
1
+ require "active_model"
2
+ require "active_record"
3
+
4
+ require "acts_as_executor/version"
5
+
1
6
  require "acts_as_executor/common/future_task"
7
+ require "acts_as_executor/common/instance_support_methods"
2
8
  require "acts_as_executor/common/units"
3
9
 
4
10
  require "acts_as_executor/executor/factory"
5
- require "acts_as_executor/executor/kinds"
6
11
 
7
- require "acts_as_executor/executor/model/actions"
8
- require "acts_as_executor/executor/model/associations"
9
- require "acts_as_executor/executor/model/attributes"
10
- require "acts_as_executor/executor/model/base"
11
- require "acts_as_executor/executor/model/helpers"
12
- require "acts_as_executor/executor/model/validations"
12
+ require "acts_as_executor/executor/model/class_methods"
13
+ require "acts_as_executor/executor/model/instance_methods"
14
+ require "acts_as_executor/executor/model/instance_support_methods"
13
15
 
16
+ require "acts_as_executor/task/clazz"
14
17
  require "acts_as_executor/task/schedules"
15
18
 
16
- require "acts_as_executor/task/model/actions"
17
- require "acts_as_executor/task/model/associations"
18
- require "acts_as_executor/task/model/attributes"
19
- require "acts_as_executor/task/model/base"
20
- require "acts_as_executor/task/model/helpers"
21
- require "acts_as_executor/task/model/validations"
19
+ require "acts_as_executor/task/model/class_methods"
20
+ require "acts_as_executor/task/model/instance_methods"
21
+ require "acts_as_executor/task/model/instance_support_methods"
22
22
 
23
23
  require "acts_as_executor/validators/class_exists_validator"
24
+ require "acts_as_executor/validators/class_includes_validator"
24
25
 
25
- ActiveRecord::Base.send :extend, ActsAsExecutor::Executor::Model::Base
26
- ActiveRecord::Base.send :extend, ActsAsExecutor::Task::Model::Base
26
+ ActiveRecord::Base.send :extend, ActsAsExecutor::Executor::Model::ClassMethods
27
+ ActiveRecord::Base.send :extend, ActsAsExecutor::Task::Model::ClassMethods
27
28
 
28
29
  module ActsAsExecutor
29
- def self.log
30
- logger
31
- end
32
- def self.logger
33
- @@logger ||= Rails.logger
34
- end
35
- def self.logger= logger
36
- @@logger = logger
37
- end
38
-
39
- def self.configure
40
- yield self
41
- end
42
-
43
- def self.rails_startup?
44
- File.basename($0) == "rails"
30
+ def self.rails_initialized?
31
+ File.basename($0) != "rake"
45
32
  end
46
33
  end
@@ -1,10 +1,10 @@
1
1
  module ActsAsExecutor
2
2
  module Common
3
3
  class FutureTask < Java::java.util.concurrent.FutureTask
4
- attr_accessor :task
4
+ attr_writer :done_handler
5
5
 
6
6
  def done
7
- task.destroy
7
+ @done_handler.call if @done_handler
8
8
  end
9
9
  end
10
10
  end
@@ -0,0 +1,15 @@
1
+ module ActsAsExecutor
2
+ module Common
3
+ module InstanceSupportMethods
4
+ private
5
+ def log_statement executor_name, statement
6
+ "\"" + executor_name + "\" " + statement
7
+ end
8
+
9
+ def log_message executor_name, doing, task_id, clazz_name, message = ""
10
+ if message.kind_of? Hash then message = "with \"" + message.inspect + "\"" end
11
+ "\"" + executor_name + "\" " + doing + " \"" + task_id + "#" + clazz_name + "\" " + message
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,30 +1,26 @@
1
1
  module ActsAsExecutor
2
2
  module Executor
3
3
  class Factory
4
- def self.create kind, size = nil
5
- case kind
6
- when ActsAsExecutor::Executor::Kinds::CACHED
7
- return Java::java.util.concurrent.Executors.new_cached_thread_pool
8
-
9
- when ActsAsExecutor::Executor::Kinds::FIXED
10
- raise TypeError, "size cannot be nil" unless size
11
- raise ArgumentError, "size must be larger than 0" unless size.to_i > 0
12
- return Java::java.util.concurrent.Executors.new_fixed_thread_pool size.to_i
13
-
14
- when ActsAsExecutor::Executor::Kinds::SINGLE
15
- return Java::java.util.concurrent.Executors.new_single_thread_executor
16
-
17
- when ActsAsExecutor::Executor::Kinds::SCHEDULED
18
- raise TypeError, "size cannot be nil" unless size
19
- raise ArgumentError, "size must be larger than 0" unless size.to_i > 0
20
- return Java::java.util.concurrent.Executors.new_scheduled_thread_pool size.to_i
21
-
22
- when ActsAsExecutor::Executor::Kinds::SINGLE_SCHEDULED
23
- return Java::java.util.concurrent.Executors.new_single_thread_scheduled_executor
24
-
4
+ def self.create max_tasks, schedulable
5
+ executor = nil
6
+ if schedulable
7
+ if max_tasks == nil
8
+ # No Cached Scheduled Implementation
9
+ elsif max_tasks > 1
10
+ executor = Java::java.util.concurrent.Executors.new_scheduled_thread_pool max_tasks.to_i # Scheduled
25
11
  else
26
- return nil
12
+ executor = Java::java.util.concurrent.Executors.new_single_thread_scheduled_executor # Single Scheduled
13
+ end
14
+ else
15
+ if max_tasks == nil
16
+ executor = Java::java.util.concurrent.Executors.new_cached_thread_pool # Cached
17
+ elsif max_tasks > 1
18
+ executor = Java::java.util.concurrent.Executors.new_fixed_thread_pool max_tasks.to_i # Fixed
19
+ else
20
+ executor = Java::java.util.concurrent.Executors.new_single_thread_executor # Single
21
+ end
27
22
  end
23
+ executor
28
24
  end
29
25
  end
30
26
  end
@@ -0,0 +1,33 @@
1
+ module ActsAsExecutor
2
+ module Executor
3
+ module Model
4
+ module ClassMethods
5
+ def acts_as_executor *arguments
6
+ send :include, ActsAsExecutor::Common::InstanceSupportMethods
7
+ send :include, ActsAsExecutor::Executor::Model::InstanceMethods
8
+ send :include, ActsAsExecutor::Executor::Model::InstanceSupportMethods
9
+
10
+ hash = arguments.last.is_a?(Hash) ? arguments.pop : {}
11
+ self.log = hash[:log]
12
+
13
+ if ActsAsExecutor.rails_initialized?
14
+ all
15
+ at_exit do
16
+ all.each do |e|
17
+ e.send :shutdown
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ def log
24
+ @@log ? @@log : Rails.logger
25
+ end
26
+
27
+ def log= log
28
+ @@log = log
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,87 @@
1
+ module ActsAsExecutor
2
+ module Executor
3
+ module Model
4
+ module InstanceMethods
5
+ def self.included base
6
+ # Associations
7
+ base.has_many :tasks, :class_name => base.table_name.singularize.camelize + "Task", :foreign_key => "executor_id"
8
+
9
+ # Callbacks
10
+ base.after_find :startup, :if => :startupable?
11
+ base.after_save :startup, :if => :startupable?
12
+ base.after_destroy :shutdown, :if => :shutdownable?
13
+
14
+ # Validations
15
+ base.validates :name, :presence => true, :uniqueness => true
16
+ base.validates :max_tasks, :numericality => { :only_integer => true, :greater_than_or_equal_to => 1 }, :allow_nil => true
17
+ end
18
+
19
+ private
20
+ def startup
21
+ log.debug log_statement name, "startup triggered"
22
+ self.executor = ActsAsExecutor::Executor::Factory.create max_tasks, schedulable
23
+ log.info log_statement name, "started"
24
+
25
+ tasks.all
26
+ end
27
+
28
+ def execute clazz, task_id, schedule = nil, start = nil, every = nil, units = nil
29
+ begin
30
+ humanized_schedule = schedule ? schedule.gsub("_", " ") : "one time"
31
+ log.debug log_message name, "preparing", task_id, clazz.class.name, "for execution (" + humanized_schedule + ")"
32
+
33
+ if schedulable?
34
+ units = Java::java.util.concurrent.TimeUnit.value_of(units.upcase)
35
+ case schedule
36
+ when ActsAsExecutor::Task::Schedules::ONE_SHOT
37
+ future = executor.schedule clazz, start, units
38
+ when ActsAsExecutor::Task::Schedules::FIXED_DELAY
39
+ future = executor.schedule_with_fixed_delay clazz, start, every, units
40
+ when ActsAsExecutor::Task::Schedules::FIXED_RATE
41
+ future = executor.schedule_at_fixed_rate clazz, start, every, units
42
+ end
43
+ else
44
+ future = ActsAsExecutor::Common::FutureTask.new clazz, nil
45
+ executor.execute future
46
+ end
47
+
48
+ log.info log_message name, "enqueued", task_id, clazz.class.name
49
+ future
50
+ rescue Java::java.util.concurrent.RejectedExecutionException
51
+ log.warn log_message name, "preparing", task_id, clazz.class.name, "encountered a rejected execution exception"
52
+ rescue Exception => exception
53
+ log.error log_message name, "preparing", task_id, clazz.class.name, "encountered an unexpected exception. " + exception
54
+ end
55
+ end
56
+
57
+ def shutdown
58
+ log.debug log_statement name, "shutdown triggered"
59
+ begin
60
+ executor.shutdown
61
+ rescue Java::java.lang.SecurityException
62
+ log.warn log_statement name, "shutdown encountered a security exception"
63
+ forced_shutdown
64
+ else
65
+ log.info log_statement name, "shutdown"
66
+ ensure
67
+ self.executor = nil
68
+ end
69
+ end
70
+
71
+ def forced_shutdown
72
+ log.debug log_statement name, "forced shutdown triggered"
73
+ begin
74
+ executor.shutdown_now
75
+ rescue Java::java.lang.SecurityException
76
+ log.error log_statement name, "forced shutdown encountered a security exception"
77
+ log.fatal log_statement name, "forced shutdown failure"
78
+ else
79
+ log.info log_statement name, "forced shutdown"
80
+ ensure
81
+ self.executor = nil
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,36 @@
1
+ module ActsAsExecutor
2
+ module Executor
3
+ module Model
4
+ module InstanceSupportMethods
5
+ private
6
+ @@executors = Hash.new
7
+ def executor
8
+ @@executors[id]
9
+ end
10
+
11
+ def executor= executor
12
+ raise ArgumentError, "cannot reference executor against nil id" unless id
13
+ @@executors[id] = executor
14
+ end
15
+
16
+ def startupable?
17
+ if executor == nil && ActsAsExecutor.rails_initialized?
18
+ return true
19
+ end
20
+ return false
21
+ end
22
+
23
+ def shutdownable?
24
+ if executor != nil && ActsAsExecutor.rails_initialized?
25
+ return true
26
+ end
27
+ return false
28
+ end
29
+
30
+ def log
31
+ self.class.log
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,26 @@
1
+ module ActsAsExecutor
2
+ module Task
3
+ module Clazz
4
+ include Java::java.lang.Runnable
5
+
6
+ attr_accessor :arguments
7
+ attr_writer :uncaught_exception_handler
8
+
9
+ private
10
+ def run
11
+ begin
12
+ if @arguments
13
+ @arguments.each_pair do |key, value|
14
+ class_eval { attr_accessor key } unless respond_to? key
15
+ send "#{key}=", value
16
+ end
17
+ end
18
+
19
+ execute
20
+ rescue Exception => exception
21
+ @uncaught_exception_handler.call exception if @uncaught_exception_handler
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,13 @@
1
+ module ActsAsExecutor
2
+ module Task
3
+ module Model
4
+ module ClassMethods
5
+ def acts_as_executor_task
6
+ send :include, ActsAsExecutor::Common::InstanceSupportMethods
7
+ send :include, ActsAsExecutor::Task::Model::InstanceMethods
8
+ send :include, ActsAsExecutor::Task::Model::InstanceSupportMethods
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,64 @@
1
+ module ActsAsExecutor
2
+ module Task
3
+ module Model
4
+ module InstanceMethods
5
+ def self.included base
6
+ # Associations
7
+ base.belongs_to :executor, :class_name => base.table_name.sub(/_tasks/, "").camelize
8
+
9
+ # Callbacks
10
+ base.after_find :enqueue, :if => :enqueueable?
11
+ base.after_save :enqueue, :if => :enqueueable?
12
+ base.after_destroy :cancel, :if => :cancelable?
13
+
14
+ # Serialization
15
+ base.serialize :arguments, Hash
16
+
17
+ # Validations
18
+ base.validates :executor_id, :presence => true
19
+ base.validates :clazz, :presence => true, :class_exists => true, :class_includes => { :includes => ActsAsExecutor::Task::Clazz }
20
+ base.validates :schedule, :inclusion => { :in => ActsAsExecutor::Task::Schedules::ALL }, :if => "executor and executor.schedulable?"
21
+ base.validates :start, :numericality => { :only_integer => true, :greater_than_or_equal_to => 0 }, :if => "executor and executor.schedulable?"
22
+ base.validates :every, :numericality => { :only_integer => true, :greater_than_or_equal_to => 1 }, :if => "executor and executor.schedulable? and schedule != ActsAsExecutor::Task::Schedules::ONE_SHOT"
23
+ base.validates :units, :inclusion => { :in => ActsAsExecutor::Common::Units::ALL }, :if => "executor and executor.schedulable?"
24
+ end
25
+
26
+ private
27
+ def enqueue
28
+ begin
29
+ executor.send(:log).debug log_message executor.name, "creating", id.to_s, clazz, arguments
30
+
31
+ instance = instantiate clazz, arguments
32
+
33
+ self.future = executor.send(:execute, instance, id.to_s, schedule, start, every, units)
34
+ future.done_handler = method :done_handler
35
+ rescue Exception => exception
36
+ executor.send(:log).error log_message executor.name, "creating", id.to_s, clazz, "encountered an unexpected exception. " + exception
37
+ end
38
+ end
39
+
40
+ def instantiate class_name, arguments
41
+ instance = Object.const_get(class_name).new
42
+ instance.arguments = arguments
43
+ instance.uncaught_exception_handler = method :uncaught_exception_handler
44
+ instance
45
+ end
46
+
47
+ def done_handler
48
+ executor.send(:log).debug log_message executor.name, "completed", id.to_s, clazz
49
+ destroy
50
+ end
51
+
52
+ def uncaught_exception_handler exception
53
+ executor.send(:log).error log_message executor.name, "executing", id.to_s, clazz, "encountered an uncaught exception. " + exception
54
+ end
55
+
56
+ def cancel
57
+ future.cancel true
58
+
59
+ self.future = nil
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,32 @@
1
+ module ActsAsExecutor
2
+ module Task
3
+ module Model
4
+ module InstanceSupportMethods
5
+ private
6
+ @@futures = Hash.new
7
+ def future
8
+ @@futures[id]
9
+ end
10
+
11
+ def future= future
12
+ raise ArgumentError, "cannot reference future against nil id" unless id
13
+ @@futures[id] = future
14
+ end
15
+
16
+ def enqueueable?
17
+ if future == nil
18
+ return true
19
+ end
20
+ return false
21
+ end
22
+
23
+ def cancelable?
24
+ if future != nil
25
+ return true
26
+ end
27
+ return false
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end