use of org.eclipse.scout.rt.platform.job.IBlockingCondition in project scout.rt by eclipse.
the class MutualExclusionTest method testBlockingCondition_InterruptedWhileReAcquiringTheMutex.
/**
* We have 3 jobs that are scheduled simultaneously. Thereby, job1 enters a blocking condition which in turn lets job2
* run. Job2 unblocks job1 so that job1 is trying to re-acquire the mutex. While waiting for the mutex to be
* available, job1 is interrupted due to a cancel-request of job2.<br/>
* This test verifies, that job1 is interrupted, competes for the mutex anew and only continues once a permit becomes
* available. Also, job3 must not start running as long as job1 did not complete.
*/
@Test
// regression
@Times(100)
public void testBlockingCondition_InterruptedWhileReAcquiringTheMutex() throws java.lang.InterruptedException {
// synchronized because modified/read by different threads.
final List<String> protocol = Collections.synchronizedList(new ArrayList<String>());
final IBlockingCondition condition = Jobs.newBlockingCondition(true);
final BlockingCountDownLatch latchJob2 = new BlockingCountDownLatch(1);
final BlockingCountDownLatch job1FinishLatch = new BlockingCountDownLatch(1);
final IFuture<Void> future1 = ModelJobs.schedule(new IRunnable() {
@Override
public void run() throws Exception {
protocol.add("running-1");
try {
protocol.add("before-blocking-1");
condition.waitFor();
} catch (ThreadInterruptedError e) {
protocol.add("interrupted-1 (a)");
} catch (RuntimeException e) {
protocol.add("jobException-1");
}
if (Thread.currentThread().isInterrupted()) {
protocol.add("interrupted-1 (b)");
}
if (ModelJobs.isModelThread()) {
protocol.add("model-thread-1");
}
protocol.add("done-1");
job1FinishLatch.countDown();
}
}, ModelJobs.newInput(ClientRunContexts.copyCurrent()).withName("job-1").withExecutionHint(JOB_IDENTIFIER).withExceptionHandling(null, false));
final IFuture<Void> future2 = ModelJobs.schedule(new IRunnable() {
@Override
public void run() throws Exception {
protocol.add("running-2a");
if (future1.getState() == JobState.WAITING_FOR_BLOCKING_CONDITION) {
protocol.add("job2: job-1-waiting-for-blocking-condition");
}
if (!future1.getExecutionSemaphore().isPermitOwner(future1)) {
protocol.add("job2: job-1-not-permit-owner");
}
protocol.add("unblocking condition");
condition.setBlocking(false);
// job-1 (interrupted acquisition task), job-2 (latch), job-3 (waiting for mutex)
JobTestUtil.waitForPermitCompetitors(m_clientSession.getModelJobSemaphore(), 3);
if (future1.getState() == JobState.WAITING_FOR_PERMIT) {
protocol.add("job2: job-1-waiting-for-mutex");
}
protocol.add("before-cancel-job1-2");
// interrupt job1 while acquiring the mutex
future1.cancel(true);
// job-1 (interrupted acquisition task), job-1 (re-acquiring a permit), job-2 (latch), job-3 (waiting for mutex)
JobTestUtil.waitForPermitCompetitors(m_clientSession.getModelJobSemaphore(), 4);
// cancelled, but still running
JobTestUtil.waitForState(future1, JobState.DONE);
protocol.add("running-2b");
latchJob2.countDownAndBlock();
protocol.add("done-2");
}
}, ModelJobs.newInput(ClientRunContexts.copyCurrent()).withName("job-2").withExecutionHint(JOB_IDENTIFIER).withExceptionHandling(null, false));
final IFuture<Void> future3 = ModelJobs.schedule(new IRunnable() {
@Override
public void run() throws Exception {
protocol.add("done-3");
}
}, ModelJobs.newInput(ClientRunContexts.copyCurrent()).withName("job-3").withExecutionHint(JOB_IDENTIFIER).withExceptionHandling(null, false));
assertTrue(latchJob2.await());
assertEquals(future1.getState(), JobState.DONE);
assertEquals(future2.getState(), JobState.RUNNING);
assertEquals(future3.getState(), JobState.WAITING_FOR_PERMIT);
latchJob2.unblock();
Jobs.getJobManager().awaitDone(Jobs.newFutureFilterBuilder().andMatchExecutionHint(JOB_IDENTIFIER).toFilter(), 5, TimeUnit.SECONDS);
List<String> expectedProtocol = new ArrayList<>();
expectedProtocol.add("running-1");
expectedProtocol.add("before-blocking-1");
expectedProtocol.add("running-2a");
expectedProtocol.add("job2: job-1-waiting-for-blocking-condition");
expectedProtocol.add("job2: job-1-not-permit-owner");
expectedProtocol.add("unblocking condition");
expectedProtocol.add("job2: job-1-waiting-for-mutex");
expectedProtocol.add("before-cancel-job1-2");
expectedProtocol.add("running-2b");
expectedProtocol.add("done-2");
expectedProtocol.add("interrupted-1 (b)");
expectedProtocol.add("model-thread-1");
expectedProtocol.add("done-1");
expectedProtocol.add("done-3");
assertEquals(expectedProtocol, protocol);
assertEquals(future1.getState(), JobState.DONE);
assertEquals(future2.getState(), JobState.DONE);
assertEquals(future3.getState(), JobState.DONE);
assertTrue(future1.isCancelled());
future1.awaitDone(1, TimeUnit.NANOSECONDS);
assertFalse(future2.isCancelled());
future2.awaitDone(1, TimeUnit.NANOSECONDS);
assertFalse(future3.isCancelled());
future3.awaitDone(1, TimeUnit.NANOSECONDS);
}
use of org.eclipse.scout.rt.platform.job.IBlockingCondition in project scout.rt by eclipse.
the class MutualExclusionTest method testBlockingConditionMultipleNested.
/**
* Tests a BlockingCondition that blocks multiple model-threads that were scheduled as nested jobs.
*/
@Test
public void testBlockingConditionMultipleNested() {
final IBlockingCondition condition = Jobs.newBlockingCondition(true);
// run the test 2 times to also test reusability of a blocking condition.
runTestBlockingCondition(condition);
runTestBlockingCondition(condition);
}
use of org.eclipse.scout.rt.platform.job.IBlockingCondition in project scout.rt by eclipse.
the class MutualExclusionTest method testBlockingConditionSingle.
/**
* Tests a BlockingCondition that blocks a single model-thread.
*/
@Test
public void testBlockingConditionSingle() {
// synchronized because modified/read by different threads.
final List<String> protocol = Collections.synchronizedList(new ArrayList<String>());
final IBlockingCondition condition = Jobs.newBlockingCondition(true);
final IFuture<Void> future1 = ModelJobs.schedule(new IRunnable() {
@Override
public void run() throws Exception {
protocol.add("1: running");
if (Jobs.getJobManager().isDone(Jobs.newFutureFilterBuilder().andMatchExecutionHint(JOB_IDENTIFIER).toFilter())) {
protocol.add("1: idle [a]");
}
if (IFuture.CURRENT.get().getState() == JobState.WAITING_FOR_BLOCKING_CONDITION) {
protocol.add("1: blocked [a]");
}
if (ModelJobs.isModelThread()) {
protocol.add("1: modelThread [a]");
}
protocol.add("1: beforeAwait");
condition.waitFor();
protocol.add("1: afterAwait");
if (IFuture.CURRENT.get().getState() == JobState.RUNNING) {
protocol.add("1: state=running");
}
if (Jobs.getJobManager().isDone(Jobs.newFutureFilterBuilder().andMatchExecutionHint(JOB_IDENTIFIER).toFilter())) {
protocol.add("1: idle [b]");
}
if (IFuture.CURRENT.get().getState() == JobState.WAITING_FOR_BLOCKING_CONDITION) {
protocol.add("1: blocked [b]");
}
if (ModelJobs.isModelThread()) {
protocol.add("1: modelThread [b]");
}
}
}, ModelJobs.newInput(ClientRunContexts.copyCurrent()).withName("job-1").withExecutionHint(JOB_IDENTIFIER).withExceptionHandling(null, false));
ModelJobs.schedule(new IRunnable() {
@Override
public void run() throws Exception {
protocol.add("2: running");
if (Jobs.getJobManager().isDone(Jobs.newFutureFilterBuilder().andMatchExecutionHint(JOB_IDENTIFIER).toFilter())) {
protocol.add("2: idle [a]");
}
if (future1.getState() == JobState.WAITING_FOR_BLOCKING_CONDITION) {
protocol.add("2: job-1-state: waiting-for-condition");
}
if (IFuture.CURRENT.get().getState() == JobState.WAITING_FOR_BLOCKING_CONDITION) {
protocol.add("2: blocked [a]");
}
if (ModelJobs.isModelThread()) {
protocol.add("2: modelThread [a]");
}
// RELEASE THE BlockingCondition
protocol.add("2: beforeSignaling");
condition.setBlocking(false);
protocol.add("2: afterSignaling");
// Wait until job1 is competing for the mutex anew
JobTestUtil.waitForPermitCompetitors(ClientRunContexts.copyCurrent().getSession().getModelJobSemaphore(), 2);
if (future1.getState() == JobState.WAITING_FOR_PERMIT) {
protocol.add("2: job-1-state: waiting-for-mutex");
}
if (Jobs.getJobManager().isDone(Jobs.newFutureFilterBuilder().andMatchExecutionHint(JOB_IDENTIFIER).toFilter())) {
protocol.add("2: idle [b]");
}
if (IFuture.CURRENT.get().getState() == JobState.WAITING_FOR_BLOCKING_CONDITION) {
protocol.add("2: blocked [b]");
}
if (ModelJobs.isModelThread()) {
protocol.add("2: modelThread [b]");
}
}
}, ModelJobs.newInput(ClientRunContexts.copyCurrent()).withName("job-2").withExecutionHint(JOB_IDENTIFIER).withExceptionHandling(null, false));
// Wait until job1 completed.
future1.awaitDoneAndGet(30, TimeUnit.SECONDS);
awaitDoneElseFail(JOB_IDENTIFIER);
List<String> expected = new ArrayList<>();
expected.add("1: running");
expected.add("1: modelThread [a]");
expected.add("1: beforeAwait");
expected.add("2: running");
expected.add("2: job-1-state: waiting-for-condition");
expected.add("2: modelThread [a]");
expected.add("2: beforeSignaling");
expected.add("2: afterSignaling");
expected.add("2: job-1-state: waiting-for-mutex");
expected.add("2: modelThread [b]");
expected.add("1: afterAwait");
expected.add("1: state=running");
expected.add("1: modelThread [b]");
assertEquals(expected, protocol);
}
use of org.eclipse.scout.rt.platform.job.IBlockingCondition in project scout.rt by eclipse.
the class ModelJobs method yield.
/**
* Instructs the job manager that the current model job is willing to temporarily yield its current model job permit.
* <p>
* It is rarely appropriate to use this method. It may be useful for debugging or testing purposes.
*/
public static void yield() {
Assertions.assertTrue(ModelJobs.isModelThread(), "'Yield' must be invoked from model thread");
final IBlockingCondition idleCondition = Jobs.newBlockingCondition(true);
ModelJobs.schedule(NULL_RUNNABLE, ModelJobs.newInput(ClientRunContexts.copyCurrent()).withName("Technical job to yield model thread")).whenDone(new IDoneHandler<Void>() {
@Override
public void onDone(final DoneEvent<Void> event) {
idleCondition.setBlocking(false);
}
}, ClientRunContexts.copyCurrent());
// Release the current model job permit and wait until all competing model jobs of this session completed their work.
idleCondition.waitFor();
}
use of org.eclipse.scout.rt.platform.job.IBlockingCondition in project scout.rt by eclipse.
the class ExecutionSemaphoreTest method testInternalDeadlock.
/**
* Tests an internal of {@link ExecutionSemaphore}, that {@link AcquisitionTask#notifyPermitAcquired()} is invoked
* outside the {@link ExecutionSemaphore} lock.
* <p>
* Otherwise, a deadlock might occur, once the resuming job-1 tries to re-acquire the permit, namely exactly the time
* when owning acquisitionLock in {@link ExecutionSemaphore#acquire(IFuture, QueuePosition)} and querying
* 'isPermitOwner'. Thereto, job-1 must compete for the semaphore lock, while job-2 (owning semaphore lock) tries to
* notify the resuming job-1 via {@link AcquisitionTask#notifyPermitAcquired()}, but cannot get monitor of
* acquisitionLock.
*/
@Test
// regression; do not remove
@Times(1_000)
public void testInternalDeadlock() {
final IExecutionSemaphore semaphore = Jobs.newExecutionSemaphore(1);
final IBlockingCondition condition = Jobs.newBlockingCondition(true);
final AtomicReference<IFuture<?>> future2Ref = new AtomicReference<>();
IFuture<Void> future1 = Jobs.schedule(new IRunnable() {
@Override
public void run() throws Exception {
future2Ref.set(Jobs.schedule(new IRunnable() {
@Override
public void run() throws Exception {
condition.setBlocking(false);
}
}, Jobs.newInput().withName("job-2").withExecutionSemaphore(semaphore)));
condition.waitFor();
}
}, Jobs.newInput().withName("job-1").withExecutionSemaphore(semaphore));
try {
future1.awaitDoneAndGet(5, TimeUnit.SECONDS);
} catch (TimedOutError e) {
fail(String.format("Deadlock while passing permit from 'job-2' to 'job-1' [job-1-state=%s, job-2-state=%s", future1.getState(), future2Ref.get().getState()));
}
}
Aggregations