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

org.jppf.client.balancer.ClientJob 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;

import static org.jppf.client.balancer.ClientJobStatus.*;

import java.io.*;
import java.util.*;

import org.jppf.JPPFException;
import org.jppf.client.*;
import org.jppf.client.event.JobEvent;
import org.jppf.client.event.JobEvent.Type;
import org.jppf.node.protocol.*;
import org.jppf.utils.*;
import org.slf4j.*;

/**
 * @author Martin JANDA
 * @author Laurent Cohen
 */
public class ClientJob extends AbstractClientJob {
  /**
   * Logger for this class.
   */
  private static final Logger log = LoggerFactory.getLogger(ClientJob.class);
  /**
   * Determines whether debug-level logging is enabled.
   */
  private static boolean debugEnabled = LoggingUtils.isDebugEnabled(log);
  /**
   * The list of the tasks.
   */
  private final List> tasks;
  /**
   * The broadcast UUID, i.e. the uuid of the connection the job is broadcast to.
   */
  private transient String broadcastUUID = null;
  /**
   * Map of all futures in this job.
   */
  private final Map bundleMap = new LinkedHashMap<>();
  /**
   * The status of this job.
   */
  private JobStatus jobStatus;
  /**
   * The listener that receives notifications of completed tasks.
   */
  private final JPPFResultCollector resultCollector;
  /**
   * Map of all dispatched broadcast jobs.
   */
  private final Map broadcastMap;
  /**
   * Map of all pending broadcast jobs.
   */
  private final Set broadcastSet = new LinkedHashSet<>();
  /**
   * Indicator whether this job is executing.
   */
  private boolean executing = false;
  /**
   * The requeue handler.
   */
  private Runnable onRequeue = null;
  /**
   * State map for tasks on which resultReceived was called.
   */
  private final TaskStateMap taskStateMap = new TaskStateMap();
  /**
   * The original number of tasks in the job.
   */
  protected final int initialTaskCount;

  /**
   * Initialized client job with task bundle and list of tasks to execute.
   * @param job   underlying task bundle.
   * @param tasks list of tasks to execute.
   */
  public ClientJob(final JPPFJob job, final List> tasks) {
    this(job, tasks, null, null);
  }

  /**
   * Initialized client job with task bundle and list of tasks to execute.
   * @param job   underlying task bundle.
   * @param tasks list of tasks to execute.
   * @param parentJob instance of parent broadcast job.
   * @param broadcastUUID the broadcast UUID, i.e. the uuid of the connection the job is broadcast to.
   */
  @SuppressWarnings("deprecation")
  protected ClientJob(final JPPFJob job, final List> tasks, final ClientJob parentJob, final String broadcastUUID) {
    super(job);
    if (tasks == null) throw new IllegalArgumentException("tasks is null");
    this.parentJob = parentJob;
    this.broadcastUUID = broadcastUUID;
    this.initialTaskCount = tasks.size();
    if (broadcastUUID == null) {
      if (job.getSLA().isBroadcastJob()) this.broadcastMap = new LinkedHashMap<>();
      else this.broadcastMap = Collections.emptyMap();
      this.resultCollector = this.job.getResultCollector();
    } else {
      this.broadcastMap = Collections.emptyMap();
      this.resultCollector = null;
    }
    JobStatus s = job.getStatus();
    this.jobStatus = s == null ? JobStatus.SUBMITTED : s;
    this.tasks = new ArrayList<>(tasks);
    for (Task result : job.getResults().getAllResults()) {
      if (result != null) taskStateMap.put(result.getPosition(), TaskState.RESULT);
    }
  }

  /**
   * Sets indicator whether is job is executing. Job start or job end is notified when state changes.
   * @param executing true when this client job is executing. false otherwise.
   */
  protected void setExecuting(final boolean executing) {
    synchronized (tasks) {
      if (this.executing == executing) return;
      this.executing = executing;
    }
  }

  /**
   * Get the current number of tasks in the job.
   * @return the number of tasks as an int.
   */
  public int getTaskCount() {
    synchronized (tasks) {
      return tasks.size();
    }
  }

  /**
   * Get the list of of the tasks.
   * @return a list of JPPFTask instances.
   */
  public List> getTasks() {
    synchronized (tasks) {
      return Collections.unmodifiableList(new ArrayList<>(tasks));
    }
  }

