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

net.e6tech.elements.common.util.concurrent.Balancer Maven / Gradle / Ivy

There is a newer version: 2.7.9
Show newest version
/*
 * Copyright 2015-2021 Futeh Kao
 *
 * 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 net.e6tech.elements.common.util.concurrent;

import net.e6tech.elements.common.logging.Logger;
import net.e6tech.elements.common.reflection.Reflection;
import net.e6tech.elements.common.util.ExceptionMapper;
import net.e6tech.elements.common.util.SystemException;
import net.e6tech.elements.common.util.function.FunctionWithException;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
 * Created by futeh.
 */
public class Balancer {
    private static Logger logger= Logger.getLogger();
    private List services = new ArrayList<>();
    private BlockingQueue liveList = new LinkedBlockingQueue<>();
    private ConcurrentLinkedQueue processingList = new ConcurrentLinkedQueue<>();
    private BlockingQueue  deadList = new LinkedBlockingQueue<>();
    private long timeout = 3000L;
    private long recoveryPeriod = 60000L;
    private Thread recoveryThread;
    private volatile boolean stopped = false;
    private boolean threadSafe = false;
    private ServiceHandler starter;
    private ServiceHandler stopper;

    @SuppressWarnings({"unchecked"})
    public T getService() {
        Class cls = Reflection.getParametrizedType(getClass(), 0);
        return (T) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { cls },
                (proxy,  method, args)-> execute(service -> method.invoke(service, args)));
    }

    public long getTimeout() {
        return timeout;
    }

    public void setTimeout(long timeout) {
        this.timeout = timeout;
    }

    public Balancer timeout(long timeout) {
        setTimeout(timeout);
        return this;
    }

    public long getRecoveryPeriod() {
        return recoveryPeriod;
    }

    public void setRecoveryPeriod(long recoveryPeriod) {
        this.recoveryPeriod = recoveryPeriod;
    }

    public Balancer recoveryPeriod(long recoveryPeriod) {
        setRecoveryPeriod(recoveryPeriod);
        return this;
    }

    public boolean isThreadSafe() {
        return threadSafe;
    }

    public void setThreadSafe(boolean threadSafe) {
        this.threadSafe = threadSafe;
    }

    public Balancer threadSafe(boolean threadSafe) {
        setThreadSafe(threadSafe);
        return this;
    }

    public void addService(T service) {
        liveList.add(service);
        services.add(service);
    }

    public void forEach(Consumer consumer) {
        services.forEach(consumer);
    }

    public Balancer timeout(T service) {
        addService(service);
        return this;
    }

    public int getAvailable() {
        return liveList.size();
    }

    public ServiceHandler getStarter() {
        return starter;
    }

    public void setStarter(ServiceHandler starter) {
        this.starter = starter;
    }

    public Balancer starter(ServiceHandler starter) {
        setStarter(starter);
        return this;
    }

    public ServiceHandler getStopper() {
        return stopper;
    }

    public void setStopper(ServiceHandler stopper) {
        this.stopper = stopper;
    }

    public Balancer stopper(ServiceHandler stopper) {
        setStopper(stopper);
        return this;
    }

    public void start() {
        Iterator iterator = liveList.iterator();
        stopped = false;
        while (iterator.hasNext()) {
            T service = iterator.next();
            try {
               start(service);
            } catch (Exception th) {
                logger.warn("Cannot start service " + service.getClass(), th);
                iterator.remove();
                recover(service);
            }
        }
    }

    public void stop() {
        stopped = true;
    }

    protected void start(T service) throws IOException {
        if (starter != null)
            starter.handle(service);
        if (service instanceof BalancerAware)
            ((BalancerAware) service).start();
    }

    protected void stop(T service) throws IOException {
        if (stopper != null)
            stopper.handle(service);
        if (service instanceof BalancerAware)
            ((BalancerAware) service).stop();
    }

    @SuppressWarnings("squid:S899")
    private void recoverTask() {
        try {
            recovering();
        } finally {
            synchronized (this) {
                recoveryThread = null;
            }
        }
    }

    @SuppressWarnings("squid:S899")
    private void recovering() {
        while (!stopped) {
            T service = null;
            try {
                service = deadList.take();
                start(service);
                liveList.offer(service);
            } catch (Exception ex) {
                if (service != null) {
                    logger.warn("Cannot restart service " + service.getClass(), ex);
                    try {
                        stop(service);
                    } catch (Exception e) {
                        logger.warn("Cannot restart service " + service.getClass(), ex);
                    }
                    deadList.offer(service);
                }
            }

            try {
                Thread.sleep(recoveryPeriod);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }

    @SuppressWarnings("squid:S899")
    protected synchronized void recover(T service) {
        try {
            stop(service);
        } catch (Exception e) {
            Logger.suppress(e);
        }
        deadList.offer(service);
        if (recoveryThread == null) {
            recoveryThread = new Thread(this::recoverTask);
            recoveryThread.start();
        }
    }

    @SuppressWarnings({"squid:S3776", "squid:S899", "squid:S1193"})
    public  R execute(FunctionWithException submit) throws IOException {
        while (true) {  // the while loop is for in case of IOException
            T service;
            boolean owner = false;
            try {
                service = liveList.poll(timeout, TimeUnit.MILLISECONDS);
                if (service != null) {
                    processingList.offer(service);
                    owner = true;
                }
            } catch (InterruptedException e) {
                if (owner) {
                    processingList.poll();
                }
                Thread.currentThread().interrupt();
                throw new IOException();
            }

            if (service == null && threadSafe) {
                service = processingList.peek();
            }

            if (service == null)
                throw new IOException("No service available");

            SystemException error = null;
            try {
                R ret = submit.apply(service);
                if (owner) {
                    liveList.offer(service);
                }
                return ret;
            } catch (Exception ex) {
                if (shouldRecover(ex)) {
                    if (owner) {
                        processingList.poll();
                        recover(service);
                    }
                } else {
                    if (owner) {
                        liveList.offer(service);
                    }
                    if (ex instanceof SystemException) {
                        error = (SystemException) ex;
                    } else if (ex instanceof InvocationTargetException) {
                        error = new SystemException(ex.getCause());
                    } else if (ex instanceof RuntimeException) {
                        error = new SystemException(ex.getCause());
                    } else {
                        error = new SystemException(ex);
                    }
                }
            }

            if (error != null)
                throw error;
        }
    }

    protected boolean shouldRecover(Exception exception) {
        Throwable throwable = ExceptionMapper.unwrap(exception);
        return throwable instanceof IOException;
    }

    // provides a way for an external system to start or to stop a service.
    public interface ServiceHandler {
        void handle(T t) throws IOException;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy