requestItr = fQueue.iterator(); requestItr.hasNext();) {
//# Request request = requestItr.next();
@@ -311,7 +319,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
}
//#ifdef excercises
- // TODO Excercise 3 - Add an annotationindicating allowed concurrency access
+ // TODO Excercise 4 - Add an annotationindicating allowed concurrency access
//#else
//# @ConfinedToDsfExecutor("fExecutor")
//#endif
@@ -324,7 +332,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
}
//#ifdef excercises
- // TODO Excercise 3 - Add an annotationindicating allowed concurrency access
+ // TODO Excercise 4 - Add an annotationindicating allowed concurrency access
//#else
//# @ConfinedToDsfExecutor("fExecutor")
//#endif
@@ -344,7 +352,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
* This method simulates changes in the supplier's data set.
*/
//#ifdef excercises
- // TODO Excercise 3 - Add an annotationindicating allowed concurrency access
+ // TODO Excercise 4 - Add an annotationindicating allowed concurrency access
//#else
//# @ConfinedToDsfExecutor("fExecutor")
//#endif
@@ -362,7 +370,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
* Calculates new size for provider's data set.
*/
//#ifdef excercises
- // TODO Excercise 3 - Add an annotationindicating allowed concurrency access
+ // TODO Excercise 4 - Add an annotationindicating allowed concurrency access
//#else
//# @ConfinedToDsfExecutor("fExecutor")
//#endif
@@ -384,7 +392,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator {
* Invalidates a random range of indexes.
*/
//#ifdef excercises
- // TODO Excercise 3 - Add an annotationindicating allowed concurrency access
+ // TODO Excercise 4 - Add an annotationindicating allowed concurrency access
//#else
//# @ConfinedToDsfExecutor("fExecutor")
//#endif
diff --git a/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/dataviewer/IDataGenerator.java b/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/dataviewer/IDataGenerator.java
index e31c0edb981..e3eb61bc6b1 100644
--- a/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/dataviewer/IDataGenerator.java
+++ b/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/dataviewer/IDataGenerator.java
@@ -18,7 +18,9 @@ import java.util.Set;
import org.eclipse.dd.dsf.concurrent.DataRequestMonitor;
import org.eclipse.dd.dsf.concurrent.RequestMonitor;
-import org.eclipse.dd.dsf.concurrent.ThreadSafe;
+//#ifdef answers
+//#import org.eclipse.dd.dsf.concurrent.ThreadSafe;
+//#endif
/**
* Data generator is simple source of data used to populate the example table
@@ -28,7 +30,7 @@ import org.eclipse.dd.dsf.concurrent.ThreadSafe;
* is changed.
*/
//#ifdef excercises
-//TODO Excercise 3 - Add an annotationindicating allowed concurrency access
+//TODO Excercise 4 - Add an annotationindicating allowed concurrency access
//#else
//#@ThreadSafe
//#endif
diff --git a/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/dataviewer/SyncDataViewer.java b/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/dataviewer/SyncDataViewer.java
index f0c4c20729e..91e52cee3f9 100644
--- a/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/dataviewer/SyncDataViewer.java
+++ b/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/dataviewer/SyncDataViewer.java
@@ -122,6 +122,12 @@ public class SyncDataViewer
}
private void refreshViewer() {
+ //#ifdef excercises
+ // TODO Excercise 5 - Add a call to getElements() to force a deadlock.
+ //#else
+//# getElements(null);
+ //#endif
+
// This method may be called on any thread, switch to the display
// thread before calling the viewer.
Display display = fViewer.getControl().getDisplay();
@@ -148,7 +154,12 @@ public class SyncDataViewer
tableViewer.getControl().setLayoutData(data);
// Create the data generator.
+ //#ifdef excercises
+ // TODO Excercise 5 - Use the DataGeneratorWithExecutor() instead.
final IDataGenerator generator = new DataGeneratorWithThread();
+ //#else
+//# final IDataGenerator generator = new DataGeneratorWithExecutor();
+ //#endif
// Create the content provider which will populate the viewer.
SyncDataViewer contentProvider = new SyncDataViewer(tableViewer, generator);
diff --git a/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/dataviewer/doc-files/dsf_concurrency_model-1.png b/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/dataviewer/doc-files/dsf_concurrency_model-1.png
deleted file mode 100644
index 1bb373447d7..00000000000
Binary files a/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/dataviewer/doc-files/dsf_concurrency_model-1.png and /dev/null differ
diff --git a/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/dataviewer/package.html b/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/dataviewer/package.html
deleted file mode 100644
index f0efe705346..00000000000
--- a/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/dataviewer/package.html
+++ /dev/null
@@ -1,289 +0,0 @@
-
-
-
-
- DSF Slow Data Provider Example
-
-
-Version
-1.0
-Pawel Piech
-© 2006, Wind River Systems. Release
-under EPL version 1.0.
-Slow Data Provider Example
-The point of DSF concurrency can be most easily explained through
-a practical example. Suppose there is a viewer which needs to
-show data that originates from a remote "provider". There is a
-considerable delay in transmitting the data to and from the provider,
-and some delay in processing the data. The viewer is a
-lazy-loading table, which means that it request information only about
-items that are visible on the screen, and as the table is scrolled, new
-requests for data are generated. The diagram below illustrates
-the
-logical relationship between components:
-
-.
-In detail, these components look like this:
-
-Table Viewer
-The table viewer is the standard
-org.eclipse.jface.viewers.TableViewer,
-created with SWT.VIRTUAL
-flag. It has an associated content
-provider, SlowDataProviderContentProvider) which handles all the
-interactions with the data provider. The lazy content provider
-operates in a very simple cycle:
-
- - Table viewer tells content provider that the input has changed by
-calling IContentProvider.inputChanged().
-This means that the content provider has to query initial state of the
-data.
- - Next the content provider tells the viewer how many elements
-there are, by calling TableViewer.setItemCount().
- - At this point, the table resizes, and it requests data values for
-items that are visible. So for each visible item it calls: ILazyContentProvider.updateElement().
- - After calculating the value, the content provider tells the table
-what the value is, by calling TableViewer.replace().
- - If the data ever changes, the content provider tells the table to
-rerequest the data, by calling TableViewer.clear().
-
-Table viewer operates in the
-SWT display thread, which means that the content provider must switch
-from the display thread to the DSF dispatch thread, whenever it is
-called by the table viewer, as in the example below:
- public void updateElement(final int index) {
assert fTableViewer != null;
if (fDataProvider == null) return;
fDataProvider.getExecutor().execute(
new Runnable() { public void run() {
// Must check again, in case disposed while redispatching.
if (fDataProvider == null) return;
queryItemData(index);
}});
}
-Likewise, when the content provider calls the table viewer, it also has
-to switch back into the display thread as in following example, when
-the content provider receives an event from the data provider, that an
-item value has changed.
- public void dataChanged(final Set<Integer> indexes) {
// Check for dispose.
if (fDataProvider == null) return;
// Clear changed items in table viewer.
if (fTableViewer != null) {
final TableViewer tableViewer = fTableViewer;
tableViewer.getTable().getDisplay().asyncExec(
new Runnable() { public void run() {
// Check again if table wasn't disposed when
// switching to the display thread.
if (tableViewer.getTable().isDisposed()) return; // disposed
for (Integer index : indexes) {
tableViewer.clear(index);
}
}});
}
}
-All of this switching back and forth between threads makes the code
-look a lot more complicated than it really is, and it takes some
-getting used to, but this is the price to be paid for multi-threading.
-Whether the participants use semaphores or the dispatch thread, the
-logic is equally complicated, and we believe that using a single
-dispatch thread, makes the synchronization very explicit and thus less
-error-prone.
-Data Provider Service
-The data provider service interface, DataProvider, is very similar
-to that of the lazy content provider. It has methods to:
-
- - get item count
- - get a value for given item
- - register as listener for changes in data count and data values
-
-But this is a DSF interface, and all methods must be called on the
-service's dispatch thread. For this reason, the DataProvider interface returns
-an instance of DsfExecutor,
-which must be used with the interface.
-Slow Data Provider
-The data provider is actually implemented as a thread which is an
-inner class of SlowDataProvider
-service. The provider thread
-communicates with the service by reading Request objects from a shared
-queue, and by posting Runnable objects directly to the DsfExecutor but
-with a simulated transmission delay. Separately, an additional
-flag is also used to control the shutdown of the provider thread.
-To simulate a real back end, the data provider randomly invalidates a
-set of items and notifies the listeners to update themselves. It
-also periodically invalidates the whole table and forces the clients to
-requery all items.
-Data and Control Flow
-
-This can be described in following steps:
-
- - The table viewer requests data for an item at a given index (SlowDataProviderContentProvider.updateElement).
-
- - The table viewer's content provider executes a Runnable in the DSF
-dispatch thread and calls the data provider interface (SlowDataProviderContentProvider.queryItemData).
- - Data provider service creates a Request object, and files it in a
-queue (SlowDataProvider.getItem).
- - Data provider thread de-queues the Request object and acts on it,
-calculating the value (ProviderThread.processItemRequest).
- - Data provider thread schedules the calculation result to be
-posted with DSF executor (SlowDataProvider.java:185).
- - The RequestMonitor callback sets the result data in the table
-viewer (SlowDataProviderContentProvider.java:167).
-
-
-Running the example and full sources
-This example is implemented in the org.eclipse.dd.examples.dsf
-plugin, in the org.eclipse.dd.examples.dsf.concurrent
-package.
-
-To run the example:
-
- - Build the test plugin (along with the org.eclipse.dsdp.DSF plugin)
-and launch the PDE.
-
- - Make sure to add the DSF
-Tests action set to your current perspective.
- - From the main menu, select DSF
-Tests -> Slow Data Provider.
- - A dialog will open and after a delay it will populate with data.
- - Scroll and resize dialog and observe the update behavior.
-
-Initial Notes
-
-This example is supposed to be representative of a typical embedded
-debugger design problem. Embedded debuggers are often slow in
-retrieving and processing data, and can sometimes be accessed through a
-relatively slow data channel, such as serial port or JTAG
-connection. But as such, this basic example presents a couple
-of major usability problems
-
- - The data provider service interface mirrors the table's content
-provider interface, in that it has a method to retrieve a single piece
-of data at a time. The result of this is visible to the user as
-lines of data are filled in one-by-one in the table. However,
-most debugger back ends are in fact capable of retrieving data in
-batches and are much more efficient at it than retrieving data items
-one-by-one.
- - When scrolling quickly through the table, the requests are
-generated by the table viewer for items which are quickly scrolled out
-of view, but the service still queues them up and calculates them in
-the order they were received. As a result, it takes a very long
-time for the table to be populated with data at the location where the
-user is looking.
-
-
-These two problems are very common in creating UI for embedded
-debugging, and there are common patterns which can be used to solve
-these problems in DSF services.
-Coalescing
-Coalescing many single-item requests into fewer multi-item requests is
-the surest way to improve performance in communication with a remote
-debugger, although it's not necessarily the simplest. There are
-two basic patterns in which coalescing is achieved:
-
- - The back end provides an interface for retrieving data in large
-chunks. So when the service implementation receives a request for
-a single item, it retrieves a whole chunk of data, returns the single
-item, and stores the rest of the data in a local cache.
- - The back end providers an interface for retrieving data in
-variable size chunks. When the service implementation receives a
-request for a single item, it buffers the request, and waits for other
-requests to come in. After a delay, the service clears the buffer
-and submits a request for the combined items to the data provider.
-
-In practice, a combination of the two patterns is needed, but for
-purpose of an example, we implemented the second pattern in the
-"Input-Coalescing Slow Data Provider" (InputCoalescingSlowDataProvider.java).
-
-Input Buffer
-The main feature of this pattern is a buffer for holding the
-requests before sending them to the data provider. In this
-example the user requests are buffered in two arrays: fGetItemIndexesBuffer and fGetItemRequestMonitorsBuffer. The
-DataProvider.getItem()
-implementation is changed as follows:
- public void getItem(final int index, final DataRequestMonitor<String> rm) {
// Schedule a buffer-servicing call, if one is needed.
if (fGetItemRequestMonitorsBuffer.isEmpty()) {
fExecutor.schedule(
new Runnable() { public void run() {
fileBufferedRequests();
}},
COALESCING_DELAY_TIME,
TimeUnit.MILLISECONDS);
}
// Add the call data to the buffer.
// Note: it doesn't matter that the items were added to the buffer
// after the buffer-servicing request was scheduled. This is because
// the buffers are guaranteed not to be modified until this dispatch
// cycle is over.
fGetItemIndexesBuffer.add(index);
fGetItemRequestMonitorsBuffer.add(rm);
}
-And method that services the buffer looks like this:
- public void fileBufferedRequests() {
// Remove a number of getItem() calls from the buffer, and combine them
// into a request.
int numToCoalesce = Math.min(fGetItemIndexesBuffer.size(), COALESCING_COUNT_LIMIT);
final ItemRequest request = new ItemRequest(new Integer[numToCoalesce], new DataRequestMonitor[numToCoalesce]);
for (int i = 0; i < numToCoalesce; i++) {
request.fIndexes[i] = fGetItemIndexesBuffer.remove(0);
request.fDones[i] = fGetItemRequestMonitorsBuffer.remove(0);
}
// Queue the coalesced request, with the appropriate transmission delay.
fQueue.add(request);
// If there are still calls left in the buffer, execute another
// buffer-servicing call, but without any delay.
if (!fGetItemIndexesBuffer.isEmpty()) {
fExecutor.execute(new Runnable() { public void run() {
fileBufferedRequests();
}});
}
}
-The most interesting feature of this implementation is the fact that
-there are no semaphores anywhere to control access to the input
-buffers. Even though the buffers are serviced with a delay and
-multiple clients can call the getItem()
-method, the use of a single
-dispatch thread prevents any race conditions that could corrupt the
-buffer data. In real-world implementations, the buffers and
-caches that need to be used are far more sophisticated with much more
-complicated logic, and this is where managing access to them using the
-dispatch thread is ever more important.
-Cancellability
-Table Viewer
-
-Unlike coalescing, which can be implemented entirely within the
-service, cancellability requires that the client be modified as well
-to take advantage of this capability. For the table viewer
-content provider, this means that additional features have to be
-added. In CancellingSlowDataProviderContentProvider.java
-ILazyContentProvider.updateElement()
-was changes as follows:
- public void updateElement(final int index) {
assert fTableViewer != null;
if (fDataProvider == null) return;
// Calculate the visible index range.
final int topIdx = fTableViewer.getTable().getTopIndex();
final int botIdx = topIdx + getVisibleItemCount(topIdx);
fCancelCallsPending.incrementAndGet();
fDataProvider.getExecutor().execute(
new Runnable() { public void run() {
// Must check again, in case disposed while redispatching.
if (fDataProvider == null || fTableViewer.getTable().isDisposed()) return;
if (index >= topIdx && index <= botIdx) {
queryItemData(index);
}
cancelStaleRequests(topIdx, botIdx);
}});
}
-Now the client keeps track of the requests it made to the service in fItemDataDones, and above, cancelStaleRequests() iterates
-through all the outstanding requests and cancels the ones that are no
-longer in the visible range.
-Data Provider Service
-
-The data provider implementation
-(CancellableInputCoalescingSlowDataProvider.java),
-builds on top of the
-coalescing data provider. To make the canceling feature useful,
-the data provider service has to limit the size of the request
-queue. This is because in this example which simulates
-communication with a target and once requests are filed into the
-request
-queue, they cannot be canceled, just like a client can't cancel
-request once it sends them over a socket. So instead, if a flood
-of getItem()
-calls comes in, the service has to hold most of them in the coalescing
-buffer in case the client decides to cancel them. Therefore the
-fileBufferedRequests()
-method includes a simple check before servicing
-the buffer, and if the request queue is full, the buffer servicing call
-is delayed.
- if (fQueue.size() >= REQUEST_QUEUE_SIZE_LIMIT) {
if (fGetItemIndexesBuffer.isEmpty()) {
fExecutor.schedule(
new Runnable() { public void run() {
fileBufferedRequests();
}},
REQUEST_BUFFER_FULL_RETRY_DELAY,
TimeUnit.MILLISECONDS);
}
return;
}
-Beyond this change, the only other significant change is that before
-the requests are queued, they are checked for cancellation.
-Final Notes
-
-The example given here is fairly simplistic, and chances are that the
-same example could be implemented using semaphores and free threading
-with perhaps fewer lines of code. But what we have found is that
-as the problem gets bigger, the amount of
-features in the data provider increases, the state of the
-communication protocol gets more complicated, and the number of modules
-needed in the service layer increases, using free threading and
-semaphores does not safely scale. Using a dispatch thread for
-synchronization certainly doesn't make the inherent problems of the
-system less complicated, but it does help eliminate the race conditions
-and deadlocks from the overall system.
-Coalescing and Cancellability are both optimizations. Neither
-of these optimizations affected the original interface of the service,
-and one of them only needed a service-side modification. But as
-with all optimizations, it is often better to first make sure that the
-whole system is working correctly and then add optimizations where they
-can make the biggest difference in user experience.
-The above examples of optimizations can take many forms, and as
-mentioned with coalescing, caching data that is retrieved from the data
-provider is the most common form of data coalescing. For
-cancellation, many services in DSF build on top of other services,
-which means that even a low-level service can cause a higher
-level service to retrieve data, while another event might cause it to
-cancel those requests. The perfect example of this is a Variables
-service, which is responsible for calculating the value of expressions
-shown in the Variables view. The Variables service reacts to the
-Run Control service, which issues a suspended event and then requests a
-set of variables to be evaluated by the debugger back end. But as
-soon as a resumed event is issued by Run Control, the Variables service
-needs to cancel the pending evaluation requests.
-
-
-
-
-
diff --git a/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/requestmonitor/AsyncHelloWorld.java b/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/requestmonitor/AsyncHelloWorld.java
index b2272f97533..d7f08ea312e 100644
--- a/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/requestmonitor/AsyncHelloWorld.java
+++ b/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/requestmonitor/AsyncHelloWorld.java
@@ -38,7 +38,12 @@ public class AsyncHelloWorld {
static void asyncHelloWorld(RequestMonitor rm) {
System.out.println("Hello world");
//#ifdef excercises
- // TODO Exercise 1: - Call the second async. "Hello world 2" method.
+ // TODO Exercise 1: - Call the second async. "Hello world 2" method.
+ // Hint: Calling an asynchronous method requires passing to it a
+ // request monitor. A new request monitor can be constructed with
+ // a parent RequestMonitor as an argument argument. The parent gets
+ // completed automatically when the lower level request monitor is
+ // completed.
rm.done();
//#else
//# RequestMonitor rm2 = new RequestMonitor(ImmediateExecutor.getInstance(), rm);
diff --git a/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/requestmonitor/AsyncQuicksort.java b/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/requestmonitor/AsyncQuicksort.java
index 21f861e6a87..f1ff70d406c 100644
--- a/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/requestmonitor/AsyncQuicksort.java
+++ b/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/requestmonitor/AsyncQuicksort.java
@@ -34,7 +34,7 @@ public class AsyncQuicksort {
public static void main(String[] args) {
final int[] array = {5, 7, 8, 3, 2, 1, 9, 5, 4};
-
+
System.out.println("To sort: " + Arrays.toString(array));
asyncQuicksort(
array, 0, array.length - 1,
@@ -51,16 +51,19 @@ public class AsyncQuicksort {
{
if (right > left) {
int pivot = left;
- //#ifdef excercises
- // TODO: Request Monitors Exercise 2 - Convert partition to an async. method.
- int newPivot = partition(array, left, right, pivot);
- printArray(array, left, right, newPivot);
-
- CountingRequestMonitor countingRm = new CountingRequestMonitor(fgExecutor, rm);
- asyncQuicksort(array, left, newPivot - 1, countingRm);
- asyncQuicksort(array, newPivot + 1, right, countingRm);
- countingRm.setDoneCount(2);
- //#else
+ //#ifdef excercises
+ // TODO: Exercise 2 - Convert the call to partition into an
+ // asynchronous call to asyncPartition().
+ // Hint: The rest of the code below should be executed inside
+ // the DataRequestMonitor.handleCompleted() overriding method.
+ int newPivot = partition(array, left, right, pivot);
+ printArray(array, left, right, newPivot);
+
+ CountingRequestMonitor countingRm = new CountingRequestMonitor(fgExecutor, rm);
+ asyncQuicksort(array, left, newPivot - 1, countingRm);
+ asyncQuicksort(array, newPivot + 1, right, countingRm);
+ countingRm.setDoneCount(2);
+ //#else
//# asyncPartition(
//# array, left, right, pivot,
//# new DataRequestMonitor(fgExecutor, rm) {
@@ -75,15 +78,16 @@ public class AsyncQuicksort {
//# countingRm.setDoneCount(2);
//# }
//# });
- //#endif
-
+ //#endif
} else {
rm.done();
}
}
-
+
//#ifdef excercises
// TODO Exercise 2 - Convert partition to an asynchronous method.
+ // Hint: a DataRequestMonitor should be used to carry the
+ // return value to the caller.
static int partition(int[] array, int left, int right, int pivot)
//#else
//# static void asyncPartition(int[] array, int left, int right, int pivot, DataRequestMonitor rm)
@@ -103,16 +107,17 @@ public class AsyncQuicksort {
}
array[right] = array[store];
array[store] = pivotValue;
-
+
//#ifdef excercises
- // TODO: Request Monitors Exercise 2 - Convert partition to an async. method.
+ // TODO: Request Monitors Exercise 2 - Return the data to caller using
+ // a request monitor.
return store;
//#else
-//# rm.setData(store);
-//# rm.done();
+ //# rm.setData(store);
+ //# rm.done();
//#endif
}
-
+
static void printArray(int[] array, int left, int right, int pivot) {
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < array.length; i++ ) {
@@ -133,7 +138,7 @@ public class AsyncQuicksort {
buffer.append(' ');
}
}
-
+
System.out.println(buffer);
}
}