hirefire 0.1.0 → 0.1.1

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.
@@ -31,8 +31,20 @@ module HireFire
31
31
  # # It'll match at { :jobs => 35, :workers => 3 }, (35 jobs or more: hire 3 workers)
32
32
  # # meaning that it'll ensure there are 3 workers running.
33
33
  #
34
- # # If there were already were 3 workers, it'll leave it as is
35
- #
34
+ # # If there were already were 3 workers, it'll leave it as is.
35
+ #
36
+ # # Alternatively, you can use a more functional syntax, which works in the same way.
37
+ #
38
+ # HireFire.configure do |config|
39
+ # config.max_workers = 5
40
+ # config.job_worker_ratio = [
41
+ # { :when => lambda {|jobs| jobs < 15 }, :workers => 1 },
42
+ # { :when => lambda {|jobs| jobs < 35 }, :workers => 2 },
43
+ # { :when => lambda {|jobs| jobs < 60 }, :workers => 3 },
44
+ # { :when => lambda {|jobs| jobs < 80 }, :workers => 4 }
45
+ # ]
46
+ # end
47
+ #
36
48
  # # If there were more than 3 workers running (say, 4 or 5), it will NOT reduce
37
49
  # # the number. This is because when you reduce the number of workers, you cannot
38
50
  # # tell which worker Heroku will shut down, meaning you might interrupt a worker
@@ -50,16 +62,97 @@ module HireFire
50
62
  jobs_count = jobs
51
63
  workers_count = workers
52
64
 
53
- ratio.each do |ratio|
54
- if jobs_count >= ratio[:jobs] and max_workers >= ratio[:workers]
55
- if not workers_count == ratio[:workers]
56
- Logger.message("Hiring more workers so we have #{ ratio[:workers] } in total.")
57
- workers(ratio[:workers])
65
+ ##
66
+ # Use "Standard Notation"
67
+ if not ratio.first[:when].respond_to? :call
68
+
69
+ ##
70
+ # Since the "Standard Notation" is defined in the in an ascending order
71
+ # in the array of hashes, we need to reverse this order in order to properly
72
+ # loop through and break out of the array at the correctly matched ratio
73
+ ratio.reverse!
74
+
75
+ ##
76
+ # Iterates through all the defined job/worker ratio's
77
+ # until it finds a match. Then it hires (if necessary) the appropriate
78
+ # amount of workers and breaks out of the loop
79
+ ratio.each do |ratio|
80
+
81
+ ##
82
+ # Standard notation
83
+ # This is the code for the default notation
84
+ #
85
+ # @example
86
+ # { :jobs => 35, :workers => 3 }
87
+ #
88
+ if jobs_count >= ratio[:jobs] and max_workers >= ratio[:workers]
89
+ if workers_count < ratio[:workers]
90
+ log_and_hire(ratio[:workers])
91
+ end
92
+
93
+ return
58
94
  end
95
+ end
96
+
97
+ ##
98
+ # If no match is found in the above job/worker ratio loop, then we'll
99
+ # perform one last operation to see whether the the job count is greater
100
+ # than the highest job/worker ratio, and if this is the case then we also
101
+ # check to see whether the maximum amount of allowed workers is greater
102
+ # than the amount that are currently running, if this is the case, we are
103
+ # allowed to hire the max amount of workers.
104
+ if jobs_count >= ratio.first[:jobs] and max_workers > workers_count
105
+ log_and_hire(max_workers)
106
+ return
107
+ end
108
+ end
109
+
110
+ ##
111
+ # Use "Functional (Lambda) Notation"
112
+ if ratio.first[:when].respond_to? :call
113
+
114
+ ##
115
+ # Iterates through all the defined job/worker ratio's
116
+ # until it finds a match. Then it hires (if necessary) the appropriate
117
+ # amount of workers and breaks out of the loop
118
+ ratio.each do |ratio|
59
119
 
60
- break
120
+ ##
121
+ # Functional (Lambda) Notation
122
+ # This is the code for the Lambda notation, more verbose,
123
+ # but more humanly understandable
124
+ #
125
+ # @example
126
+ # { :when => lambda {|jobs| jobs < 60 }, :workers => 3 }
127
+ #
128
+ if ratio[:when].call(jobs_count) and max_workers >= ratio[:workers]
129
+ if workers_count < ratio[:workers]
130
+ log_and_hire(ratio[:workers])
131
+ end
132
+
133
+ break
134
+ end
61
135
  end
