
org.jppf.client.balancer.JobManagerClient 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.balancer;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import org.jppf.client.*;
import org.jppf.client.balancer.queue.*;
import org.jppf.client.event.*;
import org.jppf.load.balancer.LoadBalancingInformation;
import org.jppf.load.balancer.spi.JPPFBundlerFactory;
import org.jppf.management.*;
import org.jppf.node.protocol.Task;
import org.jppf.queue.*;
import org.jppf.utils.*;
import org.jppf.utils.collections.*;
import org.jppf.utils.concurrent.*;
import org.jppf.utils.configuration.JPPFProperties;
import org.slf4j.*;
/**
* This task provides asynchronous management of tasks submitted through the resource adapter.
* It relies on a queue where job are first added, then submitted when a connection becomes available.
* It also provides methods to check the status of a job and retrieve the results.
* @author Laurent Cohen
* @author Martin JANDA
* @exclude
*/
public class JobManagerClient extends ThreadSynchronization implements JobManager {
/**
* Logger for this class.
*/
private static final Logger log = LoggerFactory.getLogger(JobManagerClient.class);
/**
* Determines whether debug-level logging is enabled.
*/
private static boolean debugEnabled = LoggingUtils.isDebugEnabled(log);
/**
* A reference to the tasks queue.
*/
private final JPPFPriorityQueue queue;
/**
* The bundler factory.
*/
private final JPPFBundlerFactory bundlerFactory;
/**
* The latest load-balancing information.
*/
private LoadBalancingInformation currentLoadBalancingInformation;
/**
* Synchronization lock.
*/
private final Object loadBalancingInformationLock = new Object();
/**
* Task that dispatches queued jobs to available nodes.
*/
private final JobScheduler jobScheduler;
/**
* Mapping client connections to channel wrapper.
*/
private final Map wrapperMap = new HashMap<>();
/**
* A list of all the connections.
*/
private final CollectionSortedMap allConnections = new LinkedListSortedMap<>(new DescendingIntegerComparator());
/**
* Listener used for monitoring state changes.
*/
private final ClientConnectionStatusListener statusListener = event -> {
if (event.getSource() instanceof ChannelWrapperLocal) updateConnectionStatus((ChannelWrapper) event.getSource(), event.getOldStatus());
else if (event.getSource() instanceof JPPFClientConnection) updateConnectionStatus(((JPPFClientConnection) event.getSource()), event.getOldStatus());
};
/**
* Determines whether local execution is enabled on this client.
*/
private boolean localEnabled;
/**
* Wrapper for local execution node.
*/
private ChannelWrapperLocal wrapperLocal;
/**
* Holds the current connections with ACTIVE or EXECUTING status.
*/
private final CollectionSortedMap workingConnections = new LinkedListSortedMap<>(new DescendingIntegerComparator());
/**
* Determines whether this job manager has been closed.
*/
private final AtomicBoolean closed = new AtomicBoolean(false);
/**
* The JPPF client.
*/
private final JPPFClient client;
/**
* Instantiates client job manager.
* @param client JPPF client that manages connections to the JPPF drivers.
* @param bundlerFactory the factory that creates load-balancer instances.
* @throws Exception if any error occurs.
*/
public JobManagerClient(final JPPFClient client, final JPPFBundlerFactory bundlerFactory) throws Exception {
if (client == null) throw new IllegalArgumentException("client is null");
this.client = client;
this.localEnabled = client.getConfig().get(JPPFProperties.LOCAL_EXECUTION_ENABLED);
this.queue = new JPPFPriorityQueue(this);
this.bundlerFactory = bundlerFactory;
currentLoadBalancingInformation = bundlerFactory.getCurrentInfo();
jobScheduler = new JobScheduler(queue, bundlerFactory);
this.queue.addQueueListener(new QueueListenerAdapter() {
@Override
public void bundleAdded(final QueueEvent event) {
jobScheduler.wakeUp();
}
});
ThreadUtils.startDaemonThread(jobScheduler, "JobScheduler");
this.queue.addQueueListener(client);
client.addConnectionPoolListener(new ConnectionPoolListenerAdapter() {
@Override
public void connectionRemoved(final ConnectionPoolEvent event) {
removeConnection(event.getConnection());
}
});
updateLocalExecution(this.localEnabled);
}
/**
* Add the specified connection wrapper to the list of connections handled by this manager.
* @param wrapper the connection wrapper to add.
*/
protected void addConnection(final ChannelWrapper wrapper) {
if (wrapper == null) throw new IllegalArgumentException("wrapper is null");
if (closed.get()) throw new IllegalStateException("this job manager was closed");
if (log.isDebugEnabled()) log.debug("adding connection " + wrapper);
synchronized(allConnections) {
allConnections.putValue(wrapper.getPriority(), wrapper);
}
updateConnectionStatus(wrapper, JPPFClientConnectionStatus.NEW, wrapper.getStatus());
}
/**
* Remove the specified connection wrapper from the list of connections handled by this manager.
* @param wrapper the connection wrapper to remove.
*/
protected void removeConnection(final ChannelWrapper wrapper) {
if (wrapper == null) throw new IllegalArgumentException("wrapper is null");
try {
final JPPFClientConnectionStatus status = wrapper.getStatus();
if (!status.isTerminatedStatus()) updateConnectionStatus(wrapper, wrapper.getStatus(), JPPFClientConnectionStatus.DISCONNECTED);
else updateConnectionStatus(wrapper, wrapper.getOldStatus(), wrapper.getStatus());
} finally {
synchronized(allConnections) {
allConnections.removeValue(wrapper.getPriority(), wrapper);
}
}
}
/**
* Add the specified client connection to the list of connections handled by this manager.
* @param cnn the client connection to add.
* @return wrapper for the added client connection.
*/
public ChannelWrapper addConnection(final JPPFClientConnection cnn) {
if (debugEnabled) log.debug("adding connection " + cnn);
if (closed.get()) throw new IllegalStateException("this job manager was closed");
ChannelWrapper wrapper = null;
synchronized(wrapperMap) {
wrapper = wrapperMap.get(cnn);
}
if (wrapper == null) {
try {
wrapper = new ChannelWrapperRemoteAsync(cnn);
final JPPFSystemInformation systemInfo = cnn.getSystemInfo();
if (systemInfo != null) wrapper.setSystemInformation(systemInfo);
final JPPFConnectionPool pool = cnn.getConnectionPool();
final JPPFManagementInfo info = new JPPFManagementInfo(pool.getDriverHost(), pool.getDriverIPAddress(), pool.getJmxPort(), pool.getDriverUuid(), JPPFManagementInfo.DRIVER, pool.isSslEnabled());
if (systemInfo != null) info.setSystemInfo(systemInfo);
wrapper.setManagementInfo(info);
} catch (final Throwable e) {
log.error("Error while adding connection " + cnn, e);
} finally {
synchronized(wrapperMap) {
wrapperMap.put(cnn, wrapper);
}
addConnection(wrapper);
}
}
if (log.isDebugEnabled()) log.debug("end of adding connection " + cnn);
return wrapper;
}
/**
* Remove the specified client connection from the list of connections handled by this manager.
* @param connection the client connection to remove.
* @return wrapper for the removed client connection or null.
*/
protected ChannelWrapper removeConnection(final JPPFClientConnection connection) {
ChannelWrapper wrapper = null;
synchronized(wrapperMap) {
wrapper = wrapperMap.remove(connection);
}
if (wrapper != null) {
if (debugEnabled) log.debug("removing connection {}", connection);
removeConnection(wrapper);
}
return wrapper;
}
/**
* Get all the client connections handled by this manager.
* @return a list of ChannelWrapper
instances.
*/
public List getAllConnections() {
synchronized(allConnections) {
return new ArrayList<>(allConnections.allValues());
}
}
/**
* Get all the client connections with a working status.
* @return a list of {@link ChannelWrapper} instances.
*/
public List getWorkingConnections() {
synchronized(workingConnections) {
return new ArrayList<>(workingConnections.allValues());
}
}
/**
* Get all the client connections with a working status, excluding the local executor if it is enabled.
* @return a list of {@link ChannelWrapper} instances.
*/
public List getWorkingRemoteConnections() {
final List result = getWorkingConnections();
if (isLocalExecutionEnabled()) {
final Iterator it = result.iterator();
while (it.hasNext()) {
final ChannelWrapper channel = it.next();
if (channel.isLocal()) {
it.remove();
break;
}
}
}
return result;
}
/**
* Determine whether there is at least one working connection, idle or not.
* @return {@code true} if there is at least one connection, {@code false} otherwise.
*/
public boolean hasWorkingConnection() {
synchronized(workingConnections) {
return !workingConnections.isEmpty();
}
}
/**
* @param connection the client connection.
* @param oldStatus the connection status before the change.
*/
private void updateConnectionStatus(final JPPFClientConnection connection, final JPPFClientConnectionStatus oldStatus) {
ChannelWrapper wrapper = null;
synchronized(wrapperMap) {
wrapper = wrapperMap.get(connection);
}
if (wrapper != null) {
if ((oldStatus == JPPFClientConnectionStatus.CONNECTING) && (wrapper.getStatus() == JPPFClientConnectionStatus.ACTIVE)) {
final JPPFSystemInformation systemInfo = connection.getSystemInfo();
final JMXDriverConnectionWrapper jmx = connection.getConnectionPool().getJmxConnection();
wrapper.setSystemInformation(systemInfo);
if (!wrapper.isLocal()) {
final String driverUuid = connection.getDriverUuid();
JPPFManagementInfo info = null;
final JPPFConnectionPool pool = connection.getConnectionPool();
if (jmx != null) info = new JPPFManagementInfo(pool.getDriverHost(), pool.getDriverIPAddress(), jmx.getPort(), jmx.getId(), JPPFManagementInfo.DRIVER, connection.isSSLEnabled());
else info = new JPPFManagementInfo(pool.getDriverHost(), pool.getDriverIPAddress(), -1, driverUuid != null ? driverUuid : "?", JPPFManagementInfo.DRIVER, connection.isSSLEnabled());
info.setSystemInfo(systemInfo);
wrapper.setManagementInfo(info);
}
}
updateConnectionStatus(wrapper, oldStatus);
}
}
/**
* @param wrapper the connection wrapper.
* @param oldStatus the connection status before the change.
*/
private void updateConnectionStatus(final ChannelWrapper wrapper, final JPPFClientConnectionStatus oldStatus) {
if (wrapper == null) return;
updateConnectionStatus(wrapper, oldStatus, wrapper.getStatus());
}
/**
* @param wrapper the connection wrapper.
* @param oldStatus the connection status before the change.
* @param newStatus the connection status after the change.
*/
private void updateConnectionStatus(final ChannelWrapper wrapper, final JPPFClientConnectionStatus oldStatus, final JPPFClientConnectionStatus newStatus) {
if (closed.get()) return;
if (oldStatus == null) throw new IllegalArgumentException("oldStatus is null");
if (newStatus == null) throw new IllegalArgumentException("newStatus is null");
if (debugEnabled) log.debug("updating status from {} to {} for {}", oldStatus, newStatus, wrapper);
if ((wrapper == null) || (oldStatus == newStatus)) return;
final boolean bNew = newStatus.isWorkingStatus();
final boolean bOld = oldStatus.isWorkingStatus();
//final int priority = wrapper.getPriority();
if (bNew && !bOld) {
synchronized(workingConnections) {
workingConnections.putValue(wrapper.getPriority(), wrapper);
jobScheduler.setHighestPriority(workingConnections.firstKey());
}
} else if (!bNew && bOld) {
synchronized(workingConnections) {
workingConnections.removeValue(wrapper.getPriority(), wrapper);
if (!workingConnections.isEmpty()) jobScheduler.setHighestPriority(workingConnections.firstKey());
}
}
if (newStatus == JPPFClientConnectionStatus.ACTIVE) {
if (debugEnabled) log.debug("processing active status for {}", wrapper);
wrapper.initChannelID();
if (debugEnabled) log.debug("about to add idle channel {}", wrapper);
jobScheduler.addIdleChannel(wrapper);
} else {
jobScheduler.removeIdleChannel(wrapper);
if (newStatus.isTerminatedStatus() || newStatus == JPPFClientConnectionStatus.DISCONNECTED) queue.cancelBroadcastJobs(wrapper.getUuid());
}
jobScheduler.wakeUp();
}
@Override
public String submitJob(final JPPFJob job) {
return submitJob(job, null);
}
@Override
public String submitJob(final JPPFJob job, final JobStatusListener listener) {
if (closed.get()) throw new IllegalStateException("this jobmanager was closed");
if (debugEnabled) log.debug("submitting job {}", job);
if (listener != null) job.addJobStatusListener(listener);
final List> tasks = job.getJobTasks();
final List> pendingTasks = new ArrayList<>(tasks.size());
for (final Task> task: tasks) {
if (!job.getResults().hasResult(task.getPosition())) pendingTasks.add(task);
}
queue.addBundle(new ClientJob(job, pendingTasks));
return job.getUuid();
}
@Override
public String resubmitJob(final JPPFJob job) {
return submitJob(job);
}
@Override
public boolean cancelJob(final String jobId) throws Exception {
if (debugEnabled) log.debug("requesting cancel of jobId=" + jobId);
queue.cancelJob(jobId);
return true;
}
@Override
public boolean hasAvailableConnection() {
final boolean localConnectionavailable;
synchronized(this) {
localConnectionavailable = (wrapperLocal != null) && (wrapperLocal.getStatus() == JPPFClientConnectionStatus.ACTIVE);
}
return jobScheduler.hasIdleChannel() || localConnectionavailable;
}
@Override
public synchronized boolean isLocalExecutionEnabled() {
return localEnabled;
}
@Override
public synchronized void setLocalExecutionEnabled(final boolean localExecutionEnabled) {
if (debugEnabled) log.debug("setting localExecutionEnabled = {}", localExecutionEnabled);
if (this.localEnabled == localExecutionEnabled) return;
this.localEnabled = localExecutionEnabled;
updateLocalExecution(this.localEnabled);
}
/**
* Starts or stops local execution node according to specified parameter.
* @param localExecutionEnabled true
to enable local execution, false
otherwise
*/
private void updateLocalExecution(final boolean localExecutionEnabled) {
if (closed.get()) throw new IllegalStateException("this job manager was closed");
if (localExecutionEnabled) {
wrapperLocal = new ChannelWrapperLocal(client);
wrapperLocal.addClientConnectionStatusListener(statusListener);
addConnection((ChannelWrapper) wrapperLocal);
} else if (wrapperLocal != null) {
try {
wrapperLocal.close();
} finally {
removeConnection((ChannelWrapper) wrapperLocal);
wrapperLocal = null;
}
}
}
/**
* Get the number of connections available for job scheduling.
* @return the number of available connections.
*/
public int nbAvailableConnections() {
return jobScheduler.getNbIdleChannels();
}
@Override
public Vector getAvailableConnections() {
final List idleChannels = jobScheduler.getIdleChannels();
final Vector availableConnections = new Vector<>(idleChannels.size());
for (final ChannelWrapper idleChannel : idleChannels) {
if (!idleChannel.isLocal()) {
final AbstractChannelWrapperRemote wrapperRemote = (AbstractChannelWrapperRemote) idleChannel;
availableConnections.add(wrapperRemote.getChannel());
}
}
return availableConnections;
}
@Override
public ClientConnectionStatusListener getClientConnectionStatusListener() {
return this.statusListener;
}
@Override
public void reset() {
synchronized(allConnections) {
for (ChannelWrapper channel: allConnections) {
channel.setResetting(true);
channel.close();
}
allConnections.clear();
if (jobScheduler != null) jobScheduler.clearChannels();
}
}
@Override
public void close() {
if (debugEnabled) log.debug("closing {}", this);
closed.set(true);
setStopped(true);
wakeUp();
if (jobScheduler != null) {
jobScheduler.setStopped(true);
jobScheduler.wakeUp();
}
queue.close();
synchronized(allConnections) {
for (ChannelWrapper channel: allConnections) channel.close();
allConnections.clear();
}
}
@Override
public LoadBalancingInformation getLoadBalancerSettings() {
synchronized(loadBalancingInformationLock) {
if (currentLoadBalancingInformation == null) {
final LoadBalancingInformation info = bundlerFactory.getCurrentInfo();
currentLoadBalancingInformation = new LoadBalancingInformation(info.getAlgorithm(), info.getParameters(), bundlerFactory.getBundlerProviderNames());
}
return currentLoadBalancingInformation;
}
}
@Override
public void setLoadBalancerSettings(final String algorithm, final Properties parameters) throws Exception {
if (algorithm == null) throw new IllegalArgumentException("Error: no algorithm specified (null value)");
if (!bundlerFactory.getBundlerProviderNames().contains(algorithm)) throw new IllegalArgumentException("Error: unknown algorithm '" + algorithm + '\'');
final TypedProperties props = (parameters == null) ? new TypedProperties() : new TypedProperties(parameters);
synchronized(loadBalancingInformationLock) {
final LoadBalancingInformation lbi = new LoadBalancingInformation(algorithm, props, currentLoadBalancingInformation.getAlgorithmNames());
currentLoadBalancingInformation = bundlerFactory.setAndGetCurrentInfo(lbi);
}
}
/**
* @return the job scheuler.
*/
public JobScheduler getJobScheduler() {
return jobScheduler;
}
/**
* @return the job queue.
*/
public JPPFPriorityQueue getQueue() {
return queue;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy