You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2021/11/04 09:38:15 UTC

[sis] branch geoapi-4.0 updated (fed0a14 -> 3f52106)

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

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


    from fed0a14  Fix `NullPointerException`, partially caused by changes in `MapCanvas` state when a rendering is in progress. We workaround this problem with a new `MapCanvas.runAfterRendering(Runnable)` method.
     new e2f00c4  Do not interrupt threads that are blocked on I/O operations. This is necessary for avoiding to close InterruptibleChannel.
     new 3f52106  Document better who is in charge of closing JDBC connections. Check if the connection is closed in methods that do not use the connection immediately.

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../java/org/apache/sis/gui/coverage/GridView.java |  4 +--
 .../org/apache/sis/gui/dataset/FeatureList.java    |  4 +--
 .../sis/gui/metadata/IdentificationInfo.java       |  2 +-
 .../apache/sis/gui/metadata/MetadataSummary.java   |  2 +-
 .../apache/sis/internal/gui/BackgroundThreads.java | 13 ++++++++++
 .../org/apache/sis/internal/gui/PropertyView.java  |  4 +--
 .../factory/ConcurrentAuthorityFactory.java        |  8 +++---
 .../referencing/factory/sql/AuthorityCodes.java    | 28 ++++++++++++--------
 .../factory/sql/CloseableReference.java            | 21 ++++++++++-----
 .../referencing/factory/sql/EPSGCodeFinder.java    |  2 ++
 .../referencing/factory/sql/EPSGDataAccess.java    | 30 ++++++++++++++++------
 .../sis/referencing/factory/sql/package-info.java  |  2 +-
 .../java/org/apache/sis/util/resources/Errors.java |  5 ++++
 .../apache/sis/util/resources/Errors.properties    |  1 +
 .../apache/sis/util/resources/Errors_fr.properties |  1 +
 15 files changed, 88 insertions(+), 39 deletions(-)

[sis] 02/02: Document better who is in charge of closing JDBC connections. Check if the connection is closed in methods that do not use the connection immediately.

Posted by de...@apache.org.
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 3f521061600df52d0ac8648e66759e7824b8f6c4
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Thu Nov 4 10:17:59 2021 +0100

    Document better who is in charge of closing JDBC connections.
    Check if the connection is closed in methods that do not use the connection immediately.
---
 .../factory/ConcurrentAuthorityFactory.java        |  8 +++---
 .../referencing/factory/sql/AuthorityCodes.java    | 28 ++++++++++++--------
 .../factory/sql/CloseableReference.java            | 21 ++++++++++-----
 .../referencing/factory/sql/EPSGCodeFinder.java    |  2 ++
 .../referencing/factory/sql/EPSGDataAccess.java    | 30 ++++++++++++++++------
 .../sis/referencing/factory/sql/package-info.java  |  2 +-
 .../java/org/apache/sis/util/resources/Errors.java |  5 ++++
 .../apache/sis/util/resources/Errors.properties    |  1 +
 .../apache/sis/util/resources/Errors_fr.properties |  1 +
 9 files changed, 67 insertions(+), 31 deletions(-)

diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java
index a817f9f..358fc38 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java
@@ -1779,7 +1779,7 @@ public abstract class ConcurrentAuthorityFactory<DAO extends GeodeticAuthorityFa
      * the work to the underlying Data Access Object and caches the result.
      *
      * <h4>Synchronization note</h4>
