You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by dw...@apache.org on 2021/03/10 09:48:19 UTC

[lucene] 01/04: LUCENE-8692: updates to TestStressIndexing2 demonstrating bug

This is an automated email from the ASF dual-hosted git repository.

dweiss pushed a commit to branch jira/LUCENE-8692
in repository https://gitbox.apache.org/repos/asf/lucene.git

commit af9da98e87ee2a44532cd9baf2a1bc6e64865b0d
Author: Chris Hostetter <ho...@apache.org>
AuthorDate: Tue Mar 5 16:02:26 2019 -0700

    LUCENE-8692: updates to TestStressIndexing2 demonstrating bug
---
 .../apache/lucene/index/TestStressIndexing2.java   | 162 +++++++++++++++++++--
 1 file changed, 151 insertions(+), 11 deletions(-)

diff --git a/lucene/core/src/test/org/apache/lucene/index/TestStressIndexing2.java b/lucene/core/src/test/org/apache/lucene/index/TestStressIndexing2.java
index 05d8797..ad1c3ee 100644
--- a/lucene/core/src/test/org/apache/lucene/index/TestStressIndexing2.java
+++ b/lucene/core/src/test/org/apache/lucene/index/TestStressIndexing2.java
@@ -16,7 +16,9 @@
  */
 package org.apache.lucene.index;
 
+import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.nio.file.NoSuchFileException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -31,13 +33,16 @@ import org.apache.lucene.analysis.MockAnalyzer;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
 import org.apache.lucene.document.FieldType;
+import org.apache.lucene.document.NumericDocValuesField;
 import org.apache.lucene.document.TextField;
 import org.apache.lucene.index.IndexWriterConfig.OpenMode;
 import org.apache.lucene.search.DocIdSetIterator;
 import org.apache.lucene.search.TermQuery;
 import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.MockDirectoryWrapper;
 import org.apache.lucene.util.Bits;
 import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.IOUtils;
 import org.apache.lucene.util.LuceneTestCase;
 import org.apache.lucene.util.TestUtil;
 
@@ -115,7 +120,40 @@ public class TestStressIndexing2 extends LuceneTestCase {
     }
   }
 
