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
* Preserve offsets when splitting cache eviction requests larger than 2 GiB and when advising through EOF (CASSANDRA-21085)
* 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
15 changes: 12 additions & 3 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 Down Expand Up @@ -73,6 +74,11 @@ public enum OSType
private static final int POSIX_FADV_DONTNEED = 4; /* fadvise.h */
private static final int POSIX_FADV_NOREUSE = 5; /* fadvise.h */

// POSIX_FADV_DONTNEED only discards fully covered pages, so chunk on a boundary that is a multiple of
// every common page size (4K, 16K, 64K) instead of the unaligned Integer.MAX_VALUE.
@VisibleForTesting
static final int FADVISE_MAX_CHUNK = Integer.MAX_VALUE & ~((1 << 21) - 1);

private static final NativeLibraryWrapper wrappedLibrary;
private static boolean jnaLockable = false;

Expand Down Expand Up @@ -228,14 +234,17 @@ public static void trySkipCache(String path, long offset, long len)
public static void trySkipCache(int fd, long offset, long len, String path)
{
if (len == 0)
trySkipCache(fd, 0, 0, path);
{
trySkipCache(fd, offset, 0, path);
return;
}

while (len > 0)
{
int sublen = (int) Math.min(Integer.MAX_VALUE, len);
int sublen = (int) Math.min(FADVISE_MAX_CHUNK, len);
trySkipCache(fd, offset, sublen, path);
len -= sublen;
offset -= sublen;
offset += sublen;
}
}

Expand Down
71 changes: 71 additions & 0 deletions test/unit/org/apache/cassandra/utils/NativeLibraryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,25 @@
*/
package org.apache.cassandra.utils;

import java.util.ArrayList;
import java.util.List;

import org.jboss.byteman.contrib.bmunit.BMRule;
import org.jboss.byteman.contrib.bmunit.BMUnitRunner;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileUtils;

import static org.junit.Assume.assumeTrue;

@RunWith(BMUnitRunner.class)
public class NativeLibraryTest
{
private static final ThreadLocal<List<long[]>> advisedRanges = new ThreadLocal<>();

@Test
public void testSkipCache()
{
Expand All @@ -35,6 +45,67 @@ public void testSkipCache()
NativeLibrary.trySkipCache(file.path(), 0, 0);
}

@Test
@BMRule(name = "record large skip-cache ranges",
targetClass = "org.apache.cassandra.utils.NativeLibraryLinux",
targetMethod = "callPosixFadvise(int, long, int, int)",
targetLocation = "AT ENTRY",
condition = "org.apache.cassandra.utils.NativeLibraryTest.isRecording()",
action = "return org.apache.cassandra.utils.NativeLibraryTest.recordAdvice($2, $3)")
public void testSkipCacheLargeRange()
{
long offset = 4096;
// Chunks are aligned down to 2 MiB so no page straddles a boundary and is left in cache.
long chunk = NativeLibrary.FADVISE_MAX_CHUNK;
assertAdvisedRanges(offset, 2 * chunk + 17,
new long[][] { { offset, chunk },
{ offset + chunk, chunk },
{ offset + 2 * chunk, 17 } });
}

@Test
@BMRule(name = "record zero-length skip-cache range",
targetClass = "org.apache.cassandra.utils.NativeLibraryLinux",
targetMethod = "callPosixFadvise(int, long, int, int)",
targetLocation = "AT ENTRY",
condition = "org.apache.cassandra.utils.NativeLibraryTest.isRecording()",
action = "return org.apache.cassandra.utils.NativeLibraryTest.recordAdvice($2, $3)")
public void testSkipCacheZeroLength()
{
long offset = 4096;
assertAdvisedRanges(offset, 0L, new long[][] { { offset, 0 } });
}

private static void assertAdvisedRanges(long offset, long length, long[][] expected)
{
assumeTrue("Range cache advice is Linux-only", FBUtilities.isLinux);
List<long[]> actual = new ArrayList<>();
advisedRanges.set(actual);
try
{
// Record at the native wrapper, without allocating a file or issuing a syscall.
NativeLibrary.trySkipCache(Integer.MAX_VALUE, offset, length, "recorded-skip-cache");
Assert.assertEquals("Native advice call count", expected.length, actual.size());
for (int i = 0; i < expected.length; i++)
Assert.assertArrayEquals("Native advice range " + i, expected[i], actual.get(i));
}
finally
{
advisedRanges.remove();
}
}

public static boolean isRecording()
{
return advisedRanges.get() != null;
}

public static int recordAdvice(long offset, int length)
{
advisedRanges.get().add(new long[] { offset, length });
return 0;
}

@Test
public void getPid()
{
Expand Down