62
136
  end
137
+
138
+ ##
139
+ # Applies only to the Functional (Lambda) Notation
140
+ # If the amount of queued jobs exceeds that of which was defined in the
141
+ # job/worker ratio array, it will hire the maximum amount of workers
142
+ #
143
+ # "if statements":
144
+ # 1. true if we use the Functional (Lambda) Notation
145
+ # 2. true if the last ratio (highest job/worker ratio) was exceeded
146
+ # 3. true if the max amount of workers are not yet running
147
+ #
148
+ # If all the the above statements are true, HireFire will hire the maximum
149
+ # amount of workers that were specified in the configuration
150
+ #
151
+ if ratio.last[:when].respond_to? :call \
152
+ and ratio.last[:when].call(jobs_count) === false \
153
+ and max_workers != workers_count
154
+ log_and_hire(max_workers)
155
+ end
63
156
  end
64
157
 
65
158
  ##
@@ -79,6 +172,15 @@ module HireFire
79
172
 
80
173
  private
81
174
 
175
+ ##
176
+ # Helper method for hire that logs the hiring of more workers, then hires those workers.
177
+ #
178
+ # @return [nil]
179
+ def log_and_hire(amount)
180
+ Logger.message("Hiring more workers so we have #{ amount } in total.")
181
+ workers(amount)
182
+ end
183
+
82
184
  ##
83
185
  # Wrapper method for HireFire.configuration
84
186
  # Returns the max amount of workers that may run concurrently
@@ -92,9 +194,9 @@ module HireFire
92
194
  # Wrapper method for HireFire.configuration
93
195
  # Returns the job/worker ratio array (in reversed order)
94
196
  #
95
- # @return [Array] the array of hashes containing the job/worker ratio (in reversed order)
197
+ # @return [Array] the array of hashes containing the job/worker ratio
96
198
  def ratio
97
- HireFire.configuration.job_worker_ratio.reverse
199
+ HireFire.configuration.job_worker_ratio
98
200
  end
99
201
 
100
202
  end
@@ -4,33 +4,69 @@ module HireFire
4
4
  class Initializer
5
5
 
6
6
  ##
7
- # Loads the HireFire extension in to Delayed Job and
8
- # extends the Delayed Job "jobs:work" rake task command
7
+ # Loads the HireFire extension in to the loaded worker library and
8
+ # extends that library by injecting HireFire hooks in the proper locations.
9
+ #
10
+ # Currently it supports:
11
+ # - Delayed Job
12
+ # - ActiveRecord ORM
13
+ # - Mongoid ODM
14
+ # - Resque
15
+ # - Redis
16
+ #
17
+ # @note
18
+ # Either the Delayed Job, or the Resque worker library must be
19
+ # loaded BEFORE HireFire initializes, otherwise it'll be unable
20
+ # to detect the proper library and it will not work.
9
21
  #
10
22
  # @return [nil]
11
23
  def self.initialize!
24
+
12
25
  ##
13
- # If DelayedJob is using ActiveRecord, then include
14
- # HireFire::Environment in to the ActiveRecord Delayed Job Backend
15
- if defined?(Delayed::Backend::ActiveRecord::Job)
16
- Delayed::Backend::ActiveRecord::Job.
17
- send(:include, HireFire::Environment).
18
- send(:include, HireFire::Backend)
26
+ # Initialize Delayed::Job extensions if Delayed::Job is found
27
+ if defined?(::Delayed::Job)
28
+ ##
29
+ # If DelayedJob is using ActiveRecord, then include
30
+ # HireFire::Environment in to the ActiveRecord Delayed Job Backend
31
+ if defined?(::Delayed::Backend::ActiveRecord::Job)
32
+ ::Delayed::Backend::ActiveRecord::Job.
33
+ send(:include, HireFire::Environment).
34
+ send(:include, HireFire::Backend)
35
+ end
36
+
37
+ ##
38
+ # If DelayedJob is using Mongoid, then include
39
+ # HireFire::Environment in to the Mongoid Delayed Job Backend
40
+ if defined?(::Delayed::Backend::Mongoid::Job)
41
+ ::Delayed::Backend::Mongoid::Job.
42
+ send(:include, HireFire::Environment).
43
+ send(:include, HireFire::Backend)
44
+ end
45
+
46
+ ##
47
+ # Load Delayed Job extensions, this will patch Delayed::Worker
48
+ # to implement the necessary hooks to invoke HireFire from
49
+ require File.join(HireFire::WORKERS_PATH, 'delayed_job')
19
50
  end
