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
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new c7c5901b81 Allow data stores to be closed asynchronously.
https://issues.apache.org/jira/browse/SIS-573
c7c5901b81 is described below
commit c7c5901b81e00d5843ebb2688c017d4f99424bd0
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Sat Feb 11 15:51:05 2023 +0100
Allow data stores to be closed asynchronously.
https://issues.apache.org/jira/browse/SIS-573
---
.../apache/sis/internal/gui/BackgroundThreads.java | 13 +--
.../apache/sis/internal/gui/DataStoreOpener.java | 59 ++++-------
.../apache/sis/internal/system/CommonExecutor.java | 2 +-
.../apache/sis/storage/landsat/LandsatStore.java | 56 ++++++----
.../apache/sis/storage/landsat/package-info.java | 2 +-
.../apache/sis/storage/geotiff/GeoTiffStore.java | 26 +++--
.../org/apache/sis/storage/geotiff/Reader.java | 1 +
.../org/apache/sis/internal/netcdf/Decoder.java | 11 +-
.../sis/internal/netcdf/impl/ChannelDecoder.java | 7 +-
.../sis/internal/netcdf/impl/package-info.java | 2 +-
.../sis/internal/netcdf/ucar/DecoderWrapper.java | 20 ++--
.../sis/internal/netcdf/ucar/package-info.java | 2 +-
.../org/apache/sis/storage/netcdf/NetcdfStore.java | 22 ++--
.../org/apache/sis/internal/netcdf/TestCase.java | 6 +-
.../sis/storage/netcdf/MetadataReaderTest.java | 17 ++--
.../storage/netcdf/NetcdfStoreProviderTest.java | 15 +--
.../sis/internal/storage/GridResourceWrapper.java | 26 ++---
.../sis/internal/storage/csv/package-info.java | 2 +-
.../sis/internal/storage/esri/AsciiGridStore.java | 29 ++++--
.../sis/internal/storage/esri/RasterStore.java | 2 +-
.../sis/internal/storage/esri/RawRasterStore.java | 32 +++---
.../sis/internal/storage/esri/WritableStore.java | 3 +
.../internal/storage/folder/ConcurrentCloser.java | 113 +++++++++++++++++++++
.../apache/sis/internal/storage/folder/Store.java | 38 ++++---
.../sis/internal/storage/image/WorldFileStore.java | 50 +++++----
.../sis/internal/storage/image/WritableStore.java | 3 +
.../internal/storage/io/FileCacheByteChannel.java | 23 +++--
.../org/apache/sis/internal/storage/wkt/Store.java | 25 +++--
.../org/apache/sis/internal/storage/xml/Store.java | 24 +++--
.../java/org/apache/sis/storage/DataStore.java | 5 +
.../apache/sis/storage/event/StoreListeners.java | 9 +-
.../org/apache/sis/internal/storage/gpx/Store.java | 3 +
.../internal/storage/xml/stream/StaxDataStore.java | 27 +++--
33 files changed, 431 insertions(+), 244 deletions(-)
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 0b9e25e1b9..10f333b9e0 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
@@ -29,6 +29,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Platform;
import org.apache.sis.gui.DataViewer;
import org.apache.sis.internal.system.Threads;
+import org.apache.sis.storage.DataStoreException;
import org.apache.sis.util.logging.Logging;
import org.apache.sis.util.Exceptions;
@@ -171,20 +172,16 @@ public final class BackgroundThreads extends
AtomicInteger implements ThreadFact
* This method returns soon but the background threads may continue for
some time if they did not finished
* their task yet.
*
- * @throws Exception if an error occurred while closing at least one data
store.
+ * @throws DataStoreException if an error occurred while closing at least
one data store.
*/
- public static void stop() throws Exception {
- EXECUTOR.shutdown();
+ public static void stop() throws DataStoreException {
+ EXECUTOR.shutdown(); // Prevent scheduling of more tasks.
+ DataStoreOpener.closeAll(); // Throws AsynchronousCloseException
in threads that are reading.
try {
EXECUTOR.awaitTermination(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
- /*
- * Someone does not want to wait for termination.
- * Closes the data stores now even if some of them may still be in
use.
- */
interrupted("stop", e);
}
- DataStoreOpener.closeAll();
}
/**
diff --git
a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/DataStoreOpener.java
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/DataStoreOpener.java
index 84e41c114e..73424a65f4 100644
---
a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/DataStoreOpener.java
+++
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/DataStoreOpener.java
@@ -22,11 +22,12 @@ import java.io.File;
import java.nio.file.Path;
import java.io.IOException;
import java.net.URISyntaxException;
+import java.nio.file.FileSystemNotFoundException;
+import java.util.List;
import java.util.Locale;
import java.util.Collection;
-import java.util.function.Consumer;
+import java.util.concurrent.Callable;
import java.util.function.UnaryOperator;
-import java.util.stream.Stream;
import javafx.concurrent.Task;
import javafx.event.EventHandler;
import javafx.application.Platform;
@@ -47,6 +48,7 @@ import org.apache.sis.internal.util.Strings;
import org.apache.sis.internal.storage.io.IOUtilities;
import org.apache.sis.internal.storage.io.ChannelFactory;
import org.apache.sis.internal.storage.io.InternalOptionKey;
+import org.apache.sis.internal.storage.folder.ConcurrentCloser;
import org.apache.sis.storage.DataStore;
import org.apache.sis.gui.DataViewer;
@@ -72,7 +74,7 @@ import org.apache.sis.gui.DataViewer;
* @todo Set title. Add progress listener and cancellation capability.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
*
* @see BackgroundThreads#execute(Runnable)
*
@@ -140,9 +142,9 @@ public final class DataStoreOpener extends Task<DataStore> {
source = ((Path) source).toRealPath(); // May
throw IOException.
}
}
- } catch (URISyntaxException | IOException | IllegalArgumentException
e) {
+ } catch (URISyntaxException | FileSystemNotFoundException |
IllegalArgumentException e) {
// Ignore — keep `source` as is (File, URI, URI or non-absolute
Path).
- } catch (DataStoreException | RuntimeException e) {
+ } catch (DataStoreException | IOException | RuntimeException e) {
source = null;
}
key = source;
@@ -352,43 +354,24 @@ public final class DataStoreOpener extends
Task<DataStore> {
* terminated in case some of them were using a data store. The data
stores will be closed
* in parallel.
*
- * @throws Exception if an error occurred while closing at least one data
store.
+ * @throws DataStoreException if an error occurred while closing at least
one data store.
*/
- static void closeAll() throws Exception {
- final Closer closer = new Closer();
- do {
- // Use `toArray()` because we need a snapshot.
- Stream.of(CACHE.keySet().toArray()).parallel().forEach(closer);
- } while (!CACHE.isEmpty());
- closer.rethrow();
+ static void closeAll() throws DataStoreException {
+ do CLOSER.closeAll(List.copyOf(CACHE.keySet()));
+ while (!CACHE.isEmpty());
}
/**
- * The handler in charge of closing the data store and record the failures
if some errors happen.
- * The same handler instance may be used concurrently while closing many
data stores in parallel.
+ * Helper for closing concurrently the stores.
*/
- private static final class Closer implements Consumer<Object> {
- /** The error that occurred while closing a data store. */
- private Exception error;
-
- /** Closes the given data store. */
- @Override public void accept(final Object source) {
- final DataStore toClose = CACHE.remove(source);
- if (source != null) try {
- toClose.close();
- } catch (Exception e) {
- synchronized (this) {
- if (error == null) error = e;
- else error.addSuppressed(e);
- }
- }
+ public static final ConcurrentCloser<Object> CLOSER = new
ConcurrentCloser<>() {
+ @Override protected Callable<?> closer(final Object key) {
+ final DataStore store = CACHE.remove(key);
+ if (store != null) return () -> {
+ store.close();
+ return null;
+ };
+ return null;
}
-
- /** If an error occurred, re-throws that error. */
- synchronized void rethrow() throws Exception {
- if (error != null) {
- throw error;
- }
- }
- }
+ };
}
diff --git
a/core/sis-utility/src/main/java/org/apache/sis/internal/system/CommonExecutor.java
b/core/sis-utility/src/main/java/org/apache/sis/internal/system/CommonExecutor.java
index 17b5e7b6ac..8b8e47e837 100644
---
a/core/sis-utility/src/main/java/org/apache/sis/internal/system/CommonExecutor.java
+++
b/core/sis-utility/src/main/java/org/apache/sis/internal/system/CommonExecutor.java
@@ -27,7 +27,7 @@ import java.util.concurrent.atomic.AtomicInteger;
/**
* The executor shared by most of Apache SIS library for relatively "heavy"
operations.
- * The operations should relatively long tasks, otherwise work-stealing
algorithms may
+ * The operations should be relatively long tasks, otherwise work-stealing
algorithms may
* provide better performances. For example, it may be used when each
computational unit
* is an image tile, in which case the thread scheduling overhead is small
compared to
* the size of the computational task.
diff --git
a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/LandsatStore.java
b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/LandsatStore.java
index e2ef1b4ebb..60c3117d6f 100644
---
a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/LandsatStore.java
+++
b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/LandsatStore.java
@@ -19,6 +19,7 @@ package org.apache.sis.storage.landsat;
import java.util.Map;
import java.util.List;
import java.util.Optional;
+import java.util.concurrent.Callable;
import java.io.Reader;
import java.io.BufferedReader;
import java.io.LineNumberReader;
@@ -45,6 +46,7 @@ import org.apache.sis.storage.event.StoreEvent;
import org.apache.sis.storage.event.StoreListener;
import org.apache.sis.storage.event.WarningEvent;
import org.apache.sis.internal.storage.URIDataStore;
+import org.apache.sis.internal.storage.folder.ConcurrentCloser;
import org.apache.sis.internal.system.DefaultFactories;
import org.apache.sis.internal.util.UnmodifiableArrayList;
import org.apache.sis.setup.OptionKey;
@@ -78,7 +80,7 @@ import org.apache.sis.setup.OptionKey;
*
* @author Thi Phuong Hao Nguyen (VNSC)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
* @since 1.1
*/
public class LandsatStore extends DataStore implements Aggregate {
@@ -111,7 +113,8 @@ public class LandsatStore extends DataStore implements
Aggregate {
* The array of aggregates for each Landsat band group, or {@code null} if
not yet created.
* This array is created together with {@linkplain #metadata} and is
unmodifiable.
*/
- private BandGroup[] components;
+ @SuppressWarnings("VolatileArrayField") // Array elements are not
modified after creation.
+ private volatile BandGroup[] components;
/**
* Creates a new Landsat store from the given file, URL, stream or
character reader.
@@ -203,7 +206,7 @@ public class LandsatStore extends DataStore implements
Aggregate {
* Parses the main Landsat text file.
* Also creates the array of components, but without loading GeoTIFF data
yet.
*/
- private void loadMetadata() throws DataStoreException {
+ private BandGroup[] loadMetadata() throws DataStoreException {
if (source == null) {
throw new DataStoreClosedException(getLocale(),
LandsatStoreProvider.NAME, StandardOpenOption.READ);
}
@@ -234,10 +237,12 @@ public class LandsatStore extends DataStore implements
Aggregate {
} catch (FactoryException e) {
throw new DataStoreReferencingException(e);
}
- components = BandGroup.group(listeners, resources, count);
- for (final BandGroup c : components) {
+ final BandGroup[] bands = BandGroup.group(listeners, resources, count);
+ for (final BandGroup c : bands) {
c.identifier = factory.createLocalName(scope, c.group.name());
}
+ components = bands;
+ return bands;
}
/**
@@ -266,10 +271,11 @@ public class LandsatStore extends DataStore implements
Aggregate {
*/
@Override
public synchronized List<Aggregate> components() throws DataStoreException
{
- if (components == null) {
- loadMetadata();
+ BandGroup[] bands = components;
+ if (bands == null) {
+ bands = loadMetadata();
}
- return UnmodifiableArrayList.wrap(components);
+ return UnmodifiableArrayList.wrap(bands);
}
/**
@@ -287,26 +293,32 @@ public class LandsatStore extends DataStore implements
Aggregate {
/**
* Closes this Landsat store and releases any underlying resources.
+ * This method can be invoked asynchronously for interrupting a long
reading process.
*
* @throws DataStoreException if an error occurred while closing the
Landsat file.
*/
@Override
- public synchronized void close() throws DataStoreException {
+ public void close() throws DataStoreException {
listeners.close(); // Should never fail.
- metadata = null;
- DataStoreException error = null;
- for (final Band band : BandGroup.bands(components)) {
- try {
- band.closeDataStore();
- } catch (DataStoreException e) {
- if (error == null) {
- error = e;
- } else {
- error.addSuppressed(e);
- }
+ try {
+ CLOSER.closeAll(BandGroup.bands(components));
+ } finally {
+ synchronized (this) {
+ metadata = null;
+ components = null;
}
}
- components = null;
- if (error != null) throw error;
}
+
+ /**
+ * Helper for closing concurrently the images for each band.
+ */
+ private static final ConcurrentCloser<Band> CLOSER = new
ConcurrentCloser<>() {
+ @Override protected Callable<?> closer(final Band r) {
+ return () -> {
+ r.closeDataStore();
+ return null;
+ };
+ }
+ };
}
diff --git
a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/package-info.java
b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/package-info.java
index 5149314602..93e964fb18 100644
---
a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/package-info.java
+++
b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/package-info.java
@@ -27,7 +27,7 @@
* @author Thi Phuong Hao Nguyen (VNSC)
* @author Minh Chinh Vu (VNSC)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
* @since 1.1
*/
package org.apache.sis.storage.landsat;
diff --git
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
index cc01325ed6..218f2bb4f1 100644
---
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
+++
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
@@ -68,7 +68,7 @@ import org.apache.sis.util.ArgumentChecks;
* @author Martin Desruisseaux (Geomatys)
* @author Thi Phuong Hao Nguyen (VNSC)
* @author Alexis Manin (Geomatys)
- * @version 1.3
+ * @version 1.4
* @since 0.8
*/
public class GeoTiffStore extends DataStore implements Aggregate {
@@ -83,7 +83,7 @@ public class GeoTiffStore extends DataStore implements
Aggregate {
*
* @see #reader()
*/
- private Reader reader;
+ private volatile Reader reader;
/**
* The {@link GeoTiffStoreProvider#LOCATION} parameter value, or {@code
null} if none.
@@ -220,6 +220,7 @@ public class GeoTiffStore extends DataStore implements
Aggregate {
* This method must be invoked inside a block synchronized on {@code this}.
*/
final NameSpace namespace() {
+ final Reader reader = this.reader;
if (!isNamespaceSet && reader != null) {
final NameFactory f = reader.nameFactory;
GenericName name = null;
@@ -537,19 +538,26 @@ public class GeoTiffStore extends DataStore implements
Aggregate {
/**
* Closes this GeoTIFF store and releases any underlying resources.
+ * This method can be invoked asynchronously for interrupting a long
reading process.
*
* @throws DataStoreException if an error occurred while closing the
GeoTIFF file.
*/
@Override
- public synchronized void close() throws DataStoreException {
- listeners.close(); // Should never fail.
- final Reader r = reader;
- reader = null;
- components = null;
- if (r != null) try {
- r.close();
+ public void close() throws DataStoreException {
+ try {
+ listeners.close(); // Should never fail.
+ final Reader r = reader;
+ if (r != null) r.close();
} catch (IOException e) {
throw new DataStoreException(e);
+ } finally {
+ synchronized (this) {
+ components = null;
+ namespace = null;
+ metadata = null;
+ nativeMetadata = null;
+ reader = null;
+ }
}
}
diff --git
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Reader.java
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Reader.java
index 6cee41a8b5..01c4257a13 100644
---
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Reader.java
+++
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Reader.java
@@ -453,6 +453,7 @@ final class Reader extends GeoTIFF {
/**
* Closes this reader.
+ * This method can be invoked asynchronously for interrupting a long
reading process.
*
* @throws IOException if an error occurred while closing this reader.
*/
diff --git
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
index 676c79abea..a07c219491 100644
---
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
+++
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
@@ -28,7 +28,6 @@ import java.util.concurrent.TimeUnit;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.Level;
-import java.io.Closeable;
import java.io.IOException;
import java.nio.file.Path;
import org.opengis.util.NameSpace;
@@ -61,7 +60,7 @@ import ucar.nc2.constants.CF;
* @version 1.4
* @since 0.3
*/
-public abstract class Decoder extends ReferencingFactoryContainer implements
Closeable {
+public abstract class Decoder extends ReferencingFactoryContainer {
/**
* The logger to use for messages other than warnings specific to the file
being read.
* This is rarely used directly because {@code listeners.getLogger()}
should be preferred.
@@ -525,4 +524,12 @@ public abstract class Decoder extends
ReferencingFactoryContainer implements Clo
final Resources resources() {
return Resources.forLocale(listeners.getLocale());
}
+
+ /**
+ * Closes this decoder and releases resources.
+ *
+ * @param lock the lock to use in {@code synchronized(lock)} statements.
+ * @throws IOException if an error occurred while closing the decoder.
+ */
+ public abstract void close(DataStore lock) throws IOException;
}
diff --git
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
index 0642762de2..1400357018 100644
---
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
+++
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
@@ -53,6 +53,7 @@ import org.apache.sis.internal.storage.io.ChannelDataInput;
import org.apache.sis.internal.util.Constants;
import org.apache.sis.internal.util.CollectionsExt;
import org.apache.sis.internal.util.StandardDateFormat;
+import org.apache.sis.storage.DataStore;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.DataStoreContentException;
import org.apache.sis.storage.event.StoreListeners;
@@ -74,7 +75,7 @@ import org.apache.sis.math.Vector;
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
*
* @see <a
href="http://portal.opengeospatial.org/files/?artifact_id=43734">NetCDF Classic
and 64-bit Offset Format (1.0)</a>
*
@@ -1046,11 +1047,13 @@ nextVar: for (final VariableInfo variable :
variables) {
/**
* Closes the channel.
+ * This method can be invoked asynchronously for interrupting a long
reading process.
*
+ * @param lock ignored because this method can be run asynchronously.
* @throws IOException if an error occurred while closing the channel.
*/
@Override
- public void close() throws IOException {
+ public void close(final DataStore lock) throws IOException {
input.channel.close();
}
diff --git
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/package-info.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/package-info.java
index 30825f10a6..2ab63c26b9 100644
---
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/package-info.java
+++
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/package-info.java
@@ -30,7 +30,7 @@
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
* @since 0.3
*/
package org.apache.sis.internal.netcdf.impl;
diff --git
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java
index e29dc154d8..14bacb7e98 100644
---
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java
+++
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java
@@ -61,16 +61,13 @@ import org.apache.sis.storage.event.StoreListeners;
* Provides netCDF decoding services based on the netCDF library.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
* @since 0.3
*/
public final class DecoderWrapper extends Decoder implements CancelTask {
/**
* The netCDF file to read.
* This file is set at construction time.
- *
- * <p>This {@code DecoderWrapper} class does <strong>not</strong> close
this file.
- * Closing this file after usage is the user responsibility.</p>
*/
private final NetcdfFile file;
@@ -93,7 +90,7 @@ public final class DecoderWrapper extends Decoder implements
CancelTask {
/**
* The discrete sampling features or grids found by UCAR library, or
{@code null} if none.
- * This reference is kept for making possible to close it in {@link
#close()}.
+ * This reference is kept for making possible to close it in {@link
#close(DataStore)}.
*
* @see #getDiscreteSampling(Object)
*/
@@ -668,15 +665,18 @@ public final class DecoderWrapper extends Decoder
implements CancelTask {
/**
* Closes the netCDF file.
*
+ * @param lock the lock to use in {@code synchronized(lock)} statements.
* @throws IOException if an error occurred while closing the file.
*/
@Override
- public void close() throws IOException {
- if (features != null) {
- features.close();
- features = null;
+ public void close(final DataStore lock) throws IOException {
+ synchronized (lock) {
+ if (features != null) {
+ features.close();
+ features = null;
+ }
+ file.close();
}
- file.close();
}
/**
diff --git
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/package-info.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/package-info.java
index 533a973f6f..6f7ee16ea1 100644
---
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/package-info.java
+++
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/package-info.java
@@ -20,7 +20,7 @@
* as wrappers around the UCAR netCDF library.
*
* @author Martin Desruisseaux (IRD, Geomatys)
- * @version 1.3
+ * @version 1.4
* @since 0.3
*/
package org.apache.sis.internal.netcdf.ucar;
diff --git
a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
index fcfacfaf30..7b0a2c0950 100644
---
a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
+++
b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
@@ -261,20 +261,26 @@ public class NetcdfStore extends DataStore implements
Aggregate {
/**
* Closes this netCDF store and releases any underlying resources.
+ * This method can be invoked asynchronously for interrupting a long
reading process.
*
* @throws DataStoreException if an error occurred while closing the
netCDF file.
*/
@Override
- public synchronized void close() throws DataStoreException {
- listeners.close(); // Should never fail.
- final Decoder reader = decoder;
- decoder = null;
- metadata = null;
- components = null;
- if (reader != null) try {
- reader.close();
+ public void close() throws DataStoreException {
+ try {
+ listeners.close(); // Should never fail.
+ final Decoder reader = decoder;
+ if (reader != null) {
+ reader.close(this);
+ }
} catch (IOException e) {
throw new DataStoreException(e);
+ } finally {
+ synchronized (this) {
+ components = null;
+ metadata = null;
+ decoder = null;
+ }
}
}
diff --git
a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestCase.java
b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestCase.java
index 65308fb37b..ebe37c7989 100644
---
a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestCase.java
+++
b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestCase.java
@@ -27,6 +27,7 @@ import org.apache.sis.storage.DataStoreException;
import org.apache.sis.internal.netcdf.ucar.DecoderWrapper;
import org.apache.sis.setup.GeometryLibrary;
import org.apache.sis.storage.event.StoreListeners;
+import org.apache.sis.storage.DataStoreMock;
import org.opengis.test.dataset.TestData;
import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.NetcdfFile;
@@ -42,7 +43,7 @@ import static org.junit.Assert.*;
* <p>This class is <strong>not</strong> thread safe - do not run subclasses
in parallel.</p>
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.4
* @since 0.3
*/
public abstract class TestCase extends org.apache.sis.test.TestCase {
@@ -175,13 +176,14 @@ public abstract class TestCase extends
org.apache.sis.test.TestCase {
*/
@AfterClass
public static void closeAllDecoders() throws IOException {
+ final var ds = new DataStoreMock("lock");
Throwable failure = null;
synchronized (DECODERS) { // Paranoiac safety.
final Iterator<Decoder> it = DECODERS.values().iterator();
while (it.hasNext()) {
final Decoder decoder = it.next();
try {
- decoder.close();
+ decoder.close(ds);
} catch (Throwable e) {
if (failure == null) {
failure = e;
diff --git
a/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java
b/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java
index c6c740199d..df2b33fbbb 100644
---
a/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java
+++
b/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java
@@ -33,6 +33,7 @@ import org.apache.sis.internal.netcdf.TestCase;
import org.apache.sis.internal.netcdf.Decoder;
import org.apache.sis.internal.netcdf.impl.ChannelDecoderTest;
import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.DataStoreMock;
import org.apache.sis.test.DependsOn;
import org.junit.Test;
@@ -45,7 +46,7 @@ import static org.apache.sis.test.TestUtilities.date;
* for reading netCDF attributes.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.4
* @since 0.3
*/
@DependsOn({
@@ -71,10 +72,9 @@ public final class MetadataReaderTest extends TestCase {
*/
@Test
public void testEmbedded() throws IOException, DataStoreException {
- final Metadata metadata;
- try (Decoder input =
ChannelDecoderTest.createChannelDecoder(TestData.NETCDF_2D_GEOGRAPHIC)) {
- metadata = new MetadataReader(input).read();
- }
+ final Decoder input =
ChannelDecoderTest.createChannelDecoder(TestData.NETCDF_2D_GEOGRAPHIC);
+ final Metadata metadata = new MetadataReader(input).read();
+ input.close(new DataStoreMock("lock"));
compareToExpected(metadata).assertMetadataEquals();
}
@@ -87,10 +87,9 @@ public final class MetadataReaderTest extends TestCase {
*/
@Test
public void testUCAR() throws IOException, DataStoreException {
- final Metadata metadata;
- try (Decoder input = createDecoder(TestData.NETCDF_2D_GEOGRAPHIC)) {
- metadata = new MetadataReader(input).read();
- }
+ final Decoder input = createDecoder(TestData.NETCDF_2D_GEOGRAPHIC);
+ final Metadata metadata = new MetadataReader(input).read();
+ input.close(new DataStoreMock("lock"));
final ContentVerifier verifier = compareToExpected(metadata);
verifier.addExpectedValue("identificationInfo[0].resourceFormat[0].formatSpecificationCitation.alternateTitle[1]",
"NetCDF-3/CDM");
verifier.assertMetadataEquals();
diff --git
a/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/NetcdfStoreProviderTest.java
b/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/NetcdfStoreProviderTest.java
index e7f8c75eb5..c31b9ae8c8 100644
---
a/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/NetcdfStoreProviderTest.java
+++
b/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/NetcdfStoreProviderTest.java
@@ -26,6 +26,7 @@ import org.apache.sis.internal.netcdf.impl.ChannelDecoderTest;
import org.apache.sis.storage.ProbeResult;
import org.apache.sis.storage.StorageConnector;
import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.DataStoreMock;
import org.apache.sis.util.Version;
import org.apache.sis.test.DependsOn;
import org.opengis.test.dataset.TestData;
@@ -38,7 +39,7 @@ import static org.opengis.test.Assert.*;
* Tests {@link NetcdfStoreProvider}.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.4
* @since 0.3
*/
@DependsOn({
@@ -90,9 +91,9 @@ public final class NetcdfStoreProviderTest extends TestCase {
@Test
public void testDecoderFromStream() throws IOException, DataStoreException
{
final StorageConnector c = new
StorageConnector(TestData.NETCDF_2D_GEOGRAPHIC.open());
- try (Decoder decoder = NetcdfStoreProvider.decoder(createListeners(),
c)) {
- assertInstanceOf("decoder", ChannelDecoder.class, decoder);
- }
+ final Decoder decoder = NetcdfStoreProvider.decoder(createListeners(),
c);
+ assertInstanceOf("decoder", ChannelDecoder.class, decoder);
+ decoder.close(new DataStoreMock("lock"));
}
/**
@@ -105,8 +106,8 @@ public final class NetcdfStoreProviderTest extends TestCase
{
@Test
public void testDecoderFromUCAR() throws IOException, DataStoreException {
final StorageConnector c = new
StorageConnector(createUCAR(TestData.NETCDF_2D_GEOGRAPHIC));
- try (Decoder decoder = NetcdfStoreProvider.decoder(createListeners(),
c)) {
- assertInstanceOf("decoder", DecoderWrapper.class, decoder);
- }
+ final Decoder decoder = NetcdfStoreProvider.decoder(createListeners(),
c);
+ assertInstanceOf("decoder", DecoderWrapper.class, decoder);
+ decoder.close(new DataStoreMock("lock"));
}
}
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/GridResourceWrapper.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/GridResourceWrapper.java
index 3b99c91f68..e4d5c628a9 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/GridResourceWrapper.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/GridResourceWrapper.java
@@ -38,7 +38,7 @@ import org.opengis.util.GenericName;
* The wrapped resource is created only when first needed.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.4
* @since 1.1
*/
public abstract class GridResourceWrapper implements GridCoverageResource {
@@ -46,7 +46,7 @@ public abstract class GridResourceWrapper implements
GridCoverageResource {
* The coverage resource instance which provides the data.
* This is initially {@code null} and created when first needed.
*/
- private GridCoverageResource source;
+ private volatile GridCoverageResource source;
/**
* Creates a new wrapper.
@@ -78,12 +78,16 @@ public abstract class GridResourceWrapper implements
GridCoverageResource {
* @throws DataStoreException if the resource cannot be created.
*/
protected final GridCoverageResource source() throws DataStoreException {
- synchronized (getSynchronizationLock()) {
- if (source == null) {
- source = createSource();
+ GridCoverageResource s = source;
+ if (s == null) {
+ synchronized (getSynchronizationLock()) {
+ s = source;
+ if (s == null) {
+ source = s = createSource();
+ }
}
- return source;
}
+ return s;
}
/**
@@ -224,18 +228,16 @@ public abstract class GridResourceWrapper implements
GridCoverageResource {
*/
@Override
public <T extends StoreEvent> void removeListener(Class<T> eventType,
StoreListener<? super T> listener) {
- final GridCoverageResource source;
- synchronized (getSynchronizationLock()) {
- source = this.source; // No need to invoke the `source()`
method here.
- }
- if (source != null) {
- source.removeListener(eventType, listener);
+ final GridCoverageResource s = source; // No need to invoke the
`source()` method here.
+ if (s != null) {
+ s.removeListener(eventType, listener);
}
}
/**
* Closes the data store associated to the resource, then discards the
resource.
* This method does not verify if the data store is still used by other
resources.
+ * This method can be invoked asynchronously for interrupting a long
reading process.
*
* @throws DataStoreException if an error occurred while closing the data
store.
*/
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/package-info.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/package-info.java
index 38cc6c369e..4277b0abcd 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/package-info.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/package-info.java
@@ -53,7 +53,7 @@
* </ul>
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
* @since 0.7
*/
package org.apache.sis.internal.storage.csv;
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/AsciiGridStore.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/AsciiGridStore.java
index 12c31f45d5..d9c1b684ce 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/AsciiGridStore.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/AsciiGridStore.java
@@ -140,7 +140,7 @@ import org.apache.sis.util.resources.Errors;
* which is usually the case given how inefficient this format is.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
* @since 1.2
*/
class AsciiGridStore extends RasterStore {
@@ -176,7 +176,7 @@ class AsciiGridStore extends RasterStore {
* Note that a null value does not necessarily means that the store is
closed, because
* it may have finished to read fully the {@linkplain #coverage}.
*/
- private CharactersView input;
+ private volatile CharactersView input;
/**
* The {@code NCOLS} and {@code NROWS} attributes read from the header.
@@ -345,6 +345,7 @@ cellsize: if (value != null) {
/**
* Returns the error message for an exception or log record.
+ * Invoke only in contexts where {@link #input} is known to be non-null.
*
* @param rk {@link Errors.Keys#IllegalValueForProperty_2} or {@link
Errors.Keys#MissingValueForProperty_2}.
* @param key key of the header property which was requested.
@@ -539,21 +540,27 @@ cellsize: if (value != null) {
/**
* Closes this data store and releases any underlying resources.
+ * This method can be invoked asynchronously for interrupting a long
reading process.
*
* @throws DataStoreException if an error occurred while closing this data
store.
*/
@Override
- public synchronized void close() throws DataStoreException {
- listeners.close(); // Should never fail.
- final CharactersView view = input;
- input = null; // Cleared first in case of
failure.
- coverage = null;
- gridGeometry = null;
- super.close(); // Clear more fields. Never fail.
- if (view != null) try {
- view.input.channel.close();
+ public void close() throws DataStoreException {
+ try {
+ listeners.close(); // Should never fail.
+ final CharactersView view = input;
+ if (view != null) {
+ view.input.channel.close();
+ }
} catch (IOException e) {
throw new DataStoreException(e);
+ } finally {
+ synchronized (this) {
+ super.close(); // Clear more fields. Never
fail.
+ gridGeometry = null;
+ coverage = null;
+ input = null;
+ }
}
}
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RasterStore.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RasterStore.java
index c0fa37220f..fae34dca7e 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RasterStore.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RasterStore.java
@@ -508,7 +508,7 @@ abstract class RasterStore extends PRJDataStore implements
GridCoverageResource
/**
* Closes this data store and releases any underlying resources.
- * Shall be overridden by subclasses in a synchronized method.
+ * Shall be overridden by subclasses inside a synchronized block.
*
* @throws DataStoreException if an error occurred while closing this data
store.
*/
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RawRasterStore.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RawRasterStore.java
index 9ffac1a493..60fca66fef 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RawRasterStore.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RawRasterStore.java
@@ -61,7 +61,7 @@ import static org.apache.sis.internal.util.Numerics.wholeDiv;
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
* @since 1.2
*/
final class RawRasterStore extends RasterStore {
@@ -170,7 +170,7 @@ final class RawRasterStore extends RasterStore {
/**
* The object to use for reading data, or {@code null} if the channel has
been closed.
*/
- private ChannelDataInput input;
+ private volatile ChannelDataInput input;
/**
* Helper method for reading a rectangular region from the {@linkplain
#input} stream.
@@ -244,6 +244,7 @@ final class RawRasterStore extends RasterStore {
*/
@Override
public synchronized List<SampleDimension> getSampleDimensions() throws
DataStoreException {
+ final ChannelDataInput input = this.input;
List<SampleDimension> sampleDimensions = super.getSampleDimensions();
if (sampleDimensions == null) try {
if (reader == null) {
@@ -337,6 +338,7 @@ final class RawRasterStore extends RasterStore {
*/
private void readHeader() throws IOException, DataStoreException {
assert Thread.holdsLock(this);
+ final ChannelDataInput input = this.input;
if (input == null) {
throw new DataStoreClosedException(canNotRead());
}
@@ -541,20 +543,26 @@ final class RawRasterStore extends RasterStore {
/**
* Closes this data store and releases any underlying resources.
+ * This method can be invoked asynchronously for interrupting a long
reading process.
*
* @throws DataStoreException if an error occurred while closing this data
store.
*/
@Override
- public synchronized void close() throws DataStoreException {
- listeners.close(); // Should never fail.
- final ChannelDataInput in = input;
- input = null; // Cleared first in case of
failure.
- reader = null;
- super.close(); // Clear more fields. Never fail.
- if (in != null) try {
- in.channel.close();
- } catch (IOException e) {
- throw new DataStoreException(e);
+ public void close() throws DataStoreException {
+ try {
+ listeners.close(); // Should never fail.
+ final ChannelDataInput input = this.input;
+ if (input != null) try {
+ input.channel.close();
+ } catch (IOException e) {
+ throw new DataStoreException(e);
+ }
+ } finally {
+ synchronized (this) {
+ input = null; // Cleared first in case
of failure.
+ reader = null;
+ super.close(); // Clear more fields.
Never fail.
+ }
}
}
}
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/WritableStore.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/WritableStore.java
index aeb7117f5d..b4a2b1e001 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/WritableStore.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/WritableStore.java
@@ -289,6 +289,9 @@ final class WritableStore extends AsciiGridStore implements
WritableGridCoverage
/**
* Closes this data store and releases any underlying resources.
+ * If a read or write operation is in progress in another thread,
+ * then this method blocks until that operation completed.
+ * This restriction is for avoiding data lost.
*
* @throws DataStoreException if an error occurred while closing this data
store.
*/
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/ConcurrentCloser.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/ConcurrentCloser.java
new file mode 100644
index 0000000000..15e59f86dc
--- /dev/null
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/ConcurrentCloser.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.storage.folder;
+
+import java.util.Collection;
+import java.util.concurrent.Future;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ExecutionException;
+import org.apache.sis.storage.Resource;
+import org.apache.sis.storage.DataStore;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.internal.storage.StoreResource;
+import org.apache.sis.internal.system.CommonExecutor;
+
+
+/**
+ * Helper class for closing concurrently a collection of data stores.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.4
+ *
+ * @param <R> type of resource to close.
+ *
+ * @since 1.4
+ */
+public abstract class ConcurrentCloser<R> {
+ /**
+ * A closer for a collection of resources which may be data stores.
+ * This closer does not check for {@link StoreResource} instances.
+ */
+ public static final ConcurrentCloser<Resource> RESOURCES = new
ConcurrentCloser<>() {
+ @Override protected Callable<?> closer(final Resource r) {
+ if (r instanceof DataStore) {
+ final DataStore ds = (DataStore) r;
+ return () -> {
+ ds.close();
+ return null;
+ };
+ } else return null;
+ }
+ };
+
+ /**
+ * Creates a new closer.
+ */
+ protected ConcurrentCloser() {
+ }
+
+ /**
+ * Creates a task to be invoked in a background thread for closing the
given resource.
+ * The return value of the callable will be ignored.
+ *
+ * @param resource the resource to close.
+ * @return the task for closing the given resource, or {@code null} if
none.
+ */
+ protected abstract Callable<?> closer(R resource);
+
+ /**
+ * Closes concurrently all the given resources.
+ *
+ * @param resources the resource to close.
+ * @throws DataStoreException if at least one error occurred while closing
a resource.
+ */
+ public final void closeAll(final Collection<? extends R> resources) throws
DataStoreException {
+ final ExecutorService executor = CommonExecutor.instance();
+ final Future<?>[] results = new Future<?>[resources.size()];
+ int n = 0;
+ for (final R r : resources) {
+ final Callable<?> c = closer(r);
+ if (c != null) {
+ results[n++] = executor.submit(c);
+ }
+ }
+ /*
+ * Wait for all tasks to complete and collect
+ * the exceptions that are thrown, if any.
+ */
+ DataStoreException failure = null;
+ for (int i=0; i<n; i++) {
+ try {
+ results[i].get();
+ } catch (InterruptedException | ExecutionException ex) {
+ Throwable cause = ex.getCause();
+ if (cause == null) cause = ex;
+ if (failure != null) {
+ failure.addSuppressed(cause);
+ } else if (cause instanceof DataStoreException) {
+ failure = (DataStoreException) cause;
+ } else {
+ failure = new DataStoreException(cause);
+ }
+ }
+ }
+ if (failure != null) {
+ throw failure;
+ }
+ }
+}
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
index f0d7d09bb1..0e1d24c903 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
@@ -76,7 +76,7 @@ import org.apache.sis.internal.storage.Resources;
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
* @since 0.8
*/
class Store extends DataStore implements StoreResource, UnstructuredAggregate,
DirectoryStream.Filter<Path> {
@@ -274,6 +274,7 @@ class Store extends DataStore implements StoreResource,
UnstructuredAggregate, D
mb.addResourceScope(ScopeCode.COLLECTION,
Resources.formatInternational(Resources.Keys.DirectoryContent_1,
getDisplayName()));
mb.addLanguage(locale, MetadataBuilder.Scope.RESOURCE);
mb.addEncoding(encoding, MetadataBuilder.Scope.RESOURCE);
+ final GenericName identifier = identifier(null);
String name = null;
if (identifier != null) {
name = identifier.toString();
@@ -436,28 +437,23 @@ class Store extends DataStore implements StoreResource,
UnstructuredAggregate, D
/**
* Closes all children resources.
+ * This method can be invoked asynchronously for interrupting a long
reading process
+ * if the children stores also support asynchronous close operations.
*/
@Override
- public synchronized void close() throws DataStoreException {
- listeners.close(); // Should
never fail.
- final Collection<Resource> resources = components;
- if (resources != null) {
- components = null; // Clear
first in case of failure.
- DataStoreException failure = null;
- for (final Resource r : resources) {
- if (r instanceof DataStore) try {
- ((DataStore) r).close();
- } catch (DataStoreException ex) {
- if (failure == null) {
- failure = ex;
- } else {
- failure.addSuppressed(ex);
- }
- }
- }
- if (failure != null) {
- throw failure;
- }
+ public void close() throws DataStoreException {
+ listeners.close(); // Should never fail.
+ final Collection<Resource> resources;
+ synchronized (this) {
+ resources = components;
+ components = List.of();
+ identifier = null;
+ metadata = null;
+ structuredView = null;
+ children.clear();
+ }
+ if (resources != null && !resources.isEmpty()) {
+ ConcurrentCloser.RESOURCES.closeAll(resources);
}
}
}
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/WorldFileStore.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/WorldFileStore.java
index 3915013420..948f1a83bb 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/WorldFileStore.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/WorldFileStore.java
@@ -111,7 +111,7 @@ import org.apache.sis.setup.OptionKey;
* is known to support only one image per file.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
* @since 1.2
*/
public class WorldFileStore extends PRJDataStore {
@@ -166,7 +166,7 @@ public class WorldFileStore extends PRJDataStore {
*
* @see #reader()
*/
- private ImageReader reader;
+ private volatile ImageReader reader;
/**
* The object to close when {@code WorldFileStore} is closed. It may be a
different object than
@@ -286,6 +286,7 @@ public class WorldFileStore extends PRJDataStore {
* does not support the locale, the reader's default locale will be used.
*/
private void configureReader() {
+ final ImageReader reader = this.reader;
try {
reader.setLocale(listeners.getLocale());
} catch (IllegalArgumentException e) {
@@ -432,6 +433,7 @@ loop: for (int convention=0;; convention++) {
* @return the requested names, or an empty array if none or unknown.
*/
public String[] getImageFormat(final boolean asMimeType) {
+ final ImageReader reader = this.reader;
if (reader != null) {
final ImageReaderSpi provider = reader.getOriginatingProvider();
if (provider != null) {
@@ -804,34 +806,38 @@ loop: for (int convention=0;; convention++) {
/**
* Closes this data store and releases any underlying resources.
+ * If a read operation is in progress, it will be aborted.
*
* @throws DataStoreException if an error occurred while closing this data
store.
*/
@Override
- public synchronized void close() throws DataStoreException {
+ public void close() throws DataStoreException {
listeners.close(); // Should never fail.
final ImageReader codec = reader;
- final Closeable stream = toClose;
- reader = null;
- toClose = null;
- metadata = null;
- components = null;
- gridGeometry = null;
- try {
- Object input = null;
- if (codec != null) {
- input = codec.getInput();
- codec.setInput(null);
- codec.dispose();
- if (input instanceof AutoCloseable) {
- ((AutoCloseable) input).close();
+ if (codec != null) codec.abort();
+ synchronized (this) {
+ final Closeable stream = toClose;
+ reader = null;
+ toClose = null;
+ metadata = null;
+ components = null;
+ gridGeometry = null;
+ try {
+ Object input = null;
+ if (codec != null) {
+ input = codec.getInput();
+ codec.reset();
+ codec.dispose();
+ if (input instanceof AutoCloseable) {
+ ((AutoCloseable) input).close();
+ }
}
+ if (stream != null && stream != input) {
+ stream.close();
+ }
+ } catch (Exception e) {
+ throw new DataStoreException(e);
}
- if (stream != null && stream != input) {
- stream.close();
- }
- } catch (Exception e) {
- throw new DataStoreException(e);
}
}
}
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/WritableStore.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/WritableStore.java
index 60b69bebf7..b1ec9ff290 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/WritableStore.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/WritableStore.java
@@ -488,6 +488,9 @@ writeCoeffs: for (int i=0;; i++) {
/**
* Closes this data store and releases any underlying resources.
+ * If a read or write operation is in progress in another thread,
+ * then this method blocks until that operation completed.
+ * This restriction is for avoiding data lost.
*
* @throws DataStoreException if an error occurred while closing this data
store.
*/
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/FileCacheByteChannel.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/FileCacheByteChannel.java
index 5fc3577808..c1d86edff0 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/FileCacheByteChannel.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/FileCacheByteChannel.java
@@ -273,7 +273,7 @@ public abstract class FileCacheByteChannel extends
ByteRangeChannel {
* @see #openConnection(long, long)
* @see #abort(InputStream)
*/
- private Connection connection;
+ private volatile Connection connection;
/**
* An optional filter to apply on the input stream opened for a
connections.
@@ -747,17 +747,18 @@ public abstract class FileCacheByteChannel extends
ByteRangeChannel {
private long drainAndAbort() throws IOException {
assert Thread.holdsLock(this);
long count = 0;
- final InputStream input = connection.input;
- for (int c; (c = input.available()) > 0;) {
+ final Connection c = connection;
+ final InputStream input = c.input;
+ for (int r; (r = input.available()) > 0;) {
final ByteBuffer buffer = transfer();
buffer.clear();
- if (c < BUFFER_SIZE) buffer.limit(c);
+ if (r < BUFFER_SIZE) buffer.limit(r);
final int n = input.read(buffer.array(), 0, buffer.limit());
if (n < 0) break;
cache(buffer.limit(n));
count += n;
}
- if (abort(connection)) {
+ if (abort(c)) {
connection = null;
}
return count;
@@ -803,19 +804,23 @@ public abstract class FileCacheByteChannel extends
ByteRangeChannel {
/**
* Closes this channel and releases resources.
+ * This method can be invoked asynchronously for interrupting a long
reading process.
*
* @throws IOException if an error occurred while closing the channel.
*/
@Override
- public synchronized void close() throws IOException {
+ public void close() throws IOException {
final Connection c = connection;
- connection = null;
- transfer = null;
- idleHandler = null;
try (file) {
if (c != null && !abort(c)) {
c.input.close();
}
+ } finally {
+ synchronized (this) {
+ transfer = null;
+ idleHandler = null;
+ connection = null;
+ }
}
}
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/Store.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/Store.java
index ae7ec95337..9baaaf1774 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/Store.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/Store.java
@@ -47,7 +47,7 @@ import org.apache.sis.util.CharSequences;
* the file containing WKT definition is the main file, not an auxiliary
file.</div>
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
* @since 0.7
*/
final class Store extends URIDataStore {
@@ -61,7 +61,7 @@ final class Store extends URIDataStore {
/**
* The reader, set by the constructor and cleared when no longer needed.
*/
- private Reader source;
+ private volatile Reader source;
/**
* The locale for {@link org.opengis.util.InternationalString} localization
@@ -117,7 +117,6 @@ final class Store extends URIDataStore {
private void parse() throws DataStoreException {
final Reader in = source;
if (in != null) try {
- source = null; //
Cleared first in case of error.
final String wkt;
try {
char[] buffer = new char[FirstKeywordPeek.READ_AHEAD_LIMIT];
@@ -134,6 +133,7 @@ final class Store extends URIDataStore {
}
wkt = String.valueOf(buffer, 0, length);
} finally {
+ source = null;
in.close();
}
/*
@@ -193,19 +193,24 @@ final class Store extends URIDataStore {
/**
* Closes this data store and releases any underlying resources.
+ * This method can be invoked asynchronously for interrupting a long
reading process.
*
* @throws DataStoreException if an error occurred while closing this data
store.
*/
@Override
- public synchronized void close() throws DataStoreException {
- listeners.close(); // Should never fail.
- final Reader s = source;
- source = null; // Cleared first in case of failure.
- objects.clear();
- if (s != null) try {
- s.close();
+ public void close() throws DataStoreException {
+ try {
+ listeners.close(); // Should never fail.
+ final Reader s = source;
+ if (s != null) s.close();
} catch (IOException e) {
throw new DataStoreException(e);
+ } finally {
+ synchronized (this) {
+ objects.clear();
+ metadata = null;
+ source = null;
+ }
}
}
}
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java
index d0c4643404..c8d0135aa0 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java
@@ -56,14 +56,14 @@ import org.apache.sis.setup.OptionKey;
* The above list may be extended in any future SIS version.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
* @since 0.4
*/
final class Store extends URIDataStore implements Filter {
/**
* The input stream or reader, set by the constructor and cleared when no
longer needed.
*/
- private StreamSource source;
+ private volatile StreamSource source;
/**
* The unmarshalled object, initialized only when first needed.
@@ -151,11 +151,11 @@ final class Store extends URIDataStore implements Filter {
private void unmarshal() throws DataStoreException {
final StreamSource s = source;
final Closeable in = input(s);
- source = null; // Cleared first in case of
error.
if (in != null) try {
try {
object = XML.unmarshal(s, properties());
} finally {
+ source = null;
in.close();
}
} catch (JAXBException | IOException e) {
@@ -217,19 +217,23 @@ final class Store extends URIDataStore implements Filter {
/**
* Closes this data store and releases any underlying resources.
+ * This method can be invoked asynchronously for interrupting a long
reading process.
*
* @throws DataStoreException if an error occurred while closing this data
store.
*/
@Override
- public synchronized void close() throws DataStoreException {
- listeners.close(); // Should never fail.
- object = null;
- final Closeable in = input(source);
- source = null; // Cleared first in case of
failure.
- if (in != null) try {
- in.close();
+ public void close() throws DataStoreException {
+ try {
+ listeners.close(); // Should never fail.
+ final Closeable in = input(source);
+ if (in != null) in.close();
} catch (IOException e) {
throw new DataStoreException(e);
+ } finally {
+ synchronized (this) {
+ object = null;
+ source = null;
+ }
}
}
}
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
index 22ac8baa6c..338cefde18 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
@@ -528,6 +528,11 @@ public abstract class DataStore implements Resource,
Localized, AutoCloseable {
* Closes this data store and releases any underlying resources.
* A {@link CloseEvent} is sent to listeners before the data store is
closed.
*
+ * <p>If this method is invoked asynchronously while a read operation is
in progress in another thread,
+ * then the behavior is implementation dependent. Some implementations
will interrupt the read process,
+ * for example with an {@link
java.nio.channels.AsynchronousCloseException}. This is useful if the data
+ * store was downloading a large file from a network connection.</p>
+ *
* <h4>Note for implementers</h4>
* Implementations should invoke {@code listeners.close()} on their first
line
* for sending notification to all listeners before the data store is
actually
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreListeners.java
b/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreListeners.java
index 132133dcb2..ffd79c718a 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreListeners.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreListeners.java
@@ -953,11 +953,14 @@ public class StoreListeners implements Localized {
} catch (ExecutionException ex) {
canNotNotify("close", ex);
}
- listeners = null;
/*
- * No need to cleanup `cascadedListeners`. It does not hurt (those
listeners practically
- * become no-op) and the objects are probably going to be garbage
collected soon anyway.
+ * This `StoreListeners` may not be garbage-collected immediately if
the data store has been closed
+ * asynchronously. So clearing the following fields may help to
garbage-collect some more resources.
*/
+ synchronized (this) {
+ cascadedListeners = null;
+ listeners = null;
+ }
}
/**
diff --git
a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java
b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java
index e3115b6993..9fba9cb8f4 100644
---
a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java
+++
b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java
@@ -238,6 +238,7 @@ public class Store extends StaxDataStore implements
FeatureSet {
/**
* Closes only the reader, without closing this store.
* This method may be invoked before write operation.
+ * It must be invoked inside a synchronized block.
*/
final void closeReader() throws Exception {
final Reader r = reader;
@@ -255,6 +256,8 @@ public class Store extends StaxDataStore implements
FeatureSet {
@Override
public synchronized void close() throws DataStoreException {
listeners.close(); // Should never fail.
+ version = null;
+ metadata = null;
try {
closeReader();
} catch (Exception e) {
diff --git
a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxDataStore.java
b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxDataStore.java
index 49dd9e19ff..e57fb2701c 100644
---
a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxDataStore.java
+++
b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxDataStore.java
@@ -61,7 +61,7 @@ import org.apache.sis.storage.UnsupportedStorageException;
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
* @since 0.8
*/
public abstract class StaxDataStore extends URIDataStore {
@@ -122,7 +122,7 @@ public abstract class StaxDataStore extends URIDataStore {
* stream or channel opened for that path.
*
* <p>We keep this reference as long as possible in order to use {@link
#mark()} and {@link #reset()}
- * instead of creating new streams for re-reading the data. If we cannot
reset the stream but can
+ * instead of creating new streams for re-reading the data. If we cannot
reset the stream but can
* create a new one, then this field will become a reference to the new
stream. This change should be
* done only in last resort, when there is no way to reuse the existing
stream. This is because the
* streams created by {@link ChannelFactory#inputStream(String,
StoreListeners)} are not of the same
@@ -130,7 +130,7 @@ public abstract class StaxDataStore extends URIDataStore {
*
* @see #close()
*/
- private AutoCloseable stream;
+ private volatile AutoCloseable stream;
/**
* Position of the first byte to read in the {@linkplain #stream}, or a
negative value if unknown.
@@ -604,16 +604,21 @@ public abstract class StaxDataStore extends URIDataStore {
* @throws DataStoreException if an error occurred while closing the input
or output stream.
*/
@Override
- public synchronized void close() throws DataStoreException {
- final AutoCloseable s = stream;
- stream = null;
- storage = null;
- inputFactory = null;
- outputFactory = null;
- if (s != null) try {
- s.close();
+ public void close() throws DataStoreException {
+ try {
+ final AutoCloseable s = stream;
+ if (s != null) s.close();
+ } catch (DataStoreException e) {
+ throw e;
} catch (Exception e) {
throw new DataStoreException(e);
+ } finally {
+ synchronized (this) {
+ outputFactory = null;
+ inputFactory = null;
+ storage = null;
+ stream = null;
+ }
}
}
}