  /**
   * Make a copy of this client job wrapper.
   * @param broadcastUUID the broadcast UUID, i.e. the uuid of the connection the job is broadcast to.
   * @return a new ClientJob instance.
   */
  public ClientJob createBroadcastJob(final String broadcastUUID) {
    if (broadcastUUID == null || broadcastUUID.isEmpty()) throw new IllegalArgumentException("broadcastUUID is blank");
    ClientJob clientJob;
    synchronized (tasks) {
      clientJob = new ClientJob(job, this.tasks, this, broadcastUUID);
    }
    synchronized (bundleMap) {
      broadcastSet.add(clientJob);
    }
    return clientJob;
  }

  /**
   * Make a copy of this client job wrapper containing only the first nbTasks tasks it contains.
   * @param nbTasks the number of tasks to include in the copy.
   * @return a new ClientJob instance.
   */
  public ClientTaskBundle copy(final int nbTasks) {
    List> list = (nbTasks >= this.tasks.size()) ? this.tasks : this.tasks.subList(0, nbTasks);
    ClientTaskBundle bundle = new ClientTaskBundle(this, list); // constructor makes a copy of the list
    list.clear();
    return bundle;
  }

  /**
   * Merge this client job wrapper with another.
   * @param taskList list of tasks to merge.
   * @param after determines whether the tasks from other should be added first or last.
   * @return true when this client job needs to be requeued.
   */
  protected boolean merge(final List> taskList, final boolean after) {
    synchronized (tasks) {
      boolean requeue = this.tasks.isEmpty() && !taskList.isEmpty();
      if (!after) this.tasks.addAll(0, taskList);
      if (after) this.tasks.addAll(taskList);
      return requeue;
    }
  }

  /**
   * Get the broadcast UUID.
   * @return an String instance.
   */
  public String getBroadcastUUID() {
    return broadcastUUID;
  }

  /**
   * Called when all or part of a job is dispatched to a node.
   * @param bundle  the dispatched job.
   * @param channel the node to which the job is dispatched.
   */
  public void jobDispatched(final ClientTaskBundle bundle, final ChannelWrapper channel) {
    if (bundle == null) throw new IllegalArgumentException("bundle is null");
    if (channel == null) throw new IllegalArgumentException("channel is null");
    boolean empty;
    synchronized (bundleMap) {
      empty = bundleMap.isEmpty();
      bundleMap.put(bundle, channel);
    }
    if (empty) {
      updateStatus(NEW, EXECUTING);
      setJobStatus(JobStatus.EXECUTING);
      setExecuting(true);
    }
    if (!isParentBroadcastJob()) job.fireJobEvent(JobEvent.Type.JOB_DISPATCH, channel, bundle.getTasksL());
    if (parentJob != null) parentJob.broadcastDispatched(this);
  }

  /**
   * Notify that this job was requeued.
   */
  public void jobRequeued() {
    updateStatus(EXECUTING, NEW);
  }

  /**
   * Called to notify that the results of a number of tasks have been received from the server.
   * @param bundle  the executing job.
   * @param results the list of tasks whose results have been received from the server.
   */
  public void resultsReceived(final ClientTaskBundle bundle, final List> results) {
    if (debugEnabled) log.debug("received " + results.size() + " results for bundle " + bundle);
    if (results.isEmpty()) return;
    synchronized (tasks) {
      for (int i=0; i task = results.get(i);
        taskStateMap.put(task.getPosition(), TaskState.RESULT);
        if (task instanceof JPPFExceptionResult) {
          Task originalTask = job.getJobTasks().get(task.getPosition());
          originalTask.setThrowable(task.getThrowable());
          results.set(i, originalTask);
        }
      }
    }
    callResultListener(results, null);
  }

  /**
   * Called to notify that throwable eventually raised while receiving the results.
   * @param bundle    the finished job.
   * @param throwable the throwable that was raised while receiving the results.
   */
  public void resultsReceived(final ClientTaskBundle bundle, final Throwable throwable) {
    if (bundle == null) throw new IllegalArgumentException("bundle is null");
    if (debugEnabled) log.debug("received  throwable " + throwable + " for bundle " + bundle);
    boolean ioe = (throwable instanceof IOException) && !(throwable instanceof NotSerializableException);
    Exception e = throwable instanceof Exception ? (Exception) throwable : new JPPFException(throwable);
    synchronized (tasks) {
      for (Task task : bundle.getTasksL()) {
        TaskState oldState = taskStateMap.get(task.getPosition());
        if (!ioe && (oldState != TaskState.RESULT)) {
          taskStateMap.put(task.getPosition(), TaskState.EXCEPTION);
          task.setThrowable(e);
        }
      }
    }
    callResultListener(bundle.getTasksL(), throwable);
  }

