retriable 3.8.0 → 4.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.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +20 -7
- data/.hound.yml +1 -1
- data/.rubocop.yml +4 -1
- data/CHANGELOG.md +28 -0
- data/Gemfile +2 -1
- data/README.md +93 -48
- data/lib/retriable/config.rb +3 -57
- data/lib/retriable/core_ext/kernel.rb +4 -4
- data/lib/retriable/validation.rb +4 -7
- data/lib/retriable/version.rb +1 -1
- data/lib/retriable.rb +50 -29
- data/retriable.gemspec +2 -7
- data/spec/config_spec.rb +17 -97
- data/spec/retriable_spec.rb +319 -93
- data/spec/spec_helper.rb +0 -13
- metadata +6 -52
data/spec/retriable_spec.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "rbconfig"
|
|
4
|
-
require "stringio"
|
|
5
4
|
|
|
6
5
|
describe Retriable do
|
|
7
6
|
let(:time_table_handler) do
|
|
@@ -39,6 +38,39 @@ describe Retriable do
|
|
|
39
38
|
expect { retriable { increment_tries_with_exception } }.to raise_error(StandardError)
|
|
40
39
|
expect(@tries).to eq(3)
|
|
41
40
|
end
|
|
41
|
+
|
|
42
|
+
it "passes on_give_up through the kernel extension" do
|
|
43
|
+
require_relative "../lib/retriable/core_ext/kernel"
|
|
44
|
+
received_reason = nil
|
|
45
|
+
handler = proc { |_e, _try, _elapsed, _interval, reason| received_reason = reason }
|
|
46
|
+
|
|
47
|
+
expect { retriable(tries: 1, on_give_up: handler) { increment_tries_with_exception } }
|
|
48
|
+
.to raise_error(StandardError)
|
|
49
|
+
|
|
50
|
+
expect(received_reason).to eq(:tries_exhausted)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# These two specs lock in the anonymous block forwarding (`&`) semantics
|
|
54
|
+
# across both delegation layers: Kernel#retriable_with_context ->
|
|
55
|
+
# Retriable.with_context. If the `&` is dropped at either layer, the
|
|
56
|
+
# block is not forwarded and the inner `block_given?` check at
|
|
57
|
+
# lib/retriable.rb:51 short-circuits, causing the block to never run.
|
|
58
|
+
it "forwards a block through Kernel#retriable_with_context" do
|
|
59
|
+
require_relative "../lib/retriable/core_ext/kernel"
|
|
60
|
+
Retriable.configure { |c| c.contexts[:sql] = { tries: 1 } }
|
|
61
|
+
|
|
62
|
+
retriable_with_context(:sql) { increment_tries }
|
|
63
|
+
|
|
64
|
+
expect(@tries).to eq(1)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "returns nil when Kernel#retriable_with_context is called without a block" do
|
|
68
|
+
require_relative "../lib/retriable/core_ext/kernel"
|
|
69
|
+
Retriable.configure { |c| c.contexts[:sql] = { tries: 1 } }
|
|
70
|
+
|
|
71
|
+
expect(retriable_with_context(:sql)).to be_nil
|
|
72
|
+
expect(@tries).to eq(0)
|
|
73
|
+
end
|
|
42
74
|
end
|
|
43
75
|
|
|
44
76
|
context "#retriable" do
|
|
@@ -51,9 +83,6 @@ describe Retriable do
|
|
|
51
83
|
|
|
52
84
|
it "raises a LocalJumpError if not given a block" do
|
|
53
85
|
expect { described_class.retriable }.to raise_error(LocalJumpError)
|
|
54
|
-
expect do
|
|
55
|
-
expect { described_class.retriable(timeout: 2) }.to raise_error(LocalJumpError)
|
|
56
|
-
end.to output(/timeout.*deprecated.*Retriable 4\.0/i).to_stderr
|
|
57
86
|
end
|
|
58
87
|
|
|
59
88
|
it "stops at first try if the block does not raise an exception" do
|
|
@@ -171,86 +200,8 @@ describe Retriable do
|
|
|
171
200
|
end.to raise_error(ArgumentError, /tries/)
|
|
172
201
|
end
|
|
173
202
|
|
|
174
|
-
it "
|
|
175
|
-
expect
|
|
176
|
-
expect { described_class.retriable(timeout: 1) { sleep(1.1) } }.to raise_error(Timeout::Error)
|
|
177
|
-
end.to output(/timeout.*deprecated.*Retriable 4\.0/i).to_stderr
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
context "timeout: deprecation" do
|
|
181
|
-
it "warns at most once per process across repeated retriable calls" do
|
|
182
|
-
original_stderr = $stderr
|
|
183
|
-
stderr = StringIO.new
|
|
184
|
-
begin
|
|
185
|
-
$stderr = stderr
|
|
186
|
-
|
|
187
|
-
described_class.retriable(timeout: 5) { :noop }
|
|
188
|
-
described_class.retriable(timeout: 5) { :noop }
|
|
189
|
-
described_class.retriable(timeout: 5) { :noop }
|
|
190
|
-
|
|
191
|
-
expect(stderr.string.scan("timeout:` option is deprecated").size).to eq(1)
|
|
192
|
-
ensure
|
|
193
|
-
$stderr = original_stderr
|
|
194
|
-
end
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
it "warns when timeout is passed to retriable" do
|
|
198
|
-
expect do
|
|
199
|
-
described_class.retriable(timeout: 5) { :noop }
|
|
200
|
-
end.to output(/timeout.*deprecated.*Retriable 4\.0/i).to_stderr
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
it "keeps applying timeout while deprecated" do
|
|
204
|
-
original_stderr = $stderr
|
|
205
|
-
begin
|
|
206
|
-
$stderr = StringIO.new
|
|
207
|
-
expect do
|
|
208
|
-
described_class.retriable(timeout: 0.05, tries: 1) { sleep(0.5) }
|
|
209
|
-
end.to raise_error(Timeout::Error)
|
|
210
|
-
ensure
|
|
211
|
-
$stderr = original_stderr
|
|
212
|
-
end
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
it "warns when timeout is supplied through with_override" do
|
|
216
|
-
expect do
|
|
217
|
-
described_class.with_override(timeout: 5) do
|
|
218
|
-
described_class.retriable { :noop }
|
|
219
|
-
end
|
|
220
|
-
end.to output(/timeout.*deprecated.*Retriable 4\.0/i).to_stderr
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
it "warns when timeout is supplied through configure" do
|
|
224
|
-
original_config = described_class.config
|
|
225
|
-
begin
|
|
226
|
-
expect do
|
|
227
|
-
described_class.configure { |config| config.timeout = 5 }
|
|
228
|
-
described_class.retriable { :noop }
|
|
229
|
-
end.to output(/timeout.*deprecated.*Retriable 4\.0/i).to_stderr
|
|
230
|
-
ensure
|
|
231
|
-
described_class.configure do |config|
|
|
232
|
-
original_config.to_h.each { |key, value| config.public_send("#{key}=", value) }
|
|
233
|
-
end
|
|
234
|
-
end
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
it "is silenced by Warning[:deprecated] = false", if: WARN_CATEGORY_SUPPORTED do
|
|
238
|
-
original = Warning[:deprecated]
|
|
239
|
-
begin
|
|
240
|
-
Warning[:deprecated] = false
|
|
241
|
-
expect do
|
|
242
|
-
described_class.retriable(timeout: 5) { :noop }
|
|
243
|
-
end.not_to output.to_stderr
|
|
244
|
-
ensure
|
|
245
|
-
Warning[:deprecated] = original
|
|
246
|
-
end
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
it "does not warn when timeout is absent" do
|
|
250
|
-
expect do
|
|
251
|
-
described_class.retriable { :noop }
|
|
252
|
-
end.not_to output.to_stderr
|
|
253
|
-
end
|
|
203
|
+
it "rejects timeout as an unknown option" do
|
|
204
|
+
expect { described_class.retriable(timeout: 1) { :noop } }.to raise_error(ArgumentError, /not a valid option/)
|
|
254
205
|
end
|
|
255
206
|
|
|
256
207
|
it "applies a randomized exponential backoff to each try" do
|
|
@@ -296,6 +247,187 @@ describe Retriable do
|
|
|
296
247
|
end
|
|
297
248
|
end
|
|
298
249
|
|
|
250
|
+
it "does not call on_retry when explicitly set to nil" do
|
|
251
|
+
callback_called = false
|
|
252
|
+
original_on_retry = described_class.config.on_retry
|
|
253
|
+
|
|
254
|
+
begin
|
|
255
|
+
described_class.configure do |c|
|
|
256
|
+
c.on_retry = proc { |_exception, _try, _elapsed_time, _next_interval| callback_called = true }
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
expect do
|
|
260
|
+
described_class.retriable(on_retry: nil, tries: 3) { increment_tries_with_exception }
|
|
261
|
+
end.to raise_error(StandardError)
|
|
262
|
+
|
|
263
|
+
expect(@tries).to eq(3)
|
|
264
|
+
expect(callback_called).to be(false)
|
|
265
|
+
ensure
|
|
266
|
+
described_class.configure do |c|
|
|
267
|
+
c.on_retry = original_on_retry
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
it "calls on_give_up with max elapsed time details before re-raising" do
|
|
273
|
+
described_class.configure { |c| c.sleep_disabled = false }
|
|
274
|
+
give_up_calls = []
|
|
275
|
+
on_give_up = proc do |exception, try, elapsed_time, next_interval, reason|
|
|
276
|
+
give_up_calls << [exception, try, elapsed_time, next_interval, reason]
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
expect do
|
|
280
|
+
described_class.retriable(
|
|
281
|
+
intervals: [1.0, 1.0],
|
|
282
|
+
max_elapsed_time: 0.5,
|
|
283
|
+
on_give_up: on_give_up,
|
|
284
|
+
) do
|
|
285
|
+
increment_tries_with_exception
|
|
286
|
+
end
|
|
287
|
+
end.to raise_error(StandardError)
|
|
288
|
+
|
|
289
|
+
exception, try, elapsed_time, next_interval, reason = give_up_calls.fetch(0)
|
|
290
|
+
expect(give_up_calls.size).to eq(1)
|
|
291
|
+
expect(exception).to be_a(StandardError)
|
|
292
|
+
expect(exception.message).to eq("StandardError occurred")
|
|
293
|
+
expect(try).to eq(1)
|
|
294
|
+
expect(elapsed_time).to be >= 0
|
|
295
|
+
expect(next_interval).to eq(1.0)
|
|
296
|
+
expect(reason).to eq(:max_elapsed_time)
|
|
297
|
+
expect(@tries).to eq(1)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
it "calls on_give_up with tries exhausted details before re-raising" do
|
|
301
|
+
give_up_calls = []
|
|
302
|
+
on_give_up = proc do |exception, try, elapsed_time, next_interval, reason|
|
|
303
|
+
give_up_calls << [exception, try, elapsed_time, next_interval, reason]
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
expect do
|
|
307
|
+
described_class.retriable(tries: 2, on_give_up: on_give_up) { increment_tries_with_exception }
|
|
308
|
+
end.to raise_error(StandardError)
|
|
309
|
+
|
|
310
|
+
exception, try, elapsed_time, next_interval, reason = give_up_calls.fetch(0)
|
|
311
|
+
expect(give_up_calls.size).to eq(1)
|
|
312
|
+
expect(exception).to be_a(StandardError)
|
|
313
|
+
expect(exception.message).to eq("StandardError occurred")
|
|
314
|
+
expect(try).to eq(2)
|
|
315
|
+
expect(elapsed_time).to be >= 0
|
|
316
|
+
expect(next_interval).to be_nil
|
|
317
|
+
expect(reason).to eq(:tries_exhausted)
|
|
318
|
+
expect(@tries).to eq(2)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
it "does not call on_give_up when the block eventually succeeds" do
|
|
322
|
+
callback_called = false
|
|
323
|
+
|
|
324
|
+
described_class.retriable(tries: 3, on_give_up: proc { callback_called = true }) do
|
|
325
|
+
increment_tries
|
|
326
|
+
raise StandardError if @tries < 2
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
expect(callback_called).to be(false)
|
|
330
|
+
expect(@tries).to eq(2)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
it "does not call on_give_up for non-retriable exception types" do
|
|
334
|
+
callback_called = false
|
|
335
|
+
|
|
336
|
+
expect do
|
|
337
|
+
described_class.retriable(on_give_up: proc { callback_called = true }) do
|
|
338
|
+
increment_tries_with_exception(NonStandardError)
|
|
339
|
+
end
|
|
340
|
+
end.to raise_error(NonStandardError)
|
|
341
|
+
|
|
342
|
+
expect(callback_called).to be(false)
|
|
343
|
+
expect(@tries).to eq(1)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
it "does not call on_give_up when retry_if rejects the exception" do
|
|
347
|
+
callback_called = false
|
|
348
|
+
|
|
349
|
+
expect do
|
|
350
|
+
described_class.retriable(
|
|
351
|
+
tries: 3,
|
|
352
|
+
retry_if: ->(_exception) { false },
|
|
353
|
+
on_give_up: proc { callback_called = true },
|
|
354
|
+
) do
|
|
355
|
+
increment_tries_with_exception
|
|
356
|
+
end
|
|
357
|
+
end.to raise_error(StandardError)
|
|
358
|
+
|
|
359
|
+
expect(callback_called).to be(false)
|
|
360
|
+
expect(@tries).to eq(1)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
it "does not call on_give_up when explicitly set to false" do
|
|
364
|
+
callback_called = false
|
|
365
|
+
original_on_give_up = described_class.config.on_give_up
|
|
366
|
+
|
|
367
|
+
begin
|
|
368
|
+
described_class.configure do |c|
|
|
369
|
+
c.on_give_up = proc { callback_called = true }
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
expect do
|
|
373
|
+
described_class.retriable(on_give_up: false, tries: 1) { increment_tries_with_exception }
|
|
374
|
+
end.to raise_error(StandardError)
|
|
375
|
+
|
|
376
|
+
expect(callback_called).to be(false)
|
|
377
|
+
ensure
|
|
378
|
+
described_class.configure do |c|
|
|
379
|
+
c.on_give_up = original_on_give_up
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
it "does not call on_give_up when explicitly set to nil" do
|
|
385
|
+
callback_called = false
|
|
386
|
+
original_on_give_up = described_class.config.on_give_up
|
|
387
|
+
|
|
388
|
+
begin
|
|
389
|
+
described_class.configure do |c|
|
|
390
|
+
c.on_give_up = proc { callback_called = true }
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
expect do
|
|
394
|
+
described_class.retriable(on_give_up: nil, tries: 1) { increment_tries_with_exception }
|
|
395
|
+
end.to raise_error(StandardError)
|
|
396
|
+
|
|
397
|
+
expect(callback_called).to be(false)
|
|
398
|
+
ensure
|
|
399
|
+
described_class.configure do |c|
|
|
400
|
+
c.on_give_up = original_on_give_up
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
it "calls on_retry before on_give_up when giving up" do
|
|
406
|
+
events = []
|
|
407
|
+
|
|
408
|
+
expect do
|
|
409
|
+
described_class.retriable(
|
|
410
|
+
tries: 1,
|
|
411
|
+
on_retry: proc { events << :on_retry },
|
|
412
|
+
on_give_up: proc { events << :on_give_up },
|
|
413
|
+
) do
|
|
414
|
+
increment_tries_with_exception
|
|
415
|
+
end
|
|
416
|
+
end.to raise_error(StandardError)
|
|
417
|
+
|
|
418
|
+
expect(events).to eq(%i[on_retry on_give_up])
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
it "propagates exceptions raised inside on_give_up, replacing the original exception" do
|
|
422
|
+
handler = proc { raise "handler exploded" }
|
|
423
|
+
|
|
424
|
+
expect do
|
|
425
|
+
described_class.retriable(tries: 1, on_give_up: handler) { increment_tries_with_exception }
|
|
426
|
+
end.to raise_error(RuntimeError, "handler exploded")
|
|
427
|
+
|
|
428
|
+
expect(@tries).to eq(1)
|
|
429
|
+
end
|
|
430
|
+
|
|
299
431
|
context "with rand_factor 0.0 and an on_retry handler" do
|
|
300
432
|
let(:tries) { 6 }
|
|
301
433
|
let(:no_rand_timetable) { { 1 => 0.5, 2 => 0.75, 3 => 1.125 } }
|
|
@@ -375,6 +507,19 @@ describe Retriable do
|
|
|
375
507
|
end
|
|
376
508
|
end
|
|
377
509
|
|
|
510
|
+
context "with a Set :on parameter" do
|
|
511
|
+
it "retries each exception class in the Set" do
|
|
512
|
+
described_class.retriable(on: Set[StandardError, NonStandardError]) do
|
|
513
|
+
increment_tries
|
|
514
|
+
|
|
515
|
+
raise StandardError if @tries == 1
|
|
516
|
+
raise NonStandardError if @tries == 2
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
expect(@tries).to eq(3)
|
|
520
|
+
end
|
|
521
|
+
end
|
|
522
|
+
|
|
378
523
|
context "with a hash :on parameter" do
|
|
379
524
|
let(:on_hash) { { NonStandardError => /NonStandardError occurred/ } }
|
|
380
525
|
|
|
@@ -407,6 +552,20 @@ describe Retriable do
|
|
|
407
552
|
expect(@tries).to eq(1)
|
|
408
553
|
end
|
|
409
554
|
|
|
555
|
+
it "does not call on_give_up when exception class matches but message does not" do
|
|
556
|
+
callback_called = false
|
|
557
|
+
|
|
558
|
+
expect do
|
|
559
|
+
described_class.retriable(on: on_hash, on_give_up: proc { callback_called = true }) do
|
|
560
|
+
increment_tries
|
|
561
|
+
raise SecondNonStandardError, "not a match"
|
|
562
|
+
end
|
|
563
|
+
end.to raise_error(SecondNonStandardError, /not a match/)
|
|
564
|
+
|
|
565
|
+
expect(callback_called).to be(false)
|
|
566
|
+
expect(@tries).to eq(1)
|
|
567
|
+
end
|
|
568
|
+
|
|
410
569
|
it "successfully retries when the values are arrays of exception message patterns" do
|
|
411
570
|
exceptions = []
|
|
412
571
|
handler = ->(exception, try, _elapsed_time, _next_interval) { exceptions[try] = exception }
|
|
@@ -601,6 +760,38 @@ describe Retriable do
|
|
|
601
760
|
end
|
|
602
761
|
end
|
|
603
762
|
|
|
763
|
+
context "#retriable tries/intervals precedence" do
|
|
764
|
+
it "lets a per-call tries clear globally configured intervals" do
|
|
765
|
+
described_class.configure { |c| c.intervals = [0.5, 1.0] }
|
|
766
|
+
|
|
767
|
+
expect do
|
|
768
|
+
described_class.retriable(tries: 1) { increment_tries_with_exception }
|
|
769
|
+
end.to raise_error(StandardError)
|
|
770
|
+
|
|
771
|
+
expect(@tries).to eq(1)
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
it "still lets per-call intervals win when both intervals and tries are given" do
|
|
775
|
+
expect do
|
|
776
|
+
described_class.retriable(intervals: [0.5, 1.0], tries: 1) { increment_tries_with_exception }
|
|
777
|
+
end.to raise_error(StandardError)
|
|
778
|
+
|
|
779
|
+
expect(@tries).to eq(3) # intervals.size + 1
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
it "lets a with_context tries clear context intervals" do
|
|
783
|
+
described_class.configure do |c|
|
|
784
|
+
c.contexts[:api] = { intervals: [0.5, 1.0] }
|
|
785
|
+
end
|
|
786
|
+
|
|
787
|
+
expect do
|
|
788
|
+
described_class.with_context(:api, tries: 1) { increment_tries_with_exception }
|
|
789
|
+
end.to raise_error(StandardError)
|
|
790
|
+
|
|
791
|
+
expect(@tries).to eq(1)
|
|
792
|
+
end
|
|
793
|
+
end
|
|
794
|
+
|
|
604
795
|
context "#with_override" do
|
|
605
796
|
it "takes precedence over both global config and local options" do
|
|
606
797
|
described_class.configure { |c| c.tries = 2 }
|
|
@@ -737,17 +928,15 @@ describe Retriable do
|
|
|
737
928
|
end
|
|
738
929
|
|
|
739
930
|
it "treats non-hash configured contexts as empty when override contexts are hash" do
|
|
740
|
-
|
|
741
|
-
described_class.configure { |c| c.contexts = nil }
|
|
931
|
+
described_class.configure { |c| c.contexts = nil }
|
|
742
932
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
end
|
|
746
|
-
|
|
747
|
-
expect(@tries).to eq(1)
|
|
748
|
-
ensure
|
|
749
|
-
described_class.configure { |c| c.contexts = {} }
|
|
933
|
+
described_class.with_override(contexts: { api: { tries: 1 } }) do
|
|
934
|
+
described_class.with_context(:api) { increment_tries }
|
|
750
935
|
end
|
|
936
|
+
|
|
937
|
+
expect(@tries).to eq(1)
|
|
938
|
+
ensure
|
|
939
|
+
described_class.configure { |c| c.contexts = {} }
|
|
751
940
|
end
|
|
752
941
|
|
|
753
942
|
it "ignores nil override contexts values in with_context" do
|
|
@@ -1017,6 +1206,31 @@ describe Retriable do
|
|
|
1017
1206
|
|
|
1018
1207
|
expect(other_thread_tries).to eq(3)
|
|
1019
1208
|
end
|
|
1209
|
+
|
|
1210
|
+
it "applies overridden on_give_up handlers" do
|
|
1211
|
+
callback_called = false
|
|
1212
|
+
|
|
1213
|
+
expect do
|
|
1214
|
+
described_class.with_override(on_give_up: proc { callback_called = true }) do
|
|
1215
|
+
described_class.retriable(tries: 1) { increment_tries_with_exception }
|
|
1216
|
+
end
|
|
1217
|
+
end.to raise_error(StandardError)
|
|
1218
|
+
|
|
1219
|
+
expect(callback_called).to be(true)
|
|
1220
|
+
end
|
|
1221
|
+
|
|
1222
|
+
it "applies on_give_up handlers configured via per-context overrides" do
|
|
1223
|
+
received_reason = nil
|
|
1224
|
+
handler = proc { |_e, _try, _elapsed, _interval, reason| received_reason = reason }
|
|
1225
|
+
|
|
1226
|
+
expect do
|
|
1227
|
+
described_class.with_override(contexts: { api: { tries: 1, on_give_up: handler } }) do
|
|
1228
|
+
described_class.with_context(:api) { increment_tries_with_exception }
|
|
1229
|
+
end
|
|
1230
|
+
end.to raise_error(StandardError)
|
|
1231
|
+
|
|
1232
|
+
expect(received_reason).to eq(:tries_exhausted)
|
|
1233
|
+
end
|
|
1020
1234
|
end
|
|
1021
1235
|
|
|
1022
1236
|
context "#with_context" do
|
|
@@ -1075,5 +1289,17 @@ describe Retriable do
|
|
|
1075
1289
|
described_class.with_context(:broken) { increment_tries }
|
|
1076
1290
|
expect(@tries).to eq(1)
|
|
1077
1291
|
end
|
|
1292
|
+
|
|
1293
|
+
it "invokes on_give_up configured on a context" do
|
|
1294
|
+
callback_called = false
|
|
1295
|
+
described_class.configure do |c|
|
|
1296
|
+
c.contexts[:flaky] = { tries: 1, on_give_up: proc { callback_called = true } }
|
|
1297
|
+
end
|
|
1298
|
+
|
|
1299
|
+
expect { described_class.with_context(:flaky) { increment_tries_with_exception } }
|
|
1300
|
+
.to raise_error(StandardError)
|
|
1301
|
+
|
|
1302
|
+
expect(callback_called).to be(true)
|
|
1303
|
+
end
|
|
1078
1304
|
end
|
|
1079
1305
|
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -7,21 +7,8 @@ require "pry"
|
|
|
7
7
|
require_relative "../lib/retriable"
|
|
8
8
|
require_relative "support/exceptions"
|
|
9
9
|
|
|
10
|
-
# Make Retriable's deprecation notices observable to RSpec's
|
|
11
|
-
# `output().to_stderr` matcher. On Ruby 3.0+ the `:deprecated` warning category
|
|
12
|
-
# is suppressed by default, which would hide the notices we want to assert on.
|
|
13
|
-
WARNING_DEPRECATION_SUPPORTED = defined?(Warning) && Warning.respond_to?(:[])
|
|
14
|
-
Warning[:deprecated] = true if WARNING_DEPRECATION_SUPPORTED
|
|
15
|
-
|
|
16
|
-
# Used by deprecation specs that only make sense on Rubies where `Kernel#warn`
|
|
17
|
-
# supports the `category:` keyword (added in Ruby 2.7).
|
|
18
|
-
WARN_CATEGORY_SUPPORTED = WARNING_DEPRECATION_SUPPORTED &&
|
|
19
|
-
Kernel.method(:warn).parameters.include?(%i[key category])
|
|
20
|
-
|
|
21
10
|
RSpec.configure do |config|
|
|
22
11
|
config.before(:each) do
|
|
23
12
|
srand(0)
|
|
24
|
-
Retriable::Config.timeout_deprecation_warned = false
|
|
25
|
-
Warning[:deprecated] = true if WARNING_DEPRECATION_SUPPORTED
|
|
26
13
|
end
|
|
27
14
|
end
|
metadata
CHANGED
|
@@ -1,56 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: retriable
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 4.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jack Chu
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
-
dependencies:
|
|
12
|
-
- !ruby/object:Gem::Dependency
|
|
13
|
-
name: bundler
|
|
14
|
-
requirement: !ruby/object:Gem::Requirement
|
|
15
|
-
requirements:
|
|
16
|
-
- - ">="
|
|
17
|
-
- !ruby/object:Gem::Version
|
|
18
|
-
version: '0'
|
|
19
|
-
type: :development
|
|
20
|
-
prerelease: false
|
|
21
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
-
requirements:
|
|
23
|
-
- - ">="
|
|
24
|
-
- !ruby/object:Gem::Version
|
|
25
|
-
version: '0'
|
|
26
|
-
- !ruby/object:Gem::Dependency
|
|
27
|
-
name: rspec
|
|
28
|
-
requirement: !ruby/object:Gem::Requirement
|
|
29
|
-
requirements:
|
|
30
|
-
- - "~>"
|
|
31
|
-
- !ruby/object:Gem::Version
|
|
32
|
-
version: '3'
|
|
33
|
-
type: :development
|
|
34
|
-
prerelease: false
|
|
35
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
-
requirements:
|
|
37
|
-
- - "~>"
|
|
38
|
-
- !ruby/object:Gem::Version
|
|
39
|
-
version: '3'
|
|
40
|
-
- !ruby/object:Gem::Dependency
|
|
41
|
-
name: listen
|
|
42
|
-
requirement: !ruby/object:Gem::Requirement
|
|
43
|
-
requirements:
|
|
44
|
-
- - "~>"
|
|
45
|
-
- !ruby/object:Gem::Version
|
|
46
|
-
version: '3.1'
|
|
47
|
-
type: :development
|
|
48
|
-
prerelease: false
|
|
49
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
-
requirements:
|
|
51
|
-
- - "~>"
|
|
52
|
-
- !ruby/object:Gem::Version
|
|
53
|
-
version: '3.1'
|
|
11
|
+
dependencies: []
|
|
54
12
|
description: Retriable is a simple DSL to retry failed code blocks with randomized
|
|
55
13
|
exponential backoff. This is especially useful when interacting with external APIs/services
|
|
56
14
|
or file system calls.
|
|
@@ -91,7 +49,8 @@ files:
|
|
|
91
49
|
homepage: https://github.com/kamui/retriable
|
|
92
50
|
licenses:
|
|
93
51
|
- MIT
|
|
94
|
-
metadata:
|
|
52
|
+
metadata:
|
|
53
|
+
rubygems_mfa_required: 'true'
|
|
95
54
|
rdoc_options: []
|
|
96
55
|
require_paths:
|
|
97
56
|
- lib
|
|
@@ -99,7 +58,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
99
58
|
requirements:
|
|
100
59
|
- - ">="
|
|
101
60
|
- !ruby/object:Gem::Version
|
|
102
|
-
version:
|
|
61
|
+
version: '3.2'
|
|
103
62
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
104
63
|
requirements:
|
|
105
64
|
- - ">="
|
|
@@ -110,9 +69,4 @@ rubygems_version: 4.0.3
|
|
|
110
69
|
specification_version: 4
|
|
111
70
|
summary: Retriable is a simple DSL to retry failed code blocks with randomized exponential
|
|
112
71
|
backoff
|
|
113
|
-
test_files:
|
|
114
|
-
- spec/config_spec.rb
|
|
115
|
-
- spec/exponential_backoff_spec.rb
|
|
116
|
-
- spec/retriable_spec.rb
|
|
117
|
-
- spec/spec_helper.rb
|
|
118
|
-
- spec/support/exceptions.rb
|
|
72
|
+
test_files: []
|