coney_island 0.0.4 → 0.0.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5ce6f8d79f2c7959d6f1d5650b494cae2a716ab2
4
- data.tar.gz: 8affe8a1cfcc6e8705da3337b3f7ed416534bab2
3
+ metadata.gz: fdf7b8e2dfbaa750deb86831a4c1a98b42e620b0
4
+ data.tar.gz: 05701ccb0b9c4c3cdf4f1d293270bc55062c8c26
5
5
  SHA512:
6
- metadata.gz: c8f1f23dfd854407b146fcf970eb05f3a14e2514f6146ac9273aeab36a441e7d8a7227c81ebdd780f4b0345908aa754075db33002b41abb80e1b96be7e6cb73c
7
- data.tar.gz: 402697481acad3b9a357fa192597864287ba4883efa3ecdcb2735c15cb0b7e103971914c88773c0615e20455811530ca7684a1ae624ea25ddfbc18a93cf64d9a
6
+ metadata.gz: be8931b31581fa30a558947a0625b672733b3b8dba793ef7887fed06ae659aa9f538452d30ae20d2f8bc36b97330b1844851258ee8992a31a45f051adc350001
7
+ data.tar.gz: 367b56c8b52f12e96f2451d80860e256767f013c3d3c19430822695da2923a08ef64d283771d150605be8ee3bdfd100b9b9baff8853b33c75f937373abb85b51
data/bin/coney_island CHANGED
@@ -7,4 +7,4 @@ require 'config/environment.rb'
7
7
 
8
8
  ConeyIsland.run_inline
9
9
  ConeyIsland.initialize_background
10
- ConeyIsland.start
10
+ ConeyIsland.start_worker
data/lib/coney_island.rb CHANGED
@@ -1,13 +1,8 @@
1
1
  module ConeyIsland
2
- BG_TIMEOUT_SECONDS = 30
3
2
 
4
- def self.run_inline
5
- @run_inline = true
6
- end
3
+ ### BEGIN configuration
7
4
 
8
- def self.notification_service=(service_name)
9
- @notifier = "ConeyIsland::Notifiers::#{service_name}Notifier".constantize
10
- end
5
+ BG_TIMEOUT_SECONDS = 30
11
6
 
12
7
  def self.amqp_connection
13
8
  @connection
@@ -18,13 +13,17 @@ module ConeyIsland
18
13
  end
19
14
 
20
15
  def self.amqp_parameters
21
- @amqp_parameters ||= (self.config.present? ? self.config['amqp_connection'] : nil)
16
+ @amqp_parameters ||= (self.config.present? ? self.config[:amqp_connection] : nil)
22
17
  end
23
18
 
24
19
  def self.handle_connection
25
20
  @connection ||= AMQP.connect(self.amqp_parameters)
26
21
  @channel ||= AMQP::Channel.new(@connection)
27
- @exchange ||= @channel.topic('coney_island')
22
+ self.exchange = @channel.topic('coney_island')
23
+ end
24
+
25
+ def self.exchange=(amqp_exchange)
26
+ @exchange ||= amqp_exchange
28
27
  end
29
28
 
30
29
  def self.exchange
@@ -35,266 +34,52 @@ module ConeyIsland
35
34
  @channel
36
35
  end
37
36
 
38
- def self.config
39
- if(File.exists?(File.join(Rails.root,"config","coney_island.yml")))
40
- @config = Psych.load(File.read(File.join(Rails.root,"config","coney_island.yml")))
41
- @config = @config[Rails.env]
42
- end
37
+ def self.config=(config_hash)
38
+ ConeyIsland::Worker.config=(config_hash)
43
39
  end
44
40
 
