diff --git a/dsf/org.eclipse.cdt.dsf/src/org/eclipse/cdt/dsf/concurrent/RequestCache.java b/dsf/org.eclipse.cdt.dsf/src/org/eclipse/cdt/dsf/concurrent/RequestCache.java new file mode 100644 index 00000000000..634716b2e34 --- /dev/null +++ b/dsf/org.eclipse.cdt.dsf/src/org/eclipse/cdt/dsf/concurrent/RequestCache.java @@ -0,0 +1,123 @@ +package org.eclipse.cdt.dsf.concurrent; + +/******************************************************************************* + * Copyright (c) 2008 Wind River Systems, Inc. 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 + *******************************************************************************/ + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; + +/** + * A general purpose cache, which caches the result of a single request. + * Sub classes need to implement {@link #retrieve(DataRequestMonitor)} to fetch + * data from the data source. Clients are responsible for calling + * {@link #disable()} and {@link #reset()} to manage the state of the cache in + * response to events from the data source. + *

+ * This cache requires an executor to use. The executor is used to synchronize + * access to the cache state and data. + *

+ * @since 2.1 + */ +@ConfinedToDsfExecutor("fExecutor") +public abstract class RequestCache extends AbstractCache { + + protected DataRequestMonitor fRm; + + + public RequestCache(ImmediateInDsfExecutor executor) { + super(executor); + } + + /** + * Sub-classes should override this method to retrieve the cache data + * from its source. + * + * @param rm Request monitor for completion of data retrieval. + */ + @Override + protected void retrieve() { + // Make sure to cancel the previous rm. This may lead to the rm being + // canceled twice, but that's not harmful. + if (fRm != null) { + fRm.cancel(); + } + + fRm = new DataRequestMonitor(getExecutor(), null) { + + private IStatus fRawStatus = Status.OK_STATUS; + + @Override + protected void handleCompleted() { + if (this == fRm) { + fRm = null; + IStatus status; + synchronized (this) { + status = fRawStatus; + } + set(getData(), status); + } + } + + @Override + public synchronized void setStatus(IStatus status) { + fRawStatus = status; + }; + + @Override + public boolean isCanceled() { + return super.isCanceled() || RequestCache.this.isCanceled(); + }; + }; + retrieve(fRm); + } + + /** + * Sub-classes should override this method to retrieve the cache data + * from its source. + * + * @param rm Request monitor for completion of data retrieval. + */ + protected abstract void retrieve(DataRequestMonitor rm); + + @Override + protected synchronized void canceled() { + if (fRm != null) { + fRm.cancel(); + } + } + + @Override + public void reset(V data, IStatus status) { + if (fRm != null) { + fRm.cancel(); + fRm = null; + } + super.reset(data, status); + } + + @Override + public void disable() { + if (fRm != null) { + fRm.cancel(); + fRm = null; + } + super.disable(); + } + + @Override + public void set(V data, IStatus status) { + if (fRm != null) { + fRm.cancel(); + fRm = null; + } + super.set(data, status); + } +} diff --git a/dsf/org.eclipse.cdt.dsf/src/org/eclipse/cdt/dsf/concurrent/Transaction.java b/dsf/org.eclipse.cdt.dsf/src/org/eclipse/cdt/dsf/concurrent/Transaction.java new file mode 100644 index 00000000000..8de8d5ae07f --- /dev/null +++ b/dsf/org.eclipse.cdt.dsf/src/org/eclipse/cdt/dsf/concurrent/Transaction.java @@ -0,0 +1,183 @@ +/******************************************************************************* + * Copyright (c) 2008 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.dsf.concurrent; + +import org.eclipse.core.runtime.CoreException; + +/** + * @since 2.2 + */ +public abstract class Transaction { + + /** + * The exception we throw when the client transaction logic asks us to + * validate a cache object that is stale (or has never obtained a value from + * the source) + */ + private static final InvalidCacheException INVALID_CACHE_EXCEPTION = new InvalidCacheException(); + + /** The request object we've been given to set the transaction results in */ + private DataRequestMonitor fRm; + + public static class InvalidCacheException extends Exception { + private static final long serialVersionUID = 1L; + } + + /** + * Kicks off the transaction. We'll either complete the request monitor + * immediately if all the data points the transaction needs are cached and + * valid, or we'll end up asynchronously completing the monitor if and when + * either (a) all the data points are available and up-to-date, or (b) + * obtaining them from the source encountered an error. Note that there is + * potential in (b) for us to never complete the monitor. If one or more + * data points are perpetually becoming stale, then we'll indefinitely wait + * for them to stabilize. The caller should cancel its request monitor in + * order to get us to stop waiting. + * + * @param rm Request completion monitor. + */ + public void request(DataRequestMonitor rm) { + if (fRm != null) { + assert fRm.isCanceled(); + fRm.done(); + } + fRm = rm; + execute(); + } + + /** + * The transaction logic--code that tries to synchronously make use of, + * usually, multiple data points that are normally obtained asynchronously. + * Each data point is represented by a cache object. The transaction logic + * must check the validity of each cache object just prior to using it + * (calling its getData()). It should do that check by calling one of our + * validate() methods. Those methods will throw InvalidCacheException if the + * cached data is invalid (stale, e.g.,) or CoreException if an error was + * encountered the last time it got data form the source. The exception will + * abort the transaction, but in the case of InvalidCacheException, we + * schedule an asynchronous call that will re-invoke the transaction + * logic once the cache object has been updated from the source. + * + * @return the cached data if it's valid, otherwise an exception is thrown + * @throws InvalidCacheException + * @throws CoreException + */ + abstract protected V process() throws InvalidCacheException, CoreException; + + /** + * Method which invokes the transaction logic and handles any exception that + * may result. If that logic encounters a stale/unset cache object, then we + * simply do nothing. This method will be called again once the cache + * objects tell us it has obtained an updated value form the source. + */ + private void execute() { + if (fRm.isCanceled()) { + fRm.done(); + fRm = null; + return; + } + + try { + // Execute the transaction logic + V data = process(); + + // No exception means all cache objects used by the transaction + // were valid and up to date. Complete the request + fRm.setData(data); + fRm.done(); + fRm = null; + } + catch (CoreException e) { + // At least one of the cache objects encountered a failure obtaining + // the data from the source. Complete the request. + fRm.setStatus(e.getStatus()); + fRm.done(); + fRm = null; + } + catch (InvalidCacheException e) { + // At least one of the cache objects was stale/unset. Keep the + // request monitor in the incomplete state, thus leaving our client + // "waiting" (asynchronously). We'll get called again once the cache + // objects are updated, thus re-starting the whole transaction + // attempt. + } + } + + /** + * Clients must call one of our validate methods prior to using (calling + * getData()) on data cache object. + * + * @param cache + * the object being validated + * @throws InvalidCacheException + * if the data is stale/unset + * @throws CoreException + * if an error was encountered getting the data from the source + */ + protected void validate(RequestCache cache) throws InvalidCacheException, CoreException { + if (cache.isValid()) { + if (!cache.getStatus().isOK()) { + throw new CoreException(cache.getStatus()); + } + } else { + // Throw the invalid cache exception, but first schedule a + // re-attempt of the transaction logic, to occur when the + // stale/unset cache object has been updated + cache.wait(new RequestMonitor(ImmediateExecutor.getInstance(), fRm) { + @Override + protected void handleCompleted() { + execute(); + } + }); + throw INVALID_CACHE_EXCEPTION; + } + } + + /** + * See {@link #validate(RequestCache)}. This variant simply validates + * multiple cache objects. + */ + protected void validate(RequestCache ... caches) throws InvalidCacheException, CoreException { + // Check if any of the caches have errors: + boolean allValid = true; + + for (RequestCache cache : caches) { + if (cache.isValid()) { + if (!cache.getStatus().isOK()) { + throw new CoreException(cache.getStatus()); + } + } else { + allValid = false; + } + } + if (!allValid) { + // Throw the invalid cache exception, but first schedule a + // re-attempt of the transaction logic, to occur when the + // stale/unset cache objects have been updated + CountingRequestMonitor countringRm = new CountingRequestMonitor(ImmediateExecutor.getInstance(), fRm) { + @Override + protected void handleCompleted() { + execute(); + } + }; + int count = 0; + for (RequestCache cache : caches) { + if (!cache.isValid()) { + cache.wait(countringRm); + count++; + } + } + countringRm.setDoneCount(count); + throw INVALID_CACHE_EXCEPTION; + } + } + +}