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

com.yahoo.maha.parrequest2.future.ParallelServiceExecutor Maven / Gradle / Ivy

There is a newer version: 6.158
Show newest version
// Copyright 2017, Yahoo Holdings Inc.
// Licensed under the terms of the Apache License 2.0. Please see LICENSE file in project root for terms.
package com.yahoo.maha.parrequest2.future;

import java.util.function.Function;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.yahoo.maha.parrequest2.CustomRejectPolicy;
import com.yahoo.maha.parrequest2.ParCallable;
import scala.util.Either;
import scala.util.Right;
import com.yahoo.maha.parrequest2.GeneralError;
import scala.Option;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;

import static com.google.common.base.Preconditions.checkNotNull;

public class ParallelServiceExecutor {

    private int threadPoolSize = 100;

    private int defaultTimeoutMillis = 10000;

    private String poolName = "pse";

    private int queueSize = 100;

    private RejectedExecutionHandler rejectedExecutionHandler = new CustomRejectPolicy();

    private static Logger logger = LoggerFactory.getLogger(ParallelServiceExecutor.class);

    private ListeningExecutorService threadService;

    public void setRejectedExecutionHandler(RejectedExecutionHandler rejectedExecutionHandler) {
        Preconditions.checkArgument(rejectedExecutionHandler != null, "rejectedExecutionHandler cannot be null");
        this.rejectedExecutionHandler = rejectedExecutionHandler;
    }

    public void setPoolName(String poolName) {
        Preconditions.checkArgument(poolName != null, "Pool name cannot be null");
        this.poolName = poolName;
    }

    public void setDefaultTimeoutMillis(int defaultTimeoutMillis) {
        this.defaultTimeoutMillis = defaultTimeoutMillis;
    }

    public void setThreadPoolSize(int threadPoolSize) {
        Preconditions.checkArgument(threadPoolSize > 1, "Pool size must be > 1");
        this.threadPoolSize = threadPoolSize;
    }

    public void setQueueSize(int queueSize) {
        Preconditions.checkArgument(queueSize > 1, "Queue size must be > 1");
        this.queueSize = queueSize;
    }

    public int getThreadPoolSize() {
        return this.threadPoolSize;
    }

    public void init() throws Exception {
        ExecutorService executorService = new ThreadPoolExecutor(this.threadPoolSize, this.threadPoolSize,
                                                                 0L, TimeUnit.MILLISECONDS,
                                                                 new LinkedBlockingQueue(queueSize),
                                                                 new ThreadFactoryBuilder()
                                                                     .setNameFormat(poolName + "-%d").build(),
                                                                 rejectedExecutionHandler
        );
        threadService = MoreExecutors.listeningDecorator(executorService);
    }

    public void destroy() throws Exception {
        threadService.shutdown();
    }

    public  List execute(List> callables) throws Exception {
        return execute(callables, defaultTimeoutMillis);
    }

    public  List execute(List> callables, int timeoutMillis) throws Exception {
        return execute(callables, timeoutMillis, true, null);
    }

    public  List execute(List> callables, Function generateDefaultValue)
        throws Exception {
        return execute(callables, defaultTimeoutMillis, false, generateDefaultValue);
    }

    public  List execute(List> callables, boolean failFast,
                               Function generateDefaultValue) throws Exception {
        return execute(callables, defaultTimeoutMillis, failFast, generateDefaultValue);
    }

