active_worker 0.50.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.
Files changed (55) hide show
  1. data/.document +5 -0
  2. data/.rvmrc +1 -0
  3. data/Gemfile +22 -0
  4. data/Gemfile.lock +77 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +19 -0
  7. data/Rakefile +46 -0
  8. data/VERSION +1 -0
  9. data/active_worker.gemspec +108 -0
  10. data/lib/active_worker.rb +29 -0
  11. data/lib/active_worker/behavior/acts_as_root_object.rb +80 -0
  12. data/lib/active_worker/behavior/can_be_notified.rb +23 -0
  13. data/lib/active_worker/behavior/create_from_error.rb +21 -0
  14. data/lib/active_worker/behavior/execute_concurrently.rb +142 -0
  15. data/lib/active_worker/behavior/has_modes.rb +44 -0
  16. data/lib/active_worker/behavior/has_root_object.rb +50 -0
  17. data/lib/active_worker/behavior/hashable.rb +79 -0
  18. data/lib/active_worker/configuration.rb +143 -0
  19. data/lib/active_worker/controller.rb +112 -0
  20. data/lib/active_worker/event.rb +68 -0
  21. data/lib/active_worker/expandable.rb +77 -0
  22. data/lib/active_worker/failure_event.rb +16 -0
  23. data/lib/active_worker/finished_event.rb +9 -0
  24. data/lib/active_worker/host_information.rb +13 -0
  25. data/lib/active_worker/job_queue/job_executer.rb +52 -0
  26. data/lib/active_worker/job_queue/queue_manager.rb +46 -0
  27. data/lib/active_worker/job_queue/run_remotely.rb +52 -0
  28. data/lib/active_worker/modes_map.rb +37 -0
  29. data/lib/active_worker/notification_event.rb +9 -0
  30. data/lib/active_worker/parent_event.rb +5 -0
  31. data/lib/active_worker/started_event.rb +10 -0
  32. data/lib/active_worker/templatable.rb +46 -0
  33. data/lib/active_worker/template.rb +41 -0
  34. data/lib/active_worker/termination_event.rb +21 -0
  35. data/test/mongoid.yml +28 -0
  36. data/test/test_acts_as_root_object.rb +123 -0
  37. data/test/test_can_be_notified.rb +44 -0
  38. data/test/test_configuration.rb +281 -0
  39. data/test/test_controller.rb +205 -0
  40. data/test/test_event.rb +75 -0
  41. data/test/test_execute_concurrently.rb +134 -0
  42. data/test/test_expandable.rb +113 -0
  43. data/test/test_failure_event.rb +69 -0
  44. data/test/test_finished_event.rb +35 -0
  45. data/test/test_has_modes.rb +56 -0
  46. data/test/test_helper.rb +120 -0
  47. data/test/test_integration.rb +56 -0
  48. data/test/test_job_executer.rb +65 -0
  49. data/test/test_queue_manager.rb +106 -0
  50. data/test/test_run_remotely.rb +63 -0
  51. data/test/test_started_event.rb +23 -0
  52. data/test/test_templatable.rb +45 -0
  53. data/test/test_template.rb +29 -0
  54. data/test/test_termination_event.rb +28 -0
  55. metadata +201 -0
