/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.lucene.index; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.lang.ProcessBuilder.Redirect; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.lucene.store.BaseDirectoryWrapper; import org.apache.lucene.util.Constants; import org.apache.lucene.util.SuppressForbidden; import org.apache.lucene.util.TestUtil; import com.carrotsearch.randomizedtesting.SeedUtils; /** * Runs TestNRTThreads in a separate process, crashes the JRE in the middle * of execution, then runs checkindex to make sure it's not corrupt. */ public class TestIndexWriterOnJRECrash extends TestNRTThreads { private Path tempDir; @Override public void setUp() throws Exception { super.setUp(); assumeFalse("This test fails on UNIX with Turkish default locale (https://issues.apache.org/jira/browse/LUCENE-6036)", new Locale("tr").getLanguage().equals(Locale.getDefault().getLanguage())); tempDir = createTempDir("jrecrash"); } @Override @Nightly public void testNRTThreads() throws Exception { // if we are not the fork if (System.getProperty("tests.crashmode") == null) { // try up to 10 times to create an index for (int i = 0; i < 10; i++) { forkTest(); // if we succeeded in finding an index, we are done. if (checkIndexes(tempDir)) return; } } else { // note: re-enable this if we create a 4.x impersonator, // and if its format is actually different than the real 4.x (unlikely) // TODO: the non-fork code could simply enable impersonation? // assumeFalse("does not support PreFlex, see LUCENE-3992", // Codec.getDefault().getName().equals("Lucene4x")); // we are the fork, setup a crashing thread final int crashTime = TestUtil.nextInt(random(), 3000, 4000); Thread t = new Thread() { @Override public void run() { try { Thread.sleep(crashTime); } catch (InterruptedException e) {} crashJRE(); } }; t.setPriority(Thread.MAX_PRIORITY); t.start(); // run the test until we crash. for (int i = 0; i < 1000; i++) { super.testNRTThreads(); } } } /** fork ourselves in a new jvm. sets -Dtests.crashmode=true */ @SuppressForbidden(reason = "ProcessBuilder requires java.io.File for CWD") public void forkTest() throws Exception { List<String> cmd = new ArrayList<>(); cmd.add(Paths.get(System.getProperty("java.home"), "bin", "java").toString()); cmd.add("-Xmx512m"); cmd.add("-Dtests.crashmode=true"); // passing NIGHTLY to this test makes it run for much longer, easier to catch it in the act... cmd.add("-Dtests.nightly=true"); cmd.add("-DtempDir=" + tempDir); cmd.add("-Dtests.seed=" + SeedUtils.formatSeed(random().nextLong())); cmd.add("-ea"); cmd.add("-cp"); cmd.add(System.getProperty("java.class.path")); cmd.add("org.junit.runner.JUnitCore"); cmd.add(getClass().getName()); ProcessBuilder pb = new ProcessBuilder(cmd) .directory(tempDir.toFile()) .redirectInput(Redirect.INHERIT) .redirectErrorStream(true); Process p = pb.start(); // We pump everything to stderr. PrintStream childOut = System.err; Thread stdoutPumper = ThreadPumper.start(p.getInputStream(), childOut); if (VERBOSE) childOut.println(">>> Begin subprocess output"); p.waitFor(); stdoutPumper.join(); if (VERBOSE) childOut.println("<<< End subprocess output"); } /** A pipe thread. It'd be nice to reuse guava's implementation for this... */ static class ThreadPumper { public static Thread start(final InputStream from, final OutputStream to) { Thread t = new Thread() { @Override public void run() { try { byte[] buffer = new byte [1024]; int len; while ((len = from.read(buffer)) != -1) { if (VERBOSE) { to.write(buffer, 0, len); } } } catch (IOException e) { System.err.println("Couldn't pipe from the forked process: " + e.toString()); } } }; t.start(); return t; } } /** * Recursively looks for indexes underneath <code>file</code>, * and runs checkindex on them. returns true if it found any indexes. */ public boolean checkIndexes(Path path) throws IOException { final AtomicBoolean found = new AtomicBoolean(); Files.walkFileTree(path, new SimpleFileVisitor<Path>() { @Override public FileVisitResult postVisitDirectory(Path dirPath, IOException exc) throws IOException { if (exc != null) { throw exc; } else { try (BaseDirectoryWrapper dir = newFSDirectory(dirPath)) { dir.setCheckIndexOnClose(false); // don't double-checkindex if (DirectoryReader.indexExists(dir)) { if (VERBOSE) { System.err.println("Checking index: " + dirPath); } // LUCENE-4738: if we crashed while writing first // commit it's possible index will be corrupt (by // design we don't try to be smart about this case // since that too risky): if (SegmentInfos.getLastCommitGeneration(dir) > 1) { TestUtil.checkIndex(dir); } found.set(true); } } return FileVisitResult.CONTINUE; } } }); return found.get(); } /** * currently, this only works/tested on Sun and IBM. */ @SuppressForbidden(reason = "We need Unsafe to actually crush :-)") public void crashJRE() { final String vendor = Constants.JAVA_VENDOR; final boolean supportsUnsafeNpeDereference = vendor.startsWith("Oracle") || vendor.startsWith("Sun") || vendor.startsWith("Apple"); try { if (supportsUnsafeNpeDereference) { try { Class<?> clazz = Class.forName("sun.misc.Unsafe"); Field field = clazz.getDeclaredField("theUnsafe"); field.setAccessible(true); Object o = field.get(null); Method m = clazz.getMethod("putAddress", long.class, long.class); m.invoke(o, 0L, 0L); } catch (Throwable e) { System.out.println("Couldn't kill the JVM via Unsafe."); e.printStackTrace(System.out); } } // Fallback attempt to Runtime.halt(); Runtime.getRuntime().halt(-1); } catch (Exception e) { System.out.println("Couldn't kill the JVM."); e.printStackTrace(System.out); } // We couldn't get the JVM to crash for some reason. fail(); } }