org.springframework.retry.policy.CircuitBreakerRetryPolicy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spring-retry Show documentation
Show all versions of spring-retry Show documentation
Spring Retry provides an abstraction around retrying failed operations, with an
emphasis on declarative control of the process and policy-based behaviour that is
easy to extend and customize. For instance, you can configure a plain POJO
operation to retry if it fails, based on the type of exception, and with a fixed
or exponential backoff.
/*
* Copyright 2006-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.retry.policy;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryPolicy;
import org.springframework.retry.context.RetryContextSupport;
/**
* @author Dave Syer
*
*/
@SuppressWarnings("serial")
public class CircuitBreakerRetryPolicy implements RetryPolicy {
public static final String CIRCUIT_OPEN = "circuit.open";
public static final String CIRCUIT_SHORT_COUNT = "circuit.shortCount";
private static final Log logger = LogFactory.getLog(CircuitBreakerRetryPolicy.class);
private final RetryPolicy delegate;
private long resetTimeout = 20000;
private long openTimeout = 5000;
private Supplier resetTimeoutSupplier;
private Supplier openTimeoutSupplier;
public CircuitBreakerRetryPolicy() {
this(new SimpleRetryPolicy());
}
public CircuitBreakerRetryPolicy(RetryPolicy delegate) {
this.delegate = delegate;
}
/**
* Timeout for resetting circuit in milliseconds. After the circuit opens it will
* re-close after this time has elapsed and the context will be restarted.
* @param timeout the timeout to set in milliseconds
*/
public void setResetTimeout(long timeout) {
this.resetTimeout = timeout;
}
/**
* A supplier for the timeout for resetting circuit in milliseconds. After the circuit
* opens it will re-close after this time has elapsed and the context will be
* restarted.
* @param timeoutSupplier a supplier for the timeout to set in milliseconds
* @since 2.0
*/
public void resetTimeoutSupplier(Supplier timeoutSupplier) {
this.resetTimeoutSupplier = timeoutSupplier;
}
/**
* Timeout for tripping the open circuit. If the delegate policy cannot retry and the
* time elapsed since the context was started is less than this window, then the
* circuit is opened.
* @param timeout the timeout to set in milliseconds
*/
public void setOpenTimeout(long timeout) {
this.openTimeout = timeout;
}
/**
* A supplier for the Timeout for tripping the open circuit. If the delegate policy
* cannot retry and the time elapsed since the context was started is less than this
* window, then the circuit is opened.
* @param timeoutSupplier a supplier for the timeout to set in milliseconds
* @since 2.0
*/
public void openTimeoutSupplier(Supplier timeoutSupplier) {
this.openTimeoutSupplier = timeoutSupplier;
}
@Override
public boolean canRetry(RetryContext context) {
CircuitBreakerRetryContext circuit = (CircuitBreakerRetryContext) context;
if (circuit.isOpen()) {
circuit.incrementShortCircuitCount();
return false;
}
else {
circuit.reset();
}
return this.delegate.canRetry(circuit.context);
}
@Override
public RetryContext open(RetryContext parent) {
long resetTimeout = this.resetTimeout;
if (this.resetTimeoutSupplier != null) {
resetTimeout = this.resetTimeoutSupplier.get();
}
long openTimeout = this.openTimeout;
if (this.openTimeoutSupplier != null) {
openTimeout = this.openTimeoutSupplier.get();
}
return new CircuitBreakerRetryContext(parent, this.delegate, resetTimeout, openTimeout);
}
@Override
public void close(RetryContext context) {
CircuitBreakerRetryContext circuit = (CircuitBreakerRetryContext) context;
this.delegate.close(circuit.context);
}
@Override
public void registerThrowable(RetryContext context, Throwable throwable) {
CircuitBreakerRetryContext circuit = (CircuitBreakerRetryContext) context;
circuit.registerThrowable(throwable);
this.delegate.registerThrowable(circuit.context, throwable);
}
static class CircuitBreakerRetryContext extends RetryContextSupport {
private volatile RetryContext context;
private final RetryPolicy policy;
private volatile long start = System.currentTimeMillis();
private final long timeout;
private final long openWindow;
private final AtomicInteger shortCircuitCount = new AtomicInteger();
public CircuitBreakerRetryContext(RetryContext parent, RetryPolicy policy, long timeout, long openWindow) {
super(parent);
this.policy = policy;
this.timeout = timeout;
this.openWindow = openWindow;
this.context = createDelegateContext(policy, parent);
setAttribute("state.global", true);
}
public void reset() {
shortCircuitCount.set(0);
setAttribute(CIRCUIT_SHORT_COUNT, shortCircuitCount.get());
}
public void incrementShortCircuitCount() {
shortCircuitCount.incrementAndGet();
setAttribute(CIRCUIT_SHORT_COUNT, shortCircuitCount.get());
}
private RetryContext createDelegateContext(RetryPolicy policy, RetryContext parent) {
RetryContext context = policy.open(parent);
reset();
return context;
}
public boolean isOpen() {
long time = System.currentTimeMillis() - this.start;
boolean retryable = this.policy.canRetry(this.context);
if (!retryable) {
if (time > this.timeout) {
logger.trace("Closing");
this.context = createDelegateContext(policy, getParent());
this.start = System.currentTimeMillis();
retryable = this.policy.canRetry(this.context);
}
else if (time < this.openWindow) {
if (!hasAttribute(CIRCUIT_OPEN) || (Boolean) getAttribute(CIRCUIT_OPEN) == false) {
logger.trace("Opening circuit");
setAttribute(CIRCUIT_OPEN, true);
this.start = System.currentTimeMillis();
}
return true;
}
}
else {
if (time > this.openWindow) {
logger.trace("Resetting context");
this.start = System.currentTimeMillis();
this.context = createDelegateContext(policy, getParent());
}
}
if (logger.isTraceEnabled()) {
logger.trace("Open: " + !retryable);
}
setAttribute(CIRCUIT_OPEN, !retryable);
return !retryable;
}
@Override
public int getRetryCount() {
return this.context.getRetryCount();
}
@Override
public String toString() {
return this.context.toString();
}
}
}