You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by mi...@apache.org on 2014/12/19 16:44:30 UTC

svn commit: r1646778 - in /lucene/dev/branches/branch_5x: ./ lucene/ lucene/benchmark/ lucene/benchmark/src/java/org/apache/lucene/benchmark/byTask/tasks/ lucene/core/ lucene/core/src/java/org/apache/lucene/index/ lucene/core/src/java/org/apache/lucene...

Author: mikemccand
Date: Fri Dec 19 15:44:30 2014
New Revision: 1646778

URL: http://svn.apache.org/r1646778
Log:
LUCENE-5951: try to detect if index is on an SSD and default CMS's settings accordingly

Modified:
    lucene/dev/branches/branch_5x/   (props changed)
    lucene/dev/branches/branch_5x/lucene/   (props changed)
    lucene/dev/branches/branch_5x/lucene/CHANGES.txt   (contents, props changed)
    lucene/dev/branches/branch_5x/lucene/benchmark/   (props changed)
    lucene/dev/branches/branch_5x/lucene/benchmark/src/java/org/apache/lucene/benchmark/byTask/tasks/CreateIndexTask.java
    lucene/dev/branches/branch_5x/lucene/core/   (props changed)
    lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/index/ConcurrentMergeScheduler.java
    lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/store/FSDirectory.java
    lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/util/IOUtils.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestConcurrentMergeScheduler.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/util/TestIOUtils.java
    lucene/dev/branches/branch_5x/lucene/test-framework/   (props changed)
    lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/mockfile/FilterFileStore.java
    lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/mockfile/FilterFileSystemProvider.java
    lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/mockfile/FilterPath.java
    lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/util/LuceneTestCase.java

Modified: lucene/dev/branches/branch_5x/lucene/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/CHANGES.txt?rev=1646778&r1=1646777&r2=1646778&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/CHANGES.txt (original)
+++ lucene/dev/branches/branch_5x/lucene/CHANGES.txt Fri Dec 19 15:44:30 2014
@@ -150,6 +150,11 @@ Optimizations
   TokenStream with MemoryIndex when it comes from TokenSources (term vectors) with offsets and
   positions. (David Smiley)
 
+* LUCENE-5951: ConcurrentMergeScheduler detects whether the index is on SSD or not
+  and does a better job defaulting its settings.  This only works on Linux for now;
+  other OS's will continue to use the previous defaults (tuned for spinning disks).
+  (Robert Muir, Uwe Schindler, hossman, Mike McCandless)
+
 API Changes
 
 * LUCENE-5900: Deprecated more constructors taking Version in *InfixSuggester and

Modified: lucene/dev/branches/branch_5x/lucene/benchmark/src/java/org/apache/lucene/benchmark/byTask/tasks/CreateIndexTask.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/benchmark/src/java/org/apache/lucene/benchmark/byTask/tasks/CreateIndexTask.java?rev=1646778&r1=1646777&r2=1646778&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/benchmark/src/java/org/apache/lucene/benchmark/byTask/tasks/CreateIndexTask.java (original)
+++ lucene/dev/branches/branch_5x/lucene/benchmark/src/java/org/apache/lucene/benchmark/byTask/tasks/CreateIndexTask.java Fri Dec 19 15:44:30 2014
@@ -24,7 +24,6 @@ import java.nio.charset.Charset;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.text.ParseException;
 
 import org.apache.lucene.benchmark.byTask.PerfRunData;
 import org.apache.lucene.benchmark.byTask.utils.Config;
@@ -35,8 +34,8 @@ import org.apache.lucene.index.Concurren
 import org.apache.lucene.index.IndexCommit;
 import org.apache.lucene.index.IndexDeletionPolicy;
 import org.apache.lucene.index.IndexWriter;
-import org.apache.lucene.index.IndexWriterConfig.OpenMode;
 import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.IndexWriterConfig.OpenMode;
 import org.apache.lucene.index.LogMergePolicy;
 import org.apache.lucene.index.MergePolicy;
 import org.apache.lucene.index.MergeScheduler;
@@ -102,8 +101,9 @@ public class CreateIndexTask extends Per
     iwConf.setOpenMode(mode);
     IndexDeletionPolicy indexDeletionPolicy = getIndexDeletionPolicy(config);
     iwConf.setIndexDeletionPolicy(indexDeletionPolicy);
-    if(commit != null)
+    if (commit != null) {
       iwConf.setIndexCommit(commit);
+    }
     
 
     final String mergeScheduler = config.get("merge.scheduler",
@@ -119,8 +119,8 @@ public class CreateIndexTask extends Per
       
       if (mergeScheduler.equals("org.apache.lucene.index.ConcurrentMergeScheduler")) {
         ConcurrentMergeScheduler cms = (ConcurrentMergeScheduler) iwConf.getMergeScheduler();
-        int maxThreadCount = config.get("concurrent.merge.scheduler.max.thread.count", ConcurrentMergeScheduler.DEFAULT_MAX_THREAD_COUNT);
-        int maxMergeCount = config.get("concurrent.merge.scheduler.max.merge.count", ConcurrentMergeScheduler.DEFAULT_MAX_MERGE_COUNT);
+        int maxThreadCount = config.get("concurrent.merge.scheduler.max.thread.count", ConcurrentMergeScheduler.AUTO_DETECT_MERGES_AND_THREADS);
+        int maxMergeCount = config.get("concurrent.merge.scheduler.max.merge.count", ConcurrentMergeScheduler.AUTO_DETECT_MERGES_AND_THREADS);
         cms.setMaxMergesAndThreads(maxMergeCount, maxThreadCount);
       }
     }

Modified: lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/index/ConcurrentMergeScheduler.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/index/ConcurrentMergeScheduler.java?rev=1646778&r1=1646777&r2=1646778&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/index/ConcurrentMergeScheduler.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/index/ConcurrentMergeScheduler.java Fri Dec 19 15:44:30 2014
@@ -17,14 +17,15 @@ package org.apache.lucene.index;
  * limitations under the License.
  */
 