20
51
 
21
52
  ##
22
- # If DelayedJob is using Mongoid, then include
23
- # HireFire::Environment in to the Mongoid Delayed Job Backend
24
- if defined?(Delayed::Backend::Mongoid::Job)
25
- Delayed::Backend::Mongoid::Job.
53
+ # Initialize Resque extensions if Resque is found
54
+ if defined?(::Resque)
55
+
56
+ ##
57
+ # Include the HireFire::Environment which will add an instance
58
+ # of HireFire::Environment::(Heroku|Local|Noop) to the Resque::Job.environment class method
59
+ #
60
+ # Extend the Resque::Job class with the Resque::Job.jobs class method
61
+ ::Resque::Job.
26
62
  send(:include, HireFire::Environment).
27
- send(:include, HireFire::Backend)
28
- end
63
+ send(:extend, HireFire::Backend::Resque::Redis)
29
64
 
30
- ##
31
- # Load Delayed Job extension, this is the start
32
- # method that gets invoked when running "rake jobs:work"
33
- require File.dirname(__FILE__) + '/delayed_job_extension'
65
+ ##
66
+ # Load Resque extensions, this will patch Resque, Resque::Job and Resque::Worker
67
+ # to implement the necessary hooks to invoke HireFire from
68
+ require File.join(HireFire::WORKERS_PATH, 'resque')
69
+ end
34
70
  end
35
71
 
36
72
  end
@@ -4,11 +4,37 @@ module HireFire
4
4
  class Railtie < Rails::Railtie
5
5
 
6
6
  ##
7
- # Initializes HireFire for Delayed Job when
7
+ # Initializes HireFire for either Delayed Job or Resque when
8
8
  # the Ruby on Rails web framework is done loading
9
+ #
10
+ # @note
11
+ # Either the Delayed Job, or the Resque worker library must be
12
+ # loaded BEFORE HireFire initializes, otherwise it'll be unable
13
+ # to detect the proper library and it will not work.
9
14
  initializer :after_initialize do
10
15
  HireFire::Initializer.initialize!
11
16
  end
12
17
 
18
+ ##
19
+ # Adds additional rake tasks to the Ruby on Rails environment
20
+ #
21
+ # @note
22
+ # In order for Resque to run on Heroku, it must have the 'rake jobs:work'
23
+ # rake task since that's what Heroku uses to start workers. When using
24
+ # Ruby on Rails automatically add the necessary default rake task for the user
25
+ #
26
+ # @note
27
+ # Delayed Job already has 'rake jobs:work' built in.
28
+ #
29
+ rake_tasks do
30
+
31
+ ##
32
+ # If Resque is loaded, then we load the Resque rake task
33
+ # that'll allow Heroku to start up Resque as a worker
34
+ if defined?(::Resque)
35
+ require File.join(WORKERS_PATH, 'resque', 'tasks.rb')
36
+ end
37
+ end
38
+
13
39
  end
14
40
  end
@@ -6,7 +6,7 @@ module HireFire
6
6
  ##
7
7
  # @return [String] the current version of the HireFire gem
8
8
  def self.current
9
- '0.1.0'
9
+ '0.1.1'
10
10
  end
11
11
 
12
12
  end
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+
3
+ ##
4
+ # Load the "HireFire modified" Delayed::Worker class
5
+ require File.dirname(__FILE__) + '/delayed_job/worker'
@@ -1,5 +1,9 @@
1
1
  # encoding: utf-8
2
2
 
3
+ ##
4
+ # HireFire
5
+ # This is a HireFire modified version of
6
+ # the official Delayed::Worker class
3
7
  module Delayed
4
8
  class Worker
5
9
 
@@ -42,10 +46,14 @@ module Delayed
42
46
  end
43
47
 
44
48
  ##
