@@ -161,17 +161,22 @@ class MethodInvoker implements FtSupplier<Object> {
161161 * FT handler created for the method.
162162 */
163163 private static class MethodState {
164- private FtHandlerTyped <Object > handler ;
165164 private Retry retry ;
166165 private Bulkhead bulkhead ;
167166 private CircuitBreaker breaker ;
167+ private Timeout timeout ;
168168 private State lastBreakerState ;
169169 private long breakerTimerOpen ;
170170 private long breakerTimerClosed ;
171171 private long breakerTimerHalfOpen ;
172172 private long startNanos ;
173173 }
174174
175+ /**
176+ * FT handler for this invoker.
177+ */
178+ private FtHandlerTyped <Object > handler ;
179+
175180 /**
176181 * A key used to lookup {@code MethodState} instances, which include FT handlers.
177182 * A class loader is necessary to support multiple applications as seen in the TCKs.
@@ -302,10 +307,13 @@ public boolean cancel(boolean mayInterruptIfRunning) {
302307 methodState .breakerTimerHalfOpen = 0L ;
303308 methodState .startNanos = System .nanoTime ();
304309 }
305- methodState . handler = createMethodHandler (methodState );
310+ initMethodHandler (methodState );
306311 return methodState ;
307312 });
308313
314+ // Create a new method handler to ensure correct context in fallback
315+ handler = createMethodHandler (methodState );
316+
309317 // Gather information about current request scope if active
310318 try {
311319 requestController = CDI .current ().select (RequestContextController .class ).get ();
@@ -380,7 +388,7 @@ public Object get() throws Throwable {
380388 Supplier <Single <?>> supplier = () -> {
381389 try {
382390 return Contexts .runInContextWithThrow (helidonContext ,
383- () -> methodState . handler .invoke (toCompletionStageSupplier (context ::proceed )));
391+ () -> handler .invoke (toCompletionStageSupplier (context ::proceed )));
384392 } catch (Exception e ) {
385393 return Single .error (e );
386394 }
@@ -496,38 +504,27 @@ private FtSupplier<Object> requestContextSupplier(FtSupplier<Object> supplier) {
496504 }
497505
498506 /**
499- * Creates a FT handler for an invocation by inspecting annotations. Handlers
500- * are composed as follows:
501- *
502- * fallback(retry(circuitbreaker(timeout(bulkhead(method)))))
503- *
504- * Note that timeout includes the time an invocation may be queued in a
505- * bulkhead, so it needs to be before the bulkhead call.
507+ * Initializes method state by creating handlers for all FT annotations
508+ * except fallbacks. A fallback can reference the current invocation context
509+ * (via fallback method parameters) and cannot be cached.
506510 *
507511 * @param methodState State related to this invocation's method.
508512 */
509- private FtHandlerTyped <Object > createMethodHandler (MethodState methodState ) {
510- FaultTolerance .TypedBuilder <Object > builder = FaultTolerance .typedBuilder ();
511-
512- // Create and add bulkhead
513+ private void initMethodHandler (MethodState methodState ) {
513514 if (introspector .hasBulkhead ()) {
514515 methodState .bulkhead = Bulkhead .builder ()
515516 .limit (introspector .getBulkhead ().value ())
516517 .queueLength (introspector .isAsynchronous () ? introspector .getBulkhead ().waitingTaskQueue () : 0 )
517518 .build ();
518- builder .addBulkhead (methodState .bulkhead );
519519 }
520520
521- // Create and add timeout handler
522521 if (introspector .hasTimeout ()) {
523- Timeout timeout = Timeout .builder ()
522+ methodState . timeout = Timeout .builder ()
524523 .timeout (Duration .of (introspector .getTimeout ().value (), introspector .getTimeout ().unit ()))
525524 .currentThread (!introspector .isAsynchronous ())
526525 .build ();
527- builder .addTimeout (timeout );
528526 }
529527
530- // Create and add circuit breaker
531528 if (introspector .hasCircuitBreaker ()) {
532529 methodState .breaker = CircuitBreaker .builder ()
533530 .delay (Duration .of (introspector .getCircuitBreaker ().delay (),
@@ -538,29 +535,55 @@ private FtHandlerTyped<Object> createMethodHandler(MethodState methodState) {
538535 .applyOn (mapTypes (introspector .getCircuitBreaker ().failOn ()))
539536 .skipOn (mapTypes (introspector .getCircuitBreaker ().skipOn ()))
540537 .build ();
541- builder .addBreaker (methodState .breaker );
542538 }
543539
544- // Create and add retry handler
545540 if (introspector .hasRetry ()) {
546- Retry retry = Retry .builder ()
541+ methodState . retry = Retry .builder ()
547542 .retryPolicy (Retry .JitterRetryPolicy .builder ()
548543 .calls (introspector .getRetry ().maxRetries () + 1 )
549544 .delay (Duration .of (introspector .getRetry ().delay (),
550- introspector .getRetry ().delayUnit ()))
545+ introspector .getRetry ().delayUnit ()))
551546 .jitter (Duration .of (introspector .getRetry ().jitter (),
552- introspector .getRetry ().jitterDelayUnit ()))
547+ introspector .getRetry ().jitterDelayUnit ()))
553548 .build ())
554549 .overallTimeout (Duration .of (introspector .getRetry ().maxDuration (),
555- introspector .getRetry ().durationUnit ()))
550+ introspector .getRetry ().durationUnit ()))
556551 .applyOn (mapTypes (introspector .getRetry ().retryOn ()))
557552 .skipOn (mapTypes (introspector .getRetry ().abortOn ()))
558553 .build ();
559- builder .addRetry (retry );
560- methodState .retry = retry ; // keep reference to Retry
554+ }
555+ }
556+
557+ /**
558+ * Creates a FT handler for this invocation. Handlers are composed as follows:
559+ *
560+ * fallback(retry(circuitbreaker(timeout(bulkhead(method)))))
561+ *
562+ * Uses the cached handlers defined in the method state for this invocation's
563+ * method, except for fallback.
564+ *
565+ * @param methodState State related to this invocation's method.
566+ */
567+ private FtHandlerTyped <Object > createMethodHandler (MethodState methodState ) {
568+ FaultTolerance .TypedBuilder <Object > builder = FaultTolerance .typedBuilder ();
569+
570+ if (methodState .bulkhead != null ) {
571+ builder .addBulkhead (methodState .bulkhead );
572+ }
573+
574+ if (methodState .timeout != null ) {
575+ builder .addTimeout (methodState .timeout );
576+ }
577+
578+ if (methodState .breaker != null ) {
579+ builder .addBreaker (methodState .breaker );
580+ }
581+
582+ if (methodState .retry != null ) {
583+ builder .addRetry (methodState .retry );
561584 }
562585
563- // Create and add fallback handler
586+ // Create and add fallback handler for this invocation
564587 if (introspector .hasFallback ()) {
565588 Fallback <Object > fallback = Fallback .builder ()
566589 .fallback (throwable -> {
0 commit comments