001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.lang3.concurrent; 018 019import java.util.concurrent.atomic.AtomicLong; 020 021/** 022 * A simple implementation of the <a 023 * href="https://martinfowler.com/bliki/CircuitBreaker.html">Circuit Breaker</a> pattern 024 * that opens if the requested increment amount is greater than a given threshold. 025 * 026 * <p> 027 * It contains an internal counter that starts in zero, and each call increments the counter by a given amount. 028 * If the threshold is zero, the circuit breaker will be in a permanent <em>open</em> state. 029 * </p> 030 * 031 * <p> 032 * An example of use case could be a memory circuit breaker. 033 * </p> 034 * 035 * <pre> 036 * long threshold = 10L; 037 * ThresholdCircuitBreaker breaker = new ThresholdCircuitBreaker(10L); 038 * ... 039 * public void handleRequest(Request request) { 040 * long memoryUsed = estimateMemoryUsage(request); 041 * if (breaker.incrementAndCheckState(memoryUsed)) { 042 * // actually handle this request 043 * } else { 044 * // do something else, e.g. send an error code 045 * } 046 * } 047 * </pre> 048 * 049 * <p> 050 * #Thread safe# 051 * </p> 052 * 053 * @since 3.5 054 */ 055public class ThresholdCircuitBreaker extends AbstractCircuitBreaker<Long> { 056 057 /** 058 * The initial value of the internal counter. 059 */ 060 private static final long INITIAL_COUNT = 0L; 061 062 /** 063 * The threshold. 064 */ 065 private final long threshold; 066 067 /** 068 * Controls the amount used. 069 */ 070 private final AtomicLong used; 071 072 /** 073 * Creates a new instance of {@link ThresholdCircuitBreaker} and initializes the threshold. 074 * 075 * @param threshold The threshold. 076 */ 077 public ThresholdCircuitBreaker(final long threshold) { 078 this.used = new AtomicLong(INITIAL_COUNT); 079 this.threshold = threshold; 080 } 081 082 /** 083 * {@inheritDoc} 084 */ 085 @Override 086 public boolean checkState() { 087 return !isOpen(); 088 } 089 090 /** 091 * {@inheritDoc} 092 * 093 * <p> 094 * Resets the internal counter back to its initial value (zero). 095 * </p> 096 */ 097 @Override 098 public void close() { 099 super.close(); 100 this.used.set(INITIAL_COUNT); 101 } 102 103 /** 104 * Gets the threshold. 105 * 106 * @return The threshold 107 */ 108 public long getThreshold() { 109 return threshold; 110 } 111 112 /** 113 * {@inheritDoc} 114 * 115 * <p> 116 * If the threshold is zero, the circuit breaker will be in a permanent <em>open</em> state. 117 * </p> 118 * <p> 119 * The internal counter is a protective counter and only moves toward the threshold: negative 120 * increments are rejected, and an increment that would overflow {@link Long#MAX_VALUE} saturates 121 * the counter at {@link Long#MAX_VALUE} and opens the circuit breaker instead of silently wrapping 122 * negative (which would disable the trip condition). 123 * </p> 124 * 125 * @throws IllegalArgumentException Thrown if the increment is negative. 126 */ 127 @Override 128 public boolean incrementAndCheckState(final Long increment) { 129 if (threshold == 0) { 130 open(); 131 } 132 final long delta = increment.longValue(); 133 if (delta < 0) { 134 throw new IllegalArgumentException("Increment must not be negative: " + delta); 135 } 136 final long used = this.used.accumulateAndGet(delta, (current, add) -> { 137 final long next = current + add; 138 // Both operands are non-negative, so overflow shows up as a decrease: saturate. 139 return next < current ? Long.MAX_VALUE : next; 140 }); 141 if (used > threshold || used == Long.MAX_VALUE) { 142 open(); 143 } 144 return checkState(); 145 } 146 147}