1 package net.secodo.jcircuitbreaker.breakstrategy.impl; 2 3 import java.util.Collection; 4 import java.util.Collections; 5 import java.util.List; 6 import java.util.stream.Collectors; 7 8 import net.secodo.jcircuitbreaker.breaker.execution.ExecutedTask; 9 import net.secodo.jcircuitbreaker.breaker.execution.ExecutionContext; 10 import net.secodo.jcircuitbreaker.breakstrategy.BreakStrategy; 11 import net.secodo.jcircuitbreaker.task.Task; 12 import net.secodo.jcircuitbreaker.util.TimeUtil; 13 14 15 /** 16 * An implementation of {@link BreakStrategy} which causes the circuit breaker to break (not execute the Task) in 17 * case all tasks currently running inside the circuit breaker takes long time on average. 18 * 19 * <p>In constructor you define (in milliseconds) how long on average you expect the execution of single Task to take. 20 * If on average all currently running tasks take longer than this strategy will decide to break (not to execute 21 * another Task). 22 * 23 * <p>You may additionally define {@link #percentageOfMaxTimesToSkip} which is how many percent of the executions with 24 * highest execution time (longest executions) should not be considered when taking calculating the average. 25 * 26 * <p>For example this strategy: 27 * 28 * <p>new LimitedCurrentAverageExecutionTimeStrategy(1000, 10); 29 * 30 * <p>breaks (not allows execution of target-method) in case 90% of other executions (currently in 31 * progress) takes more than one is 1 second (1000 ms). Example figures: there are 20 executions (threads) 32 * currently going through circuit breaker. 2 are running for 6 seconds, 18 are running for 900 milliseconds. We skip 33 * 10 % of longest executions - this is 2 executions (10% * 20 = 2) and we calculate average from the remaining 18 34 * executions. The average is 900 ms. This is less than allowed maximum = 1000 ms therefore next execution will be 35 * allowed. 36 * 37 * <p>NOTE: in above example, if the average was above 1000ms than this strategy would return false to circuit breaker 38 * and circuit breaker would execute {@link net.secodo.jcircuitbreaker.breakhandler.BreakHandler} to handle this 39 * situation 40 * 41 * @param <R> the return type of <i>real-method</i> that is executed by circuit breaker 42 */ 43 public class LimitedCurrentAverageExecutionTimeStrategy<R> implements BreakStrategy<R> { 44 private final TimeUtil timeUtil; 45 46 /** 47 * Protected access to allow extending classes to dynamically change it, 48 * e.g. via MBean 49 */ 50 protected float maxAllowedExecutionTimeMillis; 51 52 /** 53 * Protected access to allow extending classes to dynamically change it, 54 * e.g. via MBean 55 */ 56 protected float percentageOfMaxTimesToSkip; 57 58 /** 59 * Constructs new strategy with given maximum allowed execution. This is the execution of the threads which are 60 * currently going through the circuit breaker. 61 * 62 * @param maxAllowedAverageExecutionTimeMillis max time which does not result in "break" (which allows execution of 63 * target-method) 64 * @param percentageOfLongestTimesToSkip how many percent of longest times should not be considered when 65 * calculating the average 66 */ 67 public LimitedCurrentAverageExecutionTimeStrategy(long maxAllowedAverageExecutionTimeMillis, 68 int percentageOfLongestTimesToSkip) { 69 this.maxAllowedExecutionTimeMillis = maxAllowedAverageExecutionTimeMillis; 70 timeUtil = new TimeUtil(); 71 72 this.percentageOfMaxTimesToSkip = ((percentageOfLongestTimesToSkip >= 0) && (percentageOfLongestTimesToSkip <= 100)) 73 ? (0.01f * (float) percentageOfLongestTimesToSkip) : 0; 74 } 75 76 77 /** 78 * Constructs new strategy with given maximum allowed execution. This is the execution of the threads which are 79 * currently going through the circuit breaker. 80 * 81 * @param maxAllowedAverageExecutionTimeMillis max time which does not result in "break" (which allows execution of 82 */ 83 public LimitedCurrentAverageExecutionTimeStrategy(long maxAllowedAverageExecutionTimeMillis) { 84 this(maxAllowedAverageExecutionTimeMillis, 0); 85 } 86 87 @Override 88 public boolean shouldBreak(Task<R> task, ExecutionContext<R> executionContext) { 89 final Collection<ExecutedTask<R>> executionsInProgress = executionContext.getExecutionsInProgress(); 90 91 if (executionsInProgress.isEmpty()) { 92 return false; 93 } 94 95 final List<Long> startTimestamps = executionsInProgress.stream() 96 .map(ExecutedTask::getExecutionStartedTimestamp) 97 .collect(Collectors.toList()); 98 99 Collections.sort(startTimestamps); 100 101 final List<Long> interestingTimestamps = startTimestamps.subList(0, 102 (int) ((1f - percentageOfMaxTimesToSkip) * (float) startTimestamps.size())); 103 104 if (interestingTimestamps.isEmpty()) { 105 return false; 106 } 107 108 109 final long currentTime = timeUtil.getCurrentTimeMilis(); 110 111 final Long sumOfInterestingTimestamps = interestingTimestamps.stream().mapToLong(t -> currentTime - t).sum(); 112 113 114 final float currentAverageExecutionTime = (float) sumOfInterestingTimestamps / (float) interestingTimestamps.size(); 115 116 117 return currentAverageExecutionTime > maxAllowedExecutionTimeMillis; 118 } 119 }