You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by us...@apache.org on 2016/12/25 23:14:02 UTC

[1/2] lucene-solr:branch_6x: LUCENE-6989: Make MMapDirectory's unmap hack work with Java 9 EA (b150+): Unmapping uses new sun.misc.Unsafe#invokeCleaner(ByteBuffer).

Repository: lucene-solr
Updated Branches:
  refs/heads/branch_6x 5cda93afb -> ba5b81845


LUCENE-6989: Make MMapDirectory's unmap hack work with Java 9 EA (b150+): Unmapping uses new sun.misc.Unsafe#invokeCleaner(ByteBuffer).


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/1d92eed9
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/1d92eed9
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/1d92eed9

Branch: refs/heads/branch_6x
Commit: 1d92eed93fb6c33f174256a90fcf5ba779575255
Parents: 5cda93a
Author: Uwe Schindler <us...@apache.org>
Authored: Wed Dec 21 19:49:22 2016 +0100
Committer: Uwe Schindler <us...@apache.org>
Committed: Mon Dec 26 00:12:11 2016 +0100

----------------------------------------------------------------------
 lucene/CHANGES.txt                              |   7 ++
 .../org/apache/lucene/store/MMapDirectory.java  | 124 +++++++++++--------
 lucene/tools/junit4/tests.policy                |   1 -
 3 files changed, 78 insertions(+), 54 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/1d92eed9/lucene/CHANGES.txt
----------------------------------------------------------------------
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index e9a0b85..5054702 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -113,6 +113,13 @@ Improvements
   CorruptIndexException instead of the more confusing EOFException
   (Mike Drob via Mike McCandless)
 
+* LUCENE-6989: Make MMapDirectory's unmap hack work with Java 9 EA (b150+):
+  Unmapping uses new sun.misc.Unsafe#invokeCleaner(ByteBuffer).
+  Java 9 now needs same permissions like Java 8;
+  RuntimePermission("accessClassInPackage.jdk.internal.ref")
+  is no longer needed. Support for older Java 9 builds was removed.
+  (Uwe Schindler)
+
 Optimizations
 
 * LUCENE-7568: Optimize merging when index sorting is used but the

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/1d92eed9/lucene/core/src/java/org/apache/lucene/store/MMapDirectory.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/store/MMapDirectory.java b/lucene/core/src/java/org/apache/lucene/store/MMapDirectory.java
index be08a16..0487400 100644
--- a/lucene/core/src/java/org/apache/lucene/store/MMapDirectory.java
+++ b/lucene/core/src/java/org/apache/lucene/store/MMapDirectory.java
@@ -34,6 +34,7 @@ import java.util.Locale;
 import java.util.Objects;
 import java.util.concurrent.Future;
 import java.lang.invoke.MethodHandle;
+import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 
 import org.apache.lucene.store.ByteBufferGuard.BufferCleaner;
