diff --git a/pom.xml b/pom.xml
index b10365b43..00b2f8836 100755
--- a/pom.xml
+++ b/pom.xml
@@ -231,8 +231,10 @@
org.apache.maven.plugins
maven-surefire-plugin
+ 2.19.1
false
+ once
diff --git a/src/main/java/net/openhft/chronicle/hash/impl/VanillaChronicleHash.java b/src/main/java/net/openhft/chronicle/hash/impl/VanillaChronicleHash.java
index 803d1d957..4582481d2 100644
--- a/src/main/java/net/openhft/chronicle/hash/impl/VanillaChronicleHash.java
+++ b/src/main/java/net/openhft/chronicle/hash/impl/VanillaChronicleHash.java
@@ -164,12 +164,25 @@ public abstract class VanillaChronicleHash> allContexts;
- public VanillaChronicleHash(ChronicleMapBuilder builder) {
- createdOrInMemory = true;
+ /**
+ * {@link net.openhft.chronicle.map.ChronicleMapCloseOnExitHook} needs to use
+ * {@code VanillaChronicleHash}es as WeakHashMap keys, but with identity comparison, not Map's
+ * equals() and hashCode().
+ */
+ public class Identity {
+ public VanillaChronicleHash hash() {
+ return VanillaChronicleHash.this;
+ }
+ }
+ public transient Identity identity;
+
+ public VanillaChronicleHash(ChronicleMapBuilder builder) {
// Version
dataFileVersion = BuildVersion.version();
+ createdOrInMemory = true;
+
@SuppressWarnings({"deprecation", "unchecked"})
ChronicleHashBuilderPrivateAPI privateAPI =
(ChronicleHashBuilderPrivateAPI) builder.privateAPI();
@@ -231,13 +244,13 @@ public abstract class VanillaChronicleHash "dataFileVersion").text();
+
// Previously this assignment was done in default field initializer, but with Wire
// serialization VanillaChronicleMap instance is created with unsafe.allocateInstance(),
// that doesn't guarantee (?) to initialize fields with default values (false for boolean)
createdOrInMemory = false;
- dataFileVersion = wireIn.read(() -> "dataFileVersion").text();
-
keyClass = wireIn.read(() -> "keyClass").typeLiteral();
keySizeMarshaller = wireIn.read(() -> "keySizeMarshaller").typedMarshallable();
keyReader = wireIn.read(() -> "keyReader").typedMarshallable();
@@ -385,6 +398,7 @@ public abstract class VanillaChronicleHash();
+ identity = new Identity();
}
public final void initBeforeMapping(
diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java
index 1f7f8ee5e..223f023a8 100755
--- a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java
+++ b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java
@@ -1504,14 +1504,19 @@ public final class ChronicleMapBuilder implements
} finally {
if (result != null) {
result.registerRafReleaser();
- // Ensure safe publication of a ChronicleMap
- OS.memory().storeFence();
+ prepareMapPublication(result);
} else {
CanonicalRandomAccessFiles.release(file);
}
}
}
+ private static void prepareMapPublication(VanillaChronicleMap map) {
+ // Ensure safe publication of a ChronicleMap
+ OS.memory().storeFence();
+ ChronicleMapCloseOnExitHook.add(map);
+ }
+
/**
* @return size of the self bootstrapping header
*/
@@ -1669,8 +1674,7 @@ public final class ChronicleMapBuilder implements
lazyNativeBytesStoreWithFixedCapacity(map.sizeInBytesWithoutTiers());
map.createMappedStoreAndSegments(bytesStore);
establishReplication(map);
- // Ensure "safe publication" of a ChronicleMap
- OS.memory().storeFence();
+ prepareMapPublication(map);
return map;
} catch (IOException e) {
// file-less version should never trigger an IOException.
diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMapCloseOnExitHook.java b/src/main/java/net/openhft/chronicle/map/ChronicleMapCloseOnExitHook.java
new file mode 100644
index 000000000..cff5813f7
--- /dev/null
+++ b/src/main/java/net/openhft/chronicle/map/ChronicleMapCloseOnExitHook.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2016 Roman Leventov
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package net.openhft.chronicle.map;
+
+
+import net.openhft.chronicle.hash.impl.VanillaChronicleHash;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.TreeMap;
+import java.util.WeakHashMap;
+
+final class ChronicleMapCloseOnExitHook {
+ private static final Logger LOG = LoggerFactory.getLogger(ChronicleMapCloseOnExitHook.class);
+
+ private static WeakHashMap maps = new WeakHashMap<>();
+ private static long order = 0;
+
+ static {
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ @Override
+ public void run() {
+ closeAll();
+ }
+ });
+ }
+
+ static synchronized void add(VanillaChronicleHash hash) {
+ if (maps == null)
+ throw new IllegalStateException("Shutdown in progress");
+ maps.put(hash.identity, order++);
+ }
+
+ private static void closeAll() {
+ try {
+ WeakHashMap maps;
+ synchronized (ChronicleMapCloseOnExitHook.class) {
+ maps = ChronicleMapCloseOnExitHook.maps;
+ ChronicleMapCloseOnExitHook.maps = null;
+ }
+
+ TreeMap orderedMaps = new TreeMap<>();
+ maps.forEach((identity, order) -> orderedMaps.put(order, identity.hash()));
+ // close later added maps first
+ orderedMaps.descendingMap().values().forEach(h -> {
+ try {
+ h.close();
+ } catch (RuntimeException | AssertionError e) {
+ LOG.error("", e);
+ }
+ });
+ } catch (Throwable t) {
+ LOG.error("", t);
+ throw t;
+ }
+ }
+
+ private ChronicleMapCloseOnExitHook() {}
+}