Skip to content

Commit d407c61

Browse files
RUBY-3798 Backpressure examples
1 parent 9b51d53 commit d407c61

File tree

1 file changed

+127
-0
lines changed

1 file changed

+127
-0
lines changed
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
describe 'backpressure examples' do
6+
RETRYABLE_ERROR_LABEL = 'RetryableError'
7+
SYSTEM_OVERLOADED_ERROR = 'SystemOverloadedError'
8+
BASE_BACKOFF_MS = 100
9+
MAX_BACKOFF_MS = 10_000
10+
11+
def system_overloaded_error?(error)
12+
error.respond_to?(:label?) && error.label?(SYSTEM_OVERLOADED_ERROR)
13+
end
14+
15+
def calculate_exponential_backoff(attempt)
16+
rand * [ MAX_BACKOFF_MS, BASE_BACKOFF_MS * (2**(attempt - 1)) ].min
17+
end
18+
19+
def with_retries(max_attempts: 2)
20+
max_attempts.times do |attempt|
21+
is_retry = attempt > 0
22+
if is_retry
23+
delay = calculate_exponential_backoff(attempt)
24+
sleep(delay / 1000.0)
25+
end
26+
begin
27+
return yield
28+
rescue StandardError => e
29+
is_retryable_overload_error = system_overloaded_error?(e) && e.label?(RETRYABLE_ERROR_LABEL)
30+
can_retry = is_retryable_overload_error && attempt + 1 < max_attempts
31+
raise unless can_retry
32+
end
33+
end
34+
end
35+
describe '#system_overloaded_error?' do
36+
it 'returns true for an error with the SystemOverloadedError label' do
37+
error = Mongo::Error.new('overloaded')
38+
error.add_label(SYSTEM_OVERLOADED_ERROR)
39+
expect(system_overloaded_error?(error)).to be true
40+
end
41+
42+
it 'returns false for an error without the SystemOverloadedError label' do
43+
error = Mongo::Error.new('other')
44+
expect(system_overloaded_error?(error)).to be_falsey
45+
end
46+
47+
it 'returns false for a plain StandardError' do
48+
error = StandardError.new('plain')
49+
expect(system_overloaded_error?(error)).to be_falsey
50+
end
51+
end
52+
53+
describe '#calculate_exponential_backoff' do
54+
it 'returns a value between 0 and BASE_BACKOFF_MS for the first retry' do
55+
results = Array.new(100) { calculate_exponential_backoff(1) }
56+
expect(results).to all(be >= 0)
57+
expect(results).to all(be <= BASE_BACKOFF_MS)
58+
end
59+
60+
it 'caps at MAX_BACKOFF_MS for high attempt numbers' do
61+
results = Array.new(100) { calculate_exponential_backoff(100) }
62+
expect(results).to all(be >= 0)
63+
expect(results).to all(be <= MAX_BACKOFF_MS)
64+
end
65+
end
66+
67+
describe '#with_retries' do
68+
it 'returns the result of the block on success' do
69+
result = with_retries { 42 }
70+
expect(result).to eq(42)
71+
end
72+
73+
it 'raises non-retryable errors immediately' do
74+
attempts = 0
75+
expect do
76+
with_retries(max_attempts: 3) do
77+
attempts += 1
78+
raise StandardError, 'fatal'
79+
end
80+
end.to raise_error(StandardError, 'fatal')
81+
expect(attempts).to eq(1)
82+
end
83+
84+
it 'raises overload errors that lack the RetryableError label' do
85+
attempts = 0
86+
expect do
87+
with_retries(max_attempts: 3) do
88+
attempts += 1
89+
error = Mongo::Error.new('overloaded')
90+
error.add_label(SYSTEM_OVERLOADED_ERROR)
91+
raise error
92+
end
93+
end.to raise_error(Mongo::Error, 'overloaded')
94+
expect(attempts).to eq(1)
95+
end
96+
97+
it 'retries retryable overload errors up to max_attempts' do
98+
attempts = 0
99+
expect do
100+
with_retries(max_attempts: 3) do
101+
attempts += 1
102+
error = Mongo::Error.new('overloaded')
103+
error.add_label(SYSTEM_OVERLOADED_ERROR)
104+
error.add_label(RETRYABLE_ERROR_LABEL)
105+
raise error
106+
end
107+
end.to raise_error(Mongo::Error, 'overloaded')
108+
expect(attempts).to eq(3)
109+
end
110+
111+
it 'succeeds on retry after a retryable overload error' do
112+
attempts = 0
113+
result = with_retries(max_attempts: 3) do
114+
attempts += 1
115+
if attempts < 2
116+
error = Mongo::Error.new('overloaded')
117+
error.add_label(SYSTEM_OVERLOADED_ERROR)
118+
error.add_label(RETRYABLE_ERROR_LABEL)
119+
raise error
120+
end
121+
'ok'
122+
end
123+
expect(result).to eq('ok')
124+
expect(attempts).to eq(2)
125+
end
126+
end
127+
end

0 commit comments

Comments
 (0)