@@ -174,14 +175,13 @@ public class MMapDirectory extends FSDirectory {
    * is closed while another thread is still accessing it (SIGSEGV).
    * <p>To enable the hack, the following requirements need to be
    * fulfilled: The used JVM must be Oracle Java / OpenJDK 8
-   * <em>(preliminary support for Java 9 was added with Lucene 6)</em>.
+   * <em>(preliminary support for Java 9 EA build 150+ was added with Lucene 6.4)</em>.
    * In addition, the following permissions need to be granted
    * to {@code lucene-core.jar} in your
    * <a href="http://docs.oracle.com/javase/8/docs/technotes/guides/security/PolicyFiles.html">policy file</a>:
    * <ul>
    * <li>{@code permission java.lang.reflect.ReflectPermission "suppressAccessChecks";}</li>
    * <li>{@code permission java.lang.RuntimePermission "accessClassInPackage.sun.misc";}</li>
-   * <li>{@code permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.ref";}</li>
    * </ul>
    * @throws IllegalArgumentException if {@link #UNMAP_SUPPORTED}
    * is <code>false</code> and the workaround cannot be enabled.
@@ -335,64 +335,82 @@ public class MMapDirectory extends FSDirectory {
     }
   }
   
-  @SuppressForbidden(reason = "Needs access to private APIs in DirectBuffer and sun.misc.Cleaner to enable hack")
+  @SuppressForbidden(reason = "Needs access to private APIs in DirectBuffer, sun.misc.Cleaner, and sun.misc.Unsafe to enable hack")
   private static Object unmapHackImpl() {
     final Lookup lookup = lookup();
     try {
-      final Class<?> directBufferClass = Class.forName("java.nio.DirectByteBuffer");
-      
-      final Method m = directBufferClass.getMethod("cleaner");
-      m.setAccessible(true);
-      MethodHandle directBufferCleanerMethod = lookup.unreflect(m);
-      Class<?> cleanerClass = directBufferCleanerMethod.type().returnType();
-      
-      final MethodHandle cleanMethod;
-      if (Runnable.class.isAssignableFrom(cleanerClass)) {
-        // early Java 9 impl using Runnable (we do the security check early that the Runnable does at runtime):
-        final SecurityManager sm = System.getSecurityManager();
-        if (sm != null) {
-          sm.checkPackageAccess("jdk.internal.ref");
-        }
-        // cast return value of cleaner() to Runnable:
-        directBufferCleanerMethod = directBufferCleanerMethod.asType(directBufferCleanerMethod.type().changeReturnType(Runnable.class));
-        cleanerClass = Runnable.class;
-        // lookup run() method on the interface instead of Cleaner:
-        cleanMethod = lookup.findVirtual(cleanerClass, "run", methodType(void.class));
-      } else {
-        // can be either the old internal "sun.misc.Cleaner" or
-        // the new Java 9 "java.lang.ref.Cleaner$Cleanable":
-        cleanMethod = lookup.findVirtual(cleanerClass, "clean", methodType(void.class));
+      try {
+        // *** sun.misc.Unsafe unmapping (Java 9+) ***
+        final Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
+        // first check if Unsafe has the right method, otherwise we can give up
+        // without doing any security critical stuff:
+        final MethodHandle unmapper = lookup.findVirtual(unsafeClass, "invokeCleaner",
+            methodType(void.class, ByteBuffer.class));
+        // fetch the unsafe instance and bind it to the virtual MH:
+        final Field f = unsafeClass.getDeclaredField("theUnsafe");
+        f.setAccessible(true);
+        final Object theUnsafe = f.get(null);
+        return newBufferCleaner(ByteBuffer.class, unmapper.bindTo(theUnsafe));
+      } catch (SecurityException se) {
+        // rethrow to report errors correctly (we need to catch it here, as we also catch RuntimeException below!):
+        throw se;
+      } catch (ReflectiveOperationException | RuntimeException e) {
+        // *** sun.misc.Cleaner unmapping (Java 8) ***
+        final Class<?> directBufferClass = Class.forName("java.nio.DirectByteBuffer");
+        
+        final Method m = directBufferClass.getMethod("cleaner");
+        m.setAccessible(true);
+        final MethodHandle directBufferCleanerMethod = lookup.unreflect(m);
+        final Class<?> cleanerClass = directBufferCleanerMethod.type().returnType();
+        
+        /* "Compile" a MH that basically is equivalent to the following code:
+         * void unmapper(ByteBuffer byteBuffer) {
+         *   sun.misc.Cleaner cleaner = ((java.nio.DirectByteBuffer) byteBuffer).cleaner();
+         *   if (Objects.nonNull(cleaner)) {
+         *     cleaner.clean();
+         *   } else {
+         *     noop(cleaner); // the noop is needed because MethodHandles#guardWithTest always needs ELSE
+         *   }
+         * }
+         */
+        final MethodHandle cleanMethod = lookup.findVirtual(cleanerClass, "clean", methodType(void.class));
+        final MethodHandle nonNullTest = lookup.findStatic(Objects.class, "nonNull", methodType(boolean.class, Object.class))
+            .asType(methodType(boolean.class, cleanerClass));
+        final MethodHandle noop = dropArguments(constant(Void.class, null).asType(methodType(void.class)), 0, cleanerClass);
+        final MethodHandle unmapper = filterReturnValue(directBufferCleanerMethod, guardWithTest(nonNullTest, cleanMethod, noop))
+            .asType(methodType(void.class, ByteBuffer.class));
+        return newBufferCleaner(directBufferClass, unmapper);
       }
-      
-      final MethodHandle nonNullTest = lookup.findStatic(Objects.class, "nonNull", methodType(boolean.class, Object.class))
-          .asType(methodType(boolean.class, cleanerClass));
-      final MethodHandle noop = dropArguments(constant(Void.class, null).asType(methodType(void.class)), 0, cleanerClass);
-      final MethodHandle unmapper = filterReturnValue(directBufferCleanerMethod, guardWithTest(nonNullTest, cleanMethod, noop))
-          .asType(methodType(void.class, ByteBuffer.class));
-      
-      return (BufferCleaner) (String resourceDescription, ByteBuffer buffer) -> {
-        if (directBufferClass.isInstance(buffer)) {
-          final Throwable error = AccessController.doPrivileged((PrivilegedAction<Throwable>) () -> {
-            try {
-              unmapper.invokeExact(buffer);
-              return null;
-            } catch (Throwable t) {
-              return t;
-            }
-          });
-          if (error != null) {
-            throw new IOException("Unable to unmap the mapped buffer: " + resourceDescription, error);
-          }
-        }
-      };
-    } catch (SecurityException e) {
-      return "Unmapping is not supported, because not all required permissions are given to the Lucene JAR file: " + e +
-          " [Please grant at least the following permissions: RuntimePermission(\"accessClassInPackage.sun.misc\"), " +
-          "RuntimePermission(\"accessClassInPackage.jdk.internal.ref\"), and " +
-          "ReflectPermission(\"suppressAccessChecks\")]";
+    } catch (SecurityException se) {
+      return "Unmapping is not supported, because not all required permissions are given to the Lucene JAR file: " + se +
+          " [Please grant at least the following permissions: RuntimePermission(\"accessClassInPackage.sun.misc\") " +
+          " and ReflectPermission(\"suppressAccessChecks\")]";
     } catch (ReflectiveOperationException | RuntimeException e) {
       return "Unmapping is not supported on this platform, because internal Java APIs are not compatible to this Lucene version: " + e; 
     }
   }
   
+  private static BufferCleaner newBufferCleaner(final Class<?> unmappableBufferClass, final MethodHandle unmapper) {
+    assert Objects.equals(methodType(void.class, ByteBuffer.class), unmapper.type());
+    return (String resourceDescription, ByteBuffer buffer) -> {
+      if (!buffer.isDirect()) {
+        throw new IllegalArgumentException("unmapping only works with direct buffers");
+      }
+      if (!unmappableBufferClass.isInstance(buffer)) {
+        throw new IllegalArgumentException("buffer is not an instance of " + unmappableBufferClass.getName());
+      }
+      final Throwable error = AccessController.doPrivileged((PrivilegedAction<Throwable>) () -> {
+        try {
+          unmapper.invokeExact(buffer);
+          return null;
+        } catch (Throwable t) {
+          return t;
+        }
+      });
+      if (error != null) {
+        throw new IOException("Unable to unmap the mapped buffer: " + resourceDescription, error);
+      }
+    };
+  }
+  
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/1d92eed9/lucene/tools/junit4/tests.policy
----------------------------------------------------------------------
diff --git a/lucene/tools/junit4/tests.policy b/lucene/tools/junit4/tests.policy
index 2dde5c6..b351b17 100644
--- a/lucene/tools/junit4/tests.policy
+++ b/lucene/tools/junit4/tests.policy
@@ -63,7 +63,6 @@ grant {
   permission java.lang.RuntimePermission "createClassLoader";
   // needed to test unmap hack on platforms that support it
   permission java.lang.RuntimePermission "accessClassInPackage.sun.misc";
-  permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.ref";
   // needed by cyberneko usage by benchmarks on J9
   permission java.lang.RuntimePermission "accessClassInPackage.org.apache.xerces.util";
   // needed by jacoco to dump coverage


[2/2] lucene-solr:branch_6x: LUCENE-6989: Fix some tests that hardcode MMapDirectory (and also the FSDirectory randomizer), to only use MMapDirectory on Windows, if it supports unmapping. Otherwise tests will fail.

Posted by us...@apache.org.
LUCENE-6989: Fix some tests that hardcode MMapDirectory (and also the FSDirectory randomizer), to only use MMapDirectory on Windows, if it supports unmapping. Otherwise tests will fail.


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/ba5b8184
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/ba5b8184
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/ba5b8184

Branch: refs/heads/branch_6x
Commit: ba5b8184560b255d9bd10288735d2a2a951203c7
Parents: 1d92eed
Author: Uwe Schindler <us...@apache.org>
Authored: Wed Dec 21 23:07:50 2016 +0100
Committer: Uwe Schindler <us...@apache.org>
Committed: Mon Dec 26 00:12:12 2016 +0100

----------------------------------------------------------------------
 .../lucene/index/Test4GBStoredFields.java       |  2 ++
 .../apache/lucene/index/TestIndexWriter.java    |  3 ++-
 .../org/apache/lucene/store/TestDirectory.java  | 13 ++++++++-----
 .../apache/lucene/store/TestMmapDirectory.java  |  3 +--
 .../org/apache/lucene/store/TestMultiMMap.java  |  3 +--
 .../org/apache/lucene/util/fst/Test2BFST.java   |  2 ++
 .../index/BaseStoredFieldsFormatTestCase.java   |  2 ++
 .../org/apache/lucene/util/LuceneTestCase.java  | 20 +++++++++++++++++---
 8 files changed, 35 insertions(+), 13 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ba5b8184/lucene/core/src/test/org/apache/lucene/index/Test4GBStoredFields.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/index/Test4GBStoredFields.java b/lucene/core/src/test/org/apache/lucene/index/Test4GBStoredFields.java
index 7e173c8..2242a1e 100644
--- a/lucene/core/src/test/org/apache/lucene/index/Test4GBStoredFields.java
+++ b/lucene/core/src/test/org/apache/lucene/index/Test4GBStoredFields.java
@@ -41,6 +41,8 @@ public class Test4GBStoredFields extends LuceneTestCase {
 
   @Nightly
   public void test() throws Exception {
+    assumeWorkingMMapOnWindows();
+    
     MockDirectoryWrapper dir = new MockDirectoryWrapper(random(), new MMapDirectory(createTempDir("4GBStoredFields")));
     dir.setThrottling(MockDirectoryWrapper.Throttling.NEVER);
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ba5b8184/lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java b/lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java
index 1b639f6..20ea43c 100644
--- a/lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java
+++ b/lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java
@@ -1251,8 +1251,9 @@ public class TestIndexWriter extends LuceneTestCase {
 
 
   public void testDeleteUnusedFiles() throws Exception {
-
     assumeFalse("test relies on exact filenames", Codec.getDefault() instanceof SimpleTextCodec);
+    assumeWorkingMMapOnWindows();
+    
     for(int iter=0;iter<2;iter++) {
       // relies on windows semantics
       Path path = createTempDir();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ba5b8184/lucene/core/src/test/org/apache/lucene/store/TestDirectory.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/store/TestDirectory.java b/lucene/core/src/test/org/apache/lucene/store/TestDirectory.java
index 88f304b..5e4a593 100644
--- a/lucene/core/src/test/org/apache/lucene/store/TestDirectory.java
+++ b/lucene/core/src/test/org/apache/lucene/store/TestDirectory.java
@@ -20,6 +20,7 @@ package org.apache.lucene.store;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
@@ -37,11 +38,13 @@ public class TestDirectory extends LuceneTestCase {
       largeBuffer[i] = (byte) i; // automatically loops with modulo
     }
 
-    final FSDirectory[] dirs = new FSDirectory[] {
-      new SimpleFSDirectory(path),
-      new NIOFSDirectory(path),
-      new MMapDirectory(path)
-    };
+    final List<FSDirectory> dirs0 = new ArrayList<>();
+    dirs0.add(new SimpleFSDirectory(path));
+    dirs0.add(new NIOFSDirectory(path));
+    if (hasWorkingMMapOnWindows()) {
+      dirs0.add(new MMapDirectory(path));
+    }
+    final FSDirectory[] dirs = dirs0.stream().toArray(FSDirectory[]::new);
 
     for (int i=0; i<dirs.length; i++) {
       FSDirectory dir = dirs[i];

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ba5b8184/lucene/core/src/test/org/apache/lucene/store/TestMmapDirectory.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/store/TestMmapDirectory.java b/lucene/core/src/test/org/apache/lucene/store/TestMmapDirectory.java
index 098fd44..77b4ab4 100644
--- a/lucene/core/src/test/org/apache/lucene/store/TestMmapDirectory.java
+++ b/lucene/core/src/test/org/apache/lucene/store/TestMmapDirectory.java
@@ -39,8 +39,7 @@ public class TestMmapDirectory extends BaseDirectoryTestCase {
   @Override
   public void setUp() throws Exception {
     super.setUp();
-    assumeTrue("test requires a jre that supports unmapping: " + MMapDirectory.UNMAP_NOT_SUPPORTED_REASON,
-        MMapDirectory.UNMAP_SUPPORTED);
+    assumeTrue(MMapDirectory.UNMAP_NOT_SUPPORTED_REASON, MMapDirectory.UNMAP_SUPPORTED);
   }
   
   @Ignore("This test is for JVM testing purposes. There are no guarantees that it may not fail with SIGSEGV!")

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ba5b8184/lucene/core/src/test/org/apache/lucene/store/TestMultiMMap.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/store/TestMultiMMap.java b/lucene/core/src/test/org/apache/lucene/store/TestMultiMMap.java
index 76632cb..3a03ea9 100644
--- a/lucene/core/src/test/org/apache/lucene/store/TestMultiMMap.java
+++ b/lucene/core/src/test/org/apache/lucene/store/TestMultiMMap.java
@@ -46,8 +46,7 @@ public class TestMultiMMap extends BaseDirectoryTestCase {
   @Override
   public void setUp() throws Exception {
     super.setUp();
-    assumeTrue("test requires a jre that supports unmapping: " + MMapDirectory.UNMAP_NOT_SUPPORTED_REASON,
-        MMapDirectory.UNMAP_SUPPORTED);
+    assumeTrue(MMapDirectory.UNMAP_NOT_SUPPORTED_REASON, MMapDirectory.UNMAP_SUPPORTED);
   }
   
   public void testCloneSafety() throws Exception {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ba5b8184/lucene/core/src/test/org/apache/lucene/util/fst/Test2BFST.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/util/fst/Test2BFST.java b/lucene/core/src/test/org/apache/lucene/util/fst/Test2BFST.java
index bdec65c..c23743a 100644
--- a/lucene/core/src/test/org/apache/lucene/util/fst/Test2BFST.java
+++ b/lucene/core/src/test/org/apache/lucene/util/fst/Test2BFST.java
@@ -41,6 +41,8 @@ public class Test2BFST extends LuceneTestCase {
   private static long LIMIT = 3L*1024*1024*1024;
 
   public void test() throws Exception {
+    assumeWorkingMMapOnWindows();
+    
     int[] ints = new int[7];
     IntsRef input = new IntsRef(ints, 0, ints.length);
     long seed = random().nextLong();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ba5b8184/lucene/test-framework/src/java/org/apache/lucene/index/BaseStoredFieldsFormatTestCase.java
----------------------------------------------------------------------
diff --git a/lucene/test-framework/src/java/org/apache/lucene/index/BaseStoredFieldsFormatTestCase.java b/lucene/test-framework/src/java/org/apache/lucene/index/BaseStoredFieldsFormatTestCase.java
index 4ce5534..a914c10 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/index/BaseStoredFieldsFormatTestCase.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/index/BaseStoredFieldsFormatTestCase.java
@@ -662,6 +662,8 @@ public abstract class BaseStoredFieldsFormatTestCase extends BaseIndexFileFormat
 
   @Nightly
   public void testBigDocuments() throws IOException {
+    assumeWorkingMMapOnWindows();
+    
     // "big" as "much bigger than the chunk size"
     // for this test we force a FS dir
     // we can't just use newFSDirectory, because this test doesn't really index anything.

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ba5b8184/lucene/test-framework/src/java/org/apache/lucene/util/LuceneTestCase.java
----------------------------------------------------------------------
diff --git a/lucene/test-framework/src/java/org/apache/lucene/util/LuceneTestCase.java b/lucene/test-framework/src/java/org/apache/lucene/util/LuceneTestCase.java
index 98cd2a7..d643aa2 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/util/LuceneTestCase.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/util/LuceneTestCase.java
@@ -94,6 +94,7 @@ import org.apache.lucene.store.FSLockFactory;
 import org.apache.lucene.store.FlushInfo;
 import org.apache.lucene.store.IOContext;
 import org.apache.lucene.store.LockFactory;
+import org.apache.lucene.store.MMapDirectory;
 import org.apache.lucene.store.MergeInfo;
 import org.apache.lucene.store.MockDirectoryWrapper.Throttling;
 import org.apache.lucene.store.MockDirectoryWrapper;
@@ -455,11 +456,24 @@ public abstract class LuceneTestCase extends Assert {
     LEAVE_TEMPORARY = defaultValue;
   }
 
+  /** Returns true, if MMapDirectory supports unmapping on this platform (required for Windows), or if we are not on Windows. */
+  public static boolean hasWorkingMMapOnWindows() {
+    return !Constants.WINDOWS || MMapDirectory.UNMAP_SUPPORTED;
+  }
+  
+  /** Assumes that the current MMapDirectory implementation supports unmapping, so the test will not fail on Windows.
+   * @see #hasWorkingMMapOnWindows()
+   * */
+  public static void assumeWorkingMMapOnWindows() {
+    assumeTrue(MMapDirectory.UNMAP_NOT_SUPPORTED_REASON, hasWorkingMMapOnWindows());
+  }
+
   /** Filesystem-based {@link Directory} implementations. */
   private static final List<String> FS_DIRECTORIES = Arrays.asList(
     "SimpleFSDirectory",
     "NIOFSDirectory",
-    "MMapDirectory"
+    // SimpleFSDirectory as replacement for MMapDirectory if unmapping is not supported on Windows (to make randomization stable):
+    hasWorkingMMapOnWindows() ? "MMapDirectory" : "SimpleFSDirectory"
   );
 
   /** All {@link Directory} implementations. */
@@ -468,7 +482,7 @@ public abstract class LuceneTestCase extends Assert {
     CORE_DIRECTORIES = new ArrayList<>(FS_DIRECTORIES);
     CORE_DIRECTORIES.add("RAMDirectory");
   }
-
+  
   /** A {@link org.apache.lucene.search.QueryCachingPolicy} that randomly caches. */
   public static final QueryCachingPolicy MAYBE_CACHE_POLICY = new QueryCachingPolicy() {
 
@@ -852,7 +866,7 @@ public abstract class LuceneTestCase extends Assert {
   public static void assumeNoException(String msg, Exception e) {
     RandomizedTest.assumeNoException(msg, e);
   }
-
+  
   /**
    * Return <code>args</code> as a {@link Set} instance. The order of elements is not
    * preserved in iterators.