001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.oozie.util.db; 020 021import java.util.concurrent.Callable; 022 023import com.google.common.annotations.VisibleForTesting; 024import org.apache.oozie.util.XLog; 025 026import com.google.common.base.Preconditions; 027import com.google.common.base.Predicate; 028 029public class OperationRetryHandler { 030 private static XLog LOG = XLog.getLog(OperationRetryHandler.class); 031 @VisibleForTesting 032 static final RetryAttemptState RETRY_ATTEMPT_STATE = new RetryAttemptState(); 033 034 private final int maxRetryCount; 035 private final long initialWaitTime; 036 private final long maxWaitTime; 037 private final Predicate<Throwable> retryPredicate; 038 private final boolean shouldRetry; 039 040 public OperationRetryHandler(final int maxRetryCount, final long initialWaitTime, final long maxWaitTime, 041 final Predicate<Throwable> retryPredicate) { 042 Preconditions.checkArgument(maxRetryCount >= 0, "Retry count must not be less than zero"); 043 Preconditions.checkArgument(initialWaitTime > 0, "Initial wait time must be greater than zero"); 044 Preconditions.checkArgument(maxWaitTime >= 0, "Maximum wait time must not be less than zero"); 045 046 this.maxRetryCount = maxRetryCount; 047 this.initialWaitTime = initialWaitTime; 048 this.maxWaitTime = maxWaitTime; 049 this.retryPredicate = Preconditions.checkNotNull(retryPredicate, "Retry predicate must not be null"); 050 this.shouldRetry = !(maxRetryCount == 0 || maxWaitTime == 0); 051 052 LOG.trace("Retry handler parameters are set." + 053 "[maxRetryCount={0};initialWaitTime={1};maxWaitTime={2};retryPredicate.class={3};shouldRetry={4}]", 054 this.maxRetryCount, this.initialWaitTime, this.maxWaitTime, this.retryPredicate.getClass().getName(), shouldRetry); 055 } 056 057 public <V> V executeWithRetry(final Callable<V> operation) throws Exception { 058 int retries = 0; 059 long waitTime = initialWaitTime; 060 Exception lastException = null; 061 062 if (!shouldRetry) { 063 try { 064 LOG.trace("Configured not to retry, calling operation once."); 065 066 final V result = operation.call(); 067 068 LOG.trace("Operation called once successfully."); 069 070 return result; 071 } 072 catch (final Exception e) { 073 LOG.error("An error occurred while calling the operation once. [e.message={0}]", e.getMessage()); 074 throw e; 075 } 076 } 077 078 try { 079 RETRY_ATTEMPT_STATE.signalStart(); 080 081 while (retries < maxRetryCount) { 082 try { 083 LOG.trace("Calling operation. [retries={0}]", retries); 084 085 retries++; 086 final V result = operation.call(); 087 088 LOG.trace("Operation called successfully."); 089 090 return result; 091 } catch (final Exception e) { 092 LOG.warn("Database error", e); 093 094 // if retries have been done by an inner retry handler, 095 // then we won't make any effort to do it again 096 if (RETRY_ATTEMPT_STATE.isExhausted()) { 097 LOG.error("Retry attempts have been exhausted. [e.message={0}]", e.getMessage()); 098 throw e; 099 } 100 101 if (retryPredicate.apply(e)) { 102 LOG.trace("Exception is not on blacklist, handling retry. [retries={0};e.class={1}]", 103 retries, e.getClass().getName()); 104 waitTime = handleRetry(waitTime, retries); 105 lastException = e; 106 } 107 else { 108 LOG.warn("Exception is on blacklist, not handling retry. [retries={0};e.class={1}]", 109 retries, e.getClass().getName()); 110 throw e; 111 } 112 } 113 } 114 115 LOG.error("Number of maximum retry attempts exhausted"); 116 RETRY_ATTEMPT_STATE.signalExhausted(); // signal to possible outer retry handlers 117 throw lastException; 118 } finally { 119 RETRY_ATTEMPT_STATE.signalEnd(); 120 } 121 } 122 123 private long handleRetry(long sleepBeforeRetryMs, final int retries) throws InterruptedException { 124 LOG.warn("Operation failed, sleeping {0} milliseconds before retry #{1}", sleepBeforeRetryMs, retries); 125 Thread.sleep(sleepBeforeRetryMs); 126 sleepBeforeRetryMs *= 2; 127 128 return sleepBeforeRetryMs > maxWaitTime ? maxWaitTime : sleepBeforeRetryMs; 129 } 130}