11use crate :: {
2- Client , IsWorkerTaskLongPoll , NamespacedClient , Result , RetryConfig , raw:: IsUserLongPoll ,
2+ Client , IsWorkerTaskLongPoll , NamespacedClient , NoRetryOnMatching , Result , RetryConfig ,
3+ raw:: IsUserLongPoll ,
34} ;
45use backoff:: { Clock , SystemClock , backoff:: Backoff , exponential:: ExponentialBackoff } ;
56use futures_retry:: { ErrorHandler , FutureRetry , RetryPolicy } ;
@@ -58,13 +59,16 @@ impl<SG> RetryClient<SG> {
5859 request : Option < & Request < R > > ,
5960 ) -> CallInfo {
6061 let mut call_type = CallType :: Normal ;
62+ let mut retry_short_circuit = None ;
6163 if let Some ( r) = request. as_ref ( ) {
6264 let ext = r. extensions ( ) ;
6365 if ext. get :: < IsUserLongPoll > ( ) . is_some ( ) {
6466 call_type = CallType :: UserLongPoll ;
6567 } else if ext. get :: < IsWorkerTaskLongPoll > ( ) . is_some ( ) {
6668 call_type = CallType :: TaskLongPoll ;
6769 }
70+
71+ retry_short_circuit = ext. get :: < NoRetryOnMatching > ( ) . cloned ( ) ;
6872 }
6973 let retry_cfg = if call_type == CallType :: TaskLongPoll {
7074 RetryConfig :: task_poll_retry_policy ( )
@@ -75,6 +79,7 @@ impl<SG> RetryClient<SG> {
7579 call_type,
7680 call_name,
7781 retry_cfg,
82+ retry_short_circuit,
7883 }
7984 }
8085
@@ -111,6 +116,7 @@ pub(crate) struct TonicErrorHandler<C: Clock> {
111116 call_type : CallType ,
112117 call_name : & ' static str ,
113118 have_retried_goaway_cancel : bool ,
119+ retry_short_circuit : Option < NoRetryOnMatching > ,
114120}
115121impl TonicErrorHandler < SystemClock > {
116122 fn new ( call_info : CallInfo , throttle_cfg : RetryConfig ) -> Self {
@@ -139,6 +145,7 @@ where
139145 backoff : call_info. retry_cfg . into_exp_backoff ( clock) ,
140146 throttle_backoff : throttle_cfg. into_exp_backoff ( throttle_clock) ,
141147 have_retried_goaway_cancel : false ,
148+ retry_short_circuit : call_info. retry_short_circuit ,
142149 }
143150 }
144151
@@ -164,11 +171,12 @@ where
164171 }
165172}
166173
167- #[ derive( Clone , Debug , PartialEq ) ]
174+ #[ derive( Clone , Debug ) ]
168175pub ( crate ) struct CallInfo {
169176 pub call_type : CallType ,
170177 call_name : & ' static str ,
171178 retry_cfg : RetryConfig ,
179+ retry_short_circuit : Option < NoRetryOnMatching > ,
172180}
173181
174182#[ doc( hidden) ]
@@ -187,8 +195,6 @@ impl CallType {
187195 }
188196}
189197
190- // TODO: This ought to be configurable by the owner of the client. That way worker-specific concerns
191- // don't need to exist in this crate.
192198impl < C > ErrorHandler < tonic:: Status > for TonicErrorHandler < C >
193199where
194200 C : Clock ,
@@ -201,6 +207,12 @@ where
201207 return RetryPolicy :: ForwardError ( e) ;
202208 }
203209
210+ if let Some ( sc) = self . retry_short_circuit . as_ref ( ) {
211+ if ( sc. predicate ) ( & e) {
212+ return RetryPolicy :: ForwardError ( e) ;
213+ }
214+ }
215+
204216 // Task polls are OK with being cancelled or running into the timeout because there's
205217 // nothing to do but retry anyway
206218 let long_poll_allowed = self . call_type == CallType :: TaskLongPoll
@@ -307,6 +319,7 @@ mod tests {
307319 call_type : CallType :: TaskLongPoll ,
308320 call_name,
309321 retry_cfg : TEST_RETRY_CONFIG ,
322+ retry_short_circuit : None ,
310323 } ,
311324 TEST_RETRY_CONFIG ,
312325 FixedClock ( Instant :: now ( ) ) ,
@@ -334,6 +347,7 @@ mod tests {
334347 call_type : CallType :: TaskLongPoll ,
335348 call_name,
336349 retry_cfg : TEST_RETRY_CONFIG ,
350+ retry_short_circuit : None ,
337351 } ,
338352 TEST_RETRY_CONFIG ,
339353 FixedClock ( Instant :: now ( ) ) ,
@@ -359,6 +373,7 @@ mod tests {
359373 call_type : CallType :: TaskLongPoll ,
360374 call_name : POLL_WORKFLOW_METH_NAME ,
361375 retry_cfg : TEST_RETRY_CONFIG ,
376+ retry_short_circuit : None ,
362377 } ,
363378 RetryConfig {
364379 initial_interval : Duration :: from_millis ( 2 ) ,
@@ -389,6 +404,25 @@ mod tests {
389404 }
390405 }
391406
407+ #[ tokio:: test]
408+ async fn retry_short_circuit ( ) {
409+ let mut err_handler = TonicErrorHandler :: new_with_clock (
410+ CallInfo {
411+ call_type : CallType :: TaskLongPoll ,
412+ call_name : POLL_WORKFLOW_METH_NAME ,
413+ retry_cfg : TEST_RETRY_CONFIG ,
414+ retry_short_circuit : Some ( NoRetryOnMatching {
415+ predicate : |s : & Status | s. code ( ) == Code :: ResourceExhausted ,
416+ } ) ,
417+ } ,
418+ TEST_RETRY_CONFIG ,
419+ FixedClock ( Instant :: now ( ) ) ,
420+ FixedClock ( Instant :: now ( ) ) ,
421+ ) ;
422+ let result = err_handler. handle ( 1 , Status :: new ( Code :: ResourceExhausted , "leave me alone" ) ) ;
423+ assert_matches ! ( result, RetryPolicy :: ForwardError ( _) )
424+ }
425+
392426 #[ rstest:: rstest]
393427 #[ tokio:: test]
394428 async fn task_poll_retries_forever < R > (
0 commit comments