+  public void testRandomCorruptionIsTragic() throws Exception {
+    
+    final MockDirectoryWrapper dir = newMockDirectory();
+    dir.setCheckIndexOnClose(false); // we are corrupting it!
+    
+    final IndexWriterConfig iwc = newIndexWriterConfig();
+    final MergeScheduler ms = iwc.getMergeScheduler();
+    if (ms instanceof ConcurrentMergeScheduler) {
+      ((ConcurrentMergeScheduler) ms).setSuppressExceptions();
+    }
+    final IndexWriter w = new IndexWriter(dir, iwc);
 
+    try {
+      // NOTE: we never need to 'start' this thread, we're just going to use it's logic as a Runnable
+      CorruptibleIndexingThread th = new CorruptibleIndexingThread();
+      th.w = w;
+      th.dir = dir;
+      th.base = 0;
+      th.range = atLeast(10);
+      th.iterations = atLeast(5);
+      
+      th.run();
+      
+    } finally {
+      if (ms instanceof ConcurrentMergeScheduler) {
+        // Sneaky: CMS's merge thread will be concurrently rolling back IW due
+        // to the tragedy, with this main thread, so we have to wait here
+        // to ensure the rollback has finished, else MDW still sees open files:
+        ((ConcurrentMergeScheduler) ms).sync();
+      }
+      IOUtils.closeWhileHandlingException(w, dir);
+    }
+  }
+  
   static Term idTerm = new Term("id","");
   IndexingThread[] threads;
   static Comparator<IndexableField> fieldNameComparator = new Comparator<IndexableField>() {
@@ -765,8 +803,9 @@ public class TestStressIndexing2 extends LuceneTestCase {
       
       ArrayList<Field> fields = new ArrayList<>();
       String idString = getIdString();
-      Field idField =  newField("id", idString, customType1);
-      fields.add(idField);
+
+      fields.add(newField("id", idString, customType1));
+      fields.add(new NumericDocValuesField("docValues", nextInt(5000)));
 
       Map<String,FieldType> tvTypes = new HashMap<>();
 
@@ -853,6 +892,26 @@ public class TestStressIndexing2 extends LuceneTestCase {
       docs.put(idString, d);
     }
 
+    public void updateDocVal() throws IOException {
+      if (docs.isEmpty()) {
+        indexDoc();
+        return;
+      }
+      
+      final String idString = (new ArrayList<>(docs.keySet())).get(nextInt(docs.size()));
+      
+      if (VERBOSE) {
+        System.out.println(Thread.currentThread().getName() + ": dv update id:" + idString);
+      }
+      
+      final NumericDocValuesField val = new NumericDocValuesField("docValues", nextInt(5000));
+      w.updateDocValues(new Term("id",idString), val);
+      
+      final Document doc = docs.get(idString);
+      doc.removeFields("docValues");
+      doc.add(val);
+    }
+    
     public void deleteDoc() throws IOException {
       String idString = getIdString();
       if (VERBOSE) {
@@ -871,27 +930,108 @@ public class TestStressIndexing2 extends LuceneTestCase {
       docs.remove(idString);
     }
 
+    public void doOneRandomOp() throws IOException {
+      final int what = nextInt(100);
+      if (what < 5) {
+        deleteDoc();
+      } else if (what < 10) {
+        deleteByQuery();
+      } else if (what < 20) {
+        updateDocVal();
+      } else {
+        indexDoc();
+      }
+    }
+    
     @Override
     public void run() {
       try {
         r = new Random(base+range+seed);
         for (int i=0; i<iterations; i++) {
-          int what = nextInt(100);
-          if (what < 5) {
-            deleteDoc();
-          } else if (what < 10) {
-            deleteByQuery();
-          } else {
-            indexDoc();
-          }
+          doOneRandomOp();
         }
       } catch (Throwable e) {
         throw new RuntimeException(e);
       }
 
-      synchronized (this) {
+      synchronized (this) { // nocommit: what/why is this here?
         docs.size();
       }
     }
   }
+
+  public class CorruptibleIndexingThread extends IndexingThread {
+    MockDirectoryWrapper dir;
+    
+    @Override
+    public void run() {
+      assert null != dir;
+      assert null != w;
+      r = new Random(base+range+seed);
+      
+      // we're going to loop (effectively) forever
+      // introducing a small amount of corruption then attempting an index update
+      // once we encounter an exception from the index update, we will assert that the IW
+      // has recorded a traggic exception
+      
+      int totalCorruption = 0;
+      int numIndexOpsSucceded = 0;
+      boolean gotExpectedFailure = false;
+      
+      // "while (true)" safety valve, prevent infinite loop if IW is so broken it never throws exceptions
+      // NOTE: test could get very 'lucky' (need MockDirectoryWrapper.alwaysCorrupt to be publc)
+      while (totalCorruption < 999999) {
+        
+        // do some random corruption of a *few* files
+        try {
+          final List<String> allFiles = Arrays.asList(dir.listAll());
+          Collections.sort(allFiles);
+          Collections.shuffle(allFiles, r);
+          try {
+            dir.corruptFiles(allFiles.subList(0, Math.min(allFiles.size(), RANDOM_MULTIPLIER)));
+            totalCorruption++;
+          } catch (RuntimeException | FileNotFoundException | NoSuchFileException e) {
+            // merges can lead to this exception
+          }
+        } catch (IOException e) {
+          assertNull("IOException trying to corrupt", e);
+        }
+        
+        // do some index updates
+        try {
+          doOneRandomOp();
+          numIndexOpsSucceded++;
+
+          // nocommit: do a LOT of commits for now since that's where the (known) problem seems to be
+          // if (r.nextInt(100) < 10) { 
+          if (r.nextInt(100) < 50) {
+            w.commit();
+            numIndexOpsSucceded++;
+          }
+          
+        } catch (Throwable t) {
+          // NOTE: we don't use expectThrows because there's no garuntee that
+          // the update we attempt will cause an exception, but if it *does* cause an exception,
+          // then it must have been tragic.
+          //
+          // (nothing we're doing that might cause an exception should ever be "non-tragic")
+          try {
+            assertNotNull("index update encountered throwable, but no tragic event recorded: "
+                          + t.toString(),
+                          w.getTragicException());
+            assertFalse(w.isOpen());
+          } catch (AssertionError a) {
+            a.addSuppressed(t);
+            throw a;
+          }
+          gotExpectedFailure = true;
+          break;
+        }
+      }
+      assertTrue("Safety Valve: " + totalCorruption + " calls to corruptFiles() and " + numIndexOpsSucceded +
+                 " index ops succeded w/o any IndexWriter exceptions?",
+                 gotExpectedFailure);
+      
+    }
+  }
 }