1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-07-25 18:05:33 +02:00

Bug 329481: Inconsistency in DSF ACPM cancel behavior.

This commit is contained in:
John Cortell 2010-11-10 16:47:55 +00:00
parent 85b40d1d99
commit 5fa5ff3a81
3 changed files with 104 additions and 34 deletions

View file

@ -37,7 +37,7 @@ public abstract class AbstractCache<V> implements ICache<V> {
private class RequestCanceledListener implements RequestMonitor.ICanceledListener { private class RequestCanceledListener implements RequestMonitor.ICanceledListener {
public void requestCanceled(final RequestMonitor canceledRm) { public void requestCanceled(final RequestMonitor canceledRm) {
fExecutor.execute(new Runnable() { fExecutor.getDsfExecutor().execute(new Runnable() {
public void run() { public void run() {
handleCanceledRm(canceledRm); handleCanceledRm(canceledRm);
} }

View file

@ -12,7 +12,6 @@ package org.eclipse.cdt.dsf.concurrent;
*******************************************************************************/ *******************************************************************************/
import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
/** /**
* A general purpose cache, which caches the result of a single request. * A general purpose cache, which caches the result of a single request.
@ -46,25 +45,24 @@ public abstract class RequestCache<V> extends AbstractCache<V> {
fRm = new DataRequestMonitor<V>(getImmediateInDsfExecutor(), null) { fRm = new DataRequestMonitor<V>(getImmediateInDsfExecutor(), null) {
private IStatus fRawStatus = Status.OK_STATUS;
@Override @Override
protected void handleCompleted() { protected void handleCompleted() {
if (this == fRm) { if (this == fRm) {
fRm = null; fRm = null;
IStatus status; // If the requestor canceled the request, then leave the
synchronized (this) { // cache as is, regardless of how the retrieval completes.
status = fRawStatus; // We want the cache to stay in the invalid state so that
// it remains functional. The request may have completed
// successfully, and it may be tempting to use the result in
// that case, but that opens up a can of worms. We'll follow
// the behavior in RequestMonitor: when an RM is canceled,
// it's canceled; period.
if (!isCanceled()) {
set(getData(), getStatus());
} }
set(getData(), status);
} }
} }
@Override
public synchronized void setStatus(IStatus status) {
fRawStatus = status;
};
@Override @Override
public boolean isCanceled() { public boolean isCanceled() {
return super.isCanceled() || RequestCache.this.isCanceled(); return super.isCanceled() || RequestCache.this.isCanceled();

View file

@ -156,7 +156,7 @@ public class CacheTests {
Assert.assertFalse(fRetrieveRm.isCanceled()); Assert.assertFalse(fRetrieveRm.isCanceled());
} }
private void assertCacheCanceled() { private void assertCacheInvalidAndWithCanceledRM() {
Assert.assertFalse(fTestCache.isValid()); Assert.assertFalse(fTestCache.isValid());
try { try {
fTestCache.getData(); fTestCache.getData();
@ -435,9 +435,11 @@ public class CacheTests {
Assert.fail("Expected a cancellation exception"); Assert.fail("Expected a cancellation exception");
} catch (CancellationException e) {} // Expected exception; } catch (CancellationException e) {} // Expected exception;
assertCacheCanceled(); assertCacheInvalidAndWithCanceledRM();
// Completed the retrieve RM // 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() { fExecutor.submit(new DsfRunnable() {
public void run() { public void run() {
fRetrieveRm.setData(1); fRetrieveRm.setData(1);
@ -445,11 +447,79 @@ public class CacheTests {
} }
}).get(); }).get();
// Validate that cache accepts the canceled request data // Validate that cache didn't accept the result after its RM was canceled
assertCacheValidWithData(1); assertCacheInvalidAndWithCanceledRM();
} }
@Test @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();
Assert.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();
Assert.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 { public void cancelWhilePendingWithoutClientNotificationTest() throws InterruptedException, ExecutionException {
// Request data from cache // Request data from cache
Query<Integer> q = new Query<Integer>() { Query<Integer> q = new Query<Integer>() {
@ -479,7 +549,7 @@ public class CacheTests {
// Cancel the client request // Cancel the client request
q.cancel(true); q.cancel(true);
assertCacheCanceled(); assertCacheInvalidAndWithCanceledRM();
try { try {
q.get(); q.get();
@ -494,8 +564,8 @@ public class CacheTests {
} }
}).get(); }).get();
// Validate that cache accepts the canceled request data // Validate that cache didn't accept the result after its RM was canceled
assertCacheValidWithData(1); assertCacheInvalidAndWithCanceledRM();
} }
/** /**
@ -508,7 +578,6 @@ public class CacheTests {
public void cancelAfterCompletedRaceCondition() throws InterruptedException, ExecutionException { public void cancelAfterCompletedRaceCondition() throws InterruptedException, ExecutionException {
// Create a client request with a badly behaved cancel implementation. // Create a client request with a badly behaved cancel implementation.
@SuppressWarnings("unchecked")
final RequestMonitor[] rmBad = new RequestMonitor[1] ; final RequestMonitor[] rmBad = new RequestMonitor[1] ;
final boolean qBadCanceled[] = new boolean[] { false }; final boolean qBadCanceled[] = new boolean[] { false };
Query<Integer> qBad = new Query<Integer>() { Query<Integer> qBad = new Query<Integer>() {
@ -538,7 +607,8 @@ public class CacheTests {
// Avoid clearing cancel listeners list // Avoid clearing cancel listeners list
}; };
protected void handleSuccess() { @Override
protected void handleSuccess() {
rm.setData(fTestCache.getData()); rm.setData(fTestCache.getData());
rm.done(); rm.done();
}; };
@ -587,13 +657,15 @@ public class CacheTests {
@Test @Test
public void cancelWhilePendingWithTwoClientsTest() throws InterruptedException, ExecutionException { public void cancelWhilePendingWithTwoClientsTest() throws InterruptedException, ExecutionException {
// Request data from cache
// 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(); Query<Integer> q1 = new TestQuery();
fExecutor.execute(q1); fExecutor.submit(q1).get();
// Request data from cache again // Request data from cache again
Query<Integer> q2 = new TestQuery(); Query<Integer> q2 = new TestQuery();
fExecutor.execute(q2); fExecutor.submit(q2).get();
// Wait until the cache starts data retrieval. // Wait until the cache starts data retrieval.
@ -614,7 +686,7 @@ public class CacheTests {
Assert.fail("Expected a cancellation exception"); Assert.fail("Expected a cancellation exception");
} catch (CancellationException e) {} // Expected exception; } catch (CancellationException e) {} // Expected exception;
assertCacheCanceled(); assertCacheInvalidAndWithCanceledRM();
// Completed the retrieve RM // Completed the retrieve RM
fExecutor.submit(new DsfRunnable() { fExecutor.submit(new DsfRunnable() {
@ -624,8 +696,8 @@ public class CacheTests {
} }
}).get(); }).get();
// Validate that cache accepts the canceled request data // Validate that cache didn't accept the result after its RM was canceled
assertCacheValidWithData(1); assertCacheInvalidAndWithCanceledRM();
} }
@Test @Test
@ -634,7 +706,7 @@ public class CacheTests {
List<Query<Integer>> qList = new ArrayList<Query<Integer>>(); List<Query<Integer>> qList = new ArrayList<Query<Integer>>();
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
Query<Integer> q = new TestQuery(); Query<Integer> q = new TestQuery();
fExecutor.execute(q); fExecutor.submit(q).get();
qList.add(q); qList.add(q);
} }
@ -660,7 +732,7 @@ public class CacheTests {
// Replace canceled requests with new ones // Replace canceled requests with new ones
for (int i = 0; i < toCancel.length; i++) { for (int i = 0; i < toCancel.length; i++) {
Query<Integer> q = new TestQuery(); Query<Integer> q = new TestQuery();
fExecutor.execute(q); fExecutor.submit(q).get();
qList.set(toCancel[i], q); qList.set(toCancel[i], q);
assertCacheWaiting(); assertCacheWaiting();
} }
@ -672,7 +744,7 @@ public class CacheTests {
qList.get(i).cancel(true); qList.get(i).cancel(true);
} }
qList.get(qList.size() - 1).cancel(true); qList.get(qList.size() - 1).cancel(true);
assertCacheCanceled(); assertCacheInvalidAndWithCanceledRM();
// Completed the retrieve RM // Completed the retrieve RM
fExecutor.submit(new DsfRunnable() { fExecutor.submit(new DsfRunnable() {
@ -682,8 +754,8 @@ public class CacheTests {
} }
}).get(); }).get();
// Validate that cache accepts the canceled request data // Validate that cache didn't accept the result after its RM was canceled
assertCacheValidWithData(1); assertCacheInvalidAndWithCanceledRM();
} }
@Test @Test