45
- #BEGIN web functionality
46
-
47
- def self.submit(*args)
48
- if RequestStore.store[:cache_jobs]
49
- RequestStore.store[:jobs].push args
50
- else
51
- self.submit!(args)
52
- end
53
- end
54
-
55
- def self.submit!(args)
56
- if @run_inline
57
- self.handle_publish(args)
58
- else
59
- EventMachine.next_tick do
60
- self.handle_publish(args)
61
- end
62
- end
63
- end
64
-
65
- def self.handle_publish(args)
66
- self.handle_connection unless @run_inline
67
- jobs = (args.first.is_a? Array) ? args : [args]
68
- jobs.each do |args|
69
- 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
70
- klass = args.shift
71
- klass = klass.name unless @run_inline
72
- method_name = args.shift
73
- job_args = args.shift
74
- job_args ||= {}
75
- job_args['klass'] = klass
76
- job_args['method_name'] = method_name
77
- if @run_inline
78
- job_args.stringify_keys!
79
- method_args = job_args['args']
80
- if job_args.has_key? 'instance_id'
81
- instance_id = job_args.delete 'instance_id'
82
- object = klass.find(instance_id)
83
- else
84
- object = klass
85
- end
86
- if method_args && (method_args.length > 0)
87
- object.send method_name, *method_args
88
- else
89
- object.send method_name
90
- end
91
- else
92
- work_queue = job_args.delete :work_queue
93
- work_queue ||= 'default'
94
- self.exchange.publish((job_args.to_json), routing_key: "carousels.#{work_queue}")
95
- end
96
- end
97
- RequestStore.store[:completed_jobs] ||= 0
98
- RequestStore.store[:completed_jobs] += 1
99
- end
100
- end
101
-
102
- def self.cache_jobs
103
- RequestStore.store[:cache_jobs] = true
104
- RequestStore.store[:jobs] = []
41
+ def self.config
42
+ ConeyIsland::Worker.config
105
43
  end
106
44
 
107
- def self.flush_jobs
108
- jobs = RequestStore.store[:jobs].dup
109
- self.submit!(jobs) if jobs.any?
110
- RequestStore.store[:jobs] = []
45
+ def self.initialize_background
46
+ ConeyIsland::Worker.initialize_background
111
47
  end
112
48
 
113
- def self.run_with_em(klass, method, *args)
114
- EventMachine.run do
115
- ConeyIsland.cache_jobs
116
- ConeyIsland.submit(klass, method, *args)
117
- ConeyIsland.flush_jobs
118
- ConeyIsland.publisher_shutdown
119
- end
49
+ def self.start_worker
50
+ ConeyIsland::Worker.start
120
51
  end
121
52
 
122
- def self.publisher_shutdown
123
- EventMachine.add_periodic_timer(1) do
124
- if RequestStore.store[:jobs] && (RequestStore.store[:jobs].length > RequestStore.store[:completed_jobs])
125
- Rails.logger.info("Waiting for #{RequestStore.store[:jobs].length - RequestStore.store[:completed_jobs]} publishes to finish")
126
- else
127
- Rails.logger.info("Shutting down coney island publisher")
128
- EventMachine.stop
129
- end
130
- end
53
+ def self.run_inline
54
+ ConeyIsland::Submitter.run_inline
131
55
  end
132
56
 
133
- # BEGIN background functionality
134
-
135
- def self.initialize_background
136
- ENV['NEW_RELIC_AGENT_ENABLED'] = 'false'
137
- ENV['NEWRELIC_ENABLE'] = 'false'
138
- @ticket = ARGV[0]
139
-
140
- #TODO: set an env variable or constant that can be checked by pubnub to decide sync or not
141
- @log_io = self.config['log'].constantize rescue nil
142
- @log_io ||= self.config['log']
143
- @log = Logger.new(@log_io)
144
-
145
- @ticket ||= 'default'
146
-
147
- @instance_config = self.config['carousels'][@ticket]
148
-
149
- @prefetch_count = @instance_config['prefetch_count'] if @instance_config
150
- @prefetch_count ||= 20
151
-
152
- @worker_count = @instance_config['worker_count'] if @instance_config
153
- @worker_count ||= 1
154
- @child_count = @worker_count - 1
155
- @child_pids = []
156
-
157
- @full_instance_name = @ticket
158
- @job_attempts = {}
159
-
160
- @log.level = @config['log_level']
161
- @log.info("config: #{self.config}")
162
-
57
+ def self.stop_running_inline
58
+ ConeyIsland::Submitter.stop_running_inline
163
59
  end
164
60
 
165
- def self.shutdown(signal)
166
- shutdown_time = Time.now
167
- @child_pids.each do |child_pid|
168
- Process.kill(signal, child_pid)
169
- end
170
- @queue.unsubscribe
171
- EventMachine.add_periodic_timer(1) do
172
- if @job_attempts.any?
173
- @log.info("Waiting for #{@job_attempts.length} requests to finish")
174
- else
175
- @log.info("Shutting down coney island #{@ticket}")
176
- EventMachine.stop
177
- end
178
- end
61
+ def self.cache_jobs
62
+ ConeyIsland::Submitter.cache_jobs
179
63
  end
