diff --git a/plugins/org.eclipse.dd.examples.dsf/.externalToolBuilders/PreProcessor.launch b/plugins/org.eclipse.dd.examples.dsf/.externalToolBuilders/PreProcessor.launch index 948b857d441..ed027e9bf2d 100644 --- a/plugins/org.eclipse.dd.examples.dsf/.externalToolBuilders/PreProcessor.launch +++ b/plugins/org.eclipse.dd.examples.dsf/.externalToolBuilders/PreProcessor.launch @@ -3,16 +3,17 @@ - + - + + - + diff --git a/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/dataviewer/AsyncDataViewer.java b/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/dataviewer/AsyncDataViewer.java index 72feab635da..35f928b0b64 100644 --- a/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/dataviewer/AsyncDataViewer.java +++ b/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/dataviewer/AsyncDataViewer.java @@ -21,6 +21,8 @@ import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; +import org.eclipse.dd.dsf.concurrent.ConfinedToDsfExecutor; +import org.eclipse.dd.dsf.concurrent.ThreadSafe; import org.eclipse.dd.dsf.concurrent.DataRequestMonitor; import org.eclipse.dd.dsf.concurrent.DsfExecutor; import org.eclipse.dd.dsf.concurrent.ImmediateExecutor; @@ -51,10 +53,12 @@ import org.eclipse.swt.widgets.Table; * to check the canceled state of the requests and ignore them. *