-import org.apache.lucene.store.Directory;
-import org.apache.lucene.util.ThreadInterruptedException;
-import org.apache.lucene.util.CollectionUtil;
-
 import java.io.IOException;
-import java.util.List;
 import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.List;
+
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.CollectionUtil;
+import org.apache.lucene.util.IOUtils;
+import org.apache.lucene.util.ThreadInterruptedException;
 
 /** A {@link MergeScheduler} that runs each merge using a
  *  separate thread.
@@ -41,36 +42,44 @@ import java.util.Comparator;
  *  requested then this class will forcefully throttle the
  *  incoming threads by pausing until one more more merges
  *  complete.</p>
+ *
+ *  <p>This class attempts to detect whether the index is
+ *  on rotational storage (traditional hard drive) or not
+ *  (e.g. solid-state disk) and changes the default max merge
+ *  and thread count accordingly.  This detection is currently
+ *  Linux-only, and relies on the OS to put the right value
+ *  into /sys/block/&lt;dev&gt;/block/rotational.  For all
+ *  other operating systems it currently assumes a rotational
+ *  disk for backwards compatibility.  To enable default
+ *  settings for spinning or solid state disks for such
+ *  operating systems, use {@link #setDefaultMaxMergesAndThreads(boolean)}.
  */ 
 public class ConcurrentMergeScheduler extends MergeScheduler {
 
+  /** Dynamic default for {@code maxThreadCount} and {@code maxMergeCount},
+   *  used to detect whether the index is backed by an SSD or rotational disk and
+   *  set {@code maxThreadCount} accordingly.  If it's an SSD,
+   *  {@code maxThreadCount} is set to {@code max(1, min(3, cpuCoreCount/2))},
+   *  otherwise 1.  Note that detection only currently works on
+   *  Linux; other platforms will assume the index is not on an SSD. */
+  public static final int AUTO_DETECT_MERGES_AND_THREADS = -1;
+
   private int mergeThreadPriority = -1;
 
   /** List of currently active {@link MergeThread}s. */
   protected final List<MergeThread> mergeThreads = new ArrayList<>();
   
-  /** 
-   * Default {@code maxThreadCount}.
-   * We default to 1: tests on spinning-magnet drives showed slower
-   * indexing performance if more than one merge thread runs at
-   * once (though on an SSD it was faster)
-   */
-  public static final int DEFAULT_MAX_THREAD_COUNT = 1;
-  
-  /** Default {@code maxMergeCount}. */
-  public static final int DEFAULT_MAX_MERGE_COUNT = 2;
-
   // Max number of merge threads allowed to be running at
   // once.  When there are more merges then this, we
   // forcefully pause the larger ones, letting the smaller
   // ones run, up until maxMergeCount merges at which point
   // we forcefully pause incoming threads (that presumably
   // are the ones causing so much merging).
-  private int maxThreadCount = DEFAULT_MAX_THREAD_COUNT;
+  private int maxThreadCount = AUTO_DETECT_MERGES_AND_THREADS;
 
   // Max number of merges we accept before forcefully
   // throttling the incoming threads
-  private int maxMergeCount = DEFAULT_MAX_MERGE_COUNT;
+  private int maxMergeCount = AUTO_DETECT_MERGES_AND_THREADS;
 
   /** {@link Directory} that holds the index. */
   protected Directory dir;
@@ -88,7 +97,8 @@ public class ConcurrentMergeScheduler ex
   }
 
   /**
-   * Sets the maximum number of merge threads and simultaneous merges allowed.
+   * Expert: directly set the maximum number of merge threads and
+   * simultaneous merges allowed.
    * 
    * @param maxMergeCount the max # simultaneous merges that are allowed.
    *       If a merge is necessary yet we already have this many
@@ -99,29 +109,55 @@ public class ConcurrentMergeScheduler ex
    * @param maxThreadCount the max # simultaneous merge threads that should
    *       be running at once.  This must be &lt;= <code>maxMergeCount</code>
    */
