mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-02-17 10:25:15 +00:00
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.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.function.Predicate;
|
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
|
* 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 UseSerialGCCheck());
|
||||||
checks.add(new OnErrorCheck());
|
checks.add(new OnErrorCheck());
|
||||||
checks.add(new OnOutOfMemoryErrorCheck());
|
checks.add(new OnOutOfMemoryErrorCheck());
|
||||||
|
checks.add(new G1GCCheck());
|
||||||
return Collections.unmodifiableList(checks);
|
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 */
|
/** Checks that the JVM is ok and won't cause index corruption */
|
||||||
final class JVMCheck {
|
final class JVMCheck {
|
||||||
|
|
||||||
/** no instantiation */
|
/** no instantiation */
|
||||||
private JVMCheck() {}
|
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)
|
* 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";
|
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.
|
* Checks that the current JVM is "ok". This means it doesn't have severe bugs that cause data corruption.
|
||||||
*/
|
*/
|
||||||
static void check() {
|
static void check() {
|
||||||
if (Boolean.parseBoolean(System.getProperty(JVM_BYPASS))) {
|
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());
|
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)) {
|
} else if ("IBM Corporation".equals(Constants.JVM_VENDOR)) {
|
||||||
// currently some old JVM versions from IBM will easily result in index corruption.
|
// currently some old JVM versions from IBM will easily result in index corruption.
|
||||||
// 2.8+ seems ok for ES from testing.
|
// 2.8+ seems ok for ES from testing.
|
||||||
@ -237,4 +84,5 @@ final class JVMCheck {
|
|||||||
sb.append("]");
|
sb.append("]");
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
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.containsString;
|
||||||
import static org.hamcrest.CoreMatchers.equalTo;
|
import static org.hamcrest.CoreMatchers.equalTo;
|
||||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
import static org.hamcrest.Matchers.hasToString;
|
import static org.hamcrest.Matchers.hasToString;
|
||||||
import static org.hamcrest.Matchers.not;
|
import static org.hamcrest.Matchers.not;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
@ -530,6 +532,60 @@ public class BootstrapCheckTests extends ESTestCase {
|
|||||||
consumer.accept(e);
|
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() {
|
public void testAlwaysEnforcedChecks() {
|
||||||
final BootstrapCheck.Check check = new BootstrapCheck.Check() {
|
final BootstrapCheck.Check check = new BootstrapCheck.Check() {
|
||||||
@Override
|
@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
|
use the JVM flag `ExitOnOutOfMemoryError`. While this does not have the
|
||||||
full capabilities of `OnError` nor `OnOutOfMemoryError`, arbitrary
|
full capabilities of `OnError` nor `OnOutOfMemoryError`, arbitrary
|
||||||
forking will not be supported with seccomp enabled.
|
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…
x
Reference in New Issue
Block a user