This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit b27dfd2b887920b6ec735aa37b5c7043ac3b36df
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Wed Nov 3 15:03:15 2021 +0100

    Reduce the number of background threads created for fetching resource 
labels.
---
 .../org/apache/sis/gui/dataset/ResourceTree.java   | 93 +++++++++++++++++-----
 1 file changed, 74 insertions(+), 19 deletions(-)

diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceTree.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceTree.java
index a4fc1d9..e56f8fb 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceTree.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceTree.java
@@ -23,8 +23,10 @@ import java.net.MalformedURLException;
 import java.nio.file.FileSystemNotFoundException;
 import java.util.AbstractList;
 import java.util.Locale;
+import java.util.Queue;
 import java.util.List;
 import java.util.ArrayList;
+import java.util.LinkedList;
 import java.util.Collection;
 import java.util.Optional;
 import javafx.application.Platform;
@@ -124,11 +126,26 @@ public class ResourceTree extends TreeView<Resource> {
     public final ObjectProperty<EventHandler<ResourceEvent>> onResourceClosed;
 
     /**
+     * Tree node items (wrappers around {@link Resource} instances) waiting to 
be
+     * completed with the item labels. Those labels are fetched in a 
background thread.
+     * All accesses to this list must be synchronized on {@code pendingItems}.
+     *
+     * <h4>Design note</h4>
+     * We use a list instead of creating a {@link Task} for each item because 
the later can create a lot
+     * of threads, which are likely to be blocked anyway because of {@link 
DataStore} synchronization.
+     * Furthermore those threads of overkill in the common case where labels 
are very quick to fetch.
+     *
+     * @see #fetchLabel(Item.Completer)
+     */
+    private final Queue<Item.Completer> pendingItems;
+
+    /**
      * Creates a new tree of resources with initially no resource to show.
      * For showing a resource, invoke {@link #setResource(Resource)} after 
construction.
      */
     public ResourceTree() {
         locale = Locale.getDefault();
+        pendingItems = new LinkedList<>();
         setCellFactory((v) -> new Cell());
         setOnDragOver(ResourceTree::onDragOver);
         setOnDragDropped(this::onDragDropped);
@@ -486,6 +503,32 @@ public class ResourceTree extends TreeView<Resource> {
     }
 
     /**
+     * Updates {@link Item#label} with the resource label fetched in 
background thread.
+     * Caller should invoke this method only if {@link Item#isLoading} is 
{@code true}.
+     */
+    private void fetchLabel(final Item.Completer item) {
+        final boolean isEmpty;
+        synchronized (pendingItems) {
+            // The two operations below must be atomic (this is why we do not 
use ConcurrentLinkedQueue).
+            isEmpty = pendingItems.isEmpty();
+            pendingItems.add(item);
+        }
+        if (isEmpty) {
+            // Not a problem if 2 tasks are launched in parallel.
+            BackgroundThreads.execute(() -> {
+                for (;;) {
+                    final Item.Completer c;
+                    synchronized (pendingItems) {
+                        c = pendingItems.poll();
+                    }
+                    if (c == null) break;
+                    c.fetch(locale);
+                }
+            });
+        }
+    }
+
+    /**
      * Returns resources for current locale.
      */
     final Resources localized() {
@@ -575,7 +618,7 @@ public class ResourceTree extends TreeView<Resource> {
                     if (text == null) {
                         text = item.label = 
tree.localized().getString(Resources.Keys.Loading);
                         if (resource != null) {
-                            item.fetchLabel(resource, tree.locale);         // 
Start a background thread.
+                            tree.fetchLabel(item.new Completer(resource));     
 // Start a background thread.
                         }
                     }
                 } else if ((error = item.error) != null) {
@@ -737,28 +780,40 @@ public class ResourceTree extends TreeView<Resource> {
 
         /**
          * Update {@link #label} with the resource label fetched in background 
thread.
-         * Caller should invoke this method only if {@link #isLoading} is 
{@code true}
-         * {@link #label} is null. Note that {@link #error} should be null in 
such case.
+         * Caller should invoke this method only if {@link #isLoading} is 
{@code true}.
          */
-        final void fetchLabel(final Resource resource, final Locale locale) {
-            BackgroundThreads.execute(new Task<String>() {
-                @Override protected String call() throws DataStoreException {
-                    return findLabel(resource, locale);
-                }
+        final class Completer implements Runnable {
+            /** The resource for which to fetch a label. */
+            private final Resource resource;
 
-                @Override protected void succeeded() {
-                    isLoading = false;
-                    label = getValue();
-                    GUIUtilities.forceCellUpdate(Item.this);
-                }
+            /** Result of fetching the label of a resource. */
+            private String result;
+
+            /** Error that occurred while fetching the label. */
+            private Throwable failure;
+
+            /** Creates a new container for the label of a resource. */
+            Completer(final Resource resource) {
+                this.resource = resource;
+            }
 
-                @Override protected void failed() {
-                    isLoading = false;
-                    label = null;
-                    error = getException();
-                    GUIUtilities.forceCellUpdate(Item.this);
+            /** Invoked in a background thread for fetching the label. */
+            final void fetch(final Locale locale) {
+                try {
+                    result = findLabel(resource, locale);
+                } catch (Throwable e) {
+                    failure = e;
                 }
-            });
+                Platform.runLater(this);
+            }
+
+            /** Invoked in JavaFX thread after the label has been fetched. */
+            public void run() {
+                isLoading = false;
+                label     = result;
+                error     = failure;
+                GUIUtilities.forceCellUpdate(Item.this);
+            }
         }
 
         /**

Reply via email to