org.flowable.job.service.impl.asyncexecutor.DefaultAsyncJobExecutor.java Source code

Java tutorial

Introduction

Here is the source code for org.flowable.job.service.impl.asyncexecutor.DefaultAsyncJobExecutor.java

Source

/* 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 org.flowable.job.service.impl.asyncexecutor;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.flowable.engine.common.impl.context.Context;
import org.flowable.engine.common.impl.interceptor.Command;
import org.flowable.engine.common.impl.interceptor.CommandContext;
import org.flowable.job.api.JobInfo;
import org.flowable.job.service.impl.util.CommandContextUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Joram Barrez
 * @author Tijs Rademakers
 */
public class DefaultAsyncJobExecutor extends AbstractAsyncExecutor {

    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultAsyncJobExecutor.class);

    protected Thread timerJobAcquisitionThread;
    protected Thread asyncJobAcquisitionThread;
    protected Thread resetExpiredJobThread;

    /**
     * The minimal number of threads that are kept alive in the threadpool for job execution
     */
    protected int corePoolSize = 2;

    /**
     * The maximum number of threads that are kept alive in the threadpool for job execution
     */
    protected int maxPoolSize = 10;

    /**
     * The time (in milliseconds) a thread used for job execution must be kept alive before it is destroyed. Default setting is 0. Having a non-default setting of 0 takes resources, but in the case of
     * many job executions it avoids creating new threads all the time.
     */
    protected long keepAliveTime = 5000L;

    /** The size of the queue on which jobs to be executed are placed */
    protected int queueSize = 100;

    /** Whether to unlock jobs that are owned by this executor (have the same lockOwner) at startup */
    protected boolean unlockOwnedJobs;

    /** The queue used for job execution work */
    protected BlockingQueue<Runnable> threadPoolQueue;

    /** The executor service used for job execution */
    protected ExecutorService executorService;

    /**
     * The time (in seconds) that is waited to gracefully shut down the threadpool used for job execution
     */
    protected long secondsToWaitOnShutdown = 60L;

    @Override
    protected boolean executeAsyncJob(final JobInfo job, Runnable runnable) {
        try {
            executorService.execute(runnable);
            return true;
        } catch (RejectedExecutionException e) {

            // When a RejectedExecutionException is caught, this means that the queue for holding the jobs
            // that are to be executed is full and can't store more.
            // The job is now 'unlocked', meaning that the lock owner/time is set to null,
            // so other executors can pick the job up (or this async executor, the next time the
            // acquire query is executed.

            // This can happen while already in a command context (for example in a transaction listener
            // after the async executor has been hinted that a new async job is created)
            // or not (when executed in the acquire thread runnable)

            CommandContext commandContext = Context.getCommandContext();
            if (commandContext != null) {
                CommandContextUtil.getJobManager(commandContext).unacquire(job);

            } else {
                jobServiceConfiguration.getCommandExecutor().execute(new Command<Void>() {
                    @Override
                    public Void execute(CommandContext commandContext) {
                        CommandContextUtil.getJobManager(commandContext).unacquire(job);
                        return null;
                    }
                });
            }

            // Job queue full, returning true so (if wanted) the acquiring can be throttled
            return false;
        }
    }

    @Override
    protected void startAdditionalComponents() {
        if (!isMessageQueueMode) {
            initAsyncJobExecutionThreadPool();
            startJobAcquisitionThread();
        }

        if (unlockOwnedJobs) {
            unlockOwnedJobs();
        }

        if (timerRunnableNeeded) {
            startTimerAcquisitionThread();
        }
        startResetExpiredJobsThread();
    }

    @Override
    protected void shutdownAdditionalComponents() {
        stopResetExpiredJobsThread();
        stopTimerAcquisitionThread();
        stopJobAcquisitionThread();
        stopExecutingAsyncJobs();
    }

    protected void initAsyncJobExecutionThreadPool() {
        if (threadPoolQueue == null) {
            LOGGER.info("Creating thread pool queue of size {}", queueSize);
            threadPoolQueue = new ArrayBlockingQueue<>(queueSize);
        }

        if (executorService == null) {
            LOGGER.info("Creating executor service with corePoolSize {}, maxPoolSize {} and keepAliveTime {}",
                    corePoolSize, maxPoolSize, keepAliveTime);

            BasicThreadFactory threadFactory = new BasicThreadFactory.Builder()
                    .namingPattern("flowable-async-job-executor-thread-%d").build();
            executorService = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime,
                    TimeUnit.MILLISECONDS, threadPoolQueue, threadFactory);
        }
    }

    protected void stopExecutingAsyncJobs() {
        if (executorService != null) {

            // Ask the thread pool to finish and exit
            executorService.shutdown();

            // Waits for 1 minute to finish all currently executing jobs
            try {
                if (!executorService.awaitTermination(secondsToWaitOnShutdown, TimeUnit.SECONDS)) {
                    LOGGER.warn(
                            "Timeout during shutdown of async job executor. The current running jobs could not end within {} seconds after shutdown operation.",
                            secondsToWaitOnShutdown);
                }
            } catch (InterruptedException e) {
                LOGGER.warn("Interrupted while shutting down the async job executor. ", e);
            }

            executorService = null;
        }
    }

    /** Starts the acquisition thread */
    protected void startJobAcquisitionThread() {
        if (asyncJobAcquisitionThread == null) {
            asyncJobAcquisitionThread = new Thread(asyncJobsDueRunnable);
        }
        asyncJobAcquisitionThread.start();
    }

    protected void startTimerAcquisitionThread() {
        if (timerJobAcquisitionThread == null) {
            timerJobAcquisitionThread = new Thread(timerJobRunnable);
        }
        timerJobAcquisitionThread.start();
    }

    /** Stops the acquisition thread */
    protected void stopJobAcquisitionThread() {
        if (asyncJobAcquisitionThread != null) {
            try {
                asyncJobAcquisitionThread.join();
            } catch (InterruptedException e) {
                LOGGER.warn("Interrupted while waiting for the async job acquisition thread to terminate", e);
            }
            asyncJobAcquisitionThread = null;
        }
    }

    protected void stopTimerAcquisitionThread() {
        if (timerJobAcquisitionThread != null) {
            try {
                timerJobAcquisitionThread.join();
            } catch (InterruptedException e) {
                LOGGER.warn("Interrupted while waiting for the timer job acquisition thread to terminate", e);
            }
            timerJobAcquisitionThread = null;
        }
    }

    /** Starts the reset expired jobs thread */
    protected void startResetExpiredJobsThread() {
        if (resetExpiredJobThread == null) {
            resetExpiredJobThread = new Thread(resetExpiredJobsRunnable);
        }
        resetExpiredJobThread.start();
    }

    /** Stops the reset expired jobs thread */
    protected void stopResetExpiredJobsThread() {
        if (resetExpiredJobThread != null) {
            try {
                resetExpiredJobThread.join();
            } catch (InterruptedException e) {
                LOGGER.warn("Interrupted while waiting for the reset expired jobs thread to terminate", e);
            }

            resetExpiredJobThread = null;
        }
    }

    public Thread getTimerJobAcquisitionThread() {
        return timerJobAcquisitionThread;
    }

    public void setTimerJobAcquisitionThread(Thread timerJobAcquisitionThread) {
        this.timerJobAcquisitionThread = timerJobAcquisitionThread;
    }

    public Thread getAsyncJobAcquisitionThread() {
        return asyncJobAcquisitionThread;
    }

    public void setAsyncJobAcquisitionThread(Thread asyncJobAcquisitionThread) {
        this.asyncJobAcquisitionThread = asyncJobAcquisitionThread;
    }

    public Thread getResetExpiredJobThread() {
        return resetExpiredJobThread;
    }

    public void setResetExpiredJobThread(Thread resetExpiredJobThread) {
        this.resetExpiredJobThread = resetExpiredJobThread;
    }

    public int getQueueSize() {
        return queueSize;
    }

    @Override
    public int getRemainingCapacity() {
        if (threadPoolQueue != null) {
            return threadPoolQueue.remainingCapacity();
        } else {
            // return plenty of remaining capacity if there's no thread pool queue
            return 99;
        }
    }

    public void setQueueSize(int queueSize) {
        this.queueSize = queueSize;
    }

    public int getCorePoolSize() {
        return corePoolSize;
    }

    public void setCorePoolSize(int corePoolSize) {
        this.corePoolSize = corePoolSize;
    }

    public int getMaxPoolSize() {
        return maxPoolSize;
    }

    public void setMaxPoolSize(int maxPoolSize) {
        this.maxPoolSize = maxPoolSize;
    }

    public long getKeepAliveTime() {
        return keepAliveTime;
    }

    public void setKeepAliveTime(long keepAliveTime) {
        this.keepAliveTime = keepAliveTime;
    }

    public long getSecondsToWaitOnShutdown() {
        return secondsToWaitOnShutdown;
    }

    public void setSecondsToWaitOnShutdown(long secondsToWaitOnShutdown) {
        this.secondsToWaitOnShutdown = secondsToWaitOnShutdown;
    }

    public boolean isUnlockOwnedJobs() {
        return unlockOwnedJobs;
    }

    public void setUnlockOwnedJobs(boolean unlockOwnedJobs) {
        this.unlockOwnedJobs = unlockOwnedJobs;
    }

    public BlockingQueue<Runnable> getThreadPoolQueue() {
        return threadPoolQueue;
    }

    public void setThreadPoolQueue(BlockingQueue<Runnable> threadPoolQueue) {
        this.threadPoolQueue = threadPoolQueue;
    }

    public ExecutorService getExecutorService() {
        return executorService;
    }

    public void setExecutorService(ExecutorService executorService) {
        this.executorService = executorService;
    }

}