Merge branch cassandra-2.2 into cassandra-3.0

This commit is contained in:
Benjamin Lerer 2017-12-12 10:30:29 +01:00
commit dd187d105b
9 changed files with 650 additions and 560 deletions

View File

@ -17,6 +17,7 @@
* Mishandling of cells for removed/dropped columns when reading legacy files (CASSANDRA-13939) * Mishandling of cells for removed/dropped columns when reading legacy files (CASSANDRA-13939)
* Deserialise sstable metadata in nodetool verify (CASSANDRA-13922) * Deserialise sstable metadata in nodetool verify (CASSANDRA-13922)
Merged from 2.2: Merged from 2.2:
* Rely on the JVM to handle OutOfMemoryErrors (CASSANDRA-13006)
* Grab refs during scrub/index redistribution/cleanup (CASSANDRA-13873) * Grab refs during scrub/index redistribution/cleanup (CASSANDRA-13873)

View File

@ -18,8 +18,13 @@ using the provided 'sstableupgrade' tool.
Upgrading Upgrading
--------- ---------
- Nothing specific to this release, but please see previous upgrading sections, - Cassandra is now relying on the JVM options to properly shutdown on OutOfMemoryError. By default it will
especially if you are upgrading from 2.2. rely on the OnOutOfMemoryError option as the ExitOnOutOfMemoryError and CrashOnOutOfMemoryError options
are not supported by the older 1.7 and 1.8 JVMs. A warning will be logged at startup if none of those JVM
options are used. See CASSANDRA-13006 for more details.
- Cassandra is not logging anymore by default an Heap histogram on OutOfMemoryError. To enable that behavior
set the 'cassandra.printHeapHistogramOnOutOfMemoryError' System property to 'true'. See CASSANDRA-13006
for more details.
Materialized Views Materialized Views
------------------- -------------------

View File

@ -28,6 +28,7 @@
# #
# CLASSPATH -- A Java classpath containing everything necessary to run. # CLASSPATH -- A Java classpath containing everything necessary to run.
# JVM_OPTS -- Additional arguments to the JVM for heap size, etc # JVM_OPTS -- Additional arguments to the JVM for heap size, etc
# JVM_ON_OUT_OF_MEMORY_ERROR_OPT -- The OnOutOfMemoryError JVM option if specified
# CASSANDRA_CONF -- Directory containing Cassandra configuration files. # CASSANDRA_CONF -- Directory containing Cassandra configuration files.
# #
# As a convenience, a fragment of shell is sourced in order to set one or # As a convenience, a fragment of shell is sourced in order to set one or
@ -199,12 +200,22 @@ launch_service()
# to close stdout/stderr, but it's up to us not to background. # to close stdout/stderr, but it's up to us not to background.
if [ "x$foreground" != "x" ]; then if [ "x$foreground" != "x" ]; then
cassandra_parms="$cassandra_parms -Dcassandra-foreground=yes" cassandra_parms="$cassandra_parms -Dcassandra-foreground=yes"
exec $NUMACTL "$JAVA" $JVM_OPTS $cassandra_parms -cp "$CLASSPATH" $props "$class" if [ "x$JVM_ON_OUT_OF_MEMORY_ERROR_OPT" != "x" ]; then
exec $NUMACTL "$JAVA" $JVM_OPTS "$JVM_ON_OUT_OF_MEMORY_ERROR_OPT" $cassandra_parms -cp "$CLASSPATH" $props "$class"
else
exec $NUMACTL "$JAVA" $JVM_OPTS $cassandra_parms -cp "$CLASSPATH" $props "$class"
fi
# Startup CassandraDaemon, background it, and write the pid. # Startup CassandraDaemon, background it, and write the pid.
else else
exec $NUMACTL "$JAVA" $JVM_OPTS $cassandra_parms -cp "$CLASSPATH" $props "$class" <&- & if [ "x$JVM_ON_OUT_OF_MEMORY_ERROR_OPT" != "x" ]; then
[ ! -z "$pidpath" ] && printf "%d" $! > "$pidpath" exec $NUMACTL "$JAVA" $JVM_OPTS "$JVM_ON_OUT_OF_MEMORY_ERROR_OPT" $cassandra_parms -cp "$CLASSPATH" $props "$class" <&- &
true [ ! -z "$pidpath" ] && printf "%d" $! > "$pidpath"
true
else
exec $NUMACTL "$JAVA" $JVM_OPTS $cassandra_parms -cp "$CLASSPATH" $props "$class" <&- &
[ ! -z "$pidpath" ] && printf "%d" $! > "$pidpath"
true
fi
fi fi
return $? return $?

