Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLaurent Delaigue2015-06-22 13:15:03 +0000
committerAxel RICHARD2015-07-09 15:20:10 +0000
commit602e1b72cd0321f491360d3decf2d730dd42c115 (patch)
tree80872226034f56ce6f765b7b913c37aad4d2a302 /plugins
parentf0c155c28524981e75effcc5b6a2e44126fa1499 (diff)
downloadorg.eclipse.emf.compare-602e1b72cd0321f491360d3decf2d730dd42c115.tar.gz
org.eclipse.emf.compare-602e1b72cd0321f491360d3decf2d730dd42c115.tar.xz
org.eclipse.emf.compare-602e1b72cd0321f491360d3decf2d730dd42c115.zip
Improve robustness of ResourceComputationSchedulerTest
ResourceComputationSchedulerTest class tests multi-threaded behaviour in a non-robust fashion. It should NOT rely on Thread.sleep(1000) to make sure some treatment is over. This lead to add lifecycle events broadcasting to the Scheduler. Bug: 470676 Change-Id: I297d42b882bf2d625ad946db6ed053f9d6109b12 Signed-off-by: Laurent Delaigue <laurent.delaigue@obeo.fr>
Diffstat (limited to 'plugins')
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/resolver/ResourceComputationSchedulerTest.java191
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/resolver/ResourceComputationSchedulerWithEventBusTest.java447
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/suite/AllTests.java9
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceComputationScheduler.java148
4 files changed, 635 insertions, 160 deletions
diff --git a/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/resolver/ResourceComputationSchedulerTest.java b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/resolver/ResourceComputationSchedulerTest.java
index 4ed251a9a..d83ce6ec6 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/resolver/ResourceComputationSchedulerTest.java
+++ b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/resolver/ResourceComputationSchedulerTest.java
@@ -24,8 +24,10 @@ import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
+import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.emf.compare.ide.ui.internal.logical.resolver.IComputation;
import org.eclipse.emf.compare.ide.ui.internal.logical.resolver.ResourceComputationScheduler;
+import org.eclipse.emf.compare.ide.ui.internal.logical.resolver.ResourceComputationScheduler.ShutdownStatus;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -33,9 +35,9 @@ import org.junit.Test;
@SuppressWarnings("nls")
public class ResourceComputationSchedulerTest {
- private ResourceComputationScheduler<String> scheduler;
+ protected ResourceComputationScheduler<String> scheduler;
- private boolean flag;
+ protected boolean flag;
@Test
public void testInitializeCanBeCalledSeveralTimes() {
@@ -79,6 +81,26 @@ public class ResourceComputationSchedulerTest {
assertTrue(flag);
}
+ @Test(expected = OperationCanceledException.class)
+ public void testInterruptedExceptionInCallCausesOperationCanceledException() throws Exception {
+ scheduler.initialize();
+ scheduler.call(new Callable<String>() {
+ public String call() throws Exception {
+ throw new InterruptedException();
+ }
+ }, null);
+ }
+
+ @Test(expected = OperationCanceledException.class)
+ public void testOperationCanceledExceptionCall() throws Exception {
+ scheduler.initialize();
+ scheduler.call(new Callable<String>() {
+ public String call() throws Exception {
+ throw new OperationCanceledException();
+ }
+ }, null);
+ }
+
@Test
public void testPostTreatmentIsCalledWhenExceptionInTreatment() throws Exception {
scheduler.initialize();
@@ -265,76 +287,6 @@ public class ResourceComputationSchedulerTest {
scheduler.scheduleComputation(new TestSuccessfulComputation(desc, "comp"));
}
- @Test
- public void testDemandShutdownWithLongRunningTaskThatInterruptsImproperly() throws Exception {
- scheduler.initialize();
- final CompStatus cs = new CompStatus();
- Integer result = scheduler.call(new Callable<Integer>() {
-
- public Integer call() throws Exception {
- scheduler.scheduleComputation(new TimedComputation(cs, "long1", 1000, false));
- // We ask for shutdown before the task can complete
- // The scheduler is configured to wait only 100ms
- scheduler.demandShutdown();
-
- return Integer.valueOf(42);
- }
- }, null);
- // desc has not been updated
- assertFalse(cs.isFailed());
- assertFalse(cs.isSuccess());
- assertEquals(Integer.valueOf(42), result);
- assertTrue(scheduler.getComputedElements().isEmpty());
- Thread.sleep(1000);
- checkInterruptedAndSuccess(cs);
- }
-
- @Test
- public void testDemandShutdownWithLongRunningTaskThatInterruptsGracefully() throws Exception {
- scheduler.initialize();
- final CompStatus cs = new CompStatus();
- Integer result = scheduler.call(new Callable<Integer>() {
-
- public Integer call() throws Exception {
- scheduler.scheduleComputation(new TimedComputation(cs, "long1", 1000, true));
- // We ask for shutdown before the task can complete
- // The scheduler is configured to wait only 100ms
- scheduler.demandShutdown();
-
- return Integer.valueOf(42);
- }
- }, null);
- // desc has not been updated
- assertFalse(cs.isFailed());
- assertFalse(cs.isSuccess());
- assertEquals(Integer.valueOf(42), result);
- assertTrue(scheduler.getComputedElements().isEmpty());
- Thread.sleep(1000);
- checkInterruptedAndFailure(cs);
- }
-
- @Test
- public void testDemandShutdownWithRunningTaskThatTerminatesGracefully() throws Exception {
- scheduler = new ResourceComputationScheduler<String>(1, TimeUnit.SECONDS);
- scheduler.initialize();
- final CompStatus desc = new CompStatus();
- Integer result = scheduler.call(new Callable<Integer>() {
-
- public Integer call() throws Exception {
- scheduler.scheduleComputation(new TimedComputation(desc, "long1", 500, false));
- // We ask for shutdown before the task can complete
- // The scheduler is configured to wait 1s while the task only takes 500ms
- scheduler.demandShutdown();
-
- return Integer.valueOf(42);
- }
- }, null);
- assertEquals(Integer.valueOf(42), result);
- Thread.sleep(1000);
- checkSuccess(desc);
- assertTrue(scheduler.getComputedElements().isEmpty());
- }
-
protected void checkSuccess(CompStatus state) {
assertEquals(1, state.getCallCount());
assertFalse(state.isInterrupted());
@@ -373,7 +325,7 @@ public class ResourceComputationSchedulerTest {
@Before
public void setUp() {
- scheduler = new ResourceComputationScheduler<String>(100, TimeUnit.MILLISECONDS);
+ scheduler = new ResourceComputationScheduler<String>(10, TimeUnit.MILLISECONDS, null);
}
@After
@@ -392,15 +344,15 @@ public class ResourceComputationSchedulerTest {
private final String name;
- private TestSuccessfulComputation(CompStatus desc, String name) {
- this.cs = desc;
+ private TestSuccessfulComputation(CompStatus cs, String name) {
+ this.cs = cs;
this.name = name;
}
public void run() {
cs.addCall();
try {
- Thread.sleep(100);
+ Thread.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
cs.interrupt();
@@ -483,86 +435,11 @@ public class ResourceComputationSchedulerTest {
}
/**
- * A test computation that systematically throws an exception when run, and updates its {@link CompStatus}
- * accordingly if onFailure() is called on its post-treatment.
- *
- * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
- */
- private static class InterruptibleRunnable implements Runnable {
- protected final CompStatus cs;
-
- protected final long duration;
-
- protected final boolean throwOnInterrupt;
-
- private InterruptibleRunnable(CompStatus desc, long duration, boolean throwOnInterrupt) {
- this.cs = desc;
- this.duration = duration;
- this.throwOnInterrupt = throwOnInterrupt;
- }
-
- public void run() {
- cs.addCall();
- try {
- Thread.sleep(duration);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- cs.interrupt();
- if (throwOnInterrupt) {
- throw new RuntimeException("Interrupted");
- }
- }
- }
- }
-
- /**
- * A test computation lasts for a given time, and updates its {@link CompStatus} accordingly if
- * onFailure() is called on its post-treatment.
- *
- * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
- */
- private static final class TimedComputation extends InterruptibleRunnable implements IComputation<String> {
-
- protected final String name;
-
- private TimedComputation(CompStatus desc, String name, long duration, boolean throwOnInterrupt) {
- super(desc, duration, throwOnInterrupt);
- this.name = name;
- }
-
- public FutureCallback<Object> getPostTreatment() {
- return new FutureCallback<Object>() {
- public void onFailure(Throwable t) {
- if (cs.isInterrupted() && throwOnInterrupt) {
- cs.fail("as expected");
- } else {
- cs.fail("onFailure() called on TimedComputation " + name
- + ", should have been onSuccess().");
- }
- }
-
- public void onSuccess(Object r) {
- if (cs.isInterrupted() && throwOnInterrupt) {
- cs.fail("onSuccess() called on TimedComputation" + name
- + ", should have been onFailure().");
- } else {
- cs.success("as expected");
- }
- }
- };
- }
-
- public String getKey() {
- return name;
- }
- }
-
- /**
* Computation Status.
*
* @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
*/
- private static class CompStatus {
+ protected static class CompStatus {
private boolean success = false;
private boolean failed = false;
@@ -571,6 +448,8 @@ public class ResourceComputationSchedulerTest {
private boolean interrupted;
+ private ShutdownStatus shutdownStatus;
+
private String message;
public String getMessage() {
@@ -612,5 +491,13 @@ public class ResourceComputationSchedulerTest {
public boolean isInterrupted() {
return interrupted;
}
+
+ public void setShutdownStatus(ShutdownStatus shutdownStatus) {
+ this.shutdownStatus = shutdownStatus;
+ }
+
+ public ShutdownStatus getShutdownStatus() {
+ return shutdownStatus;
+ }
}
}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/resolver/ResourceComputationSchedulerWithEventBusTest.java b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/resolver/ResourceComputationSchedulerWithEventBusTest.java
new file mode 100644
index 000000000..af9d2e1bc
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/resolver/ResourceComputationSchedulerWithEventBusTest.java
@@ -0,0 +1,447 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.tests.logical.resolver;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.eventbus.EventBus;
+import com.google.common.eventbus.Subscribe;
+import com.google.common.util.concurrent.FutureCallback;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.emf.compare.ide.ui.internal.logical.resolver.IComputation;
+import org.eclipse.emf.compare.ide.ui.internal.logical.resolver.ResourceComputationScheduler;
+import org.eclipse.emf.compare.ide.ui.internal.logical.resolver.ResourceComputationScheduler.CallStatus;
+import org.eclipse.emf.compare.ide.ui.internal.logical.resolver.ResourceComputationScheduler.ComputationState;
+import org.eclipse.emf.compare.ide.ui.internal.logical.resolver.ResourceComputationScheduler.ShutdownState;
+import org.eclipse.emf.compare.ide.ui.internal.logical.resolver.ResourceComputationScheduler.ShutdownStatus;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * These tests are the same as in the extend class, except they use a {@link ResourceComputationScheduler}
+ * initialized with a non-null {@link EventBus}. It also adds tests that can only be performed when an
+ * EventBus is set on the scheduler.
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ */
+@SuppressWarnings("nls")
+public class ResourceComputationSchedulerWithEventBusTest extends ResourceComputationSchedulerTest {
+
+ private EventBus bus;
+
+ @Test
+ public void testEventsLaunchedInStandardCase() throws Exception {
+ scheduler.initialize();
+ final List<CallStatus> receivedEvents = new ArrayList<CallStatus>();
+ bus.register(new Object() {
+ @Subscribe
+ public void callStatusChanged(CallStatus cs) {
+ receivedEvents.add(cs);
+ }
+ });
+ scheduler.call(new Callable<String>() {
+ public String call() throws Exception {
+ return "";
+ }
+ }, null);
+ assertEquals(4, receivedEvents.size());
+ assertEquals(ComputationState.SETTING_UP, receivedEvents.get(0).getState());
+ assertEquals(ComputationState.SCHEDULED, receivedEvents.get(1).getState());
+ assertEquals(ComputationState.FINISHING, receivedEvents.get(2).getState());
+ assertEquals(ComputationState.FINISHED, receivedEvents.get(3).getState());
+ }
+
+ @Test
+ public void testEventsLaunchedWhenCallThrowsException() throws Exception {
+ scheduler.initialize();
+ final List<CallStatus> receivedEvents = new ArrayList<CallStatus>();
+ bus.register(new Object() {
+ @Subscribe
+ public void callStatusChanged(CallStatus cs) {
+ receivedEvents.add(cs);
+ }
+ });
+ Exception exceptionReceived = null;
+ try {
+ scheduler.call(new Callable<String>() {
+ public String call() throws Exception {
+ throw new Exception("Test");
+ }
+ }, null);
+ } catch (Exception e) {
+ exceptionReceived = e;
+ }
+ assertEquals(5, receivedEvents.size());
+ assertEquals(ComputationState.SETTING_UP, receivedEvents.get(0).getState());
+ assertEquals(ComputationState.SCHEDULED, receivedEvents.get(1).getState());
+ assertEquals(ComputationState.FAILED, receivedEvents.get(2).getState());
+ // Check the root exception has been well passed in the event too
+ assertNotNull(exceptionReceived);
+ assertSame(exceptionReceived.getCause(), receivedEvents.get(2).getCause());
+ assertEquals(ComputationState.FINISHING, receivedEvents.get(3).getState());
+ assertEquals(ComputationState.FINISHED, receivedEvents.get(4).getState());
+ }
+
+ @Test
+ public void testEventsLaunchedWhenPostTreamentThrowsException() throws Exception {
+ scheduler.initialize();
+ final List<CallStatus> receivedEvents = new ArrayList<CallStatus>();
+ bus.register(new Object() {
+ @Subscribe
+ public void callStatusChanged(CallStatus cs) {
+ receivedEvents.add(cs);
+ }
+ });
+ try {
+ scheduler.call(new Callable<String>() {
+ public String call() throws Exception {
+ return "";
+ }
+ }, new Runnable() {
+ public void run() {
+ throw new RuntimeException();
+ }
+ });
+ fail("There should have been a RuntimeException");
+ } catch (RuntimeException e) {
+ // As expected
+ }
+ // Flag will only be true if the CallStatus "FINISH event has been received
+ assertEquals(4, receivedEvents.size());
+ assertEquals(ComputationState.SETTING_UP, receivedEvents.get(0).getState());
+ assertEquals(ComputationState.SCHEDULED, receivedEvents.get(1).getState());
+ assertEquals(ComputationState.FINISHING, receivedEvents.get(2).getState());
+ assertEquals(ComputationState.FINISHED, receivedEvents.get(3).getState());
+ }
+
+ /**
+ * This test checks that when a scheduler executes a long-runnning task that does not handle interrupts
+ * "gracefully", that is by taking care to stop its treatment ASAP, then the scheduler can nevertheless
+ * shutdown correctly.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testDemandShutdownWithLongRunningTaskThatInterruptsImproperly() throws Exception {
+ scheduler.initialize();
+ final CompStatus cs = new CompStatus();
+ final AtomicBoolean readyForFinalChecks = new AtomicBoolean(false);
+
+ // The following computation does not handle properly interruption
+ // so we expect its treatment to terminate properly with succeeded instead of failed
+ // The computation used here will set cs to "interrupted" when it is interrupted, but it will go on
+ // until cs.trigger() is called, which is done after all the checks are made in the test.
+ final TriggerableComputation tc = new TriggerableComputation(cs, "long1", false) {
+ @Override
+ protected void succeeded(Object r) {
+ cs.success("As expected");
+ }
+
+ @Override
+ protected void failed(Throwable t) {
+ cs.fail("failed() called, should have been succeeded().");
+ }
+ };
+
+ bus.register(new Object() {
+ @Subscribe
+ public void check(ShutdownStatus status) {
+ synchronized(scheduler) {
+ switch (status.getState()) {
+ case FINISH_SUCCESS:
+ cs.setShutdownStatus(status);
+ readyForFinalChecks.compareAndSet(false, true);
+ // This will trigger the execution of the final tests
+ scheduler.notifyAll();
+ break;
+ case FINISH_FAILED:
+ cs.setShutdownStatus(status);
+ readyForFinalChecks.compareAndSet(false, true);
+ scheduler.notifyAll();
+ }
+ }
+ }
+ });
+ try {
+ Integer result = scheduler.call(new Callable<Integer>() {
+
+ public Integer call() throws Exception {
+ scheduler.scheduleComputation(tc);
+ // We ask for shutdown before the task can complete
+ // The scheduler is configured to wait only 10ms
+ scheduler.demandShutdown();
+
+ return Integer.valueOf(42);
+ }
+ }, null);
+ assertEquals(Integer.valueOf(42), result);
+ assertTrue(scheduler.getComputedElements().isEmpty());
+
+ // This will wait for the shutdown to be over
+ // But the computation is still running
+ synchronized(scheduler) {
+ while (!readyForFinalChecks.get()) {
+ scheduler.wait();
+ }
+ assertEquals(1, cs.getCallCount());
+ assertTrue(cs.isInterrupted());
+ assertFalse(cs.isFailed());
+ // 2 next lines make sure the computation is still running
+ assertFalse(cs.isSuccess());
+ assertNull(cs.getMessage());
+ // Make sure the shutdown is ok
+ assertEquals(ShutdownState.FINISH_SUCCESS, cs.getShutdownStatus().getState());
+ }
+ } finally {
+ // We finally allow the tested computation, that is still running, to terminate
+ tc.trigger();
+ }
+ }
+
+ @Test
+ public void testDemandShutdownWithLongRunningTaskThatInterruptsGracefully() throws Exception {
+ scheduler.initialize();
+ final CompStatus cs = new CompStatus();
+ final AtomicBoolean readyForFinalChecks = new AtomicBoolean(false);
+
+ // The following computation handles interruption properly
+ // so we expect its treatment to terminate with failed
+ final TriggerableComputation tc = new TriggerableComputation(cs, "long1", true) {
+ @Override
+ protected void failed(Throwable t) {
+ cs.fail("As expected");
+ }
+
+ @Override
+ protected void succeeded(Object r) {
+ cs.success("Computation ends successfully when it should have failed.");
+ }
+ };
+ bus.register(new Object() {
+ @Subscribe
+ public void check(ShutdownStatus status) {
+ synchronized(scheduler) {
+ switch (status.getState()) {
+ case FINISH_SUCCESS:
+ cs.setShutdownStatus(status);
+ readyForFinalChecks.compareAndSet(false, true);
+ // This will trigger the execution of the final tests
+ scheduler.notifyAll();
+ break;
+ case FINISH_FAILED:
+ cs.setShutdownStatus(status);
+ readyForFinalChecks.compareAndSet(false, true);
+ scheduler.notifyAll();
+ }
+ }
+ }
+ });
+ Integer result = scheduler.call(new Callable<Integer>() {
+
+ public Integer call() throws Exception {
+ scheduler.scheduleComputation(tc);
+ // We ask for shutdown before the task can complete
+ // The scheduler is configured to wait only 100ms
+ scheduler.demandShutdown();
+
+ return Integer.valueOf(42);
+ }
+ }, null);
+
+ assertEquals(Integer.valueOf(42), result);
+ assertTrue(scheduler.getComputedElements().isEmpty());
+
+ synchronized(scheduler) {
+ // This will wait until the computation has finished to perform the final tests
+ while (!readyForFinalChecks.get()) {
+ scheduler.wait();
+ }
+ assertEquals(1, cs.getCallCount());
+ assertTrue(cs.isInterrupted());
+ assertTrue(cs.isFailed());
+ assertFalse(cs.isSuccess());
+ assertEquals("As expected", cs.getMessage());
+ }
+ }
+
+ @Test
+ public void testDemandShutdownWithRunningTaskThatTerminatesGracefully() throws Exception {
+ scheduler = new ResourceComputationScheduler<String>(1, TimeUnit.SECONDS, bus);
+ scheduler.initialize();
+ final CompStatus cs = new CompStatus();
+ final AtomicBoolean readyForFinalChecks = new AtomicBoolean(false);
+
+ // The following computation does not handle interruption properly
+ // but it has the time to terminate before the pools shut down
+ // so we expect to have it succeed
+ final TriggerableComputation tc = new TriggerableComputation(cs, "long1", false) {
+ @Override
+ protected void succeeded(Object r) {
+ getStatus().success("As expected");
+ }
+
+ @Override
+ protected void failed(Throwable t) {
+ getStatus().fail("failed() should not have been called.");
+ }
+ };
+ bus.register(new Object() {
+ @Subscribe
+ public void check(ShutdownStatus status) {
+ synchronized(scheduler) {
+ switch (status.getState()) {
+ case FINISH_SUCCESS:
+ cs.setShutdownStatus(status);
+ readyForFinalChecks.compareAndSet(false, true);
+ // This will trigger the execution of the final tests
+ scheduler.notifyAll();
+ break;
+ case FINISH_FAILED:
+ cs.setShutdownStatus(status);
+ readyForFinalChecks.compareAndSet(false, true);
+ scheduler.notifyAll();
+ }
+ }
+ }
+ });
+ Integer result = scheduler.call(new Callable<Integer>() {
+
+ public Integer call() throws Exception {
+ scheduler.scheduleComputation(tc);
+ // We ask for shutdown before the task can complete
+ // The scheduler is configured to wait 1s while the task only takes 500ms
+ scheduler.demandShutdown();
+ // This allows the test to make sure that the shutdown has been demanded before
+ // the treatment is over
+ tc.trigger();
+ return Integer.valueOf(42);
+ }
+ }, null);
+
+ assertEquals(Integer.valueOf(42), result);
+ assertTrue(scheduler.getComputedElements().isEmpty());
+
+ synchronized(scheduler) {
+ // This will wait until the computation has finished to perform the final tests
+ while (!readyForFinalChecks.get()) {
+ scheduler.wait();
+ }
+ assertEquals(1, cs.getCallCount());
+ assertFalse(cs.isInterrupted());
+ assertTrue(cs.isSuccess());
+ assertFalse(cs.isFailed());
+ assertEquals("As expected", cs.getMessage());
+ assertEquals(ShutdownState.FINISH_SUCCESS, cs.getShutdownStatus().getState());
+ }
+ }
+
+ @Override
+ @Before
+ public void setUp() {
+ bus = new EventBus();
+ scheduler = new ResourceComputationScheduler<String>(10, TimeUnit.MILLISECONDS, bus);
+ }
+
+ /**
+ * A test computation that sleeps until it's either triggered by a call to {@link #trigger()} or
+ * interrupted.
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ */
+ private static class TriggerableComputation implements IComputation<String> {
+ private volatile boolean start = false;
+
+ private final CompStatus cs;
+
+ private final String name;
+
+ private final boolean throwOnInterrupt;
+
+ public TriggerableComputation(CompStatus cs, String name, boolean throwOnInterrupt) {
+ this.cs = cs;
+ this.name = name;
+ this.throwOnInterrupt = throwOnInterrupt;
+ }
+
+ public void trigger() {
+ start = true;
+ }
+
+ public void run() {
+ cs.addCall();
+ while (!start) {
+ try {
+ Thread.sleep(1);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ cs.interrupt();
+ if (throwOnInterrupt) {
+ throw new RuntimeException("Interrupted");
+ }
+ }
+ }
+ }
+
+ public FutureCallback<Object> getPostTreatment() {
+ return new FutureCallback<Object>() {
+ public void onFailure(Throwable t) {
+ failed(t);
+ }
+
+ public void onSuccess(Object r) {
+ succeeded(r);
+ }
+ };
+ }
+
+ public CompStatus getStatus() {
+ return cs;
+ }
+
+ public String getKey() {
+ return name;
+ }
+
+ /**
+ * Override this method to specify specific expected behaviour in tests. Update variable cs to convey
+ * information about succes or failure, since using junit methods (fail, assertTrue, etc.) here will
+ * not cause a test to fail.
+ *
+ * @param t
+ */
+ protected void failed(Throwable t) {
+ cs.fail("As expected");
+ }
+
+ /**
+ * Override this method to specify specific expected behaviour in tests. Update variable cs to convey
+ * information about succes or failure, since using junit methods (fail, assertTrue, etc.) here will
+ * not cause a test to fail.
+ *
+ * @param t
+ */
+ protected void succeeded(@SuppressWarnings("unused") Object r) {
+ cs.success("As expected");
+ }
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/suite/AllTests.java b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/suite/AllTests.java
index 560fdc18a..26969f1e9 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/suite/AllTests.java
+++ b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/suite/AllTests.java
@@ -27,6 +27,7 @@ import org.eclipse.emf.compare.ide.ui.tests.logical.resolver.LocalMonitoredProxy
import org.eclipse.emf.compare.ide.ui.tests.logical.resolver.RemoteMonitoredProxyCreationListenerTest;
import org.eclipse.emf.compare.ide.ui.tests.logical.resolver.ResolutionEventsTest;
import org.eclipse.emf.compare.ide.ui.tests.logical.resolver.ResourceComputationSchedulerTest;
+import org.eclipse.emf.compare.ide.ui.tests.logical.resolver.ResourceComputationSchedulerWithEventBusTest;
import org.eclipse.emf.compare.ide.ui.tests.logical.resolver.ThreadedModelResolverGraphTest;
import org.eclipse.emf.compare.ide.ui.tests.logical.resolver.ThreadedModelResolverWithCustomDependencyProviderTest;
import org.eclipse.emf.compare.ide.ui.tests.structuremergeviewer.NavigatableTest;
@@ -47,10 +48,10 @@ import org.junit.runners.Suite.SuiteClasses;
@SuiteClasses({EMFCompareConfigurationTest.class, DependenciesTest.class, MergeActionTest.class,
PseudoConflictsMergeActionTest.class, BugsTestSuite.class, NavigatableTest.class,
NotLoadedFragmentNodeTest.class, NotLoadedFragmentItemTest.class, ResolutionEventsTest.class,
- ResourceComputationSchedulerTest.class, ThreadedModelResolverGraphTest.class,
- ThreadedModelResolverWithCustomDependencyProviderTest.class, DependencyGraphUpdaterTest.class,
- GraphResolutionTest.class, EMFModelProviderTest.class, MergeAllCommandTests.class,
- LocalMonitoredProxyCreationListenerTest.class,
+ ResourceComputationSchedulerTest.class, ResourceComputationSchedulerWithEventBusTest.class,
+ ThreadedModelResolverGraphTest.class, ThreadedModelResolverWithCustomDependencyProviderTest.class,
+ DependencyGraphUpdaterTest.class, GraphResolutionTest.class, EMFModelProviderTest.class,
+ MergeAllCommandTests.class, LocalMonitoredProxyCreationListenerTest.class,
RemoteMonitoredProxyCreationListenerTest.class })
public class AllTests {
/**
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceComputationScheduler.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceComputationScheduler.java
index d922e7523..20804a0ff 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceComputationScheduler.java
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceComputationScheduler.java
@@ -14,6 +14,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
+import com.google.common.eventbus.EventBus;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@@ -93,6 +94,9 @@ public class ResourceComputationScheduler<T> {
/** Unit of the above duration. */
private final TimeUnit shutdownWaitUnit;
+ /** Event bus used to send state change events */
+ private final EventBus eventBus;
+
/**
* Constructor, configured to wait for tasks completion for 5 seconds (will wait at most 10 seconds).
*/
@@ -110,12 +114,28 @@ public class ResourceComputationScheduler<T> {
* Unit to use to interpret the other parameter.
*/
public ResourceComputationScheduler(int shutdownWaitDuration, TimeUnit shutdownWaitUnit) {
+ this(shutdownWaitDuration, shutdownWaitUnit, null);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param shutdownWaitDuration
+ * Time to wait for current tasks completion when shutting down the pools (will wait at most
+ * twice this amount of time).
+ * @param shutdownWaitUnit
+ * Unit to use to interpret the other parameter.
+ * @param eventBus
+ * The {@link EventBus} used to post events (shutdown events), can be {@code null}
+ */
+ public ResourceComputationScheduler(int shutdownWaitDuration, TimeUnit shutdownWaitUnit, EventBus eventBus) {
this.lock = new ReentrantLock(true);
this.endOfTasks = lock.newCondition();
this.currentlyComputing = new HashSet<T>();
this.shutdownInProgress = new AtomicBoolean(false);
this.shutdownWaitDuration = shutdownWaitDuration;
this.shutdownWaitUnit = shutdownWaitUnit;
+ this.eventBus = eventBus;
}
/**
@@ -171,11 +191,20 @@ public class ResourceComputationScheduler<T> {
/**
* If {@link #shutdownInProgress shutdown has not been requested before}, it submits a new task to
* {@link #shutdownPools() shut down} {@link #computingPool} and {@link #unloadingPool}. Do nothing if
- * current thread already is interrupted.
+ * current thread already is interrupted. If a shutdown is actually started, events will be posted to the
+ * scheduler's eventBus if there is one. The events will be:
+ * <ol>
+ * <li>STARTED</li>
+ * <li>SUCCESS if shutdown has succeeded or FAILURE if shutdown has failed</li>
+ * </ol>
+ * <b>Note</b> that these events will be sent in the calling Thread.
*/
public void demandShutdown() {
if (!Thread.currentThread().isInterrupted()) {
if (shutdownInProgress.compareAndSet(false, true)) {
+ if (eventBus != null) {
+ eventBus.post(ShutdownStatus.STARTED);
+ }
Runnable runnable = new Runnable() {
public void run() {
shutdownPools();
@@ -186,10 +215,16 @@ public class ResourceComputationScheduler<T> {
Futures.addCallback(listenableFuture, new FutureCallback<Object>() {
public void onSuccess(Object result) {
shutdownInProgress.set(false);
+ if (eventBus != null) {
+ eventBus.post(ShutdownStatus.SUCCESS);
+ }
}
public void onFailure(Throwable t) {
shutdownInProgress.set(false);
+ if (eventBus != null) {
+ eventBus.post(new ShutdownStatus(t));
+ }
EMFCompareIDEUIPlugin.getDefault().log(t);
}
});
@@ -257,6 +292,16 @@ public class ResourceComputationScheduler<T> {
* <li>{@link #dispose()} has not been called</li>
* </ul>
* </p>
+ * If the scheduler has an eventBus, it will post the following events:
+ * <ol>
+ * <li>SETTING_UP</li>
+ * <li>SCHEDULED as soon as the set-up is finished</li>
+ * <li>FAILURE if and only if the given callable throws an exception</li>
+ * <li>FINISHING as soon as the given callable has finished running (successfully or not)</li>
+ * <li>FINISHED as soon as the tear-down is finished</li>
+ * </ol>
+ * <b>Note</b> that these events will be sent in the Thread that ran the computation, NOT in the calling
+ * Thread.
*
* @param callable
* will be executed as soon as this instance is no longer computing anything. Must not be
@@ -271,9 +316,18 @@ public class ResourceComputationScheduler<T> {
public synchronized <U> U call(Callable<U> callable, Runnable postTreatment) {
checkNotNull(callable);
try {
+ if (eventBus != null) {
+ eventBus.post(CallStatus.SETTING_UP);
+ }
setUpComputation();
+ if (eventBus != null) {
+ eventBus.post(CallStatus.SCHEDULED);
+ }
return callable.call();
} catch (Exception e) {
+ if (eventBus != null) {
+ eventBus.post(new CallStatus(e));
+ }
if (e instanceof InterruptedException) {
throw new OperationCanceledException();
}
@@ -282,9 +336,18 @@ public class ResourceComputationScheduler<T> {
}
throw new RuntimeException(e);
} finally {
- tearDownComputation();
- if (postTreatment != null) {
- postTreatment.run();
+ if (eventBus != null) {
+ eventBus.post(CallStatus.FINISHING);
+ }
+ try {
+ tearDownComputation();
+ if (postTreatment != null) {
+ postTreatment.run();
+ }
+ } finally {
+ if (eventBus != null) {
+ eventBus.post(CallStatus.FINISHED);
+ }
}
}
}
@@ -548,4 +611,81 @@ public class ResourceComputationScheduler<T> {
}
}
}
+
+ public static enum ComputationState {
+ /** Computation is setting-up (preparing pools and so on). */
+ SETTING_UP,
+ /** Computation is set-up and scheduled. */
+ SCHEDULED,
+ /** Computation is over, tear-down and post-treatments are starting. */
+ FINISHING,
+ /** Computation is over and has failed. */
+ FAILED,
+ /** Computation is over and tear-down + post-treatments are finished. */
+ FINISHED;
+ }
+
+ public static class CallStatus {
+ public static final CallStatus SETTING_UP = new CallStatus(ComputationState.SETTING_UP);
+
+ public static final CallStatus SCHEDULED = new CallStatus(ComputationState.SCHEDULED);
+
+ public static final CallStatus FINISHING = new CallStatus(ComputationState.FINISHING);
+
+ public static final CallStatus FINISHED = new CallStatus(ComputationState.FINISHED);
+
+ private final Throwable cause;
+
+ private final ComputationState state;
+
+ private CallStatus(ComputationState state) {
+ this.state = state;
+ this.cause = null;
+ }
+
+ private CallStatus(Throwable cause) {
+ this.state = ComputationState.FAILED;
+ this.cause = cause;
+ }
+
+ public Throwable getCause() {
+ return cause;
+ }
+
+ public ComputationState getState() {
+ return state;
+ }
+ }
+
+ public static enum ShutdownState {
+ STARTED, FINISH_FAILED, FINISH_SUCCESS;
+ }
+
+ public static class ShutdownStatus {
+ public static final ShutdownStatus STARTED = new ShutdownStatus(ShutdownState.STARTED);
+
+ public static final ShutdownStatus SUCCESS = new ShutdownStatus(ShutdownState.FINISH_SUCCESS);
+
+ private final Throwable cause;
+
+ private final ShutdownState state;
+
+ private ShutdownStatus(ShutdownState state) {
+ this.state = state;
+ this.cause = null;
+ }
+
+ private ShutdownStatus(Throwable cause) {
+ this.state = ShutdownState.FINISH_FAILED;
+ this.cause = cause;
+ }
+
+ public Throwable getCause() {
+ return cause;
+ }
+
+ public ShutdownState getState() {
+ return state;
+ }
+ }
}

Back to the top