1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-04-29 19:45:01 +02:00

[220446] Updated the "DSF Common Patterns" document.

This commit is contained in:
Pawel Piech 2008-03-01 00:51:12 +00:00
parent f4f4a35474
commit 90f4dc4c71
9 changed files with 88 additions and 339 deletions

View file

@ -3,16 +3,17 @@
<booleanAttribute key="org.eclipse.ant.ui.ATTR_TARGETS_UPDATED" value="true"/>
<booleanAttribute key="org.eclipse.ant.ui.DEFAULT_VM_INSTALL" value="false"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/org.eclipse.dd.examples.dsf"/>
<listEntry value="/org.eclipse.dd.examples.dsf/build_preprocess.xml"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
<listEntry value="1"/>
</listAttribute>
<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.eclipse.ant.ui.AntClasspathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="true"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="org.eclipse.dd.examples.dsf"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_BUILD_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;launchConfigurationWorkingSet factoryID=&quot;org.eclipse.ui.internal.WorkingSetFactory&quot; label=&quot;workingSet&quot; name=&quot;workingSet&quot;&gt;&#10;&lt;item factoryID=&quot;org.eclipse.ui.internal.model.ResourceFactory&quot; path=&quot;/org.eclipse.dd.examples.dsf/src_preprocess&quot; type=&quot;2&quot;/&gt;&#10;&lt;/launchConfigurationWorkingSet&gt;}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/org.eclipse.dd.examples.dsf/build_preprocess.xml}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value=""/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
</launchConfiguration>

View file

@ -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.
* </p>
*/
@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<Integer> indexes) {
// Mark the changed items in table viewer as dirty, this will
// trigger update requests for these indexes if they are

View file

@ -50,7 +50,7 @@ import org.eclipse.dd.examples.dsf.DsfExamplesPlugin;
* </p>
*/
//#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<Integer> 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<String> 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<Request> 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

View file

@ -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

View file

@ -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);

View file

