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; 16 : 17 : import static java.util.Objects.requireNonNull; 18 : 19 : import com.google.auto.value.AutoValue; 20 : import com.google.common.collect.ImmutableList; 21 : import com.google.gerrit.common.Nullable; 22 : import com.google.gerrit.extensions.annotations.ExtensionPoint; 23 : import java.util.Optional; 24 : 25 : /** 26 : * Allows implementors to control how certain exceptions should be handled. 27 : * 28 : * <p>This interface is intended to be implemented for cluster setups with multiple primary nodes to 29 : * control the behavior for handling exceptions that are thrown by a lower layer that handles the 30 : * consensus and synchronization between different server nodes. E.g. if an operation fails because 31 : * consensus for a Git update could not be achieved (e.g. due to slow responding server nodes) this 32 : * interface can be used to retry the request instead of failing it immediately. 33 : */ 34 : @ExtensionPoint 35 : public interface ExceptionHook { 36 : /** 37 : * Whether an operation should be retried if it failed with the given throwable. 38 : * 39 : * <p>Only affects operations that are executed with {@link 40 : * com.google.gerrit.server.update.RetryHelper}. 41 : * 42 : * <p>Should return {@code true} only for exceptions that are caused by temporary issues where a 43 : * retry of the operation has a chance to succeed. 44 : * 45 : * <p>If {@code false} is returned the operation is still retried once to capture a trace, unless 46 : * {@link #skipRetryWithTrace(String, String, Throwable)} skips the auto-retry. 47 : * 48 : * <p>If multiple exception hooks are registered, the operation is retried if any of them returns 49 : * {@code true} from this method. 50 : * 51 : * @param throwable throwable that was thrown while executing the operation 52 : * @param actionType the type of the action for which the exception occurred 53 : * @param actionName the name of the action for which the exception occurred 54 : * @return whether the operation should be retried 55 : */ 56 : default boolean shouldRetry(String actionType, String actionName, Throwable throwable) { 57 0 : return false; 58 : } 59 : 60 : /** 61 : * Whether auto-retrying of an operation with tracing should be skipped for the given throwable. 62 : * 63 : * <p>Only affects operations that are executed with {@link 64 : * com.google.gerrit.server.update.RetryHelper}. 65 : * 66 : * <p>This method is only called for exceptions for which the operation should not be retried 67 : * ({@link #shouldRetry(String, String, Throwable)} returned {@code false}). 68 : * 69 : * <p>By default this method returns {@code false}, so that by default traces for unexpected 70 : * exceptions are captured, which allows to investigate them. 71 : * 72 : * <p>Implementors may use this method to skip retry with tracing for exceptions that occur due to 73 : * known causes that are permanent and where a trace is not needed for the investigation. For 74 : * example, if an operation fails because persisted data is corrupt, it makes no sense to retry 75 : * the operation with a trace, because the trace will not help with fixing the corrupt data. 76 : * 77 : * <p>This method is only invoked if retry with tracing is enabled on the server ({@code 78 : * retry.retryWithTraceOnFailure} in {@code gerrit.config} is set to {@code true}). 79 : * 80 : * <p>If multiple exception hooks are registered, retrying with tracing is skipped if any of them 81 : * returns {@code true} from this method. 82 : * 83 : * @param throwable throwable that was thrown while executing the operation 84 : * @param actionType the type of the action for which the exception occurred 85 : * @param actionName the name of the action for which the exception occurred 86 : * @return whether auto-retrying of an operation with tracing should be skipped for the given 87 : * throwable 88 : */ 89 : default boolean skipRetryWithTrace(String actionType, String actionName, Throwable throwable) { 90 0 : return false; 91 : } 92 : 93 : /** 94 : * Formats the cause of an exception for use in metrics. 95 : * 96 : * <p>This method allows implementors to group exceptions that have the same cause into one metric 97 : * bucket. 98 : * 99 : * <p>If multiple exception hooks return a value from this method, the value from the exception 100 : * hook that is registered first is used. 101 : * 102 : * @param throwable the exception cause 103 : * @return formatted cause or {@link Optional#empty()} if no formatting was done 104 : */ 105 : default Optional<String> formatCause(Throwable throwable) { 106 0 : return Optional.empty(); 107 : } 108 : 109 : /** 110 : * Returns messages that should be returned to the user. 111 : * 112 : * <p>These messages are included into the HTTP response that is sent to the user. 113 : * 114 : * <p>If multiple exception hooks return a value from this method, all the values are included 115 : * into the HTTP response (in the order in which the exception hooks are registered). 116 : * 117 : * @param throwable throwable that was thrown while executing an operation 118 : * @param traceId ID of the trace if this request was traced, otherwise {@code null} 119 : * @return error messages that should be returned to the user, {@link Optional#empty()} if no 120 : * message should be returned to the user 121 : */ 122 : default ImmutableList<String> getUserMessages(Throwable throwable, @Nullable String traceId) { 123 0 : return ImmutableList.of(); 124 : } 125 : 126 : /** 127 : * Returns the HTTP status that should be returned to the user. 128 : * 129 : * <p>Implementors may use this method to change the status for certain exceptions (e.g. using 130 : * this method it would be possible to return {@code 503 Lock failure} for {@link 131 : * com.google.gerrit.git.LockFailureException}s instead of {@code 500 Internal server error}). 132 : * 133 : * <p>If no value is returned ({@link Optional#empty()}) it means that this exception hook doesn't 134 : * want to change the default response code for the given exception which is {@code 500 Internal 135 : * Server Error}, but is fine if other exception hook implementation do so. 136 : * 137 : * <p>If multiple exception hooks return a value from this method, the value from exception hook 138 : * that is registered first is used. 139 : * 140 : * <p>{@link #getUserMessages(Throwable, String)} allows to define which additional messages 141 : * should be included into the body of the HTTP response. 142 : * 143 : * @param throwable throwable that was thrown while executing an operation 144 : * @return HTTP status that should be returned to the user, {@link Optional#empty()} if the 145 : * exception should result in {@code 500 Internal Server Error} 146 : */ 147 : default Optional<Status> getStatus(Throwable throwable) { 148 0 : return Optional.empty(); 149 : } 150 : 151 : @AutoValue 152 1 : public abstract class Status { 153 : public abstract int statusCode(); 154 : 155 : public abstract String statusMessage(); 156 : 157 : public static Status create(int statusCode, String statusMessage) { 158 1 : return new AutoValue_ExceptionHook_Status( 159 1 : statusCode, requireNonNull(statusMessage, "statusMessage")); 160 : } 161 : } 162 : }