Migrate G1GC JVM check to bootstrap check
This commit fixes an assertion in G1GCCheck#jvmVersion that was mistakenly asserting on itself. Relates #21388
This commit is contained in:
parent
f80aa65fe9
commit
b30732c464
|
@ -44,6 +44,8 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* We enforce limits once any network host is configured. In this case we assume the node is running in production
|
||||
|
@ -172,6 +174,7 @@ final class BootstrapCheck {
|
|||
checks.add(new UseSerialGCCheck());
|
||||
checks.add(new OnErrorCheck());
|
||||
checks.add(new OnOutOfMemoryErrorCheck());
|
||||
checks.add(new G1GCCheck());
|
||||
return Collections.unmodifiableList(checks);
|
||||
}
|
||||
|
||||
|
@ -549,4 +552,51 @@ final class BootstrapCheck {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap check for versions of HotSpot that are known to have issues that can lead to index corruption when G1GC is enabled.
|
||||
*/
|
||||
static class G1GCCheck implements BootstrapCheck.Check {
|
||||
|
||||
@Override
|
||||
public boolean check() {
|
||||
if ("Oracle Corporation".equals(jvmVendor()) && isG1GCEnabled()) {
|
||||
final String jvmVersion = jvmVersion();
|
||||
final Pattern pattern = Pattern.compile("(\\d+)\\.(\\d+)-b\\d+");
|
||||
final Matcher matcher = pattern.matcher(jvmVersion);
|
||||
final boolean matches = matcher.matches();
|
||||
assert matches : jvmVersion;
|
||||
final int major = Integer.parseInt(matcher.group(1));
|
||||
final int update = Integer.parseInt(matcher.group(2));
|
||||
return major == 25 && update < 40;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// visible for testing
|
||||
String jvmVendor() {
|
||||
return Constants.JVM_VENDOR;
|
||||
}
|
||||
|
||||
// visible for testing
|
||||
boolean isG1GCEnabled() {
|
||||
assert "Oracle Corporation".equals(jvmVendor());
|
||||
return JvmInfo.jvmInfo().useG1GC().equals("true");
|
||||
}
|
||||
|
||||
// visible for testing
|
||||
String jvmVersion() {
|
||||
assert "Oracle Corporation".equals(jvmVendor());
|
||||
return Constants.JVM_VERSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String errorMessage() {
|
||||
return String.format(
|
||||
Locale.ROOT,
|
||||
"JVM version [%s] can cause data corruption when used with G1GC; upgrade to at least Java 8u40", jvmVersion());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,174 +31,21 @@ import java.util.Optional;
|
|||
|
||||
/** Checks that the JVM is ok and won't cause index corruption */
|
||||
final class JVMCheck {
|
||||
|
||||
/** no instantiation */
|
||||
private JVMCheck() {}
|
||||
|
||||
/**
|
||||
* URL with latest JVM recommendations
|
||||
*/
|
||||
static final String JVM_RECOMMENDATIONS = "http://www.elastic.co/guide/en/elasticsearch/reference/current/_installation.html";
|
||||
|
||||
/**
|
||||
* System property which if set causes us to bypass the check completely (but issues a warning in doing so)
|
||||
*/
|
||||
static final String JVM_BYPASS = "es.bypass.vm.check";
|
||||
|
||||
/**
|
||||
* Metadata and messaging for checking and reporting HotSpot
|
||||
* issues.
|
||||
*/
|
||||
interface HotSpotCheck {
|
||||
/**
|
||||
* If this HotSpot check should be executed.
|
||||
*
|
||||
* @return true if this HotSpot check should be executed
|
||||
*/
|
||||
boolean check();
|
||||
|
||||
/**
|
||||
* The error message to display when this HotSpot issue is
|
||||
* present.
|
||||
*
|
||||
* @return the error message for this HotSpot issue
|
||||
*/
|
||||
String getErrorMessage();
|
||||
|
||||
/**
|
||||
* The warning message for this HotSpot issue if a workaround
|
||||
* exists and is used.
|
||||
*
|
||||
* @return the warning message for this HotSpot issue
|
||||
*/
|
||||
Optional<String> getWarningMessage();
|
||||
|
||||
/**
|
||||
* The workaround for this HotSpot issue, if one exists.
|
||||
*
|
||||
* @return the workaround for this HotSpot issue, if one exists
|
||||
*/
|
||||
Optional<String> getWorkaround();
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata and messaging for hotspot bugs.
|
||||
*/
|
||||
static class HotspotBug implements HotSpotCheck {
|
||||
|
||||
/** OpenJDK bug URL */
|
||||
final String bugUrl;
|
||||
|
||||
/** Compiler workaround flag (null if there is no workaround) */
|
||||
final String workAround;
|
||||
|
||||
HotspotBug(String bugUrl, String workAround) {
|
||||
this.bugUrl = bugUrl;
|
||||
this.workAround = workAround;
|
||||
}
|
||||
|
||||
/** Returns an error message to the user for a broken version */
|
||||
public String getErrorMessage() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Java version: ").append(fullVersion());
|
||||
sb.append(" suffers from critical bug ").append(bugUrl);
|
||||
sb.append(" which can cause data corruption.");
|
||||
sb.append(System.lineSeparator());
|
||||
sb.append("Please upgrade the JVM, see ").append(JVM_RECOMMENDATIONS);
|
||||
sb.append(" for current recommendations.");
|
||||
if (workAround != null) {
|
||||
sb.append(System.lineSeparator());
|
||||
sb.append("If you absolutely cannot upgrade, please add ").append(workAround);
|
||||
sb.append(" to the ES_JAVA_OPTS environment variable.");
|
||||
sb.append(System.lineSeparator());
|
||||
sb.append("Upgrading is preferred, this workaround will result in degraded performance.");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** Warns the user when a workaround is being used to dodge the bug */
|
||||
public Optional<String> getWarningMessage() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Workaround flag ").append(workAround);
|
||||
sb.append(" for bug ").append(bugUrl);
|
||||
sb.append(" found. ");
|
||||
sb.append(System.lineSeparator());
|
||||
sb.append("This will result in degraded performance!");
|
||||
sb.append(System.lineSeparator());
|
||||
sb.append("Upgrading is preferred, see ").append(JVM_RECOMMENDATIONS);
|
||||
sb.append(" for current recommendations.");
|
||||
return Optional.of(sb.toString());
|
||||
}
|
||||
|
||||
public boolean check() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getWorkaround() {
|
||||
return Optional.of(workAround);
|
||||
}
|
||||
}
|
||||
|
||||
static class G1GCCheck implements HotSpotCheck {
|
||||
@Override
|
||||
public boolean check() {
|
||||
return JvmInfo.jvmInfo().useG1GC().equals("true");
|
||||
}
|
||||
|
||||
/** Returns an error message to the user for a broken version */
|
||||
public String getErrorMessage() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Java version: ").append(fullVersion());
|
||||
sb.append(" can cause data corruption");
|
||||
sb.append(" when used with G1GC.");
|
||||
sb.append(System.lineSeparator());
|
||||
sb.append("Please upgrade the JVM, see ").append(JVM_RECOMMENDATIONS);
|
||||
sb.append(" for current recommendations.");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getWarningMessage() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getWorkaround() {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/** mapping of hotspot version to hotspot bug information for the most serious bugs */
|
||||
static final Map<String, HotSpotCheck> JVM_BROKEN_HOTSPOT_VERSIONS;
|
||||
|
||||
static {
|
||||
Map<String, HotSpotCheck> bugs = new HashMap<>();
|
||||
|
||||
G1GCCheck g1GcCheck = new G1GCCheck();
|
||||
bugs.put("25.0-b70", g1GcCheck);
|
||||
bugs.put("25.11-b03", g1GcCheck);
|
||||
bugs.put("25.20-b23", g1GcCheck);
|
||||
bugs.put("25.25-b02", g1GcCheck);
|
||||
bugs.put("25.31-b07", g1GcCheck);
|
||||
|
||||
JVM_BROKEN_HOTSPOT_VERSIONS = Collections.unmodifiableMap(bugs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the current JVM is "ok". This means it doesn't have severe bugs that cause data corruption.
|
||||
*/
|
||||
static void check() {
|
||||
if (Boolean.parseBoolean(System.getProperty(JVM_BYPASS))) {
|
||||
Loggers.getLogger(JVMCheck.class).warn("bypassing jvm version check for version [{}], this can result in data corruption!", fullVersion());
|
||||
} else if ("Oracle Corporation".equals(Constants.JVM_VENDOR)) {
|
||||
HotSpotCheck bug = JVM_BROKEN_HOTSPOT_VERSIONS.get(Constants.JVM_VERSION);
|
||||
if (bug != null && bug.check()) {
|
||||
if (bug.getWorkaround().isPresent() && ManagementFactory.getRuntimeMXBean().getInputArguments().contains(bug.getWorkaround().get())) {
|
||||
Loggers.getLogger(JVMCheck.class).warn("{}", bug.getWarningMessage().get());
|
||||
} else {
|
||||
throw new RuntimeException(bug.getErrorMessage());
|
||||
}
|
||||
}
|
||||
} else if ("IBM Corporation".equals(Constants.JVM_VENDOR)) {
|
||||
// currently some old JVM versions from IBM will easily result in index corruption.
|
||||
// 2.8+ seems ok for ES from testing.
|
||||
|
@ -237,4 +84,5 @@ final class JVMCheck {
|
|||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
@ -42,6 +43,7 @@ import static org.hamcrest.CoreMatchers.allOf;
|
|||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.Matchers.hasToString;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
@ -530,6 +532,60 @@ public class BootstrapCheckTests extends ESTestCase {
|
|||
consumer.accept(e);
|
||||
}
|
||||
|
||||
public void testG1GCCheck() throws NodeValidationException {
|
||||
final AtomicBoolean isG1GCEnabled = new AtomicBoolean(true);
|
||||
final AtomicReference<String> jvmVersion =
|
||||
new AtomicReference<>(String.format(Locale.ROOT, "25.%d-b%d", randomIntBetween(0, 39), randomIntBetween(1, 128)));
|
||||
final BootstrapCheck.G1GCCheck oracleCheck = new BootstrapCheck.G1GCCheck() {
|
||||
|
||||
@Override
|
||||
String jvmVendor() {
|
||||
return "Oracle Corporation";
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isG1GCEnabled() {
|
||||
return isG1GCEnabled.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
String jvmVersion() {
|
||||
return jvmVersion.get();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
final NodeValidationException e =
|
||||
expectThrows(
|
||||
NodeValidationException.class,
|
||||
() -> BootstrapCheck.check(true, Collections.singletonList(oracleCheck), "testG1GCCheck"));
|
||||
assertThat(
|
||||
e.getMessage(),
|
||||
containsString(
|
||||
"JVM version [" + jvmVersion.get() + "] can cause data corruption when used with G1GC; upgrade to at least Java 8u40"));
|
||||
|
||||
// if G1GC is disabled, nothing should happen
|
||||
isG1GCEnabled.set(false);
|
||||
BootstrapCheck.check(true, Collections.singletonList(oracleCheck), "testG1GCCheck");
|
||||
|
||||
// if on or after update 40, nothing should happen independent of whether or not G1GC is enabled
|
||||
isG1GCEnabled.set(randomBoolean());
|
||||
jvmVersion.set(String.format(Locale.ROOT, "25.%d-b%d", randomIntBetween(40, 112), randomIntBetween(1, 128)));
|
||||
BootstrapCheck.check(true, Collections.singletonList(oracleCheck), "testG1GCCheck");
|
||||
|
||||
final BootstrapCheck.G1GCCheck nonOracleCheck = new BootstrapCheck.G1GCCheck() {
|
||||
|
||||
@Override
|
||||
String jvmVendor() {
|
||||
return randomAsciiOfLength(8);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// if not on an Oracle JVM, nothing should happen
|
||||
BootstrapCheck.check(true, Collections.singletonList(nonOracleCheck), "testG1GCCheck");
|
||||
}
|
||||
|
||||
public void testAlwaysEnforcedChecks() {
|
||||
final BootstrapCheck.Check check = new BootstrapCheck.Check() {
|
||||
@Override
|
||||
|
|
|
@ -161,3 +161,11 @@ enabled. This check is always enforced. To pass this check do not enable
|
|||
use the JVM flag `ExitOnOutOfMemoryError`. While this does not have the
|
||||
full capabilities of `OnError` nor `OnOutOfMemoryError`, arbitrary
|
||||
forking will not be supported with seccomp enabled.
|
||||
|
||||
=== G1GC check
|
||||
|
||||
Early versions of the HotSpot JVM that shipped with JDK 8 are known to have
|
||||
issues that can lead to index corruption when the G1GC collector is enabled.
|
||||
The versions impacted are those earlier than the version of HotSpot that
|
||||
shipped with JDK 8u40. The G1GC check detects these early versions of the
|
||||
HotSpot JVM.
|
||||
|
|
Loading…
Reference in New Issue