-  public void setMaxMergesAndThreads(int maxMergeCount, int maxThreadCount) {
-    if (maxThreadCount < 1) {
-      throw new IllegalArgumentException("maxThreadCount should be at least 1");
-    }
-    if (maxMergeCount < 1) {
-      throw new IllegalArgumentException("maxMergeCount should be at least 1");
+  public synchronized void setMaxMergesAndThreads(int maxMergeCount, int maxThreadCount) {
+    if (maxMergeCount == AUTO_DETECT_MERGES_AND_THREADS && maxThreadCount == AUTO_DETECT_MERGES_AND_THREADS) {
+      // OK
+      this.maxMergeCount = AUTO_DETECT_MERGES_AND_THREADS;
+      this.maxThreadCount = AUTO_DETECT_MERGES_AND_THREADS;
+    } else if (maxMergeCount == AUTO_DETECT_MERGES_AND_THREADS) {
+      throw new IllegalArgumentException("both maxMergeCount and maxThreadCount must be AUTO_DETECT_MERGES_AND_THREADS");
+    } else if (maxThreadCount == AUTO_DETECT_MERGES_AND_THREADS) {
+      throw new IllegalArgumentException("both maxMergeCount and maxThreadCount must be AUTO_DETECT_MERGES_AND_THREADS");
+    } else {
+      if (maxThreadCount < 1) {
+        throw new IllegalArgumentException("maxThreadCount should be at least 1");
+      }
+      if (maxMergeCount < 1) {
+        throw new IllegalArgumentException("maxMergeCount should be at least 1");
+      }
+      if (maxThreadCount > maxMergeCount) {
+        throw new IllegalArgumentException("maxThreadCount should be <= maxMergeCount (= " + maxMergeCount + ")");
+      }
+      this.maxThreadCount = maxThreadCount;
+      this.maxMergeCount = maxMergeCount;
     }
-    if (maxThreadCount > maxMergeCount) {
-      throw new IllegalArgumentException("maxThreadCount should be <= maxMergeCount (= " + maxMergeCount + ")");
+  }
+
+  /** Sets max merges and threads to proper defaults for rotational
+   *  or non-rotational storage.
+   *
+   * @param spins true to set defaults best for traditional rotatational storage (spinning disks), 
+   *        else false (e.g. for solid-state disks)
+   */
+  public synchronized void setDefaultMaxMergesAndThreads(boolean spins) {
+    if (spins) {
+      maxThreadCount = 1;
+      maxMergeCount = 2;
+    } else {
+      maxThreadCount = Math.max(1, Math.min(3, Runtime.getRuntime().availableProcessors()/2));
+      maxMergeCount = maxThreadCount+2;
     }
-    this.maxThreadCount = maxThreadCount;
-    this.maxMergeCount = maxMergeCount;
   }
 
   /** Returns {@code maxThreadCount}.
    *
    * @see #setMaxMergesAndThreads(int, int) */
-  public int getMaxThreadCount() {
+  public synchronized int getMaxThreadCount() {
     return maxThreadCount;
   }
 
   /** See {@link #setMaxMergesAndThreads}. */
-  public int getMaxMergeCount() {
+  public synchronized int getMaxMergeCount() {
     return maxMergeCount;
   }
 
@@ -257,6 +293,17 @@ public class ConcurrentMergeScheduler ex
     }
   }
 
+  private synchronized void initMaxMergesAndThreads() throws IOException {
+    if (maxThreadCount == AUTO_DETECT_MERGES_AND_THREADS) {
+      assert writer != null;
+      boolean spins = IOUtils.spins(writer.getDirectory());
+      setDefaultMaxMergesAndThreads(spins);
+      if (verbose()) {
+        message("initMaxMergesAndThreads spins=" + spins + " maxThreadCount=" + maxThreadCount + " maxMergeCount=" + maxMergeCount);
+      }
+    }
+  }
+
   @Override
   public void close() {
     sync();
@@ -318,6 +365,7 @@ public class ConcurrentMergeScheduler ex
     this.writer = writer;
 
     initMergeThreadPriority();
+    initMaxMergesAndThreads();
 
     dir = writer.getDirectory();
 

Modified: lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/store/FSDirectory.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/store/FSDirectory.java?rev=1646778&r1=1646777&r2=1646778&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/store/FSDirectory.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/store/FSDirectory.java Fri Dec 19 15:44:30 2014
@@ -126,7 +126,7 @@ public abstract class FSDirectory extend
    */
   protected FSDirectory(Path path, LockFactory lockFactory) throws IOException {
     super(lockFactory);
-    Files.createDirectories(path);  // create directory, if it doesnt exist
+    Files.createDirectories(path);  // create directory, if it doesn't exist
     directory = path.toRealPath();
   }
 

Modified: lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/util/IOUtils.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/util/IOUtils.java?rev=1646778&r1=1646777&r2=1646778&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/util/IOUtils.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/util/IOUtils.java Fri Dec 19 15:44:30 2014
@@ -17,8 +17,6 @@ package org.apache.lucene.util;
  * limitations under the License.
  */
 
-import org.apache.lucene.store.Directory;
-
 import java.io.BufferedReader;
 import java.io.Closeable;
 import java.io.IOException;
@@ -30,6 +28,7 @@ import java.nio.charset.Charset;
 import java.nio.charset.CharsetDecoder;
 import java.nio.charset.CodingErrorAction;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.FileStore;
 import java.nio.file.FileVisitResult;
 import java.nio.file.FileVisitor;
 import java.nio.file.Files;
@@ -41,6 +40,12 @@ import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.store.FileSwitchDirectory;
+import org.apache.lucene.store.FilterDirectory;
+import org.apache.lucene.store.RAMDirectory;
+
 /** This class emulates the new Java 7 "Try-With-Resources" statement.
  * Remove once Lucene is on Java 7.
  * @lucene.internal */
@@ -420,4 +425,129 @@ public final class IOUtils {
     // Throw original exception
     throw exc;
   }
+
+  /** If the dir is an {@link FSDirectory} or wraps one via possibly
+   *  nested {@link FilterDirectory} or {@link FileSwitchDirectory},
+   *  this returns {@link #spins(Path)} for the wrapped directory,
+   *  else, true.
+   *
+   *  @throws IOException if {@code path} does not exist.
+   *
+   *  @lucene.internal */
+  public static boolean spins(Directory dir) throws IOException {
+    dir = FilterDirectory.unwrap(dir);
+    if (dir instanceof FileSwitchDirectory) {
+      FileSwitchDirectory fsd = (FileSwitchDirectory) dir;
+      // Spinning is contagious:
+      return spins(fsd.getPrimaryDir()) || spins(fsd.getSecondaryDir());
+    } else if (dir instanceof RAMDirectory) {
+      return false;
+    } else if (dir instanceof FSDirectory) {
+      return spins(((FSDirectory) dir).getDirectory());
+    } else {
+      return true;
+    }
+  }
+
+  /** Rough Linux-only heuristics to determine whether the provided
+   *  {@code Path} is backed by spinning storage.  For example, this
+   *  returns false if the disk is a solid-state disk.
+   *
+   *  @param path a location to check which must exist. the mount point will be determined from this location.
+   *  @return false if the storage is non-rotational (e.g. an SSD), or true if it is spinning or could not be determined
+   *  @throws IOException if {@code path} does not exist.
+   *
+   *  @lucene.internal */
+  public static boolean spins(Path path) throws IOException {
+    // resolve symlinks (this will throw exception if the path does not exist)
+    path = path.toRealPath();
+    
+    // Super cowboy approach, but seems to work!
+    if (!Constants.LINUX) {
+      return true; // no detection
+    }
+
+    try {
+      return spinsLinux(path);
+    } catch (Exception exc) {
+      // our crazy heuristics can easily trigger SecurityException, AIOOBE, etc ...
+      return true;
+    }
+  }
+  
+  // following methods are package-private for testing ONLY
+  
+  // note: requires a real or fake linux filesystem!
+  static boolean spinsLinux(Path path) throws IOException {
+    FileStore store = getFileStore(path);
+    
+    // if fs type is tmpfs, it doesn't spin.
+    // this won't have a corresponding block device
+    if ("tmpfs".equals(store.type())) {
+      return false;
+    }
+    
+    // get block device name
+    String devName = getBlockDevice(store);
+    // not a device (e.g. NFS server)
+    if (!devName.startsWith("/")) {
+      return true;
+    }
+    
+    // resolve any symlinks to real block device (e.g. LVM)
+    // /dev/sda0 -> sda0
+    // /devices/XXX -> sda0
+    devName = path.getRoot().resolve(devName).toRealPath().getFileName().toString();
+  
+    // now read:
+    Path sysinfo = path.getRoot().resolve("sys/block");
+    Path devinfo = sysinfo.resolve(devName);
+    
+    // tear away partition numbers until we find it.
+    while (!Files.exists(devinfo)) {
+      if (!devName.isEmpty() && Character.isDigit(devName.charAt(devName.length()-1))) {
+        devName = devName.substring(0, devName.length()-1);
+      } else {
+        break; // give up
+      }
+      devinfo = sysinfo.resolve(devName);
+    }
+    
+    // read first byte from rotational, its a 1 if it spins.
+    Path info = devinfo.resolve("queue/rotational");
+    try (InputStream stream = Files.newInputStream(info)) {
+      return stream.read() == '1'; 
+    }
+  }
+  
+  // Files.getFileStore(Path) useless here!
+  // don't complain, just try it yourself
+  static FileStore getFileStore(Path path) throws IOException {
+    FileStore store = Files.getFileStore(path);
+    String mount = getMountPoint(store);
+
+    // find the "matching" FileStore from system list, its the one we want.
+    for (FileStore fs : path.getFileSystem().getFileStores()) {
+      if (mount.equals(getMountPoint(fs))) {
+        return fs;
+      }
+    }
+
+    // fall back to crappy one we got from Files.getFileStore
+    return store;    
+  }
+  
+  // these are hacks that are not guaranteed
+  static String getMountPoint(FileStore store) {
+    String desc = store.toString();
+    return desc.substring(0, desc.lastIndexOf('(') - 1);
+  }
+  
+  // these are hacks that are not guaranteed
+  static String getBlockDevice(FileStore store) {
+    String desc = store.toString();
+    int start = desc.lastIndexOf('(');
+    int end = desc.indexOf(')', start);
+    return desc.substring(start+1, end);
+  }
 }

Modified: lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestConcurrentMergeScheduler.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestConcurrentMergeScheduler.java?rev=1646778&r1=1646777&r2=1646778&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestConcurrentMergeScheduler.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestConcurrentMergeScheduler.java Fri Dec 19 15:44:30 2014
@@ -394,6 +394,22 @@ public class TestConcurrentMergeSchedule
     d.close();
   }
 
+  public void testInvalidMaxMergeCountAndThreads() throws Exception {
+    ConcurrentMergeScheduler cms = new ConcurrentMergeScheduler();
+    try {
+      cms.setMaxMergesAndThreads(ConcurrentMergeScheduler.AUTO_DETECT_MERGES_AND_THREADS, 3);
+      fail("did not hit exception");
+    } catch (IllegalArgumentException iae) {
+      // good
+    }
+    try {
+      cms.setMaxMergesAndThreads(3, ConcurrentMergeScheduler.AUTO_DETECT_MERGES_AND_THREADS);
+      fail("did not hit exception");
+    } catch (IllegalArgumentException iae) {
+      // good
+    }
+  }
+
   public void testLiveMaxMergeCount() throws Exception {
     Directory d = newDirectory();
     IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
@@ -429,6 +445,9 @@ public class TestConcurrentMergeSchedule
         }
       };
 
+    assertEquals(ConcurrentMergeScheduler.AUTO_DETECT_MERGES_AND_THREADS, cms.getMaxMergeCount());
+    assertEquals(ConcurrentMergeScheduler.AUTO_DETECT_MERGES_AND_THREADS, cms.getMaxThreadCount());
+
     cms.setMaxMergesAndThreads(5, 3);
 
     iwc.setMergeScheduler(cms);
@@ -550,4 +569,75 @@ public class TestConcurrentMergeSchedule
     w.rollback();
     dir.close();
   }
