blob: 077f6fd767ffe815bc1d0bb0b8108cbb44a1071e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006 Wind River Systems 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:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.tests.dsf.concurrent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor;
import org.eclipse.cdt.dsf.concurrent.ImmediateInDsfExecutor;
import org.eclipse.cdt.dsf.concurrent.Query;
import org.eclipse.cdt.dsf.concurrent.RequestCache;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.tests.dsf.DsfTestPlugin;
import org.eclipse.cdt.tests.dsf.TestDsfExecutor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* Tests that exercise the DataCache object.
*/
public class CacheTests {
TestDsfExecutor fExecutor;
TestCache fTestCache;
DataRequestMonitor<Integer> fRetrieveRm;
class TestCache extends RequestCache<Integer> {
public TestCache() {
super(new ImmediateInDsfExecutor(fExecutor));
}
@Override
protected void retrieve(DataRequestMonitor<Integer> rm) {
synchronized(CacheTests.this) {
fRetrieveRm = rm;
CacheTests.this.notifyAll();
}
}
@Override
protected void reset() {
super.reset();
}
@Override
public void set(Integer data, IStatus status) {
super.set(data, status);
}
}
class TestQuery extends Query<Integer> {
@Override
protected void execute(final DataRequestMonitor<Integer> rm) {
fTestCache.update(new RequestMonitor(ImmediateExecutor.getInstance(), rm) {
@Override
protected void handleSuccess() {
rm.setData(fTestCache.getData());
rm.done();
}
});
}
}
/**
* There's no rule on how quickly the cache has to start data retrieval
* after it has been requested. It could do it immediately, or it could
* wait a dispatch cycle, etc..
*/
private void waitForRetrieveRm() {
synchronized(this) {
while (fRetrieveRm == null) {
try {
wait();
} catch (InterruptedException e) {
return;
}
}
}
}
@Before
public void startExecutor() throws ExecutionException, InterruptedException {
fExecutor = new TestDsfExecutor();
fTestCache = new TestCache();
}
@After
public void shutdownExecutor() throws ExecutionException, InterruptedException {
fExecutor.submit(new DsfRunnable() { public void run() {
fExecutor.shutdown();
}}).get();
if (fExecutor.exceptionsCaught()) {
Throwable[] exceptions = fExecutor.getExceptions();
throw new ExecutionException(exceptions[0]);
}
fRetrieveRm = null;
fTestCache = null;
fExecutor = null;
}
private void assertCacheValidWithData(Object data) {
assertTrue(fTestCache.isValid());
assertEquals(data, fTestCache.getData());
assertTrue(fTestCache.getStatus().isOK());
}
private void assertCacheResetWithoutData() {
assertFalse(fTestCache.isValid());
try {
fTestCache.getData();
fail("Expected an IllegalStateException");
} catch (IllegalStateException e) {}
try {
fTestCache.getStatus();
fail("Expected an IllegalStateException");
} catch (IllegalStateException e) {}
}
private void assertCacheValidWithoutData() {
assertTrue(fTestCache.isValid());
assertEquals(null, fTestCache.getData());
assertFalse(fTestCache.getStatus().isOK());
assertEquals(fTestCache.getStatus().getCode(), ERRCODE_TARGET_RUNNING);
}
private void assertCacheWaiting() {
assertFalse(fTestCache.isValid());
try {
fTestCache.getData();
fail("Expected an IllegalStateException");
} catch (IllegalStateException e) {}
try {
fTestCache.getStatus();
fail("Expected an IllegalStateException");
} catch (IllegalStateException e) {}
assertFalse(fRetrieveRm.isCanceled());
}
private void assertCacheInvalidAndWithCanceledRM() {
assertFalse(fTestCache.isValid());
try {
fTestCache.getData();
fail("Expected an IllegalStateException");
} catch (IllegalStateException e) {}
try {
fTestCache.getStatus();
fail("Expected an IllegalStateException");
} catch (IllegalStateException e) {}
assertTrue(fRetrieveRm.isCanceled());
}
@Test
public void getWithCompletionInDsfThreadTest() throws InterruptedException, ExecutionException {
// Request data from cache
Query<Integer> q = new TestQuery();
// Check initial state
assertFalse(fTestCache.isValid());
fExecutor.execute(q);
// Wait until the cache requests the data.
waitForRetrieveRm();
// Check state while waiting for data
assertFalse(fTestCache.isValid());
// Complete the cache's retrieve data request.
fExecutor.submit(new Callable<Object>() { public Object call() {
fRetrieveRm.setData(1);
fRetrieveRm.done();
// Check that the data is available in the cache immediately
// (in the same dispatch cycle).
assertEquals(1, (int)fTestCache.getData());
assertTrue(fTestCache.isValid());
return null;
}}).get();
assertEquals(1, (int)q.get());
// Re-check final state
assertCacheValidWithData(1);
}
@Test
public void getTest() throws InterruptedException, ExecutionException {
// Check initial state
assertFalse(fTestCache.isValid());
// Request data from cache
Query<Integer> q = new TestQuery();
fExecutor.execute(q);
// Wait until the cache starts data retrieval.
waitForRetrieveRm();
// Check state while waiting for data
assertFalse(fTestCache.isValid());
// Set the data without using an executor.
fRetrieveRm.setData(1);
fRetrieveRm.done();
assertEquals(1, (int)q.get());
// Check final state
assertCacheValidWithData(1);
}
@Test
public void getTestWithTwoClients() throws InterruptedException, ExecutionException {
// Check initial state
assertFalse(fTestCache.isValid());
// Request data from cache
Query<Integer> q1 = new TestQuery();
fExecutor.execute(q1);
// Request data from cache again
Query<Integer> q2 = new TestQuery();
fExecutor.execute(q2);
// Wait until the cache starts data retrieval.
waitForRetrieveRm();
// Check state while waiting for data
assertFalse(fTestCache.isValid());
// Set the data without using an executor.
fRetrieveRm.setData(1);
fRetrieveRm.done();
assertEquals(1, (int)q1.get());
assertEquals(1, (int)q2.get());
// Check final state
assertCacheValidWithData(1);
}
@Test
public void getTestWithManyClients() throws InterruptedException, ExecutionException {
// Check initial state
assertFalse(fTestCache.isValid());
// Request data from cache
List<Query<Integer>> qList = new ArrayList<Query<Integer>>();
for (int i = 0; i < 10; i++) {
Query<Integer> q = new TestQuery();
fExecutor.execute(q);
qList.add(q);
}
// Wait until the cache starts data retrieval.
waitForRetrieveRm();
// Check state while waiting for data
assertFalse(fTestCache.isValid());
// Set the data without using an executor.
fRetrieveRm.setData(1);
fRetrieveRm.done();
for (Query<Integer> q : qList) {
assertEquals(1, (int)q.get());
}
// Check final state
assertCacheValidWithData(1);
}
private static final int ERRCODE_TARGET_RUNNING = 1234;
private static final Status STATUS_TARGET_RUNNING = new Status(Status.ERROR, DsfTestPlugin.PLUGIN_ID, ERRCODE_TARGET_RUNNING, "Target is running", null);
// DISABLE TESTS
//
// We say a cache is "disabled" when its most recent attempt to update from
// the source failed. Also, a cache may make itself disabled as a reaction
// to a state change notification from its source (e.g., the target
// resumed). In either case, the cache is in the valid state but it has no
// data and the status reflects an error. Keep in mind that the 'valid'
// state is not a reflection of the quality of the data, but merely whether
// the cache object's representation of the data is stale or
// not. A transaction that uses a "disabled" cache object will simply fail;
// it will not ask the cache to update its data from the source. Only a
// change in the source's state would cause the cache to put itself back in
// the invalid state, thus opening the door to another update.
/**
* Test behavior when a cache object is asked to update itself after it has
* become "disabled". Since a "disabled" cache is in the valid state, a
* request for it to update from the source should be ignored.
*/
@Test
public void disableBeforeRequestTest() throws InterruptedException, ExecutionException {
// Disable the cache
fExecutor.submit(new DsfRunnable() {
public void run() {
fTestCache.set(null, STATUS_TARGET_RUNNING);
}
}).get();
assertCacheValidWithoutData();
// Try to request data from cache
Query<Integer> q = new TestQuery();
fExecutor.execute(q);
Thread.sleep(100);
// Retrieval should never have been made.
assertEquals(null, fRetrieveRm);
// The cache has no data so the query should have failed
try {
q.get();
fail("expected an exeption");
} catch (ExecutionException e) {
// expected the exception
}
}
/**
* Test behavior when a cache object goes into the "disabled" state while an
* update request is ongoing. The subsequent completion of the request should
* have no effect on the cache
*/
@Test
public void disableWhilePendingTest() throws InterruptedException, ExecutionException {
// Request data from cache
Query<Integer> q = new TestQuery();
fExecutor.execute(q);
// Disable the cache
fExecutor.submit(new DsfRunnable() {
public void run() {
fTestCache.set(null, STATUS_TARGET_RUNNING);
}
}).get();
assertCacheValidWithoutData();
// Complete the retrieve RM. Note that the disabling of the cache above
// disassociates it from its retrieval RM. Thus regardless of how that
// request completes, it does not affect the cache.
fExecutor.submit(new DsfRunnable() {
public void run() {
fRetrieveRm.setData(1);
fRetrieveRm.done();
}
}).get();
// Validate that cache is still disabled without data.
assertCacheValidWithoutData();
}
/**
* Test behavior when a cache object goes into the "disabled" state while
* it's in the valid state. The cache remains in the valid state but it
* loses its data and obtains an error status.
*/
@Test
public void disableWhileValidTest() throws InterruptedException, ExecutionException {
// Request data from cache
Query<Integer> q = new TestQuery();
fExecutor.execute(q);
// Wait until the cache starts data retrieval.
waitForRetrieveRm();
// Complete the request
fRetrieveRm.setData(1);
fRetrieveRm.done();
assertEquals(Integer.valueOf(1), q.get());
// Disable the cache
fExecutor.submit(new DsfRunnable() {
public void run() {
fTestCache.set(null, STATUS_TARGET_RUNNING);
}
}).get();
// Check final state
assertCacheValidWithoutData();
}
@Test
public void setWithValueTest() throws InterruptedException, ExecutionException {
// Disable the cache
fExecutor.submit(new DsfRunnable() {
public void run() {
fTestCache.set(2, Status.OK_STATUS);
}
}).get();
// Validate that cache is disabled without data.
assertCacheValidWithData(2);
}
@Test
public void cancelWhilePendingTest() throws InterruptedException, ExecutionException {
// Request data from cache
Query<Integer> q = new TestQuery();
fExecutor.execute(q);
// Wait until the cache starts data retrieval.
waitForRetrieveRm();
// Cancel the client request
q.cancel(true);
try {
q.get();
fail("Expected a cancellation exception");
} catch (CancellationException e) {} // Expected exception;
assertCacheInvalidAndWithCanceledRM();
// Simulate the retrieval completing successfully despite the cancel
// request. Perhaps the retrieval logic isn't checking the RM status.
// Even if it is checking, it may have gotten passed its last checkpoint
fExecutor.submit(new DsfRunnable() {
public void run() {
fRetrieveRm.setData(1);
fRetrieveRm.done();
}
}).get();
// Validate that cache didn't accept the result after its RM was canceled
assertCacheInvalidAndWithCanceledRM();
}
@Test
public void cancelWhilePendingTest2() throws InterruptedException, ExecutionException {
// Request data from cache
Query<Integer> q = new TestQuery();
fExecutor.execute(q);
// Wait until the cache starts data retrieval.
waitForRetrieveRm();
// Cancel the client request
q.cancel(true);
try {
q.get();
fail("Expected a cancellation exception");
} catch (CancellationException e) {} // Expected exception;
assertCacheInvalidAndWithCanceledRM();
// Simulate retrieval logic that is regularly checking the RM's cancel
// status and has discovered that the request has been canceled. It
// technically does not need to explicitly set a cancel status object in
// the RM, thanks to RequestMonitor.getStatus() automatically returning
// Status.CANCEL_STATUS when its in the cancel state. So here we
// simulate the retrieval logic just aborting its operations and
// completing the RM. Note that it hasn't provided the data to the
// cache.
fExecutor.submit(new DsfRunnable() {
public void run() {
fRetrieveRm.done();
}
}).get();
assertCacheInvalidAndWithCanceledRM();
}
@Test
public void cancelWhilePendingTest3() throws InterruptedException, ExecutionException {
// Request data from cache
Query<Integer> q = new TestQuery();
fExecutor.execute(q);
// Wait until the cache starts data retrieval.
waitForRetrieveRm();
// Cancel the client request
q.cancel(true);
try {
q.get();
fail("Expected a cancellation exception");
} catch (CancellationException e) {} // Expected exception;
assertCacheInvalidAndWithCanceledRM();
// Simulate retrieval logic that is regularly checking the RM's cancel
// status and has discovered that the request has been canceled. It
// aborts its processing, sets STATUS.CANCEL_STATUS in the RM and
// completes it.
fExecutor.submit(new DsfRunnable() {
public void run() {
fRetrieveRm.setStatus(Status.CANCEL_STATUS);
fRetrieveRm.done();
}
}).get();
// Validate that cache didn't accept the result after its RM was canceled
assertCacheInvalidAndWithCanceledRM();
}
@Test
public void cancelWhilePendingWithoutClientNotificationTest() throws InterruptedException, ExecutionException {
final boolean canceledCalled[] = new boolean[] { false };
fTestCache = new TestCache() {
@Override
protected synchronized void canceled() {
canceledCalled[0] = true;
};
};
// Request data from cache
Query<Integer> q = new Query<Integer>() {
@Override
protected void execute(final DataRequestMonitor<Integer> rm) {
fTestCache.update(new RequestMonitor(ImmediateExecutor.getInstance(), rm) {
@Override
public synchronized void addCancelListener(ICanceledListener listener) {
// Do not add the cancel listener so that the cancel request is not
// propagated to the cache.
}
@Override
protected void handleSuccess() {
rm.setData(fTestCache.getData());
rm.done();
}
});
}
};
fExecutor.execute(q);
// Wait until the cache starts data retrieval.
waitForRetrieveRm();
// Cancel the client request
q.cancel(true);
assertCacheInvalidAndWithCanceledRM();
// AbstractCache.canceled() should be called after isCanceled()
// discovers that the client has canceled its request. The canceled() method is
// called in a separate dispatch cycle, so we have to wait one cycle of the executor
// after is canceled is called.
fRetrieveRm.isCanceled();
fExecutor.submit(new Runnable() { public void run() {} }).get();
assertTrue(canceledCalled[0]);
try {
q.get();
fail("Expected a cancellation exception");
} catch (CancellationException e) {} // Expected exception;
// Completed the retrieve RM
fExecutor.submit(new DsfRunnable() {
public void run() {
fRetrieveRm.setData(1);
fRetrieveRm.done();
}
}).get();
// Validate that cache didn't accept the result after its RM was canceled
assertCacheInvalidAndWithCanceledRM();
}
/**
* This test forces a race condition where a client that requested data
* cancels. While shortly after a second client starts a new request.
* The first request's cancel should not interfere with the second
* request.
*/
@Test
public void cancelAfterCompletedRaceCondition() throws InterruptedException, ExecutionException {
// Create a client request with a badly behaved cancel implementation.
final RequestMonitor[] rmBad = new RequestMonitor[1] ;
final boolean qBadCanceled[] = new boolean[] { false };
Query<Integer> qBad = new Query<Integer>() {
@Override
protected void execute(final DataRequestMonitor<Integer> rm) {
rmBad[0] = new RequestMonitor(ImmediateExecutor.getInstance(), rm) {
@Override
public synchronized void removeCancelListener(ICanceledListener listener) {
// Do not add the cancel listener so that the cancel request is not
// propagated to the cache.
}
@Override
public void cancel() {
if (qBadCanceled[0]) {
super.cancel();
}
}
@Override
public synchronized boolean isCanceled() {
return qBadCanceled[0];
}
@Override
public synchronized void done() {
// Avoid clearing cancel listeners list
};
@Override
protected void handleSuccess() {
rm.setData(fTestCache.getData());
rm.done();
};
};
fTestCache.update(rmBad[0]);
}
};
fExecutor.execute(qBad);
// Wait until the cache starts data retrieval.
waitForRetrieveRm();
// Reset the cache
fExecutor.submit(new DsfRunnable() {
public void run() {
fRetrieveRm = null;
fTestCache.set(null, Status.OK_STATUS);
fTestCache.reset();
}
}).get();
Query<Integer> qGood = new TestQuery();
fExecutor.execute(qGood);
// Wait until the cache starts data retrieval.
waitForRetrieveRm();
qBadCanceled[0] = true;
rmBad[0].cancel();
assertFalse(fRetrieveRm.isCanceled());
// Completed the retrieve RM
fExecutor.submit(new DsfRunnable() {
public void run() {
fRetrieveRm.setData(1);
fRetrieveRm.done();
}
}).get();
qGood.get();
assertCacheValidWithData(1);
}
@Test
public void cancelWhilePendingWithTwoClientsTest() throws InterruptedException, ExecutionException {
// Request data from cache. Use submit/get to ensure both update
// requests are initiated before we wait for retrieval to start
Query<Integer> q1 = new TestQuery();
fExecutor.submit(q1).get();
// Request data from cache again
Query<Integer> q2 = new TestQuery();
fExecutor.submit(q2).get();
// Wait until the cache starts data retrieval.
waitForRetrieveRm();
// Cancel the first client request
q1.cancel(true);
try {
q1.get();
fail("Expected a cancellation exception");
} catch (CancellationException e) {} // Expected exception;
assertCacheWaiting();
// Cancel the second request
q2.cancel(true);
try {
q2.get();
fail("Expected a cancellation exception");
} catch (CancellationException e) {} // Expected exception;
assertCacheInvalidAndWithCanceledRM();
// Completed the retrieve RM
fExecutor.submit(new DsfRunnable() {
public void run() {
fRetrieveRm.setData(1);
fRetrieveRm.done();
}
}).get();
// Validate that cache didn't accept the result after its RM was canceled
assertCacheInvalidAndWithCanceledRM();
}
@Test
public void cancelWhilePendingWithManyClientsTest() throws InterruptedException, ExecutionException {
// Request data from cache
List<Query<Integer>> qList = new ArrayList<Query<Integer>>();
for (int i = 0; i < 10; i++) {
Query<Integer> q = new TestQuery();
fExecutor.submit(q).get();
qList.add(q);
}
// Wait until the cache starts data retrieval.
waitForRetrieveRm();
// Cancel some client requests
int[] toCancel = new int[] { 0, 2, 5, 9};
for (int i = 0; i < toCancel.length; i++) {
// Cancel request and verify that its canceled
Query<Integer> q = qList.get(toCancel[i]);
q.cancel(true);
try {
q.get();
fail("Expected a cancellation exception");
} catch (CancellationException e) {} // Expected exception;
qList.set(toCancel[i], null);
assertCacheWaiting();
}
// Replace canceled requests with new ones
for (int i = 0; i < toCancel.length; i++) {
Query<Integer> q = new TestQuery();
fExecutor.submit(q).get();
qList.set(toCancel[i], q);
assertCacheWaiting();
}
// Now cancel all requests
for (int i = 0; i < (qList.size() - 1); i++) {
// Validate that cache is still waiting and is not canceled
assertCacheWaiting();
qList.get(i).cancel(true);
}
qList.get(qList.size() - 1).cancel(true);
assertCacheInvalidAndWithCanceledRM();
// Completed the retrieve RM
fExecutor.submit(new DsfRunnable() {
public void run() {
fRetrieveRm.setData(1);
fRetrieveRm.done();
}
}).get();
// Validate that cache didn't accept the result after its RM was canceled
assertCacheInvalidAndWithCanceledRM();
}
@Test
public void resetWhileValidTest() throws InterruptedException, ExecutionException {
// Request data from cache
Query<Integer> q = new TestQuery();
fExecutor.execute(q);
// Wait until the cache starts data retrieval.
waitForRetrieveRm();
// Complete the request
fRetrieveRm.setData(1);
fRetrieveRm.done();
q.get();
// Disable cache
fExecutor.submit(new DsfRunnable() {
public void run() {
fTestCache.reset();
}
}).get();
// Check final state
assertCacheResetWithoutData();
}
}