All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jppf.client.balancer.queue.TaskQueueChecker Maven / Gradle / Ivy

There is a newer version: 6.3-alpha
Show newest version
/*
 * JPPF.
 * Copyright (C) 2005-2015 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.queue;

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.Lock;

import org.jppf.client.*;
import org.jppf.client.balancer.*;
import org.jppf.execute.ExecutorStatus;
import org.jppf.load.balancer.*;
import org.jppf.load.balancer.impl.*;
import org.jppf.utils.*;
import org.jppf.utils.collections.*;
import org.slf4j.*;

/**
 * This class ensures that idle nodes get assigned pending tasks in the queue.
 */
public class TaskQueueChecker extends ThreadSynchronization implements Runnable {
  /**
   * Logger for this class.
   */
  private static final Logger log = LoggerFactory.getLogger(TaskQueueChecker.class);
  /**
   * Determines whether DEBUG logging level is enabled.
   */
  private static final boolean debugEnabled = LoggingUtils.isDebugEnabled(log);
  /**
   * Determines whether TRACE logging level is enabled.
   */
  private static final boolean traceEnabled = log.isTraceEnabled();
  /**
   * Random number generator used to randomize the choice of idle channel.
   */
  private final Random random = new Random(System.nanoTime());
  /**
   * Reference to the job queue.
   */
  private final JPPFPriorityQueue queue;
  /**
   * Lock on the job queue.
   */
  private final Lock queueLock;
  /**
   * The list of idle node channels.
   */
  private final AbstractCollectionSortedMap idleChannels = new SetSortedMap<>(new AbstractJPPFClient.DescendingIntegerComparator());
  /**
   * Bundler used to schedule tasks for the corresponding node.
   */
  private Bundler bundler;
  /**
   * Holds information about the execution context.
   */
  private final JPPFContext jppfContext;
  /**
   * Used to add channels asynchronously to avoid deadlocks.
   * @see JPPF-398 Deadlock in the client
   */
  private final ExecutorService channelsExecutor = Executors.newSingleThreadExecutor(new JPPFThreadFactory("ChannelsExecutor"));

  /**
   * Initialize this task queue checker with the specified node server.
   * @param queue        the reference queue to use.
   */
  public TaskQueueChecker(final JPPFPriorityQueue queue) {
    this.queue = queue;
    this.jppfContext = new JPPFContextClient(queue);
    this.queueLock = queue.getLock();
    this.bundler = createDefault();
  }

  /**
   * Get the corresponding node's context information.
   * @return a {@link JPPFContext} instance.
   */
  public JPPFContext getJPPFContext() {
    return jppfContext;
  }

  /**
   * Create new instance of default bundler.
   * @return a new {@link Bundler} instance.
   */
  protected Bundler createDefault() {
    FixedSizeProfile profile = new FixedSizeProfile();
    profile.setSize(1);
    return new FixedSizeBundler(profile);
  }

  /**
   * Get the bundler used to schedule tasks for the corresponding node.
   * @return a {@link Bundler} instance.
   */
  public Bundler getBundler() {
    return bundler;
  }

  /**
   * Set the bundler used to schedule tasks for the corresponding node.
   * @param bundler a {@link Bundler} instance.
   */
  public void setBundler(final Bundler bundler) {
    this.bundler = (bundler == null) ? createDefault() : bundler;
  }

  /**
   * Get the number of idle channels.
   * @return the size of the underlying list of idle channels.
   */
  public int getNbIdleChannels() {
    synchronized (idleChannels) {
      return idleChannels.size();
    }
  }

  /**
   * Add a channel to the list of idle channels.
   * @param channel the channel to add to the list.
   */
  public void addIdleChannel(final ChannelWrapper channel) {
    if (channel == null) throw new IllegalArgumentException("channel is null");
    if (channel.getExecutionStatus() != ExecutorStatus.ACTIVE) throw new IllegalStateException("channel is not active: " + channel);
    channelsExecutor.execute(new Runnable() {
      @Override
      public void run() {
        if (traceEnabled) log.trace("Adding idle channel " + channel);
        synchronized (idleChannels) {
          idleChannels.putValue(channel.getPriority(), channel);
        }
        wakeUp();
      }
    });
  }

  /**
   * Get the list of idle channels.
   * @return a new copy of the underlying list of idle channels.
   */
  public List getIdleChannels() {
    synchronized (idleChannels) {
      return idleChannels.allValues();
    }
  }

  /**
   * Remove a channel from the list of idle channels.
   * @param channel the channel to remove from the list.
   * @return a reference to the removed channel.
   */
  public ChannelWrapper removeIdleChannel(final ChannelWrapper channel) {
    if (traceEnabled) log.trace("Removing idle channel " + channel);
    channelsExecutor.execute(new Runnable() {
      @Override
      public void run() {
        synchronized (idleChannels) {
          idleChannels.removeValue(channel.getPriority(), channel);
        }
      }
    });
    return channel;
  }

  /**
   * Return whether any idle channel is available.
   * @return true when there are no idle channels.
   */
  public boolean hasIdleChannel() {
    synchronized (idleChannels) {
      return !idleChannels.isEmpty();
    }
  }

  /**
   * Perform the assignment of tasks.
   */
  @Override
  public void run() {
    while (!isStopped()) {
      if (!dispatch()) goToSleep(10L, 10000);
    }
  }

