2020import static io .grpc .MethodDescriptor .MethodType .UNARY ;
2121import static io .grpc .Status .Code .UNAVAILABLE ;
2222import static java .util .concurrent .TimeUnit .MILLISECONDS ;
23+ import static org .junit .Assert .fail ;
2324
2425import com .google .common .collect .ImmutableList ;
2526import com .google .common .collect .ImmutableMap ;
@@ -155,13 +156,14 @@ public void managedChannelServiceConfig_parseMethodConfig() {
155156 "name" , ImmutableList .of (name1 , name2 ),
156157 "timeout" , "1.234s" ,
157158 "retryPolicy" ,
158- ImmutableMap .of (
159- "maxAttempts" , 3.0D ,
160- "initialBackoff" , "1s" ,
161- "maxBackoff" , "10s" ,
162- "backoffMultiplier" , 1.5D ,
163- "retryableStatusCodes" , ImmutableList .of ("UNAVAILABLE" )
164- ));
159+ ImmutableMap .builder ()
160+ .put ("maxAttempts" , 3.0D )
161+ .put ("initialBackoff" , "1s" )
162+ .put ("maxBackoff" , "10s" )
163+ .put ("backoffMultiplier" , 1.5D )
164+ .put ("perAttemptRecvTimeout" , "2.5s" )
165+ .put ("retryableStatusCodes" , ImmutableList .of ("UNAVAILABLE" ))
166+ .build ());
165167 Map <String , ?> defaultMethodConfig = ImmutableMap .of (
166168 "name" , ImmutableList .of (ImmutableMap .of ()),
167169 "timeout" , "4.321s" );
@@ -187,6 +189,8 @@ public void managedChannelServiceConfig_parseMethodConfig() {
187189 methodInfo = serviceConfig .getMethodConfig (methodForName ("service1" , "method1" ));
188190 assertThat (methodInfo .timeoutNanos ).isEqualTo (MILLISECONDS .toNanos (1234 ));
189191 assertThat (methodInfo .retryPolicy .maxAttempts ).isEqualTo (2 );
192+ assertThat (methodInfo .retryPolicy .perAttemptRecvTimeoutNanos )
193+ .isEqualTo (MILLISECONDS .toNanos (2500 ));
190194 assertThat (methodInfo .retryPolicy .retryableStatusCodes ).containsExactly (UNAVAILABLE );
191195 methodInfo = serviceConfig .getMethodConfig (methodForName ("service1" , "methodX" ));
192196 assertThat (methodInfo .timeoutNanos ).isEqualTo (MILLISECONDS .toNanos (4321 ));
@@ -212,6 +216,84 @@ public void getDefaultConfigSelectorFromConfig() {
212216 .isEqualTo (serviceConfig .getMethodConfig (method ));
213217 }
214218
219+ @ Test
220+ public void retryConfig_emptyRetriableStatusCodesAllowedWithPerAttemptRecvTimeoutGiven () {
221+ Map <String , ?> retryPolicy = ImmutableMap .<String , Object >builder ()
222+ .put ("maxAttempts" , 3.0D )
223+ .put ("initialBackoff" , "1s" )
224+ .put ("maxBackoff" , "10s" )
225+ .put ("backoffMultiplier" , 1.5D )
226+ .put ("perAttemptRecvTimeout" , "2.5s" )
227+ .put ("retryableStatusCodes" , ImmutableList .of ())
228+ .build ();
229+ Map <String , ?> methodConfig = ImmutableMap .of (
230+ "name" , ImmutableList .of (ImmutableMap .of ()), "retryPolicy" , retryPolicy );
231+ Map <String , ?> rawServiceConfig =
232+ ImmutableMap .of ("methodConfig" , ImmutableList .of (methodConfig ));
233+ assertThat (ManagedChannelServiceConfig .fromServiceConfig (rawServiceConfig , true , 5 , 5 , null ))
234+ .isNotNull ();
235+ }
236+
237+ @ Test
238+ public void retryConfig_PerAttemptRecvTimeoutUnsetAllowedIfRetryableStatusCodesNonempty () {
239+ Map <String , ?> retryPolicy = ImmutableMap .<String , Object >builder ()
240+ .put ("maxAttempts" , 3.0D )
241+ .put ("initialBackoff" , "1s" )
242+ .put ("maxBackoff" , "10s" )
243+ .put ("backoffMultiplier" , 1.5D )
244+ .put ("retryableStatusCodes" , ImmutableList .of ("UNAVAILABLE" ))
245+ .build ();
246+ Map <String , ?> methodConfig = ImmutableMap .of (
247+ "name" , ImmutableList .of (ImmutableMap .of ()), "retryPolicy" , retryPolicy );
248+ Map <String , ?> rawServiceConfig =
249+ ImmutableMap .of ("methodConfig" , ImmutableList .of (methodConfig ));
250+ assertThat (ManagedChannelServiceConfig .fromServiceConfig (rawServiceConfig , true , 5 , 5 , null ))
251+ .isNotNull ();
252+ }
253+
254+ @ Test
255+ public void retryConfig_emptyRetriableStatusCodesNotAllowedWithPerAttemptRecvTimeoutUnset () {
256+ Map <String , ?> retryPolicy = ImmutableMap .<String , Object >builder ()
257+ .put ("maxAttempts" , 3.0D )
258+ .put ("initialBackoff" , "1s" )
259+ .put ("maxBackoff" , "10s" )
260+ .put ("backoffMultiplier" , 1.5D )
261+ .put ("retryableStatusCodes" , ImmutableList .of ())
262+ .build ();
263+ Map <String , ?> methodConfig = ImmutableMap .of (
264+ "name" , ImmutableList .of (ImmutableMap .of ()), "retryPolicy" , retryPolicy );
265+ Map <String , ?> rawServiceConfig =
266+ ImmutableMap .of ("methodConfig" , ImmutableList .of (methodConfig ));
267+ try {
268+ ManagedChannelServiceConfig .fromServiceConfig (rawServiceConfig , true , 5 , 5 , null );
269+ fail ("The expected IllegalArgumentException is not thrown" );
270+ } catch (IllegalArgumentException e ) {
271+ assertThat (e ).hasMessageThat ().contains (
272+ "retryableStatusCodes cannot be empty without perAttemptRecvTimeout" );
273+ }
274+ }
275+
276+ // For now we allow perAttemptRecvTimeout being zero although it does not make sense.
277+ // TODO(zdapeng): disallow zero perAttemptRecvTimeout if hedging is not enabled once we support
278+ // hedge_on_per_try_timeout.
279+ @ Test
280+ public void retryConfig_AllowPerAttemptRecvTimeoutZero () {
281+ Map <String , ?> retryPolicy = ImmutableMap .<String , Object >builder ()
282+ .put ("maxAttempts" , 3.0D )
283+ .put ("initialBackoff" , "1s" )
284+ .put ("maxBackoff" , "10s" )
285+ .put ("backoffMultiplier" , 1.5D )
286+ .put ("perAttemptRecvTimeout" , "0s" )
287+ .put ("retryableStatusCodes" , ImmutableList .of ())
288+ .build ();
289+ Map <String , ?> methodConfig = ImmutableMap .of (
290+ "name" , ImmutableList .of (ImmutableMap .of ()), "retryPolicy" , retryPolicy );
291+ Map <String , ?> rawServiceConfig =
292+ ImmutableMap .of ("methodConfig" , ImmutableList .of (methodConfig ));
293+ assertThat (ManagedChannelServiceConfig .fromServiceConfig (rawServiceConfig , true , 5 , 5 , null ))
294+ .isNotNull ();
295+ }
296+
215297 private static MethodDescriptor <?, ?> methodForName (String service , String method ) {
216298 return MethodDescriptor .<Void , Void >newBuilder ()
217299 .setFullMethodName (service + "/" + method )
0 commit comments