+
+  public void testDynamicDefaults() throws Exception {
+    Directory dir = newDirectory();
+    IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
+    ConcurrentMergeScheduler cms = new ConcurrentMergeScheduler();
+    assertEquals(ConcurrentMergeScheduler.AUTO_DETECT_MERGES_AND_THREADS, cms.getMaxMergeCount());
+    assertEquals(ConcurrentMergeScheduler.AUTO_DETECT_MERGES_AND_THREADS, cms.getMaxThreadCount());
+    iwc.setMergeScheduler(cms);
+    iwc.setMaxBufferedDocs(2);
+    LogMergePolicy lmp = newLogMergePolicy();
+    lmp.setMergeFactor(2);
+    iwc.setMergePolicy(lmp);
+
+    IndexWriter w = new IndexWriter(dir, iwc);
+    w.addDocument(new Document());
+    w.addDocument(new Document());
+    // flush
+
+    w.addDocument(new Document());
+    w.addDocument(new Document());
+    // flush + merge
+
+    // CMS should have now set true values:
+    assertTrue(cms.getMaxMergeCount() != ConcurrentMergeScheduler.AUTO_DETECT_MERGES_AND_THREADS);
+    assertTrue(cms.getMaxThreadCount() != ConcurrentMergeScheduler.AUTO_DETECT_MERGES_AND_THREADS);
+    w.close();
+    dir.close();
+  }
+
+  public void testResetToAutoDefault() throws Exception {
+    ConcurrentMergeScheduler cms = new ConcurrentMergeScheduler();
+    assertEquals(ConcurrentMergeScheduler.AUTO_DETECT_MERGES_AND_THREADS, cms.getMaxMergeCount());
+    assertEquals(ConcurrentMergeScheduler.AUTO_DETECT_MERGES_AND_THREADS, cms.getMaxThreadCount());
+    cms.setMaxMergesAndThreads(4, 3);
+    assertEquals(4, cms.getMaxMergeCount());
+    assertEquals(3, cms.getMaxThreadCount());
+
+    try {
+      cms.setMaxMergesAndThreads(ConcurrentMergeScheduler.AUTO_DETECT_MERGES_AND_THREADS, 4);
+      fail("did not hit exception");
+    } catch (IllegalArgumentException iae) {
+      // expected
+    }
+
+    try {
+      cms.setMaxMergesAndThreads(4, ConcurrentMergeScheduler.AUTO_DETECT_MERGES_AND_THREADS);
+      fail("did not hit exception");
+    } catch (IllegalArgumentException iae) {
+      // expected
+    }
+
+    cms.setMaxMergesAndThreads(ConcurrentMergeScheduler.AUTO_DETECT_MERGES_AND_THREADS, ConcurrentMergeScheduler.AUTO_DETECT_MERGES_AND_THREADS);
+    assertEquals(ConcurrentMergeScheduler.AUTO_DETECT_MERGES_AND_THREADS, cms.getMaxMergeCount());
+    assertEquals(ConcurrentMergeScheduler.AUTO_DETECT_MERGES_AND_THREADS, cms.getMaxThreadCount());
+  }
+
+  public void testSpinningDefaults() throws Exception {
+    ConcurrentMergeScheduler cms = new ConcurrentMergeScheduler();
+    cms.setDefaultMaxMergesAndThreads(true);
+    assertEquals(1, cms.getMaxThreadCount());
+    assertEquals(2, cms.getMaxMergeCount());
+  }
+
+  public void testNonSpinningDefaults() throws Exception {
+    ConcurrentMergeScheduler cms = new ConcurrentMergeScheduler();
+    cms.setDefaultMaxMergesAndThreads(false);
+    int threadCount = cms.getMaxThreadCount();
+    assertTrue(threadCount >= 1);
+    assertTrue(threadCount <= 3);
+    assertEquals(cms.getMaxMergeCount(), 2+threadCount);
+  }
 }