  /**
   * Perform the assignment of tasks.
   * @return true if a job was dispatched, false otherwise.
   * @see Runnable#run()
   */
  public boolean dispatch() {
    boolean dispatched = false;
    try {
      queue.processPendingBroadcasts();
      synchronized (idleChannels) {
        if (idleChannels.isEmpty() || queue.isEmpty()) return false;
        if (debugEnabled) log.debug(Integer.toString(idleChannels.size()) + " channels idle");
        ChannelWrapper channel = null;
        ClientJob selectedBundle = null;
        queueLock.lock();
        try {
          Iterator it = queue.iterator();
          while ((channel == null) && it.hasNext() && !idleChannels.isEmpty()) {
            ClientJob bundleWrapper = it.next();
            channel = retrieveChannel(bundleWrapper);
            if (channel != null) selectedBundle = bundleWrapper;
          }
          if (debugEnabled) log.debug((channel == null) ? "no channel found for bundle" : "channel found for bundle: " + channel);
          if (channel != null) {
            dispatchJobToChannel(channel, selectedBundle);
            dispatched = true;
          }
        } catch (Exception ex) {
          log.error("An error occurred while attempting to dispatch task bundles. This is most likely due to an error in the load balancer implementation.", ex);
        } finally {
          queueLock.unlock();
        }
      }
    } catch (Exception ex) {
      log.error("An error occurred while preparing for bundle creation and dispatching.", ex);
    }
    return dispatched;
  }

  /**
   * Retrieve a suitable channel for the specified job.
   * @param bundleWrapper the job to execute.
   * @return a channel for a node on which to execute the job.
   * @throws Exception if any error occurs.
   */
  private ChannelWrapper retrieveChannel(final ClientJob bundleWrapper) throws Exception {
    return findIdleChannelIndex(bundleWrapper);
  }

  /**
   * Find a channel that can send the specified task bundle for execution.
   * @param bundle the bundle to execute.
   * @return the index of an available and acceptable channel, or -1 if no channel could be found.
   */
  private ChannelWrapper findIdleChannelIndex(final ClientJob bundle) {
    int idleChannelsSize = idleChannels.size();
    List acceptableChannels = new ArrayList<>(idleChannelsSize);
    //Iterator iterator = idleChannels.iterator();
    Integer highestPriority = idleChannels.firstKey();
    if (highestPriority == null) return null;
    Iterator iterator = idleChannels.getValues(highestPriority).iterator();
    Queue channelsToRemove = new LinkedBlockingQueue<>();
    while (iterator.hasNext()) {
      ChannelWrapper ch = iterator.next();
      if (ch.getExecutionStatus() != ExecutorStatus.ACTIVE) {
        if (debugEnabled) log.debug("channel is not opened: " + ch);
        channelsToRemove.offer(ch);
        continue;
      }
      if (!bundle.acceptsChannel(ch)) continue;
      if(bundle.getBroadcastUUID() != null && !bundle.getBroadcastUUID().equals(ch.getUuid())) continue;
      acceptableChannels.add(ch);
    }
    if (!channelsToRemove.isEmpty()){
      ChannelWrapper ch = null;
      while ((ch = channelsToRemove.poll()) != null) idleChannels.removeValue(ch.getPriority(), ch);
    }
    int size = acceptableChannels.size();
    if (debugEnabled) log.debug("found " + size + " acceptable channels");
    return (size > 0) ? acceptableChannels.get(size > 1 ? random.nextInt(size) : 0) : null;
  }

  /**
   * Dispatch the specified job to the selected channel, after applying the load balancer to the job.
   * @param channel the driver channel to dispatch the job to.
   * @param selectedBundle the job to dispatch.
   * @throws Exception if any error occurs.
   */
  @SuppressWarnings("unchecked")
  private void dispatchJobToChannel(final ChannelWrapper channel, final ClientJob selectedBundle)  throws Exception {
    if (debugEnabled) log.debug(String.format("dispatching jobUuid=%s to channel %s, connectionUuid=%s", selectedBundle.getJob().getUuid(), channel, channel.getConnectionUuid()));
    synchronized (channel.getMonitor()) {
      int bundlerSize = 1;
      try {
        updateBundler(getBundler(), selectedBundle.getJob(), channel);
        int selectedSize = selectedBundle.getTaskCount();
        bundlerSize = Math.min(channel.getBundler().getBundleSize(), selectedBundle.getTaskCount());
        boolean local = channel.isLocal();
        if (debugEnabled) log.debug("dispatching " + Math.min(bundlerSize, selectedSize)  + " tasks to " + (local ? "local" : "remote") + " channel");
      } catch (Exception e) {
        log.error("Error in load balancer implementation, switching to 'manual' with a bundle size of 1: {}", ExceptionUtils.getStackTrace(e));
        FixedSizeProfile profile = new FixedSizeProfile();
        profile.setSize(1);
        setBundler(new FixedSizeBundler(profile));
      }
      ClientTaskBundle bundleWrapper = queue.nextBundle(selectedBundle, bundlerSize);
      selectedBundle.addChannel(channel);
      channel.submit(bundleWrapper);
    }
  }

  /**
   * Perform the checks on the bundler before submitting a job.
   * @param bundler    the bundler to check and update.
   * @param taskBundle the job.
   * @param context    the current node context.
   */
  @SuppressWarnings("deprecation")
  private void updateBundler(final Bundler bundler, final JPPFJob taskBundle, final ChannelWrapper context) {
    context.checkBundler(bundler, jppfContext);
    if (context.getBundler() instanceof JobAwareness) {
      ((JobAwareness) context.getBundler()).setJobMetadata(taskBundle.getMetadata());
    } else if (context.getBundler() instanceof JobAwarenessEx) {
      ((JobAwarenessEx) context.getBundler()).setJob(taskBundle);
    }
  }

  /**
   * Clear all channels from this task queue checker.
   */
  public void clearChannels() {
    synchronized (idleChannels) {
      idleChannels.clear();
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy