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

io.inugami.commons.threads.RunAndCloseService Maven / Gradle / Ivy

There is a newer version: 3.3.5
Show newest version
/* --------------------------------------------------------------------
 *  Inugami
 * --------------------------------------------------------------------
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see .
 */
package io.inugami.commons.threads;

import io.inugami.api.exceptions.Asserts;
import io.inugami.api.loggers.Loggers;
import io.inugami.api.models.tools.Chrono;
import io.inugami.api.monitoring.MonitoringInitializer;
import io.inugami.api.monitoring.RequestContext;
import io.inugami.api.monitoring.RequestInformation;
import io.inugami.api.spi.SpiLoader;
import lombok.Builder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;

/**
 * ThreadsExecutor
 *
 * @author patrickguillerm
 * @since 24 mars 2018
 */
@SuppressWarnings({"java:S3014", "java:S2142", "java:S1874"})
public class RunAndCloseService implements ThreadFactory {

    // =========================================================================
    // ATTRIBUTES
    // =========================================================================
    private static final Logger LOGGER = LoggerFactory.getLogger(RunAndCloseService.class);

    private static final List monitoringInitializer = initMonitoringInitializers();

    private final String threadsName;

    private final List> tasks;

    private final Map, Callable> tasksAndFutures;

    private final long timeout;

    private final BiFunction, T> onError;

    private final ExecutorService executor;

    private final CompletionService completion;

    private final ThreadGroup threadGroup;

    private final AtomicInteger threadIndex = new AtomicInteger();

    private final List data = new ArrayList<>();

    private final RequestInformation requestContext;

    // =========================================================================
    // CONSTRUCTORS
    // =========================================================================
    private static List initMonitoringInitializers() {
        final List spiServices = SpiLoader.getInstance()
                                                                 .loadSpiService(MonitoringInitializer.class);
        return spiServices == null ? Collections.emptyList() : spiServices;
    }

    @SafeVarargs
    public RunAndCloseService(final String threadsName, final long timeout, final int nbThreads,
                              final BiFunction, T> onError, final Callable... tasks) {
        this(threadsName, timeout, nbThreads, Arrays.asList(tasks), onError);
    }

    @SafeVarargs
    public RunAndCloseService(final String threadsName, final long timeout, final int nbThreads,
                              final Callable... tasks) {
        this(threadsName, timeout, nbThreads, Arrays.asList(tasks), null);
    }

    public RunAndCloseService(final String threadsName, final long timeout, final int nbThreads,
                              final List> tasks) {
        this(threadsName, timeout, nbThreads, tasks, null);
    }

    public RunAndCloseService(final String threadsName, final long timeout, final int nbThreads,
                              final List> tasks, final BiFunction, T> onError) {
        this(threadsName, timeout, nbThreads, null, tasks, onError);
    }

    @Builder
    public RunAndCloseService(final String threadsName,
                              final long timeout,
                              final int nbThreads,
                              final ExecutorService executor,
                              final List> tasks,
                              final BiFunction, T> onError) {
        super();
        Asserts.assertNotNull(tasks);

        this.tasks = tasks;

        ExecutorService currentExecutor = executor;
        if (executor == null) {
            int howManyThreads = tasks.size() < nbThreads ? tasks.size() : nbThreads;
            if (howManyThreads <= 0) {
                howManyThreads = 1;
            }

            currentExecutor = Executors.newFixedThreadPool(howManyThreads, this);
        }
        this.executor = currentExecutor;
        this.threadsName = threadsName;
        this.timeout = timeout;
        this.onError = onError;
        tasksAndFutures = new HashMap<>();
        threadGroup = Thread.currentThread().getThreadGroup();

        completion = new ExecutorCompletionService<>(currentExecutor);
        this.requestContext = RequestContext.getInstance();

    }


    // =========================================================================
    // METHODS
    // =========================================================================
    public List run() {
        final List> futures   = sumitTask();
        int                   tasksLeft = futures.size();
        long                  timeLeft  = timeout;
        final Chrono          chrono    = Chrono.startChrono();

        while ((tasksLeft > 0) && (chrono.snapshot().getDuration() < timeout)) {
            timeLeft = computeTimeLeft(timeLeft, chrono);
            Future itemFuture = null;
            T         taskData   = null;
            try {
                itemFuture = completion.poll(timeLeft, TimeUnit.MILLISECONDS);
                if (itemFuture != null) {
                    taskData = itemFuture.get();
                    tasksLeft = tasksLeft - 1;
                }
            } catch (final ExecutionException | InterruptedException error) {
                tasksLeft = tasksLeft - 1;
            }

            if (taskData != null) {
                data.add(taskData);
            }
        }
        executor.shutdown();

        data.addAll(handlerTimeoutTask());
        return data;
    }

    private long computeTimeLeft(final long timeLeft, final Chrono chrono) {
        final long result = timeLeft - chrono.snapshot().getDuration();
        return result < 0 ? 0 : result;
    }

    // =========================================================================
    // PRIVATE
    // =========================================================================
    private List> sumitTask() {
        final List> result = new ArrayList<>();
        for (final Callable task : tasks) {
            final Future future = completion.submit(new CallableTask<>(task, this));
            result.add(future);
            tasksAndFutures.put(future, task);
        }
        return result;
    }

    private class CallableTask implements Callable {
        private final Callable task;

        private final RunAndCloseService runAndCloseService;

        public CallableTask(final Callable task, final RunAndCloseService runAndCloseService) {
            this.task = task;
            this.runAndCloseService = runAndCloseService;
        }

        @Override
        public U call() throws Exception {
            U result = null;
            try {
                result = task.call();
            } catch (final Exception e) {
                LOGGER.error(e.getMessage(), e);
                result = runAndCloseService.processHandlerError(e, task);
            }

            return result;
        }

    }

    // =========================================================================
    // new Thread
    // =========================================================================
    @Override
    public Thread newThread(final Runnable runnable) {
        final String name = String.join(".", threadsName, String.valueOf(threadIndex.getAndIncrement()));
        final Thread result = new MonitoredThread(threadGroup, runnable, name, 10, requestContext,
                                                  monitoringInitializer);
        result.setDaemon(false);
        return result;
    }

    // =========================================================================
    // ERRORS
    // =========================================================================
    private List handlerTimeoutTask() {
        final List result = new ArrayList<>();
        for (final Map.Entry, Callable> entry : tasksAndFutures.entrySet()) {
            if (!entry.getKey().isDone()) {
                final Callable task     = entry.getValue();
                final T           taskData = processHandlerError(null, task);
                if (taskData != null) {
                    result.add(taskData);
                }
            }
        }
        return result;
    }

    private synchronized T processHandlerError(final Exception error, final Callable task) {
        T result = null;
        if (onError == null) {
            result = handlerError(error, task);
        } else {
            result = onError.apply(error, task);
        }
        return result;
    }

    private T handlerError(final Exception error, final Callable task) {
        T result = null;
        if (task instanceof CallableWithErrorResult) {
            if (error == null) {
                result = ((CallableWithErrorResult) task).getTimeoutResult();
            } else {
                result = ((CallableWithErrorResult) task).getErrorResult(error);
            }
        }

        return result;
    }

    public void forceShutdown() {
        if (!executor.isShutdown()) {
            if (!executor.isTerminated()) {
                try {
                    executor.awaitTermination(0, TimeUnit.MILLISECONDS);
                } catch (final InterruptedException e) {
                    Loggers.DEBUG.error(e.getMessage(), e);
                }
            }
            executor.shutdown();
        }
    }
}