180
64
 
181
- def self.handle_job(metadata,args,job_id)
182
- class_name = args['klass']
183
- method_name = args['method_name']
184
- klass = class_name.constantize
185
- method_args = args['args']
186
- timeout = args['timeout']
187
- timeout ||= BG_TIMEOUT_SECONDS
188
- begin
189
- Timeout::timeout(timeout) do
190
- if args.has_key? 'instance_id'
191
- instance_id = args['instance_id']
192
- object = klass.find(instance_id)
193
- else
194
- object = klass
195
- end
196
- if method_args and method_args.length > 0
197
- object.send method_name, *method_args
198
- else
199
- object.send method_name
200
- end
201
- end
202
- rescue Timeout::Error => e
203
- if @job_attempts.has_key? job_id
204
- if @job_attempts[job_id] >= 3
205
- @log.error("Request #{job_id} timed out after #{timeout} seconds, bailing out after 3 attempts")
206
- self.finalize_job(metadata,job_id)
207
- self.poke_the_badger(e, {work_queue: @ticket, job_payload: args, reason: 'Bailed out after 3 attempts'})
208
- else
209
- @log.error("Request #{job_id} timed out after #{timeout} seconds on attempt number #{@job_attempts[job_id]}, retrying...")
210
- @job_attempts[job_id] += 1
211
- self.handle_job(metadata,args,job_id)
212
- end
213
- end
214
- rescue Exception => e
215
- self.poke_the_badger(e, {work_queue: @ticket, job_payload: args})
216
- @log.error("Error executing #{class_name}##{method_name} #{job_id} for id #{args['instance_id']} with args #{args}:")
217
- @log.error(e.message)
218
- @log.error(e.backtrace.join("\n"))
219
- self.finalize_job(metadata,job_id)
220
- else
221
- self.finalize_job(metadata,job_id)
222
- end
65
+ def self.stop_caching_jobs
66
+ ConeyIsland::Submitter.stop_caching_jobs
223
67
  end
224
68
 
225
- def self.finalize_job(metadata,job_id)
226
- metadata.ack
227
- @log.info("finished job #{job_id}")
228
- @job_attempts.delete job_id
69
+ def self.flush_jobs
70
+ ConeyIsland::Submitter.flush_jobs
229
71
  end
230
72
 
231
- def self.start
232
- @child_count.times do
233
- child_pid = Process.fork
234
- unless child_pid
235
- @log.info("started child for ticket #{@ticket} with pid #{Process.pid}")
236
- break
237
- end
238
- @child_pids.push child_pid
239
- end
240
- defined?(ActiveRecord::Base) and
241
- ActiveRecord::Base.establish_connection
242
- EventMachine.run do
243
-
244
- Signal.trap('INT') do
245
- self.shutdown('INT')
246
- end
247
- Signal.trap('TERM') do
248
- self.shutdown('TERM')
249
- end
250
-
251
- self.handle_connection
252
- @log.info("Connecting to AMQP broker. Running #{AMQP::VERSION}")
253
-
254
- #send a heartbeat every 15 seconds to avoid aggresive network configurations that close quiet connections
255
- heartbeat_exchange = self.channel.fanout('coney_island_heartbeat')
256
- EventMachine.add_periodic_timer(15) do
257
- heartbeat_exchange.publish({:instance_name => @ticket})
258
- end
259
-
260
- self.channel.prefetch @prefetch_count
261
- @queue = self.channel.queue(@full_instance_name, auto_delete: false, durable: true)
262
- @queue.bind(self.exchange, routing_key: 'carousels.' + @ticket)
263
- @queue.subscribe(:ack => true) do |metadata,payload|
264
- begin
265
- job_id = SecureRandom.uuid
266
- @job_attempts[job_id] = 1
267
- args = JSON.parse(payload)
268
- @log.info ("Starting job #{job_id}: #{args}")
269
- if args.has_key? 'delay'
270
- EventMachine.add_timer(args['delay'].to_i) do
271
- self.handle_job(metadata,args,job_id)
272
- end
273
- else
274
- self.handle_job(metadata,args,job_id)
275
- end
276
- rescue Timeout::Error => e
277
- self.poke_the_badger(e, {code_source: 'ConeyIsland', job_payload: args, reason: 'timeout in subscribe code before calling job method'})
278
- rescue Exception => e
279
- self.poke_the_badger(e, {code_source: 'ConeyIsland', job_payload: args})
280
- @log.error("ConeyIsland code error, not application code:\n#{e.inspect}\nARGS: #{args}")
281
- end
282
- end
283
- end
73
+ def self.run_with_em(klass, method, *args)
74
+ ConeyIsland::Submitter.run_with_em(klass, method, *args)
284
75
  end
