blob: bd84632fe4630ef4b944661e2d0cc6d06494c2f0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 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.runtime.jobs;
import java.util.*;
import java.util.concurrent.Semaphore;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.*;
import org.eclipse.core.tests.harness.TestBarrier;
import org.eclipse.core.tests.harness.TestJob;
/**
* Tests for {@link Job#yieldRule(IProgressMonitor)}.
*/
public class YieldTest extends AbstractJobManagerTest {
class TestJobListener extends JobChangeAdapter {
private Set<Job> scheduled = Collections.synchronizedSet(new HashSet<Job>());
public void cancelAllJobs() {
Job[] jobs = scheduled.toArray(new Job[0]);
for (Job job : jobs) {
job.cancel();
}
}
@Override
public void done(IJobChangeEvent event) {
synchronized (YieldTest.this) {
if (scheduled.remove(event.getJob())) {
//wake up the waitForCompletion method
completedJobs++;
YieldTest.this.notify();
}
}
}
@Override
public void scheduled(IJobChangeEvent event) {
Job job = event.getJob();
synchronized (YieldTest.this) {
if (job instanceof TestJob) {
scheduledJobs++;
scheduled.add(job);
}
}
}
}
protected int completedJobs;
private IJobChangeListener[] jobListeners;
protected int scheduledJobs;
public static Test suite() {
return new TestSuite(YieldTest.class);
// TestSuite suite = new TestSuite();
// for (int i = 0; i < 100; i++) {
// suite.addTest(new YieldTest("testYieldJobToJobsInterleaved"));
// }
// return suite;
}
public YieldTest(String name) {
super(name);
}
@Override
protected void setUp() throws Exception {
super.setUp();
completedJobs = 0;
scheduledJobs = 0;
jobListeners = new IJobChangeListener[] {/* new VerboseJobListener(),*/
new TestJobListener()};
for (IJobChangeListener jobListener : jobListeners) {
manager.addJobChangeListener(jobListener);
}
}
@Override
protected void tearDown() throws Exception {
for (IJobChangeListener jobListener : jobListeners) {
if (jobListener instanceof TestJobListener) {
((TestJobListener) jobListener).cancelAllJobs();
}
}
waitForCompletion();
for (IJobChangeListener jobListener : jobListeners) {
manager.removeJobChangeListener(jobListener);
}
super.tearDown();
}
public void testExceptionWhenYieldingNotOwner() {
int[] location = new int[2];
final TestBarrier barrier1 = new TestBarrier(location, 0);
final TestBarrier barrier2 = new TestBarrier(location, 1);
final Job yielding = new Job(getName() + " Yielding") {
@Override
protected IStatus run(IProgressMonitor monitor) {
barrier2.setStatus(TestBarrier.STATUS_START);
barrier1.waitForStatus(TestBarrier.STATUS_START);
return Status.OK_STATUS;
}
};
Job otherJob = new Job(getName() + " ShouldFault") {
@Override
protected IStatus run(IProgressMonitor monitor) {
barrier2.waitForStatus(TestBarrier.STATUS_START);
try {
yielding.yieldRule(null);
} catch (IllegalArgumentException e) {
//expected
return new Status(IStatus.ERROR, "org.eclipse.core.tests.runtime", "Expected failure");
} finally {
barrier1.setStatus(TestBarrier.STATUS_START);
}
return Status.OK_STATUS;
}
};
yielding.schedule();
otherJob.schedule();
waitForCompletion(otherJob);
assertTrue(!otherJob.getResult().isOK());
waitForCompletion(yielding);
}
public void testExceptionWhenYieldingNotRunning() {
final Job yielding = new Job(getName() + " Yielding") {
@Override
protected IStatus run(IProgressMonitor monitor) {
return Status.OK_STATUS;
}
};
try {
yielding.yieldRule(null);
fail("Did not throw exception");
} catch (IllegalArgumentException e) {
// ignore
}
}
public void testThreadRestored() {
final PathRule rule = new PathRule(getName());
final Job[] jobs = new Job[2];
Job yieldJob = new Job(getName() + " Yielding") {
@Override
protected IStatus run(IProgressMonitor monitor) {
assertNull("Conflicting job result is not null: " + jobs[1].getResult(), jobs[1].getResult());
Thread before = getThread();
while (yieldRule(null) == null) {
//loop until yield succeeds
}
waitForCompletion(jobs[1]);
Thread after = getThread();
assertEquals("Thread not restored", before, after);
assertTrue("Conflicting job not done", jobs[1].getResult().isOK());
assertTrue("Conflicting job still running", jobs[1].getState() == Job.NONE);
return Status.OK_STATUS;
}
};
jobs[0] = yieldJob;
yieldJob.setRule(rule);
Job conflictingJob = new Job(getName() + " Conflicting") {
@Override
protected IStatus run(IProgressMonitor monitor) {
assertTrue(jobs[0].getState() == WAITING);
assertTrue(jobs[0].getResult() == null);
return Status.OK_STATUS;
}
};
jobs[1] = conflictingJob;
conflictingJob.setRule(rule);
yieldJob.schedule();
conflictingJob.schedule();
waitForCompletion(yieldJob);
assertTrue("Result is not ok: " + yieldJob.getResult(), yieldJob.getResult().isOK());
waitForCompletion(conflictingJob);
assertTrue(conflictingJob.getResult().isOK());
}
public void testYieldJobToJob() {
final PathRule rule = new PathRule(getName());
final Job[] jobs = new Job[2];
Job yieldJob = new Job(getName() + " Yielding") {
@Override
protected IStatus run(IProgressMonitor monitor) {
assertTrue(jobs[1].getResult() == null);
while (yieldRule(null) == null) {
//loop until yield succeeds
}
waitForCompletion(jobs[1]);
assertTrue(jobs[1].getResult().isOK());
assertTrue(jobs[1].getState() == Job.NONE);
return Status.OK_STATUS;
}
};
jobs[0] = yieldJob;
yieldJob.setRule(rule);
Job conflictingJob = new Job(getName() + " Conflicting") {
@Override
protected IStatus run(IProgressMonitor monitor) {
assertEquals(WAITING, jobs[0].getState());
assertTrue(jobs[0].getResult() == null);
return Status.OK_STATUS;
}
};
jobs[1] = conflictingJob;
conflictingJob.setRule(rule);
yieldJob.schedule();
conflictingJob.schedule();
waitForCompletion(yieldJob);
final IStatus yieldResult = yieldJob.getResult();
if (!yieldResult.isOK()) {
Throwable t = yieldResult.getException();
if (t != null) {
fail("yieldJob failed", t);
}
fail("yieldJob failed:" + yieldResult);
}
waitForCompletion(conflictingJob);
assertTrue(conflictingJob.getResult().isOK());
}
// public void testILockTransfer() {
// final PathRule rule = new PathRule("testYield");
// final ILock lock = Job.getJobManager().newLock();
// final Job[] jobs = new Job[2];
// final IStatus[] results = new IStatus[2];
// Job yieldJob = new Job("Yielding") {
// protected IStatus run(IProgressMonitor monitor) {
// assertTrue(results[1] == null);
// assertTrue(jobs[1].getState() == Job.WAITING);
// lock.acquire();
// while (true)
// if (yield())
// break;
// assertTrue(results[1].isOK());
// assertTrue(jobs[1].getState() == Job.NONE);
// return Status.OK_STATUS;
// }
// };
// jobs[0] = yieldJob;
// yieldJob.setRule(rule);
// yieldJob.addJobChangeListener(new YieldJobListener(results, 0));
//
// Job conflictingJob = new Job("Conflicting") {
// protected IStatus run(IProgressMonitor monitor) {
// lock.acquire();
// assertTrue(jobs[0].getState() == WAITING);
// assertTrue(results[0] == null);
// return Status.OK_STATUS;
// }
// };
// jobs[1] = conflictingJob;
// conflictingJob.setRule(rule);
// conflictingJob.addJobChangeListener(new YieldJobListener(results, 1));
//
// yieldJob.schedule();
// conflictingJob.schedule();
// waitForCompletion(yieldJob);
// assertTrue(yieldJob.getResult().isOK());
// waitForCancel(conflictingJob);
// assertTrue(conflictingJob.getResult().isOK());
// }
public void testYieldJobToJobAndEnsureConflictingRunsBeforeResume() {
final PathRule rule = new PathRule(getName());
int[] location = new int[2];
final TestBarrier barrier1 = new TestBarrier(location, 0);
final Job[] jobs = new Job[2];
Job yieldJob = new Job(getName() + " Yielding") {
@Override
protected IStatus run(IProgressMonitor monitor) {
barrier1.waitForStatus(TestBarrier.STATUS_START);
barrier1.setStatus(TestBarrier.STATUS_RUNNING);
while (yieldRule(null) == null) {
//loop until yield succeeds
}
barrier1.waitForStatus(TestBarrier.STATUS_DONE);
return Status.OK_STATUS;
}
};
jobs[0] = yieldJob;
yieldJob.setRule(rule);
final Job conflictingJob1 = new Job(getName() + " Conflicting") {
@Override
protected IStatus run(IProgressMonitor monitor) {
barrier1.setStatus(TestBarrier.STATUS_DONE);
return Status.OK_STATUS;
}
};
conflictingJob1.setRule(rule);
Job nonConflict = new Job(getName() + " Non-conflicting") {
@Override
protected IStatus run(IProgressMonitor monitor) {
return Status.OK_STATUS;
}
};
yieldJob.schedule();
conflictingJob1.schedule();
barrier1.setStatus(TestBarrier.STATUS_START);
// we are testing a race-condition here... (This test will not catch all conditions)
// make sure starting a non-conflicting job does not cause
// the yielding job to resume before conflicting job has a chance to run
nonConflict.schedule(1);
waitForCompletion(yieldJob);
waitForCompletion(conflictingJob1);
waitForCompletion(nonConflict);
assertTrue(yieldJob.getResult().isOK());
assertTrue(conflictingJob1.getResult().isOK());
assertTrue(nonConflict.getResult().isOK());
}
public void testYieldJobToThread() {
final PathRule rule = new PathRule(getName());
final TestBarrier barrier = new TestBarrier();
final Job yielding = new Job(getName() + " Yielding") {
@Override
protected IStatus run(IProgressMonitor monitor) {
barrier.setStatus(TestBarrier.STATUS_START);
while (yieldRule(null) == null) {
//loop until yield succeeds
}
// ensure that the other job ran
barrier.waitForStatus(TestBarrier.STATUS_DONE);
return Status.OK_STATUS;
}
};
yielding.setRule(rule);
Thread t = new Thread(getName() + " Conflicting") {
@Override
public void run() {
try {
Job.getJobManager().beginRule(rule, null);
barrier.setStatus(TestBarrier.STATUS_DONE);
} finally {
Job.getJobManager().endRule(rule);
}
}
};
yielding.schedule();
barrier.waitForStatus(TestBarrier.STATUS_START);
t.start();
waitForCompletion(yielding);
assertTrue(yielding.getResult().isOK());
}
public void testYieldThreadJobToThread() {
final PathRule rule = new PathRule(getName());
final TestBarrier barrier = new TestBarrier();
final Job yielding = new Job(getName() + " Yielding") {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
Job.getJobManager().beginRule(rule, null);
barrier.setStatus(TestBarrier.STATUS_START);
while (yieldRule(null) == null) {
//loop until yield succeeds
}
// ensure that the other job ran
barrier.waitForStatus(TestBarrier.STATUS_DONE);
} finally {
Job.getJobManager().endRule(rule);
}
return Status.OK_STATUS;
}
};
Thread t = new Thread(getName() + " Conflicting") {
@Override
public void run() {
try {
Job.getJobManager().beginRule(rule, null);
barrier.setStatus(TestBarrier.STATUS_DONE);
} finally {
Job.getJobManager().endRule(rule);
}
}
};
yielding.schedule();
barrier.waitForStatus(TestBarrier.STATUS_START);
t.start();
waitForCompletion(yielding, 5000);
assertTrue(yielding.getResult().isOK());
}
public void testYieldThreadToJob() {
final PathRule rule = new PathRule(getName());
final TestBarrier barrier = new TestBarrier();
final Thread t = new Thread() {
@Override
public void run() {
IJobManager m = Job.getJobManager();
try {
m.beginRule(rule, null);
Job j = m.currentJob();
barrier.setStatus(TestBarrier.STATUS_START);
barrier.waitForStatus(TestBarrier.STATUS_WAIT_FOR_START);
while (j.yieldRule(null) == null) {
//loop until yield succeeds
}
// ensure that the other job ran
barrier.waitForStatus(TestBarrier.STATUS_DONE);
} finally {
m.endRule(rule);
barrier.setStatus(TestBarrier.STATUS_WAIT_FOR_DONE);
}
}
};
final Job conflictingJob = new Job(getName() + " Conflicting") {
@Override
protected IStatus run(IProgressMonitor monitor) {
barrier.setStatus(TestBarrier.STATUS_DONE);
return Status.OK_STATUS;
}
};
t.start();
barrier.waitForStatus(TestBarrier.STATUS_START);
conflictingJob.setRule(rule);
conflictingJob.schedule();
barrier.setStatus(TestBarrier.STATUS_WAIT_FOR_START);
barrier.waitForStatus(TestBarrier.STATUS_WAIT_FOR_DONE);
waitForCompletion(conflictingJob);
assertTrue(conflictingJob.getResult().isOK());
}
public void testYieldThreadToThreadJob() {
final PathRule rule = new PathRule(getName());
final TestBarrier barrier = new TestBarrier();
Thread t = new Thread(getName() + " Yielding") {
@Override
public void run() {
try {
Job.getJobManager().beginRule(rule, null);
barrier.setStatus(TestBarrier.STATUS_START);
while (Job.getJobManager().currentJob().yieldRule(null) == null) {
//loop until yield succeeds
}
// ensure that the other job ran
barrier.waitForStatus(TestBarrier.STATUS_DONE);
} finally {
Job.getJobManager().endRule(rule);
}
// status code irrelevant
barrier.setStatus(TestBarrier.STATUS_BLOCKED);
}
};
final Job conflicting = new Job(getName() + " Conflicting") {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
Job.getJobManager().beginRule(rule, null);
} finally {
Job.getJobManager().endRule(rule);
}
barrier.setStatus(TestBarrier.STATUS_DONE);
return Status.OK_STATUS;
}
};
t.start();
barrier.waitForStatus(TestBarrier.STATUS_START);
conflicting.schedule();
waitForCompletion(conflicting, 5000);
assertTrue(conflicting.getResult().isOK());
barrier.waitForStatus(TestBarrier.STATUS_BLOCKED);
}
private synchronized void waitForCompletion() {
int i = 0;
assertTrue("Jobs completed that weren't scheduled", completedJobs <= scheduledJobs);
while (completedJobs < scheduledJobs) {
try {
wait(500);
} catch (InterruptedException e) {
//ignore
}
//sanity test to avoid hanging tests
if (i++ > 1000) {
dumpState();
assertTrue("Timeout waiting for job to complete", false);
}
}
}
public void transferRuleToYieldingThreadJobException() {
final TestBarrier barrier = new TestBarrier();
final PathRule rule = new PathRule(getName());
final Thread A = new Thread() {
@Override
public void run() {
try {
Job.getJobManager().beginRule(rule, null);
barrier.setStatus(TestBarrier.STATUS_RUNNING);
while (Job.getJobManager().currentJob().yieldRule(null) == null) {
//loop until yield succeeds
}
} finally {
Job.getJobManager().endRule(rule);
}
}
};
Thread B = new Thread() {
@Override
public void run() {
barrier.waitForStatus(TestBarrier.STATUS_RUNNING);
try {
Job.getJobManager().beginRule(rule, null);
// transfer it back to other thread -
// this will cause exception since the target thread already owns
// this rule
try {
Job.getJobManager().transferRule(rule, A);
} catch (Exception e) {
barrier.setStatus(TestBarrier.STATUS_DONE);
}
} finally {
Job.getJobManager().endRule(rule);
}
}
};
A.start();
B.start();
barrier.waitForStatus(TestBarrier.STATUS_DONE);
}
public void transferRuleToYieldingJobException() {
final TestBarrier barrier = new TestBarrier();
final PathRule rule = new PathRule(getName());
final Thread[] t = new Thread[1];
final Job A = new Job(getName() + "A") {
@Override
protected IStatus run(IProgressMonitor monitor) {
t[0] = getThread();
try {
Job.getJobManager().beginRule(rule, null);
barrier.setStatus(TestBarrier.STATUS_RUNNING);
while (Job.getJobManager().currentJob().yieldRule(null) == null) {
//loop until yield succeeds
}
} finally {
Job.getJobManager().endRule(rule);
}
return Status.OK_STATUS;
}
};
Job B = new Job(getName() + "B") {
@Override
protected IStatus run(IProgressMonitor monitor) {
barrier.waitForStatus(TestBarrier.STATUS_RUNNING);
try {
Job.getJobManager().beginRule(rule, null);
// transfer it back to other thread -
// this will cause exception since the target thread already owns
// this rule
try {
Job.getJobManager().transferRule(rule, t[0]);
} catch (Exception e) {
barrier.setStatus(TestBarrier.STATUS_DONE);
}
} finally {
Job.getJobManager().endRule(rule);
}
return Status.OK_STATUS;
}
};
A.schedule();
B.schedule();
barrier.waitForStatus(TestBarrier.STATUS_DONE);
}
public void testYieldPingPong() {
// yield from one job, then yield again
final PathRule rule = new PathRule(getName());
final Job[] jobs = new Job[2];
Job yieldJob = new Job(getName() + " Yielding") {
@Override
protected IStatus run(IProgressMonitor monitor) {
assertTrue(jobs[1].getResult() == null);
while (yieldRule(null) == null) {
//loop until yield succeeds
}
assertTrue(jobs[1].getState() == Job.WAITING);
return Status.OK_STATUS;
}
};
jobs[0] = yieldJob;
yieldJob.setRule(rule);
Job conflictingJob = new Job(getName() + " ConflictingJob1") {
@Override
protected IStatus run(IProgressMonitor monitor) {
assertEquals(WAITING, jobs[0].getState());
while (yieldRule(null) == null) {
//loop until yield succeeds
}
assertEquals(NONE, jobs[0].getState());
return Status.OK_STATUS;
}
};
jobs[1] = conflictingJob;
conflictingJob.setRule(rule);
yieldJob.schedule();
conflictingJob.schedule();
waitForCompletion(yieldJob);
final IStatus yieldResult = yieldJob.getResult();
if (!yieldResult.isOK()) {
Throwable t = yieldResult.getException();
if (t != null) {
fail("yieldJob failed", t);
}
fail("yieldJob failed:" + yieldResult);
}
waitForCompletion(conflictingJob);
assertTrue(conflictingJob.toString(), conflictingJob.getResult().isOK());
}
public void testYieldPingPongBetweenMultipleJobs() throws Throwable {
final TestBarrier barrier = new TestBarrier();
final PathRule rule = new PathRule(getName());
final Object SYNC = new Object();
final int count = 100;
final Integer[] started = new Integer[] {Integer.valueOf(0)};
final List<Job> jobs = new ArrayList<>();
for (int i = 0; i < count; i++) {
Job conflictingJob = new Job(getName() + " ConflictingJob" + i) {
@Override
protected IStatus run(IProgressMonitor monitor) {
synchronized (SYNC) {
started[0] = Integer.valueOf(started[0].intValue() + 1);
SYNC.notifyAll();
}
barrier.waitForStatus(TestBarrier.STATUS_WAIT_FOR_START);
while (true) {
if (yieldRule(null) != null) {
break;
}
if (getFinishedJobs(jobs.toArray(new Job[jobs.size()])).size() == count - 1) {
System.out.println(this + " Ended via no more jobs to yield");
break;
}
//loop until yield succeeds
}
return Status.OK_STATUS;
}
};
conflictingJob.setRule(rule);
jobs.add(conflictingJob);
}
for (Job conflict : jobs) {
conflict.schedule();
}
// wait for jobs to start running
synchronized (SYNC) {
while (started[0].intValue() != count) {
try {
SYNC.wait();
} catch (InterruptedException e) {
fail("4.99", e);
}
}
}
// release all waiting jobs
barrier.setStatus(TestBarrier.STATUS_WAIT_FOR_START);
// wait for jobs to finish within 5s
waitForJobsCompletion(jobs.toArray(new Job[jobs.size()]), 5000);
for (Job conflict : jobs) {
assertNotNull("Null result for " + conflict, conflict.getResult());
assertTrue(conflict.getResult().isOK());
}
}
public void testParallelYieldPingPongBetweenMultipleJobs() throws Throwable {
// same as above, but use two conflicting job families and make sure they don't interfere
final int count = 10;
//----------
// FAMILY A
//----------
final TestBarrier barrier_A = new TestBarrier();
final PathRule rule_A = new PathRule(getName() + "_ruleA");
final Object SYNC_A = new Object();
final Integer[] started_A = new Integer[] {Integer.valueOf(0)};
final List<Job> jobs_A = new ArrayList<>();
for (int i = 0; i < count; i++) {
Job conflictingJob = new Job(getName() + " ConflictingJob_A_" + i) {
@Override
protected IStatus run(IProgressMonitor monitor) {
synchronized (SYNC_A) {
started_A[0] = Integer.valueOf(started_A[0].intValue() + 1);
SYNC_A.notifyAll();
}
barrier_A.waitForStatus(TestBarrier.STATUS_WAIT_FOR_START);
while (true) {
if (yieldRule(null) != null) {
break;
}
if (getFinishedJobs(jobs_A.toArray(new Job[jobs_A.size()])).size() == count - 1) {
System.out.println(this + " Ended via no more jobs to yield");
break;
}
//loop until yield succeeds
}
return Status.OK_STATUS;
}
};
conflictingJob.setRule(rule_A);
jobs_A.add(conflictingJob);
}
for (Job conflict : jobs_A) {
conflict.schedule();
}
// wait for jobs to start running
synchronized (SYNC_A) {
while (started_A[0].intValue() != count) {
try {
SYNC_A.wait();
} catch (InterruptedException e) {
fail("4.99", e);
}
}
}
// release all waiting jobs
barrier_A.setStatus(TestBarrier.STATUS_WAIT_FOR_START);
//----------
// FAMILY B
//----------
final TestBarrier barrier_B = new TestBarrier();
final PathRule rule_B = new PathRule(getName() + "_ruleB");
final Object SYNC_B = new Object();
final Integer[] started_B = new Integer[] { Integer.valueOf(0) };
final List<Job> jobs_B = new ArrayList<>();
for (int i = 0; i < count; i++) {
Job conflictingJob = new Job(getName() + " ConflictingJob_B_" + i) {
@Override
protected IStatus run(IProgressMonitor monitor) {
synchronized (SYNC_B) {
started_B[0] = Integer.valueOf(started_B[0].intValue() + 1);
SYNC_B.notifyAll();
}
barrier_B.waitForStatus(TestBarrier.STATUS_WAIT_FOR_START);
while (true) {
if (yieldRule(null) != null) {
break;
}
if (getFinishedJobs(jobs_B.toArray(new Job[jobs_B.size()])).size() == count - 1) {
System.out.println(this + " Ended via no more jobs to yield");
break;
}
//loop until yield succeeds
}
return Status.OK_STATUS;
}
};
conflictingJob.setRule(rule_B);
jobs_B.add(conflictingJob);
}
for (Job conflict : jobs_B) {
conflict.schedule();
}
// wait for jobs to start running
synchronized (SYNC_B) {
while (started_B[0].intValue() != count) {
try {
SYNC_B.wait();
} catch (InterruptedException e) {
fail("4.99", e);
}
}
}
// release all waiting jobs
barrier_B.setStatus(TestBarrier.STATUS_WAIT_FOR_START);
// wait for jobs to finish within 5s
waitForJobsCompletion(jobs_A.toArray(new Job[jobs_A.size()]), 5000);
// wait for jobs to finish within 5s
waitForJobsCompletion(jobs_B.toArray(new Job[jobs_B.size()]), 5000);
for (Job conflict : jobs_A) {
assertNotNull("Null result for " + conflict, conflict.getResult());
assertTrue(conflict.getResult().isOK());
}
for (Job conflict : jobs_B) {
assertNotNull("Null result for " + conflict, conflict.getResult());
assertTrue(conflict.getResult().isOK());
}
}
public void testYieldIsInterruptable() throws Exception {
Semaphore semaphoreA = new Semaphore(0);
Semaphore semaphoreB = new Semaphore(0);
Semaphore mainThreadSemaphore = new Semaphore(0);
final PathRule rule = new PathRule(getName());
String[] failureMessage = new String[1];
boolean[] operationWasCanceled = new boolean[1];
final Job yieldB = new Job(getName() + " YieldingB") {
@Override
protected IStatus run(IProgressMonitor monitor) {
// Step 2
semaphoreA.release();
try {
// Block until thread yieldA reaches step 3
semaphoreB.acquire();
} catch (InterruptedException e) {
failureMessage[0] = "yieldB was interrupted too early";
return Status.OK_STATUS;
}
// Step 5
mainThreadSemaphore.release();
return Status.OK_STATUS;
}
};
Job yieldA = new Job(getName() + " YieldingA") {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
// Block until the main thread reaches step 1
semaphoreA.acquire();
} catch (InterruptedException e) {
failureMessage[0] = "yieldA was interrupted too early";
return Status.OK_STATUS;
}
// If this fails to interrupt the following yieldRule call,
// this thread will deadlock.
Thread.currentThread().interrupt();
try {
// Allow yieldB to start execution
while (yieldRule(null) != yieldB) {
}
} catch (OperationCanceledException e) {
operationWasCanceled[0] = true;
}
if (Thread.interrupted()) {
failureMessage[0] = "The thread was interrupted after yieldRule returned";
}
try {
// Block until yieldB reaches step 2
semaphoreA.acquire();
} catch (InterruptedException e) {
failureMessage[0] = "yieldA was interrupted too early";
return Status.OK_STATUS;
}
// Step 3
semaphoreB.release();
// Step 4
mainThreadSemaphore.release();
return Status.OK_STATUS;
}
};
yieldA.setRule(rule);
yieldA.schedule();
yieldB.setRule(rule);
yieldB.schedule();
// Step 1
semaphoreA.release();
// Block until step 4 and step 5 have occurred
mainThreadSemaphore.acquire(2);
if (failureMessage[0] != null) {
assertNull(failureMessage[0], failureMessage[0]);
}
assertTrue("yieldRule should have thrown OperationCanceledException", operationWasCanceled[0]);
}
public void testYieldJobToJobsInterleaved() {
// yield from job to multiple waiting others
final TestBarrier barrier = new TestBarrier();
final PathRule rule = new PathRule(getName());
final int count = 50;
Job yieldA = new Job(getName() + " YieldingA") {
@Override
protected IStatus run(IProgressMonitor monitor) {
barrier.waitForStatus(TestBarrier.STATUS_START);
int yields = 0;
while (yields < count) {
if (yieldRule(null) != null) {
yields++;
}
}
return Status.OK_STATUS;
}
};
yieldA.setRule(rule);
yieldA.schedule();
Job yieldB = new Job(getName() + " YieldingB") {
@Override
protected IStatus run(IProgressMonitor monitor) {
barrier.waitForStatus(TestBarrier.STATUS_START);
int yields = 0;
while (yields < count) {
if (yieldRule(null) != null) {
yields++;
}
}
return Status.OK_STATUS;
}
};
yieldB.setRule(rule);
yieldB.schedule();
barrier.setStatus(TestBarrier.STATUS_START);
List<Job> jobs = new ArrayList<>();
jobs.add(yieldA);
jobs.add(yieldB);
// wait for jobs to start
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
fail("4.99", e);
}
waitForJobsCompletion(jobs.toArray(new Job[jobs.size()]), 20000);
for (Job conflict : jobs) {
assertTrue(conflict.getResult().isOK());
}
}
public void testYieldThreadJobToBlockedConflictingJob() throws Exception {
final PathRule rule = new PathRule(getName());
final TestBarrier b = new TestBarrier(TestBarrier.STATUS_START);
final Job yieldA = new Job(getName()) {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
Job.getJobManager().beginRule(rule, monitor);
b.setStatus(TestBarrier.STATUS_RUNNING);
while (yieldRule(null) == null && !monitor.isCanceled()) {
//loop until yield succeeds
}
} finally {
Job.getJobManager().endRule(rule);
}
return Status.OK_STATUS;
}
};
Job conflicting = new Job(getName() + " Conflicting") {
@Override
protected IStatus run(IProgressMonitor monitor) {
return Status.OK_STATUS;
}
};
conflicting.setRule(rule);
// start testing
yieldA.schedule();
b.waitForStatus(TestBarrier.STATUS_RUNNING);
conflicting.schedule();
try {
waitForCompletion(yieldA);
waitForCompletion(conflicting);
} finally {
//always cleanup even if we fail
yieldA.cancel();
}
}
public void testResumingThreadJobIsNotRescheduled() {
final PathRule rule = new PathRule(getName());
final TestBarrier b = new TestBarrier(TestBarrier.STATUS_START);
final Job yieldA = new Job(getName()) {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
Job.getJobManager().beginRule(rule, monitor);
b.setStatus(TestBarrier.STATUS_RUNNING);
while (yieldRule(null) == null) {
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
//loop until yield succeeds
}
}
} finally {
Job.getJobManager().endRule(rule);
}
return Status.OK_STATUS;
}
};
final Job conflicting = new Job(getName() + " Conflicting") {
@Override
protected IStatus run(IProgressMonitor monitor) {
return Status.OK_STATUS;
}
};
conflicting.setRule(rule);
final int[] count = new int[1];
JobChangeAdapter a = new JobChangeAdapter() {
@Override
public void running(org.eclipse.core.runtime.jobs.IJobChangeEvent event) {
if (event.getJob() == yieldA) {
count[0]++;
}
}
};
Job.getJobManager().addJobChangeListener(a);
// start testing
yieldA.schedule();
b.waitForStatus(TestBarrier.STATUS_RUNNING);
conflicting.schedule();
try {
waitForCompletion(yieldA);
waitForCompletion(conflicting);
assertEquals("While resuming from yieldRule, implicit Job should only run once", 1, count[0]);
} finally {
//clean up even if the test fails
yieldA.cancel();
Job.getJobManager().removeJobChangeListener(a);
}
}
public void testNestedAcquireJobIsNotRescheduled() {
final PathRule rule = new PathRule(getName());
final PathRule subRule = new PathRule(getName() + "/subRule");
final TestBarrier b = new TestBarrier(TestBarrier.STATUS_START);
final Job yieldA = new Job(getName()) {
@Override
protected IStatus run(IProgressMonitor monitor) {
b.setStatus(TestBarrier.STATUS_RUNNING);
try {
Job.getJobManager().beginRule(subRule, null);
while (yieldRule(null) == null) {
//loop until yield succeeds
}
} finally {
Job.getJobManager().endRule(subRule);
}
return Status.OK_STATUS;
}
};
yieldA.setRule(rule);
final Job conflicting = new Job(getName() + " Conflicting") {
@Override
protected IStatus run(IProgressMonitor monitor) {
return Status.OK_STATUS;
}
};
conflicting.setRule(rule);
final int[] count = new int[1];
JobChangeAdapter a = new JobChangeAdapter() {
@Override
public void running(org.eclipse.core.runtime.jobs.IJobChangeEvent event) {
if (event.getJob() == conflicting) {
count[0]++;
}
}
};
Job.getJobManager().addJobChangeListener(a);
try {
// start testing
yieldA.schedule();
b.waitForStatus(TestBarrier.STATUS_RUNNING);
conflicting.schedule();
waitForCompletion(yieldA);
waitForCompletion(conflicting);
assertEquals("While resuming from yieldRule, conflicting job should only run once", 1, count[0]);
} finally {
Job.getJobManager().removeJobChangeListener(a);
}
}
}