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 : }