Modified: lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/util/TestIOUtils.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/util/TestIOUtils.java?rev=1646778&r1=1646777&r2=1646778&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/util/TestIOUtils.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/util/TestIOUtils.java Fri Dec 19 15:44:30 2014
@@ -17,8 +17,27 @@ package org.apache.lucene.util;
  * limitations under the License.
  */
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.AccessMode;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystem;
 import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.OpenOption;
 import java.nio.file.Path;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+import org.apache.lucene.mockfile.FilterFileStore;
+import org.apache.lucene.mockfile.FilterFileSystem;
+import org.apache.lucene.mockfile.FilterFileSystemProvider;
+import org.apache.lucene.mockfile.FilterPath;
 
 /** Simple test methods for IOUtils */
 public class TestIOUtils extends LuceneTestCase {
@@ -78,4 +97,281 @@ public class TestIOUtils extends LuceneT
     // no exception
     // actually deletes file2
   }
+  
+  public void testSpinsBasics() throws Exception {
+    Path dir = createTempDir();
+    // no exception, directory exists
+    IOUtils.spins(dir);
+    Path file = dir.resolve("exists");
+    Files.createFile(file);
+    // no exception, file exists
+    IOUtils.spins(file);
+    
+    // exception: file doesn't exist
+    Path fake = dir.resolve("nonexistent");
+    try {
+      IOUtils.spins(fake);
+      fail();
+    } catch (IOException expected) {
+      // ok
+    }
+  }
+  
+  // fake up a filestore to test some underlying methods
+  static class MockFileStore extends FilterFileStore {
+    final String description;
+    final String type;
+    
+    MockFileStore(FileStore delegate, String description) {
+      this(delegate, description, "mockfs");
+    }
+    
+    MockFileStore(FileStore delegate, String description, String type) {
+      super(delegate, "justafake://");
+      this.description = description;
+      this.type = type;
+    }
+
+    @Override
+    public String type() {
+      return type;
+    }
+
+    @Override
+    public String toString() {
+      return description;
+    }
+  }
+  
+  public void testGetBlockDevice() throws Exception {
+    Path dir = createTempDir();
+    FileStore actual = Files.getFileStore(dir);
+
+    assertEquals("/dev/sda1", IOUtils.getBlockDevice(new MockFileStore(actual, "/ (/dev/sda1)")));
+    assertEquals("/dev/sda1", IOUtils.getBlockDevice(new MockFileStore(actual, "/test/ space(((trash)))/ (/dev/sda1)")));
+    assertEquals("notreal", IOUtils.getBlockDevice(new MockFileStore(actual, "/ (notreal)")));
+  }
+  
+  public void testGetMountPoint() throws Exception {
+    Path dir = createTempDir();
+    FileStore actual = Files.getFileStore(dir);
+
+    assertEquals("/", IOUtils.getMountPoint(new MockFileStore(actual, "/ (/dev/sda1)")));
+    assertEquals("/test/ space(((trash)))/", IOUtils.getMountPoint(new MockFileStore(actual, "/test/ space(((trash)))/ (/dev/sda1)")));
+    assertEquals("/", IOUtils.getMountPoint(new MockFileStore(actual, "/ (notreal)")));
+  }
+  
+  /** mock linux that takes mappings of test files, to their associated filesystems.
+   *  it will chroot /dev and /sys requests to root, so you can mock those too.
+   *  <p>
+   *  It is hacky by definition, so don't try putting it around a complex chain or anything.
+   *  Use FilterPath.unwrap
+   */
+  static class MockLinuxFileSystemProvider extends FilterFileSystemProvider {
+    final Map<String,FileStore> filesToStore;
+    final Path root;
+    
+    public MockLinuxFileSystemProvider(FileSystem delegateInstance, final Map<String,FileStore> filesToStore, Path root) {
+      super("mocklinux://", delegateInstance);
+      final Collection<FileStore> allStores = new HashSet<>(filesToStore.values());
+      this.fileSystem = new FilterFileSystem(this, delegateInstance) {
+        @Override
+        public Iterable<FileStore> getFileStores() {
+          return allStores;
+        }
+
+        @Override
+        public Path getPath(String first, String... more) {
+          return new MockLinuxPath(super.getPath(first, more), this);
+        }
+      };
+      this.filesToStore = filesToStore;
+      this.root = root;
+    }
+
+    @Override
+    public FileStore getFileStore(Path path) throws IOException {
+      FileStore ret = filesToStore.get(path.toString());
+      if (ret == null) {
+        throw new IllegalArgumentException("this mock doesnt know wtf to do with: " + path);
+      }
+      // act like the linux fs provider here, return a crazy rootfs one
+      if (ret.toString().startsWith(root + " (")) {
+        return new MockFileStore(ret, root + " (rootfs)", "rootfs");
+      }
+
+      return ret;
+    }
+
+    Path maybeChroot(Path path) {
+      if (path.toAbsolutePath().startsWith("/sys") || path.toAbsolutePath().startsWith("/dev")) {
+        // map to our chrooted location;
+        return path.getRoot().resolve(root).resolve(path.toString().substring(1));
+      } else {
+        return path;
+      }
+    }
+    
+    @Override
+    public void checkAccess(Path path, AccessMode... modes) throws IOException {
+      // TODO: kinda screwed up how we do this, but its easy to get lost. just unravel completely.
+      delegate.checkAccess(FilterPath.unwrap(path), modes);
+    }
+
+    @Override
+    public InputStream newInputStream(Path path, OpenOption... options) throws IOException {
+      return super.newInputStream(maybeChroot(path), options);
+    }
+
+    class MockLinuxPath extends FilterPath {
+      MockLinuxPath(Path delegate, FileSystem fileSystem) {
+        super(delegate, fileSystem);
+      }
+      
+      @Override
+      public Path toRealPath(LinkOption... options) throws IOException {
+        Path p = maybeChroot(this);
+        if (p == this) {
+          return super.toRealPath(options);
+        } else {
+          return p.toRealPath(options);
+        }
+      }
+
+      @Override
+      protected Path wrap(Path other) {
+        return new MockLinuxPath(other, fileSystem);
+      }
+    }
+  }
+  
+  public void testGetFileStore() throws Exception {
+    Path dir = createTempDir();
+    dir = FilterPath.unwrap(dir).toRealPath();
+    
+    // now we can create some fake mount points:
+    FileStore root = new MockFileStore(Files.getFileStore(dir), dir.toString() + " (/dev/sda1)");
+    FileStore usr = new MockFileStore(Files.getFileStore(dir), dir.resolve("usr").toString() + " (/dev/sda2)");
+
+    // associate some preset files to these
+    Map<String,FileStore> mappings = new HashMap<>();
+    mappings.put(dir.toString(), root);
+    mappings.put(dir.resolve("foo.txt").toString(), root);
+    mappings.put(dir.resolve("usr").toString(), usr);
+    mappings.put(dir.resolve("usr/bar.txt").toString(), usr);
+    
+    FileSystem mockLinux = new MockLinuxFileSystemProvider(dir.getFileSystem(), mappings, dir).getFileSystem(null);
+    Path mockPath = mockLinux.getPath(dir.toString());
+    
+    // sanity check our mock:
+    assertSame(usr, Files.getFileStore(mockPath.resolve("usr")));
+    assertSame(usr, Files.getFileStore(mockPath.resolve("usr/bar.txt")));
+    // for root filesystem we get a crappy one
+    assertNotSame(root, Files.getFileStore(mockPath));
+    assertNotSame(usr, Files.getFileStore(mockPath));
+    assertNotSame(root, Files.getFileStore(mockPath.resolve("foo.txt")));
+    assertNotSame(usr, Files.getFileStore(mockPath.resolve("foo.txt")));
+    
+    // now test our method:
+    assertSame(usr, IOUtils.getFileStore(mockPath.resolve("usr")));
+    assertSame(usr, IOUtils.getFileStore(mockPath.resolve("usr/bar.txt")));
+    assertSame(root, IOUtils.getFileStore(mockPath));
+    assertSame(root, IOUtils.getFileStore(mockPath.resolve("foo.txt")));
+  }
+  
+  public void testTmpfsDoesntSpin() throws Exception {
+    Path dir = createTempDir();
+    dir = FilterPath.unwrap(dir).toRealPath();
+    
+    // fake tmpfs
+    FileStore root = new MockFileStore(Files.getFileStore(dir), dir.toString() + " (/dev/sda1)", "tmpfs");
+    Map<String,FileStore> mappings = Collections.singletonMap(dir.toString(), root);
+    FileSystem mockLinux = new MockLinuxFileSystemProvider(dir.getFileSystem(), mappings, dir).getFileSystem(null);
+    
+    Path mockPath = mockLinux.getPath(dir.toString());
+    assertFalse(IOUtils.spinsLinux(mockPath));
+  }
+  
+  public void testNfsSpins() throws Exception {
+    Path dir = createTempDir();
+    dir = FilterPath.unwrap(dir).toRealPath();
+    
+    // fake nfs
+    FileStore root = new MockFileStore(Files.getFileStore(dir), dir.toString() + " (somenfsserver:/some/mount)", "nfs");
+    Map<String,FileStore> mappings = Collections.singletonMap(dir.toString(), root);
+    FileSystem mockLinux = new MockLinuxFileSystemProvider(dir.getFileSystem(), mappings, dir).getFileSystem(null);
+    
+    Path mockPath = mockLinux.getPath(dir.toString());
+    assertTrue(IOUtils.spinsLinux(mockPath));
+  }
+  
+  public void testSSD() throws Exception {
+    Path dir = createTempDir();
+    dir = FilterPath.unwrap(dir).toRealPath();
+    
+    // fake ssd
+    FileStore root = new MockFileStore(Files.getFileStore(dir), dir.toString() + " (/dev/zzz1)");
+    // make a fake /dev/zzz1 for it
+    Path devdir = dir.resolve("dev");
+    Files.createDirectories(devdir);
+    Files.createFile(devdir.resolve("zzz1"));
+    // make a fake /sys/block/zzz/queue/rotational file for it
+    Path sysdir = dir.resolve("sys").resolve("block").resolve("zzz").resolve("queue");
+    Files.createDirectories(sysdir);
+    try (OutputStream o = Files.newOutputStream(sysdir.resolve("rotational"))) {
+      o.write("0\n".getBytes(StandardCharsets.US_ASCII));
+    }
+    Map<String,FileStore> mappings = Collections.singletonMap(dir.toString(), root);
+    FileSystem mockLinux = new MockLinuxFileSystemProvider(dir.getFileSystem(), mappings, dir).getFileSystem(null);
+    
+    Path mockPath = mockLinux.getPath(dir.toString());
+    assertFalse(IOUtils.spinsLinux(mockPath));
+  }
+  
+  public void testRotatingPlatters() throws Exception {
+    Path dir = createTempDir();
+    dir = FilterPath.unwrap(dir).toRealPath();
+    
+    // fake ssd
+    FileStore root = new MockFileStore(Files.getFileStore(dir), dir.toString() + " (/dev/zzz1)");
+    // make a fake /dev/zzz1 for it
+    Path devdir = dir.resolve("dev");
+    Files.createDirectories(devdir);
+    Files.createFile(devdir.resolve("zzz1"));
+    // make a fake /sys/block/zzz/queue/rotational file for it
+    Path sysdir = dir.resolve("sys").resolve("block").resolve("zzz").resolve("queue");
+    Files.createDirectories(sysdir);
+    try (OutputStream o = Files.newOutputStream(sysdir.resolve("rotational"))) {
+      o.write("1\n".getBytes(StandardCharsets.US_ASCII));
+    }
+    Map<String,FileStore> mappings = Collections.singletonMap(dir.toString(), root);
+    FileSystem mockLinux = new MockLinuxFileSystemProvider(dir.getFileSystem(), mappings, dir).getFileSystem(null);
+    
+    Path mockPath = mockLinux.getPath(dir.toString());
+    assertTrue(IOUtils.spinsLinux(mockPath));
+  }
+  
+  public void testManyPartitions() throws Exception {
+    Path dir = createTempDir();
+    dir = FilterPath.unwrap(dir).toRealPath();
+    
+    // fake ssd
+    FileStore root = new MockFileStore(Files.getFileStore(dir), dir.toString() + " (/dev/zzz12)");
+    // make a fake /dev/zzz11 for it
+    Path devdir = dir.resolve("dev");
+    Files.createDirectories(devdir);
+    Files.createFile(devdir.resolve("zzz12"));
+    // make a fake /sys/block/zzz/queue/rotational file for it
+    Path sysdir = dir.resolve("sys").resolve("block").resolve("zzz").resolve("queue");
+    Files.createDirectories(sysdir);
+    try (OutputStream o = Files.newOutputStream(sysdir.resolve("rotational"))) {
+      o.write("0\n".getBytes(StandardCharsets.US_ASCII));
+    }
+    Map<String,FileStore> mappings = Collections.singletonMap(dir.toString(), root);
+    FileSystem mockLinux = new MockLinuxFileSystemProvider(dir.getFileSystem(), mappings, dir).getFileSystem(null);
+    
+    Path mockPath = mockLinux.getPath(dir.toString());
+    assertFalse(IOUtils.spinsLinux(mockPath));
+  }
+  
 }