View File

@ -420,6 +420,16 @@ Function SetCassandraEnvironment
$env:JVM_OPTS="$env:JVM_OPTS -XX:+HeapDumpOnOutOfMemoryError" $env:JVM_OPTS="$env:JVM_OPTS -XX:+HeapDumpOnOutOfMemoryError"
# stop the jvm on OutOfMemoryError as it can result in some data corruption
# uncomment the preferred option
# ExitOnOutOfMemoryError and CrashOnOutOfMemoryError require a JRE greater or equals to 1.7 update 101 or 1.8 update 92
# $env:JVM_OPTS="$env:JVM_OPTS -XX:+ExitOnOutOfMemoryError"
# $env:JVM_OPTS="$env:JVM_OPTS -XX:+CrashOnOutOfMemoryError"
$env:JVM_OPTS="$env:JVM_OPTS -XX:OnOutOfMemoryError=""taskkill /F /PID %p"""
# print an heap histogram on OutOfMemoryError
# $env:JVM_OPTS="$env:JVM_OPTS -Dcassandra.printHeapHistogramOnOutOfMemoryError=true"
# Per-thread stack size. # Per-thread stack size.
$env:JVM_OPTS="$env:JVM_OPTS -Xss256k" $env:JVM_OPTS="$env:JVM_OPTS -Xss256k"

View File

@ -247,6 +247,18 @@ if [ "x$CASSANDRA_HEAPDUMP_DIR" != "x" ]; then
JVM_OPTS="$JVM_OPTS -XX:HeapDumpPath=$CASSANDRA_HEAPDUMP_DIR/cassandra-`date +%s`-pid$$.hprof" JVM_OPTS="$JVM_OPTS -XX:HeapDumpPath=$CASSANDRA_HEAPDUMP_DIR/cassandra-`date +%s`-pid$$.hprof"
fi fi
# stop the jvm on OutOfMemoryError as it can result in some data corruption
# uncomment the preferred option
# ExitOnOutOfMemoryError and CrashOnOutOfMemoryError require a JRE greater or equals to 1.7 update 101 or 1.8 update 92
# For OnOutOfMemoryError we cannot use the JVM_OPTS variables because bash commands split words
# on white spaces without taking quotes into account
# JVM_OPTS="$JVM_OPTS -XX:+ExitOnOutOfMemoryError"
# JVM_OPTS="$JVM_OPTS -XX:+CrashOnOutOfMemoryError"
JVM_ON_OUT_OF_MEMORY_ERROR_OPT="-XX:OnOutOfMemoryError=kill -9 %p"
# print an heap histogram on OutOfMemoryError
# JVM_OPTS="$JVM_OPTS -Dcassandra.printHeapHistogramOnOutOfMemoryError=true"
# uncomment to have Cassandra JVM listen for remote debuggers/profilers on port 1414 # uncomment to have Cassandra JVM listen for remote debuggers/profilers on port 1414
# JVM_OPTS="$JVM_OPTS -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=1414" # JVM_OPTS="$JVM_OPTS -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=1414"

View File

