coney_island 0.8 → 0.9
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.
- checksums.yaml +4 -4
- data/Rakefile +1 -1
- data/lib/coney_island.rb +14 -0
- data/lib/coney_island/job.rb +91 -0
- data/lib/coney_island/performer.rb +41 -0
- data/lib/coney_island/submitter.rb +13 -4
- data/lib/coney_island/version.rb +1 -1
- data/lib/coney_island/worker.rb +27 -70
- data/test/dummy/log/test.log +33 -0
- data/test/job_test.rb +151 -0
- data/test/performer_test.rb +113 -0
- data/test/submitter_test.rb +4 -4
- data/test/test_helper.rb +15 -0
- data/test/worker_test.rb +17 -37
- metadata +8 -3
- data/README.rdoc +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7dba043d7a55d3830d4a1d26147fa56148433713
|
4
|
+
data.tar.gz: be41a962dd7a4507839f4f7285e823c2a4ba0508
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9e816ff7f5e2620a672c6fb8be05755b5b270c77be586595befcea3258b8624d3a65011461781d9c62b8972acf608ef06ae6799336dc13bb27b472e44aeb1512
|
7
|
+
data.tar.gz: 3d8e612aad3bd9e432e15f3328cbdad05de6497a46ae62e4a6ba517fab9b89e211e0f6415e354cca7d28e6db341d2ce592abfcfba80b361d3990c5e2c69a3cc2
|
data/Rakefile
CHANGED
data/lib/coney_island.rb
CHANGED
@@ -57,6 +57,14 @@ module ConeyIsland
|
|
57
57
|
ConeyIsland::Worker.config
|
58
58
|
end
|
59
59
|
|
60
|
+
def self.delay_seed
|
61
|
+
@delay_seed ||= 2
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.delay_seed=(seed)
|
65
|
+
@delay_seed = seed
|
66
|
+
end
|
67
|
+
|
60
68
|
def self.single_amqp_connection?
|
61
69
|
!!self.amqp_parameters
|
62
70
|
end
|
@@ -73,6 +81,10 @@ module ConeyIsland
|
|
73
81
|
ConeyIsland::Submitter.run_inline
|
74
82
|
end
|
75
83
|
|
84
|
+
def self.running_inline?
|
85
|
+
ConeyIsland::Submitter.running_inline?
|
86
|
+
end
|
87
|
+
|
76
88
|
def self.stop_running_inline
|
77
89
|
ConeyIsland::Submitter.stop_running_inline
|
78
90
|
end
|
@@ -112,6 +124,8 @@ end
|
|
112
124
|
|
113
125
|
require 'coney_island/notifiers/honeybadger_notifier'
|
114
126
|
require 'coney_island/worker'
|
127
|
+
require 'coney_island/job'
|
115
128
|
require 'coney_island/submitter'
|
116
129
|
require 'coney_island/job_argument_error'
|
117
130
|
require 'coney_island/railtie' if defined?(Rails)
|
131
|
+
require 'coney_island/performer'
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module ConeyIsland
|
2
|
+
class Job
|
3
|
+
attr_accessor :delay, :timeout, :method_name, :class_name, :klass, :method_args, :id, :args,
|
4
|
+
:instance_id, :object, :metadata, :attempts, :retry_limit, :retry_on_exception
|
5
|
+
|
6
|
+
def initialize(metadata, args)
|
7
|
+
@args = args
|
8
|
+
@id = SecureRandom.uuid
|
9
|
+
self.log.info ("Starting job #{@id}: #{@args}")
|
10
|
+
@delay = args['delay'].to_i if args['delay']
|
11
|
+
@timeout = args['timeout']
|
12
|
+
@method_name = args['method_name']
|
13
|
+
@instance_id = args['instance_id']
|
14
|
+
@class_name = args['klass']
|
15
|
+
@klass = @class_name.constantize
|
16
|
+
@method_args = args['args']
|
17
|
+
@attempts = args['attempt_count'] || 1
|
18
|
+
@retry_limit = args['retry_limit'] || 3
|
19
|
+
@retry_on_exception = args['retry_on_exception']
|
20
|
+
@metadata = metadata
|
21
|
+
if @klass.respond_to? :coney_island_settings
|
22
|
+
@delay ||= @klass.coney_island_settings[:delay]
|
23
|
+
@timeout ||= @klass.coney_island_settings[:timeout]
|
24
|
+
end
|
25
|
+
@timeout ||= BG_TIMEOUT_SECONDS
|
26
|
+
if @instance_id.present?
|
27
|
+
@object = @klass.find(@instance_id)
|
28
|
+
else
|
29
|
+
@object = @klass
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def ticket
|
34
|
+
ConeyIsland::Worker.ticket
|
35
|
+
end
|
36
|
+
|
37
|
+
def log
|
38
|
+
ConeyIsland::Worker.log
|
39
|
+
end
|
40
|
+
|
41
|
+
def execute_job_method
|
42
|
+
if method_args.present? and method_args.length > 0
|
43
|
+
object.send method_name, *method_args
|
44
|
+
else
|
45
|
+
object.send method_name
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def handle_job
|
50
|
+
Timeout::timeout(timeout) do
|
51
|
+
execute_job_method
|
52
|
+
end
|
53
|
+
rescue Timeout::Error => e
|
54
|
+
if self.attempts >= self.retry_limit
|
55
|
+
log.error("Request #{self.id} timed out after #{self.timeout} seconds, bailing out after 3 attempts")
|
56
|
+
ConeyIsland.poke_the_badger(e, {work_queue: self.ticket, job_payload: self.args, reason: 'Bailed out after 3 attempts'})
|
57
|
+
else
|
58
|
+
log.error("Request #{self.id} timed out after #{self.timeout} seconds on attempt number #{self.attempts}, retrying...")
|
59
|
+
self.attempts += 1
|
60
|
+
ConeyIsland.submit(self.klass, self.method_name, self.resubmit_args)
|
61
|
+
end
|
62
|
+
rescue Exception => e
|
63
|
+
if retry_on_exception && (self.attempts < self.retry_limit)
|
64
|
+
self.attempts += 1
|
65
|
+
ConeyIsland.poke_the_badger(e, {work_queue: self.ticket, job_payload: self.args, attempt_count: self.attempts})
|
66
|
+
ConeyIsland.submit(self.klass, self.method_name, self.resubmit_args)
|
67
|
+
log.error("Resubmitting after error on attempt ##{self.attempts}:")
|
68
|
+
else
|
69
|
+
ConeyIsland.poke_the_badger(e, {work_queue: self.ticket, job_payload: self.args})
|
70
|
+
log.error("Bailing out after error on final attempt ##{self.attempts}:")
|
71
|
+
end
|
72
|
+
log.error("Error executing #{self.class_name}##{self.method_name} #{self.id} for id #{self.instance_id} with args #{self.args}:")
|
73
|
+
log.error(e.message)
|
74
|
+
log.error(e.backtrace.join("\n"))
|
75
|
+
ensure
|
76
|
+
finalize_job
|
77
|
+
end
|
78
|
+
|
79
|
+
def resubmit_args
|
80
|
+
args.select{|key,val| ['timeout','retry_on_exception','retry_limit','args','instance_id'].include? key}.merge(
|
81
|
+
'attempt_count' => self.attempts, 'work_queue' => self.ticket, 'delay' => ConeyIsland.delay_seed**(self.attempts - 1))
|
82
|
+
end
|
83
|
+
|
84
|
+
def finalize_job
|
85
|
+
metadata.ack unless ConeyIsland.running_inline?
|
86
|
+
log.info("finished job #{id}")
|
87
|
+
ConeyIsland::Worker.running_jobs.delete self
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module ConeyIsland
|
2
|
+
module Performer
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def set_background_defaults(work_queue: nil, delay: nil, timeout: nil)
|
10
|
+
self.coney_island_settings[:work_queue] = work_queue
|
11
|
+
self.coney_island_settings[:delay] = delay
|
12
|
+
self.coney_island_settings[:timeout] = timeout
|
13
|
+
end
|
14
|
+
|
15
|
+
def coney_island_settings
|
16
|
+
@coney_island_settings ||= {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_instance_async_methods(*synchronous_methods)
|
20
|
+
synchronous_methods.each do |synchronous_method|
|
21
|
+
define_method("#{synchronous_method}_async") do |*args|
|
22
|
+
unless self.respond_to? :id
|
23
|
+
raise StandardError.new(
|
24
|
+
"#{synchronous_method} is not an instance method, ConeyIsland can't async it via :create_instance_async_methods")
|
25
|
+
end
|
26
|
+
ConeyIsland.submit(self.class, synchronous_method, instance_id: self.id, args: args)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def create_class_async_methods(*synchronous_methods)
|
32
|
+
synchronous_methods.each do |synchronous_method|
|
33
|
+
define_singleton_method("#{synchronous_method}_async") do |*args|
|
34
|
+
ConeyIsland.submit(self, synchronous_method, args: args)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -9,6 +9,10 @@ module ConeyIsland
|
|
9
9
|
@run_inline = false
|
10
10
|
end
|
11
11
|
|
12
|
+
def self.running_inline?
|
13
|
+
@run_inline
|
14
|
+
end
|
15
|
+
|
12
16
|
def self.submit(*args)
|
13
17
|
if RequestStore.store[:cache_jobs]
|
14
18
|
job_id = SecureRandom.uuid
|
@@ -93,19 +97,24 @@ module ConeyIsland
|
|
93
97
|
def self.publish_job(args, job_id = nil)
|
94
98
|
if (args.first.is_a? Class or args.first.is_a? Module) and (args[1].is_a? String or args[1].is_a? Symbol) and args.last.is_a? Hash and 3 == args.length
|
95
99
|
klass = args.shift
|
96
|
-
|
100
|
+
klass_name = klass.name
|
101
|
+
|
97
102
|
method_name = args.shift
|
98
103
|
job_args = args.shift
|
99
104
|
job_args ||= {}
|
100
|
-
job_args['klass'] =
|
105
|
+
job_args['klass'] = klass_name
|
101
106
|
job_args['method_name'] = method_name
|
102
107
|
if @run_inline
|
103
108
|
job_args.stringify_keys!
|
104
|
-
ConeyIsland::
|
109
|
+
job = ConeyIsland::Job.new(nil, job_args)
|
110
|
+
job.handle_job
|
105
111
|
else
|
106
112
|
work_queue = job_args.delete :work_queue
|
113
|
+
if klass.respond_to? :coney_island_settings
|
114
|
+
work_queue ||= klass.coney_island_settings[:work_queue]
|
115
|
+
end
|
107
116
|
work_queue ||= 'default'
|
108
|
-
self.exchange.publish(
|
117
|
+
self.exchange.publish(job_args.to_json, {routing_key: "carousels.#{work_queue}"}) do
|
109
118
|
RequestStore.store[:jobs].delete job_id if RequestStore.store[:jobs] && job_id.present?
|
110
119
|
end
|
111
120
|
end
|
data/lib/coney_island/version.rb
CHANGED
data/lib/coney_island/worker.rb
CHANGED
@@ -17,12 +17,24 @@ module ConeyIsland
|
|
17
17
|
@log = log_thing
|
18
18
|
end
|
19
19
|
|
20
|
-
def self.
|
21
|
-
@
|
20
|
+
def self.running_jobs
|
21
|
+
@running_jobs ||= []
|
22
22
|
end
|
23
23
|
|
24
|
-
def self.
|
25
|
-
@
|
24
|
+
def self.clear_running_jobs
|
25
|
+
@running_jobs = []
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.ticket
|
29
|
+
@ticket
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.ticket=(some_ticket)
|
33
|
+
@ticket = some_ticket
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.reset_child_pids
|
37
|
+
@child_pids = []
|
26
38
|
end
|
27
39
|
|
28
40
|
def self.initialize_background
|
@@ -42,7 +54,7 @@ module ConeyIsland
|
|
42
54
|
@worker_count = @instance_config[:worker_count] if @instance_config
|
43
55
|
@worker_count ||= 1
|
44
56
|
@child_count = @worker_count - 1
|
45
|
-
|
57
|
+
reset_child_pids
|
46
58
|
|
47
59
|
@full_instance_name = @ticket
|
48
60
|
|
@@ -146,16 +158,15 @@ module ConeyIsland
|
|
146
158
|
|
147
159
|
def self.handle_incoming_message(metadata,payload)
|
148
160
|
begin
|
149
|
-
job_id = SecureRandom.uuid
|
150
|
-
self.job_attempts[job_id] = 1
|
151
161
|
args = JSON.parse(payload)
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
162
|
+
job = Job.new(metadata, args)
|
163
|
+
self.running_jobs << job
|
164
|
+
if job.delay.present?
|
165
|
+
EventMachine.add_timer(job.delay) do
|
166
|
+
job.handle_job
|
156
167
|
end
|
157
168
|
else
|
158
|
-
|
169
|
+
job.handle_job
|
159
170
|
end
|
160
171
|
rescue Timeout::Error => e
|
161
172
|
ConeyIsland.poke_the_badger(e, {code_source: 'ConeyIsland', job_payload: args, reason: 'timeout in subscribe code before calling job method'})
|
@@ -165,60 +176,6 @@ module ConeyIsland
|
|
165
176
|
end
|
166
177
|
end
|
167
178
|
|
168
|
-
def self.handle_job(metadata,args,job_id)
|
169
|
-
timeout = args['timeout']
|
170
|
-
timeout ||= BG_TIMEOUT_SECONDS
|
171
|
-
begin
|
172
|
-
Timeout::timeout(timeout) do
|
173
|
-
self.execute_job_method(args)
|
174
|
-
end
|
175
|
-
rescue Timeout::Error => e
|
176
|
-
if self.job_attempts.has_key? job_id
|
177
|
-
if self.job_attempts[job_id] >= 3
|
178
|
-
self.log.error("Request #{job_id} timed out after #{timeout} seconds, bailing out after 3 attempts")
|
179
|
-
self.finalize_job(metadata,job_id)
|
180
|
-
ConeyIsland.poke_the_badger(e, {work_queue: @ticket, job_payload: args, reason: 'Bailed out after 3 attempts'})
|
181
|
-
else
|
182
|
-
self.log.error("Request #{job_id} timed out after #{timeout} seconds on attempt number #{self.job_attempts[job_id]}, retrying...")
|
183
|
-
self.job_attempts[job_id] += 1
|
184
|
-
self.handle_job(metadata,args,job_id)
|
185
|
-
end
|
186
|
-
end
|
187
|
-
rescue Exception => e
|
188
|
-
ConeyIsland.poke_the_badger(e, {work_queue: @ticket, job_payload: args})
|
189
|
-
self.log.error("Error executing #{args['klass']}##{args['method_name']} #{job_id} for id #{args['instance_id']} with args #{args}:")
|
190
|
-
self.log.error(e.message)
|
191
|
-
self.log.error(e.backtrace.join("\n"))
|
192
|
-
self.finalize_job(metadata,job_id)
|
193
|
-
else
|
194
|
-
self.finalize_job(metadata,job_id)
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
def self.execute_job_method(args)
|
199
|
-
class_name = args['klass']
|
200
|
-
method_name = args['method_name']
|
201
|
-
klass = class_name.constantize
|
202
|
-
method_args = args['args']
|
203
|
-
if args.has_key? 'instance_id'
|
204
|
-
instance_id = args['instance_id']
|
205
|
-
object = klass.find(instance_id)
|
206
|
-
else
|
207
|
-
object = klass
|
208
|
-
end
|
209
|
-
if method_args and method_args.length > 0
|
210
|
-
object.send method_name, *method_args
|
211
|
-
else
|
212
|
-
object.send method_name
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
|
-
def self.finalize_job(metadata,job_id)
|
217
|
-
metadata.ack
|
218
|
-
self.log.info("finished job #{job_id}")
|
219
|
-
self.job_attempts.delete job_id
|
220
|
-
end
|
221
|
-
|
222
179
|
def self.handle_missing_children
|
223
180
|
@child_pids.each do |child_pid|
|
224
181
|
begin
|
@@ -231,7 +188,7 @@ module ConeyIsland
|
|
231
188
|
|
232
189
|
def self.abandon_and_shutdown
|
233
190
|
self.log.info("Lost RabbitMQ connection, abandoning current jobs and shutting down")
|
234
|
-
self.
|
191
|
+
self.clear_running_jobs
|
235
192
|
self.shutdown('TERM')
|
236
193
|
end
|
237
194
|
|
@@ -242,8 +199,8 @@ module ConeyIsland
|
|
242
199
|
end
|
243
200
|
@queue.unsubscribe rescue nil
|
244
201
|
EventMachine.add_periodic_timer(1) do
|
245
|
-
if self.
|
246
|
-
self.log.info("Waiting for #{self.
|
202
|
+
if self.running_jobs.any?
|
203
|
+
self.log.info("Waiting for #{self.running_jobs.length} requests to finish")
|
247
204
|
else
|
248
205
|
self.log.info("Shutting down coney island #{@ticket}")
|
249
206
|
EventMachine.stop
|
@@ -252,4 +209,4 @@ module ConeyIsland
|
|
252
209
|
end
|
253
210
|
|
254
211
|
end
|
255
|
-
end
|
212
|
+
end
|
data/test/dummy/log/test.log
CHANGED
@@ -2,19 +2,52 @@ ConeyIsland::Submitter.submit! about to iterate over this many jobs: 1
|
|
2
2
|
Failed to connecto to RabbitMQ Attempt #1 time(s), trying again in 0 seconds...
|
3
3
|
ConeyIsland::Submitter.submit! about to iterate over this many jobs: 1
|
4
4
|
Failed to connecto to RabbitMQ Attempt #1 time(s), trying again in 0 seconds...
|
5
|
+
Failed to connecto to RabbitMQ Attempt #1 time(s), trying again in 0 seconds...
|
6
|
+
ConeyIsland::Submitter.submit! about to iterate over this many jobs: 1
|
7
|
+
ConeyIsland::Submitter.submit! about to iterate over this many jobs: 1
|
8
|
+
Failed to connecto to RabbitMQ Attempt #1 time(s), trying again in 0 seconds...
|
9
|
+
Failed to connecto to RabbitMQ Attempt #1 time(s), trying again in 0 seconds...
|
10
|
+
ConeyIsland::Submitter.submit! about to iterate over this many jobs: 1
|
11
|
+
mocked method :publish expects 0 arguments, got 2
|
12
|
+
undefined method `publish' for nil:NilClass
|
13
|
+
undefined method `publish' for nil:NilClass
|
14
|
+
undefined method `publish' for nil:NilClass
|
15
|
+
undefined method `publish' for nil:NilClass
|
16
|
+
undefined method `publish' for nil:NilClass
|
17
|
+
undefined method `publish' for nil:NilClass
|
18
|
+
undefined method `publish' for nil:NilClass
|
19
|
+
undefined method `publish' for nil:NilClass
|
20
|
+
undefined method `publish' for nil:NilClass
|
21
|
+
undefined method `publish' for nil:NilClass
|
22
|
+
undefined method `publish' for nil:NilClass
|
23
|
+
undefined method `publish' for nil:NilClass
|
24
|
+
undefined method `publish' for nil:NilClass
|
25
|
+
mocked method :publish expects 0 arguments, got 2
|
26
|
+
mocked method :publish expects 0 arguments, got 2
|
27
|
+
mocked method :publish expects 0 arguments, got 2
|
28
|
+
Failed to connecto to RabbitMQ Attempt #1 time(s), trying again in 0 seconds...
|
29
|
+
ConeyIsland::Submitter.submit! about to iterate over this many jobs: 1
|
30
|
+
Failed to connecto to RabbitMQ Attempt #1 time(s), trying again in 0 seconds...
|
31
|
+
ConeyIsland::Submitter.submit! about to iterate over this many jobs: 1
|
32
|
+
Failed to connecto to RabbitMQ Attempt #1 time(s), trying again in 0 seconds...
|
33
|
+
ConeyIsland::Submitter.submit! about to iterate over this many jobs: 1
|
5
34
|
ConeyIsland::Submitter.submit! about to iterate over this many jobs: 1
|
6
35
|
Failed to connecto to RabbitMQ Attempt #1 time(s), trying again in 0 seconds...
|
7
36
|
ConeyIsland::Submitter.submit! about to iterate over this many jobs: 1
|
8
37
|
Failed to connecto to RabbitMQ Attempt #1 time(s), trying again in 0 seconds...
|
9
38
|
Failed to connecto to RabbitMQ Attempt #1 time(s), trying again in 0 seconds...
|
10
39
|
ConeyIsland::Submitter.submit! about to iterate over this many jobs: 1
|
40
|
+
ConeyIsland::Submitter.submit! about to iterate over this many jobs: 1
|
11
41
|
Failed to connecto to RabbitMQ Attempt #1 time(s), trying again in 0 seconds...
|
12
42
|
ConeyIsland::Submitter.submit! about to iterate over this many jobs: 1
|
43
|
+
Failed to connecto to RabbitMQ Attempt #1 time(s), trying again in 0 seconds...
|
13
44
|
ConeyIsland::Submitter.submit! about to iterate over this many jobs: 1
|
14
45
|
Failed to connecto to RabbitMQ Attempt #1 time(s), trying again in 0 seconds...
|
15
46
|
ConeyIsland::Submitter.submit! about to iterate over this many jobs: 1
|
16
47
|
Failed to connecto to RabbitMQ Attempt #1 time(s), trying again in 0 seconds...
|
17
48
|
Failed to connecto to RabbitMQ Attempt #1 time(s), trying again in 0 seconds...
|
18
49
|
ConeyIsland::Submitter.submit! about to iterate over this many jobs: 1
|
50
|
+
Failed to connecto to RabbitMQ Attempt #1 time(s), trying again in 0 seconds...
|
19
51
|
ConeyIsland::Submitter.submit! about to iterate over this many jobs: 1
|
20
52
|
Failed to connecto to RabbitMQ Attempt #1 time(s), trying again in 0 seconds...
|
53
|
+
ConeyIsland::Submitter.submit! about to iterate over this many jobs: 1
|
data/test/job_test.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class JobTest < MiniTest::Test
|
4
|
+
describe "ConeyIsland::Job" do
|
5
|
+
describe "handling a job" do
|
6
|
+
before do
|
7
|
+
@metadata = MiniTest::Mock.new
|
8
|
+
@metadata.expect :ack, nil
|
9
|
+
end
|
10
|
+
|
11
|
+
it "retries on timeout with correct initial attempt_count and delay" do
|
12
|
+
job = ConeyIsland::Job.new(@metadata,
|
13
|
+
{ 'klass' => 'TestModel',
|
14
|
+
'method_name' => :take_too_long,
|
15
|
+
'timeout' => 0.0001 }
|
16
|
+
)
|
17
|
+
capture_submissions = lambda { |klass,method_name,options|
|
18
|
+
::JobTest.messages[:job_options] ||= []
|
19
|
+
::JobTest.messages[:job_options] << options
|
20
|
+
}
|
21
|
+
ConeyIsland.stub(:submit, capture_submissions) do
|
22
|
+
job.handle_job
|
23
|
+
end
|
24
|
+
::JobTest.messages[:job_options].last['attempt_count'].must_equal 2
|
25
|
+
::JobTest.messages[:job_options].last['delay'].must_equal 2
|
26
|
+
end
|
27
|
+
|
28
|
+
it "retries on timeout with correct subsequent attempt_count and delay" do
|
29
|
+
job = ConeyIsland::Job.new(@metadata,
|
30
|
+
{ 'klass' => 'TestModel',
|
31
|
+
'method_name' => :take_too_long,
|
32
|
+
'timeout' => 0.0001,
|
33
|
+
'attempt_count' => 2,
|
34
|
+
'delay' => 2 }
|
35
|
+
)
|
36
|
+
capture_submissions = lambda { |klass,method_name,options|
|
37
|
+
::JobTest.messages[:job_options] ||= []
|
38
|
+
::JobTest.messages[:job_options] << options
|
39
|
+
}
|
40
|
+
ConeyIsland.stub(:submit, capture_submissions) do
|
41
|
+
job.handle_job
|
42
|
+
end
|
43
|
+
::JobTest.messages[:job_options].last['attempt_count'].must_equal 3
|
44
|
+
::JobTest.messages[:job_options].last['delay'].must_equal 4
|
45
|
+
end
|
46
|
+
|
47
|
+
it "bails out on timeout if retry limit reached" do
|
48
|
+
job = ConeyIsland::Job.new(@metadata,
|
49
|
+
{ 'klass' => 'TestModel',
|
50
|
+
'method_name' => :take_too_long,
|
51
|
+
'timeout' => 0.0001,
|
52
|
+
'attempt_count' => 3,
|
53
|
+
'delay' => 2 }
|
54
|
+
)
|
55
|
+
capture_submissions = lambda { |klass,method_name,options|
|
56
|
+
fail "Should not have called ConeyIsland.submit, should have bailed out instead"
|
57
|
+
}
|
58
|
+
ConeyIsland.stub(:submit, capture_submissions) do
|
59
|
+
job.handle_job
|
60
|
+
end
|
61
|
+
@metadata.verify #we ack the message bus when we bail out
|
62
|
+
end
|
63
|
+
|
64
|
+
it "retries on exception with correct initial attempt_count and delay if retry_on_exception set" do
|
65
|
+
job = ConeyIsland::Job.new(@metadata,
|
66
|
+
{ 'klass' => 'TestModel',
|
67
|
+
'method_name' => :throw_an_error,
|
68
|
+
'retry_on_exception' => true }
|
69
|
+
)
|
70
|
+
capture_submissions = lambda { |klass,method_name,options|
|
71
|
+
::JobTest.messages[:job_options] ||= []
|
72
|
+
::JobTest.messages[:job_options] << options
|
73
|
+
}
|
74
|
+
ConeyIsland.stub(:submit, capture_submissions) do
|
75
|
+
job.handle_job
|
76
|
+
end
|
77
|
+
::JobTest.messages[:job_options].last['attempt_count'].must_equal 2
|
78
|
+
::JobTest.messages[:job_options].last['delay'].must_equal 2
|
79
|
+
end
|
80
|
+
|
81
|
+
it "bails out on exception if retry_on_exception set and retry_limit reached" do
|
82
|
+
job = ConeyIsland::Job.new(@metadata,
|
83
|
+
{ 'klass' => 'TestModel',
|
84
|
+
'method_name' => :throw_an_error,
|
85
|
+
'retry_on_exception' => true,
|
86
|
+
'retry_limit' => 2,
|
87
|
+
'attempt_count' => 2}
|
88
|
+
)
|
89
|
+
capture_submissions = lambda { |klass,method_name,options|
|
90
|
+
fail "Should not have called ConeyIsland.submit, should have bailed out instead"
|
91
|
+
}
|
92
|
+
ConeyIsland.stub(:submit, capture_submissions) do
|
93
|
+
job.handle_job
|
94
|
+
end
|
95
|
+
@metadata.verify #we ack the message bus when we bail out
|
96
|
+
end
|
97
|
+
|
98
|
+
it "bails out on exception if retry_on_exception not set" do
|
99
|
+
job = ConeyIsland::Job.new(@metadata,
|
100
|
+
{ 'klass' => 'TestModel',
|
101
|
+
'method_name' => :throw_an_error }
|
102
|
+
)
|
103
|
+
capture_submissions = lambda { |klass,method_name,options|
|
104
|
+
fail "Should not have called ConeyIsland.submit, should have bailed out instead"
|
105
|
+
}
|
106
|
+
ConeyIsland.stub(:submit, capture_submissions) do
|
107
|
+
job.handle_job
|
108
|
+
end
|
109
|
+
@metadata.verify #we ack the message bus when we bail out
|
110
|
+
end
|
111
|
+
|
112
|
+
it "sends exeptions to a notification service when bailing out" do
|
113
|
+
@poke_the_badger = MiniTest::Mock.new
|
114
|
+
@poke_the_badger.expect :call, nil, [Exception,Hash]
|
115
|
+
job = ConeyIsland::Job.new(@metadata,
|
116
|
+
{ 'klass' => 'TestModel',
|
117
|
+
'method_name' => :throw_an_error }
|
118
|
+
)
|
119
|
+
ConeyIsland.stub(:poke_the_badger,@poke_the_badger) do
|
120
|
+
job.handle_job
|
121
|
+
end
|
122
|
+
@poke_the_badger.verify
|
123
|
+
end
|
124
|
+
|
125
|
+
it "calls find on the submitted class if an instance_id is present" do
|
126
|
+
TestModel.new('id' => 'my_id')
|
127
|
+
job = ConeyIsland::Job.new(@metadata,{
|
128
|
+
'klass' => 'TestModel',
|
129
|
+
'method_name' => :set_color,
|
130
|
+
'instance_id' => 'my_id',
|
131
|
+
'args' => ['green']
|
132
|
+
})
|
133
|
+
job.handle_job
|
134
|
+
my_thing = TestModel.find('my_id')
|
135
|
+
my_thing.color.must_equal 'green'
|
136
|
+
end
|
137
|
+
|
138
|
+
it "acknowledges job completion to the message bus" do
|
139
|
+
ConeyIsland.stop_running_inline
|
140
|
+
job = ConeyIsland::Job.new(@metadata,
|
141
|
+
{ 'klass' => 'TestModel',
|
142
|
+
'method_name' => :add_to_list,
|
143
|
+
'args' => [[]]})
|
144
|
+
job.handle_job
|
145
|
+
@metadata.verify
|
146
|
+
ConeyIsland::Worker.running_jobs.must_be :empty?
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class PerformerTest < MiniTest::Test
|
4
|
+
describe "settings" do
|
5
|
+
it 'sets default class-level settings' do
|
6
|
+
job = ConeyIsland::Job.new(nil,
|
7
|
+
{ 'klass' => 'MyPerformer',
|
8
|
+
'method_name' => 'set_color',
|
9
|
+
'args' => ['carmine'] }
|
10
|
+
)
|
11
|
+
job.delay.must_equal 1
|
12
|
+
job.timeout.must_equal 5
|
13
|
+
end
|
14
|
+
it 'overrides class-level settings with job-level settings' do
|
15
|
+
job = ConeyIsland::Job.new(nil,
|
16
|
+
{ 'klass' => 'MyPerformer',
|
17
|
+
'method_name' => 'set_color',
|
18
|
+
'args' => ['carmine'],
|
19
|
+
'delay' => 3,
|
20
|
+
'timeout' => 10 }
|
21
|
+
)
|
22
|
+
job.delay.must_equal 3
|
23
|
+
job.timeout.must_equal 10
|
24
|
+
end
|
25
|
+
it 'sets work queue for the class' do
|
26
|
+
capture_publish = proc do |payload,options|
|
27
|
+
end
|
28
|
+
@exchange = Minitest::Mock.new
|
29
|
+
def @exchange.publish(payload,options,&blk)
|
30
|
+
::PerformerTest.messages[:publish_hash] = options
|
31
|
+
end
|
32
|
+
ConeyIsland::Submitter.stub(:handle_connection, nil) do
|
33
|
+
ConeyIsland::Submitter.stub(:exchange, @exchange) do
|
34
|
+
ConeyIsland::Submitter.stop_running_inline
|
35
|
+
ConeyIsland::Submitter.submit(MyPerformer, :add_to_list, args: [[]])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
@exchange.verify
|
39
|
+
::PerformerTest.messages[:publish_hash][:routing_key].must_equal "carousels.cyclone"
|
40
|
+
end
|
41
|
+
it 'overrides the class work queue with the job-level work queue' do
|
42
|
+
capture_publish = proc do |payload,options|
|
43
|
+
end
|
44
|
+
@exchange = Minitest::Mock.new
|
45
|
+
def @exchange.publish(payload,options,&blk)
|
46
|
+
::PerformerTest.messages[:publish_hash] = options
|
47
|
+
end
|
48
|
+
ConeyIsland::Submitter.stub(:handle_connection, nil) do
|
49
|
+
ConeyIsland::Submitter.stub(:exchange, @exchange) do
|
50
|
+
ConeyIsland::Submitter.stop_running_inline
|
51
|
+
ConeyIsland::Submitter.submit(MyPerformer, :add_to_list, work_queue: 'boardwalk', args: [[]])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
@exchange.verify
|
55
|
+
::PerformerTest.messages[:publish_hash][:routing_key].must_equal "carousels.boardwalk"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe 'async methods' do
|
60
|
+
it 'creates async instance methods' do
|
61
|
+
my_performer = MyPerformer.new(7)
|
62
|
+
ConeyIsland.run_inline
|
63
|
+
my_performer.set_color_async 'sage'
|
64
|
+
my_performer.color.must_equal 'sage'
|
65
|
+
end
|
66
|
+
it 'creates async class methods' do
|
67
|
+
ConeyIsland.run_inline
|
68
|
+
MyPerformer.set_tone_async 'contemplative'
|
69
|
+
MyPerformer.tone.must_equal 'contemplative'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class MyPerformer
|
75
|
+
include ConeyIsland::Performer
|
76
|
+
set_background_defaults work_queue: 'cyclone', delay: 1, timeout: 5
|
77
|
+
create_class_async_methods :set_tone
|
78
|
+
create_instance_async_methods :set_color
|
79
|
+
|
80
|
+
def self.instances
|
81
|
+
@instances ||= {}
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.find(id)
|
85
|
+
instances[id]
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.set_tone(tone)
|
89
|
+
@tone = tone
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.tone
|
93
|
+
@tone
|
94
|
+
end
|
95
|
+
|
96
|
+
def id
|
97
|
+
@id
|
98
|
+
end
|
99
|
+
|
100
|
+
def initialize(id)
|
101
|
+
@id = id
|
102
|
+
self.class.instances[id] = self
|
103
|
+
end
|
104
|
+
|
105
|
+
def set_color(color)
|
106
|
+
@color = color
|
107
|
+
end
|
108
|
+
|
109
|
+
def color
|
110
|
+
@color
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
data/test/submitter_test.rb
CHANGED
@@ -4,13 +4,13 @@ class SubmitterTest < MiniTest::Test
|
|
4
4
|
describe "ConeyIsland::Submitter" do
|
5
5
|
describe "running jobs inline" do
|
6
6
|
it "calls the worker directly" do
|
7
|
-
@
|
8
|
-
@
|
9
|
-
ConeyIsland::
|
7
|
+
@job = Minitest::Mock.new
|
8
|
+
@job.expect :handle_job, nil
|
9
|
+
ConeyIsland::Job.stub(:new,@job) do
|
10
10
|
ConeyIsland::Submitter.run_inline
|
11
11
|
ConeyIsland::Submitter.submit(TestModel, :add_to_list, args: [[]])
|
12
12
|
end
|
13
|
-
@
|
13
|
+
@job.verify
|
14
14
|
end
|
15
15
|
end
|
16
16
|
describe "running jobs in the background" do
|
data/test/test_helper.rb
CHANGED
@@ -18,6 +18,21 @@ if ActiveSupport::TestCase.method_defined?(:fixture_path=)
|
|
18
18
|
ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__)
|
19
19
|
end
|
20
20
|
|
21
|
+
class MiniTest::Test
|
22
|
+
def self.messages
|
23
|
+
@@messages[Thread.current.object_id] ||= {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.clear_messages
|
27
|
+
@@messages ||= {}
|
28
|
+
@@messages[Thread.current.object_id] = {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def setup
|
32
|
+
self.class.clear_messages
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
21
36
|
class TestModel
|
22
37
|
|
23
38
|
def self.add_to_list(array)
|
data/test/worker_test.rb
CHANGED
@@ -7,46 +7,26 @@ class WorkerTest < MiniTest::Test
|
|
7
7
|
@metadata = MiniTest::Mock.new
|
8
8
|
@metadata.expect :ack, nil
|
9
9
|
end
|
10
|
-
it
|
11
|
-
|
12
|
-
|
13
|
-
ConeyIsland::Worker.handle_job(@metadata,{
|
14
|
-
'klass' => 'TestModel',
|
15
|
-
'method_name' => :take_too_long,
|
16
|
-
'timeout' => 0.0001
|
17
|
-
},'my_job_id')
|
10
|
+
it 'handles incoming messages' do
|
11
|
+
@capture_running_jobs = []
|
12
|
+
def @capture_running_jobs.delete(item)
|
18
13
|
end
|
19
|
-
ConeyIsland::Worker.
|
20
|
-
|
21
|
-
|
22
|
-
@poke_the_badger = MiniTest::Mock.new
|
23
|
-
@poke_the_badger.expect :call, nil, [Exception,Hash]
|
24
|
-
ConeyIsland.stub(:poke_the_badger,@poke_the_badger) do
|
25
|
-
ConeyIsland::Worker.handle_job(@metadata,{
|
26
|
-
'klass' => 'TestModel',
|
27
|
-
'method_name' => :throw_an_error
|
28
|
-
},'my_job_id')
|
14
|
+
ConeyIsland::Worker.stub :running_jobs, @capture_running_jobs do
|
15
|
+
ConeyIsland::Worker.handle_incoming_message(@metadata,
|
16
|
+
"{\"klass\":\"TestModel\",\"method_name\":\"add_to_list\",\"args\":[[]]}")
|
29
17
|
end
|
30
|
-
@
|
31
|
-
end
|
32
|
-
it "calls find on the submitted class if an instance_id is present" do
|
33
|
-
TestModel.new('id' => 'my_id')
|
34
|
-
ConeyIsland::Worker.handle_job(@metadata,{
|
35
|
-
'klass' => 'TestModel',
|
36
|
-
'method_name' => :set_color,
|
37
|
-
'instance_id' => 'my_id',
|
38
|
-
'args' => ['green']
|
39
|
-
},'my_job_id')
|
40
|
-
my_thing = TestModel.find('my_id')
|
41
|
-
my_thing.color.must_equal 'green'
|
18
|
+
@capture_running_jobs.first.args['method_name'].must_equal 'add_to_list'
|
42
19
|
end
|
43
|
-
it
|
44
|
-
ConeyIsland::Worker.
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
20
|
+
it 'passes work_queue to the job' do
|
21
|
+
ConeyIsland::Worker.ticket = 'test_queue'
|
22
|
+
@capture_running_jobs = []
|
23
|
+
def @capture_running_jobs.delete(item)
|
24
|
+
end
|
25
|
+
ConeyIsland::Worker.stub :running_jobs, @capture_running_jobs do
|
26
|
+
ConeyIsland::Worker.handle_incoming_message(@metadata,
|
27
|
+
"{\"klass\":\"TestModel\",\"method_name\":\"add_to_list\",\"args\":[[]]}")
|
28
|
+
end
|
29
|
+
@capture_running_jobs.first.ticket.must_equal 'test_queue'
|
50
30
|
end
|
51
31
|
end
|
52
32
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: coney_island
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.9'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Draut
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-
|
12
|
+
date: 2015-03-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -104,14 +104,15 @@ extensions: []
|
|
104
104
|
extra_rdoc_files: []
|
105
105
|
files:
|
106
106
|
- MIT-LICENSE
|
107
|
-
- README.rdoc
|
108
107
|
- Rakefile
|
109
108
|
- bin/coney_island
|
110
109
|
- lib/coney_island.rb
|
111
110
|
- lib/coney_island/coney_island_adapter.rb
|
111
|
+
- lib/coney_island/job.rb
|
112
112
|
- lib/coney_island/job_argument_error.rb
|
113
113
|
- lib/coney_island/notifiers/airbrake_notifier.rb
|
114
114
|
- lib/coney_island/notifiers/honeybadger_notifier.rb
|
115
|
+
- lib/coney_island/performer.rb
|
115
116
|
- lib/coney_island/railtie.rb
|
116
117
|
- lib/coney_island/submitter.rb
|
117
118
|
- lib/coney_island/version.rb
|
@@ -153,6 +154,8 @@ files:
|
|
153
154
|
- test/dummy/public/422.html
|
154
155
|
- test/dummy/public/500.html
|
155
156
|
- test/dummy/public/favicon.ico
|
157
|
+
- test/job_test.rb
|
158
|
+
- test/performer_test.rb
|
156
159
|
- test/submitter_test.rb
|
157
160
|
- test/test_helper.rb
|
158
161
|
- test/worker_test.rb
|
@@ -220,6 +223,8 @@ test_files:
|
|
220
223
|
- test/dummy/public/favicon.ico
|
221
224
|
- test/dummy/Rakefile
|
222
225
|
- test/dummy/README.rdoc
|
226
|
+
- test/job_test.rb
|
227
|
+
- test/performer_test.rb
|
223
228
|
- test/submitter_test.rb
|
224
229
|
- test/test_helper.rb
|
225
230
|
- test/worker_test.rb
|
data/README.rdoc
DELETED