285
76
 
286
- def self.poke_the_badger(message, context, attempts = 1)
287
- begin
288
- Timeout::timeout(3) do
289
- @notifier.notify(message, context)
290
- end
291
- rescue
292
- if attempts <= 3
293
- attempts += 1
294
- self.poke_the_badger(message, context, attempts)
295
- end
296
- end
77
+ def self.submit(*args)
78
+ ConeyIsland::Submitter.submit(*args)
297
79
  end
298
80
  end
81
+
299
82
  require 'coney_island/notifiers/honeybadger_notifier'
83
+ require 'coney_island/worker'
84
+ require 'coney_island/submitter'
300
85
 
@@ -0,0 +1,92 @@
1
+ module ConeyIsland
2
+ class Submitter
3
+
4
+ def self.run_inline
5
+ @run_inline = true
6
+ end
7
+
8
+ def self.stop_running_inline
9
+ @run_inline = false
10
+ end
11
+
12
+ def self.submit(*args)
13
+ if RequestStore.store[:cache_jobs]
14
+ RequestStore.store[:jobs].push args
15
+ else
16
+ self.submit!(args)
17
+ end
18
+ end
19
+
20
+ def self.submit!(args)
21
+ if @run_inline
22
+ self.handle_publish(args)
23
+ else
24
+ EventMachine.next_tick do
25
+ self.handle_publish(args)
26
+ end
27
+ end
28
+ end
29
+
30
+ def self.handle_publish(args)
31
+ ConeyIsland.handle_connection unless @run_inline
32
+ jobs = (args.first.is_a? Array) ? args : [args]
33
+ jobs.each do |args|
34
+ 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
35
+ klass = args.shift
36
+ klass = klass.name
37
+ method_name = args.shift
38
+ job_args = args.shift
39
+ job_args ||= {}
40
+ job_args['klass'] = klass
41
+ job_args['method_name'] = method_name
42
+ if @run_inline
43
+ job_args.stringify_keys!
44
+ ConeyIsland::Worker.execute_job_method(job_args)
45
+ else
46
+ work_queue = job_args.delete :work_queue
47
+ work_queue ||= 'default'
48
+ ConeyIsland.exchange.publish((job_args.to_json), routing_key: "carousels.#{work_queue}")
49
+ end
50
+ end
51
+ RequestStore.store[:completed_jobs] ||= 0
52
+ RequestStore.store[:completed_jobs] += 1
53
+ end
54
+ end
55
+
56
+ def self.cache_jobs
57
+ RequestStore.store[:cache_jobs] = true
58
+ RequestStore.store[:jobs] = []
59
+ end
60
+
61
+ def self.flush_jobs
62
+ jobs = RequestStore.store[:jobs].dup
63
+ self.submit!(jobs) if jobs.any?
64
+ RequestStore.store[:jobs] = []
65
+ end
66
+
67
+ def self.stop_caching_jobs
68
+ RequestStore.store[:cache_jobs] = false
69
+ end
70
+
71
+ def self.run_with_em(klass, method, *args)
72
+ EventMachine.run do
73
+ ConeyIsland.cache_jobs
74
+ klass.send(method, *args)
75
+ ConeyIsland.flush_jobs
76
+ ConeyIsland.publisher_shutdown
77
+ end
78
+ end
79
+
80
+ def self.publisher_shutdown
81
+ EventMachine.add_periodic_timer(1) do
82
+ if RequestStore.store[:jobs] && (RequestStore.store[:jobs].length > RequestStore.store[:completed_jobs])
83
+ Rails.logger.info("Waiting for #{RequestStore.store[:jobs].length - RequestStore.store[:completed_jobs]} publishes to finish")
84
+ else
85
+ Rails.logger.info("Shutting down coney island publisher")
86
+ EventMachine.stop
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+
@@ -1,3 +1,3 @@
1
1
  module ConeyIsland
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
@@ -0,0 +1,195 @@
1
+ module ConeyIsland
2
+ class Worker
3
+
4
+ def self.config=(config_hash)
5
+ @config = config_hash.symbolize_keys!
6
+ end
7
+
8
+ def self.config
9
+ @config
10
+ end
11
+
12
+ def self.log
13
+ @log ||= Logger.new(File.open(File::NULL, "w"))
14
+ end
15
+
16
+ def self.log=(log_thing)
17
+ @log = log_thing
18
+ end
19
+
20
+ def self.job_attempts
21
+ @job_attempts ||= {}
22
+ end
23
+
24
+ def self.initialize_background
25
+ ENV['NEW_RELIC_AGENT_ENABLED'] = 'false'
26
+ ENV['NEWRELIC_ENABLE'] = 'false'
27
+ @ticket = ARGV[0]
28
+ @ticket ||= 'default'
29
+
30
+ @log_io = self.config[:log]
31
+ self.log = Logger.new(@log_io)
32
+
33
+ @instance_config = self.config[:carousels][@ticket]
34
+
35
+ @prefetch_count = @instance_config[:prefetch_count] if @instance_config
36
+ @prefetch_count ||= 20
37
+
38
+ @worker_count = @instance_config[:worker_count] if @instance_config
39
+ @worker_count ||= 1
40
+ @child_count = @worker_count - 1
41
+ @child_pids = []
42
+
43
+ @full_instance_name = @ticket
44
+
45
+ self.log.level = self.config[:log_level]
46
+ self.log.info("config: #{self.config}")
47
+
48
+ @notifier = "ConeyIsland::Notifiers::#{self.config[:notifier_service]}Notifier".constantize
49
+ end
50
+
51
+ def self.start
52
+ @child_count.times do
53
+ child_pid = Process.fork
54
+ unless child_pid
55
+ self.log.info("started child for ticket #{@ticket} with pid #{Process.pid}")
56
+ break
57
+ end
58
+ @child_pids.push child_pid
59
+ end
60
+ defined?(ActiveRecord::Base) and
61
+ ActiveRecord::Base.establish_connection
62
+ EventMachine.run do
63
+
64
+ Signal.trap('INT') do
65
+ self.shutdown('INT')
66
+ end
67
+ Signal.trap('TERM') do
68
+ self.shutdown('TERM')
69
+ end
70
+
71
+ ConeyIsland.handle_connection
72
+
73
+ self.log.info("Connecting to AMQP broker. Running #{AMQP::VERSION}")
74
+
75
+ #send a heartbeat every 15 seconds to avoid aggresive network configurations that close quiet connections
76
+ heartbeat_exchange = ConeyIsland.channel.fanout('coney_island_heartbeat')
77
+ EventMachine.add_periodic_timer(15) do
78
+ heartbeat_exchange.publish({:instance_name => @ticket})
79
+ end
80
+
81
+ ConeyIsland.channel.prefetch @prefetch_count
82
+ @queue = ConeyIsland.channel.queue(@full_instance_name, auto_delete: false, durable: true)
83
+ @queue.bind(ConeyIsland.exchange, routing_key: 'carousels.' + @ticket)
84
+ @queue.subscribe(:ack => true) do |metadata,payload|
85
+ self.handle_incoming_message(metadata,payload)
86
+ end
87
+ end
88
+ end
89
+
90
+ def self.handle_incoming_message(metadata,payload)
91
+ begin
92
+ job_id = SecureRandom.uuid
93
+ self.job_attempts[job_id] = 1
94
+ args = JSON.parse(payload)
95
+ self.log.info ("Starting job #{job_id}: #{args}")
96
+ if args.has_key? 'delay'
97
+ EventMachine.add_timer(args['delay'].to_i) do
98
+ self.handle_job(metadata,args,job_id)
99
+ end
100
+ else
101
+ self.handle_job(metadata,args,job_id)
102
+ end
103
+ rescue Timeout::Error => e
104
+ self.poke_the_badger(e, {code_source: 'ConeyIsland', job_payload: args, reason: 'timeout in subscribe code before calling job method'})
105
+ rescue Exception => e
106
+ self.poke_the_badger(e, {code_source: 'ConeyIsland', job_payload: args})
107
+ self.log.error("ConeyIsland code error, not application code:\n#{e.inspect}\nARGS: #{args}")
108
+ end
109
+ end
110
+
111
+ def self.handle_job(metadata,args,job_id)
112
+ timeout = args['timeout']
113
+ timeout ||= BG_TIMEOUT_SECONDS
114
+ begin
115
+ Timeout::timeout(timeout) do
116
+ self.execute_job_method(args)
117
+ end
118
+ rescue Timeout::Error => e
119
+ if self.job_attempts.has_key? job_id
120
+ if self.job_attempts[job_id] >= 3
121
+ self.log.error("Request #{job_id} timed out after #{timeout} seconds, bailing out after 3 attempts")
122
+ self.finalize_job(metadata,job_id)
123
+ self.poke_the_badger(e, {work_queue: @ticket, job_payload: args, reason: 'Bailed out after 3 attempts'})
124
+ else
125
+ self.log.error("Request #{job_id} timed out after #{timeout} seconds on attempt number #{self.job_attempts[job_id]}, retrying...")
126
+ self.job_attempts[job_id] += 1
127
+ self.handle_job(metadata,args,job_id)
128
+ end
129
+ end
130
+ rescue Exception => e
131
+ self.poke_the_badger(e, {work_queue: @ticket, job_payload: args})
132
+ self.log.error("Error executing #{args['klass']}##{args['method_name']} #{job_id} for id #{args['instance_id']} with args #{args}:")
133
+ self.log.error(e.message)
134
+ self.log.error(e.backtrace.join("\n"))
135
+ self.finalize_job(metadata,job_id)
136
+ else
137
+ self.finalize_job(metadata,job_id)
138
+ end
139
+ end
140
+
141
+ def self.execute_job_method(args)
142
+ class_name = args['klass']
143
+ method_name = args['method_name']
144
+ klass = class_name.constantize
145
+ method_args = args['args']
146
+ if args.has_key? 'instance_id'
147
+ instance_id = args['instance_id']
148
+ object = klass.find(instance_id)
149
+ else
150
+ object = klass
151
+ end
152
+ if method_args and method_args.length > 0
153
+ object.send method_name, *method_args
154
+ else
155
+ object.send method_name
156
+ end
157
+ end
158
+
159
+ def self.finalize_job(metadata,job_id)
160
+ metadata.ack
161
+ self.log.info("finished job #{job_id}")
162
+ self.job_attempts.delete job_id
163
+ end
164
+
165
+ def self.poke_the_badger(message, context, attempts = 1)
166
+ begin
167
+ Timeout::timeout(3) do
168
+ @notifier.notify(message, context)
169
+ end
170
+ rescue
171
+ if attempts <= 3
172
+ attempts += 1
173
+ self.poke_the_badger(message, context, attempts)
174
+ end
175
+ end
176
+ end
177
+
178
+ def self.shutdown(signal)
179
+ shutdown_time = Time.now
180
+ @child_pids.each do |child_pid|
181
+ Process.kill(signal, child_pid)
182
+ end
183
+ @queue.unsubscribe
184
+ EventMachine.add_periodic_timer(1) do
185
+ if self.job_attempts.any?
186
+ self.log.info("Waiting for #{self.job_attempts.length} requests to finish")
187
+ else
188
+ self.log.info("Shutting down coney island #{@ticket}")
189
+ EventMachine.stop
190
+ end
191
+ end
192
+ end
193
+
194
+ end
195
+ end
@@ -1,7 +1,23 @@
1
1
  require 'test_helper'