Modified: lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/mockfile/FilterFileStore.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/mockfile/FilterFileStore.java?rev=1646778&r1=1646777&r2=1646778&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/mockfile/FilterFileStore.java (original)
+++ lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/mockfile/FilterFileStore.java Fri Dec 19 15:44:30 2014
@@ -59,12 +59,12 @@ public class FilterFileStore extends Fil
 
   @Override
   public String type() {
-    return scheme + "(" + delegate.type() + ")";
+    return delegate.type();
   }
 
   @Override
   public String toString() {
-    return scheme + "(" + delegate.toString() + ")";
+    return delegate.toString();
   }
 
   @Override

Modified: lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/mockfile/FilterFileSystemProvider.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/mockfile/FilterFileSystemProvider.java?rev=1646778&r1=1646777&r2=1646778&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/mockfile/FilterFileSystemProvider.java (original)
+++ lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/mockfile/FilterFileSystemProvider.java Fri Dec 19 15:44:30 2014
@@ -59,7 +59,7 @@ public class FilterFileSystemProvider ex
   /** 
    * The underlying {@code FileSystem} instance. 
    */
-  protected final FileSystem fileSystem;
+  protected FileSystem fileSystem;
   /** 
    * The URI scheme for this provider.
    */
@@ -78,6 +78,18 @@ public class FilterFileSystemProvider ex
     this.delegate = delegateInstance.provider();
     this.fileSystem = new FilterFileSystem(this, delegateInstance);
   }