@ -1,289 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta content="text/html; charset=ISO-8859-1"
http-equiv="content-type">
<title>DSF Slow Data Provider Example</title>
</head>
<body>
<p class="MsoNormal" style="line-height: normal;"><b><span
style="font-size: 12pt; font-family: &quot;Times New Roman&quot;;">Version
1.0<br>
Pawel Piech<br>
&copy; 2006, Wind River Systems.<span style="">&nbsp; </span>Release
under EPL version 1.0.</span></b><b><span
style="font-size: 18pt; font-family: &quot;Times New Roman&quot;;"><o:p></o:p></span></b></p>
<h3>Slow Data Provider Example</h3>
The point of DSF concurrency can be most easily explained through
a practical example.&nbsp; Suppose there is a viewer which needs to
show data that originates from a remote "provider".&nbsp; There is a
considerable delay in transmitting the data to and from the provider,
and some delay in processing the data.&nbsp; 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.&nbsp; The diagram below illustrates
the
logical relationship between components:<br>
<br>
.<img alt="" title="Slow Data Provider Diagram"
src="doc-files%5Cdsf_concurrency_model-1.png"
style="width: 636px; height: 128px;"><br>
<p>In detail, these components look like this:<span
style="text-decoration: underline;"></span></p>
<p><span style="text-decoration: underline;"></span></p>
<span style="text-decoration: underline;">Table Viewer</span><br>
<p>The table viewer is the standard
<span style="font-family: monospace;">org.eclipse.jface.viewers.TableViewer</span>,
created with <span style="font-family: monospace;">SWT.VIRTUAL</span>
flag.&nbsp; It has an associated content
provider, SlowDataProviderContentProvider) which handles all the
interactions with the data provider.&nbsp; The lazy content provider
operates in a very simple cycle:</p>
<ol>
<li>Table viewer tells content provider that the input has changed by
calling <span style="font-family: monospace;">IContentProvider.inputChanged()</span>.&nbsp;
This means that the content provider has to query initial state of the
data.</li>
<li>Next the content provider tells the viewer how many elements
there are, by calling <span style="font-family: monospace;">TableViewer.setItemCount()</span>.</li>
<li>At this point, the table resizes, and it requests data values for
items that are visible.&nbsp; So for each visible item it calls: <span
style="font-family: monospace;">ILazyContentProvider.updateElement()</span>.</li>
<li>After calculating the value, the content provider tells the table
what the value is, by calling <span style="font-family: monospace;">TableViewer.replace().</span></li>
<li>If the data ever changes, the content provider tells the table to
rerequest the data, by calling <span style="font-family: monospace;">TableViewer.clear()</span>.</li>
</ol>
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:<br>
<pre> public void updateElement(final int index) {<br> assert fTableViewer != null;<br> if (fDataProvider == null) return;<br><br> fDataProvider.getExecutor().execute(<br> new Runnable() { public void run() {<br> // Must check again, in case disposed while redispatching.<br> if (fDataProvider == null) return;<br> <br> queryItemData(index);<br> }});<br> }<br></pre>
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.<br>
<pre> public void dataChanged(final Set&lt;Integer&gt; indexes) {<br> // Check for dispose.<br> if (fDataProvider == null) return;<br><br> // Clear changed items in table viewer.<br> if (fTableViewer != null) {<br> final TableViewer tableViewer = fTableViewer;<br> tableViewer.getTable().getDisplay().asyncExec(<br> new Runnable() { public void run() {<br> // Check again if table wasn't disposed when <br> // switching to the display thread.<br> if (tableViewer.getTable().isDisposed()) return; // disposed<br> for (Integer index : indexes) {<br> tableViewer.clear(index);<br> }<br> }});<br> }<br> }<br></pre>
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.<br>
<p><span style="text-decoration: underline;">Data Provider Service</span></p>
<p>The data provider service interface, <span
style="font-family: monospace;">DataProvider</span>, is very similar
to that of the lazy content provider.&nbsp; It has methods to: </p>
<ul>
<li>get item count</li>
<li>get a value for given item</li>
<li>register as listener for changes in data count and data values</li>
</ul>
But this is a DSF interface, and all methods must be called on the
service's dispatch thread.&nbsp; For this reason, the <span
style="font-family: monospace;">DataProvider </span>interface returns
an instance of <span style="font-family: monospace;">DsfExecutor</span>,
which must be used with the interface.<br>
<p><span style="text-decoration: underline;">Slow Data Provider</span></p>
<p>The data provider is actually implemented as a thread which is an
inner class of <span style="font-family: monospace;">SlowDataProvider</span>
service.&nbsp; The provider thread
communicates with the service by reading Request objects from a shared
queue, and by posting Runnable objects directly to the <span
style="font-family: monospace;">DsfExecutor</span> but
with a simulated transmission delay.&nbsp; Separately, an additional
flag is also used to control the shutdown of the provider thread.</p>
To simulate a real back end, the data provider randomly invalidates a
set of items and notifies the listeners to update themselves.&nbsp; It
also periodically invalidates the whole table and forces the clients to
requery all items.<br>
<h4>Data and Control Flow<br>
</h4>
This can be described in following steps:<br>
<ol>
<li>The table viewer requests data for an item at a given index (<span
style="font-family: monospace;">SlowDataProviderContentProvider.updateElement</span>).<br>
</li>
<li>The table viewer's content provider executes a <span
style="font-family: monospace;">Runnable </span>in the DSF
dispatch thread and calls the data provider interface (<span
style="font-family: monospace;">SlowDataProviderContentProvider.queryItemData</span>).</li>
<li>Data provider service creates a Request object, and files it in a
queue (<span style="font-family: monospace;">SlowDataProvider.getItem</span>).</li>
<li>Data provider thread de-queues the Request object and acts on it,
calculating the value (<span style="font-family: monospace;">ProviderThread.processItemRequest</span>).</li>
<li>Data provider thread schedules the calculation result to be
posted with DSF executor (<span style="font-family: monospace;">SlowDataProvider.java:185</span>).</li>
<li>The RequestMonitor callback sets the result data in the table
viewer (<span style="font-family: monospace;">SlowDataProviderContentProvider.java:167</span>).<br>
</li>
</ol>
<h4>Running the example and full sources</h4>
This example is implemented in the <span
style="font-family: monospace;">org.eclipse.dd.examples.dsf</span>
plugin, in the <span style="font-family: monospace;">org.eclipse.dd.examples.dsf.concurrent</span>
package.&nbsp; <br>
<br>
To run the example:<br>
<ol>
<li>Build the test plugin (along with the <span
style="font-family: monospace;">org.eclipse.dsdp.DSF plugin</span>)
and launch the PDE.&nbsp; <br>
</li>
<li>Make sure to add the <span style="font-style: italic;">DSF
Tests</span> action set to your current perspective.</li>
<li>From the main menu, select <span style="font-style: italic;">DSF
Tests -&gt; Slow Data Provider</span>.</li>
<li>A dialog will open and after a delay it will populate with data.</li>
<li>Scroll and resize dialog and observe the update behavior.</li>
</ol>
<h4>Initial Notes<br>
</h4>
This example is supposed to be representative of a typical embedded
debugger design problem.&nbsp; 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.&nbsp; But as such, this basic example presents a couple
of major usability problems<br>
<ol>
<li>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.&nbsp; The result of this is visible to the user as
lines of data are filled in one-by-one in the table.&nbsp; 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.</li>
<li>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.&nbsp; 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.&nbsp; <br>
</li>
</ol>
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. <br>
<h3>Coalescing</h3>
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.&nbsp; There are
two basic patterns in which coalescing is achieved:<br>
<ol>
<li>The back end provides an interface for retrieving data in large
chunks.&nbsp; 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.</li>
<li>The back end providers an interface for retrieving data in
variable size chunks.&nbsp; When the service implementation receives a
request for a single item, it buffers the request, and waits for other
requests to come in.&nbsp; After a delay, the service clears the buffer
and submits a request for the combined items to the data provider.</li>
</ol>
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" (<span
style="font-family: monospace;">InputCoalescingSlowDataProvider.java</span>).&nbsp;
<br>
<p><span style="text-decoration: underline;">Input Buffer</span></p>
<p>The main feature of this pattern is a buffer for holding the
requests before sending them to the data provider.&nbsp; In this
example the user requests are buffered in two arrays: <span
style="font-family: monospace;">fGetItemIndexesBuffer</span> and <span
style="font-family: monospace;">fGetItemRequestMonitorsBuffer.&nbsp; </span>The
<span style="font-family: monospace;">DataProvider.getItem()</span>
implementation is changed as follows:</p>
<pre> public void getItem(final int index, final DataRequestMonitor&lt;String&gt; rm) {<br> // Schedule a buffer-servicing call, if one is needed.<br> if (<span
style="font-family: monospace;">fGetItemRequestMonitorsBuffer</span>.isEmpty()) {<br> fExecutor.schedule(<br> new Runnable() { public void run() {<br> fileBufferedRequests();<br> }},<br> COALESCING_DELAY_TIME, <br> TimeUnit.MILLISECONDS);<br> }<br> <br> // Add the call data to the buffer. <br> // Note: it doesn't matter that the items were added to the buffer <br> // after the buffer-servicing request was scheduled. This is because<br> // the buffers are guaranteed not to be modified until this dispatch<br> // cycle is over.<br> fGetItemIndexesBuffer.add(index);<br> <span
style="font-family: monospace;">fGetItemRequestMonitorsBuffer</span>.add(rm);<br> } <br><br></pre>
And method that services the buffer looks like this:<br>
<pre> public void fileBufferedRequests() { <br> // Remove a number of getItem() calls from the buffer, and combine them<br> // into a request.<br> int numToCoalesce = Math.min(fGetItemIndexesBuffer.size(), COALESCING_COUNT_LIMIT);<br> final ItemRequest request = new ItemRequest(new Integer[numToCoalesce], new DataRequestMonitor[numToCoalesce]); <br> for (int i = 0; i &lt; numToCoalesce; i++) {<br> request.fIndexes[i] = fGetItemIndexesBuffer.remove(0);<br> request.fDones[i] = <span
style="font-family: monospace;">fGetItemRequestMonitorsBuffer</span>.remove(0);<br> }<br><br> // Queue the coalesced request, with the appropriate transmission delay.<br> fQueue.add(request);<br> <br> // If there are still calls left in the buffer, execute another <br> // buffer-servicing call, but without any delay.<br> if (!fGetItemIndexesBuffer.isEmpty()) {<br> fExecutor.execute(new Runnable() { public void run() {<br> fileBufferedRequests();<br> }});<br> }<br> }<br></pre>
The most interesting feature of this implementation is the fact that
there are no semaphores anywhere to control access to the input
buffers.&nbsp; Even though the buffers are serviced with a delay and
multiple clients can call the <span style="font-family: monospace;">getItem()</span>
method, the use of a single
dispatch thread prevents any race conditions that could corrupt the
buffer data.&nbsp; 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.<br>
<h3>Cancellability</h3>
<p><span style="text-decoration: underline;">Table Viewer</span></p>
<p><span style="text-decoration: underline;"></span></p>
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.&nbsp; For the table viewer
content provider, this means that additional features have to be
added.&nbsp; In <span style="font-family: monospace;">CancellingSlowDataProviderContentProvider.java</span>
<span style="font-family: monospace;">ILazyContentProvider.updateElement()</span>
was changes as follows:<br>
<pre> public void updateElement(final int index) {<br> assert fTableViewer != null;<br> if (fDataProvider == null) return;<br> <br> // Calculate the visible index range.<br> final int topIdx = fTableViewer.getTable().getTopIndex();<br> final int botIdx = topIdx + getVisibleItemCount(topIdx);<br> <br> fCancelCallsPending.incrementAndGet();<br> fDataProvider.getExecutor().execute(<br> new Runnable() { public void run() {<br> // Must check again, in case disposed while redispatching.<br> if (fDataProvider == null || fTableViewer.getTable().isDisposed()) return;<br> if (index &gt;= topIdx &amp;&amp; index &lt;= botIdx) {<br> queryItemData(index);<br> }<br> cancelStaleRequests(topIdx, botIdx);<br> }});<br> }<br></pre>
Now the client keeps track of the requests it made to the service in <span
style="font-family: monospace;">fItemDataDones</span>, and above, <span
style="font-family: monospace;">cancelStaleRequests()</span> iterates
through all the outstanding requests and cancels the ones that are no
longer in the visible range.<br>
<p><span style="text-decoration: underline;">Data Provider Service<span
style="text-decoration: underline;"></span></span></p>
<p><span style="text-decoration: underline;"><span
style="text-decoration: underline;"></span></span></p>
<p>The data provider implementation
(<span style="font-family: monospace;">CancellableInputCoalescingSlowDataProvider.java</span>),
builds on top of the
coalescing data provider.&nbsp; To make the canceling feature useful,
the data provider service has to limit the size of the request
queue.&nbsp; 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.&nbsp; So instead, if a flood
of <span style="font-family: monospace;">getItem()</span>
calls comes in, the service has to hold most of them in the coalescing
buffer in case the client decides to cancel them.&nbsp; Therefore the
<span style="font-family: monospace;">fileBufferedRequests()</span>
method includes a simple check before servicing
the buffer, and if the request queue is full, the buffer servicing call
is delayed.</p>
<pre> if (fQueue.size() &gt;= REQUEST_QUEUE_SIZE_LIMIT) {<br> if (fGetItemIndexesBuffer.isEmpty()) {<br> fExecutor.schedule(<br> new Runnable() { public void run() {<br> fileBufferedRequests();<br> }},<br> REQUEST_BUFFER_FULL_RETRY_DELAY, <br> TimeUnit.MILLISECONDS);<br> }<br> return;<br> } <br></pre>
Beyond this change, the only other significant change is that before
the requests are queued, they are checked for cancellation.<br>
<h3>Final Notes<br>
</h3>
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.&nbsp; 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.&nbsp; 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.<br>
<p>Coalescing and Cancellability are both optimizations.&nbsp; Neither
of these optimizations affected the original interface of the service,
and one of them only needed a service-side modification.&nbsp; 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.&nbsp; </p>
<p>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.&nbsp; 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.&nbsp; The perfect example of this is a Variables
service, which is responsible for calculating the value of expressions
shown in the Variables view.&nbsp; 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.&nbsp; But as
soon as a resumed event is issued by Run Control, the Variables service
needs to cancel&nbsp; the pending evaluation requests.<br>
</p>
<br>
<br>
</body>
</html>

View file

@ -39,6 +39,11 @@ public class AsyncHelloWorld {
System.out.println("Hello world");
//#ifdef excercises
// 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);

View file

@ -52,7 +52,10 @@ public class AsyncQuicksort {
if (right > left) {
int pivot = left;
//#ifdef excercises
// TODO: Request Monitors Exercise 2 - Convert partition to an async. method.
// 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);
@ -76,7 +79,6 @@ public class AsyncQuicksort {
//# }
//# });
//#endif
} else {
rm.done();
}
@ -84,6 +86,8 @@ public class AsyncQuicksort {
//#ifdef excercises
// TODO Exercise 2 - Convert partition to an asynchronous method.
// Hint: a DataRequestMonitor<Integer> 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<Integer> rm)
@ -105,11 +109,12 @@ public class AsyncQuicksort {
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
}