From 77b678184f2c9fcb27912ab477e208ca169a2e16 Mon Sep 17 00:00:00 2001 From: udaysagar2177 Date: Tue, 4 Jun 2019 18:16:45 -0700 Subject: [PATCH] be able to skip automatic closing of a hash resource --- .../chronicle/hash/ChronicleHashBuilder.java | 11 ++ .../hash/ChronicleHashBuilderPrivateAPI.java | 5 + .../hash/impl/VanillaChronicleHash.java | 10 +- .../chronicle/map/ChronicleMapBuilder.java | 7 ++ .../map/ChronicleMapBuilderPrivateAPI.java | 5 + .../chronicle/set/ChronicleSetBuilder.java | 6 + .../set/ChronicleSetBuilderPrivateAPI.java | 5 + .../openhft/chronicle/map/ExitHookTest.java | 117 ++++++++++++++---- 8 files changed, 140 insertions(+), 26 deletions(-) diff --git a/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java b/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java index 1cf48903c..32b1bb439 100755 --- a/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java +++ b/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java @@ -762,6 +762,17 @@ public interface ChronicleHashBuilder, */ B setPreShutdownAction(Runnable preShutdownAction); + /** + * Skips the default automatic close configuration on the {@link ChronicleHash} created by + * this builder. By setting this to true, the caller agrees to closing the built {@link ChronicleHash} + * explicitly. Any pre-shutdown action configured via {@link this#setPreShutdownAction(Runnable)} + * won't be executed if skipCloseOnExitHook is set to true. + * + * @param skipCloseOnExitHook if {@code true}, default automatic close configuration is not enabled. + * @return this builder back + */ + B skipCloseOnExitHook(boolean skipCloseOnExitHook); + /** * @deprecated don't use private API in the client code */ diff --git a/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilderPrivateAPI.java b/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilderPrivateAPI.java index 18d9b7662..339b0d666 100644 --- a/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilderPrivateAPI.java +++ b/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilderPrivateAPI.java @@ -97,4 +97,9 @@ public interface ChronicleHashBuilderPrivateAPI { * @return registered pre-shutdown action. */ Runnable getPreShutdownAction(); + + /** + * Whether to skip closing a {@link ChronicleHash} using the default shutdown hook. + */ + boolean skipCloseOnExitHook(); } 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 7a80a7638..1b35b95a2 100755 --- a/src/main/java/net/openhft/chronicle/hash/impl/VanillaChronicleHash.java +++ b/src/main/java/net/openhft/chronicle/hash/impl/VanillaChronicleHash.java @@ -126,6 +126,7 @@ public abstract class VanillaChronicleHash implements MapEntryOperations entryOperations = mapEntryOperations(); MapRemoteOperations remoteOperations = mapRemoteOperations(); Runnable preShutdownAction; + boolean skipCloseOnExitHook = false; private String name; // not final because of cloning private ChronicleMapBuilderPrivateAPI privateAPI = @@ -1607,6 +1608,12 @@ public final class ChronicleMapBuilder implements return this; } + @Override + public ChronicleMapBuilder skipCloseOnExitHook(boolean skipCloseOnExitHook) { + this.skipCloseOnExitHook = skipCloseOnExitHook; + return this; + } + @Override public ChronicleMap create() { // clone() to make this builder instance thread-safe, because createWithoutFile() method diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilderPrivateAPI.java b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilderPrivateAPI.java index 5c942413d..62944a4f5 100644 --- a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilderPrivateAPI.java +++ b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilderPrivateAPI.java @@ -120,4 +120,9 @@ class ChronicleMapBuilderPrivateAPI public Runnable getPreShutdownAction() { return b.preShutdownAction; } + + @Override + public boolean skipCloseOnExitHook() { + return b.skipCloseOnExitHook; + } } diff --git a/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java b/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java index fc038ef49..3065f5574 100755 --- a/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java +++ b/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java @@ -358,6 +358,12 @@ public final class ChronicleSetBuilder return this; } + @Override + public ChronicleSetBuilder skipCloseOnExitHook(boolean skipCloseOnExitHook) { + chronicleMapBuilder.skipCloseOnExitHook(skipCloseOnExitHook); + return this; + } + /** * @deprecated don't use private API in the client code */ diff --git a/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilderPrivateAPI.java b/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilderPrivateAPI.java index 8edb56d5c..ca9c5b7b1 100644 --- a/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilderPrivateAPI.java +++ b/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilderPrivateAPI.java @@ -138,4 +138,9 @@ class ChronicleSetBuilderPrivateAPI public Runnable getPreShutdownAction() { return mapB.getPreShutdownAction(); } + + @Override + public boolean skipCloseOnExitHook() { + return mapB.skipCloseOnExitHook(); + } } diff --git a/src/test/java/net/openhft/chronicle/map/ExitHookTest.java b/src/test/java/net/openhft/chronicle/map/ExitHookTest.java index 26837dbf8..b5003cb8c 100644 --- a/src/test/java/net/openhft/chronicle/map/ExitHookTest.java +++ b/src/test/java/net/openhft/chronicle/map/ExitHookTest.java @@ -17,6 +17,7 @@ package net.openhft.chronicle.map; import com.google.common.base.Preconditions; +import net.bytebuddy.implementation.bytecode.Throw; import net.openhft.chronicle.core.OS; import org.junit.Rule; import org.junit.Test; @@ -27,39 +28,75 @@ import java.io.IOException; import java.lang.reflect.Field; import java.nio.file.Files; import java.util.Iterator; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; public class ExitHookTest { private static final int KEY = 1; + private static final int JVM_STARTUP_WAIT_TIME_MS = 15_000; private static final String PRE_SHUTDOWN_ACTION_EXECUTED = "PRE_SHUTDOWN_ACTION_EXECUTED"; + private static final String USER_SHUTDOWN_HOOK_EXECUTED = "USER_SHUTDOWN_HOOK_EXECUTED"; @Rule public TemporaryFolder folder = new TemporaryFolder(); public static void main(String[] args) throws IOException, InterruptedException { - System.out.println("Other process started"); - File mapFile = new File(args[0]); - File preShutdownActionExecutionConfirmationFile = new File(args[1]); - ChronicleMapBuilder mapBuilder = createMapBuilder(); - mapBuilder.setPreShutdownAction(() -> { + try { + System.out.println("Other process started, yo"); + File mapFile = new File(args[0]); + System.out.println("1"); + File shutdownActionConfirmationFile = new File(args[1]); + System.out.println("2"); + ChronicleMapBuilder mapBuilder = null; try { - System.out.println("Executing pre-shutdown action"); - Preconditions.checkState(preShutdownActionExecutionConfirmationFile.exists()); - Files.write(preShutdownActionExecutionConfirmationFile.toPath(), - PRE_SHUTDOWN_ACTION_EXECUTED.getBytes()); - } catch (Exception e) { - throw new RuntimeException(e); + mapBuilder = createMapBuilder(); + } catch (Throwable e) { + e.printStackTrace(); } - }); - ChronicleMap map = mapBuilder.createPersistedTo(mapFile); - try (ExternalMapQueryContext c = map.queryContext(KEY)) { - c.writeLock().lock(); - Thread.sleep(30_000); + System.out.println("3"); + boolean skipCloseOnExitHook = false; + System.out.println("4"); + if (Boolean.parseBoolean(args[2])) { + skipCloseOnExitHook = true; + } + AtomicReference> mapReference = new AtomicReference<>(); + System.out.println("exit hook" + skipCloseOnExitHook); + if (skipCloseOnExitHook) { + mapBuilder.skipCloseOnExitHook(skipCloseOnExitHook); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + // In practice, do something with map before shutdown - like create a new map using data from this map. + mapReference.get().close(); + try { + System.out.println("Executing user defined shutdown hook"); + Preconditions.checkState(shutdownActionConfirmationFile.exists()); + Files.write(shutdownActionConfirmationFile.toPath(), + USER_SHUTDOWN_HOOK_EXECUTED.getBytes()); + } catch (Exception e) { + throw new RuntimeException(e); + } + })); + } else { + mapBuilder.setPreShutdownAction(() -> { + try { + System.out.println("Executing pre-shutdown action"); + Preconditions.checkState(shutdownActionConfirmationFile.exists()); + Files.write(shutdownActionConfirmationFile.toPath(), + PRE_SHUTDOWN_ACTION_EXECUTED.getBytes()); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + mapReference.set(mapBuilder.createPersistedTo(mapFile)); + try (ExternalMapQueryContext c = mapReference.get().queryContext(KEY)) { + c.writeLock().lock(); + Thread.sleep(30_000); + } + } catch (Exception e) { + e.printStackTrace(); } } @@ -94,10 +131,10 @@ public class ExitHookTest { File mapFile = folder.newFile(); File preShutdownActionExecutionConfirmationFile = folder.newFile(); // Create a process which opens the map, acquires the lock and "hangs" for 30 seconds - Process process = startOtherProcess(mapFile, preShutdownActionExecutionConfirmationFile); + Process process = startOtherProcess(mapFile, preShutdownActionExecutionConfirmationFile, false); // Let the other process actually reach the moment when it locks the map // (JVM startup and chronicle map creation are not instant) - Thread.sleep(10_000); + Thread.sleep(JVM_STARTUP_WAIT_TIME_MS); // Interrupt that process to trigger Chronicle Map's shutdown hooks interruptProcess(getPidOfProcess(process)); process.waitFor(); @@ -107,7 +144,7 @@ public class ExitHookTest { ChronicleMap map = createMapBuilder().createPersistedTo(mapFile); try (ExternalMapQueryContext c = map.queryContext(KEY)) { // Test that we are able to lock the segment, i. e. the lock was released in other - // process thanks to shutdown hooks + // process, thanks to default shutdown hook. c.writeLock().lock(); } try (Stream lines = Files.lines(preShutdownActionExecutionConfirmationFile.toPath())) { @@ -119,13 +156,45 @@ public class ExitHookTest { } } + @Test + public void testSkipExitHook() throws IOException, InterruptedException { + if (!OS.isLinux() && !OS.isMacOSX()) + return; // This test runs only in Unix-like OSes + File mapFile = folder.newFile(); + File shutdownActionConfirmationFile = folder.newFile(); + // Create a process which opens the map, acquires the lock and "hangs" for 30 seconds + Process process = startOtherProcess(mapFile, shutdownActionConfirmationFile, true); + // Let the other process actually reach the moment when it locks the map + // (JVM startup and chronicle map creation are not instant) + Thread.sleep(JVM_STARTUP_WAIT_TIME_MS); + // Interrupt that process to trigger Chronicle Map's shutdown hooks + interruptProcess(getPidOfProcess(process)); + process.waitFor(); + int actual = process.exitValue(); + if (actual != 0) // clean shutdown + assertEquals(130, actual); // 130 is exit code for SIGINT (interruption). + ChronicleMap map = createMapBuilder().createPersistedTo(mapFile); + try (ExternalMapQueryContext c = map.queryContext(KEY)) { + // Test that we are able to lock the segment, i. e. the lock was released in other + // process, thanks to user shutdown hook. + c.writeLock().lock(); + } + try (Stream lines = Files.lines(shutdownActionConfirmationFile.toPath())) { + Iterator lineIterator = lines.iterator(); + assertTrue(lineIterator.hasNext()); + String line = lineIterator.next(); + assertEquals(USER_SHUTDOWN_HOOK_EXECUTED, line); + assertFalse(lineIterator.hasNext()); + } + } + // http://stackoverflow.com/a/7835467/648955 private void interruptProcess(long pidOfProcess) throws IOException { Runtime.getRuntime().exec("kill -SIGINT " + pidOfProcess); } // http://stackoverflow.com/a/723914/648955 - private Process startOtherProcess(File mapFile, File outputFile) throws IOException { + private Process startOtherProcess(File mapFile, File outputFile, boolean skipCloseOnExitHook) throws IOException { String javaHome = System.getProperty("java.home"); String javaBin = javaHome + File.separator + "bin" + @@ -135,7 +204,7 @@ public class ExitHookTest { String className = ExitHookTest.class.getCanonicalName(); String[] command = new String[] {javaBin, "-cp", classpath, className, - mapFile.getAbsolutePath(), outputFile.getAbsolutePath()}; + mapFile.getAbsolutePath(), outputFile.getAbsolutePath(), String.valueOf(skipCloseOnExitHook)}; ProcessBuilder builder = new ProcessBuilder(command); builder.inheritIO(); return builder.start();