/* * Copyright (c) 2015 Red Hat, Inc. and/or its affiliates. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.jberet.spi; import java.util.ArrayDeque; import java.util.Deque; import java.util.concurrent.Executor; import java.util.concurrent.locks.ReentrantLock; import org.jberet._private.BatchMessages; /** * An executor service to be used with batch jobs. Implementations of JBeret should use this to execute batch tasks. * Some tasks require special handling and therefore need to be queued before they can be run in some circumstances. * <p> * Partition jobs require some special handling with executors. Extending this in implementations will give the desired * behavior to the implementing class. Note that {@link #getMaximumPoolSize()} should return a value greater than 2. * </p> * * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a> */ public abstract class JobExecutor implements Executor { private final ReentrantLock lock; private final Executor delegate; private final Deque<JobTask> queuedTasks; private int usedPermits; /** * Creates a new executor. * * @param delegate the executor that tasks should be submitted to when they are able to run */ protected JobExecutor(final Executor delegate) { lock = new ReentrantLock(true); this.delegate = delegate; queuedTasks = new ArrayDeque<JobTask>(); usedPermits = 0; } /** * Returns the maximum number of threads allowed to be executed. The value returned must be greater than 0. * * @return the maximum number of threads allowed to be executed */ protected abstract int getMaximumPoolSize(); @Override public final void execute(final Runnable runnable) { execute(wrap(runnable), false); } /** * Executes the given task at some time in the future. * * <p> * If the {@link JobTask#getRequiredRemainingPermits()} is any value greater than {@code 0} then the task may be * queued for later execution if the number of permits used is greater than the maximum permits allowed plus the * number of remaining permits required. * </p> * * <p> * As an example if the maximum number of permits allowed is 5 and 4 threads are executing an invocation with 2 * {@link JobTask#getRequiredRemainingPermits()} would be queued. * </p> * * @param task the task to run */ public final void execute(final JobTask task) { execute(task, false); } private void execute(final JobTask task, final boolean reentry) { final int requiredRemainingPermits = (task.getRequiredRemainingPermits() < 0 ? 0 : task.getRequiredRemainingPermits()); Runnable r = null; final int maxPermits = getMaximumPoolSize(); if (requiredRemainingPermits > maxPermits) { throw BatchMessages.MESSAGES.insufficientPermits(requiredRemainingPermits, maxPermits); } lock.lock(); try { // Compare the max permits allowed and the number of used permits, queue if the requiredRemainPermits is // greater than the max permits, otherwise execute the task. If all permits can be used, // requiredRemainingPermits == 0, then just use the delegate executor to queue the tasks as it sees fit. // Note that if this executor needs to act as a thread-pool then two arrays will be needed for tasks that // require permits to stay open and tasks that don't require permits to stay open. if ((usedPermits + requiredRemainingPermits) <= maxPermits || requiredRemainingPermits == 0) { ++usedPermits; r = new Runnable() { @Override public void run() { try { task.run(); } finally { release(); } } }; } else { // If the entry was already from the queue, but there's not enough permits add the task to the top of // the queue if (reentry) { queuedTasks.addFirst(task); } else { queuedTasks.add(task); } } } finally { lock.unlock(); } if (r != null) { delegate.execute(r); } } private void release() { final JobTask next; lock.lock(); try { --usedPermits; if (usedPermits < 0) usedPermits = 0; next = queuedTasks.poll(); } finally { lock.unlock(); } if (next != null) { execute(next, true); } } private JobTask wrap(final Runnable task) { if (task instanceof JobTask) { return (JobTask) task; } return new JobTask() { @Override public int getRequiredRemainingPermits() { return 0; } @Override public void run() { task.run(); } }; } }