+  
+  /**
+   * Construct a {@code FilterFileSystemProvider} indicated by
+   * the specified {@code scheme} and wrapping functionality of the
+   * provider. You must set the singleton {@code filesystem} yourself.
+   * @param scheme URI scheme
+   * @param delegate specified base provider.
+   */
+  public FilterFileSystemProvider(String scheme, FileSystemProvider delegate) {
+    this.scheme = Objects.requireNonNull(scheme);
+    this.delegate = Objects.requireNonNull(delegate);
+  }
 
   @Override
   public String getScheme() {
@@ -86,21 +98,33 @@ public class FilterFileSystemProvider ex
 
   @Override
   public FileSystem newFileSystem(URI uri, Map<String,?> env) throws IOException {
+    if (fileSystem == null) {
+      throw new IllegalStateException("subclass did not initialize singleton filesystem");
+    }
     return fileSystem;
   }
   
   @Override
   public FileSystem newFileSystem(Path path, Map<String,?> env) throws IOException {
+    if (fileSystem == null) {
+      throw new IllegalStateException("subclass did not initialize singleton filesystem");
+    }
     return fileSystem;
   }
 
   @Override
   public FileSystem getFileSystem(URI uri) {
+    if (fileSystem == null) {
+      throw new IllegalStateException("subclass did not initialize singleton filesystem");
+    }
     return fileSystem;
   }
 
   @Override
   public Path getPath(URI uri) {
+    if (fileSystem == null) {
+      throw new IllegalStateException("subclass did not initialize singleton filesystem");
+    }
     Path path = delegate.getPath(toDelegate(uri));
     return new FilterPath(path, fileSystem);
   }

Modified: lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/mockfile/FilterPath.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/mockfile/FilterPath.java?rev=1646778&r1=1646777&r2=1646778&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/mockfile/FilterPath.java (original)
+++ lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/mockfile/FilterPath.java Fri Dec 19 15:44:30 2014
@@ -82,7 +82,7 @@ public class FilterPath implements Path
     if (root == null) {
       return null;
     }
-    return new FilterPath(root, fileSystem);
+    return wrap(root);
   }
 
   @Override
