blob: 0d2c07956617de31c2f9484dddba4a16243c61b3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2017 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 - Initial API and implementation
*******************************************************************************/
package org.eclipse.core.tests.internal.resources;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.tests.harness.CancelingProgressMonitor;
import org.eclipse.core.tests.harness.TestBarrier;
import org.eclipse.core.tests.resources.ResourceTest;
/**
* Tests concurrency issues when dealing with operations on the workspace
*/
public class WorkspaceConcurrencyTest extends ResourceTest {
public static Test suite() {
return new TestSuite(WorkspaceConcurrencyTest.class);
}
public WorkspaceConcurrencyTest() {
super("");
}
public WorkspaceConcurrencyTest(String name) {
super(name);
}
private void sleep(long duration) {
try {
Thread.sleep(duration);
} catch (InterruptedException e) {
//ignore
}
}
public void testEndRuleInWorkspaceOperation() {
try {
final IProject project = getWorkspace().getRoot().getProject("testEndRuleInWorkspaceOperation");
getWorkspace().run((IWorkspaceRunnable) monitor -> Job.getJobManager().endRule(project), project, IResource.NONE, getMonitor());
//should have failed
fail("1.0");
} catch (CoreException e) {
fail("1.99", e);
} catch (RuntimeException e) {
//expected
}
}
/**
* Tests that it is possible to cancel a workspace operation when it is blocked
* by activity in another thread. This is a regression test for bug 56118.
*/
public void testCancelOnBlocked() {
//create a dummy project
ensureExistsInWorkspace(getWorkspace().getRoot().getProject("P1"), true);
//add a resource change listener that blocks forever, thus
//simulating a scenario where workspace lock is held indefinitely
final int[] barrier = new int[1];
final Throwable[] error = new Throwable[1];
IResourceChangeListener listener = event -> {
//block until we are told to do otherwise
barrier[0] = TestBarrier.STATUS_START;
try {
TestBarrier.waitForStatus(barrier, TestBarrier.STATUS_DONE);
} catch (Throwable e) {
error[0] = e;
}
};
getWorkspace().addResourceChangeListener(listener);
try {
//create a thread that modifies the workspace. This should
//hang indefinitely due to the misbehaving listener
TestWorkspaceJob testJob = new TestWorkspaceJob(10);
testJob.setTouch(true);
testJob.setRule(getWorkspace().getRoot());
testJob.schedule();
//wait until blocked on the listener
TestBarrier.waitForStatus(barrier, TestBarrier.STATUS_START);
//create a second thread that attempts to modify the workspace, but immediately
//cancels itself. This thread should terminate immediately with a cancelation exception
final boolean[] canceled = new boolean[] {false};
Thread t2 = new Thread(() -> {
try {
getWorkspace().run((IWorkspaceRunnable) monitor -> {
//no-op
}, new CancelingProgressMonitor());
} catch (CoreException e1) {
fail("1.99", e1);
} catch (OperationCanceledException e2) {
canceled[0] = true;
}
});
t2.start();
try {
t2.join();
} catch (InterruptedException e) {
fail("1.88", e);
}
//should have canceled
assertTrue("2.0", canceled[0]);
//finally release the listener and ensure the first thread completes
barrier[0] = TestBarrier.STATUS_DONE;
try {
testJob.join();
} catch (InterruptedException e1) {
//ignore
}
if (error[0] != null) {
fail("3.0", error[0]);
}
} finally {
getWorkspace().removeResourceChangeListener(listener);
}
}
/**
* Tests calling IWorkspace.run with a non-workspace rule. This should be
* allowed. This is a regression test for bug 60114.
*/
public void testRunnableWithOtherRule() {
ISchedulingRule rule = new ISchedulingRule() {
@Override
public boolean contains(ISchedulingRule rule) {
return rule == this;
}
@Override
public boolean isConflicting(ISchedulingRule rule) {
return rule == this;
}
};
try {
getWorkspace().run((IWorkspaceRunnable) monitor -> {
//noop
}, rule, IResource.NONE, getMonitor());
} catch (CoreException e) {
fail("1.99", e);
}
}
/**
* Tests three overlapping jobs
* - Job 1 (root rule) does a build.
* - Job 2 (null rule) overlaps Job 1 and has null scheduling rule
* - Job 3 (project rule) overlaps Job 2
*
* The POST_BUILD event should occur at the end of Job 1. If it
* is delayed until the end of Job 3, an appropriate scheduling rule
* will not be available and it will fail.
* This is a regression test for bug 62927.
*/
public void testRunWhileBuilding() {
final IWorkspace workspace = ResourcesPlugin.getWorkspace();
//create a POST_BUILD listener that will touch a project
final IProject touch = workspace.getRoot().getProject("ToTouch");
final IProject rule = workspace.getRoot().getProject("jobThree");
final IFile ruleFile = rule.getFile("somefile.txt");
ensureExistsInWorkspace(rule, true);
ensureExistsInWorkspace(touch, true);
ensureExistsInWorkspace(ruleFile, true);
final Throwable[] failure = new Throwable[1];
IResourceChangeListener listener = event -> {
try {
touch.touch(null);
} catch (CoreException e1) {
failure[0] = e1;
} catch (RuntimeException e2) {
failure[0] = e2;
}
};
workspace.addResourceChangeListener(listener, IResourceChangeEvent.POST_BUILD);
try {
//create one job that does a build, and then waits
final int[] status = new int[3];
Job jobOne = new Job("jobOne") {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
workspace.run((IWorkspaceRunnable) monitor1 -> {
//do a build
workspace.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor1);
//signal that the job has done the build
status[0] = TestBarrier.STATUS_RUNNING;
//wait for job two to start
TestBarrier.waitForStatus(status, 0, TestBarrier.STATUS_WAIT_FOR_DONE);
}, null);
} catch (CoreException e) {
return e.getStatus();
}
return Status.OK_STATUS;
}
};
//schedule and wait for job one to start
jobOne.schedule();
TestBarrier.waitForStatus(status, 0, TestBarrier.STATUS_RUNNING);
//create job two that does an empty workspace operation
Job jobTwo = new Job("jobTwo") {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
workspace.run((IWorkspaceRunnable) monitor1 -> {
//signal that this job has started
status[1] = TestBarrier.STATUS_RUNNING;
//let job one finish
status[0] = TestBarrier.STATUS_WAIT_FOR_DONE;
//wait for job three to start
TestBarrier.waitForStatus(status, 1, TestBarrier.STATUS_WAIT_FOR_DONE);
}, null, IResource.NONE, null);
} catch (CoreException e) {
return e.getStatus();
}
return Status.OK_STATUS;
}
};
jobTwo.schedule();
//create job three that has a non-null rule
Job jobThree = new Job("jobThree") {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
workspace.run((IWorkspaceRunnable) monitor1 -> {
//signal that this job has started
status[2] = TestBarrier.STATUS_RUNNING;
//let job two finish
status[1] = TestBarrier.STATUS_WAIT_FOR_DONE;
//ensure this job does something so the build listener runs
ruleFile.touch(null);
//wait for the ok to complete
TestBarrier.waitForStatus(status, 2, TestBarrier.STATUS_WAIT_FOR_DONE);
}, workspace.getRuleFactory().modifyRule(ruleFile), IResource.NONE, null);
} catch (CoreException e) {
return e.getStatus();
}
return Status.OK_STATUS;
}
};
jobThree.schedule();
//wait for job two to complete
waitForCompletion(jobTwo);
//let job three complete
status[2] = TestBarrier.STATUS_WAIT_FOR_DONE;
//wait for job three to complete
waitForCompletion(jobThree);
//ensure no jobs failed
IStatus result = jobOne.getResult();
if (!result.isOK()) {
fail("1.0", new CoreException(result));
}
result = jobTwo.getResult();
if (!result.isOK()) {
fail("1.1", new CoreException(result));
}
result = jobThree.getResult();
if (!result.isOK()) {
fail("1.2", new CoreException(result));
}
if (failure[0] != null) {
fail("1.3", failure[0]);
}
} finally {
//ensure listener is removed
workspace.removeResourceChangeListener(listener);
}
}
private void waitForCompletion(Job job) {
int i = 0;
while (job.getState() != Job.NONE) {
sleep(100);
//sanity test to avoid hanging tests
assertTrue("Timeout waiting for job to complete", i++ < 1000);
}
}
}