@ -20,6 +20,8 @@ package org.apache.cassandra.service;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.nio.file.*; import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.util.*; import java.util.*;
@ -189,6 +191,78 @@ public class StartupChecks
{ {
logger.warn("Non-Oracle JVM detected. Some features, such as immediate unmap of compacted SSTables, may not work as intended"); logger.warn("Non-Oracle JVM detected. Some features, such as immediate unmap of compacted SSTables, may not work as intended");
} }
else
{
checkOutOfMemoryHandling();
}
}
/**
* Checks that the JVM is configured to handle OutOfMemoryError
*/
private void checkOutOfMemoryHandling()
{
int version = getJavaVersion();
int update = getUpdate();
// The ExitOnOutOfMemory and CrashOnOutOfMemory are supported since the version 7u101 and 8u92
boolean jreSupportExitOnOutOfMemory = version > 8
|| (version == 7 && update >= 101)
|| (version == 8 && update >= 92);
if (jreSupportExitOnOutOfMemory)
{
if (!jvmOptionsContainsOneOf("-XX:OnOutOfMemoryError=", "-XX:+ExitOnOutOfMemoryError", "-XX:+CrashOnOutOfMemoryError"))
logger.warn("The JVM is not configured to stop on OutOfMemoryError which can cause data corruption."
+ " Use one of the following JVM options to configure the behavior on OutOfMemoryError: "
+ " -XX:+ExitOnOutOfMemoryError, -XX:+CrashOnOutOfMemoryError, or -XX:OnOutOfMemoryError=\"<cmd args>;<cmd args>\"");
}
else
{
if (!jvmOptionsContainsOneOf("-XX:OnOutOfMemoryError="))
logger.warn("The JVM is not configured to stop on OutOfMemoryError which can cause data corruption."
+ " Either upgrade your JRE to a version greater or equal to 8u92 and use -XX:+ExitOnOutOfMemoryError/-XX:+CrashOnOutOfMemoryError"
+ " or use -XX:OnOutOfMemoryError=\"<cmd args>;<cmd args>\" on your current JRE.");
}
}
/**
* Returns the java version number for an Oracle JVM.
* @return the java version number
*/
private int getJavaVersion()
{
String jreVersion = System.getProperty("java.version");
String version = jreVersion.startsWith("1.") ? jreVersion.substring(2, 3) // Pre 9 version
: jreVersion.substring(0, jreVersion.indexOf('.'));
return Integer.parseInt(version);
}
/**
* Return the update number for an Oracle JVM.
* @return the update number
*/
private int getUpdate()
{
String jreVersion = System.getProperty("java.version");
int updateSeparatorIndex = jreVersion.indexOf('_');
return Integer.parseInt(jreVersion.substring(updateSeparatorIndex + 1));
}
/**
* Checks if one of the specified options is being used.
* @param optionNames The name of the options to check
* @return {@code true} if one of the specified options is being used, {@code false} otherwise.
*/
private boolean jvmOptionsContainsOneOf(String... optionNames)
{
RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
List<String> inputArguments = runtimeMxBean.getInputArguments();
for (String argument : inputArguments)
{
for (String optionName : optionNames)
if (argument.startsWith(optionName))
return true;
}
return false;
} }
}; };

View File

