Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
7.0
* Tolerate unsupported fsync only for kernel-verified directory descriptors, using host-resolved errno values; on AIX, whose jnr Errno table has no ENOTSUP constant, only EINVAL/EOPNOTSUPP are tolerated (CASSANDRA-14380)
* Allow CQLSSTableWriter to specify SSTable id generator to use (CASSANDRA-21012)
* Reject LIKE patterns with a wildcard (%) anywhere other than the start or end (CASSANDRA-21068)
* Support pluggable default role initialization (CASSANDRA-21546)
Expand Down
2 changes: 1 addition & 1 deletion src/java/org/apache/cassandra/db/lifecycle/LogReplica.java
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ void syncDirectory()
try
{
if (directoryDescriptor >= 0)
NativeLibrary.trySync(directoryDescriptor);
NativeLibrary.trySyncDirectory(directoryDescriptor, getDirectory());
}
catch (FSError e)
{
Expand Down
7 changes: 5 additions & 2 deletions src/java/org/apache/cassandra/hints/HintsCatalog.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.utils.NativeLibrary;
import org.apache.cassandra.utils.SyncUtil;
import org.apache.cassandra.utils.Throwables;

import static java.util.stream.Collectors.groupingBy;

Expand Down Expand Up @@ -170,8 +171,10 @@ void fsyncDirectory()
{
try
{
SyncUtil.trySync(fd);
NativeLibrary.tryCloseFD(fd);
Throwables.maybeFail(() -> {
if (!SyncUtil.SKIP_SYNC)
NativeLibrary.trySyncDirectory(fd, hintsDirectory.absolutePath());
}, () -> NativeLibrary.tryCloseFD(fd));
}
catch (FSError e) // trySync failed
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,7 @@ public static long readMarker(File file)

private static void trySyncJournalDirectory()
{
trySyncDirectory(getAccordJournalDirectory());
}

private static void trySyncDirectory(String path)
{
int fd = NativeLibrary.tryOpenDirectory(path);
NativeLibrary.trySync(fd);
NativeLibrary.trySyncDirectory(getAccordJournalDirectory());
}

public static File saveDirectory()
Expand Down
126 changes: 121 additions & 5 deletions src/java/org/apache/cassandra/utils/NativeLibrary.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.nio.channels.FileChannel;
import java.util.concurrent.TimeUnit;

import com.google.common.annotations.VisibleForTesting;
import com.sun.jna.LastErrorException;

import org.slf4j.Logger;
Expand All @@ -31,8 +32,13 @@
import org.apache.cassandra.io.FSWriteError;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileInputStreamPlus;
import org.apache.cassandra.io.util.FileUtils;

import jnr.constants.Constant;
import jnr.constants.ConstantSet;

import static org.apache.cassandra.config.CassandraRelevantProperties.IGNORE_MISSING_NATIVE_FILE_HINTS;
import static org.apache.cassandra.config.CassandraRelevantProperties.JAVA_IO_TMPDIR;
import static org.apache.cassandra.config.CassandraRelevantProperties.OS_ARCH;
import static org.apache.cassandra.config.CassandraRelevantProperties.OS_NAME;
import static org.apache.cassandra.utils.LocalizeString.toLowerCaseLocalized;
Expand Down Expand Up @@ -65,6 +71,8 @@ public enum OSType
private static final int F_NOCACHE = 48; /* Mac OS X specific flag, turns cache on/off */
private static final int O_DIRECT = 040000; /* fcntl.h */
private static final int O_RDONLY = 00000000; /* fcntl.h */
@VisibleForTesting
static final int O_DIRECTORY; /* fcntl.h; jnr-resolved value is confirmed against the kernel at class init, see verifyODirectory() */

private static final int POSIX_FADV_NORMAL = 0; /* fadvise.h */
private static final int POSIX_FADV_RANDOM = 1; /* fadvise.h */
Expand Down Expand Up @@ -103,6 +111,10 @@ public enum OSType
default: wrappedLibrary = new NativeLibraryLinux();
}

ConstantSet openFlags = ConstantSet.getConstantSet("OpenFlags");
Constant oDirectory = openFlags == null ? null : openFlags.getConstant("O_DIRECTORY");
O_DIRECTORY = verifyODirectory(isHostConstant(oDirectory) ? oDirectory.intValue() : 0);

if (toLowerCaseLocalized(OS_ARCH.getString()).contains("ppc"))
{
if (osType == LINUX)
Expand All @@ -128,6 +140,39 @@ else if (osType == AIX)
}
}

/**
* jnr-constants only maps a handful of architectures (aarch64, s390x, mips64el, loongarch64) to their
* per-arch OpenFlags tables; on others (e.g. ppc64le, arm32) a resolved value can silently be the wrong
* flag for this host. Do not trust the table: confirm it opens java.io.tmpdir and rejects a regular file
* with ENOTDIR before relying on it to gate directory-fsync tolerance.
*/
private static int verifyODirectory(int candidate)
{
if (candidate == 0)
return 0;
File probe = FileUtils.createDeletableTempFile("odirectory-probe", "tmp");
boolean verified;
try
{
wrappedLibrary.callClose(wrappedLibrary.callOpen(JAVA_IO_TMPDIR.getString(), O_RDONLY | candidate));
wrappedLibrary.callClose(wrappedLibrary.callOpen(probe.path(), O_RDONLY | candidate));
verified = false; // must reject a regular file with ENOTDIR; it did not
}
catch (RuntimeException | UnsatisfiedLinkError e)
{
ConstantSet errnos = ConstantSet.getConstantSet("Errno");
Constant enotdir = errnos == null ? null : errnos.getConstant("ENOTDIR");
verified = e instanceof LastErrorException && matchesErrno(enotdir, errno((LastErrorException) e));
}
finally
{
probe.tryDelete();
}
if (!verified)
logger.info("O_DIRECTORY capability probe failed; disabling directory fsync tolerance");
return verified ? candidate : 0;
}