2
2
 
3
- class ConeyIslandTest < ActiveSupport::TestCase
4
- test "truth" do
5
- assert_kind_of Module, ConeyIsland
3
+ class ConeyIslandTest < MiniTest::Test
4
+ describe "running jobs" do
5
+ it "runs inline" do
6
+ ConeyIsland.run_inline
7
+ my_array = []
8
+ ConeyIsland.submit(TestModel, :add_to_list, args: [my_array])
9
+ my_array.first.must_equal 'Added one!'
10
+ end
11
+ it "caches jobs" do
12
+ ConeyIsland.run_inline
13
+ my_array = []
14
+ ConeyIsland.cache_jobs
15
+ ConeyIsland.submit(TestModel, :add_to_list, args: [my_array])
16
+ RequestStore.store[:jobs].length.must_equal 1
17
+ my_array.length.must_equal 0
18
+ ConeyIsland.flush_jobs
19
+ my_array.first.must_equal 'Added one!'
20
+ ConeyIsland.stop_caching_jobs
21
+ end
6
22
  end
7
23
  end
File without changes
File without changes
@@ -0,0 +1,33 @@
1
+ require 'test_helper'
2
+
3
+ class SubmitterTest < MiniTest::Test
4
+ describe "running jobs inline" do
5
+ it "calls the worker directly" do
6
+ @execute_job_method = Minitest::Mock.new
7
+ @execute_job_method.expect :call, nil, [Hash]
8
+ ConeyIsland::Worker.stub(:execute_job_method,@execute_job_method) do
9
+ ConeyIsland::Submitter.run_inline
10
+ ConeyIsland::Submitter.submit(TestModel, :add_to_list, args: [[]])
11
+ end
12
+ @execute_job_method.verify
13
+ end
14
+ end
15
+ describe "running jobs in the background" do
16
+ it "publishes the job to the message bus" do
17
+ @exchange = Minitest::Mock.new
18
+ @exchange.expect :publish, nil, [String,Hash]
19
+ @next_tick = Minitest::Mock.new
20
+ @next_tick.expect :call, nil, []
21
+ EventMachine.stub(:next_tick, @next_tick) do
22
+ ConeyIsland.stub(:handle_connection, nil) do
23
+ ConeyIsland.stub(:exchange, @exchange) do
24
+ ConeyIsland::Submitter.stop_running_inline
25
+ ConeyIsland::Submitter.submit(TestModel, :add_to_list, args: [[]])
26
+ end
27
+ end
28
+ end
29
+ @next_tick.verify
30
+ @exchange.verify
31
+ end
32
+ end
33
+ end
data/test/test_helper.rb CHANGED
@@ -3,9 +3,13 @@ ENV["RAILS_ENV"] = "test"
3
3
 
