blob: f741f5d137ed60aa095f4f5424904da4be90cd2e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2015 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.core.tests.resources.usecase;
import org.eclipse.core.internal.resources.ResourceStatus;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.runtime.*;
/**
* A class to help testing use cases that access the workspace concurrently but in
* a deterministic way.
* Operations can be defined with some sync points. It means that the operation will
* run to that point and wait for some other thread signal (call #proceed) before continuing.
*
* Some of the constructs can lead to deadlocks if not used correctly.
*
* One tip to avoid deadlocks is aways honour the sync points defined by the operation.
* For example, exceptions should be logged using #logException instead of thrown.
*
* Although operations of this type can run using IWorkspace.run(), it was designed
* to run in a separate thread.
* Example: new Thread(concurrentOperation).start();
*/
public abstract class ConcurrentOperation implements Runnable, IWorkspaceRunnable {
/** workspace */
protected IWorkspace workspace;
/** synchronization flags */
protected boolean go;
protected boolean isWaiting;
protected int hasStarted;
/** log any exception we get */
protected MultiStatus status;
/** locks */
protected Object startedLock = new Object();
/** constants */
protected static final int STARTED_NONE = 0;
protected static final int STARTED_YES = 1;
protected static final int STARTED_NO = 2;
public ConcurrentOperation(IWorkspace workspace) {
this.workspace = workspace;
reset();
}
/**
* This method should verify all pre-requisites necessaries in order
* to the operation run properly.
*/
abstract protected void assertRequisites() throws Exception;
public IStatus getStatus() {
return status;
}
/**
* Returns only when we get out of the STARTED_NONE state.
*/
public boolean hasStarted() {
synchronized (startedLock) {
while (hasStarted == STARTED_NONE) {
try {
startedLock.wait();
} catch (InterruptedException e) {
// ignore
}
}
}
return hasStarted == STARTED_YES;
}
protected boolean isReadyToStart() {
boolean ok = true;
try {
assertRequisites();
} catch (Exception e) {
logException(e);
ok = false;
}
return ok;
}
protected void logException(Exception e) {
if (e instanceof CoreException) {
status.add(((CoreException) e).getStatus());
} else {
status.add(new ResourceStatus(0, null, null, e));
}
}
/**
* @see #waitNotification
*/
public synchronized void proceed() {
go = true;
notify();
}
public void reset() {
go = false;
isWaiting = false;
hasStarted = STARTED_NONE;
status = new MultiStatus("a plugin", IStatus.INFO, "", null);
}
/**
* Only returns from this method if the operation is in
* a sync point.
*
* This method can cause deadlock.
*/
public synchronized void returnWhenInSyncPoint() {
while (!isWaiting && hasStarted()) {
try {
wait();
} catch (InterruptedException e) {
// ignore
}
}
}
@Override
public void run() {
if (isReadyToStart()) {
setHasStarted(true);
try {
workspace.run(this, null);
} catch (Exception e) {
logException(e);
}
} else {
setHasStarted(false);
}
}
protected void setHasStarted(boolean value) {
synchronized (startedLock) {
hasStarted = value ? STARTED_YES : STARTED_NO;
startedLock.notify();
}
}
/**
* Waits until #proceed is called.
*
* This method can cause deadlock.
*/
protected synchronized void syncPoint() {
/* we set "go" to false because we want to continue only when proceed is called */
go = false;
isWaiting = true;
notify(); // notify we are in a sync point
while (!go) {
try {
wait();
} catch (InterruptedException e) {
// ignore
}
}
isWaiting = false;
}
}