All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ajjpj.afoundation.concurrent.ACircuitBreaker Maven / Gradle / Ivy

The newest version!
package com.ajjpj.afoundation.concurrent;

import com.ajjpj.afoundation.collection.ACollectionHelper;
import com.ajjpj.afoundation.function.AFunction1;
import com.ajjpj.afoundation.function.AFunction1NoThrow;
import com.ajjpj.afoundation.function.AStatement2NoThrow;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;


/**
 * @author arno
 */
public class ACircuitBreaker implements ATaskScheduler {
    private final ATaskScheduler threadPool;

    private final AtomicLong retryAt = new AtomicLong ();
    private final AtomicInteger numFailures = new AtomicInteger ();

    private final int maxNumFailures;
    private final long recoveryMillis;

    @SuppressWarnings ("unused")
    public ACircuitBreaker (ATaskScheduler threadPool) {
        this (threadPool, 3, 1, TimeUnit.MINUTES);
    }

    public ACircuitBreaker (ATaskScheduler threadPool, int maxNumFailures, long recoveryDelay, TimeUnit recoveryTimeUnit) {
        this.threadPool = threadPool;
        this.maxNumFailures = maxNumFailures;
        this.recoveryMillis = recoveryTimeUnit.toMillis (recoveryDelay);
    }

    @Override public  AFuture submit (Callable task, long timeout, TimeUnit timeoutUnit) {
        if (shouldSubmit ()) {
            return withCircuitBreaker (threadPool.submit (task, timeout, timeoutUnit));
        }
        return failure();
    }

    @Override public  AFuture submit (Runnable task, T result, long timeout, TimeUnit timeoutUnit) {
        if (shouldSubmit ()) {
            return withCircuitBreaker (threadPool.submit (task, result, timeout, timeoutUnit));
        }
        return failure();
    }

    @Override public  List> submitAll (List params, AFunction1 taskFunction, long timeout, TimeUnit timeoutUnit) {
        if (shouldSubmit ()) {
            final List> raw = threadPool.submitAll (params, taskFunction, timeout, timeoutUnit);
            return ACollectionHelper.map (raw, new AFunction1NoThrow, AFuture> () {
                @Override public AFuture apply (AFuture param) {
                    return withCircuitBreaker (param);
                }
            });
        }
        return failureList (params.size ());
    }

    @Override public  List> submitAllWithDefaultValue (List params, AFunction1 taskFunction, long timeout, TimeUnit timeoutUnit, R defaultValue) {
        if (shouldSubmit ()) {
            final List> raw = threadPool.submitAllWithDefaultValue (params, taskFunction, timeout, timeoutUnit, defaultValue);
            return ACollectionHelper.map (raw, new AFunction1NoThrow, AFuture> () {
                @Override public AFuture apply (AFuture param) {
                    return withCircuitBreaker (param);
                }
            });
        }
        return failureList (params.size ());
    }

    private  List> failureList (int size) {
        final List> result = new ArrayList<> ();
        for (int i=0; ifailure ());
        }
        return result;
    }

    private  AFuture failure () {
        final AFutureImpl result = AFutureImpl.unscheduled (threadPool);
        result.setException (new RejectedByCircuitBreakerException ("recovering from previous failures (CircuitBreaker)"));
        return result;
    }

    private boolean shouldSubmit () {
        if (numFailures.get () < maxNumFailures) {
            return true;
        }

        //store 'System.currentTimeMillis()' in a variable for speedup: we can reuse the value below
        final long now = System.currentTimeMillis ();

        // we need to remember this initial value of 'retryAt' for both the initial check and the later compareAndSet(), otherwise we would have a race condition
        final long retryAtValue = retryAt.get ();
        //noinspection SimplifiableIfStatement
        if (retryAtValue > now) {
            return false;
        }

        return retryAt.compareAndSet (retryAtValue, now + recoveryMillis);
    }

    private  AFuture withCircuitBreaker (AFuture f) {
        f.onFinished (new AStatement2NoThrow () {
            @Override public void apply (T result, Throwable th) {
                if (th != null) {
                    numFailures.incrementAndGet ();
                    retryAt.set (System.currentTimeMillis () + recoveryMillis);
                }
                else {
                    numFailures.set (0);
                }
            }
        });
        return f;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy