diff --git a/CHANGES.txt b/CHANGES.txt index 9f946a3517..71ccc5823a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 3.0 + * Switch CommitLogSegment from RandomAccessFile to nio (CASSANDRA-8308) * Allow mixing token and partition key restrictions (CASSANDRA-7016) * Support index key/value entries on map collections (CASSANDRA-8473) * Modernize schema tables (CASSANDRA-8261) diff --git a/src/java/org/apache/cassandra/db/commitlog/CommitLogSegment.java b/src/java/org/apache/cassandra/db/commitlog/CommitLogSegment.java index 185f57a03b..3383f1e2cd 100644 --- a/src/java/org/apache/cassandra/db/commitlog/CommitLogSegment.java +++ b/src/java/org/apache/cassandra/db/commitlog/CommitLogSegment.java @@ -23,6 +23,7 @@ import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; +import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; @@ -104,7 +105,7 @@ public class CommitLogSegment public final long id; private final File logFile; - private final RandomAccessFile logFileAccessor; + private final FileChannel channel; private final int fd; private final MappedByteBuffer buffer; @@ -134,7 +135,6 @@ public class CommitLogSegment id = getNextId(); descriptor = new CommitLogDescriptor(id); logFile = new File(DatabaseDescriptor.getCommitLogLocation(), descriptor.fileName()); - boolean isCreating = true; try { @@ -147,25 +147,37 @@ public class CommitLogSegment logger.debug("Re-using discarded CommitLog segment for {} from {}", id, filePath); if (!oldFile.renameTo(logFile)) throw new IOException("Rename from " + filePath + " to " + id + " failed"); - isCreating = false; + } + else + { + logger.debug("Creating new CommitLog segment: " + logFile); } } - // Open the initial the segment file - logFileAccessor = new RandomAccessFile(logFile, "rw"); + // Extend or truncate the file size to the standard segment size as we may have restarted after a segment + // size configuration change, leaving "incorrectly" sized segments on disk. + // NOTE: while we're using RAF to allow extension of file on disk w/out sparse, we need to avoid using RAF + // for grabbing the FileChannel due to FILE_SHARE_DELETE flag bug on windows. + // See: https://bugs.openjdk.java.net/browse/JDK-6357433 and CASSANDRA-8308 + if (logFile.length() != DatabaseDescriptor.getCommitLogSegmentSize()) + { + try (RandomAccessFile raf = new RandomAccessFile(logFile, "rw")) + { + raf.setLength(DatabaseDescriptor.getCommitLogSegmentSize()); + } + catch (IOException e) + { + throw new FSWriteError(e, logFile); + } + } - if (isCreating) - logger.debug("Creating new commit log segment {}", logFile.getPath()); + channel = FileChannel.open(logFile.toPath(), StandardOpenOption.WRITE, StandardOpenOption.READ); - // Map the segment, extending or truncating it to the standard segment size. - // (We may have restarted after a segment size configuration change, leaving "incorrectly" - // sized segments on disk.) - logFileAccessor.setLength(DatabaseDescriptor.getCommitLogSegmentSize()); - fd = CLibrary.getfd(logFileAccessor.getFD()); + fd = CLibrary.getfd(channel); + buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, DatabaseDescriptor.getCommitLogSegmentSize()); - buffer = logFileAccessor.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, DatabaseDescriptor.getCommitLogSegmentSize()); - // write the header CommitLogDescriptor.writeHeader(buffer, descriptor); + // mark the initial sync marker as uninitialised buffer.putInt(CommitLogDescriptor.HEADER_SIZE, 0); buffer.putLong(CommitLogDescriptor.HEADER_SIZE + 4, 0); @@ -415,7 +427,7 @@ public class CommitLogSegment { if (FileUtils.isCleanerAvailable()) FileUtils.clean(buffer); - logFileAccessor.close(); + channel.close(); } catch (IOException e) { diff --git a/src/java/org/apache/cassandra/utils/CLibrary.java b/src/java/org/apache/cassandra/utils/CLibrary.java index b2595f6f0b..4dbd904fb0 100644 --- a/src/java/org/apache/cassandra/utils/CLibrary.java +++ b/src/java/org/apache/cassandra/utils/CLibrary.java @@ -20,6 +20,7 @@ package org.apache.cassandra.utils; import java.io.FileDescriptor; import java.io.RandomAccessFile; import java.lang.reflect.Field; +import java.nio.channels.FileChannel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -276,6 +277,24 @@ public final class CLibrary } } + public static int getfd(FileChannel channel) + { + Field field = FBUtilities.getProtectedField(channel.getClass(), "fd"); + + if (field == null) + return -1; + + try + { + return getfd((FileDescriptor)field.get(channel)); + } + catch (IllegalArgumentException|IllegalAccessException e) + { + logger.warn("Unable to read fd field from FileChannel"); + } + return -1; + } + /** * Get system file descriptor from FileDescriptor object. * @param descriptor - FileDescriptor objec to get fd from @@ -295,7 +314,7 @@ public final class CLibrary catch (Exception e) { JVMStabilityInspector.inspectThrowable(e); - logger.warn("unable to read fd field from FileDescriptor"); + logger.warn("Unable to read fd field from FileDescriptor"); } return -1; diff --git a/test/unit/org/apache/cassandra/SchemaLoader.java b/test/unit/org/apache/cassandra/SchemaLoader.java index 2fcfd55a93..2be4cc075f 100644 --- a/test/unit/org/apache/cassandra/SchemaLoader.java +++ b/test/unit/org/apache/cassandra/SchemaLoader.java @@ -419,6 +419,7 @@ public class SchemaLoader public static void cleanupAndLeaveDirs() { + CommitLog.instance.resetUnsafe(); // unmap CLS before attempting to delete or Windows complains mkdirs(); cleanup(); mkdirs(); @@ -434,7 +435,11 @@ public class SchemaLoader File dir = new File(dirName); if (!dir.exists()) throw new RuntimeException("No such directory: " + dir.getAbsolutePath()); - FileUtils.deleteRecursive(dir); + + // Leave the folder around as Windows will complain about directory deletion w/handles open to children files + String[] children = dir.list(); + for (String child : children) + FileUtils.deleteRecursive(new File(dir, child)); } cleanupSavedCaches(); @@ -445,7 +450,9 @@ public class SchemaLoader File dir = new File(dirName); if (!dir.exists()) throw new RuntimeException("No such directory: " + dir.getAbsolutePath()); - FileUtils.deleteRecursive(dir); + String[] children = dir.list(); + for (String child : children) + FileUtils.deleteRecursive(new File(dir, child)); } }