Skip to content

Catch FileSystemException when opening direct IO #128834

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,18 @@
@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);

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;
}
}

Expand Down Expand Up @@ -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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
}

Expand Down