@@ -91,7 +91,7 @@ public class FilterPath implements Path
     if (fileName == null) {
       return null;
     }
-    return new FilterPath(fileName, fileSystem);
+    return wrap(fileName);
   }
 
   @Override
@@ -100,7 +100,7 @@ public class FilterPath implements Path
     if (parent == null) {
       return null;
     }
-    return new FilterPath(parent, fileSystem);
+    return wrap(parent);
   }
 
   @Override
@@ -110,12 +110,12 @@ public class FilterPath implements Path
 
   @Override
   public Path getName(int index) {
-    return new FilterPath(delegate.getName(index), fileSystem);
+    return wrap(delegate.getName(index));
   }
 
   @Override
   public Path subpath(int beginIndex, int endIndex) {
-    return new FilterPath(delegate.subpath(beginIndex, endIndex), fileSystem);
+    return wrap(delegate.subpath(beginIndex, endIndex));
   }
 
   @Override
@@ -148,7 +148,7 @@ public class FilterPath implements Path
 
   @Override
   public Path normalize() {
-    return new FilterPath(delegate.normalize(), fileSystem);
+    return wrap(delegate.normalize());
   }
 
   @Override
@@ -156,12 +156,12 @@ public class FilterPath implements Path
     if (other instanceof FilterPath) {
       other = ((FilterPath)other).delegate;
     }
-    return new FilterPath(delegate.resolve(other), fileSystem);
+    return wrap(delegate.resolve(other));
   }
 
   @Override
   public Path resolve(String other) {
-    return new FilterPath(delegate.resolve(other), fileSystem);
+    return wrap(delegate.resolve(other));
   }
 
   @Override
@@ -169,12 +169,12 @@ public class FilterPath implements Path
     if (other instanceof FilterPath) {
       other = ((FilterPath)other).delegate;
     }
-    return new FilterPath(delegate.resolveSibling(other), fileSystem);
+    return wrap(delegate.resolveSibling(other));
   }
 
   @Override
   public Path resolveSibling(String other) {
-    return new FilterPath(delegate.resolveSibling(other), fileSystem);
+    return wrap(delegate.resolveSibling(other));
   }
 
   @Override
@@ -182,7 +182,7 @@ public class FilterPath implements Path
     if (other instanceof FilterPath) {
       other = ((FilterPath)other).delegate;
     }
-    return new FilterPath(delegate.relativize(other), fileSystem);
+    return wrap(delegate.relativize(other));
   }
 
   // TODO: should these methods not expose delegate result directly?
@@ -200,12 +200,12 @@ public class FilterPath implements Path
 
   @Override
   public Path toAbsolutePath() {
-    return new FilterPath(delegate.toAbsolutePath(), fileSystem);
+    return wrap(delegate.toAbsolutePath());
   }
 
   @Override
   public Path toRealPath(LinkOption... options) throws IOException {
-    return new FilterPath(delegate.toRealPath(options), fileSystem);
+    return wrap(delegate.toRealPath(options));
   }
 
   @Override
@@ -235,7 +235,7 @@ public class FilterPath implements Path
 
       @Override
       public Path next() {
-        return new FilterPath(iterator.next(), fileSystem);
+        return wrap(iterator.next());
       }
 
       @Override
@@ -267,4 +267,10 @@ public class FilterPath implements Path
     }
     return path;
   }
+  
+  /** Override this to customize the return wrapped
+   *  path from various operations */
+  protected Path wrap(Path other) {
+    return new FilterPath(other, fileSystem);
+  }
 }

Modified: lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/util/LuceneTestCase.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/util/LuceneTestCase.java?rev=1646778&r1=1646777&r2=1646778&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/util/LuceneTestCase.java (original)
+++ lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/util/LuceneTestCase.java Fri Dec 19 15:44:30 2014
@@ -906,8 +906,6 @@ public abstract class LuceneTestCase ext
     if (r.nextBoolean()) {
       c.setMergeScheduler(new SerialMergeScheduler());
     } else if (rarely(r)) {
-      int maxThreadCount = TestUtil.nextInt(r, 1, 4);
-      int maxMergeCount = TestUtil.nextInt(r, maxThreadCount, maxThreadCount + 4);
       ConcurrentMergeScheduler cms;
       if (r.nextBoolean()) {
         cms = new ConcurrentMergeScheduler();
@@ -918,6 +916,8 @@ public abstract class LuceneTestCase ext
             }
           };
       }
+      int maxThreadCount = TestUtil.nextInt(r, 1, 4);
+      int maxMergeCount = TestUtil.nextInt(r, maxThreadCount, maxThreadCount + 4);
       cms.setMaxMergesAndThreads(maxMergeCount, maxThreadCount);
       c.setMergeScheduler(cms);
     }