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() {} +}