diff --git a/server/src/internalClusterTest/java/org/elasticsearch/index/store/DirectIOIT.java b/server/src/internalClusterTest/java/org/elasticsearch/index/store/DirectIOIT.java index 02e17e3395760..600555320dc02 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/index/store/DirectIOIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/index/store/DirectIOIT.java @@ -43,6 +43,8 @@ @LuceneTestCase.SuppressCodecs("*") // only use our own codecs public class DirectIOIT extends ESIntegTestCase { + private static boolean SUPPORTED; + @BeforeClass public static void checkSupported() { assumeTrue("Direct IO is not enabled", ES818BinaryQuantizedVectorsFormat.USE_DIRECT_IO); @@ -50,8 +52,9 @@ public static void checkSupported() { Path path = createTempDir("directIOProbe"); try (Directory dir = open(path); IndexOutput out = dir.createOutput("out", IOContext.DEFAULT)) { out.writeString("test"); + SUPPORTED = true; } catch (IOException e) { - assumeNoException("test requires filesystem that supports Direct IO", e); + SUPPORTED = false; } } @@ -109,15 +112,21 @@ static void assertBBQIndexType(String type) { @TestLogging(value = "org.elasticsearch.index.store.FsDirectoryFactory:DEBUG", reason = "to capture trace logging for direct IO") public void testDirectIOUsed() { try (MockLog mockLog = MockLog.capture(FsDirectoryFactory.class)) { - // we're just looking for some evidence direct IO is used - mockLog.addExpectation( - new MockLog.PatternSeenEventExpectation( + // we're just looking for some evidence direct IO is used (or not) + MockLog.LoggingExpectation expectation = SUPPORTED + ? new MockLog.PatternSeenEventExpectation( "Direct IO used", FsDirectoryFactory.class.getCanonicalName(), Level.DEBUG, "Opening .*\\.vec with direct IO" ) - ); + : new MockLog.PatternSeenEventExpectation( + "Direct IO not used", + FsDirectoryFactory.class.getCanonicalName(), + Level.DEBUG, + "Could not open .*\\.vec with direct IO" + ); + mockLog.addExpectation(expectation); indexVectors(); diff --git a/server/src/main/java/org/elasticsearch/index/store/FsDirectoryFactory.java b/server/src/main/java/org/elasticsearch/index/store/FsDirectoryFactory.java index a5b35b64e7664..bcc09dbe65f9e 100644 --- a/server/src/main/java/org/elasticsearch/index/store/FsDirectoryFactory.java +++ b/server/src/main/java/org/elasticsearch/index/store/FsDirectoryFactory.java @@ -22,6 +22,7 @@ import org.apache.lucene.store.NativeFSLockFactory; import org.apache.lucene.store.ReadAdvice; import org.apache.lucene.store.SimpleFSLockFactory; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting.Property; import org.elasticsearch.common.util.FeatureFlag; @@ -36,6 +37,7 @@ import org.elasticsearch.plugins.IndexStorePlugin; import java.io.IOException; +import java.nio.file.FileSystemException; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashSet; @@ -157,22 +159,38 @@ protected boolean useDirectIO(String name, IOContext context, OptionalLong fileL @Override public IndexInput openInput(String name, IOContext context) throws IOException { + Throwable directIOException = null; if (directIODelegate != null && context.hints().contains(DirectIOHint.INSTANCE)) { ensureOpen(); ensureCanRead(name); - Log.debug("Opening {} with direct IO", name); - return directIODelegate.openInput(name, context); - } else if (useDelegate(name, context)) { - // we need to do these checks on the outer directory since the inner doesn't know about pending deletes - ensureOpen(); - ensureCanRead(name); - // we only use the mmap to open inputs. Everything else is managed by the NIOFSDirectory otherwise - // we might run into trouble with files that are pendingDelete in one directory but still - // listed in listAll() from the other. We on the other hand don't want to list files from both dirs - // and intersect for perf reasons. - return delegate.openInput(name, context); - } else { - return super.openInput(name, context); + try { + Log.debug("Opening {} with direct IO", name); + return directIODelegate.openInput(name, context); + } catch (FileSystemException e) { + Log.debug(() -> Strings.format("Could not open %s with direct IO", name), e); + directIOException = e; + // and fallthrough to normal opening below + } + } + + try { + if (useDelegate(name, context)) { + // we need to do these checks on the outer directory since the inner doesn't know about pending deletes + ensureOpen(); + ensureCanRead(name); + // we only use the mmap to open inputs. Everything else is managed by the NIOFSDirectory otherwise + // we might run into trouble with files that are pendingDelete in one directory but still + // listed in listAll() from the other. We on the other hand don't want to list files from both dirs + // and intersect for perf reasons. + return delegate.openInput(name, context); + } else { + return super.openInput(name, context); + } + } catch (Throwable t) { + if (directIOException != null) { + t.addSuppressed(directIOException); + } + throw t; } }