    public  List execute(List> callables, int timeoutMillis, boolean failFast,
                               Function generateDefaultValue) throws Exception {
        List> futureResultList = null;
        List resultList = new ArrayList();

        try {
            if (callables != null && !callables.isEmpty()) {
                futureResultList = threadService.invokeAll(callables, timeoutMillis, TimeUnit.MILLISECONDS);
            }

            if (futureResultList != null && !futureResultList.isEmpty()) {
                for (Future future : futureResultList) {
                    try {
                        T output = future.get();
                        if (output != null) {
                            resultList.add(output);
                        } else {
                            logger.warn("The result of a future was null!");
                        }
                        logger.info("is future done: " + future.isDone() + " is cancelled: " + future.isCancelled());
                        logger.info("future get response: " + future.get());
                    } catch (CancellationException | ExecutionException | InterruptedException e) {
                        if (failFast) {
                            throw e;
                        } else {
                            logger.warn("CancellationException/Interrupted/Execution exception: ", e);
                            if (generateDefaultValue != null) {
                                resultList.add(generateDefaultValue.apply(e));
                            }
                        }
                    }
                }
            } else {
                logger.error("futureResultList is empty");
            }
            return resultList;
        } catch (Exception e) {
            logger.error("Execution exception: ", e);
            throw new Exception("UNKNOWN_SERVER_ERROR", e);
        }
    }

    @Deprecated
    public  Either produceResult(ParCallable> callable) {
        try {
            ListenableFuture> results = threadService.submit(callable);
            return results.get();
        } catch (Exception e) {
            logger.error("Parallel execution error", e);
            return GeneralError.either("produceResult", "execution failed", e);
        }
    }

    @Deprecated
    public  ListenableFuture> asyncProduceResult(
        ParCallable> callable) {
        try {
            ListenableFuture> results = threadService.submit(callable);
            return results;
        } catch (Exception e) {
            logger.error("Parallel execution error", e);
            return Futures.>immediateFuture(
                    GeneralError.either("asyncProduceResult", "execution failed", e));
        }
    }

     ListenableFuture> submitParCallable(ParCallable> callable) {
        try {
            ListenableFuture> results = threadService.submit(callable);
            return results;
        } catch (Exception e) {
            logger.error("Parallel execution error", e);
            return Futures.>immediateFuture(
                    GeneralError.either("asyncProduceResult", "execution failed", e));
        }
    }

    /**
     * Add a listener to given future which executes the given runnable, backed by internal executor
     */
    public  void addListener(ListenableFuture future, Runnable runnable) {
        future.addListener(runnable, threadService);
    }

    /**
     * Create a builder for ParRequest backed by internal executor
     */
    public  ParRequest.Builder parRequestBuilder() {
        return new ParRequest.Builder(this);
    }

    /**
     * Create a builder for ParRequest2 backed by internal executor
     */
    public  ParRequest2.Builder parRequest2Builder() {
        return new ParRequest2.Builder(this);
    }

    /**
     * Create a builder for ParRequest2 backed by internal executor
     */
    public  ParRequest2Option.Builder parRequest2OptionBuilder() {
        return new ParRequest2Option.Builder(this);
    }

    /**
     * Create a builder for ParRequest3 backed by internal executor
     */
    public  ParRequest3.Builder parRequest3Builder() {
        return new ParRequest3.Builder(this);
    }

    /**
     * Create a builder for ParRequest3Option backed by internal executor
     */
    public  ParRequest3Option.Builder parRequest3OptionBuilder() {
        return new ParRequest3Option.Builder(this);
    }


    /**
     * Create a builder for ParRequest4 backed by internal executor
     */
    public  ParRequest4.Builder parRequest4Builder() {
        return new ParRequest4.Builder(this);
    }

    /**
     * Create a builder for ParRequest5 backed by internal executor
     */
    public  ParRequest5.Builder parRequest5Builder() {
        return new ParRequest5.Builder(this);
    }

    /**
     * Create a builder for ParRequest6 backed by internal executor
     */
    public  ParRequest6.Builder parRequest6Builder() {
        return new ParRequest6.Builder(this);
    }

    public  ParRequestListOption.Builder parRequestListOptionBuilder() {
        return new ParRequestListOption.Builder(this);
    }

    public  ParRequestListEither.Builder parRequestListEitherBuilder() {
        return new ParRequestListEither.Builder(this);
    }