  /**
   * Invoke the job's {@link TaskResultListener} callback and log any exception that may result from the invocation.
   * @param results the tasks to provide as results.
   * @param throwable an eventual {@link Throwable} that may have been raised while the tasks were executing.
   * @see JPPF-257 Better exception handling for overriden or custom TaskResultListener implementations
   */
  @SuppressWarnings("deprecation")
  private void callResultListener(final List> results, final Throwable throwable) {
    JPPFResultCollector listener = resultCollector;
    if (listener != null) {
      try {
        synchronized (listener) {
          listener.resultsReceived(results, throwable, !isParentBroadcastJob());
        }
      } catch(Exception e) {
        log.error("error while calling the TaskResultListener for job [name={}, uuid={}] : {}", new Object[] {job.getName(), job.getUuid(), ExceptionUtils.getStackTrace(e)});
      }
    } else if (isChildBroadcastJob()) {
      job.fireJobEvent(JobEvent.Type.JOB_RETURN, null, results);
    } else log.warn(" result collector for job {}", this.job);
  }

  /**
   * Called to notify that the execution of a task has completed.
   * @param bundle    the completed task.
   * @param exception the {@link Exception} thrown during job execution or null.
   */
  public void taskCompleted(final ClientTaskBundle bundle, final Exception exception) {
    if (debugEnabled) log.debug("bundle=" + bundle + ", exception=" + exception + " for " + this);
    boolean empty;
    synchronized (bundleMap) {
      ChannelWrapper channel = bundleMap.remove(bundle);
      if ((bundle != null) && (channel == null)) throw new IllegalStateException("future already removed");
      empty = bundleMap.isEmpty() && broadcastMap.isEmpty();
    }
    boolean requeue = false;
    if (getSLA().isBroadcastJob()) {
      List> list = new ArrayList<>();
      synchronized (tasks) {
        if (bundle != null) {
          for (Task task : bundle.getTasksL()) {
            if (taskStateMap.put(task.getPosition(), TaskState.RESULT) != TaskState.RESULT) list.add(task);
          }
        }
        if (isCancelled() || getBroadcastUUID() == null) {
          list.addAll(this.tasks);
          this.tasks.clear();
        }
      }
      resultsReceived(bundle, list);
    } else if (bundle == null) {
      if (isCancelled()) {
        List> list = new ArrayList<>();
        synchronized (tasks) {
          list.addAll(this.tasks);
          this.tasks.clear();
        }
        resultsReceived(bundle, list);
      }
    } else {
      if (bundle.isCancelled()) {
        List> list = new ArrayList<>();
        synchronized (tasks) {
          for (Task task : bundle.getTasksL()) {
            if (taskStateMap.get(task.getPosition()) != TaskState.RESULT) list.add(task);
          }
          list.addAll(this.tasks);
          this.tasks.clear();
        }
        resultsReceived(bundle, list);
      }
      if (bundle.isRequeued()) {
        List> list = new ArrayList<>();
        synchronized (tasks) {
          for (Task task : bundle.getTasksL()) {
            if (taskStateMap.get(task.getPosition()) != TaskState.RESULT) list.add(task);
          }
          requeue = merge(list, false);
        }
      }
    }
    if (hasPending()) {
      if (exception != null) setJobStatus(exception instanceof NotSerializableException ? JobStatus.COMPLETE : JobStatus.FAILED);
      if (empty) setExecuting(false);
      if (requeue && onRequeue != null) {
        onRequeue.run();
        updateStatus(NEW, EXECUTING);
      }
    } else {
      boolean callDone = updateStatus(isCancelled() ? CANCELLED : EXECUTING, DONE);
      if (empty) setExecuting(false);
      try {
        if (callDone) done();
      } finally {
        if (parentJob != null) parentJob.broadcastCompleted(this);
      }
      setJobStatus(JobStatus.COMPLETE);
    }
  }

  /**
   * Get indicator whether job has pending tasks.
   * @return true when job has some pending tasks.
   */
  protected boolean hasPending() {
    synchronized (tasks) {
      if (tasks.isEmpty() && taskStateMap.size() >= job.getJobTasks().size()) return taskStateMap.getStateCount(TaskState.EXCEPTION) > 0;
      else return true;
    }
  }

  /**
   * Get the status of this job.
   * @return a {@link JobStatus} enumerated value.
   */
  public JobStatus getJobStatus() {
    return jobStatus;
  }

  /**
   * Set the status of this job.
   * @param jobStatus a {@link JobStatus} enumerated value.
   */
  public void setJobStatus(final JobStatus jobStatus) {
    if (this.jobStatus == jobStatus) return;
    this.jobStatus = jobStatus;
    if (resultCollector instanceof JobStatusHandler) ((JobStatusHandler) resultCollector).setStatus(this.jobStatus);
    else if (((jobStatus == JobStatus.COMPLETE) || (jobStatus == JobStatus.FAILED)) && isChildBroadcastJob()) job.fireJobEvent(Type.JOB_END, null, tasks);
  }