*/ +@ConfinedToDsfExecutor("fDisplayExecutor") public class AsyncDataViewer implements ILazyContentProvider, IDataGenerator.Listener { // Executor to use instead of Display.asyncExec(). + @ThreadSafe final private DsfExecutor fDisplayExecutor; // The viewer and generator that this content provider using. @@ -106,10 +110,12 @@ public class AsyncDataViewer return Math.min((table.getBounds().height / table.getItemHeight()) + 2, itemCount - top); } + @ThreadSafe public void countChanged() { queryItemCount(); } + @ThreadSafe public void valuesChanged(final Set indexes) { // Mark the changed items in table viewer as dirty, this will // trigger update requests for these indexes if they are diff --git a/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/dataviewer/DataGeneratorWithExecutor.java b/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/dataviewer/DataGeneratorWithExecutor.java index fb70c5e15a4..d667b2810d4 100644 --- a/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/dataviewer/DataGeneratorWithExecutor.java +++ b/plugins/org.eclipse.dd.examples.dsf/src_preprocess/org/eclipse/dd/examples/dsf/dataviewer/DataGeneratorWithExecutor.java @@ -50,7 +50,7 @@ import org.eclipse.dd.examples.dsf.DsfExamplesPlugin; *

*/ //#ifdef excercises -// TODO Excercise 3 - Add an annotationindicating allowed concurrency access +// TODO Excercise 4 - Add an annotationindicating allowed concurrency access //#else //#@ThreadSafe //#endif @@ -59,7 +59,8 @@ public class DataGeneratorWithExecutor implements IDataGenerator { // Request objects are used to serialize the interface calls into objects // which can then be pushed into a queue. //#ifdef excercises - // TODO Excercise 3 - Add an annotationindicating allowed concurrency access + // TODO Excercise 4 - Add an annotationindicating allowed concurrency access + // Hint: Request and its subclasses have all their fields declared as final. //#else //# @Immutable //#endif @@ -72,7 +73,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 //# @Immutable //#endif @@ -83,7 +84,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 //# @Immutable //#endif @@ -97,7 +98,10 @@ public class DataGeneratorWithExecutor implements IDataGenerator { // The executor used to access all internal data of the generator. //#ifdef excercises - // TODO Excercise 3 - Add an annotation indicating allowed concurrency access + // TODO Excercise 4 - Add an annotation indicating allowed concurrency access + // Hint: If a member does not have an annotation, the programmer can assume + // that the concurrency rule that applies to the class also applies to this + // member. //#endif private DsfExecutor fExecutor; @@ -106,7 +110,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator { // method reads from it. // The executor used to access all internal data of the generator. //#ifdef excercises - // TODO Excercise 3 - Add an annotationindicating allowed concurrency access + // TODO Excercise 4 - Add an annotationindicating allowed concurrency access //#else //# @ConfinedToDsfExecutor("fExecutor") //#endif @@ -115,7 +119,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator { // List of listeners is not synchronized, it also has to be accessed // using the executor. //#ifdef excercises - // TODO Excercise 3 - Add an annotationindicating allowed concurrency access + // TODO Excercise 4 - Add an annotationindicating allowed concurrency access //#else //# @ConfinedToDsfExecutor("fExecutor") //#endif @@ -123,7 +127,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator { // Current number of elements in this generator. //#ifdef excercises - // TODO Excercise 3 - Add an annotationindicating allowed concurrency access + // TODO Excercise 4 - Add an annotationindicating allowed concurrency access //#else //# @ConfinedToDsfExecutor("fExecutor") //#endif @@ -131,7 +135,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator { // Counter used to determine when to reset the element count. //#ifdef excercises - // TODO Excercise 3 - Add an annotationindicating allowed concurrency access + // TODO Excercise 4 - Add an annotationindicating allowed concurrency access //#else //# @ConfinedToDsfExecutor("fExecutor") //#endif @@ -139,7 +143,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator { // Elements which were modified since the last reset. //#ifdef excercises - // TODO Excercise 3 - Add an annotationindicating allowed concurrency access + // TODO Excercise 4 - Add an annotationindicating allowed concurrency access //#else //# @ConfinedToDsfExecutor("fExecutor") //#endif @@ -147,14 +151,14 @@ public class DataGeneratorWithExecutor implements IDataGenerator { // Flag used to ensure that requests are processed sequentially. //#ifdef excercises - // TODO Excercise 3 - Add an annotationindicating allowed concurrency access + // TODO Excercise 4 - Add an annotationindicating allowed concurrency access //#else //# @ConfinedToDsfExecutor("fExecutor") //#endif private boolean fServiceQueueInProgress = false; //#ifdef excercises - // TODO Excercise 3 - Add an annotation indicating allowed concurrency access + // TODO Excercise 4 - Add an annotation indicating allowed concurrency access //#endif public DataGeneratorWithExecutor() { // Create the executor @@ -173,7 +177,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator { } //#ifdef excercises - // TODO Excercise 3 - Add an annotation indicating allowed concurrency access + // TODO Excercise 4 - Add an annotation indicating allowed concurrency access //#endif public void shutdown(final RequestMonitor rm) { try { @@ -199,7 +203,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator { } //#ifdef excercises - // TODO Excercise 3 - Add an annotation indicating allowed concurrency access + // TODO Excercise 4 - Add an annotation indicating allowed concurrency access //#endif public void getCount(final DataRequestMonitor rm) { try { @@ -216,7 +220,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator { } //#ifdef excercises - // TODO Excercise 3 - Add an annotation indicating allowed concurrency access + // TODO Excercise 4 - Add an annotation indicating allowed concurrency access //#endif public void getValue(final int index, final DataRequestMonitor rm) { try { @@ -233,7 +237,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator { } //#ifdef excercises - // TODO Excercise 3 - Add an annotation indicating allowed concurrency access + // TODO Excercise 4 - Add an annotation indicating allowed concurrency access //#endif public void addListener(final Listener listener) { try { @@ -246,7 +250,7 @@ public class DataGeneratorWithExecutor implements IDataGenerator { } //#ifdef excercises - // TODO Excercise 3 - Add an annotation indicating allowed concurrency access + // TODO Excercise 4 - Add an annotation indicating allowed concurrency access //#endif public void removeListener(final Listener listener) { try { @@ -260,14 +264,18 @@ public class DataGeneratorWithExecutor implements IDataGenerator { // Main processing function of this generator. //#ifdef excercises - // TODO Excercise 3 - Add an annotationindicating allowed concurrency access + // TODO Excercise 4 - Add an annotationindicating allowed concurrency access //#else //# @ConfinedToDsfExecutor("fExecutor") //#endif private void serviceQueue() { //#ifdef excercises - // TODO Excercise 4 - Add logic to discard requests from queue. + // TODO Excercise 3 - Add logic to discard requests from queue. + // Hint: Since serviceQueue() is called using the executor, and the + // fQueue list can only be modified when running in the executor + // thread. This method can safely iterate and modify fQueue without + // risk of race conditions or concurrent modification exceptions. //#else //# for (Iterator 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:

-
    -
  1. 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.
  2. -
  3. Next the content provider tells the viewer how many elements -there are, by calling TableViewer.setItemCount().
  4. -
  5. 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().
  6. -
  7. After calculating the value, the content provider tells the table -what the value is, by calling TableViewer.replace().
  8. -
  9. If the data ever changes, the content provider tells the table to -rerequest the data, by calling TableViewer.clear().
  10. -
-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:
-
    -
  1. The table viewer requests data for an item at a given index (SlowDataProviderContentProvider.updateElement).
    -
  2. -
  3. The table viewer's content provider executes a Runnable in the DSF -dispatch thread and calls the data provider interface (SlowDataProviderContentProvider.queryItemData).
  4. -
  5. Data provider service creates a Request object, and files it in a -queue (SlowDataProvider.getItem).
  6. -
  7. Data provider thread de-queues the Request object and acts on it, -calculating the value (ProviderThread.processItemRequest).
  8. -
  9. Data provider thread schedules the calculation result to be -posted with DSF executor (SlowDataProvider.java:185).
  10. -
  11. The RequestMonitor callback sets the result data in the table -viewer (SlowDataProviderContentProvider.java:167).
    -
  12. -
-

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:
-
    -
  1. Build the test plugin (along with the org.eclipse.dsdp.DSF plugin) -and launch the PDE. 
    -
  2. -
  3. Make sure to add the DSF -Tests action set to your current perspective.
  4. -
  5. From the main menu, select DSF -Tests -> Slow Data Provider.
  6. -
  7. A dialog will open and after a delay it will populate with data.
  8. -
  9. Scroll and resize dialog and observe the update behavior.
  10. -
-

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
-
    -
  1. 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.
  2. -
  3. 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. 
    -
  4. -
-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:
-
    -
  1. 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.
  2. -
  3. 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.
  4. -
-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); } }