4
4
  require File.expand_path("../dummy/config/environment.rb", __FILE__)
5
5
  require "rails/test_help"
6
+ require 'minitest/unit'
7
+ require 'minitest/autorun'
8
+ require 'minitest/pride'
9
+ require 'request_store'
10
+ require 'amqp'
6
11
 
7
12
  Rails.backtrace_cleaner.remove_silencers!
8
-
9
13
  # Load support files
10
14
  Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
11
15
 
@@ -13,3 +17,38 @@ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
13
17
  if ActiveSupport::TestCase.method_defined?(:fixture_path=)
14
18
  ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__)
15
19
  end
20
+
21
+ class TestModel
22
+
23
+ def self.add_to_list(array)
24
+ array.push "Added one!"
25
+ end
26
+
27
+ def self.take_too_long
28
+ sleep(10)
29
+ end
30
+
31
+ def self.throw_an_error
32
+ raise 'It broke!'
33
+ end
34
+
35
+ def self.instances
36
+ @instances ||= {}
37
+ end
38
+
39
+ def self.find(id)
40
+ self.instances[id]
41
+ end
42
+
43
+ def initialize(params)
44
+ TestModel.instances[params['id']] = self
45
+ end
46
+
47
+ def set_color(color)
48
+ @color = (color)
49
+ end
50
+
51
+ def color
52
+ @color
53
+ end
54
+ end
@@ -0,0 +1,51 @@
1
+ require 'test_helper'
2
+
3
+ class WorkerTest < MiniTest::Test
4
+ describe "handling jobs" do
5
+ before do
6
+ @metadata = MiniTest::Mock.new
7
+ @metadata.expect :ack, nil
8
+ end
9
+ it "handles timeouts with 3 retries before bailing out" do
10
+ ConeyIsland::Worker.job_attempts['my_job_id'] = 1
11
+ ConeyIsland::Worker.job_attempts.stub(:delete,nil) do
12
+ ConeyIsland::Worker.handle_job(@metadata,{
13
+ 'klass' => 'TestModel',
14
+ 'method_name' => :take_too_long,
15
+ 'timeout' => 0.01
16
+ },'my_job_id')
17
+ end
18
+ ConeyIsland::Worker.job_attempts['my_job_id'].must_equal 3
19
+ end
20
+ it "sends other exeptions to a notification service" do
21
+ @poke_the_badger = MiniTest::Mock.new
22
+ @poke_the_badger.expect :call, nil, [Exception,Hash]
23
+ ConeyIsland::Worker.stub(:poke_the_badger,@poke_the_badger) do
24
+ ConeyIsland::Worker.handle_job(@metadata,{
25
+ 'klass' => 'TestModel',
26
+ 'method_name' => :throw_an_error
27
+ },'my_job_id')
28
+ end
29
+ @poke_the_badger.verify
30
+ end
31
+ it "calls find on the submitted class if an instance_id is present" do
32
+ TestModel.new('id' => 'my_id')
33
+ ConeyIsland::Worker.handle_job(@metadata,{
34
+ 'klass' => 'TestModel',
35
+ 'method_name' => :set_color,
36
+ 'instance_id' => 'my_id',
37
+ 'args' => ['green']
38
+ },'my_job_id')
39
+ my_thing = TestModel.find('my_id')
40
+ my_thing.color.must_equal 'green'
41
+ end
42
+ it "acknowledges job completion to the message bus" do
43
+ ConeyIsland::Worker.handle_job(@metadata,
44
+ { 'klass' => 'TestModel',
45
+ 'method_name' => :add_to_list,
46
+ 'args' => [[]]},
47
+ 'my_job_id')
48
+ @metadata.verify
49
+ end
50
+ end
51
+ 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.0.4
4
+ version: 0.0.5
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: 2014-09-12 00:00:00.000000000 Z
12
+ date: 2014-09-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -81,6 +81,20 @@ dependencies:
81
81
  - - ">="