@@ -0,0 +1,112 @@
1
+ module ActiveWorker
2
+ class Controller
3
+ extend Behavior::ExecuteConcurrently
4
+ extend JobQueue::RunRemotely
5
+
6
+ attr_reader :configuration
7
+
8
+ def self.execute_expanded(configuration_id)
9
+ config = Configuration.find(configuration_id)
10
+ configurations = config.expand_for_threads
11
+
12
+ execute_concurrently(configurations)
13
+
14
+ after_thread_launch_methods.each { |method| send(method, config, configurations) }
15
+
16
+ wait_for_children
17
+ ensure
18
+ worker_cleanup_methods.each { |method| send(method, configurations) }
19
+ end
20
+
21
+ def self.execute(configuration)
22
+ worker = new(configuration)
23
+ worker.started
24
+ worker.execute
25
+ worker.finished
26
+ end
27
+
28
+ def self.handle_error(error, method, params)
29
+ configuration_id = params.shift
30
+ configuration = Configuration.find(configuration_id)
31
+
32
+ if threaded?
33
+ FailureEvent.from_error(configuration, error)
34
+ end
35
+
36
+ if forking? && parent?
37
+ ParentEvent.create_error_from_configuration(configuration, error)
38
+ kill_children
39
+ wait_for_children
40
+ end
41
+
42
+ if forked?
43
+ FailureEvent.create_error_from_configuration(configuration, error)
44
+ end
45
+
46
+ rescue Exception => e
47
+ puts "ERROR: While Handling #{error} Had : #{e}\n#{e.backtrace.join("\n")}"
48
+ end
49
+
50
+ def self.handle_termination(params)
51
+ if forking? && parent?
52
+ kill_children
53
+ wait_for_children
54
+ end
55
+
56
+ configuration_id = params.shift
57
+ configuration = Configuration.find(configuration_id)
58
+
59
+ if threaded?
60
+ TerminationEvent.from_termination(configuration)
61
+ end
62
+ if forked?
63
+ TerminationEvent.create_termination_from_configuration(configuration)
64
+ end
65
+
66
+ end
67
+
68
+ def self.after_thread_launch(method)
69
+ after_thread_launch_methods << method
70
+ end
71
+
72
+ def self.after_thread_launch_methods
73
+ @after_thread_launch_methods ||= []
74
+ end
75
+
76
+ def self.worker_cleanup(method)
77
+ worker_cleanup_methods << method
78
+ end
79
+
80
+ def self.worker_cleanup_methods
81
+ @worker_cleanup_methods ||= []
82
+ end
83
+
84
+ def initialize(configuration)
85
+ @configuration = configuration
86
+ setup
87
+ end
88
+
89
+ def setup
90
+
91
+ end
92
+
93
+ def started
94
+ configuration.started
95
+ end
96
+
97
+ def execute
98
+ raise "Can't call execute on base controller #{configuration.inspect}'"
99
+ end
100
+
101
+ def finished
102
+ configuration.finished
103
+ end
104
+
105
+ private
106
+
107
+ def finished?(configurations)
108
+ FinishedEvent.exists_for_configurations?(configurations)
109
+ end
110
+
111
+ end
112
+ end
@@ -0,0 +1,68 @@
1
+ require_relative "behavior/has_root_object"
2
+ module ActiveWorker
3
+ class Event
4
+ include Mongoid::Document
5
+ include Mongoid::Timestamps
6
+ include Behavior::HasRootObject
7
+
8
+ belongs_to :configuration, :class_name => "ActiveWorker::Configuration"
9
+ alias_method :root_owner, :configuration
10
+ root_object_relation :events
11
+
12
+ index({:configuration_id => -1, :_type => 1}, {:background => true})
13
+
14
+ before_save :set_message, :set_process_information
15
+
16
+ validates_presence_of :configuration, :message => "Events must be owned by a Configuration"
17
+
18
+ field :message, :type => String
19
+ field :host, :type => String
20
+ field :process_id, :type => Integer
21
+ field :worker_pid, :type => Integer
22
+
23
+ scope :for_root_object_id, lambda {|root_object_id| where(:root_object_id => root_object_id).descending(:created_at)}
24
+
25
+
26
+ def self.exists_for_configurations?(configurations)
27
+ where(:configuration_id.in => configurations.map(&:id)).count == configurations.size
28
+ end
29
+
30
+ def fields_for_view
31
+ view_fields = {}
32
+ fields.keys.each do |field|
33
+ view_fields[field] = self.send(field)
34
+ end
35
+ view_fields
36
+ end
37
+
38
+ def event_type
39
+ self.class.name.split('::').last.underscore
40
+ end
41
+
42
+ def set_process_information
43
+ self.host = HostInformation.hostname
44
+ self.process_id = get_pid
45
+ #self.worker_pid = get_worker_pid
46
+ end
47
+
48
+ def get_pid
49
+ Process.pid.to_i
50
+ end
51
+
52
+ def get_worker_pid
53
+ worker = JobQueue::QueueManager.new.active_jobs_for_configurations([configuration.to_param]).first
54
+ return worker["pid"] if worker
55
+ nil
56
+ end
57
+
58
+ def set_message
59
+ return if message
60
+ self.message = generate_message
61
+ end
62
+
63
+ def generate_message
64
+ "#{configuration.event_name} base message"
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,77 @@
1
+ module ActiveWorker
2
+ module Expandable
3
+
4
+ def self.included(base)
5
+ base.template_field :number_of_threads, type: Integer, default: 1
6
+ base.template_field :number_of_workers, type: Integer, default: 1
7
+ base.belongs_to :thread_root_configuration, :polymorphic => true
8
+ base.has_many :thread_expanded_configurations,
9
+ :as => :thread_root_configuration,
10
+ :class_name => 'ActiveWorker::Configuration',
11
+ :autosave => true
12
+ end
13
+
14
+ def expand_for_threads
15
+ maps = expansion_maps_for_threads
16
+ maps[1..-1].each do |map_hash|
17
+ map_hash[:thread_root_configuration_id] = id
18
+ map_hash[:thread_root_configuration_type] = _type
19
+ end
20
+ expand_from_maps(maps)
21
+ end
22
+
23
+ def expand_for_workers
24
+ maps = expansion_maps_for_workers
25
+ expand_from_maps(maps)
26
+ end
27
+
28
+ def configurations_for_events
29
+ [self] + thread_expanded_configurations
30
+ end
31
+
32
+ private
33
+
34
+ def expand_from_maps(maps)
35
+ first_config = true
36
+ maps.map do |map_hash|
37
+ if first_config
38
+ first_config = false
39
+ modify_for_expansion(map_hash)
40
+ else
41
+ create_for_expansion(map_hash)
42
+ end
43
+ end
44
+ end
45
+
46
+ def create_for_expansion(options = {})
47
+ self.class.create(defined_fields.merge(parent_configuration: parent_configuration,
48
+ root_object_id: root_object_id,
49
+ root_object_type: root_object_type,
50
+ renderable: false).merge(options))
51
+ end
52
+
53
+ def modify_for_expansion(options = {})
54
+ self.update_attributes!(options)
55
+ self
56
+ end
57
+
58
+
59
+ def expansion_maps_for_workers
60
+ expansion_maps_for(number_of_workers)
61
+ end
62
+
63
+ def expansion_maps_for_threads
64
+ expansion_maps_for(number_of_threads)
65
+
66
+ end
67
+
68
+ def expansion_maps_for(number_of_configurations)
69
+ maps = []
70
+ number_of_configurations.times do
71
+ maps << {}
72
+ end
73
+ maps
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,16 @@
1
+ module ActiveWorker
2
+ class FailureEvent < ActiveWorker::FinishedEvent
3
+ extend Behavior::CreateFromError
4
+
5
+ def self.from_error(thread_root_configuration, error)
6
+ events = []
7
+ thread_root_configuration.configurations_for_events.each do |configuration|
8
+ events << create_error_from_configuration(configuration,error) unless configuration.completed?
9
+ end
10
+ events
11
+ end
12
+
13
+
14
+
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ module ActiveWorker
2
+ class FinishedEvent < ActiveWorker::Event
3
+ def generate_message
4
+ "#{configuration.event_name} finished"
5
+ end
6
+
7
+ after_create :notify_root_of_finished
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ module ActiveWorker
2
+ module HostInformation
3
+
4
+ def self.hostname
5
+ `hostname -f`.chomp
6
+ end
7
+
8
+ def self.short_hostname
9
+ `hostname`.chomp
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,52 @@
1
+ require 'resque/errors'
2
+
3
+ module ActiveWorker
4
+ module JobQueue
5
+ class JobExecuter
6
+
7
+ Thread.abort_on_exception = false
8
+
9
+ @queue = :execute
10
+
11
+ def self.perform(args)
12
+ execute_task_from_args(args)
13
+ end
14
+
15
+ def self.execute_task_from_args(args)
16
+ class_name = args["class_name"]
17
+ method = args["method"]
18
+ params = args["params"]
19
+
20
+ klass = class_name.constantize
21
+ klass.send(method,*params)
22
+
23
+ rescue Resque::TermException => e
24
+ handle_termination(klass, params, e)
25
+ rescue SignalException => e
26
+ handle_exception(e, method, params, klass)
27
+ raise e
28
+ rescue Exception => e
29
+ handle_exception(e, method, params, klass)
30
+ end
31
+
32
+ def self.handle_exception(e, method, params, klass)
33
+ log_error "Handling exception for #{klass} because #{e.message}"
34
+ klass.handle_error e, method, params
35
+ rescue Exception => handle_error_error
36
+ log_error "Handle error exception: #{handle_error_error.message}"
37
+ log_error handle_error_error.backtrace.join("\n")
38
+ end
39
+
40
+ def self.handle_termination(klass, params, exception = nil)
41
+ log_error "Handling #{exception || "termination" } for #{klass}"
42
+ klass.handle_termination params
43
+ exit
44
+ end
45
+
46
+ def self.log_error(message)
47
+ puts "JOB EXECUTOR: #{message}"
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,46 @@
1
+ module ActiveWorker
2
+ module JobQueue
3
+ class QueueManager
4
+
5
+ def active_jobs
6
+ Resque.working.map {|w| create_job_hash_from_worker(w)}.compact
7
+ end
8
+
9
+ def active_jobs_for_configurations(configuration_ids)
10
+ workers = Resque.working.select do|w|
11
+ configuration_ids.include? configuration_id_from_worker(w)
12
+ end
13
+ workers.map do |w|
14
+ create_job_hash_from_worker(w)
15
+ end
16
+ end
17
+
18
+ def configuration_id_from_worker(worker)
19
+ params = params_from_worker(worker)
20
+ params.first if params
21
+ end
22
+
23
+ def create_job_hash_from_worker(worker)
24
+ worker_id = worker.to_s.split(":")
25
+ host = worker_id[0]
26
+ pid = worker_id[1]
27
+ queues = worker_id[2].split(",")
28
+ args = args_from_worker(worker)
29
+
30
+ if worker_id && host && pid && queues && args
31
+ return {"host" => host, "queues" => queues, "pid" => pid, "args" => args }
32
+ end
33
+ end
34
+
35
+ def params_from_worker(worker)
36
+ args = args_from_worker(worker)
37
+ args["params"] if args
38
+ end
39
+
40
+ def args_from_worker(worker)
41
+ payload = worker.job["payload"]
42
+ payload["args"].first if payload
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,52 @@
1
+ module ActiveWorker
2
+ module JobQueue
3
+ module RunRemotely
4
+
5
+ THREADED = "threaded"
6
+ RESQUE = "resque"
7
+
8
+
9
+ def self.worker_mode=(mode)
10
+ @@worker_mode = mode
11
+ end
12
+
13
+ def self.worker_mode
14
+ @@worker_mode
15
+ end
16
+
17
+ self.worker_mode = RESQUE
18
+
19
+ class RemoteRunner
20
+ def initialize(klass)
21
+ @klass = klass
22
+ end
23
+
24
+ def method_missing(method,*params)
25
+ args = construct_args(method,params)
26
+ thread = nil
27
+ case RunRemotely.worker_mode
28
+ when THREADED
29
+ thread = Thread.new do
30
+ ActiveWorker::JobQueue::JobExecuter.execute_task_from_args(args)
31
+ end
32
+ when RESQUE
33
+ Resque.enqueue(JobExecuter,args)
34
+ end
35
+ thread
36
+ end
37
+
38
+ def construct_args(method, params)
39
+ {
40
+ "class_name" => @klass.to_s,
41
+ "method" => method.to_s,
42
+ "params" => params
43
+ }
44
+ end
45
+ end
46
+
47
+ def run_remotely
48
+ RemoteRunner.new(self.to_s)
49
+ end
50
+ end
51
+ end
52
+ end