/* * ProActive Parallel Suite(TM): * The Open Source library for parallel and distributed * Workflows & Scheduling, Orchestration, Cloud Automation * and Big Data Analysis on Enterprise Grids & Clouds. * * Copyright (c) 2007 - 2017 ActiveEon * Contact: contact@activeeon.com * * This library is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License * as published by the Free Software Foundation: version 3 of * the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * If needed, contact us to obtain a release under GPL Version 2 or 3 * or a different license than the AGPL. */ package functionaltests.monitor; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map.Entry; import java.util.Vector; import org.objectweb.proactive.core.ProActiveTimeoutException; import org.objectweb.proactive.utils.TimeoutAccounter; import org.ow2.proactive.scheduler.common.SchedulerEvent; import org.ow2.proactive.scheduler.common.SchedulerState; import org.ow2.proactive.scheduler.common.job.JobId; import org.ow2.proactive.scheduler.common.job.JobInfo; import org.ow2.proactive.scheduler.common.job.JobState; import org.ow2.proactive.scheduler.common.task.TaskId; import org.ow2.proactive.scheduler.common.task.TaskInfo; import org.ow2.proactive.utils.TaskIdWrapper; /** * This class provides waiting methods on different Scheduler events. * A thread can ask to wait for specific events, related to jobs, tasks * and Scheduler state. If event has already occurred, * waiting methods return immediately, otherwise, waiting is performed. * It provides waiting methods with a timeout too. * * waitForEvent**() methods, this object act as Producer-consumer mechanism; * a Scheduler produces events that are memorized by this object, * and waiting methods waitForEvent**() are as consumer of these event. * It means that an event asked to be waited by a call to waitForEvent**() methods, is removed * after its occurrence. On the contrary, an event is kept till a waitForEvent**() for this event * has been called. * * waitForTerminatedJob() method doesn't act has other waitForEvent**() Methods. * This method deduce a job finished from current Scheduler's job states and received event. * This method can also be used for testing for job submission when killing and restarting * Scheduler. * * @author ProActive team */ public class SchedulerMonitorsHandler { /** * Event concerning SchedulerState (started, killed, etc) * and not yet checked by a waiter. */ private List<SchedulerEvent> schedulerStateEvents; /** * Jobs Events received and not yet checked by a waiter. * (not yet 'consumed') */ private HashMap<JobId, List<JobEventMonitor>> jobsEvents; /** * Tasks events received and not yet checked by a waiter. * (not yet 'consumed') */ private HashMap<TaskIdWrapper, List<TaskEventMonitor>> tasksEvents; /** * Awaited event from Scheduler ; * a list of monitors, used for synchronization with threads that have called waitForEvent**() * methods. These monitors are notified only if corresponding event is thrown by Scheduler */ private ArrayList<EventMonitor> eventsMonitors = null; /** * List of terminated jobs. * */ private List<JobId> finishedJobs; /** * Constructor */ public SchedulerMonitorsHandler() { jobsEvents = new HashMap<>(); tasksEvents = new HashMap<>(); schedulerStateEvents = new ArrayList<>(); finishedJobs = new ArrayList<>(); eventsMonitors = new ArrayList<>(); } /** * * @param state */ public void init(SchedulerState state) { synchronized (this) { for (JobState j : (Vector<JobState>) state.getFinishedJobs()) { this.finishedJobs.add(j.getId()); } } } //---------------------------------------------------------------// // events waiting methods //---------------------------------------------------------------// /** * Wait for event : submission of Job to Scheduler. * @param id JobId to wait for submission * @param timeout max waiting time in milliseconds. * @return A Job representing submitted job * @throws ProActiveTimeoutException if timeout has expired */ public JobState waitForEventJobSubmitted(JobId id, long timeout) throws ProActiveTimeoutException { JobEventMonitor monitor = null; synchronized (this) { monitor = removeJobEvent(id, SchedulerEvent.JOB_SUBMITTED); if (monitor != null) { //event occurred, remove it and return associated Job object return monitor.getJobState(); } monitor = (JobEventMonitor) getMonitor(new JobEventMonitor(SchedulerEvent.JOB_SUBMITTED, id)); } waitWithMonitor(monitor, timeout); return monitor.getJobState(); } /** * Wait for an event related to a job. * Warning : this method must not be called to wait for a job submitted event (because * associated object for this event is different). should use for this event * @{link SchedulerMonitorsHandler.waitForEventJobSubmitted(Id id, long timeout)} * * @param event SchedulerEvent to wait for. * @param id JobId for which event is awaited. * @param timeout max waiting time in milliseconds. * @return JobEvent associated to event (thrown by scheduler) * @throws ProActiveTimeoutException */ public JobInfo waitForEventJob(SchedulerEvent event, JobId id, long timeout) throws ProActiveTimeoutException { JobEventMonitor monitor = null; synchronized (this) { monitor = removeJobEvent(id, event); if (monitor != null) { //event occurred, remove it and return associated Job object return monitor.getJobInfo(); } monitor = (JobEventMonitor) getMonitor(new JobEventMonitor(event, id)); } waitWithMonitor(monitor, timeout); return monitor.getJobInfo(); } /** * Wait for an event related to a task. * @param event SchedulerEvent to wait for ; an event related to Task * @param jobId task's corresponding jobId * @param taskName for which event is awaited * @param timeout max waiting time in milliseconds. * @return TaskEvent associated to event (thrown by scheduler) * @throws ProActiveTimeoutException */ public TaskInfo waitForEventTask(SchedulerEvent event, JobId jobId, String taskName, long timeout) throws ProActiveTimeoutException { TaskEventMonitor monitor = null; synchronized (this) { monitor = removeTaskEvent(jobId, taskName, event); if (monitor != null) { //job is already finished, no need to wait return monitor.getTaskInfo(); } monitor = (TaskEventMonitor) getMonitor(new TaskEventMonitor(event, jobId, taskName)); } waitWithMonitor(monitor, timeout); return monitor.getTaskInfo(); } /** * Wait for an event regarding Scheduler state : started, resumed, stopped... * @param event awaited event. * @param timeout in milliseconds * @throws ProActiveTimeoutException if timeout is reached */ public void waitForEventSchedulerState(SchedulerEvent event, long timeout) throws ProActiveTimeoutException { EventMonitor monitor = null; synchronized (this) { if (schedulerStateEvents.contains(event)) { schedulerStateEvents.remove(event); return; } monitor = getMonitor(new EventMonitor(event)); } waitWithMonitor(monitor, timeout); } /** * Wait for job a Job finished. * This method act differently from other waitForEvent**() methods ; * it doesn't wait strictly Job finished event. if job is already * on Scheduler's finished jobs list, then methods returns. * Otherwise, a wait for is performed. * This method corresponds to the running to finished transition * * @param id JobId representing the job awaited to be finished. * @param timeout in milliseconds * @throws ProActiveTimeoutException if timeout is reached */ public void waitForFinishedJob(JobId id, long timeout) throws ProActiveTimeoutException { EventMonitor monitor = null; synchronized (this) { if (this.finishedJobs.contains(id)) { return; } else { monitor = getMonitor(new JobEventMonitor(SchedulerEvent.JOB_RUNNING_TO_FINISHED, id)); } } waitWithMonitor(monitor, timeout); } //---------------------------------------------------------------// //private methods // these methods MUST be called from a synchronized(this) block //---------------------------------------------------------------// /** * Add a job event to the list of occurred events (memorize it). * @param event type of event occurred. * @param jInfo associated JobEvent object to event occurred. */ private void addJobEvent(SchedulerEvent event, JobInfo jInfo) { if (!jobsEvents.containsKey(jInfo.getJobId())) { List<JobEventMonitor> list = new ArrayList<>(); jobsEvents.put(jInfo.getJobId(), list); } jobsEvents.get(jInfo.getJobId()).add(new JobEventMonitor(event, jInfo)); } /** * Add a job Event to the list of occurred events (memorize it). * Here object associated to Event is a Job and not a Job event because some * event (as Job submitted event have a Job object associated). * @param event type of event occurred. * @param jState associated Job object to the event */ private void addJobEvent(SchedulerEvent event, JobState jState) { if (!jobsEvents.containsKey(jState.getId())) { List<JobEventMonitor> list = new ArrayList<>(); jobsEvents.put(jState.getId(), list); } jobsEvents.get(jState.getId()).add(new JobEventMonitor(event, jState)); } /** * Add a Task Event to the list of occurred events (memorize it). * @param event type of task event * @param taskInfo TaskEvent object associated to Event */ private void addTaskEvent(SchedulerEvent event, TaskInfo taskInfo) { TaskIdWrapper taskId = TaskIdWrapper.wrap(taskInfo.getTaskId()); List<TaskEventMonitor> taskEventMonitors = tasksEvents.get(taskId); if (taskEventMonitors == null) { taskEventMonitors = new ArrayList<>(); tasksEvents.put(taskId, taskEventMonitors); } taskEventMonitors.add(new TaskEventMonitor(event, taskInfo)); } /** * Remove, if exist a JobEventMonitor to the list of memorized job events * @param id JobId object representing a specific job to look for. * @param event type to look for * @return removed JobEventMonitor, or null if not exists. */ private JobEventMonitor removeJobEvent(JobId id, SchedulerEvent event) { JobEventMonitor tmp = new JobEventMonitor(event, id); if (jobsEvents.containsKey(id) && jobsEvents.get(id).contains(tmp)) { return jobsEvents.get(id).remove((jobsEvents.get(id).indexOf(tmp))); } else return null; } /** * Remove, if exist a TaskEventMonitor to the list of memorized tasks events * @param id * @param taskName * @param event * @return */ private TaskEventMonitor removeTaskEvent(JobId id, String taskName, SchedulerEvent event) { TaskEventMonitor tmp = new TaskEventMonitor(event, id, taskName); for (Entry<TaskIdWrapper, List<TaskEventMonitor>> entry : tasksEvents.entrySet()) { TaskId taskId = entry.getKey().getTaskId(); List<TaskEventMonitor> value = entry.getValue(); if (taskId.getJobId().equals(id) && taskId.getReadableName().equals(taskName) && value.contains(tmp)) { return value.remove(value.indexOf(tmp)); } } return null; } /** * Notify threads, if any that wait for a specific event * * @param monitor TaskEventMonitor object to look and notify if found * @return true if a monitor has been found and notification has been performed, false otherwise */ private boolean lookAndNotifyMonitor(EventMonitor monitor) { EventMonitor monitorToNotify = this.getAndRemoveMonitor(monitor); if (monitorToNotify != null) { synchronized (monitorToNotify) { //monitor exists, maybe created by a waiter that has reached timeout //so check if it has been timeouted if (!monitorToNotify.isTimeouted()) { notifyMonitor(monitorToNotify); return true; } } } return false; } /** * Notify threads, if any, that are waiting for a specific Job event. Set Job or JobEvent * (thrown by Scheduler) to associated JobEventMonitor * @param monitor * @return */ private boolean lookAndNotifyMonitor(JobEventMonitor monitor) { JobEventMonitor monitorToNotify = (JobEventMonitor) getAndRemoveMonitor(monitor); if (monitorToNotify != null) { synchronized (monitorToNotify) { //monitor exists, maybe created by a waiter that has reached timeout //so check if it has been timeouted if (!monitorToNotify.isTimeouted()) { //set JobEvent (if any) to monitor to notify monitorToNotify.setJobInfo(monitor.getJobInfo()); //set Job (if any) to monitor to notify monitorToNotify.setJobState(monitor.getJobState()); notifyMonitor(monitorToNotify); return true; } } } return false; } /** * Notify threads, if any, that are waiting for a specific task event. Set TaskEvent * (thrown by Scheduler) to associated TaskEventMonitor. * @param monitor * @return if monitors has been notified, false otherwise */ private boolean lookAndNotifyMonitor(TaskEventMonitor monitor) { TaskEventMonitor monitorToNotify = (TaskEventMonitor) getAndRemoveMonitor(monitor); if (monitorToNotify != null) { synchronized (monitorToNotify) { //monitor exists, maybe created by a waiter that has reached timeout //so check if it has been timeout if (!monitorToNotify.isTimeouted()) { monitorToNotify.setTaskInfo(monitor.getTaskInfo()); notifyMonitor(monitorToNotify); return true; } } } return false; } /** * Return an EventMonitor that is currently used to wait for an event, or null if * this event is awaited by no one. * @param monitor EventMonitor Object representing Event to look for. * @return an eventMonitor object to notify, or null. */ private EventMonitor getAndRemoveMonitor(EventMonitor monitor) { if (eventsMonitors.contains(monitor)) { return eventsMonitors.remove(eventsMonitors.indexOf(monitor)); } return null; } /** * Returns a monitor used to wait for a specific event that hasn't yet occurred. * If there is not yet a monitor for this event, EventMonitor passed in parameter is * used as Monitor object for this event. * @param monitor representing event to wait for. * @return an EventMonitorJob to use as waiting Monitor. */ private EventMonitor getMonitor(EventMonitor monitor) { if (!eventsMonitors.contains(monitor)) { eventsMonitors.add(monitor); return monitor; } else { return eventsMonitors.get(eventsMonitors.indexOf(monitor)); } } /** * Notify an EventMonitor object, i.e resume threads that have perform * a wait on EventMonitor object passed in parameter. * @param monitorToNotify EventMonitor to notify. */ private void notifyMonitor(EventMonitor monitorToNotify) { // System.out.println("==========================================="); // System.out.println("NOTIFYING FOR EVENT : " + monitorToNotify.getWaitedEvent()); // System.out.println("==========================================="); synchronized (monitorToNotify) { monitorToNotify.setEventOccured(); monitorToNotify.notify(); } } //---------------------------------------------------------------// //private methods // these methods MUST NOT be called from a synchronized(this) block //---------------------------------------------------------------// private void waitWithMonitor(EventMonitor monitor, long timeout) throws ProActiveTimeoutException { TimeoutAccounter counter = TimeoutAccounter.getAccounter(timeout); synchronized (monitor) { monitor.setTimeouted(false); while (!counter.isTimeoutElapsed()) { if (monitor.eventOccured()) return; try { //System.out.println("I AM WAITING FOR EVENT : " + monitor.getWaitedEvent() + " during " + // counter.getRemainingTimeout()); monitor.wait(counter.getRemainingTimeout()); } catch (InterruptedException e) { //spurious wake-up, nothing to do e.printStackTrace(); } } monitor.setTimeouted(true); } throw new ProActiveTimeoutException("timeout elapsed"); } //---------------------------------------------------------------// //Method called by SchedulerEventListener //---------------------------------------------------------------// /** * Memorize or notify a waiter for a new job event received * * @param event Job event type * @param jInfo event's associated JobInfo object */ public void handleJobEvent(SchedulerEvent event, JobInfo jInfo) { synchronized (this) { if (event.equals(SchedulerEvent.JOB_RUNNING_TO_FINISHED) || event.equals(SchedulerEvent.JOB_PENDING_TO_FINISHED)) { this.finishedJobs.add(jInfo.getJobId()); } if (!lookAndNotifyMonitor(new JobEventMonitor(event, jInfo))) { //no monitor notified, memorize event. addJobEvent(event, jInfo); } } } /** * Memorize or notify a waiter for a new job event received * * @param event Job event type * @param jState event's associated state object */ public void handleJobEvent(SchedulerEvent event, JobState jState) { synchronized (this) { if (event.equals(SchedulerEvent.JOB_RUNNING_TO_FINISHED) || event.equals(SchedulerEvent.JOB_PENDING_TO_FINISHED)) { this.finishedJobs.add(jState.getId()); } if (!lookAndNotifyMonitor(new JobEventMonitor(event, jState))) { //no monitor notified, memorize event. addJobEvent(event, jState); } } } /** * Memorize or notify a waiter for a new task event received * @param event task event type * @param tInfo event's associated Job object */ public void handleTaskEvent(SchedulerEvent event, TaskInfo tInfo) { synchronized (this) { if (!lookAndNotifyMonitor(new TaskEventMonitor(event, tInfo))) { //no monitor notified, memorize event. addTaskEvent(event, tInfo); } } } /** * * @param event */ public void handleSchedulerStateEvent(SchedulerEvent event) { synchronized (this) { if (!lookAndNotifyMonitor(new EventMonitor(event))) { schedulerStateEvents.add(event); } } } }