82
82
  - !ruby/object:Gem::Version
83
83
  version: '0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: minitest
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
84
98
  description: An industrial-strength background worker system for rails using RabbitMQ.
85
99
  email:
86
100
  - edraut@gmail.com
@@ -96,7 +110,9 @@ files:
96
110
  - lib/coney_island.rb
97
111
  - lib/coney_island/notifiers/airbrake_notifier.rb
98
112
  - lib/coney_island/notifiers/honeybadger_notifier.rb
113
+ - lib/coney_island/submitter.rb
99
114
  - lib/coney_island/version.rb
115
+ - lib/coney_island/worker.rb
100
116
  - lib/tasks/coney_island_tasks.rake
101
117
  - test/coney_island_test.rb
102
118
  - test/dummy/README.rdoc
@@ -128,11 +144,15 @@ files:
128
144
  - test/dummy/config/locales/en.yml
129
145
  - test/dummy/config/routes.rb
130
146
  - test/dummy/config/secrets.yml
147
+ - test/dummy/db/test.sqlite3
148
+ - test/dummy/log/test.log
131
149
  - test/dummy/public/404.html
132
150
  - test/dummy/public/422.html
133
151
  - test/dummy/public/500.html
134
152
  - test/dummy/public/favicon.ico
153
+ - test/submitter_test.rb
135
154
  - test/test_helper.rb
