mirror of
https://github.com/OpenHFT/Chronicle-Map.git
synced 2021-09-13 01:26:12 +00:00
Merge pull request #184 from udaysagar2177/master
be able to skip automatic closing of a hash resource
This commit is contained in:
@@ -762,6 +762,17 @@ public interface ChronicleHashBuilder<K, H extends ChronicleHash<K, ?, ?, ?>,
|
||||
*/
|
||||
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
|
||||
*/
|
||||
|
||||
@@ -97,4 +97,9 @@ public interface ChronicleHashBuilderPrivateAPI<K, RO> {
|
||||
* @return registered pre-shutdown action.
|
||||
*/
|
||||
Runnable getPreShutdownAction();
|
||||
|
||||
/**
|
||||
* Whether to skip closing a {@link ChronicleHash} using the default shutdown hook.
|
||||
*/
|
||||
boolean skipCloseOnExitHook();
|
||||
}
|
||||
|
||||
@@ -126,6 +126,7 @@ public abstract class VanillaChronicleHash<K,
|
||||
public transient Identity identity;
|
||||
protected int log2TiersInBulk;
|
||||
private Runnable preShutdownAction;
|
||||
private boolean skipCloseOnExitHook;
|
||||
/////////////////////////////////////////////////
|
||||
// Bytes Store (essentially, the base address) and serialization-dependent offsets
|
||||
protected transient BytesStore bs;
|
||||
@@ -211,6 +212,7 @@ public abstract class VanillaChronicleHash<K,
|
||||
checksumEntries = privateAPI.checksumEntries();
|
||||
|
||||
preShutdownAction = privateAPI.getPreShutdownAction();
|
||||
skipCloseOnExitHook = privateAPI.skipCloseOnExitHook();
|
||||
}
|
||||
|
||||
public static IOException throwRecoveryOrReturnIOException(
|
||||
@@ -476,7 +478,9 @@ public abstract class VanillaChronicleHash<K,
|
||||
}
|
||||
|
||||
public void addToOnExitHook() {
|
||||
ChronicleHashCloseOnExitHook.add(this);
|
||||
if (!skipCloseOnExitHook) {
|
||||
ChronicleHashCloseOnExitHook.add(this);
|
||||
}
|
||||
}
|
||||
|
||||
public final void createMappedStoreAndSegments(ChronicleHashResources resources)
|
||||
@@ -664,7 +668,9 @@ public abstract class VanillaChronicleHash<K,
|
||||
// Releases nothing after resources.releaseManually(), only removes the cleaner
|
||||
// from the internal linked list of all cleaners.
|
||||
cleaner.clean();
|
||||
ChronicleHashCloseOnExitHook.remove(this);
|
||||
if (!skipCloseOnExitHook) {
|
||||
ChronicleHashCloseOnExitHook.remove(this);
|
||||
}
|
||||
// Make GC life easier
|
||||
keyReader = null;
|
||||
keyDataAccess = null;
|
||||
|
||||
@@ -181,6 +181,7 @@ public final class ChronicleMapBuilder<K, V> implements
|
||||
MapEntryOperations<K, V, ?> entryOperations = mapEntryOperations();
|
||||
MapRemoteOperations<K, V, ?> remoteOperations = mapRemoteOperations();
|
||||
Runnable preShutdownAction;
|
||||
boolean skipCloseOnExitHook = false;
|
||||
private String name;
|
||||
// not final because of cloning
|
||||
private ChronicleMapBuilderPrivateAPI<K, V> privateAPI =
|
||||
@@ -1607,6 +1608,12 @@ public final class ChronicleMapBuilder<K, V> implements
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChronicleMapBuilder<K, V> skipCloseOnExitHook(boolean skipCloseOnExitHook) {
|
||||
this.skipCloseOnExitHook = skipCloseOnExitHook;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChronicleMap<K, V> create() {
|
||||
// clone() to make this builder instance thread-safe, because createWithoutFile() method
|
||||
|
||||
@@ -120,4 +120,9 @@ class ChronicleMapBuilderPrivateAPI<K, V>
|
||||
public Runnable getPreShutdownAction() {
|
||||
return b.preShutdownAction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean skipCloseOnExitHook() {
|
||||
return b.skipCloseOnExitHook;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,6 +358,12 @@ public final class ChronicleSetBuilder<K>
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChronicleSetBuilder<K> skipCloseOnExitHook(boolean skipCloseOnExitHook) {
|
||||
chronicleMapBuilder.skipCloseOnExitHook(skipCloseOnExitHook);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated don't use private API in the client code
|
||||
*/
|
||||
|
||||
@@ -138,4 +138,9 @@ class ChronicleSetBuilderPrivateAPI<K>
|
||||
public Runnable getPreShutdownAction() {
|
||||
return mapB.getPreShutdownAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean skipCloseOnExitHook() {
|
||||
return mapB.skipCloseOnExitHook();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Integer, Integer> 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<Integer, Integer> 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<Integer, Integer> map = mapBuilder.createPersistedTo(mapFile);
|
||||
try (ExternalMapQueryContext<Integer, Integer, ?> 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<ChronicleMap<Integer, Integer>> 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<Integer, Integer, ?> 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<Integer, Integer> map = createMapBuilder().createPersistedTo(mapFile);
|
||||
try (ExternalMapQueryContext<Integer, Integer, ?> 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<String> 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<Integer, Integer> map = createMapBuilder().createPersistedTo(mapFile);
|
||||
try (ExternalMapQueryContext<Integer, Integer, ?> 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<String> lines = Files.lines(shutdownActionConfirmationFile.toPath())) {
|
||||
Iterator<String> 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();
|
||||
|
||||
Reference in New Issue
Block a user