    /**
     * Combine list of combinable requests, meaning the result ParRequestListOption will finish when all combinable requests in the list finish
     */
    public  ParRequestListOption combineList(
            final List> requestList) {
        return combineList(requestList, false);
    }

    /**
     * Combine list of combinable requests, meaning the result ParRequestListOption will finish when all combinable requests in the list finish
     */
    public  ParRequestListOption combineList(
            final List> requestList, boolean allMustSucceed) {
        String joinedLabel = requestList.stream().map(x -> x.label).collect(Collectors.joining("-"));
        return new ParRequestListOption(joinedLabel, this, new ArrayList>(requestList), allMustSucceed);
    }

    /**
     * Combine list of combinable requests, meaning the result ParRequestListEither will finish when all combinable requests in the list finish
     */
    public  ParRequestListEither combineListEither(
            final List> requestList) {
        return combineListEither(requestList, false);
    }

    /**
     * Combine list of combinable requests, meaning the result ParRequestListOption will finish when all combinable requests in the list finish
     */
    public  ParRequestListEither combineListEither(
            final List> requestList, boolean allMustSucceed) {
        String joinedLabel = requestList.stream().map(x -> x.label).collect(Collectors.joining("-"));
        return new ParRequestListEither(joinedLabel, this, new ArrayList>(requestList), allMustSucceed);
    }

    /**
     * Combine 2 combinable requests, meaning the result ParRequest2 will finish when the 2 combinable requests finish
     */
    public  ParRequest2 combine2(
        final CombinableRequest firstRequest,
        final CombinableRequest secondRequest) {
        return new ParRequest2(firstRequest.label + secondRequest.label, this, firstRequest, secondRequest);
    }

    /**
     * Combine 2 combinable requests meaning the result ParRequest2Option will finish when the 2 combinable requests
     * finish.  The result is optionally available, meaning available if future completed without error.
     */
    public  ParRequest2Option optionalCombine2(
        final CombinableRequest firstRequest,
        final CombinableRequest secondRequest) {
        return new ParRequest2Option(firstRequest.label + secondRequest.label, this, firstRequest, secondRequest,
                                           false);
    }

    /**
     * Combine 3 combinable requests, meaning the result ParRequest3 will finish when the 3 combinable requests finish
     */
    public  ParRequest3 combine3(
        final CombinableRequest firstRequest,
        final CombinableRequest secondRequest,
        final CombinableRequest thirdRequest) {
        return new ParRequest3(firstRequest.label + secondRequest.label + thirdRequest.label, this,
                                        firstRequest, secondRequest, thirdRequest);
    }

    /**
     * Combine 3 combinable requests meaning the result ParRequest3Option will finish when the 3 combinable requests
     * finish.  The result is optionally available, meaning available if future completed without error.
     */
    public  ParRequest3Option optionalCombine3(
        final CombinableRequest firstRequest,
        final CombinableRequest secondRequest,
        final CombinableRequest thirdRequest) {
        return new ParRequest3Option(firstRequest.label + secondRequest.label + thirdRequest.label, this,
                                              firstRequest, secondRequest, thirdRequest, false);
    }

    /**
     * Combine 4 combinable requests, meaning the result ParRequest4 will finish when the 4 combinable requests finish
     */
    public  ParRequest4 combine4(
            final CombinableRequest firstRequest,
            final CombinableRequest secondRequest,
            final CombinableRequest thirdRequest,
            final CombinableRequest fourthRequest) {
        return new ParRequest4(firstRequest.label + secondRequest.label + thirdRequest.label + fourthRequest.label, this,
                firstRequest, secondRequest, thirdRequest, fourthRequest);
    }


