@@ -16,83 +16,72 @@ def make_overload_error(message = 'overloaded')
1616 )
1717 end
1818
19- let ( :cluster ) { double ( 'cluster' ) }
20- let ( :server ) { double ( 'server' ) }
21- let ( :server_selector ) { double ( 'server_selector' ) }
19+ let ( :cluster ) { instance_double ( Mongo :: Cluster ) }
20+ let ( :server ) { instance_double ( Mongo :: Server ) }
21+ let ( :server_selector ) { instance_double ( Mongo :: ServerSelector :: Primary ) }
2222
2323 let ( :session ) do
24- double ( 'session' , retry_reads? : true , in_transaction? : false )
24+ instance_double ( Mongo :: Session , retry_reads? : true , in_transaction? : false )
2525 end
2626
2727 let ( :context ) do
28- double ( 'context' , remaining_timeout_sec : nil , csot? : false , deadline : nil ) . tap do |ctx |
29- allow ( ctx ) . to receive ( :check_timeout! )
30- end
28+ instance_double (
29+ Mongo ::Operation ::Context ,
30+ remaining_timeout_sec : nil , csot? : false , deadline : nil
31+ ) . tap { |ctx | allow ( ctx ) . to receive ( :check_timeout! ) }
3132 end
3233
33- # -------------------------------------------------------------------------
34- # Test 1: Operation Retry Uses Exponential Backoff
35- # -------------------------------------------------------------------------
36- describe 'Test 1: operation retry uses exponential backoff' do
34+ # Shared client/retryable/worker setup for read-worker prose tests.
35+ shared_context 'with read worker' do
3736 let ( :retry_policy ) { Mongo ::Retryable ::RetryPolicy . new }
3837
3938 let ( :client ) do
40- double ( 'client' ) . tap do |c |
39+ instance_double ( Mongo :: Client ) . tap do |c |
4140 allow ( c ) . to receive ( :retry_policy ) . and_return ( retry_policy )
4241 allow ( c ) . to receive ( :cluster ) . and_return ( cluster )
4342 end
4443 end
4544
4645 let ( :retryable ) do
47- double ( 'retryable' , client : client , cluster : cluster ) . tap do |r |
46+ instance_double ( Mongo :: Collection , client : client , cluster : cluster ) . tap do |r |
4847 allow ( r ) . to receive ( :select_server ) . and_return ( server )
4948 end
5049 end
5150
5251 let ( :worker ) { Mongo ::Retryable ::ReadWorker . new ( retryable ) }
5352
54- it 'with jitter=0 backoff is near-zero; with jitter~1 backoff >= 2.1s' do
55- sleep_args = [ ]
56- allow ( worker ) . to receive ( :sleep ) { |d | sleep_args << d }
57-
58- max_retries = Mongo ::Retryable ::Backpressure ::MAX_RETRIES
53+ before { allow ( worker ) . to receive ( :sleep ) }
54+ end
5955
60- # Run with jitter=0 (no backoff).
61- allow ( retry_policy ) . to receive ( :backoff_delay ) { |attempt |
62- Mongo ::Retryable ::Backpressure . backoff_delay ( attempt , jitter : 0.0 )
63- }
56+ # -------------------------------------------------------------------------
57+ # Test 1: Operation Retry Uses Exponential Backoff
58+ # -------------------------------------------------------------------------
59+ describe 'Test 1: operation retry uses exponential backoff' do
60+ include_context 'with read worker'
6461
65- sleep_args . clear
66- call_count = 0
67- expect do
68- worker . read_with_retry ( session , server_selector , context ) do |_s , _r |
69- call_count += 1
70- raise make_overload_error
71- end
72- end . to raise_error ( Mongo ::Error ::OperationFailure )
62+ let ( :sleep_args ) { [ ] }
7363
74- no_backoff_total = sleep_args . sum
64+ before do
65+ allow ( worker ) . to receive ( :sleep ) { |d | sleep_args << d }
66+ end
7567
76- # Run with jitter~1 (maximum backoff).
68+ def total_sleep_with_jitter ( jitter_value )
7769 allow ( retry_policy ) . to receive ( :backoff_delay ) { |attempt |
78- Mongo ::Retryable ::Backpressure . backoff_delay ( attempt , jitter : 1.0 )
70+ Mongo ::Retryable ::Backpressure . backoff_delay ( attempt , jitter : jitter_value )
7971 }
80-
8172 sleep_args . clear
82- call_count = 0
83- expect do
84- worker . read_with_retry ( session , server_selector , context ) do |_s , _r |
85- call_count += 1
86- raise make_overload_error
87- end
88- end . to raise_error ( Mongo ::Error ::OperationFailure )
89-
90- with_backoff_total = sleep_args . sum
73+ begin
74+ worker . read_with_retry ( session , server_selector , context ) { |_s , _r | raise make_overload_error }
75+ rescue Mongo ::Error ::OperationFailure
76+ # expected
77+ end
78+ sleep_args . sum
79+ end
9180
92- # The spec says the difference should be >= 2.1 seconds.
93- # With jitter=1 the total is 0.1+0.2+0.4+0.8+1.6 = 3.1s;
94- # with jitter=0 the total is 0.0s.
95- expect ( with_backoff_total - no_backoff_total ) . to be >= 2.1
81+ it 'with jitter=0 backoff is near-zero; with jitter~1 backoff >= 2.1s' do
82+ no_backoff = total_sleep_with_jitter ( 0.0 )
83+ with_backoff = total_sleep_with_jitter ( 1.0 )
84+ expect ( with_backoff - no_backoff ) . to be >= 2.1
9685 end
9786 end
9887
@@ -105,105 +94,54 @@ def make_overload_error(message = 'overloaded')
10594 bucket = policy . token_bucket
10695 capacity = Mongo ::Retryable ::Backpressure ::DEFAULT_RETRY_TOKEN_CAPACITY
10796
108- # Assert initial capacity.
10997 expect ( bucket . tokens ) . to eq ( capacity )
110- expect ( bucket . capacity ) . to eq ( capacity )
111-
112- # Simulate a successful (non-retry) command - deposits
113- # RETRY_TOKEN_RETURN_RATE (0.1) tokens.
11498 policy . record_success ( is_retry : false )
115-
116- # Tokens must not exceed capacity.
11799 expect ( bucket . tokens ) . to be <= capacity
118- expect ( bucket . tokens ) . to eq ( capacity )
119100 end
120101 end
121102
122103 # -------------------------------------------------------------------------
123104 # Test 3: Overload Errors are Retried MAX_RETRIES Times
124105 # -------------------------------------------------------------------------
125106 describe 'Test 3: overload errors are retried MAX_RETRIES times' do
126- let ( :retry_policy ) { Mongo ::Retryable ::RetryPolicy . new }
127-
128- let ( :client ) do
129- double ( 'client' ) . tap do |c |
130- allow ( c ) . to receive ( :retry_policy ) . and_return ( retry_policy )
131- allow ( c ) . to receive ( :cluster ) . and_return ( cluster )
132- end
133- end
134-
135- let ( :retryable ) do
136- double ( 'retryable' , client : client , cluster : cluster ) . tap do |r |
137- allow ( r ) . to receive ( :select_server ) . and_return ( server )
138- end
139- end
140-
141- let ( :worker ) { Mongo ::Retryable ::ReadWorker . new ( retryable ) }
142-
143- before { allow ( worker ) . to receive ( :sleep ) }
107+ include_context 'with read worker'
144108
145109 it 'attempts the command exactly MAX_RETRIES + 1 times' do
146- max_retries = Mongo ::Retryable ::Backpressure ::MAX_RETRIES
147110 call_count = 0
148-
149111 expect do
150112 worker . read_with_retry ( session , server_selector , context ) do |_s , _r |
151113 call_count += 1
152114 raise make_overload_error
153115 end
154- end . to raise_error ( Mongo ::Error ::OperationFailure ) { |e |
155- expect ( e . label? ( 'RetryableError' ) ) . to be true
156- expect ( e . label? ( 'SystemOverloadedError' ) ) . to be true
157- }
116+ end . to raise_error ( Mongo ::Error ::OperationFailure )
158117
159- expect ( call_count ) . to eq ( max_retries + 1 )
118+ expect ( call_count ) . to eq ( Mongo :: Retryable :: Backpressure :: MAX_RETRIES + 1 )
160119 end
161120 end
162121
163122 # -------------------------------------------------------------------------
164123 # Test 4: Adaptive Retries are Limited by Token Bucket Tokens
165124 # -------------------------------------------------------------------------
166125 describe 'Test 4: adaptive retries are limited by token bucket tokens' do
167- let ( :retry_policy ) { Mongo :: Retryable :: RetryPolicy . new ( adaptive_retries : true ) }
126+ include_context 'with read worker'
168127
169- let ( :client ) do
170- double ( 'client' ) . tap do |c |
171- allow ( c ) . to receive ( :retry_policy ) . and_return ( retry_policy )
172- allow ( c ) . to receive ( :cluster ) . and_return ( cluster )
173- end
174- end
175-
176- let ( :retryable ) do
177- double ( 'retryable' , client : client , cluster : cluster ) . tap do |r |
178- allow ( r ) . to receive ( :select_server ) . and_return ( server )
179- end
180- end
181-
182- let ( :worker ) { Mongo ::Retryable ::ReadWorker . new ( retryable ) }
183-
184- before { allow ( worker ) . to receive ( :sleep ) }
128+ let ( :retry_policy ) { Mongo ::Retryable ::RetryPolicy . new ( adaptive_retries : true ) }
185129
186- it 'retries only as many times as there are tokens (2 tokens -> 3 total attempts)' do
130+ before do
187131 bucket = retry_policy . token_bucket
188-
189- # Drain the bucket down to exactly 2 tokens.
190132 bucket . consume ( bucket . capacity )
191133 bucket . deposit ( 2 )
192- expect ( bucket . tokens ) . to eq ( 2 )
134+ end
193135
136+ it 'retries only as many times as there are tokens (2 tokens -> 3 total attempts)' do
194137 call_count = 0
195-
196138 expect do
197139 worker . read_with_retry ( session , server_selector , context ) do |_s , _r |
198140 call_count += 1
199141 raise make_overload_error
200142 end
201- end . to raise_error ( Mongo ::Error ::OperationFailure ) { |e |
202- expect ( e . label? ( 'RetryableError' ) ) . to be true
203- expect ( e . label? ( 'SystemOverloadedError' ) ) . to be true
204- }
143+ end . to raise_error ( Mongo ::Error ::OperationFailure )
205144
206- # 1 initial attempt + 2 retries (one token consumed per retry).
207145 expect ( call_count ) . to eq ( 3 )
208146 end
209147 end
0 commit comments