45
- # If there are no jobs currently queued,
46
- # and the worker is still running, it'll kill itself
49
+ # HireFire Hook
50
+ # After the last job in the queue finishes processing, Delayed::Job.new.jobs (queued.jobs)
51
+ # will return 0. This means that there aren't any more jobs to process for any of the workers.
52
+ # If this is the case it'll command the current environment to fire all the hired workers
53
+ # and then immediately break out of this infinite loop.
47
54
  if queued.jobs == 0
48
55
  Delayed::Job.environment.fire
56
+ break
49
57
  end
50
58
 
51
59
  break if $exit
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ ##
4
+ # Load the "HireFire modified"
5
+ # Resque::Job and Resque::Worker classes
6
+ require File.dirname(__FILE__) + '/resque/job'
7
+ require File.dirname(__FILE__) + '/resque/worker'
8
+
9
+ ##
10
+ # HireFire
11
+ # This is a HireFire modified version of
12
+ # the official Resque module
13
+ module ::Resque
14
+ def self.enqueue(klass, *args)
15
+ Job.create(queue_from_class(klass), klass, *args)
16
+
17
+ ##
18
+ # HireFire Hook
19
+ # After a new job gets queued, we command the current environment
20
+ # to calculate the amount of workers we need to process the jobs
21
+ # that are currently queued, and hire them accordingly.
22
+ if ::Resque.info[:working].to_i == 0 \
23
+ or ::Resque.info[:jobs] == 1
24
+ ::Resque::Job.environment.hire
25
+ end
26
+
27
+ Plugin.after_enqueue_hooks(klass).each do |hook|
28
+ klass.send(hook, *args)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,70 @@
1
+ # encoding: utf-8
2
+
3
+ ##
4
+ # HireFire
5
+ # This is a HireFire modified version of
6
+ # the official Resque::Job class
7
+ module ::Resque
8
+ class Job
9
+ def perform
10
+ job = payload_class
11
+ job_args = args || []
12
+ job_was_performed = false
13
+
14
+ before_hooks = Plugin.before_hooks(job)
15
+ around_hooks = Plugin.around_hooks(job)
16
+ after_hooks = Plugin.after_hooks(job)
17
+ failure_hooks = Plugin.failure_hooks(job)
18
+
19
+ begin
20
+ begin
21
+ before_hooks.each do |hook|
22
+ job.send(hook, *job_args)
23
+ end
24
+ rescue DontPerform
25
+ return false
26
+ end
27
+
28
+ if around_hooks.empty?
29
+ job.perform(*job_args)
30
+ job_was_performed = true
31
+ else
32
+ stack = around_hooks.reverse.inject(nil) do |last_hook, hook|
33
+ if last_hook
34
+ lambda do
35
+ job.send(hook, *job_args) { last_hook.call }
36
+ end
37
+ else
38
+ lambda do
39
+ job.send(hook, *job_args) do
40
+ result = job.perform(*job_args)
41
+ job_was_performed = true
42
+ result
43
+ end
44
+ end
45
+ end
46
+ end
47
+ stack.call
48
+ end
49
+
50
+ ##
51
+ # HireFire Hook
52
+ # After a job finishes processing, we invoke the #fire
53
+ # method on the environment object which will check to see whether
54
+ # we can fire all the hired workers
55
+ ::Resque::Job.environment.fire
56
+
57
+ after_hooks.each do |hook|
58
+ job.send(hook, *job_args)
59
+ end
60
+
61
+ return job_was_performed
62
+
63
+ rescue Object => e
64
+ failure_hooks.each { |hook| job.send(hook, e, *job_args) }
65
+ raise e
66
+ end
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+
3
+ ##
4
+ # Load in the official Resque rake tasks
5
+ require 'resque/tasks'
6
+
7
+ ##
8
+ # Overwrite the resque:setup rake task to first load
9
+ # in the application environment before proceeding
10
+ #
11
+ # ENV['QUEUE'] will default to '*' unless it's defined
12
+ # as an environment variable on Heroku or the Local machine
13
+ task 'resque:setup' => :environment do
14
+ ENV['QUEUE'] ||= '*'
15
+ end
16
+
17
+ ##
18
+ # This is an alias to the "resque:work" task since Heroku doesn't respond
19
+ # to "resque:work", we need to add this alias so Resque can be initialized by Heroku
20
+ desc 'Alias of "resque:work" - This is required for running on Heroku.'
21
+ task 'jobs:work' => 'resque:work'