/* DefaultFailureInterpreter.java * * Copyright 2009-2012 Comcast Interactive Media, LLC. * * 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 * * http://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.fishwife.jrugged; import java.util.Arrays; import java.util.HashSet; import java.util.Set; /** * Trips a {@link CircuitBreaker} if the number of failures in a given time window exceed * a specified tolerance. By default, all {@link Throwable} occurrences will be considered * failures. */ public final class DefaultFailureInterpreter implements FailureInterpreter { private Set<Class<? extends Throwable>> ignore = new HashSet<>(); private int limit = 0; private long windowMillis = 0; private WindowedEventCounter counter; @SuppressWarnings("unchecked") private static Class<? extends Throwable>[] defaultIgnore = new Class[0]; /** * Default constructor. Any {@link Throwable} will cause the breaker to trip. */ public DefaultFailureInterpreter() { setIgnore(defaultIgnore); } /** * Constructor that allows a tolerance for a certain number of failures within a given * window of time without tripping. * * @param limit * the number of failures that will be tolerated (i.e. the number of * failures has to be strictly <em>greater than</em> this number in order * than</em> this number in order to trip the breaker). For example, if * during the window will cause the breaker to trip. * @param windowMillis * length of the window in milliseconds */ public DefaultFailureInterpreter(final int limit, final long windowMillis) { setIgnore(defaultIgnore); setLimit(limit); setWindowMillis(windowMillis); initCounter(); } /** * Constructor where we specify certain {@link Throwable} classes that will be ignored * by the breaker and not be treated as failures (they will be passed through * transparently without causing the breaker to trip). * * @param ignore * an array of {@link Throwable} classes that will be ignored. Any given * <code>Throwable</code> that is a subclass of one of these classes will * be ignored. */ public DefaultFailureInterpreter(final Class<? extends Throwable>[] ignore) { setIgnore(ignore); } /** * Constructor where we specify tolerance and a set of ignored failures. * * @param ignore * an array of {@link Throwable} classes that will be ignored. Any given * <code>Throwable</code> that is a subclass of one of these classes will * be ignored. * @param limit * the number of failures that will be tolerated (i.e. the number of * failures has to be strictly <em>greater than</em> this number in order * than</em> this number in order to trip the breaker). For example, if * during the window will cause the breaker to trip. * @param windowMillis * length of the window in milliseconds */ public DefaultFailureInterpreter(final Class<? extends Throwable>[] ignore, final int limit, final long windowMillis) { setIgnore(ignore); setLimit(limit); setWindowMillis(windowMillis); initCounter(); } private boolean hasWindowConditions() { return limit > 0 && windowMillis > 0; } @Override public boolean shouldTrip(final Throwable cause) { for (final Class<?> clazz : ignore) { if (clazz.isInstance(cause)) { return false; } } // if Exception is of specified type, and window conditions exist, // keep circuit open unless exception threshold has passed if (hasWindowConditions()) { counter.mark(); // Trip if the exception count has passed the limit return counter.tally() > limit; } return true; } private void initCounter() { if (hasWindowConditions()) { final int capacity = limit + 1; if (counter == null) { counter = new WindowedEventCounter(capacity, windowMillis); } else { if (capacity != counter.getCapacity()) { counter.setCapacity(capacity); } if (windowMillis != counter.getWindowMillis()) { counter.setWindowMillis(windowMillis); } } } else { // we're not under windowConditions, no counter needed counter = null; } } /** * Returns the set of currently ignored {@link Throwable} classes. * * @return {@link Set} */ public Set<Class<? extends Throwable>> getIgnore() { return ignore; } /** * Specifies an array of {@link Throwable} classes to ignore. These will not be * considered failures. * * @param ignore * array of {@link Class} objects */ public synchronized void setIgnore(final Class<? extends Throwable>[] ignore) { this.ignore = new HashSet<>(Arrays.asList(ignore)); } /** * Returns the current number of failures within the window that will be tolerated * without tripping the breaker. * * @return int */ public int getLimit() { return limit; } /** * Specifies the number of tolerated failures within the configured time window. If * limit is set to <em>n</em> then the <em>(n+1)</em>th failure will trip the breaker. * Mutating the limit at runtime can reset previous failure counts. * * @param limit * <code>int</code> */ public void setLimit(final int limit) { this.limit = limit; initCounter(); } /** * Returns the length of the currently configured tolerance window in milliseconds. * * @return <code>long</code> */ public long getWindowMillis() { return windowMillis; } /** * Specifies the length of the tolerance window in milliseconds. * * @param windowMillis * <code>long</code> */ public void setWindowMillis(final long windowMillis) { this.windowMillis = windowMillis; initCounter(); } }