155
+ - test/worker_test.rb
136
156
  homepage: https://github.com/edraut/coney_island
137
157
  licenses:
138
158
  - MIT
@@ -157,8 +177,9 @@ rubygems_version: 2.2.2
157
177
  signing_key:
158
178
  specification_version: 4
159
179
  summary: Want guaranteed delivery between your queue and your workers using ACKs?
160
- How about load-balancing? Throw in all the features other background worker systems
161
- offer and you must have a ticket to ride at Coney Island.
180
+ How about load-balancing? Would job-specific timeouts be nice? Throw in all the
181
+ features other background worker systems offer and you must have a ticket to ride
182
+ at Coney Island.
162
183
  test_files:
163
184
  - test/coney_island_test.rb
164
185
  - test/dummy/app/assets/javascripts/application.js
@@ -188,10 +209,14 @@ test_files:
188
209
  - test/dummy/config/routes.rb
189
210
  - test/dummy/config/secrets.yml
190
211
  - test/dummy/config.ru
212
+ - test/dummy/db/test.sqlite3
213
+ - test/dummy/log/test.log
191
214
  - test/dummy/public/404.html
192
215
  - test/dummy/public/422.html
193
216
  - test/dummy/public/500.html
194
217
  - test/dummy/public/favicon.ico
195
218
  - test/dummy/Rakefile
196
219
  - test/dummy/README.rdoc
220
+ - test/submitter_test.rb
197
221
  - test/test_helper.rb
222
+ - test/worker_test.rb