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.
- data/README.md +61 -14
- data/hirefire.gemspec +2 -11
- data/lib/hirefire.rb +39 -13
- data/lib/hirefire/backend.rb +21 -6
- data/lib/hirefire/backend/delayed_job/active_record.rb +31 -0
- data/lib/hirefire/backend/delayed_job/mongoid.rb +32 -0
- data/lib/hirefire/backend/resque/redis.rb +20 -0
- data/lib/hirefire/environment.rb +65 -12
- data/lib/hirefire/environment/base.rb +112 -10
- data/lib/hirefire/initializer.rb +54 -18
- data/lib/hirefire/railtie.rb +27 -1
- data/lib/hirefire/version.rb +1 -1
- data/lib/hirefire/workers/delayed_job.rb +5 -0
- data/lib/hirefire/{delayed_job_extension.rb → workers/delayed_job/worker.rb} +10 -2
- data/lib/hirefire/workers/resque.rb +31 -0
- data/lib/hirefire/workers/resque/job.rb +70 -0
- data/lib/hirefire/workers/resque/tasks.rb +21 -0
- data/lib/hirefire/workers/resque/worker.rb +57 -0
- data/spec/configuration_spec.rb +20 -0
- data/spec/environment_spec.rb +359 -0
- metadata +36 -9
- data/lib/hirefire/backend/active_record.rb +0 -20
- data/lib/hirefire/backend/mongoid.rb +0 -21
@@ -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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
-
|
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
|
197
|
+
# @return [Array] the array of hashes containing the job/worker ratio
|
96
198
|
def ratio
|
97
|
-
HireFire.configuration.job_worker_ratio
|
199
|
+
HireFire.configuration.job_worker_ratio
|
98
200
|
end
|
99
201
|
|
100
202
|
end
|
data/lib/hirefire/initializer.rb
CHANGED
@@ -4,33 +4,69 @@ module HireFire
|
|
4
4
|
class Initializer
|
5
5
|
|
6
6
|
##
|
7
|
-
# Loads the HireFire extension in to
|
8
|
-
# extends
|
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
|
-
#
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
#
|
23
|
-
|
24
|
-
|
25
|
-
|
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(:
|
28
|
-
end
|
63
|
+
send(:extend, HireFire::Backend::Resque::Redis)
|
29
64
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
data/lib/hirefire/railtie.rb
CHANGED
@@ -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
|
data/lib/hirefire/version.rb
CHANGED
@@ -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
|
-
#
|
46
|
-
#
|
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'
|