roundhouse-x 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.travis.yml +16 -0
  4. data/3.0-Upgrade.md +70 -0
  5. data/Changes.md +1127 -0
  6. data/Gemfile +27 -0
  7. data/LICENSE +7 -0
  8. data/README.md +52 -0
  9. data/Rakefile +9 -0
  10. data/bin/roundhouse +19 -0
  11. data/bin/roundhousectl +93 -0
  12. data/lib/generators/roundhouse/templates/worker.rb.erb +9 -0
  13. data/lib/generators/roundhouse/templates/worker_spec.rb.erb +6 -0
  14. data/lib/generators/roundhouse/templates/worker_test.rb.erb +8 -0
  15. data/lib/generators/roundhouse/worker_generator.rb +49 -0
  16. data/lib/roundhouse/actor.rb +39 -0
  17. data/lib/roundhouse/api.rb +859 -0
  18. data/lib/roundhouse/cli.rb +396 -0
  19. data/lib/roundhouse/client.rb +210 -0
  20. data/lib/roundhouse/core_ext.rb +105 -0
  21. data/lib/roundhouse/exception_handler.rb +30 -0
  22. data/lib/roundhouse/fetch.rb +154 -0
  23. data/lib/roundhouse/launcher.rb +98 -0
  24. data/lib/roundhouse/logging.rb +104 -0
  25. data/lib/roundhouse/manager.rb +236 -0
  26. data/lib/roundhouse/middleware/chain.rb +149 -0
  27. data/lib/roundhouse/middleware/i18n.rb +41 -0
  28. data/lib/roundhouse/middleware/server/active_record.rb +13 -0
  29. data/lib/roundhouse/middleware/server/logging.rb +40 -0
  30. data/lib/roundhouse/middleware/server/retry_jobs.rb +206 -0
  31. data/lib/roundhouse/monitor.rb +124 -0
  32. data/lib/roundhouse/paginator.rb +42 -0
  33. data/lib/roundhouse/processor.rb +159 -0
  34. data/lib/roundhouse/rails.rb +24 -0
  35. data/lib/roundhouse/redis_connection.rb +77 -0
  36. data/lib/roundhouse/scheduled.rb +115 -0
  37. data/lib/roundhouse/testing/inline.rb +28 -0
  38. data/lib/roundhouse/testing.rb +193 -0
  39. data/lib/roundhouse/util.rb +68 -0
  40. data/lib/roundhouse/version.rb +3 -0
  41. data/lib/roundhouse/web.rb +264 -0
  42. data/lib/roundhouse/web_helpers.rb +249 -0
  43. data/lib/roundhouse/worker.rb +90 -0
  44. data/lib/roundhouse.rb +177 -0
  45. data/roundhouse.gemspec +27 -0
  46. data/test/config.yml +9 -0
  47. data/test/env_based_config.yml +11 -0
  48. data/test/fake_env.rb +0 -0
  49. data/test/fixtures/en.yml +2 -0
  50. data/test/helper.rb +49 -0
  51. data/test/test_api.rb +521 -0
  52. data/test/test_cli.rb +389 -0
  53. data/test/test_client.rb +294 -0
  54. data/test/test_exception_handler.rb +55 -0
  55. data/test/test_fetch.rb +206 -0
  56. data/test/test_logging.rb +34 -0
  57. data/test/test_manager.rb +169 -0
  58. data/test/test_middleware.rb +160 -0
  59. data/test/test_monitor.rb +258 -0
  60. data/test/test_processor.rb +176 -0
  61. data/test/test_rails.rb +23 -0
  62. data/test/test_redis_connection.rb +127 -0
  63. data/test/test_retry.rb +390 -0
  64. data/test/test_roundhouse.rb +87 -0
  65. data/test/test_scheduled.rb +120 -0
  66. data/test/test_scheduling.rb +75 -0
  67. data/test/test_testing.rb +78 -0
  68. data/test/test_testing_fake.rb +240 -0
  69. data/test/test_testing_inline.rb +65 -0
  70. data/test/test_util.rb +18 -0
  71. data/test/test_web.rb +605 -0
  72. data/test/test_web_helpers.rb +52 -0
  73. data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
  74. data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
  75. data/web/assets/images/logo.png +0 -0
  76. data/web/assets/images/status/active.png +0 -0
  77. data/web/assets/images/status/idle.png +0 -0
  78. data/web/assets/images/status-sd8051fd480.png +0 -0
  79. data/web/assets/javascripts/application.js +83 -0
  80. data/web/assets/javascripts/dashboard.js +300 -0
  81. data/web/assets/javascripts/locales/README.md +27 -0
  82. data/web/assets/javascripts/locales/jquery.timeago.ar.js +96 -0
  83. data/web/assets/javascripts/locales/jquery.timeago.bg.js +18 -0
  84. data/web/assets/javascripts/locales/jquery.timeago.bs.js +49 -0
  85. data/web/assets/javascripts/locales/jquery.timeago.ca.js +18 -0
  86. data/web/assets/javascripts/locales/jquery.timeago.cs.js +18 -0
  87. data/web/assets/javascripts/locales/jquery.timeago.cy.js +20 -0
  88. data/web/assets/javascripts/locales/jquery.timeago.da.js +18 -0
  89. data/web/assets/javascripts/locales/jquery.timeago.de.js +18 -0
  90. data/web/assets/javascripts/locales/jquery.timeago.el.js +18 -0
  91. data/web/assets/javascripts/locales/jquery.timeago.en-short.js +20 -0
  92. data/web/assets/javascripts/locales/jquery.timeago.en.js +20 -0
  93. data/web/assets/javascripts/locales/jquery.timeago.es.js +18 -0
  94. data/web/assets/javascripts/locales/jquery.timeago.et.js +18 -0
  95. data/web/assets/javascripts/locales/jquery.timeago.fa.js +22 -0
  96. data/web/assets/javascripts/locales/jquery.timeago.fi.js +28 -0
  97. data/web/assets/javascripts/locales/jquery.timeago.fr-short.js +16 -0
  98. data/web/assets/javascripts/locales/jquery.timeago.fr.js +17 -0
  99. data/web/assets/javascripts/locales/jquery.timeago.he.js +18 -0
  100. data/web/assets/javascripts/locales/jquery.timeago.hr.js +49 -0
  101. data/web/assets/javascripts/locales/jquery.timeago.hu.js +18 -0
  102. data/web/assets/javascripts/locales/jquery.timeago.hy.js +18 -0
  103. data/web/assets/javascripts/locales/jquery.timeago.id.js +18 -0
  104. data/web/assets/javascripts/locales/jquery.timeago.it.js +16 -0
  105. data/web/assets/javascripts/locales/jquery.timeago.ja.js +19 -0
  106. data/web/assets/javascripts/locales/jquery.timeago.ko.js +17 -0
  107. data/web/assets/javascripts/locales/jquery.timeago.lt.js +20 -0
  108. data/web/assets/javascripts/locales/jquery.timeago.mk.js +20 -0
  109. data/web/assets/javascripts/locales/jquery.timeago.nl.js +20 -0
  110. data/web/assets/javascripts/locales/jquery.timeago.no.js +18 -0
  111. data/web/assets/javascripts/locales/jquery.timeago.pl.js +31 -0
  112. data/web/assets/javascripts/locales/jquery.timeago.pt-br.js +16 -0
  113. data/web/assets/javascripts/locales/jquery.timeago.pt.js +16 -0
  114. data/web/assets/javascripts/locales/jquery.timeago.ro.js +18 -0
  115. data/web/assets/javascripts/locales/jquery.timeago.rs.js +49 -0
  116. data/web/assets/javascripts/locales/jquery.timeago.ru.js +34 -0
  117. data/web/assets/javascripts/locales/jquery.timeago.sk.js +18 -0
  118. data/web/assets/javascripts/locales/jquery.timeago.sl.js +44 -0
  119. data/web/assets/javascripts/locales/jquery.timeago.sv.js +18 -0
  120. data/web/assets/javascripts/locales/jquery.timeago.th.js +20 -0
  121. data/web/assets/javascripts/locales/jquery.timeago.tr.js +16 -0
  122. data/web/assets/javascripts/locales/jquery.timeago.uk.js +34 -0
  123. data/web/assets/javascripts/locales/jquery.timeago.uz.js +19 -0
  124. data/web/assets/javascripts/locales/jquery.timeago.zh-cn.js +20 -0
  125. data/web/assets/javascripts/locales/jquery.timeago.zh-tw.js +20 -0
  126. data/web/assets/stylesheets/application.css +746 -0
  127. data/web/assets/stylesheets/bootstrap.css +9 -0
  128. data/web/locales/cs.yml +68 -0
  129. data/web/locales/da.yml +68 -0
  130. data/web/locales/de.yml +69 -0
  131. data/web/locales/el.yml +68 -0
  132. data/web/locales/en.yml +77 -0
  133. data/web/locales/es.yml +69 -0
  134. data/web/locales/fr.yml +69 -0
  135. data/web/locales/hi.yml +75 -0
  136. data/web/locales/it.yml +69 -0
  137. data/web/locales/ja.yml +69 -0
  138. data/web/locales/ko.yml +68 -0
  139. data/web/locales/nl.yml +68 -0
  140. data/web/locales/no.yml +69 -0
  141. data/web/locales/pl.yml +59 -0
  142. data/web/locales/pt-br.yml +68 -0
  143. data/web/locales/pt.yml +67 -0
  144. data/web/locales/ru.yml +75 -0
  145. data/web/locales/sv.yml +68 -0
  146. data/web/locales/ta.yml +75 -0
  147. data/web/locales/zh-cn.yml +68 -0
  148. data/web/locales/zh-tw.yml +68 -0
  149. data/web/views/_footer.erb +22 -0
  150. data/web/views/_job_info.erb +84 -0
  151. data/web/views/_nav.erb +66 -0
  152. data/web/views/_paging.erb +23 -0
  153. data/web/views/_poll_js.erb +5 -0
  154. data/web/views/_poll_link.erb +7 -0
  155. data/web/views/_status.erb +4 -0
  156. data/web/views/_summary.erb +40 -0
  157. data/web/views/busy.erb +90 -0
  158. data/web/views/dashboard.erb +75 -0
  159. data/web/views/dead.erb +34 -0
  160. data/web/views/layout.erb +31 -0
  161. data/web/views/morgue.erb +71 -0
  162. data/web/views/queue.erb +45 -0
  163. data/web/views/queues.erb +27 -0
  164. data/web/views/retries.erb +74 -0
  165. data/web/views/retry.erb +34 -0
  166. data/web/views/scheduled.erb +54 -0
  167. data/web/views/scheduled_job_info.erb +8 -0
  168. metadata +404 -0
