org.apache.hive.hcatalog.templeton.JobRequestExecutor.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hive.hcatalog.templeton.JobRequestExecutor.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.hive.hcatalog.templeton;

import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.Future;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class JobRequestExecutor<T> {
    private static final Logger LOG = LoggerFactory.getLogger(JobRequestExecutor.class);
    private static AppConfig appConf = Main.getAppConfigInstance();

    /*
     * Thread pool to execute job requests.
     */
    private ThreadPoolExecutor jobExecutePool = null;

    /*
     * Type of job request.
     */
    private JobRequestType requestType;

    /*
     * Config name used to find the number of concurrent requests.
     */
    private String concurrentRequestsConfigName;

    /*
     * Config name used to find the maximum time job request can be executed.
     */
    private String jobTimeoutConfigName;

    /*
     * Job request execution time out in seconds. If it is 0 then request
     * will not be timed out.
     */
    private int requestExecutionTimeoutInSec = 0;

    /*
     * Amount of time a thread can be alive in thread pool before cleaning this up. Core threads
     * will not be cleanup from thread pool.
     */
    private int threadKeepAliveTimeInHours = 1;

    /*
     * Maximum number of times a cancel request is sent to job request execution
     * thread. Future.cancel may not be able to interrupt the thread if it is
     * blocked on network calls.
     */
    private int maxTaskCancelRetryCount = 10;

    /*
     * Wait time in milliseconds before another cancel request is made.
     */
    private int maxTaskCancelRetryWaitTimeInMs = 1000;

    /*
     * A flag to indicate whether to cancel the task when exception TimeoutException or
     * InterruptedException or CancellationException raised. The default is cancel thread.
     */
    private boolean enableCancelTask = true;

    /*
     * Job Request type.
     */
    public enum JobRequestType {
        Submit, Status, List
    }

    /*
     * Creates a job request object and sets up execution environment. Creates a thread pool
     * to execute job requests.
     *
     * @param requestType
     *          Job request type
     *
     * @param concurrentRequestsConfigName
     *          Config name to be used to extract number of concurrent requests to be serviced.
     *
     * @param jobTimeoutConfigName
     *          Config name to be used to extract maximum time a task can execute a request.
     *
     * @param enableCancelTask
     *          A flag to indicate whether to cancel the task when exception TimeoutException
     *          or InterruptedException or CancellationException raised.
     *
     */
    public JobRequestExecutor(JobRequestType requestType, String concurrentRequestsConfigName,
            String jobTimeoutConfigName, boolean enableCancelTask) {

        this.concurrentRequestsConfigName = concurrentRequestsConfigName;
        this.jobTimeoutConfigName = jobTimeoutConfigName;
        this.requestType = requestType;
        this.enableCancelTask = enableCancelTask;

        /*
         * The default number of threads will be 0. That means thread pool is not used and
         * operation is executed with the current thread.
         */
        int threads = !StringUtils.isEmpty(concurrentRequestsConfigName)
                ? appConf.getInt(concurrentRequestsConfigName, 0)
                : 0;

        if (threads > 0) {
            /*
             * Create a thread pool with no queue wait time to execute the operation. This will ensure
             * that job requests are rejected if there are already maximum number of threads busy.
             */
            this.jobExecutePool = new ThreadPoolExecutor(threads, threads, threadKeepAliveTimeInHours,
                    TimeUnit.HOURS, new SynchronousQueue<Runnable>());
            this.jobExecutePool.allowCoreThreadTimeOut(true);

            /*
             * Get the job request time out value. If this configuration value is set to 0
             * then job request will wait until it finishes.
             */
            if (!StringUtils.isEmpty(jobTimeoutConfigName)) {
                this.requestExecutionTimeoutInSec = appConf.getInt(jobTimeoutConfigName, 0);
            }

            LOG.info("Configured " + threads + " threads for job request type " + this.requestType
                    + " with time out " + this.requestExecutionTimeoutInSec + " s.");
        } else {
            /*
             * If threads are not configured then they will be executed in current thread itself.
             */
            LOG.info("No thread pool configured for job request type " + this.requestType);
        }
    }

    /*
     * Creates a job request object and sets up execution environment. Creates a thread pool
     * to execute job requests.
     *
     * @param requestType
     *          Job request type
     *
     * @param concurrentRequestsConfigName
     *          Config name to be used to extract number of concurrent requests to be serviced.
     *
     * @param jobTimeoutConfigName
     *          Config name to be used to extract maximum time a task can execute a request.
     *
     */
    public JobRequestExecutor(JobRequestType requestType, String concurrentRequestsConfigName,
            String jobTimeoutConfigName) {
        this(requestType, concurrentRequestsConfigName, jobTimeoutConfigName, true);
    }

    /*
     * Returns true of thread pool is created and can be used for executing a job request.
     * Otherwise, returns false.
     */
    public boolean isThreadPoolEnabled() {
        return this.jobExecutePool != null;
    }

    /*
     * Executes job request operation. If thread pool is not created then job request is
     * executed in current thread itself.
     *
     * @param jobExecuteCallable
     *          Callable object to run the job request task.
     *
     */
    public T execute(JobCallable<T> jobExecuteCallable)
            throws InterruptedException, TimeoutException, TooManyRequestsException, ExecutionException {
        /*
         * The callable shouldn't be null to execute. The thread pool also should be configured
         * to execute requests.
         */
        assert (jobExecuteCallable != null);
        assert (this.jobExecutePool != null);

        String type = this.requestType.toString().toLowerCase();

        String retryMessageForConcurrentRequests = "Please wait for some time before retrying "
                + "the operation. Please refer to the config " + concurrentRequestsConfigName
                + " to configure concurrent requests.";

        LOG.debug("Starting new " + type + " job request with time out " + this.requestExecutionTimeoutInSec
                + "seconds.");
        Future<T> future = null;

        try {
            future = this.jobExecutePool.submit(jobExecuteCallable);
        } catch (RejectedExecutionException rejectedException) {
            /*
             * Not able to find thread to execute the job request. Raise Busy exception and client
             * can retry the operation.
             */
            String tooManyRequestsExceptionMessage = "Unable to service the " + type + " job request as "
                    + "templeton service is busy with too many " + type + " job requests. "
                    + retryMessageForConcurrentRequests;

            LOG.warn(tooManyRequestsExceptionMessage);
            throw new TooManyRequestsException(tooManyRequestsExceptionMessage);
        }

        T result = null;

        try {
            result = this.requestExecutionTimeoutInSec > 0
                    ? future.get(this.requestExecutionTimeoutInSec, TimeUnit.SECONDS)
                    : future.get();
        } catch (TimeoutException e) {
            /*
             * See if the execution thread has just completed operation and result is available.
             * If result is available then return the result. Otherwise, raise exception.
             */
            if ((result = tryGetJobResultOrSetJobStateFailed(jobExecuteCallable)) == null) {
                String message = this.requestType + " job request got timed out. Please wait for some time "
                        + "before retrying the operation. Please refer to the config " + jobTimeoutConfigName
                        + " to configure job request time out.";
                LOG.warn(message);

                /*
                 * Throw TimeoutException to caller.
                 */
                throw new TimeoutException(message);
            }
        } catch (InterruptedException e) {
            /*
             * See if the execution thread has just completed operation and result is available.
             * If result is available then return the result. Otherwise, raise exception.
             */
            if ((result = tryGetJobResultOrSetJobStateFailed(jobExecuteCallable)) == null) {
                String message = this.requestType + " job request got interrupted. Please wait for some time "
                        + "before retrying the operation.";
                LOG.warn(message);

                /*
                 * Throw TimeoutException to caller.
                 */
                throw new InterruptedException(message);
            }
        } catch (CancellationException e) {
            /*
             * See if the execution thread has just completed operation and result is available.
             * If result is available then return the result. Otherwise, raise exception.
             */
            if ((result = tryGetJobResultOrSetJobStateFailed(jobExecuteCallable)) == null) {
                String message = this.requestType + " job request got cancelled and thread got interrupted. "
                        + "Please wait for some time before retrying the operation.";
                LOG.warn(message);

                throw new InterruptedException(message);
            }
        } finally {
            /*
             * If the thread is still active and needs to be cancelled then cancel it. This may
             * happen in case task got interrupted, or timed out.
             */
            if (enableCancelTask) {
                cancelExecutePoolThread(future);
            }
        }

        LOG.debug("Completed " + type + " job request.");

        return result;
    }

    /*
     * Initiate cancel request to cancel the thread execution and interrupt the thread.
     * If thread interruption is not handled by jobExecuteCallable then thread may continue
     * running to completion. The cancel call may fail for some scenarios. In that case,
     * retry the cancel call until it returns true or max retry count is reached.
     *
     * @param future
     *          Future object which has handle to cancel the thread.
     *
     */
    private void cancelExecutePoolThread(Future<T> future) {
        int retryCount = 0;
        while (retryCount < this.maxTaskCancelRetryCount && !future.isDone()) {
            LOG.info("Task is still executing the job request. Cancelling it with retry count: " + retryCount);
            if (future.cancel(true)) {
                /*
                 * Cancelled the job request and return to client.
                 */
                LOG.info("Cancel job request issued successfully.");
                return;
            }

            retryCount++;
            try {
                Thread.sleep(this.maxTaskCancelRetryWaitTimeInMs);
            } catch (InterruptedException e) {
                /*
                 * Nothing to do. Just retry.
                 */
            }
        }

        LOG.warn("Failed to cancel the job. isCancelled: " + future.isCancelled() + " Retry count: " + retryCount);
    }

    /*
     * Tries to get the job result if job request is completed. Otherwise it sets job status
     * to FAILED such that execute thread can do necessary clean up based on FAILED state.
     */
    private T tryGetJobResultOrSetJobStateFailed(JobCallable<T> jobExecuteCallable) {
        if (!jobExecuteCallable.setJobStateFailed()) {
            LOG.info("Job is already COMPLETED. Returning the result.");
            return jobExecuteCallable.returnResult;
        } else {
            LOG.info("Job status set to FAILED. Job clean up to be done by execute thread "
                    + "after job request is executed.");
            return null;
        }
    }
}