@@ -4,10 +4,12 @@ import * as sinon from 'sinon';
44import {
55 type Collection ,
66 INITIAL_TOKEN_BUCKET_SIZE ,
7+ MAX_RETRIES ,
78 type MongoClient ,
89 MongoServerError
910} from '../../mongodb' ;
1011import { clearFailPoint , configureFailPoint , measureDuration } from '../../tools/utils' ;
12+ import { filterForCommands } from '../shared' ;
1113
1214describe ( 'Client Backpressure (Prose)' , function ( ) {
1315 let client : MongoClient ;
@@ -78,4 +80,121 @@ describe('Client Backpressure (Prose)', function () {
7880 // above DEFAULT_RETRY_TOKEN_CAPACITY.
7981 expect ( tokenBucket ) . to . have . property ( 'budget' ) . that . is . at . most ( INITIAL_TOKEN_BUCKET_SIZE ) ;
8082 } ) ;
83+
84+ it (
85+ 'Test 3: Overload Errors are Retried a Maximum of MAX_RETRIES times' ,
86+ {
87+ requires : {
88+ mongodb : '>=4.4'
89+ }
90+ } ,
91+ async function ( ) {
92+ // 1. Let `client` be a `MongoClient` with command event monitoring enabled.
93+ const client = this . configuration . newClient ( {
94+ monitorCommands : true
95+ } ) ;
96+ await client . connect ( ) ;
97+
98+ // 2. Let `coll` be a collection.
99+ const collection = client . db ( 'foo' ) . collection ( 'bar' ) ;
100+ const commandsStarted = [ ] ;
101+ client . on ( 'commandStarted' , filterForCommands ( [ 'find' ] , commandsStarted ) ) ;
102+
103+ /*
104+ * 3. Configure the following failpoint:
105+ {
106+ configureFailPoint: 'failCommand',
107+ mode: 'alwaysOn',
108+ data: {
109+ failCommands: ['find'],
110+ errorCode: 462, // IngressRequestRateLimitExceeded
111+ errorLabels: ['SystemOverloadedError', 'RetryableError']
112+ }
113+ }
114+ * */
115+ await configureFailPoint ( this . configuration , {
116+ configureFailPoint : 'failCommand' ,
117+ mode : 'alwaysOn' ,
118+ data : {
119+ failCommands : [ 'find' ] ,
120+ errorCode : 462 ,
121+ errorLabels : [ 'RetryableError' , 'SystemOverloadedError' ]
122+ }
123+ } ) ;
124+
125+ // 4. Perform a find operation with `coll` that fails.
126+ const error = await collection . findOne ( { } ) . catch ( e => e ) ;
127+
128+ // 5. Assert that the raised error contains both the `RetryableError` and `SystemOverLoadedError` error labels.
129+ expect ( error ) . to . be . instanceof ( MongoServerError ) ;
130+ expect ( error . hasErrorLabel ( 'RetryableError' ) ) . to . be . true ;
131+ expect ( error . hasErrorLabel ( 'SystemOverLoadedError' ) ) . to . be . true ;
132+
133+ // 6. Assert that the total number of started commands is MAX_RETRIES + 1 (6).
134+ expect ( commandsStarted ) . to . have . length ( MAX_RETRIES + 1 ) ;
135+
136+ await client . close ( ) ;
137+ }
138+ ) ;
139+
140+ it (
141+ 'Test 4: Adaptive Retries are Limited by Token Bucket Tokens' ,
142+ {
143+ requires : {
144+ mongodb : '>=4.4'
145+ }
146+ } ,
147+ async function ( ) {
148+ // 1. Let `client` be a `MongoClient` with `adaptiveRetries=True` and command event monitoring enabled.
149+ const client = this . configuration . newClient ( {
150+ adaptiveRetries : true ,
151+ monitorCommands : true
152+ } ) ;
153+ await client . connect ( ) ;
154+
155+ // 2. Set `client`'s retry token bucket to have 2 tokens.
156+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
157+ client . topology ! . tokenBucket [ 'budget' ] = 2 ;
158+
159+ // 3. Let `coll` be a collection.
160+ const collection = client . db ( 'foo' ) . collection ( 'bar' ) ;
161+ const commandsStarted = [ ] ;
162+ client . on ( 'commandStarted' , filterForCommands ( [ 'find' ] , commandsStarted ) ) ;
163+
164+ /*
165+ * 4. Configure the following failpoint:
166+ {
167+ configureFailPoint: 'failCommand',
168+ mode: {times: 3},
169+ data: {
170+ failCommands: ['find'],
171+ errorCode: 462, // IngressRequestRateLimitExceeded
172+ errorLabels: ['SystemOverloadedError', 'RetryableError']
173+ }
174+ }
175+ * */
176+ await configureFailPoint ( this . configuration , {
177+ configureFailPoint : 'failCommand' ,
178+ mode : { times : 3 } ,
179+ data : {
180+ failCommands : [ 'find' ] ,
181+ errorCode : 462 ,
182+ errorLabels : [ 'RetryableError' , 'SystemOverloadedError' ]
183+ }
184+ } ) ;
185+
186+ // 5. Perform a find operation with `coll` that fails.
187+ const error = await collection . findOne ( { } ) . catch ( e => e ) ;
188+
189+ // 6. Assert that the raised error contains both the `RetryableError` and `SystemOverLoadedError` error labels.
190+ expect ( error ) . to . be . instanceof ( MongoServerError ) ;
191+ expect ( error . hasErrorLabel ( 'RetryableError' ) ) . to . be . true ;
192+ expect ( error . hasErrorLabel ( 'SystemOverLoadedError' ) ) . to . be . true ;
193+
194+ // 7. Assert that the total number of started commands is 3: one for the initial attempt and two for the retries.
195+ expect ( commandsStarted ) . to . have . length ( 3 ) ;
196+
197+ await client . close ( ) ;
198+ }
199+ ) ;
81200} ) ;
0 commit comments