    /**
     * Combine 5 combinable requests, meaning the result ParRequest4 will finish when the 5 combinable requests finish
     */
    public  ParRequest5 combine5(
            final CombinableRequest firstRequest,
            final CombinableRequest secondRequest,
            final CombinableRequest thirdRequest,
            final CombinableRequest fourthRequest,
            final CombinableRequest fifthRequest) {
        return new ParRequest5(firstRequest.label + secondRequest.label + thirdRequest.label + fourthRequest.label + fifthRequest.label, this,
                firstRequest, secondRequest, thirdRequest, fourthRequest, fifthRequest);
    }


    /**
     * Combine 6 combinable requests, meaning the result ParRequest6 will finish when the 6 combinable requests finish
     */
    public  ParRequest6 combine6(
            final CombinableRequest firstRequest,
            final CombinableRequest secondRequest,
            final CombinableRequest thirdRequest,
            final CombinableRequest fourthRequest,
            final CombinableRequest fifthRequest,
            final CombinableRequest sixthRequest) {
        return new ParRequest6(firstRequest.label + secondRequest.label + thirdRequest.label + fourthRequest.label + fifthRequest.label + sixthRequest.label, this,
                firstRequest, secondRequest, thirdRequest, fourthRequest, fifthRequest, sixthRequest);
    }

    /**
     * Given a future which is holding an Either, block on result and return an Either.
     * All expected exceptions are caught and returns as GeneralError inside an Either
     */
    public  Either getEitherSafely(String label, ListenableFuture> future,
                                                       long timeoutMillis) {
        try {
            return future.get(timeoutMillis, TimeUnit.MILLISECONDS);
        } catch (CancellationException e) {
            return GeneralError
                .either("getEitherSafely", String.format("%s request cancelled : %s", label, e.getMessage()), e);
        } catch (TimeoutException e) {
            return GeneralError
                .either("getEitherSafely", String.format("%s request timeout : %s", label, e.getMessage()), e);
        } catch (ExecutionException e) {
            return GeneralError
                .either("getEitherSafely", String.format("%s execution failed : %s", label, e.getMessage()), e);
        } catch (InterruptedException e) {
            return GeneralError
                .either("getEitherSafely", String.format("%s execution interrupted : %s", label, e.getMessage()), e);
        }
    }

    /**
     * Given a future which is holding an Either, block on result and return an Either.
     * All expected exceptions are caught and returns as GeneralError inside an Either
     */
    public  Either getEitherSafely(String label, ListenableFuture> future) {
        return getEitherSafely(label, future, defaultTimeoutMillis);
    }

    public  Option> getSafely(String label,
                                                         Option>> optionalFuture) {
        if (optionalFuture.isDefined()) {
            return Option.apply(getEitherSafely(label, optionalFuture.get()));
        } else {
            return Option.empty();
        }
    }

    /**
     * Given a future which is holding a type T, block on result and return an Either. All expected
     * exceptions are caught and returns as GeneralError inside an Either
     */
    public  Either getSafely(String label, ListenableFuture future, long timeoutMillis) {
        try {
            return new Right<>(future.get(timeoutMillis, TimeUnit.MILLISECONDS));
        } catch (CancellationException e) {
            return GeneralError.either("getSafely", label + " request cancelled", e);
        } catch (TimeoutException e) {
            return GeneralError.either("getSafely", label + " request timeout", e);
        } catch (ExecutionException e) {
            return GeneralError.either("getSafely", label + " execution failed", e);
        } catch (InterruptedException e) {
            return GeneralError.either("getSafely", label + " execution interrupted", e);
        }
    }

    /**
     * Given a future which is holding a type T, block on result and return an Either. All expected
     * exceptions are caught and returns as GeneralError inside an Either
     */
    public  Either getSafely(String label, ListenableFuture future) {
        return getSafely(label, future, defaultTimeoutMillis);
    }

    public  ParRequest immediateResult(String label, Either t) {
        checkNotNull(t, "result is null");
        return new ParRequest(label, this, Futures.immediateFuture(t));
    }

    public  ParRequest fromFuture(String label, ListenableFuture> future) {
        checkNotNull(future, "future is null");
        return new ParRequest(label, this, future);
    }
}