@ -19,11 +19,6 @@ package org.apache.cassandra.utils;
import java.io.*; import java.io.*;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.text.StrBuilder; import org.apache.commons.lang3.text.StrBuilder;
@ -32,7 +27,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
* Utility to generate heap dumps. * Utility to log heap histogram.
* *
*/ */
public final class HeapUtils public final class HeapUtils
@ -40,65 +35,43 @@ public final class HeapUtils
private static final Logger logger = LoggerFactory.getLogger(HeapUtils.class); private static final Logger logger = LoggerFactory.getLogger(HeapUtils.class);
/** /**
* Generates a HEAP dump in the directory specified by the <code>HeapDumpPath</code> JVM option * Generates a HEAP histogram in the log file.
* or in the <code>CASSANDRA_HOME</code> directory.
*/ */
public static void generateHeapDump() public static void logHeapHistogram()
{ {
Long processId = getProcessId(); try
if (processId == null)
{ {
logger.error("The process ID could not be retrieved. Skipping heap dump generation."); logger.info("Trying to log the heap histogram using jcmd");
return;
}
String heapDumpPath = getHeapDumpPathOption(); Long processId = getProcessId();
if (heapDumpPath == null) if (processId == null)
{
String cassandraHome = System.getenv("CASSANDRA_HOME");
if (cassandraHome == null)
{ {
logger.error("The process ID could not be retrieved. Skipping heap histogram generation.");
return; return;
} }
heapDumpPath = cassandraHome; String jcmdPath = getJcmdPath();
}
Path dumpPath = FileSystems.getDefault().getPath(heapDumpPath); // The jcmd file could not be found. In this case let's default to jcmd in the hope that it is in the path.
if (Files.isDirectory(dumpPath)) String jcmdCommand = jcmdPath == null ? "jcmd" : jcmdPath;
{
dumpPath = dumpPath.resolve("java_pid" + processId + ".hprof");
}
String jmapPath = getJmapPath(); String[] histoCommands = new String[] {jcmdCommand,
processId.toString(),
"GC.class_histogram"};
// The jmap file could not be found. In this case let's default to jmap in the hope that it is in the path.
String jmapCommand = jmapPath == null ? "jmap" : jmapPath;
String[] dumpCommands = new String[] {jmapCommand,
"-dump:format=b,file=" + dumpPath,
processId.toString()};
// Lets also log the Heap histogram
String[] histoCommands = new String[] {jmapCommand,
"-histo",
processId.toString()};
try
{
logProcessOutput(Runtime.getRuntime().exec(dumpCommands));
logProcessOutput(Runtime.getRuntime().exec(histoCommands)); logProcessOutput(Runtime.getRuntime().exec(histoCommands));
} }
catch (IOException e) catch (Throwable e)
{ {
logger.error("The heap dump could not be generated due to the following error: ", e); logger.error("The heap histogram could not be generated due to the following error: ", e);
} }
} }
/** /**
* Retrieve the path to the JMAP executable. * Retrieve the path to the JCMD executable.
* @return the path to the JMAP executable or null if it cannot be found. * @return the path to the JCMD executable or null if it cannot be found.
*/ */
private static String getJmapPath() private static String getJcmdPath()
{ {
// Searching in the JAVA_HOME is safer than searching into System.getProperty("java.home") as the Oracle // Searching in the JAVA_HOME is safer than searching into System.getProperty("java.home") as the Oracle
// JVM might use the JRE which do not contains jmap. // JVM might use the JRE which do not contains jmap.
@ -111,7 +84,7 @@ public final class HeapUtils
{ {
public boolean accept(File dir, String name) public boolean accept(File dir, String name)
{ {
return name.startsWith("jmap"); return name.startsWith("jcmd");
} }
}); });
return ArrayUtils.isEmpty(files) ? null : files[0].getPath(); return ArrayUtils.isEmpty(files) ? null : files[0].getPath();
@ -136,32 +109,6 @@ public final class HeapUtils
logger.info(builder.toString()); logger.info(builder.toString());
} }
/**
* Retrieves the value of the <code>HeapDumpPath</code> JVM option.
* @return the value of the <code>HeapDumpPath</code> JVM option or <code>null</code> if the value has not been
* specified.
*/
private static String getHeapDumpPathOption()
{
RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
List<String> inputArguments = runtimeMxBean.getInputArguments();
String heapDumpPathOption = null;
for (String argument : inputArguments)
{
if (argument.startsWith("-XX:HeapDumpPath="))
{
heapDumpPathOption = argument;
// We do not break in case the option has been specified several times.
// In general it seems that JVMs use the right-most argument as the winner.
}
}
if (heapDumpPathOption == null)
return null;
return heapDumpPathOption.substring(17, heapDumpPathOption.length());
}
/** /**
* Retrieves the process ID or <code>null</code> if the process ID cannot be retrieved. * Retrieves the process ID or <code>null</code> if the process ID cannot be retrieved.
* @return the process ID or <code>null</code> if the process ID cannot be retrieved. * @return the process ID or <code>null</code> if the process ID cannot be retrieved.

View File

@ -23,6 +23,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -41,6 +42,8 @@ public final class JVMStabilityInspector
private static final Logger logger = LoggerFactory.getLogger(JVMStabilityInspector.class); private static final Logger logger = LoggerFactory.getLogger(JVMStabilityInspector.class);
private static Killer killer = new Killer(); private static Killer killer = new Killer();
private static Object lock = new Object();
private static boolean printingHeapHistogram;
private JVMStabilityInspector() {} private JVMStabilityInspector() {}
@ -55,8 +58,25 @@ public final class JVMStabilityInspector
boolean isUnstable = false; boolean isUnstable = false;
if (t instanceof OutOfMemoryError) if (t instanceof OutOfMemoryError)
{ {
isUnstable = true; if (Boolean.getBoolean("cassandra.printHeapHistogramOnOutOfMemoryError"))
HeapUtils.generateHeapDump(); {
// We want to avoid printing multiple time the heap histogram if multiple OOM errors happen in a short
// time span.
synchronized(lock)
{
if (printingHeapHistogram)
return;
printingHeapHistogram = true;
}
HeapUtils.logHeapHistogram();
}
logger.error("OutOfMemory error letting the JVM handle the error:", t);
StorageService.instance.removeShutdownHook();
// We let the JVM handle the error. The startup checks should have warned the user if it did not configure
// the JVM behavior in case of OOM (CASSANDRA-13006).
throw (OutOfMemoryError) t;
} }
if (DatabaseDescriptor.getDiskFailurePolicy() == Config.DiskFailurePolicy.die) if (DatabaseDescriptor.getDiskFailurePolicy() == Config.DiskFailurePolicy.die)

View File

@ -27,8 +27,10 @@ import org.apache.cassandra.config.Config;
import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.io.FSReadError; import org.apache.cassandra.io.FSReadError;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class JVMStabilityInspectorTest public class JVMStabilityInspectorTest
{ {
@ -46,10 +48,6 @@ public class JVMStabilityInspectorTest
JVMStabilityInspector.inspectThrowable(new IOException()); JVMStabilityInspector.inspectThrowable(new IOException());
assertFalse(killerForTests.wasKilled()); assertFalse(killerForTests.wasKilled());
killerForTests.reset();
JVMStabilityInspector.inspectThrowable(new OutOfMemoryError());
assertTrue(killerForTests.wasKilled());
DatabaseDescriptor.setDiskFailurePolicy(Config.DiskFailurePolicy.die); DatabaseDescriptor.setDiskFailurePolicy(Config.DiskFailurePolicy.die);
killerForTests.reset(); killerForTests.reset();
JVMStabilityInspector.inspectThrowable(new FSReadError(new IOException(), "blah")); JVMStabilityInspector.inspectThrowable(new FSReadError(new IOException(), "blah"));
@ -63,11 +61,6 @@ public class JVMStabilityInspectorTest
killerForTests.reset(); killerForTests.reset();
JVMStabilityInspector.inspectThrowable(new Exception(new IOException())); JVMStabilityInspector.inspectThrowable(new Exception(new IOException()));
assertFalse(killerForTests.wasKilled()); assertFalse(killerForTests.wasKilled());
killerForTests.reset();
JVMStabilityInspector.inspectThrowable(new Exception(new OutOfMemoryError()));
assertTrue(killerForTests.wasKilled());
} }
finally finally
{ {
@ -77,6 +70,23 @@ public class JVMStabilityInspectorTest
} }
} }
@Test
public void testOutOfMemoryHandling()
{
for (Throwable oom : asList(new OutOfMemoryError(), new Exception(new OutOfMemoryError())))
{
try
{
JVMStabilityInspector.inspectThrowable(oom);
fail("The JVMStabilityInspector should delegate the handling of OutOfMemoryErrors to the JVM");
}
catch (OutOfMemoryError e)
{
assertTrue(true);
}
}
}
@Test @Test
public void fileHandleTest() public void fileHandleTest()
{ {