LCOV - code coverage report
Current view: top level - server/update - RetryableAction.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 35 37 94.6 %
Date: 2022-11-19 15:00:39 Functions: 11 11 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2019 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.server.update;
      16             : 
      17             : import static java.util.Objects.requireNonNull;
      18             : 
      19             : import com.github.rholder.retry.RetryListener;
      20             : import com.google.common.base.Throwables;
      21             : import com.google.gerrit.server.ExceptionHook;
      22             : import java.util.ArrayList;
      23             : import java.util.List;
      24             : import java.util.function.Consumer;
      25             : import java.util.function.Predicate;
      26             : 
      27             : /**
      28             :  * An action that is executed with retrying.
      29             :  *
      30             :  * <p>Instances of this class are created via {@link RetryHelper} (see {@link
      31             :  * RetryHelper#action(ActionType, String, Action)}, {@link RetryHelper#accountUpdate(String,
      32             :  * Action)}, {@link RetryHelper#changeUpdate(String, Action)}, {@link
      33             :  * RetryHelper#groupUpdate(String, Action)}, {@link RetryHelper#pluginUpdate(String, Action)}).
      34             :  *
      35             :  * <p>Which exceptions cause a retry is controlled by {@link ExceptionHook#shouldRetry(String,
      36             :  * String, Throwable)}. In addition callers can specify additional exception that should cause a
      37             :  * retry via {@link #retryOn(Predicate)}.
      38             :  */
      39             : public class RetryableAction<T> {
      40             :   /**
      41             :    * Type of an retryable action.
      42             :    *
      43             :    * <p>The action type is used for two purposes:
      44             :    *
      45             :    * <ul>
      46             :    *   <li>to determine the default timeout for executing the action (see {@link
      47             :    *       RetryHelper#getDefaultTimeout(String)})
      48             :    *   <li>as bucket for all retry metrics (see {@link RetryHelper.Metrics})
      49             :    * </ul>
      50             :    */
      51         151 :   public enum ActionType {
      52         151 :     ACCOUNT_UPDATE,
      53         151 :     CHANGE_UPDATE,
      54         151 :     GIT_UPDATE,
      55         151 :     GROUP_UPDATE,
      56         151 :     INDEX_QUERY,
      57         151 :     PLUGIN_UPDATE,
      58         151 :     REST_READ_REQUEST,
      59         151 :     REST_WRITE_REQUEST,
      60         151 :     SEND_EMAIL,
      61             :   }
      62             : 
      63             :   @FunctionalInterface
      64             :   public interface Action<T> {
      65             :     T call() throws Exception;
      66             :   }
      67             : 
      68             :   private final RetryHelper retryHelper;
      69             :   private final String actionType;
      70             :   private final Action<T> action;
      71         151 :   private final RetryHelper.Options.Builder options = RetryHelper.options();
      72         151 :   private final List<Predicate<Throwable>> exceptionPredicates = new ArrayList<>();
      73             : 
      74             :   RetryableAction(
      75             :       RetryHelper retryHelper, ActionType actionType, String actionName, Action<T> action) {
      76         151 :     this(retryHelper, requireNonNull(actionType, "actionType").name(), actionName, action);
      77         151 :   }
      78             : 
      79         151 :   RetryableAction(RetryHelper retryHelper, String actionType, String actionName, Action<T> action) {
      80         151 :     this.retryHelper = requireNonNull(retryHelper, "retryHelper");
      81         151 :     this.actionType = requireNonNull(actionType, "actionType");
      82         151 :     this.action = requireNonNull(action, "action");
      83         151 :     options.actionName(requireNonNull(actionName, "actionName"));
      84         151 :   }
      85             : 
      86             :   /**
      87             :    * Adds an additional condition that should trigger retries.
      88             :    *
      89             :    * <p>For some exceptions retrying is enabled globally (see {@link
      90             :    * ExceptionHook#shouldRetry(String, String, Throwable)}). Conditions for those exceptions do not
      91             :    * need to be specified here again.
      92             :    *
      93             :    * <p>This method can be invoked multiple times to add further conditions that should trigger
      94             :    * retries.
      95             :    *
      96             :    * @param exceptionPredicate predicate that decides if the action should be retried for a given
      97             :    *     exception
      98             :    * @return this instance to enable chaining of calls
      99             :    */
     100             :   public RetryableAction<T> retryOn(Predicate<Throwable> exceptionPredicate) {
     101          53 :     exceptionPredicates.add(exceptionPredicate);
     102          53 :     return this;
     103             :   }
     104             : 
     105             :   /**
     106             :    * Sets a condition that should trigger auto-retry with tracing.
     107             :    *
     108             :    * <p>This condition is only relevant if an exception occurs that doesn't trigger (normal) retry.
     109             :    *
     110             :    * <p>Auto-retry with tracing automatically captures traces for unexpected exceptions so that they
     111             :    * can be investigated.
     112             :    *
     113             :    * <p>Every call of this method overwrites any previously set condition for auto-retry with
     114             :    * tracing.
     115             :    *
     116             :    * @param exceptionPredicate predicate that decides if the action should be retried with tracing
     117             :    *     for a given exception
     118             :    * @return this instance to enable chaining of calls
     119             :    */
     120             :   public RetryableAction<T> retryWithTrace(Predicate<Throwable> exceptionPredicate) {
     121         114 :     options.retryWithTrace(exceptionPredicate);
     122         114 :     return this;
     123             :   }
     124             : 
     125             :   /**
     126             :    * Sets a callback that is invoked when auto-retry with tracing is triggered.
     127             :    *
     128             :    * <p>Via the callback callers can find out with trace ID was used for the retry.
     129             :    *
     130             :    * <p>Every call of this method overwrites any previously set trace ID consumer.
     131             :    *
     132             :    * @param traceIdConsumer trace ID consumer
     133             :    * @return this instance to enable chaining of calls
     134             :    */
     135             :   public RetryableAction<T> onAutoTrace(Consumer<String> traceIdConsumer) {
     136          28 :     options.onAutoTrace(traceIdConsumer);
     137          28 :     return this;
     138             :   }
     139             : 
     140             :   /**
     141             :    * Sets a listener that is invoked when the action is retried.
     142             :    *
     143             :    * <p>Every call of this method overwrites any previously set listener.
     144             :    *
     145             :    * @param retryListener retry listener
     146             :    * @return this instance to enable chaining of calls
     147             :    */
     148             :   public RetryableAction<T> listener(RetryListener retryListener) {
     149          53 :     options.listener(retryListener);
     150          53 :     return this;
     151             :   }
     152             : 
     153             :   /**
     154             :    * Increases the default timeout by the given multiplier.
     155             :    *
     156             :    * <p>Every call of this method overwrites any previously set timeout.
     157             :    *
     158             :    * @param multiplier multiplier for the default timeout
     159             :    * @return this instance to enable chaining of calls
     160             :    */
     161             :   public RetryableAction<T> defaultTimeoutMultiplier(int multiplier) {
     162          67 :     options.timeout(retryHelper.getDefaultTimeout(actionType).multipliedBy(multiplier));
     163          67 :     return this;
     164             :   }
     165             : 
     166             :   /**
     167             :    * Executes this action with retry.
     168             :    *
     169             :    * @return the result of the action
     170             :    */
     171             :   public T call() throws Exception {
     172             :     try {
     173         151 :       return retryHelper.execute(
     174             :           actionType,
     175             :           action,
     176         151 :           options.build(),
     177          38 :           t -> exceptionPredicates.stream().anyMatch(p -> p.test(t)));
     178          38 :     } catch (Exception t) {
     179          36 :       Throwables.throwIfUnchecked(t);
     180           0 :       Throwables.throwIfInstanceOf(t, Exception.class);
     181           0 :       throw new IllegalStateException(t);
     182             :     }
     183             :   }
     184             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750