-     * our public API claims that {@link IdentifiedObjectFinder}s are not thread-safe.
+     * Our public API claims that {@link IdentifiedObjectFinder}s are not thread-safe.
      * Nevertheless we synchronize this particular implementation for safety, because the consequence of misuse
      * are more dangerous than other implementations. Furthermore this is also a way to assert that no code path
      * go to the {@link #create(AuthorityFactoryProxy, String)} method from a non-overridden public method.
@@ -2090,7 +2090,7 @@ public abstract class ConcurrentAuthorityFactory<DAO extends GeodeticAuthorityFa
      *
      * @param  availableDAOs  the queue of factories to close.
      */
-    static <DAO extends GeodeticAuthorityFactory> List<DAO> clear(final Deque<DataAccessRef<DAO>> availableDAOs) {
+    private static <DAO extends GeodeticAuthorityFactory> List<DAO> clear(final Deque<DataAccessRef<DAO>> availableDAOs) {
         assert Thread.holdsLock(availableDAOs);
         final List<DAO> factories = new ArrayList<>(availableDAOs.size());
         DataAccessRef<DAO> dao;
@@ -2107,9 +2107,9 @@ public abstract class ConcurrentAuthorityFactory<DAO extends GeodeticAuthorityFa
      * @param  factories  the factories to close.
      * @throws Exception the exception thrown by the first factory that failed to close.
      */
-    static <DAO extends GeodeticAuthorityFactory> void close(final List<DAO> factories) throws Exception {
+    private static <DAO extends GeodeticAuthorityFactory> void close(final List<DAO> factories) throws Exception {
         Exception exception = null;
-        for (int i=factories.size(); --i>=0;) {
+        for (int i = factories.size(); --i >= 0;) {
             final DAO factory = factories.get(i);
             if (factory instanceof AutoCloseable) try {
                 ((AutoCloseable) factory).close();
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/AuthorityCodes.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/AuthorityCodes.java
index 2afa8d9..90f55f9 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/AuthorityCodes.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/AuthorityCodes.java
@@ -34,20 +34,26 @@ import org.apache.sis.util.collection.IntegerList;
  * A map of EPSG authority codes as keys and object names as values.
  * This map requires a living connection to the EPSG database.
  *
- * <p>Serialization of this class stores a copy of all authority codes.
- * The serialization does not preserve any connection to the database.</p>
+ * <h2>Serialization</h2>
+ * Serialization of this class stores a copy of all authority codes.
+ * The serialization does not preserve any connection to the database.
  *
- * <p>This method does not implement {@link AutoCloseable} because the same instance may be shared by many users,
+ * <h2>Garbage collection</h2>
+ * This method does not implement {@link AutoCloseable} because the same instance may be shared by many users,
  * since {@link EPSGDataAccess#getAuthorityCodes(Class)} caches {@code AuthorityCodes} instances. Furthermore we can
  * not rely on the users closing {@code AuthorityCodes} themselves because this is not part of the usual contract
  * for Java collection classes (we could document that recommendation in method Javadoc, but not every developers
  * read Javadoc). Relying on the garbage collector for disposing this resource is far from ideal, but alternatives
  * are not very convincing either (load the same codes many time, have the risk that users do not dispose resources,
- * have the risk to return to user an already closed {@code AuthorityCodes} instance).</p>
+ * have the risk to return to user an already closed {@code AuthorityCodes} instance).
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.8
- * @since   0.7
+ * @version 1.2
+ *
+ * @see EPSGDataAccess#canClose()
+ * @see CloseableReference#close()
+ *
+ * @since 0.7
  * @module
  */
 @SuppressWarnings("serial")   // serialVersionUID not needed because of writeReplace().
@@ -64,9 +70,9 @@ final class AuthorityCodes extends AbstractMap<String,String> implements Seriali
     private static final int ALL = 0, ONE = 1;
 
     /**
-     * The factory which is the owner of this map. One purpose of this field is to prevent garbage collection
-     * of that factory as long as this map is in use. This is required because {@link EPSGDataAccess#finalize()}
-     * closes the JDBC connections.
+     * The factory which is the owner of this map. One purpose of this field is to prevent
+     * garbage collection of that factory as long as this map is in use. This is required
+     * because {@link CloseableReference#dispose()} closes the JDBC connections.
      */
     private final transient EPSGDataAccess factory;
 
@@ -170,8 +176,8 @@ final class AuthorityCodes extends AbstractMap<String,String> implements Seriali
      * when the garbage collector determined that this {@code AuthorityCodes} instance is no longer in use.
      * See class Javadoc for more information.
      */
-    final CloseableReference<AuthorityCodes> createReference() {
-        return new CloseableReference<>(this, factory, statements);
+    final CloseableReference createReference() {
+        return new CloseableReference(this, factory, statements);
     }
 
     /**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/CloseableReference.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/CloseableReference.java
index b82bb84..9868b47 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/CloseableReference.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/CloseableReference.java
@@ -26,15 +26,20 @@ import org.apache.sis.util.logging.Logging;
 
 
 /**
- * Closes JDBC resources when {@link AuthorityCodes} is garbage collected.
- * Those weak references are stored in the {@link EPSGDataAccess#authorityCodes} map.
+ * Closes JDBC statements when {@link AuthorityCodes} is garbage collected.
+ * Those weak references are stored in the {@link EPSGDataAccess#authorityCodes} map as cached values.
+ * Connection is not closed by this class because they will be closed when {@link EPSGDataAccess} will
+ * be closed.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
- * @since   0.7
+ * @version 1.2
+ *
+ * @see EPSGFactory#canClose(EPSGDataAccess)
+ *
+ * @since 0.7
  * @module
  */
-final class CloseableReference<T> extends WeakReference<T> implements Disposable {
+final class CloseableReference extends WeakReference<AuthorityCodes> implements Disposable {
     /**
      * The EPSG factory, used for synchronization lock.
      */
@@ -50,7 +55,7 @@ final class CloseableReference<T> extends WeakReference<T> implements Disposable
      * Creates a new phantom reference which will close the given statements
      * when the given referenced object will be garbage collected.
      */
-    CloseableReference(final T ref, final EPSGDataAccess factory, final Statement[] statements) {
+    CloseableReference(final AuthorityCodes ref, final EPSGDataAccess factory, final Statement[] statements) {
         super(ref, ReferenceQueueConsumer.QUEUE);
         this.statements = statements;
         this.factory = factory;
@@ -58,13 +63,15 @@ final class CloseableReference<T> extends WeakReference<T> implements Disposable
 
     /**
      * Closes the statements. If an exception occurred, it will be thrown only after all statements have been closed.
+     * The connection is not closed in this method because it will be closed later by (indirectly)
+     * {@link org.apache.sis.referencing.factory.ConcurrentAuthorityFactory#close(List)}.
      *
      * @throws SQLException if an error occurred while closing the statements.
      */
     final void close() throws SQLException {
         SQLException exception = null;
         synchronized (factory) {
-            for (int i=statements.length; --i >= 0;) {
+            for (int i = statements.length; --i >= 0;) {
                 final Statement s = statements[i];
                 statements[i] = null;
                 if (s != null) try {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
index 51e7225..43245e5 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
@@ -70,6 +70,8 @@ import static org.apache.sis.internal.metadata.NameToIdentifier.Simplifier.ESRI_
 final class EPSGCodeFinder extends IdentifiedObjectFinder {
     /**
      * The data access object to use for searching EPSG codes.
+     * Supplied at construction time and assumed alive for the duration of this {@code EPSGCodeFinder} life
+     * (i.e. this class does not create and does not close DAO by itself).
      */
     private final EPSGDataAccess dao;
 
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index 2159a90..47552d8 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
@@ -157,7 +157,7 @@ import static org.apache.sis.internal.referencing.ServicesForMetadata.CONNECTION
  * @author  Matthias Basler
  * @author  Andrea Aime (TOPP)
  * @author  Johann Sorel (Geomatys)
- * @version 1.1
+ * @version 1.2
  *
  * @see <a href="http://sis.apache.org/tables/CoordinateReferenceSystems.html">List of authority codes</a>
  *
@@ -267,7 +267,7 @@ public class EPSGDataAccess extends GeodeticAuthorityFactory implements CRSAutho
      * and returns {@code false} if some are found (thus blocking the call to {@link #close()}
      * by the {@link org.apache.sis.referencing.factory.ConcurrentAuthorityFactory} timer).</p>
      */
-    private final Map<Class<?>, CloseableReference<AuthorityCodes>> authorityCodes = new HashMap<>();
+    private final Map<Class<?>, CloseableReference> authorityCodes = new HashMap<>();
 
     /**
      * Cache for axis names. This service is not provided by {@code ConcurrentAuthorityFactory}
@@ -534,6 +534,9 @@ addURIs:    for (int i=0; ; i++) {
     @Override
     public Set<String> getAuthorityCodes(final Class<? extends IdentifiedObject> type) throws FactoryException {
         try {
+            if (connection.isClosed()) {
+                throw new FactoryException(error().getString(Errors.Keys.ConnectionClosed));
+            }
             return getCodeMap(type).keySet();
         } catch (SQLException exception) {
             throw new FactoryException(exception.getLocalizedMessage(), exception);
@@ -552,7 +555,7 @@ addURIs:    for (int i=0; ; i++) {
      * @see #getDescriptionText(String)
      */
     private synchronized Map<String,String> getCodeMap(final Class<?> type) throws SQLException {
-        CloseableReference<AuthorityCodes> reference = authorityCodes.get(type);
+        CloseableReference reference = authorityCodes.get(type);
         if (reference != null) {
             AuthorityCodes existing = reference.get();
             if (existing != null) {
@@ -586,7 +589,7 @@ addURIs:    for (int i=0; ; i++) {
                     if (existing != null) {
                         codes = existing;
                     } else {
-                        reference = null;   // The weak reference is no longer valid.
+                        reference = null;           // The weak reference is no longer valid.
                     }
                 }
                 if (reference == null) {
@@ -3132,7 +3135,14 @@ next:                   while (r.next()) {
      */
     @Override
     public IdentifiedObjectFinder newIdentifiedObjectFinder() throws FactoryException {
-        return new EPSGCodeFinder(this);
+        try {
+            if (connection.isClosed()) {
+                throw new FactoryException(error().getString(Errors.Keys.ConnectionClosed));
+            }
+            return new EPSGCodeFinder(this);
+        } catch (SQLException exception) {
+            throw new FactoryException(exception.getLocalizedMessage(), exception);
+        }
     }
 
     /**
@@ -3331,14 +3341,18 @@ next:                   while (r.next()) {
      * Returns {@code true} if it is safe to close this factory. This method is invoked indirectly
      * by {@link EPSGFactory} after some timeout in order to release resources.
      * This method will block the disposal if some {@link AuthorityCodes} are still in use.
+     *
+     * @return {@code true} if this Data Access Object can be closed.
+     *
+     * @see EPSGFactory#canClose(EPSGDataAccess)
      */
     final synchronized boolean canClose() {
         boolean can = true;
         if (!authorityCodes.isEmpty()) {
             System.gc();                // For cleaning as much weak references as we can before we check them.
-            final Iterator<CloseableReference<AuthorityCodes>> it = authorityCodes.values().iterator();
+            final Iterator<CloseableReference> it = authorityCodes.values().iterator();
             while (it.hasNext()) {
-                final AuthorityCodes codes = it.next().get();
+                final AuthorityCodes codes = it.next().get();       // TODO: use referTo(null) with JDK16.
                 if (codes == null) {
                     it.remove();
                 } else {
@@ -3378,7 +3392,7 @@ next:                   while (r.next()) {
             }
             ip.remove();
         }
-        final Iterator<CloseableReference<AuthorityCodes>> it = authorityCodes.values().iterator();
+        final Iterator<CloseableReference> it = authorityCodes.values().iterator();
         while (it.hasNext()) {
             try {
                 it.next().close();
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/package-info.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/package-info.java
index 70a8d97..3e533d8 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/package-info.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/package-info.java
@@ -82,7 +82,7 @@
  * @author  Jody Garnett (Refractions)
  * @author  Didier Richard (IGN)
  * @author  John Grange
- * @version 1.1
+ * @version 1.2
  *
  * @see org.apache.sis.metadata.sql
  *
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
index cfc3af0..474444d 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
@@ -204,6 +204,11 @@ public final class Errors extends IndexedResourceBundle {
         public static final short CloneNotSupported_1 = 20;
 
         /**
+         * Connection is closed.
+         */
+        public static final short ConnectionClosed = 194;
+
+        /**
          * Cross references are not supported.
          */
         public static final short CrossReferencesNotSupported = 167;
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
index eee7216..684af4c 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
@@ -52,6 +52,7 @@ CanNotWriteFile_2                 = Can not write \u201c{1}\u201d as a file in t
 CircularReference                 = Circular reference.
 ClassNotFinal_1                   = Class \u2018{0}\u2019 is not final.
 CloneNotSupported_1               = Can not clone an object of type \u2018{0}\u2019.
+ConnectionClosed                  = Connection is closed.
 CrossReferencesNotSupported       = Cross references are not supported.
 DatabaseError_2                   = Database error while creating a \u2018{0}\u2019 object for the \u201c{1}\u201d identifier.
 DatabaseUpdateFailure_3           = Failed to {0,choice,0#insert|1#update} record \u201c{2}\u201d in database table \u201c{1}\u201d.
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
index e0ac843..d4cd22d 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
@@ -49,6 +49,7 @@ CanNotWriteFile_2                 = Ne peut pas \u00e9crire \u00ab\u202f{1}\u202
 CircularReference                 = R\u00e9f\u00e9rence circulaire.
 ClassNotFinal_1                   = La classe \u2018{0}\u2019 n\u2019est pas finale.
 CloneNotSupported_1               = Un objet de type \u2018{0}\u2019 ne peut pas \u00eatre clon\u00e9.
+ConnectionClosed                  = La connexion est ferm\u00e9e.
 CrossReferencesNotSupported       = Les r\u00e9f\u00e9rences crois\u00e9es ne sont pas support\u00e9es.
 DatabaseError_2                   = Erreur de base de donn\u00e9es lors de la cr\u00e9ation d\u2019un objet \u2018{0}\u2019 pour l\u2019identifiant \u00ab\u202f{1}\u202f\u00bb.
 DatabaseUpdateFailure_3           = \u00c9chec lors de {0,choice,0#l\u2019insertion|1#la mise \u00e0 jour} de l\u2019enregistrement \u00ab\u202f{2}\u202f\u00bb dans la table \u00ab\u202f{1}\u202f\u00bb de la base de donn\u00e9es.

[sis] 01/02: Do not interrupt threads that are blocked on I/O operations. This is necessary for avoiding to close InterruptibleChannel.

Posted by de...@apache.org.
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 e2f00c48d881d8d9a23d5411d61bbd339996e034
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Thu Nov 4 10:15:40 2021 +0100

    Do not interrupt threads that are blocked on I/O operations.
    This is necessary for avoiding to close InterruptibleChannel.
---
 .../src/main/java/org/apache/sis/gui/coverage/GridView.java |  4 ++--
 .../main/java/org/apache/sis/gui/dataset/FeatureList.java   |  4 ++--
 .../org/apache/sis/gui/metadata/IdentificationInfo.java     |  2 +-
 .../java/org/apache/sis/gui/metadata/MetadataSummary.java   |  2 +-
 .../java/org/apache/sis/internal/gui/BackgroundThreads.java | 13 +++++++++++++
 .../main/java/org/apache/sis/internal/gui/PropertyView.java |  4 ++--
 6 files changed, 21 insertions(+), 8 deletions(-)

diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java
index bf6dd8c..057c7e7 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java
@@ -316,7 +316,7 @@ public class GridView extends Control {
             final ImageLoader previous = loader;
             loader = null;
             if (previous != null) {
-                previous.cancel();
+                previous.cancel(BackgroundThreads.NO_INTERRUPT_DURING_IO);
             }
             loader = new ImageLoader(source, true);
             BackgroundThreads.execute(loader);
@@ -444,7 +444,7 @@ public class GridView extends Control {
      */
     private void onImageSpecified(final RenderedImage image) {
         if (loader != null) {
-            loader.cancel();
+            loader.cancel(BackgroundThreads.NO_INTERRUPT_DURING_IO);
             loader = null;
         }
         tiles.clear();          // Let garbage collector dispose the rasters.
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureList.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureList.java
index ab8b9cc..5c1ef2c 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureList.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureList.java
@@ -149,7 +149,7 @@ final class FeatureList extends ObservableListBase<Feature> {
         final FeatureLoader previous = nextPageLoader;
         if (previous != null) {
             nextPageLoader = null;
-            previous.cancel();
+            previous.cancel(BackgroundThreads.NO_INTERRUPT_DURING_IO);
         }
         if (features != null) {
             nextPageLoader = new FeatureLoader(table, features);
@@ -333,7 +333,7 @@ final class FeatureList extends ObservableListBase<Feature> {
         final FeatureLoader loader = nextPageLoader;
         nextPageLoader = null;
         if (loader != null) {
-            loader.cancel();
+            loader.cancel(BackgroundThreads.NO_INTERRUPT_DURING_IO);
             BackgroundThreads.execute(loader::waitAndClose);
         }
     }
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/IdentificationInfo.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/IdentificationInfo.java
index 1c83184..8da5616 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/IdentificationInfo.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/IdentificationInfo.java
@@ -238,7 +238,7 @@ final class IdentificationInfo extends Section<Identification> {
     @Override
     void setInformation(final Metadata metadata) {
         if (aggregateWalker != null) {
-            aggregateWalker.cancel();
+            aggregateWalker.cancel(BackgroundThreads.NO_INTERRUPT_DURING_IO);
             aggregateWalker = null;
         }
         final Collection<? extends Identification> info;
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataSummary.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataSummary.java
index dee04f1..b17445e 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataSummary.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataSummary.java
@@ -200,7 +200,7 @@ public class MetadataSummary extends Widget {
     public void setMetadata(final Resource resource) {
         assert Platform.isFxApplicationThread();
         if (getter != null) {
-            getter.cancel();
+            getter.cancel(BackgroundThreads.NO_INTERRUPT_DURING_IO);
             getter = null;
         }
         if (resource == null) {
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/BackgroundThreads.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/BackgroundThreads.java
index 1251829..116ca02 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/BackgroundThreads.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/BackgroundThreads.java
@@ -53,6 +53,19 @@ import org.apache.sis.util.logging.Logging;
 @SuppressWarnings("serial")                         // Not intended to be serialized.
 public final class BackgroundThreads extends AtomicInteger implements ThreadFactory {
     /**
+     * The {@code mayInterruptIfRunning} argument value to give to calls to
+     * {@link java.util.concurrent.Future#cancel(boolean)} if the background task
+     * may be doing I/O operations on a {@link java.nio.channels.InterruptibleChannel}.
+     * Interruption must be disabled for avoiding the channel to be closed.
+     *
+     * <p>Note that the default value of {@link javafx.concurrent.Task#cancel()} is {@code true}.
+     * So task doing I/O operations should be cancelled with {@code cancel(NO_INTERRUPT_DURING_IO)}.
+     * This flag is defined mostly for tracking places in the code where tasks doing I/O operations
+     * may be interrupted.</p>
+     */
+    public static final boolean NO_INTERRUPT_DURING_IO = false;
+
+    /**
      * The executor for background tasks. This is actually an {@link ExecutorService} instance,
      * but only the {@link Executor} method should be used according JavaFX documentation.
      */
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/PropertyView.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/PropertyView.java
index 36f854f..9543886 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/PropertyView.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/PropertyView.java
@@ -55,7 +55,7 @@ import org.apache.sis.util.resources.Vocabulary;
  * <p>This class extends {@link CompoundFormat} for implementation convenience only.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
@@ -212,7 +212,7 @@ public final class PropertyView extends CompoundFormat<Object> {
     public void set(final Object newValue, final Rectangle visibleBounds) {
         if (newValue != value || !Objects.equals(visibleBounds, visibleImageBounds)) {
             if (runningTask != null) {
-                runningTask.cancel();
+                runningTask.cancel(BackgroundThreads.NO_INTERRUPT_DURING_IO);
                 runningTask = null;
             }
             visibleImageBounds = visibleBounds;