  @Override
  public boolean cancel(final boolean mayInterruptIfRunning) {
    if (debugEnabled) log.debug("requesting cancel of jobId=" + this.getUuid());
    if (super.cancel(mayInterruptIfRunning)) {
      job.getCancelledFlag().set(true);
      List list;
      Map map = null;
      synchronized (bundleMap) {
        list = new ArrayList<>(broadcastSet.size() + broadcastMap.size());
        list.addAll(broadcastMap.values());
        list.addAll(broadcastSet);
        map = new HashMap<>(bundleMap);
      }
      for (ClientJob broadcastJob : list) broadcastJob.cancel(mayInterruptIfRunning);
      Set uuids = new HashSet<>();
      for (Map.Entry entry: map.entrySet()) {
        try {
          ChannelWrapper wrapper = entry.getValue();
          wrapper.cancel(entry.getKey());
          if (wrapper instanceof ChannelWrapperRemote) {
            JPPFConnectionPool pool = ((ChannelWrapperRemote) wrapper).getChannel().getConnectionPool();
            String driverUuid = pool.getDriverUuid();
            if (!uuids.contains(driverUuid)) {
              uuids.add(driverUuid);
              try {
                if (debugEnabled) log.debug("sending cancel request for jobId={} to driver={}", this.getUuid(), driverUuid);
                pool.getJmxConnection().cancelJob(this.getUuid());
              } catch(Exception e) {
                if (debugEnabled) log.debug(e.getMessage(), e);
                else log.warn(ExceptionUtils.getMessage(e));
              }
            }
          }
        } catch (Exception e) {
          log.error("Error cancelling job " + this, e);
        }
      }
      boolean empty;
      synchronized (bundleMap) {
        broadcastSet.clear();
        empty = bundleMap.isEmpty() && broadcastMap.isEmpty();
      }
      if (empty) taskCompleted(null, null);
      if (debugEnabled) log.debug("setting cancelled flag on job {}", job);
      return true;
    }
    else return false;
  }

  /**
   * Called when all or part of broadcast job is dispatched to a driver.
   * @param broadcastJob    the dispatched job.
   */
  protected void broadcastDispatched(final ClientJob broadcastJob) {
    if (broadcastJob == null) throw new IllegalArgumentException("broadcastJob is null");
    boolean empty;
    synchronized (bundleMap) {
      broadcastSet.remove(broadcastJob);
      empty = broadcastMap.isEmpty();
      broadcastMap.put(broadcastJob.getBroadcastUUID(), broadcastJob);
    }
    if (empty) {
      updateStatus(NEW, EXECUTING);
      setJobStatus(JobStatus.EXECUTING);
      setExecuting(true);
    }
  }

  /**
   * Called to notify that the execution of broadcast job has completed.
   * @param broadcastJob    the completed job.
   */
  protected void broadcastCompleted(final ClientJob broadcastJob) {
    if (broadcastJob == null) throw new IllegalArgumentException("broadcastJob is null");
    //    if (debugEnabled) log.debug("received " + n + " tasks for node uuid=" + uuid);
    boolean empty;
    synchronized (bundleMap) {
      if ((broadcastMap.remove(broadcastJob.getBroadcastUUID()) != broadcastJob) && !broadcastSet.contains(broadcastJob)) {
        if (debugEnabled) log.debug("broadcast job not found: " + broadcastJob);
      }
      empty = broadcastMap.isEmpty();
    }
    if (empty) taskCompleted(null, null);
  }

  /**
   * Set the requeue handler.
   * @param onRequeue {@link Runnable} executed on requeue.
   */
  public void setOnRequeue(final Runnable onRequeue) {
    if (getSLA().isBroadcastJob()) return; // broadcast jobs cannot be requeued
    this.onRequeue = onRequeue;
  }

  /**
   * Get count of channels on which this job is executed.
   * @return the number used for job execution.
   */
  public int getNbChannels() {
    synchronized (bundleMap) {
      return bundleMap.size();
    }
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('[');
    sb.append("uuid=").append(job.getUuid());
    sb.append(", jobName=").append(job.getName());
    sb.append(", jobStatus=").append(jobStatus);
    sb.append(", broadcastUUID=").append(broadcastUUID);
    sb.append(", executing=").append(executing);
    sb.append(", nbTasks=").append(tasks.size());
    //sb.append(", taskStateMap=").append(taskStateMap);
    sb.append(']');
    return sb.toString();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy