/* 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.activiti.engine.impl.asyncexecutor; import java.util.LinkedList; import java.util.UUID; import org.activiti.engine.impl.context.Context; import org.activiti.engine.impl.interceptor.Command; import org.activiti.engine.impl.interceptor.CommandContext; import org.activiti.engine.impl.interceptor.CommandExecutor; import org.activiti.engine.impl.persistence.entity.JobEntity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Joram Barrez * @author Tijs Rademakers */ public abstract class AbstractAsyncJobExecutor implements AsyncExecutor { private static Logger log = LoggerFactory.getLogger(AbstractAsyncJobExecutor.class); /** * 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; protected Thread timerJobAcquisitionThread; protected Thread asyncJobAcquisitionThread; protected AcquireTimerJobsRunnable timerJobRunnable; protected AcquireAsyncJobsDueRunnable asyncJobsDueRunnable; protected ExecuteAsyncRunnableFactory executeAsyncRunnableFactory; protected boolean isAutoActivate = false; protected boolean isActive = false; protected int maxTimerJobsPerAcquisition = 1; protected int maxAsyncJobsDuePerAcquisition = 1; protected int defaultTimerJobAcquireWaitTimeInMillis = 10 * 1000; protected int defaultAsyncJobAcquireWaitTimeInMillis = 10 * 1000; protected int defaultQueueSizeFullWaitTime = 0; protected String lockOwner = UUID.randomUUID().toString(); protected int timerLockTimeInMillis = 5 * 60 * 1000; protected int asyncJobLockTimeInMillis = 5 * 60 * 1000; protected int retryWaitTimeInMillis = 500; // Job queue used when async executor is not yet started and jobs are already added. // This is mainly used for testing purpose. protected LinkedList<JobEntity> temporaryJobQueue = new LinkedList<JobEntity>(); protected CommandExecutor commandExecutor; public boolean executeAsyncJob(JobEntity job) { if (isActive) { Runnable runnable = createRunnableForJob(job); boolean result = executeAsyncJob(runnable); if (!result) { doUnlockJob(job); } return result; // false indicates that the job was rejected. } else { temporaryJobQueue.add(job); return true; } } protected abstract boolean executeAsyncJob(Runnable runnable); protected void doUnlockJob(final JobEntity job) { // The job will now be '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 aquire thread runnable) CommandContext commandContext = Context.getCommandContext(); if (commandContext != null) { unlockJob(job, commandContext); } else { commandExecutor.execute(new Command<Void>() { public Void execute(CommandContext commandContext) { unlockJob(job, commandContext); return null; } }); } } protected void unlockJob(JobEntity job, CommandContext commandContext) { commandContext.getJobEntityManager().unacquireJob(job.getId()); } protected Runnable createRunnableForJob(JobEntity job) { return executeAsyncRunnableFactory.createExecuteAsyncRunnable(job, commandExecutor); } /** Starts the async executor */ public void start() { if (isActive) { return; } log.info("Starting up the default async job executor [{}].", getClass().getName()); initialize(); startExecutingAsyncJobs(); isActive = true; while (temporaryJobQueue.isEmpty() == false) { JobEntity job = temporaryJobQueue.pop(); executeAsyncJob(job); } isActive = true; } protected void initialize() { if (timerJobRunnable == null) { timerJobRunnable = new AcquireTimerJobsRunnable(this); } if (asyncJobsDueRunnable == null) { asyncJobsDueRunnable = new AcquireAsyncJobsDueRunnable(this); } if (executeAsyncRunnableFactory == null) { executeAsyncRunnableFactory = new DefaultExecuteAsyncRunnableFactory(); } } /** Shuts down the whole job executor */ public synchronized void shutdown() { if (!isActive) { return; } log.info("Shutting down the default async job executor [{}].", getClass().getName()); timerJobRunnable.stop(); asyncJobsDueRunnable.stop(); stopExecutingAsyncJobs(); timerJobRunnable = null; asyncJobsDueRunnable = null; isActive = false; } protected abstract void startExecutingAsyncJobs(); protected abstract void stopExecutingAsyncJobs(); /** Starts the acquisition thread */ protected void startJobAcquisitionThread() { if (timerJobAcquisitionThread == null) { timerJobAcquisitionThread = new Thread(timerJobRunnable); } timerJobAcquisitionThread.start(); if (asyncJobAcquisitionThread == null) { asyncJobAcquisitionThread = new Thread(asyncJobsDueRunnable); } asyncJobAcquisitionThread.start(); } /** Stops the acquisition thread */ protected void stopJobAcquisitionThread() { try { timerJobAcquisitionThread.join(); } catch (InterruptedException e) { log.warn("Interrupted while waiting for the timer job acquisition thread to terminate", e); } try { asyncJobAcquisitionThread.join(); } catch (InterruptedException e) { log.warn("Interrupted while waiting for the async job acquisition thread to terminate", e); } timerJobAcquisitionThread = null; asyncJobAcquisitionThread = null; } /* getters and setters */ public CommandExecutor getCommandExecutor() { return commandExecutor; } public void setCommandExecutor(CommandExecutor commandExecutor) { this.commandExecutor = commandExecutor; } public boolean isAutoActivate() { return isAutoActivate; } public void setAutoActivate(boolean isAutoActivate) { this.isAutoActivate = isAutoActivate; } public boolean isActive() { return isActive; } public long getKeepAliveTime() { return keepAliveTime; } public void setKeepAliveTime(long keepAliveTime) { this.keepAliveTime = keepAliveTime; } public String getLockOwner() { return lockOwner; } public void setLockOwner(String lockOwner) { this.lockOwner = lockOwner; } public int getTimerLockTimeInMillis() { return timerLockTimeInMillis; } public void setTimerLockTimeInMillis(int timerLockTimeInMillis) { this.timerLockTimeInMillis = timerLockTimeInMillis; } public int getAsyncJobLockTimeInMillis() { return asyncJobLockTimeInMillis; } public void setAsyncJobLockTimeInMillis(int asyncJobLockTimeInMillis) { this.asyncJobLockTimeInMillis = asyncJobLockTimeInMillis; } public int getMaxTimerJobsPerAcquisition() { return maxTimerJobsPerAcquisition; } public void setMaxTimerJobsPerAcquisition(int maxTimerJobsPerAcquisition) { this.maxTimerJobsPerAcquisition = maxTimerJobsPerAcquisition; } public int getMaxAsyncJobsDuePerAcquisition() { return maxAsyncJobsDuePerAcquisition; } public void setMaxAsyncJobsDuePerAcquisition(int maxAsyncJobsDuePerAcquisition) { this.maxAsyncJobsDuePerAcquisition = maxAsyncJobsDuePerAcquisition; } public int getDefaultTimerJobAcquireWaitTimeInMillis() { return defaultTimerJobAcquireWaitTimeInMillis; } public void setDefaultTimerJobAcquireWaitTimeInMillis(int defaultTimerJobAcquireWaitTimeInMillis) { this.defaultTimerJobAcquireWaitTimeInMillis = defaultTimerJobAcquireWaitTimeInMillis; } public int getDefaultAsyncJobAcquireWaitTimeInMillis() { return defaultAsyncJobAcquireWaitTimeInMillis; } public void setDefaultAsyncJobAcquireWaitTimeInMillis(int defaultAsyncJobAcquireWaitTimeInMillis) { this.defaultAsyncJobAcquireWaitTimeInMillis = defaultAsyncJobAcquireWaitTimeInMillis; } public int getDefaultQueueSizeFullWaitTimeInMillis() { return defaultQueueSizeFullWaitTime; } public void setDefaultQueueSizeFullWaitTimeInMillis(int defaultQueueSizeFullWaitTime) { this.defaultQueueSizeFullWaitTime = defaultQueueSizeFullWaitTime; } public void setTimerJobRunnable(AcquireTimerJobsRunnable timerJobRunnable) { this.timerJobRunnable = timerJobRunnable; } public void setAsyncJobsDueRunnable(AcquireAsyncJobsDueRunnable asyncJobsDueRunnable) { this.asyncJobsDueRunnable = asyncJobsDueRunnable; } public int getRetryWaitTimeInMillis() { return retryWaitTimeInMillis; } public void setRetryWaitTimeInMillis(int retryWaitTimeInMillis) { this.retryWaitTimeInMillis = retryWaitTimeInMillis; } public ExecuteAsyncRunnableFactory getExecuteAsyncRunnableFactory() { return executeAsyncRunnableFactory; } public void setExecuteAsyncRunnableFactory(ExecuteAsyncRunnableFactory executeAsyncRunnableFactory) { this.executeAsyncRunnableFactory = executeAsyncRunnableFactory; } }