private NativeLibrary() {}

/**
Expand Down Expand Up @@ -299,12 +344,17 @@ public static int tryFcntl(int fd, int command, int flags)
}

public static int tryOpenDirectory(String path)
{
return tryOpenDirectory(path, wrappedLibrary);
}

private static int tryOpenDirectory(String path, NativeLibraryWrapper library)
{
int fd = -1;

try
{
return wrappedLibrary.callOpen(path, O_RDONLY);
return library.callOpen(path, O_RDONLY | O_DIRECTORY);
}
catch (UnsatisfiedLinkError e)
{
Expand All @@ -316,20 +366,45 @@ public static int tryOpenDirectory(String path)
throw e;

if (REQUIRE)
logger.warn("open({}, O_RDONLY) failed, errno ({}).", path, errno(e));
logger.warn("openDirectory({}) failed, errno ({}).", path, errno(e));
}

return fd;
}

public static void trySync(int fd)
{
trySync(fd, null, wrappedLibrary);
}

/**
* Sync a descriptor opened for a directory, tolerating filesystems without directory fsync support.
* The caller retains ownership of the descriptor.
*/
public static void trySyncDirectory(int fd, String path)
{
trySync(fd, path, wrappedLibrary);
}

public static void trySyncDirectory(String path)
{
trySyncDirectory(path, wrappedLibrary);
}

static void trySyncDirectory(String path, NativeLibraryWrapper library)
{
int fd = tryOpenDirectory(path, library);
Throwables.maybeFail(() -> trySync(fd, path, library), () -> tryCloseFD(fd, library));
}

static void trySync(int fd, String directory, NativeLibraryWrapper library)
{
if (fd == -1)
return;

try
{
wrappedLibrary.callFsync(fd);
library.callFsync(fd);
}
catch (UnsatisfiedLinkError e)
{
Expand All @@ -340,23 +415,64 @@ public static void trySync(int fd)
if (!(e instanceof LastErrorException))
throw e;

int err = errno(e);
// Capability errors are safe to ignore only for a kernel-verified directory descriptor.
if (directory != null && isUnsupportedDirectorySync(err))
{
// Key the throttle on the directory so one unsupported mount cannot silence the others.
NoSpamLogger.log(logger, NoSpamLogger.Level.WARN, directory, 10, TimeUnit.MINUTES,
"Directory fsync on {} not supported by underlying filesystem, ignoring: errno ({})", directory, err);
return;
}

if (REQUIRE)
{
String errMsg = String.format("fsync(%s) failed, errno (%s) %s", fd, errno(e), e.getMessage());
String errMsg = String.format("fsync(%s) failed, errno (%s) %s", fd, err, e.getMessage());
logger.warn(errMsg);
throw new FSWriteError(e, errMsg);
}
}
}

private static boolean isUnsupportedDirectorySync(int error)
{
// Without a kernel-verified O_DIRECTORY the descriptor cannot be confirmed to be a directory,
// so capability errors are not tolerated.
if (O_DIRECTORY == 0)
return false;

ConstantSet errors = ConstantSet.getConstantSet("Errno");
// Missing host constants remain failures rather than using another platform's values.
return errors != null && (matchesErrno(errors.getConstant("EINVAL"), error)
|| matchesErrno(errors.getConstant("ENOTSUP"), error)
|| matchesErrno(errors.getConstant("EOPNOTSUPP"), error));
}

private static boolean matchesErrno(Constant expected, int actual)
{
return expected != null && expected.defined() && expected.intValue() == actual;
}

private static boolean isHostConstant(Constant constant)
{
// jnr's unknown-platform fallback reports defined() == true for synthetic, non-native OpenFlags
// values; used only to sanity-check the resolved O_DIRECTORY candidate before it is probed above.
return constant != null && constant.defined() && !(constant instanceof jnr.constants.platform.fake.OpenFlags);
}

public static void tryCloseFD(int fd)
{
tryCloseFD(fd, wrappedLibrary);
}

private static void tryCloseFD(int fd, NativeLibraryWrapper library)
{
if (fd == -1)
return;

try
{
wrappedLibrary.callClose(fd);
library.callClose(fd);
}
catch (UnsatisfiedLinkError e)
{
Expand Down
10 changes: 1 addition & 9 deletions src/java/org/apache/cassandra/utils/SyncUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,6 @@ public static void trySyncDir(File dir)
if (SKIP_SYNC)
return;

int directoryFD = NativeLibrary.tryOpenDirectory(dir.path());
try
{
trySync(directoryFD);
}
finally
{
NativeLibrary.tryCloseFD(directoryFD);
}
NativeLibrary.trySyncDirectory(dir.path());
}
}
Loading