/*******************************************************************************
* Copyright (c) 2013 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.foundation.core.jobs;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.jboss.tools.foundation.core.internal.Trace;
/**
* This class is a Job with the purpose of specifically
* waiting on a barrier array to have a non-null value in the
* index 0 slot, OR, to have a canceled progress monitor.
*
* This job does not guarantee an immediate return when an interrupt is received.
* The job does its best to clean up itself
* after an interrupt is received, but it is up to the delegating runnable
* to handle such interrupts in an appropriate time.
*
* The purpose of this job is to ensure that a workflow exists to return as fast as possible
* when a progress monitor is canceled. This class is most useful for tasks that may block
* for a duration and may not have an opportunity to check for interrupts or a canceled flag.
* This job will segregate those threads into the background and clean them up in time,
* but primarily to ensure the fast return time upon cancelation.
*
* Proper use of this job is as follows:
*
* BarrierProgressWaitJob j = new BarrierProgressWaitJob(name, runnable);
* j.schedule();
* // This join will also poll the provided monitor for cancelations
* j.monitorSafeJoin(monitor);
* if( j.getReturnValue() != null)
* return (ReturnType)j.getReturnValue();
*
* This job guarantees that a call to monitorSafeJoin() will return within 200ms of a
* cancelation in the progress monitor, or immediately upon the completion of the
* delegate runnable's execution, whichever comes first.
*
* In the event that a cancelation is registered, and the delegate runnable
* completes within the 200 ms, the Job may be recognized as both canceled and
* still have a valid return value from the delegate runnable. Clients should
* decide whether they wish to prioritize their logic for a canceled call,
* or use the return value.
*/
public class BarrierProgressWaitJob extends Job {
/**
* An interface for use with this job type.
*/
public interface IRunnableWithProgress {
/**
* Run the given runnable and hold the return value for inspection later.
* @param monitor
* @return
* @throws Exception
*/
public Object run(IProgressMonitor monitor) throws Exception;
}
private IRunnableWithProgress runnable;
private Object[] barrier;
private boolean throwableCaught = false;
public BarrierProgressWaitJob(String name, IRunnableWithProgress runnable) {
super(name);
setSystem(true);
this.runnable = runnable;
barrier = new Object[1];
}
protected IStatus run(IProgressMonitor monitor) {
Trace.trace(Trace.STRING_FINER, "Launching job " + getName() + ", a BarrierProgressWaitJob");
try {
Object ret = runnable.run(monitor);
synchronized(barrier) {
barrier[0] = ret;
barrier.notify();
return Status.OK_STATUS;
}
}catch(Exception e) {
Trace.trace(Trace.STRING_FINER, "Job " + getName() + ", a BarrierProgressWaitJob, failed with exception " + e.getMessage());
this.throwableCaught = true;
synchronized(barrier) {
barrier.notify();
barrier[0] = e;
return Status.OK_STATUS;
}
}
}
/**
* While the Job is running and doing the work, ensure that the progress monitor
* provided as a parameter is respected when canceled.
*
* This method effectively waits for either the Job to complete, or
* for the provided progress monitor to be canceled. In the event of the latter,
*
* This method guarantees a return time equal to that of the delegate method,
* or, a return within 200 ms of the progress monitor being canceled
*
* @param monitor
*/
public void monitorSafeJoin(IProgressMonitor monitor) {
// Since we're waiting for the barrier OR the monitor,
// we must wait only for small intervals, then check the monitor, then loop
// This ensures that if the monitor is canceled, we respond within 200 ms.
boolean done = false;
while( !done ) {
synchronized(barrier) {
try {
barrier.wait(200);
} catch (InterruptedException e) {
}
if( barrier[0] != null || monitor.isCanceled()) {
Trace.trace(Trace.STRING_FINER, "job " + getName() + ", a BarrierProgressWaitJob, is finished due to " +
(barrier[0] != null ? "a non-null result" : "a canceled progress monitor"));
done = true;
}
}
}
// If this monitor is canceled, cancel the job immediately
if( monitor.isCanceled()) {
Trace.trace(Trace.STRING_FINER, "Progress monitor for job " + getName() + ", a BarrierProgressWaitJob, has been canceled");
cancel();
}
}
/**
* This job has been set to canceled. Interrupt the thread doing the work.
*/
protected void canceling() {
Thread t = getThread();
if( t != null )
t.interrupt();
}
/**
* Get any throwable which may have been caught during execution.
* @return
*/
public Throwable getThrowable() {
if( throwableCaught && barrier[0] instanceof Throwable )
return (Exception)barrier[0];
return null;
}
/**
* Get the return value of the delegate runnable
* @return
*/
public Object getReturnValue() {
return throwableCaught ? null : barrier[0];
}
}