data/test/test_api.rb ADDED
@@ -0,0 +1,521 @@
1
+ require_relative 'helper'
2
+ require 'roundhouse/api'
3
+ require 'active_support/core_ext/date/conversions'
4
+ require 'active_support/core_ext/date_time/conversions'
5
+
6
+ class TestApi < Roundhouse::Test
7
+
8
+ describe "stats" do
9
+ before do
10
+ @before = DateTime::DATE_FORMATS[:default]
11
+ DateTime::DATE_FORMATS[:default] = "%d/%m/%Y %H:%M:%S"
12
+ Roundhouse.redis = REDIS
13
+ Roundhouse.redis {|c| c.flushdb }
14
+ end
15
+
16
+ after do
17
+ DateTime::DATE_FORMATS[:default] = @before
18
+ end
19
+
20
+ describe "processed" do
21
+ it "is initially zero" do
22
+ s = Roundhouse::Stats.new
23
+ assert_equal 0, s.processed
24
+ end
25
+
26
+ it "returns number of processed jobs" do
27
+ Roundhouse.redis { |conn| conn.set("stat:processed", 5) }
28
+ s = Roundhouse::Stats.new
29
+ assert_equal 5, s.processed
30
+ end
31
+ end
32
+
33
+ describe "failed" do
34
+ it "is initially zero" do
35
+ s = Roundhouse::Stats.new
36
+ assert_equal 0, s.failed
37
+ end
38
+
39
+ it "returns number of failed jobs" do
40
+ Roundhouse.redis { |conn| conn.set("stat:failed", 5) }
41
+ s = Roundhouse::Stats.new
42
+ assert_equal 5, s.failed
43
+ end
44
+ end
45
+
46
+ describe "reset" do
47
+ before do
48
+ Roundhouse.redis do |conn|
49
+ conn.set('stat:processed', 5)
50
+ conn.set('stat:failed', 10)
51
+ end
52
+ end
53
+
54
+ it 'will reset all stats by default' do
55
+ Roundhouse::Stats.new.reset
56
+ Roundhouse.redis do |conn|
57
+ assert_equal '0', conn.get('stat:processed')
58
+ assert_equal '0', conn.get('stat:failed')
59
+ end
60
+ end
61
+
62
+ it 'can reset individual stats' do
63
+ Roundhouse::Stats.new.reset('failed')
64
+ Roundhouse.redis do |conn|
65
+ assert_equal '5', conn.get('stat:processed')
66
+ assert_equal '0', conn.get('stat:failed')
67
+ end
68
+ end
69
+
70
+ it 'can accept anything that responds to #to_s' do
71
+ Roundhouse::Stats.new.reset(:failed)
72
+ Roundhouse.redis do |conn|
73
+ assert_equal '5', conn.get('stat:processed')
74
+ assert_equal '0', conn.get('stat:failed')
75
+ end
76
+ end
77
+
78
+ it 'ignores anything other than "failed" or "processed"' do
79
+ Roundhouse::Stats.new.reset((1..10).to_a, ['failed'])
80
+ Roundhouse.redis do |conn|
81
+ assert_equal '5', conn.get('stat:processed')
82
+ assert_equal '0', conn.get('stat:failed')
83
+ end
84
+ end
85
+ end
86
+
87
+ describe "queues" do
88
+ it "returns all queues" do
89
+ assert_equal Roundhouse::Stats.new.queues, Roundhouse::Stats::Queues.new.lengths
90
+ end
91
+
92
+ it "is initially empty" do
93
+ s = Roundhouse::Stats::Queues.new
94
+ assert_equal 0, s.lengths.size
95
+ end
96
+
97
+ it "returns a hash of queue and size in order" do
98
+ Roundhouse.redis do |conn|
99
+ conn.rpush 'queue:foo', '{}'
100
+ conn.sadd 'queues', 'foo'
101
+
102
+ 3.times { conn.rpush 'queue:bar', '{}' }
103
+ conn.sadd 'queues', 'bar'
104
+ end
105
+
106
+ s = Roundhouse::Stats::Queues.new
107
+ assert_equal ({ "foo" => 1, "bar" => 3 }), s.lengths
108
+ assert_equal "bar", s.lengths.first.first
109
+ end
110
+ end
111
+
112
+ describe 'queue stats' do
113
+ describe 'initially empty' do
114
+ it "is initially empty" do
115
+ s = Roundhouse::Stats.new
116
+ assert_equal 0, s.in_rotation
117
+ end
118
+ end
119
+
120
+ describe 'with enqueued jobs' do
121
+ before do
122
+ Roundhouse.redis do |conn|
123
+ Roundhouse::Monitor.push_job conn, [{'queue_id' => 2500, 'class' => 'MyWorker', 'args' => [1]}]
124
+ 4.times { Roundhouse::Monitor.push_job conn, [{'queue_id' => 500, 'class' => 'MyWorker', 'args' => [1]}] }
125
+ Roundhouse::Monitor.set_queue_is_empty(conn, 3000)
126
+ Roundhouse::Monitor.suspend(conn, 200)
127
+ end
128
+ end
129
+
130
+ it 'should give back correct stats' do
131
+ s = Roundhouse::Stats.new
132
+ assert_equal 2, s.in_rotation
133
+ assert_equal 5, s.enqueued
134
+ assert_equal (5/4), s.avg_queue_len
135
+ assert_equal 4, s.num_queues
136
+ assert_equal 1, s.num_empty_queues
137
+ assert_equal 1, s.num_suspended_queues
138
+ end
139
+
140
+ end
141
+
142
+ end
143
+
144
+ describe "over time" do
145
+ describe "processed" do
146
+ it 'retrieves hash of dates' do
147
+ Roundhouse.redis do |c|
148
+ c.incrby("stat:processed:2012-12-24", 4)
149
+ c.incrby("stat:processed:2012-12-25", 1)
150
+ c.incrby("stat:processed:2012-12-26", 6)
151
+ c.incrby("stat:processed:2012-12-27", 2)
152
+ end
153
+ Time.stub(:now, Time.parse("2012-12-26 1:00:00 -0500")) do
154
+ s = Roundhouse::Stats::History.new(2)
155
+ assert_equal ({ "2012-12-26" => 6, "2012-12-25" => 1 }), s.processed
156
+
157
+ s = Roundhouse::Stats::History.new(3)
158
+ assert_equal ({ "2012-12-26" => 6, "2012-12-25" => 1, "2012-12-24" => 4 }), s.processed
159
+
160
+ s = Roundhouse::Stats::History.new(2, Date.parse("2012-12-25"))
161
+ assert_equal ({ "2012-12-25" => 1, "2012-12-24" => 4 }), s.processed
162
+ end
163
+ end
164
+ end
165
+
166
+ describe "failed" do
167
+ it 'retrieves hash of dates' do
168
+ Roundhouse.redis do |c|
169
+ c.incrby("stat:failed:2012-12-24", 4)
170
+ c.incrby("stat:failed:2012-12-25", 1)
171
+ c.incrby("stat:failed:2012-12-26", 6)
172
+ c.incrby("stat:failed:2012-12-27", 2)
173
+ end
174
+ Time.stub(:now, Time.parse("2012-12-26 1:00:00 -0500")) do
175
+ s = Roundhouse::Stats::History.new(2)
176
+ assert_equal ({ "2012-12-26" => 6, "2012-12-25" => 1 }), s.failed
177
+
178
+ s = Roundhouse::Stats::History.new(3)
179
+ assert_equal ({ "2012-12-26" => 6, "2012-12-25" => 1, "2012-12-24" => 4 }), s.failed
180
+
181
+ s = Roundhouse::Stats::History.new(2, Date.parse("2012-12-25"))
182
+ assert_equal ({ "2012-12-25" => 1, "2012-12-24" => 4 }), s.failed
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
188
+
189
+ describe 'with an empty database' do
190
+ include Roundhouse::Util
191
+
192
+ before do
193
+ Roundhouse.redis = REDIS
194
+ Roundhouse.redis {|c| c.flushdb }
195
+ end
196
+
197
+ it 'shows queue as empty' do
198
+ q = Roundhouse::Queue.new(100)
199
+ assert_equal 0, q.size
200
+ assert_equal 0, q.latency
201
+ end
202
+
203
+ class ApiWorker
204
+ include Roundhouse::Worker
205
+ end
206
+
207
+ it 'can enumerate jobs' do
208
+ q = Roundhouse::Queue.new(100)
209
+ Time.stub(:now, Time.new(2012, 12, 26)) do
210
+ ApiWorker.perform_async(100, 1, 'mike')
211
+ assert_equal ['TestApi::ApiWorker'], q.map(&:klass)
212
+
213
+ job = q.first
214
+ assert_equal 24, job.jid.size
215
+ assert_equal [1, 'mike'], job.args
216
+ assert_equal Time.new(2012, 12, 26), job.enqueued_at
217
+ end
218
+
219
+ assert q.latency > 10_000_000
220
+
221
+ q = Roundhouse::Queue.new('200')
222
+ assert_equal 0, q.size
223
+ end
224
+
225
+ it 'has no enqueued_at time for jobs enqueued in the future' do
226
+ job_id = ApiWorker.perform_in(100, 1, 'foo')
227
+ job = Roundhouse::ScheduledSet.new.find_job(job_id)
228
+ assert_nil job.enqueued_at
229
+ end
230
+
231
+ it 'unwraps delayed jobs' do
232
+ skip 'no delay'
233
+ ApiWorker.delay.foo(1,2,3)
234
+ q = Roundhouse::Queue.new(100)
235
+ x = q.first
236
+ assert_equal "TestApi::ApiWorker.foo", x.display_class
237
+ assert_equal [1,2,3], x.display_args
238
+ end
239
+
240
+ it 'can delete jobs' do
241
+ q = Roundhouse::Queue.new(100)
242
+ ApiWorker.perform_async(100, 1, 'mike')
243
+ assert_equal 1, q.size
244
+
245
+ x = q.first
246
+ assert_equal "TestApi::ApiWorker", x.display_class
247
+ assert_equal [1,'mike'], x.display_args
248
+
249
+ assert_equal [true], q.map(&:delete)
250
+ assert_equal 0, q.size
251
+ end
252
+
253
+ it "can move scheduled job to queue" do
254
+ remain_id = ApiWorker.perform_in(100, 1, 'jason')
255
+ job_id = ApiWorker.perform_in(100, 1, 'jason')
256
+ job = Roundhouse::ScheduledSet.new.find_job(job_id)
257
+ q = Roundhouse::Queue.new(100)
258
+ job.add_to_queue
259
+ queued_job = q.find_job(job_id)
260
+ refute_nil queued_job
261
+ assert_equal queued_job.jid, job_id
262
+ assert_nil Roundhouse::ScheduledSet.new.find_job(job_id)
263
+ refute_nil Roundhouse::ScheduledSet.new.find_job(remain_id)
264
+ end
265
+
266
+ it "handles multiple scheduled jobs when moving to queue" do
267
+ jids = Roundhouse::Client.push_bulk('class' => ApiWorker,
268
+ 'queue_id' => 100,
269
+ 'args' => [[1, 'jason'], [2, 'jason']],
270
+ 'at' => Time.now.to_f)
271
+ assert_equal 2, jids.size
272
+ (remain_id, job_id) = jids
273
+ job = Roundhouse::ScheduledSet.new.find_job(job_id)
274
+ q = Roundhouse::Queue.new(100)
275
+ job.add_to_queue
276
+ queued_job = q.find_job(job_id)
277
+ refute_nil queued_job
278
+ assert_equal queued_job.jid, job_id
279
+ assert_nil Roundhouse::ScheduledSet.new.find_job(job_id)
280
+ refute_nil Roundhouse::ScheduledSet.new.find_job(remain_id)
281
+ end
282
+
283
+ it 'can find job by id in sorted sets' do
284
+ job_id = ApiWorker.perform_in(100, 1, 'jason')
285
+ job = Roundhouse::ScheduledSet.new.find_job(job_id)
286
+ refute_nil job
287
+ assert_equal job_id, job.jid
288
+ assert_in_delta job.latency, 0.0, 0.1
289
+ end
290
+
291
+ it 'can remove jobs when iterating over a sorted set' do
292
+ # scheduled jobs must be greater than SortedSet#each underlying page size
293
+ 51.times do
294
+ ApiWorker.perform_in(100, 100, 'aaron')
295
+ end
296
+ set = Roundhouse::ScheduledSet.new
297
+ set.map(&:delete)
298
+ assert_equal set.size, 0
299
+ end
300
+
301
+ it 'can remove jobs when iterating over a queue' do
302
+ # initial queue size must be greater than Queue#each underlying page size
303
+ 51.times do
304
+ ApiWorker.perform_async(1, 'aaron')
305
+ end
306
+ q = Roundhouse::Queue.new(100)
307
+ q.map(&:delete)
308
+ assert_equal q.size, 0
309
+ end
310
+
311
+ it 'can find job by id in queues' do
312
+ q = Roundhouse::Queue.new(100)
313
+ job_id = ApiWorker.perform_async(100, 1, 'jason')
314
+ job = q.find_job(job_id)
315
+ refute_nil job
316
+ assert_equal job_id, job.jid
317
+ end
318
+
319
+ it 'can clear a queue' do
320
+ q = Roundhouse::Queue.new(100)
321
+ 2.times { ApiWorker.perform_async(1, 'mike') }
322
+ q.clear
323
+
324
+ Roundhouse.redis do |conn|
325
+ refute conn.smembers('queues').include?('foo')
326
+ refute conn.exists('queue:foo')
327
+ end
328
+ end
329
+
330
+ it 'can fetch by score' do
331
+ same_time = Time.now.to_f
332
+ add_retry('bob1', same_time)
333
+ add_retry('bob2', same_time)
334
+ r = Roundhouse::RetrySet.new
335
+ assert_equal 2, r.fetch(same_time).size
336
+ end
337
+
338
+ it 'can fetch by score and jid' do
339
+ same_time = Time.now.to_f
340
+ add_retry('bob1', same_time)
341
+ add_retry('bob2', same_time)
342
+ r = Roundhouse::RetrySet.new
343
+ assert_equal 1, r.fetch(same_time, 'bob1').size
344
+ end
345
+
346
+ it 'shows empty retries' do
347
+ r = Roundhouse::RetrySet.new
348
+ assert_equal 0, r.size
349
+ end
350
+
351
+ it 'can enumerate retries' do
352
+ add_retry
353
+
354
+ r = Roundhouse::RetrySet.new
355
+ assert_equal 1, r.size
356
+ array = r.to_a
357
+ assert_equal 1, array.size
358
+
359
+ retri = array.first
360
+ assert_equal 'ApiWorker', retri.klass
361
+ assert_equal 100, retri.queue_id
362
+ assert_equal 'bob', retri.jid
363
+ assert_in_delta Time.now.to_f, retri.at.to_f, 0.02
364
+ end
365
+
366
+ it 'requires a jid to delete an entry' do
367
+ start_time = Time.now.to_f
368
+ add_retry('bob2', Time.now.to_f)
369
+ assert_raises(ArgumentError) do
370
+ Roundhouse::RetrySet.new.delete(start_time)
371
+ end
372
+ end
373
+
374
+ it 'can delete a single retry from score and jid' do
375
+ same_time = Time.now.to_f
376
+ add_retry('bob1', same_time)
377
+ add_retry('bob2', same_time)
378
+ r = Roundhouse::RetrySet.new
379
+ assert_equal 2, r.size
380
+ Roundhouse::RetrySet.new.delete(same_time, 'bob1')
381
+ assert_equal 1, r.size
382
+ end
383
+
384
+ it 'can retry a retry' do
385
+ add_retry
386
+ r = Roundhouse::RetrySet.new
387
+ assert_equal 1, r.size
388
+ r.first.retry
389
+ assert_equal 0, r.size
390
+ assert_equal 1, Roundhouse::Queue.new(100).size
391
+ job = Roundhouse::Queue.new(100).first
392
+ assert_equal 'bob', job.jid
393
+ assert_equal 1, job['retry_count']
394
+ end
395
+
396
+ it 'can clear retries' do
397
+ add_retry
398
+ add_retry('test')
399
+ r = Roundhouse::RetrySet.new
400
+ assert_equal 2, r.size
401
+ r.clear
402
+ assert_equal 0, r.size
403
+ end
404
+
405
+ it 'can enumerate processes' do
406
+ identity_string = "identity_string"
407
+ odata = {
408
+ 'pid' => 123,
409
+ 'hostname' => hostname,
410
+ 'key' => identity_string,
411
+ 'identity' => identity_string,
412
+ 'started_at' => Time.now.to_f - 15,
413
+ }
414
+
415
+ time = Time.now.to_f
416
+ Roundhouse.redis do |conn|
417
+ conn.multi do
418
+ conn.sadd('processes', odata['key'])
419
+ conn.hmset(odata['key'], 'info', Roundhouse.dump_json(odata), 'busy', 10, 'beat', time)
420
+ conn.sadd('processes', 'fake:pid')
421
+ end
422
+ end
423
+
424
+ ps = Roundhouse::ProcessSet.new.to_a
425
+ assert_equal 1, ps.size
426
+ data = ps.first
427
+ assert_equal 10, data['busy']
428
+ assert_equal time, data['beat']
429
+ assert_equal 123, data['pid']
430
+ data.quiet!
431
+ data.stop!
432
+ signals_string = "#{odata['key']}-signals"
433
+ assert_equal "TERM", Roundhouse.redis{|c| c.lpop(signals_string) }
434
+ assert_equal "USR1", Roundhouse.redis{|c| c.lpop(signals_string) }
435
+ end
436
+
437
+ it 'can enumerate workers' do
438
+ w = Roundhouse::Workers.new
439
+ assert_equal 0, w.size
440
+ w.each do
441
+ assert false
442
+ end
443
+
444
+ key = "#{hostname}:#{$$}"
445
+ pdata = { 'pid' => $$, 'hostname' => hostname, 'started_at' => Time.now.to_i }
446
+ Roundhouse.redis do |conn|
447
+ conn.sadd('processes', key)
448
+ conn.hmset(key, 'info', Roundhouse.dump_json(pdata), 'busy', 0, 'beat', Time.now.to_f)
449
+ end
450
+
451
+ s = "#{key}:workers"
452
+ data = Roundhouse.dump_json({ 'payload' => {}, 'queue' => 'default', 'run_at' => Time.now.to_i })
453
+ Roundhouse.redis do |c|
454
+ c.hmset(s, '1234', data)
455
+ end
456
+
457
+ w.each do |p, x, y|
458
+ assert_equal key, p
459
+ assert_equal "1234", x
460
+ assert_equal 'default', y['queue']
461
+ assert_equal Time.now.year, Time.at(y['run_at']).year
462
+ end
463
+
464
+ s = "#{key}:workers"
465
+ data = Roundhouse.dump_json({ 'payload' => {}, 'queue' => 'default', 'run_at' => (Time.now.to_i - 2*60*60) })
466
+ Roundhouse.redis do |c|
467
+ c.multi do
468
+ c.hmset(s, '5678', data)
469
+ c.hmset("b#{s}", '5678', data)
470
+ end
471
+ end
472
+
473
+ assert_equal ['1234', '5678'], w.map { |_, tid, _| tid }
474
+ end
475
+
476
+ it 'can reschedule jobs' do
477
+ add_retry('foo1')
478
+ add_retry('foo2')
479
+
480
+ retries = Roundhouse::RetrySet.new
481
+ assert_equal 2, retries.size
482
+ refute(retries.map { |r| r.score > (Time.now.to_f + 9) }.any?)
483
+
484
+ retries.each do |retri|
485
+ retri.reschedule(Time.now.to_f + 10) if retri.jid == 'foo2'
486
+ end
487
+
488
+ assert_equal 2, retries.size
489
+ assert(retries.map { |r| r.score > (Time.now.to_f + 9) }.any?)
490
+ end
491
+
492
+ it 'prunes processes which have died' do
493
+ data = { 'pid' => rand(10_000), 'hostname' => "app#{rand(1_000)}", 'started_at' => Time.now.to_f }
494
+ key = "#{data['hostname']}:#{data['pid']}"
495
+ Roundhouse.redis do |conn|
496
+ conn.sadd('processes', key)
497
+ conn.hmset(key, 'info', Roundhouse.dump_json(data), 'busy', 0, 'beat', Time.now.to_f)
498
+ end
499
+
500
+ ps = Roundhouse::ProcessSet.new
501
+ assert_equal 1, ps.size
502
+ assert_equal 1, ps.to_a.size
503
+
504
+ Roundhouse.redis do |conn|
505
+ conn.sadd('processes', "bar:987")
506
+ conn.sadd('processes', "bar:986")
507
+ end
508
+
509
+ ps = Roundhouse::ProcessSet.new
510
+ assert_equal 1, ps.size
511
+ assert_equal 1, ps.to_a.size
512
+ end
513
+
514
+ def add_retry(jid = 'bob', at = Time.now.to_f)
515
+ payload = Roundhouse.dump_json('class' => 'ApiWorker', 'args' => [1, 'mike'], 'queue_id' => 100, 'jid' => jid, 'retry_count' => 2, 'failed_at' => Time.now.to_f)
516
+ Roundhouse.redis do |conn|
517
+ conn.zadd('retry', at.to_s, payload)
518
+ end
519
+ end
520
+ end
521
+ end