CASSANDRA-21511: Fix page cache eviction for ranges larger than 2 GiB

NativeLibrary.trySkipCache splits ranges larger than Integer.MAX_VALUE
before passing them to posix_fadvise, but incorrectly moved the offset
backward after each chunk. Advance the offset so all chunks cover
consecutive forward file ranges, and add deterministic unit coverage for
large, boundary, nonzero-offset, and whole-file ranges.
This commit is contained in:
Runtian Liu 2026-07-13 08:30:49 -07:00
parent 45f04ad9fa
commit b089b14ab5
2 changed files with 60 additions and 3 deletions

View File

@ -226,16 +226,27 @@ public final class NativeLibrary
}
public static void trySkipCache(int fd, long offset, long len, String path)
{
trySkipCache(offset, len, (subOffset, subLength) -> trySkipCache(fd, subOffset, subLength, path));
}
@FunctionalInterface
interface SkipCache
{
void skip(long offset, int len);
}
static void trySkipCache(long offset, long len, SkipCache skipCache)
{
if (len == 0)
trySkipCache(fd, 0, 0, path);
skipCache.skip(0, 0);
while (len > 0)
{
int sublen = (int) Math.min(Integer.MAX_VALUE, len);
trySkipCache(fd, offset, sublen, path);
skipCache.skip(offset, sublen);
len -= sublen;
offset -= sublen;
offset += sublen;
}
}

View File

@ -18,6 +18,8 @@
*/
package org.apache.cassandra.utils;
import java.util.ArrayList;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
@ -41,4 +43,48 @@ public class NativeLibraryTest
long pid = NativeLibrary.getProcessID();
Assert.assertTrue(pid > 0);
}
@Test
public void testSkipCacheSplitsLargeRangeAtIncreasingOffsets()
{
long offset = 1024;
long length = 2L * Integer.MAX_VALUE + 1;
List<long[]> ranges = new ArrayList<>();
NativeLibrary.trySkipCache(offset, length, (subOffset, subLength) -> ranges.add(new long[]{ subOffset, subLength }));
Assert.assertEquals(3, ranges.size());
assertRange(ranges.get(0), offset, Integer.MAX_VALUE);
assertRange(ranges.get(1), offset + Integer.MAX_VALUE, Integer.MAX_VALUE);
assertRange(ranges.get(2), offset + 2L * Integer.MAX_VALUE, 1);
}
@Test
public void testSkipCacheDoesNotSplitMaximumIntegerRange()
{
long offset = 4096;
List<long[]> ranges = new ArrayList<>();
NativeLibrary.trySkipCache(offset, Integer.MAX_VALUE, (subOffset, subLength) -> ranges.add(new long[]{ subOffset, subLength }));
Assert.assertEquals(1, ranges.size());
assertRange(ranges.get(0), offset, Integer.MAX_VALUE);
}
@Test
public void testSkipCacheZeroLengthTargetsEntireFile()
{
List<long[]> ranges = new ArrayList<>();
NativeLibrary.trySkipCache(4096, 0, (subOffset, subLength) -> ranges.add(new long[]{ subOffset, subLength }));
Assert.assertEquals(1, ranges.size());
assertRange(ranges.get(0), 0, 0);
}
private static void assertRange(long[] range, long offset, int length)
{
Assert.assertEquals(offset, range[0]);
Assert.assertEquals(length, range[1]);
}
}