
org.jppf.client.concurrent.BatchHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jppf-client Show documentation
Show all versions of jppf-client Show documentation
JPPF, the open source grid computing solution
The newest version!
/*
* JPPF.
* Copyright (C) 2005-2019 JPPF Team.
* http://www.jppf.org
*
* 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.jppf.client.concurrent;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.concurrent.locks.*;
import org.jppf.JPPFException;
import org.jppf.client.JPPFJob;
import org.jppf.client.event.JobListener;
import org.jppf.client.taskwrapper.JPPFAnnotatedTask;
import org.jppf.node.protocol.Task;
import org.jppf.utils.*;
import org.jppf.utils.concurrent.ThreadSynchronization;
import org.slf4j.*;
/**
* This class is a processor for tasks submitted via a {@link JPPFExecutorService}.
* It handles both normal mode and batching mode, where the tasks throughput is streamlined
* by specifying how many tasks should be sent to the grid, and a which intervals.
* @author Laurent Cohen
*/
class BatchHandler extends ThreadSynchronization implements Runnable {
/**
* Logger for this class.
*/
private static Logger log = LoggerFactory.getLogger(BatchHandler.class);
/**
* Determines whether debug-level logging is enabled.
*/
private static boolean debugEnabled = LoggingUtils.isDebugEnabled(log);
/**
* Count of jobs created by this executor service.
*/
private static final AtomicLong JOB_COUNT = new AtomicLong(0);
/**
* The minimum number of tasks that must be submitted before they are sent to the server.
*/
private int batchSize;
/**
* The maximum time to wait before the next batch of tasks is to be sent for execution.
*/
private long batchTimeout;
/**
* The JPPFExecutorService whose tasks are batched.
*/
private final JPPFExecutorService executor;
/**
* The job to send for execution. If the reference is ont null, then the job is sent immediately.
*/
private final AtomicReference currentJobRef = new AtomicReference<>(null);
/**
* The next job being prepared. It will be assigned to currentJobRef
when it is ready for execution, depending on the batching parameters.
*/
private final AtomicReference nextJobRef = new AtomicReference<>(null);
/**
* The time at which we started to count for the the timeout.
*/
private long start;
/**
* Time elapsed since the start.
*/
private long elapsed;
/**
* Used to synchronize access to currentJobRef
and nextJobRef
.
*/
private final ReentrantLock lock = new ReentrantLock(true);
/**
* Represents a condition to await for and corresponding to when currentJobRef
is not null.
*/
private final Condition jobReady = lock.newCondition();
/**
* Represents a condition to await for and corresponding to when currentJobRef
is not null.
*/
private final Condition submittingJob = lock.newCondition();
/**
* The configuration for this batch handler.
*/
private ExecutorServiceConfiguration config = new ExecutorServiceConfigurationImpl();
/**
* Default constructor.
* @param executor the JPPFExecutorService whose tasks are batched.
*/
BatchHandler(final JPPFExecutorService executor) {
this(executor, 0, 0L);
}
/**
* Initialize with the specified executor service, batch size and batch tiemout.
* @param executor the JPPFExecutorService whose tasks are batched.
* @param batchSize the minimum number of tasks that must be submitted before they are sent to the server.
* @param batchTimeout the maximum time to wait before the next batch of tasks is to be sent for execution.
*/
BatchHandler(final JPPFExecutorService executor, final int batchSize, final long batchTimeout) {
this.executor = executor;
this.batchSize = batchSize;
this.batchTimeout = batchTimeout;
resetTimeout();
nextJobRef.set(createJob());
}
/**
* Get the minimum number of tasks that must be submitted before they are sent to the server.
* @return the batch size as an int.
*/
int getBatchSize() {
lock.lock();
try {
return batchSize;
} finally {
lock.unlock();
}
}
/**
* Set the minimum number of tasks that must be submitted before they are sent to the server.
* @param batchSize the batch size as an int.
*/
void setBatchSize(final int batchSize) {
lock.lock();
try {
if (debugEnabled) log.debug("setting batchSize = {}", batchSize);
this.batchSize = batchSize;
jobReady.signal();
} finally {
lock.unlock();
}
}
/**
* Get the maximum time to wait before the next batch of tasks is to be sent for execution.
* @return the timeout as a long.
*/
long getBatchTimeout() {
lock.lock();
try {
return batchTimeout;
} finally {
lock.unlock();
}
}
/**
* Set the maximum time to wait before the next batch of tasks is to be sent for execution.
* @param batchTimeout the timeout as a long.
*/
void setBatchTimeout(final long batchTimeout) {
lock.lock();
try {
if (debugEnabled) log.debug("setting batchTimeout = {}", batchTimeout);
if (this.batchTimeout <=0L) resetTimeout();
this.batchTimeout = batchTimeout;
jobReady.signal();
} finally {
lock.unlock();
}
}
@Override
public void run() {
start = System.nanoTime();
while (!isStopped()) {
try {
lock.lock();
try {
JPPFJob job = null;
while (!isStopped() && ((job = currentJobRef.get()) == null)) {
final long batchTimeout = getBatchTimeout();
if (batchTimeout > 0) {
final long n = batchTimeout - elapsed;
if (n > 0) jobReady.await(n, TimeUnit.MILLISECONDS);
}
else jobReady.await();
updateNextJob(false);
}
if (isStopped()) break;
if (debugEnabled) log.debug("submitting job {} with {} tasks", job.getName(), job.getJobTasks().size());
configureJob(job);
executor.submitJob(job);
currentJobRef.set(null);
elapsed = (System.nanoTime() - start) / 1_000_000L;
submittingJob.signal();
} finally {
lock.unlock();
}
} catch (final Exception e) {
log.error(e.getMessage(), e);
}
}
}
/**
* Update the next job to submit if one is ready.
* @param sendSignal true if signal is to be sent, false otherwise.
*/
private void updateNextJob(final boolean sendSignal) {
final JPPFJob job = nextJobRef.get();
final int size = job.getJobTasks().size();
final long batchTimeout = getBatchTimeout();
final int batchSize = getBatchSize();
if (batchTimeout > 0L) elapsed = (System.nanoTime() - start) / 1_000_000L;
if (size == 0) {
if ((batchTimeout > 0L) && (elapsed >= batchTimeout)) resetTimeout();
return;
}
if (((batchTimeout > 0L) && (elapsed >= batchTimeout)) ||
((batchSize > 0) && (size >= batchSize)) ||
((batchSize <= 0) && (batchTimeout <= 0L))) {
if (debugEnabled) log.debug("preparing job {} for submission, batchTimeout={}, elapsed={}, batchSize={}, size={}", job.getName(), batchTimeout, elapsed, batchSize, size);
currentJobRef.set(job);
nextJobRef.set(createJob());
resetTimeout();
if (sendSignal) {
jobReady.signal();
try {
submittingJob.await();
} catch (final InterruptedException e) {
throw new RejectedExecutionException(e);
}
}
}
}
/**
* Reset the timeout counter.
*/
private void resetTimeout() {
start = System.nanoTime();
elapsed = 0L;
}
/**
* Submit a {@link Task} that returns the specified type of result.
* @param the type of result returned by the task.
* @param task the task to submit.
* @param result this parameter is only here for type inference (I know, it's ugly).
* @return a {@link Future} representing pending completion of the task.
*/
Future addTask(final Task> task, final T result) {
lock.lock();
try {
if (debugEnabled) log.debug("submitting JPPF task");
Future future = null;
final JPPFJob job = nextJobRef.get();
try {
job.add(task);
future = new JPPFTaskFuture<>(job, task.getPosition());
} catch (final JPPFException e) {
log.error(e.getMessage(), e);
throw new RejectedExecutionException(e);
}
updateNextJob(true);
return future;
} finally {
lock.unlock();
}
}
/**
* Submit a {@link Runnable} that returns the specified type of result.
* @param the type of result returned by the task.
* @param task the task to submit.
* @param result the result for the task.
* @return a {@link Future} representing pending completion of the task.
*/
Future addTask(final Runnable task, final T result) {
lock.lock();
try {
if (debugEnabled) log.debug("submitting Runnable task with result");
Future future = null;
final JPPFJob job = nextJobRef.get();
try {
final JPPFAnnotatedTask t = (JPPFAnnotatedTask) job.add(task);
t.setResult(result);
configureTask(t);
future = new JPPFTaskFuture<>(job, t.getPosition());
} catch (final JPPFException e) {
log.error(e.getMessage(), e);
throw new RejectedExecutionException(e);
}
updateNextJob(true);
return future;
} finally {
lock.unlock();
}
}
/**
* Submit a task for execution.
* @param the type of results.
* @param task the task to submit.
* @return a {@link Future} representing pending completion of the task.
*/
Future addTask(final Callable task) {
lock.lock();
try {
if (debugEnabled) log.debug("submitting Callable task");
Future future = null;
final JPPFJob job = nextJobRef.get();
try {
final JPPFAnnotatedTask jppfTask = (JPPFAnnotatedTask) job.add(task);
configureTask(jppfTask);
future = new JPPFTaskFuture<>(job, jppfTask.getPosition());
} catch (final JPPFException e) {
log.error(e.getMessage(), e);
throw new RejectedExecutionException(e);
}
updateNextJob(true);
return future;
} finally {
lock.unlock();
}
}
/**
* Submit a list of tasks for execution.
* @param the type of the results.
* @param tasks the tasks to submit.
* @return a pair representing the result collector used in the current job, along with the position of the first task.
*/
Pair addTasks(final Collection extends Callable> tasks) {
lock.lock();
try {
if (debugEnabled) log.debug("submitting " + tasks.size() + " Callable tasks");
Pair pair = null;
final JPPFJob job = nextJobRef.get();
int start = 0;
try {
final List> jobTasks = job.getJobTasks();
start = jobTasks.size();
for (final Callable> task: tasks) {
final Task> t = job.add(task);
configureTask((JPPFAnnotatedTask) t);
}
} catch (final JPPFException e) {
log.error(e.getMessage(), e);
throw new RejectedExecutionException(e);
}
pair = new Pair<>(job, start);
updateNextJob(true);
return pair;
} finally {
lock.unlock();
}
}
/**
* Create a new job with a FutureResultCollector a results listener.
* @return a {@link JPPFJob} instance.
*/
private JPPFJob createJob() {
final JPPFJob job = new JPPFJob();
job.setName(getClass().getSimpleName() + " job " + JOB_COUNT.incrementAndGet());
job.addJobListener(executor);
if (debugEnabled) log.debug("created job " + job);
//configureJob(job);
return job;
}
/**
* Configure the specified job using the current configuration.
* @param job the job to configure.
*/
private synchronized void configureJob(final JPPFJob job) {
if (config != null) {
final JobConfiguration jc = config.getJobConfiguration();
job.setSLA(jc.getSLA());
job.setClientSLA(jc.getClientSLA());
job.setMetadata(jc.getMetadata());
job.setPersistenceManager(jc.getPersistenceManager());
job.setDataProvider(jc.getDataProvider());
for (JobListener listener: jc.getAllJobListeners()) job.addJobListener(listener);
for (ClassLoader cl: jc.getClassLoaders()) executor.client.registerClassLoader(cl, job.getUuid());
}
}
/**
* Configure the specified job using the current configuration.
* @param task the task to configure.
*/
private synchronized void configureTask(final JPPFAnnotatedTask task) {
if (config != null) {
final TaskConfiguration tc = config.getTaskConfiguration();
task.setCancelCallback(tc.getOnCancelCallback());
task.setTimeoutCallback(tc.getOnTimeoutCallback());
task.setTimeoutSchedule(tc.getTimeoutSchedule());
}
}
/**
* Close this batch handler.
*/
void close() {
setStopped(true);
lock.lock();
try {
jobReady.signalAll();
} finally {
lock.unlock();
}
}
/**
* Get the configuration for this batch handler.
* @return an {@link ExecutorServiceConfiguration} instance.
*/
synchronized ExecutorServiceConfiguration getConfig() {
return config;
}
/**
* Set the configuration for this batch handler.
* @param config an {@link ExecutorServiceConfiguration} instance.
* @throws IllegalArgumentException if the new configuration is null.
*/
synchronized void setConfig(final ExecutorServiceConfiguration config) throws IllegalArgumentException {
if (config == null) throw new IllegalArgumentException("configuration cannot be null");
this.config = config;
}
/**
* Get the configuration for this batch handler.
* @return an {@link ExecutorServiceConfiguration} instance.
*/
synchronized ExecutorServiceConfiguration resetConfig() {
config = new ExecutorServiceConfigurationImpl();
return config;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy