From 708e4891ba93cac4c74cad6ad723c5d541f731de Mon Sep 17 00:00:00 2001 From: David Stuebe Date: Sat, 9 Nov 2019 21:45:15 -0500 Subject: [PATCH 1/7] Add jni wrapper for system madvise to control page cache --- build.gradle | 97 ++++++++++++++++++- gradle/wrapper/gradle-wrapper.properties | 2 +- settings.gradle | 1 - .../uppend/AppendOnlyStoreBuilder.java | 18 +++- .../upserve/uppend/AppendStorePartition.java | 4 +- .../java/com/upserve/uppend/BlockedLongs.java | 8 ++ .../com/upserve/uppend/blobs/FilePage.java | 4 - .../com/upserve/uppend/blobs/NativeIO.java | 25 +++++ .../upserve/uppend/blobs/VirtualPageFile.java | 60 ++++++++---- .../uppend/blobs/VirtualPageFileIO.java | 1 + .../upserve/uppend/cli/CommandBenchmark.java | 6 +- .../c/com_upserve_uppend_blobs_NativeIO.c | 91 +++++++++++++++++ .../com_upserve_uppend_blobs_NativeIO.h | 21 ++++ .../com/upserve/uppend/cli/BenchmarkTest.java | 4 +- 14 files changed, 310 insertions(+), 32 deletions(-) create mode 100644 src/main/java/com/upserve/uppend/blobs/NativeIO.java create mode 100644 src/nativeIO/c/com_upserve_uppend_blobs_NativeIO.c create mode 100644 src/nativeIO/headers/com_upserve_uppend_blobs_NativeIO.h diff --git a/build.gradle b/build.gradle index c82ac942..8cc34cb3 100644 --- a/build.gradle +++ b/build.gradle @@ -29,9 +29,7 @@ if (new File('.git').exists() && (exec { version "unspecified" } - group 'com.upserve' - description = """Uppend: fast, append-only key-multivalue store""" // TODO unused-dependency is broken - claims all dependencies are unused! @@ -41,11 +39,95 @@ apply plugin: 'java' apply plugin: 'maven' apply plugin: 'jacoco' apply plugin: 'com.bmuschko.nexus' +apply plugin: 'c' jacoco { toolVersion = "0.8.2" // Fixed to resolve issue with JDK 11 in Gradle 4.X.Y } +def SYS_INCLUDE_DIR = "/usr/include" // this.properties['system.include.dir'] +def SYS_LOCAL_INCLUDE_DIR = '/usr/local/include' // this.properties['system.local.include.dir'] +def JNI_INCLUDE_DIR = "${System.properties['java.home']}/include" +def JNI_LIB_DIR = "${System.properties['java.home']}/lib" + +println "Using system include directory: " + SYS_INCLUDE_DIR +println "Using system local include directory: " + SYS_LOCAL_INCLUDE_DIR +println "Using JNI include directory: " + JNI_INCLUDE_DIR +println "Using JNI lib directory: " + JNI_LIB_DIR + +model { + platforms { + x64 { + architecture "x86_64" + } + } + + components { + nativeIO(NativeLibrarySpec) { + targetPlatform "x64" + // Using default path for source and headers + } + } + + toolChains { + gcc(Gcc) { + eachPlatform { + if (System.properties['os.name'].equals('Mac OS X')) { + cCompiler.withArguments { args -> + args << "-O2" + args << "-I" + SYS_INCLUDE_DIR + args << "-I" + SYS_LOCAL_INCLUDE_DIR + args << "-I" + JNI_INCLUDE_DIR + args << "-I" + JNI_INCLUDE_DIR + "/darwin" + } + linker.withArguments { args -> + args << "-O2" + } + } else { + path "/opt/rh/devtoolset-2/root/usr/bin/gcc" + cCompiler.withArguments { args -> + args << "-O2" + args << "-I" + SYS_INCLUDE_DIR + args << "-I" + SYS_LOCAL_INCLUDE_DIR + args << "-I" + JNI_INCLUDE_DIR + args << "-I" + JNI_INCLUDE_DIR + "/linux" + } + linker.withArguments { args -> + args << "-O2" + } + } + } + } + clang(Clang) { + eachPlatform { + if (System.properties['os.name'].equals('Mac OS X')) { + cCompiler.withArguments { args -> + args << "-O2" + args << "-I" + SYS_INCLUDE_DIR + args << "-I" + SYS_LOCAL_INCLUDE_DIR + args << "-I" + JNI_INCLUDE_DIR + args << "-I" + JNI_INCLUDE_DIR + "/darwin" + } + linker.withArguments { args -> + args << "-O2" + } + } else { + cCompiler.withArguments { args -> + args << "-O2" + args << "-I" + SYS_INCLUDE_DIR + args << "-I" + SYS_LOCAL_INCLUDE_DIR + args << "-I" + JNI_INCLUDE_DIR + args << "-I" + JNI_INCLUDE_DIR + "/linux" + } + linker.withArguments { args -> + args << "-O2" + } + } + } + } + } +} + sourceCompatibility = 1.9 targetCompatibility = 1.9 // Requires 1.9 or greater due to unsafe memory access in MappebByteBuffer - Oracle Incident Report 9119653 @@ -68,7 +150,9 @@ dependencies { } tasks.withType(JavaCompile) { - options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" << "-Werror" + + options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" << "-Werror" << "-h" << ("${buildDir}/../src/nativeIO/headers/" as String) + //options.verbose = true } sourceSets { @@ -89,7 +173,10 @@ processResources { } } +// TODO include the cross compiled nativeIO libs as a resource. Unpack and load from jar! task fatJar(type: Jar) { + dependsOn 'assembleDependentsNativeIO' + dependencies { compile 'org.apache.logging.log4j:log4j-core:2.8' compile 'org.apache.logging.log4j:log4j-slf4j-impl:2.8' @@ -103,6 +190,10 @@ task fatJar(type: Jar) { } tasks.withType(Test) { + dependsOn 'assembleDependentsNativeIO' + systemProperty "java.library.path", "${buildDir}/libs/nativeIO/shared/" + maxHeapSize = "2048m" + // From https://stackoverflow.com/a/36130467/2136991 testLogging { // set options for log level LIFECYCLE diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e2ea6fd6..5e14162f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/settings.gradle b/settings.gradle index c498e0be..7d17cac5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1 @@ rootProject.name = 'uppend' - diff --git a/src/main/java/com/upserve/uppend/AppendOnlyStoreBuilder.java b/src/main/java/com/upserve/uppend/AppendOnlyStoreBuilder.java index c3aa32c8..cd98adb6 100644 --- a/src/main/java/com/upserve/uppend/AppendOnlyStoreBuilder.java +++ b/src/main/java/com/upserve/uppend/AppendOnlyStoreBuilder.java @@ -5,14 +5,16 @@ public class AppendOnlyStoreBuilder extends FileStoreBuilder { // Blocked Longs Config Options public static final int DEFAULT_BLOBS_PER_BLOCK = 127; - private int blobsPerBlock = DEFAULT_BLOBS_PER_BLOCK; // Blob Cache Options public static final int DEFAULT_BLOB_PAGE_SIZE = 4 * 1024 * 1024; - private int blobPageSize = DEFAULT_BLOB_PAGE_SIZE; + + public static final boolean DEFAULT_CACHE_BUFFERS = true; // Defaults to madvise normal LRU like page cache behavior + private boolean cacheBuffers = DEFAULT_CACHE_BUFFERS; + private BlobStoreMetrics.Adders blobStoreMetricsAdders = new BlobStoreMetrics.Adders(); private BlockedLongMetrics.Adders blockedLongMetricsAdders = new BlockedLongMetrics.Adders(); @@ -28,6 +30,11 @@ public AppendOnlyStoreBuilder withBlobPageSize(int blobPageSize) { return this; } + public AppendOnlyStoreBuilder withCacheBuffers(boolean cacheBuffers) { + this.cacheBuffers = cacheBuffers; + return this; + } + public AppendOnlyStore build() { return build(false); } @@ -54,11 +61,18 @@ public int getBlobPageSize() { public BlockedLongMetrics.Adders getBlockedLongMetricsAdders() { return blockedLongMetricsAdders; } + public boolean getCacheBuffers() { + return cacheBuffers; + } + @Override public String toString() { return "AppendOnlyStoreBuilder{" + "blobsPerBlock=" + blobsPerBlock + ", blobPageSize=" + blobPageSize + + ", cacheBuffers=" + cacheBuffers + + ", blobStoreMetricsAdders=" + blobStoreMetricsAdders + + ", blockedLongMetricsAdders=" + blockedLongMetricsAdders + '}' + super.toString(); } } diff --git a/src/main/java/com/upserve/uppend/AppendStorePartition.java b/src/main/java/com/upserve/uppend/AppendStorePartition.java index 20658958..8b7c53f9 100644 --- a/src/main/java/com/upserve/uppend/AppendStorePartition.java +++ b/src/main/java/com/upserve/uppend/AppendStorePartition.java @@ -84,12 +84,14 @@ static AppendStorePartition openPartition(Path parentDir, String partition, bool builder.getBlockedLongMetricsAdders() ); + // Allow control of caching buffers only for large blob content VirtualPageFile blobs = new VirtualPageFile( blobsFile(partitionDir), builder.getLookupHashCount(), builder.getBlobPageSize(), builder.getTargetBufferSize(), - readOnly + readOnly, + builder.getCacheBuffers() ); VirtualPageFile metadata = new VirtualPageFile( metadataPath(partitionDir), diff --git a/src/main/java/com/upserve/uppend/BlockedLongs.java b/src/main/java/com/upserve/uppend/BlockedLongs.java index 43b4f4a0..8455e711 100644 --- a/src/main/java/com/upserve/uppend/BlockedLongs.java +++ b/src/main/java/com/upserve/uppend/BlockedLongs.java @@ -1,6 +1,7 @@ package com.upserve.uppend; import com.google.common.util.concurrent.Striped; +import com.upserve.uppend.blobs.NativeIO; import com.upserve.uppend.metrics.*; import org.slf4j.Logger; @@ -40,6 +41,7 @@ public class BlockedLongs implements AutoCloseable, Flushable { private final AtomicLong posMem; private final MappedByteBuffer appendCountBuf; + private final NativeIO nativeIO; private final AtomicInteger currentPage; private final boolean readOnly; @@ -61,6 +63,8 @@ public class BlockedLongs implements AutoCloseable, Flushable { this.readOnly = readOnly; this.blockedLongMetricsAdders = blockedLongMetricsAdders; + nativeIO = new NativeIO(); + Path dir = file.getParent(); try { Files.createDirectories(dir); @@ -100,6 +104,7 @@ public class BlockedLongs implements AutoCloseable, Flushable { try { posBuf = blocks.map(readOnly ? FileChannel.MapMode.READ_ONLY : FileChannel.MapMode.READ_WRITE, posBufPosition, 8); + nativeIO.madvise(posBuf, NativeIO.Advice.WillNeed); // Will include the first few blocks } catch (IOException e) { throw new UncheckedIOException("Unable to map pos buffer at in " + file, e); } @@ -134,6 +139,8 @@ else if (pos < HEADER_BYTES) { } initialAppendCount = appendCountBuf.getLong(0); + + posMem = new AtomicLong(pos); } @@ -504,6 +511,7 @@ private MappedByteBuffer ensurePage(int pageIndex) { try { FileChannel.MapMode mapMode = readOnly ? FileChannel.MapMode.READ_ONLY : FileChannel.MapMode.READ_WRITE; page = blocks.map(mapMode, pageStart, PAGE_SIZE); + // Could experiment with advise_random to reduce memory use or advise_willneed to hold more in page cache? } catch (IOException e) { throw new UncheckedIOException("unable to map page at page index " + pageIndex + " (" + pageStart + " + " + PAGE_SIZE + ") in " + file, e); } diff --git a/src/main/java/com/upserve/uppend/blobs/FilePage.java b/src/main/java/com/upserve/uppend/blobs/FilePage.java index 887227f3..48fe5275 100644 --- a/src/main/java/com/upserve/uppend/blobs/FilePage.java +++ b/src/main/java/com/upserve/uppend/blobs/FilePage.java @@ -4,13 +4,10 @@ import java.nio.ByteBuffer; import java.nio.channels.FileChannel; -import static java.lang.Integer.min; - /** * File backed implementation of Page */ public class FilePage implements Page { - private final FileChannel channel; private final int pageSize; private final long pageStart; @@ -25,7 +22,6 @@ public class FilePage implements Page { this.channel = channel; this.pageStart = pageStart; this.pageSize = pageSize; - } @Override diff --git a/src/main/java/com/upserve/uppend/blobs/NativeIO.java b/src/main/java/com/upserve/uppend/blobs/NativeIO.java new file mode 100644 index 00000000..ee168589 --- /dev/null +++ b/src/main/java/com/upserve/uppend/blobs/NativeIO.java @@ -0,0 +1,25 @@ +package com.upserve.uppend.blobs; + +import org.slf4j.Logger; +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.nio.MappedByteBuffer; + +public class NativeIO { + private static final Logger log = org.slf4j.LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + public enum Advice { + Normal(0), Sequential(1), Random(2), WillNeed(3), DontNeed(4); + private final int value; + Advice(int val) { + this.value = val; + } + } + + public native void madvise(MappedByteBuffer buffer, Advice advise) throws IOException; + + static { + log.info("loading nativeIO libbrary"); + System.loadLibrary("nativeIO"); + } +} diff --git a/src/main/java/com/upserve/uppend/blobs/VirtualPageFile.java b/src/main/java/com/upserve/uppend/blobs/VirtualPageFile.java index 0649cc19..403e5b36 100644 --- a/src/main/java/com/upserve/uppend/blobs/VirtualPageFile.java +++ b/src/main/java/com/upserve/uppend/blobs/VirtualPageFile.java @@ -64,19 +64,21 @@ public class VirtualPageFile implements Closeable { final Path filePath; private final FileChannel channel; + private final NativeIO nativeIO; - private final LongBuffer headerBlockLocations; + private final MappedByteBuffer headerBlockLocations; private final MappedByteBuffer headerBuffer; private final AtomicLong nextPagePosition; private final boolean readOnly; + private final boolean cacheBuffers; private final AtomicLong[] virtualFilePositions; // the current position in the virtual file for each virtual file private final AtomicInteger[] virtualFilePageCounts; // the number of pages currently allocated for each virtual file private final LongAdder pageAllocationCount; - private final LongBuffer[] pageTables; // Array of Index-able list of page start locations for each virtual file + private final MappedByteBuffer[] pageTables; // Array of Index-able list of page start locations for each virtual file private final int virtualFiles; private final int pageSize; @@ -95,8 +97,20 @@ public Path getFilePath() { @Override public void close() throws IOException { if (!channel.isOpen()) return; + + for (MappedByteBuffer buf: mappedByteBuffers){ + if (Objects.nonNull(buf)) nativeIO.madvise(buf, NativeIO.Advice.DontNeed); + } Arrays.fill(mappedByteBuffers, null); + for (MappedByteBuffer buf: pageTables) { + if (Objects.nonNull(buf)) nativeIO.madvise(buf, NativeIO.Advice.DontNeed); + } + Arrays.fill(pageTables, null); + + nativeIO.madvise(headerBuffer, NativeIO.Advice.DontNeed); + nativeIO.madvise(headerBlockLocations, NativeIO.Advice.DontNeed); + if (!readOnly) { channel.truncate(nextPagePosition.get()); } @@ -218,7 +232,8 @@ Page getOrCreatePage(int virtualFileNumber, int pageNumber) { startPosition = allocatePosition(virtualFileNumber, pageNumber); } - return filePage(startPosition); + // Laptop benchmark 2019-11-09 shows using mapped pages for writing is faster. Confirm in production env. + return mappedPage(startPosition); } /** @@ -256,12 +271,18 @@ long getFileSize(){ } public VirtualPageFile(Path filePath, int virtualFiles, int pageSize, int targetBufferSize, boolean readOnly) { + this(filePath, virtualFiles, pageSize, targetBufferSize, readOnly, true); + } + + public VirtualPageFile(Path filePath, int virtualFiles, int pageSize, int targetBufferSize, boolean readOnly, boolean cacheBuffers) { this.filePath = filePath; this.readOnly = readOnly; this.virtualFiles = virtualFiles; this.pageSize = pageSize; + this.cacheBuffers = cacheBuffers; this.mappedByteBuffers = new MappedByteBuffer[MAX_BUFFERS]; + this.nativeIO = new NativeIO(); if (targetBufferSize < (pageSize)) throw new IllegalArgumentException("Target buffer size " + targetBufferSize + " must be larger than a page " + pageSize); @@ -292,8 +313,10 @@ public VirtualPageFile(Path filePath, int virtualFiles, int pageSize, int target final long initialSize; try { initialSize = channel.size(); - headerBlockLocations = channel.map(mapMode, SELF_DESCRIBING_HEADER_SIZE, PAGE_TABLE_BLOCK_LOCATION_HEADER_SIZE).asLongBuffer(); + headerBlockLocations = channel.map(mapMode, SELF_DESCRIBING_HEADER_SIZE, PAGE_TABLE_BLOCK_LOCATION_HEADER_SIZE); + nativeIO.madvise(headerBlockLocations, NativeIO.Advice.WillNeed); + LongBuffer longHeaderBlockLocations = headerBlockLocations.asLongBuffer(); ByteBuffer intBuffer = LOCAL_INT_BUFFER.get(); if (!readOnly && initialSize == 0) { intBuffer.putInt(virtualFiles); @@ -302,7 +325,7 @@ public VirtualPageFile(Path filePath, int virtualFiles, int pageSize, int target intBuffer.flip().putInt(pageSize); channel.write(intBuffer.flip(), 4); - headerBlockLocations.put(0, SELF_DESCRIBING_HEADER_SIZE + PAGE_TABLE_BLOCK_LOCATION_HEADER_SIZE + headerSize); + longHeaderBlockLocations.put(0, SELF_DESCRIBING_HEADER_SIZE + PAGE_TABLE_BLOCK_LOCATION_HEADER_SIZE + headerSize); } else { channel.read(intBuffer, 0); int val = intBuffer.flip().getInt(); @@ -314,7 +337,7 @@ public VirtualPageFile(Path filePath, int virtualFiles, int pageSize, int target if (val != virtualFiles) throw new IllegalArgumentException("The specfied page size " + pageSize + " does not match the value in the datastore " + val + " in file " + getFilePath()); - long longVal = headerBlockLocations.get(0); + long longVal = longHeaderBlockLocations.get(0); if (longVal != SELF_DESCRIBING_HEADER_SIZE + PAGE_TABLE_BLOCK_LOCATION_HEADER_SIZE + headerSize) throw new IllegalArgumentException("The header sizes " + (SELF_DESCRIBING_HEADER_SIZE + PAGE_TABLE_BLOCK_LOCATION_HEADER_SIZE + headerSize) + " does not match the value in the datastore " + longVal + " in file " + getFilePath()); } @@ -326,6 +349,7 @@ public VirtualPageFile(Path filePath, int virtualFiles, int pageSize, int target try { headerBuffer = channel.map(mapMode, SELF_DESCRIBING_HEADER_SIZE + PAGE_TABLE_BLOCK_LOCATION_HEADER_SIZE, headerSize); + nativeIO.madvise(headerBuffer, NativeIO.Advice.WillNeed); } catch (IOException e) { throw new UncheckedIOException("unable to map header for path: " + filePath, e); } @@ -346,11 +370,10 @@ public VirtualPageFile(Path filePath, int virtualFiles, int pageSize, int target pageAllocationCount = new LongAdder(); pageAllocationCount.add(Arrays.stream(virtualFilePageCounts).mapToLong(AtomicInteger::get).sum()); - pageTables = new LongBuffer[MAX_PAGE_TABLE_BLOCKS]; + pageTables = new MappedByteBuffer[MAX_PAGE_TABLE_BLOCKS]; try { - pageTables[0] = channel - .map(mapMode, headerSize + SELF_DESCRIBING_HEADER_SIZE + PAGE_TABLE_BLOCK_LOCATION_HEADER_SIZE, tableSize) - .asLongBuffer(); + pageTables[0] = channel.map(mapMode, headerSize + SELF_DESCRIBING_HEADER_SIZE + PAGE_TABLE_BLOCK_LOCATION_HEADER_SIZE, tableSize); + nativeIO.madvise(pageTables[0], NativeIO.Advice.WillNeed); } catch (IOException e) { throw new UncheckedIOException("unable to map page locations for path: " + filePath, e); } @@ -470,7 +493,8 @@ private MappedByteBuffer ensureBuffered(int bufferIndex) { if (buffer == null) { long bufferStart = ((long) bufferIndex * bufferSize) + totalHeaderSize; try { - buffer = channel.map(FileChannel.MapMode.READ_ONLY, bufferStart, bufferSize); + buffer = channel.map(mapMode, bufferStart, bufferSize); + if (!cacheBuffers) nativeIO.madvise(buffer, NativeIO.Advice.Random); } catch (IOException e) { throw new UncheckedIOException("Unable to map buffer for index " + bufferIndex + " at (" + bufferStart + " start position) in file " + filePath, e); } @@ -482,13 +506,13 @@ private MappedByteBuffer ensureBuffered(int bufferIndex) { } private LongBuffer ensurePageTable(int pageNumber) { - LongBuffer buffer = pageTables[pageNumber]; + MappedByteBuffer buffer = pageTables[pageNumber]; if (buffer == null) { synchronized (pageTables) { buffer = pageTables[pageNumber]; if (buffer == null) { - long bufferStart = headerBlockLocations.get(pageNumber); + long bufferStart = headerBlockLocations.asLongBuffer().get(pageNumber); // All allocated space must be in multiples of pageSize to guarantee a buffer will not end in the middle of a page final int apparentSize; @@ -500,10 +524,11 @@ private LongBuffer ensurePageTable(int pageNumber) { if (!readOnly && bufferStart == 0) { bufferStart = nextPagePosition.getAndAdd(apparentSize); - headerBlockLocations.put(pageNumber, bufferStart); + headerBlockLocations.asLongBuffer().put(pageNumber, bufferStart); } try { - buffer = channel.map(mapMode, bufferStart, tableSize).asLongBuffer(); + buffer = channel.map(mapMode, bufferStart, tableSize); + nativeIO.madvise(buffer, NativeIO.Advice.WillNeed); } catch (IOException e) { throw new UncheckedIOException("Unable to map buffer for page table " + pageNumber + " at (" + bufferStart + " start position) in file " + filePath, e); } @@ -511,7 +536,7 @@ private LongBuffer ensurePageTable(int pageNumber) { } } } - return buffer; + return buffer.asLongBuffer(); } // Called during initialize only - no need to synchronize @@ -522,7 +547,8 @@ private void preloadBuffers(long nextPagePosition){ if (bufferStart >= nextPagePosition) break; try { - MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, bufferStart, bufferSize); + MappedByteBuffer buffer = channel.map(mapMode, bufferStart, bufferSize); + if (!cacheBuffers) nativeIO.madvise(buffer, NativeIO.Advice.Random); mappedByteBuffers[bufferIndex] = buffer; } catch (IOException e) { throw new UncheckedIOException("Unable to preload mapped buffer for index " + bufferIndex + " at (" + bufferStart + " start position) in file " + filePath, e); diff --git a/src/main/java/com/upserve/uppend/blobs/VirtualPageFileIO.java b/src/main/java/com/upserve/uppend/blobs/VirtualPageFileIO.java index 9f7e961f..1b2622d1 100644 --- a/src/main/java/com/upserve/uppend/blobs/VirtualPageFileIO.java +++ b/src/main/java/com/upserve/uppend/blobs/VirtualPageFileIO.java @@ -4,6 +4,7 @@ import com.upserve.uppend.util.ThreadLocalByteBuffers; import org.slf4j.Logger; +import java.io.*; import java.lang.invoke.MethodHandles; import java.nio.ByteBuffer; import java.util.function.Supplier; diff --git a/src/main/java/com/upserve/uppend/cli/CommandBenchmark.java b/src/main/java/com/upserve/uppend/cli/CommandBenchmark.java index 53a64489..f3cec0aa 100644 --- a/src/main/java/com/upserve/uppend/cli/CommandBenchmark.java +++ b/src/main/java/com/upserve/uppend/cli/CommandBenchmark.java @@ -44,6 +44,9 @@ public class CommandBenchmark implements Callable { @Option(names = {"-b", "--buffer-size"}, description = "Buffer Size (small|medium|large)") BufferSize bufferSize = BufferSize.medium; + @Option(names = {"-k", "--keep-buffer-cache"}, description = "Keep page cache buffers for blobs") + boolean keepBufferCache = false; // Default is use madvise random! Use True for madvise normal (LRU like behavior). + @SuppressWarnings("unused") @Option(names = "--help", usageHelp = true, description = "Print usage") boolean help; @@ -135,7 +138,8 @@ private Benchmark createBenchmark() { //.withMetadataTTL(30) // To run with a metadata expiration to force reload of new keys .withFlushThreshold(flushThreshold) .withFlushDelaySeconds(flushDelay) - .withStoreMetrics(metrics); + .withStoreMetrics(metrics) + .withCacheBuffers(keepBufferCache); return new Benchmark(mode, builder, keys, count); } diff --git a/src/nativeIO/c/com_upserve_uppend_blobs_NativeIO.c b/src/nativeIO/c/com_upserve_uppend_blobs_NativeIO.c new file mode 100644 index 00000000..6a8ad076 --- /dev/null +++ b/src/nativeIO/c/com_upserve_uppend_blobs_NativeIO.c @@ -0,0 +1,91 @@ +#include "com_upserve_uppend_blobs_NativeIO.h" +#include +#include // posix_madvise, madvise +#include // errno +#include // strerror + +// TODO add Lucene license and changes +// https://github.com/apache/lucene-solr/tree/master/lucene/misc/src/java/org/apache/lucene/store + +/* + * Class: com_upserve_uppend_blobs_NativeIO + * Method: madvise + * Signature: (Ljava/nio/MappedByteBuffer;Lcom/upserve/uppend/blobs/NativeIO/Advice;)V + */ +JNIEXPORT void JNICALL Java_com_upserve_uppend_blobs_NativeIO_madvise (JNIEnv *env, jclass _ignore, jobject buffer, jobject advice) { + char exBuffer[80]; + jclass class_npe = (*env)->FindClass(env, "java/lang/NullPointerException"); + jclass class_ioex = (*env)->FindClass(env, "java/io/IOException"); + jclass class_argEx = (*env)->FindClass(env, "java/lang/IllegalArgumentException"); + + if ((*env)->IsSameObject(env, buffer, NULL)) { + (*env)->ThrowNew(env, class_npe, "buffer argument can not be null"); + return; + } + + if ((*env)->IsSameObject(env, advice, NULL)) { + (*env)->ThrowNew(env, class_npe, "advice argument can not be null"); + return; + } + + // Parse the advice and lookup POSIX value (See man page for madvise) + jclass adviceClass = (*env)->GetObjectClass(env, advice); + jfieldID fidNumber = (*env)->GetFieldID(env, adviceClass, "value", "I"); + jint intAdvice = (*env)->GetIntField(env, advice, fidNumber); + + int osAdvice; + switch(intAdvice) { + case 0: + osAdvice = MADV_NORMAL; + break; + case 1: + osAdvice = POSIX_MADV_SEQUENTIAL; + break; + case 2: + osAdvice = POSIX_MADV_RANDOM; + break; + case 3: + osAdvice = POSIX_MADV_WILLNEED; + break; + case 4: + osAdvice = POSIX_MADV_DONTNEED; + break; + default: + sprintf(exBuffer, "invalid advice value: '%d'", intAdvice); + (*env)->ThrowNew(env, class_argEx, exBuffer); + return ; + } + + void *p = (*env)->GetDirectBufferAddress(env, buffer); + if (p == NULL) { + (*env)->ThrowNew(env, class_ioex, strerror(errno)); + return; + } + + size_t size = (size_t) (*env)->GetDirectBufferCapacity(env, buffer); + if (size <= 0) { + (*env)->ThrowNew(env, class_ioex, strerror(errno)); + return; + } + + int page = getpagesize(); + + // round start down to start of page + long long start = (long long) p; + start = start & (~(page-1)); + + // round end up to start of page + long long end = start + size; + end = (end + page-1)&(~(page-1)); + size = (end-start); + + //printf("\nDO madvise: page=%d p=0x%lx 0x%lx size=0x%lx\n", page, p, start, size); + + int result = madvise((void *) start, size, osAdvice); + if (result != 0) { + sprintf(exBuffer, "system madvice call failed: '%d'", result); + (*env)->ThrowNew(env, class_ioex, exBuffer); + return ; + } + return; +} diff --git a/src/nativeIO/headers/com_upserve_uppend_blobs_NativeIO.h b/src/nativeIO/headers/com_upserve_uppend_blobs_NativeIO.h new file mode 100644 index 00000000..6a774a96 --- /dev/null +++ b/src/nativeIO/headers/com_upserve_uppend_blobs_NativeIO.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_upserve_uppend_blobs_NativeIO */ + +#ifndef _Included_com_upserve_uppend_blobs_NativeIO +#define _Included_com_upserve_uppend_blobs_NativeIO +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_upserve_uppend_blobs_NativeIO + * Method: madvise + * Signature: (Ljava/nio/MappedByteBuffer;Lcom/upserve/uppend/blobs/NativeIO/Advice;)V + */ +JNIEXPORT void JNICALL Java_com_upserve_uppend_blobs_NativeIO_madvise + (JNIEnv *, jobject, jobject, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/test/java/com/upserve/uppend/cli/BenchmarkTest.java b/src/test/java/com/upserve/uppend/cli/BenchmarkTest.java index 60ea2bef..69d51f48 100644 --- a/src/test/java/com/upserve/uppend/cli/BenchmarkTest.java +++ b/src/test/java/com/upserve/uppend/cli/BenchmarkTest.java @@ -30,7 +30,7 @@ public void tearDownDir() throws IOException { @Test public void tesUsage() { commandLine.execute("--help"); - assertStdOutContains("Usage: uppend benchmark [--help] [-b=] [-c=]"); + assertStdOutContains("Usage: uppend benchmark [-k] [--help] [-b=] [-c=]"); assertStdOutContains("[-m=] [-s=] "); } @@ -48,7 +48,7 @@ public void testBadArgument() { @Test public void testBenchmark() { - commandLine.execute("-s", "small", "-b", "small", "build/test/cli/bench"); + commandLine.execute("-s", "small", "-b", "small", "-k", "build/test/cli/bench"); assertEquals(1000000L, commandBenchmark.benchmark.writerStats().getCount()); } From edfb2efa578b8c9bed8d235d95033b9eb432f906 Mon Sep 17 00:00:00 2001 From: David Stuebe Date: Fri, 15 Nov 2019 14:07:25 -0500 Subject: [PATCH 2/7] Make madvice static. Include unistd.h. Remove redudent returns. --- build.gradle | 1 - .../java/com/upserve/uppend/BlockedLongs.java | 5 +--- .../com/upserve/uppend/blobs/NativeIO.java | 2 +- .../upserve/uppend/blobs/VirtualPageFile.java | 23 ++++++++----------- .../c/com_upserve_uppend_blobs_NativeIO.c | 3 +-- .../com_upserve_uppend_blobs_NativeIO.h | 2 +- 6 files changed, 14 insertions(+), 22 deletions(-) diff --git a/build.gradle b/build.gradle index 8cc34cb3..a015bd50 100644 --- a/build.gradle +++ b/build.gradle @@ -84,7 +84,6 @@ model { args << "-O2" } } else { - path "/opt/rh/devtoolset-2/root/usr/bin/gcc" cCompiler.withArguments { args -> args << "-O2" args << "-I" + SYS_INCLUDE_DIR diff --git a/src/main/java/com/upserve/uppend/BlockedLongs.java b/src/main/java/com/upserve/uppend/BlockedLongs.java index 8455e711..707c5ada 100644 --- a/src/main/java/com/upserve/uppend/BlockedLongs.java +++ b/src/main/java/com/upserve/uppend/BlockedLongs.java @@ -41,7 +41,6 @@ public class BlockedLongs implements AutoCloseable, Flushable { private final AtomicLong posMem; private final MappedByteBuffer appendCountBuf; - private final NativeIO nativeIO; private final AtomicInteger currentPage; private final boolean readOnly; @@ -63,8 +62,6 @@ public class BlockedLongs implements AutoCloseable, Flushable { this.readOnly = readOnly; this.blockedLongMetricsAdders = blockedLongMetricsAdders; - nativeIO = new NativeIO(); - Path dir = file.getParent(); try { Files.createDirectories(dir); @@ -104,7 +101,7 @@ public class BlockedLongs implements AutoCloseable, Flushable { try { posBuf = blocks.map(readOnly ? FileChannel.MapMode.READ_ONLY : FileChannel.MapMode.READ_WRITE, posBufPosition, 8); - nativeIO.madvise(posBuf, NativeIO.Advice.WillNeed); // Will include the first few blocks + NativeIO.madvise(posBuf, NativeIO.Advice.WillNeed); // Will include the first few blocks } catch (IOException e) { throw new UncheckedIOException("Unable to map pos buffer at in " + file, e); } diff --git a/src/main/java/com/upserve/uppend/blobs/NativeIO.java b/src/main/java/com/upserve/uppend/blobs/NativeIO.java index ee168589..14d08410 100644 --- a/src/main/java/com/upserve/uppend/blobs/NativeIO.java +++ b/src/main/java/com/upserve/uppend/blobs/NativeIO.java @@ -16,7 +16,7 @@ public enum Advice { } } - public native void madvise(MappedByteBuffer buffer, Advice advise) throws IOException; + public static native void madvise(MappedByteBuffer buffer, Advice advise) throws IOException; static { log.info("loading nativeIO libbrary"); diff --git a/src/main/java/com/upserve/uppend/blobs/VirtualPageFile.java b/src/main/java/com/upserve/uppend/blobs/VirtualPageFile.java index 403e5b36..820cc7e9 100644 --- a/src/main/java/com/upserve/uppend/blobs/VirtualPageFile.java +++ b/src/main/java/com/upserve/uppend/blobs/VirtualPageFile.java @@ -64,8 +64,6 @@ public class VirtualPageFile implements Closeable { final Path filePath; private final FileChannel channel; - private final NativeIO nativeIO; - private final MappedByteBuffer headerBlockLocations; private final MappedByteBuffer headerBuffer; @@ -99,17 +97,17 @@ public void close() throws IOException { if (!channel.isOpen()) return; for (MappedByteBuffer buf: mappedByteBuffers){ - if (Objects.nonNull(buf)) nativeIO.madvise(buf, NativeIO.Advice.DontNeed); + if (Objects.nonNull(buf)) NativeIO.madvise(buf, NativeIO.Advice.DontNeed); } Arrays.fill(mappedByteBuffers, null); for (MappedByteBuffer buf: pageTables) { - if (Objects.nonNull(buf)) nativeIO.madvise(buf, NativeIO.Advice.DontNeed); + if (Objects.nonNull(buf)) NativeIO.madvise(buf, NativeIO.Advice.DontNeed); } Arrays.fill(pageTables, null); - nativeIO.madvise(headerBuffer, NativeIO.Advice.DontNeed); - nativeIO.madvise(headerBlockLocations, NativeIO.Advice.DontNeed); + NativeIO.madvise(headerBuffer, NativeIO.Advice.DontNeed); + NativeIO.madvise(headerBlockLocations, NativeIO.Advice.DontNeed); if (!readOnly) { channel.truncate(nextPagePosition.get()); @@ -282,7 +280,6 @@ public VirtualPageFile(Path filePath, int virtualFiles, int pageSize, int target this.cacheBuffers = cacheBuffers; this.mappedByteBuffers = new MappedByteBuffer[MAX_BUFFERS]; - this.nativeIO = new NativeIO(); if (targetBufferSize < (pageSize)) throw new IllegalArgumentException("Target buffer size " + targetBufferSize + " must be larger than a page " + pageSize); @@ -314,7 +311,7 @@ public VirtualPageFile(Path filePath, int virtualFiles, int pageSize, int target try { initialSize = channel.size(); headerBlockLocations = channel.map(mapMode, SELF_DESCRIBING_HEADER_SIZE, PAGE_TABLE_BLOCK_LOCATION_HEADER_SIZE); - nativeIO.madvise(headerBlockLocations, NativeIO.Advice.WillNeed); + NativeIO.madvise(headerBlockLocations, NativeIO.Advice.WillNeed); LongBuffer longHeaderBlockLocations = headerBlockLocations.asLongBuffer(); ByteBuffer intBuffer = LOCAL_INT_BUFFER.get(); @@ -349,7 +346,7 @@ public VirtualPageFile(Path filePath, int virtualFiles, int pageSize, int target try { headerBuffer = channel.map(mapMode, SELF_DESCRIBING_HEADER_SIZE + PAGE_TABLE_BLOCK_LOCATION_HEADER_SIZE, headerSize); - nativeIO.madvise(headerBuffer, NativeIO.Advice.WillNeed); + NativeIO.madvise(headerBuffer, NativeIO.Advice.WillNeed); } catch (IOException e) { throw new UncheckedIOException("unable to map header for path: " + filePath, e); } @@ -373,7 +370,7 @@ public VirtualPageFile(Path filePath, int virtualFiles, int pageSize, int target pageTables = new MappedByteBuffer[MAX_PAGE_TABLE_BLOCKS]; try { pageTables[0] = channel.map(mapMode, headerSize + SELF_DESCRIBING_HEADER_SIZE + PAGE_TABLE_BLOCK_LOCATION_HEADER_SIZE, tableSize); - nativeIO.madvise(pageTables[0], NativeIO.Advice.WillNeed); + NativeIO.madvise(pageTables[0], NativeIO.Advice.WillNeed); } catch (IOException e) { throw new UncheckedIOException("unable to map page locations for path: " + filePath, e); } @@ -494,7 +491,7 @@ private MappedByteBuffer ensureBuffered(int bufferIndex) { long bufferStart = ((long) bufferIndex * bufferSize) + totalHeaderSize; try { buffer = channel.map(mapMode, bufferStart, bufferSize); - if (!cacheBuffers) nativeIO.madvise(buffer, NativeIO.Advice.Random); + if (!cacheBuffers) NativeIO.madvise(buffer, NativeIO.Advice.Random); } catch (IOException e) { throw new UncheckedIOException("Unable to map buffer for index " + bufferIndex + " at (" + bufferStart + " start position) in file " + filePath, e); } @@ -528,7 +525,7 @@ private LongBuffer ensurePageTable(int pageNumber) { } try { buffer = channel.map(mapMode, bufferStart, tableSize); - nativeIO.madvise(buffer, NativeIO.Advice.WillNeed); + NativeIO.madvise(buffer, NativeIO.Advice.WillNeed); } catch (IOException e) { throw new UncheckedIOException("Unable to map buffer for page table " + pageNumber + " at (" + bufferStart + " start position) in file " + filePath, e); } @@ -548,7 +545,7 @@ private void preloadBuffers(long nextPagePosition){ try { MappedByteBuffer buffer = channel.map(mapMode, bufferStart, bufferSize); - if (!cacheBuffers) nativeIO.madvise(buffer, NativeIO.Advice.Random); + if (!cacheBuffers) NativeIO.madvise(buffer, NativeIO.Advice.Random); mappedByteBuffers[bufferIndex] = buffer; } catch (IOException e) { throw new UncheckedIOException("Unable to preload mapped buffer for index " + bufferIndex + " at (" + bufferStart + " start position) in file " + filePath, e); diff --git a/src/nativeIO/c/com_upserve_uppend_blobs_NativeIO.c b/src/nativeIO/c/com_upserve_uppend_blobs_NativeIO.c index 6a8ad076..2d221e2b 100644 --- a/src/nativeIO/c/com_upserve_uppend_blobs_NativeIO.c +++ b/src/nativeIO/c/com_upserve_uppend_blobs_NativeIO.c @@ -1,5 +1,6 @@ #include "com_upserve_uppend_blobs_NativeIO.h" #include +#include #include // posix_madvise, madvise #include // errno #include // strerror @@ -85,7 +86,5 @@ JNIEXPORT void JNICALL Java_com_upserve_uppend_blobs_NativeIO_madvise (JNIEnv *e if (result != 0) { sprintf(exBuffer, "system madvice call failed: '%d'", result); (*env)->ThrowNew(env, class_ioex, exBuffer); - return ; } - return; } diff --git a/src/nativeIO/headers/com_upserve_uppend_blobs_NativeIO.h b/src/nativeIO/headers/com_upserve_uppend_blobs_NativeIO.h index 6a774a96..a68b273c 100644 --- a/src/nativeIO/headers/com_upserve_uppend_blobs_NativeIO.h +++ b/src/nativeIO/headers/com_upserve_uppend_blobs_NativeIO.h @@ -13,7 +13,7 @@ extern "C" { * Signature: (Ljava/nio/MappedByteBuffer;Lcom/upserve/uppend/blobs/NativeIO/Advice;)V */ JNIEXPORT void JNICALL Java_com_upserve_uppend_blobs_NativeIO_madvise - (JNIEnv *, jobject, jobject, jobject); + (JNIEnv *, jclass, jobject, jobject); #ifdef __cplusplus } From 79be5ded0bcb005edfae0aa64013a85fe0c222f1 Mon Sep 17 00:00:00 2001 From: David Stuebe Date: Fri, 15 Nov 2019 15:00:22 -0500 Subject: [PATCH 3/7] Streamline close for VirtualFilePage and BlockedLongs --- .../java/com/upserve/uppend/BlockedLongs.java | 23 ++++--------------- .../upserve/uppend/blobs/VirtualPageFile.java | 10 -------- 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/upserve/uppend/BlockedLongs.java b/src/main/java/com/upserve/uppend/BlockedLongs.java index 707c5ada..d896ecd7 100644 --- a/src/main/java/com/upserve/uppend/BlockedLongs.java +++ b/src/main/java/com/upserve/uppend/BlockedLongs.java @@ -424,32 +424,17 @@ public void clear() { public void close() throws IOException { log.debug("closing {}", file); - if (readOnly) { - blocks.close(); - return; - } + Arrays.fill(pages, null); - IntStream.range(0, LOCK_SIZE).forEach(index -> stripedLocks.getAt(index).lock()); - try { - flush(); - blocks.close(); - } finally { - IntStream.range(0, LOCK_SIZE).forEach(index -> stripedLocks.getAt(index).unlock()); - } + flush(); + blocks.close(); } @Override public void flush() { if (readOnly) return; log.debug("flushing {}", file); - posBuf.force(); appendCountBuf.putLong(0, initialAppendCount + appendCounter.sum()); - appendCountBuf.force(); - - Arrays.stream(pages) - .parallel() - .filter(Objects::nonNull) - .forEach(MappedByteBuffer::force); log.debug("flushed {}", file); } @@ -491,7 +476,7 @@ private MappedByteBuffer page(long pos) { private void preloadPage(int pageIndex) { if (pageIndex < MAX_PAGES && pages[pageIndex] == null) { // preload page - int prev = currentPage.getAndUpdate(current -> current < pageIndex ? pageIndex : current); + int prev = currentPage.getAndUpdate(current -> Math.max(pageIndex, current)); if (prev < pageIndex) { ensurePage(pageIndex); } diff --git a/src/main/java/com/upserve/uppend/blobs/VirtualPageFile.java b/src/main/java/com/upserve/uppend/blobs/VirtualPageFile.java index 820cc7e9..f6040755 100644 --- a/src/main/java/com/upserve/uppend/blobs/VirtualPageFile.java +++ b/src/main/java/com/upserve/uppend/blobs/VirtualPageFile.java @@ -96,19 +96,9 @@ public Path getFilePath() { public void close() throws IOException { if (!channel.isOpen()) return; - for (MappedByteBuffer buf: mappedByteBuffers){ - if (Objects.nonNull(buf)) NativeIO.madvise(buf, NativeIO.Advice.DontNeed); - } Arrays.fill(mappedByteBuffers, null); - - for (MappedByteBuffer buf: pageTables) { - if (Objects.nonNull(buf)) NativeIO.madvise(buf, NativeIO.Advice.DontNeed); - } Arrays.fill(pageTables, null); - NativeIO.madvise(headerBuffer, NativeIO.Advice.DontNeed); - NativeIO.madvise(headerBlockLocations, NativeIO.Advice.DontNeed); - if (!readOnly) { channel.truncate(nextPagePosition.get()); } From 8978fd59aa85f355565d73029af75a4cc31ca4ac Mon Sep 17 00:00:00 2001 From: David Stuebe Date: Sun, 17 Nov 2019 17:36:57 -0500 Subject: [PATCH 4/7] Add shared library to jar --- build.gradle | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index a015bd50..4992b6db 100644 --- a/build.gradle +++ b/build.gradle @@ -172,9 +172,16 @@ processResources { } } +jar{ + dependsOn('buildDependentsNativeIO') + from("${buildDir}/libs/nativeIO/shared") { + include 'libnativeIO.*' + } +} + // TODO include the cross compiled nativeIO libs as a resource. Unpack and load from jar! task fatJar(type: Jar) { - dependsOn 'assembleDependentsNativeIO' + dependsOn 'buildDependentsNativeIO' dependencies { compile 'org.apache.logging.log4j:log4j-core:2.8' @@ -189,7 +196,7 @@ task fatJar(type: Jar) { } tasks.withType(Test) { - dependsOn 'assembleDependentsNativeIO' + dependsOn 'buildDependentsNativeIO' systemProperty "java.library.path", "${buildDir}/libs/nativeIO/shared/" maxHeapSize = "2048m" From 5392a9b57eea8ea63ac8ddfd801db5d3ac488ddf Mon Sep 17 00:00:00 2001 From: David Stuebe Date: Sun, 17 Nov 2019 17:37:25 -0500 Subject: [PATCH 5/7] Attempt to load nativeIO library from jar --- .../com/upserve/uppend/blobs/NativeIO.java | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/upserve/uppend/blobs/NativeIO.java b/src/main/java/com/upserve/uppend/blobs/NativeIO.java index 14d08410..0e8d47e4 100644 --- a/src/main/java/com/upserve/uppend/blobs/NativeIO.java +++ b/src/main/java/com/upserve/uppend/blobs/NativeIO.java @@ -1,9 +1,12 @@ package com.upserve.uppend.blobs; import org.slf4j.Logger; -import java.io.IOException; + +import java.io.*; import java.lang.invoke.MethodHandles; +import java.net.URL; import java.nio.MappedByteBuffer; +import java.nio.file.Files; public class NativeIO { private static final Logger log = org.slf4j.LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); @@ -19,7 +22,31 @@ public enum Advice { public static native void madvise(MappedByteBuffer buffer, Advice advise) throws IOException; static { - log.info("loading nativeIO libbrary"); - System.loadLibrary("nativeIO"); + log.info("loading nativeIO libbrary..."); + + try{ + System.loadLibrary("nativeIO"); + log.info("System.loadLibrary(\"nativeIO\") - successful!"); + } catch (UnsatisfiedLinkError e) { + + String libName = "libnativeIO.dylib"; // The name of the file in resources/ dir + URL url = NativeIO.class.getResource("/" + libName); + File tmpDir = null; + try { + tmpDir = Files.createTempDirectory("nativeIO-lib").toFile(); + } catch (IOException ex) { + throw new UncheckedIOException("Could not create tmpdir for nativeIO library", ex); + } + tmpDir.deleteOnExit(); + File nativeLibTmpFile = new File(tmpDir, libName); + nativeLibTmpFile.deleteOnExit(); + try (InputStream in = url.openStream()) { + Files.copy(in, nativeLibTmpFile.toPath()); + } catch (IOException ex) { + throw new UncheckedIOException("Could not copy libnativeIO", ex); + } + System.load(nativeLibTmpFile.getAbsolutePath()); + log.info("System.load(nativeLibTmpFile.getAbsolutePath()); - successful!"); + } } } From 894771999552f6ee38cbb7b895fe16997b1efffa Mon Sep 17 00:00:00 2001 From: David Stuebe Date: Mon, 18 Nov 2019 20:06:20 -0500 Subject: [PATCH 6/7] Use JNR-FFI to implement madvise --- build.gradle | 95 +------------------ .../com/upserve/uppend/blobs/NativeIO.java | 75 ++++++++------- .../c/com_upserve_uppend_blobs_NativeIO.c | 90 ------------------ .../com_upserve_uppend_blobs_NativeIO.h | 21 ---- .../upserve/uppend/blobs/NativeIOTest.java | 80 ++++++++++++++++ 5 files changed, 124 insertions(+), 237 deletions(-) delete mode 100644 src/nativeIO/c/com_upserve_uppend_blobs_NativeIO.c delete mode 100644 src/nativeIO/headers/com_upserve_uppend_blobs_NativeIO.h create mode 100644 src/test/java/com/upserve/uppend/blobs/NativeIOTest.java diff --git a/build.gradle b/build.gradle index 4992b6db..238311af 100644 --- a/build.gradle +++ b/build.gradle @@ -45,88 +45,6 @@ jacoco { toolVersion = "0.8.2" // Fixed to resolve issue with JDK 11 in Gradle 4.X.Y } -def SYS_INCLUDE_DIR = "/usr/include" // this.properties['system.include.dir'] -def SYS_LOCAL_INCLUDE_DIR = '/usr/local/include' // this.properties['system.local.include.dir'] -def JNI_INCLUDE_DIR = "${System.properties['java.home']}/include" -def JNI_LIB_DIR = "${System.properties['java.home']}/lib" - -println "Using system include directory: " + SYS_INCLUDE_DIR -println "Using system local include directory: " + SYS_LOCAL_INCLUDE_DIR -println "Using JNI include directory: " + JNI_INCLUDE_DIR -println "Using JNI lib directory: " + JNI_LIB_DIR - -model { - platforms { - x64 { - architecture "x86_64" - } - } - - components { - nativeIO(NativeLibrarySpec) { - targetPlatform "x64" - // Using default path for source and headers - } - } - - toolChains { - gcc(Gcc) { - eachPlatform { - if (System.properties['os.name'].equals('Mac OS X')) { - cCompiler.withArguments { args -> - args << "-O2" - args << "-I" + SYS_INCLUDE_DIR - args << "-I" + SYS_LOCAL_INCLUDE_DIR - args << "-I" + JNI_INCLUDE_DIR - args << "-I" + JNI_INCLUDE_DIR + "/darwin" - } - linker.withArguments { args -> - args << "-O2" - } - } else { - cCompiler.withArguments { args -> - args << "-O2" - args << "-I" + SYS_INCLUDE_DIR - args << "-I" + SYS_LOCAL_INCLUDE_DIR - args << "-I" + JNI_INCLUDE_DIR - args << "-I" + JNI_INCLUDE_DIR + "/linux" - } - linker.withArguments { args -> - args << "-O2" - } - } - } - } - clang(Clang) { - eachPlatform { - if (System.properties['os.name'].equals('Mac OS X')) { - cCompiler.withArguments { args -> - args << "-O2" - args << "-I" + SYS_INCLUDE_DIR - args << "-I" + SYS_LOCAL_INCLUDE_DIR - args << "-I" + JNI_INCLUDE_DIR - args << "-I" + JNI_INCLUDE_DIR + "/darwin" - } - linker.withArguments { args -> - args << "-O2" - } - } else { - cCompiler.withArguments { args -> - args << "-O2" - args << "-I" + SYS_INCLUDE_DIR - args << "-I" + SYS_LOCAL_INCLUDE_DIR - args << "-I" + JNI_INCLUDE_DIR - args << "-I" + JNI_INCLUDE_DIR + "/linux" - } - linker.withArguments { args -> - args << "-O2" - } - } - } - } - } -} - sourceCompatibility = 1.9 targetCompatibility = 1.9 // Requires 1.9 or greater due to unsafe memory access in MappebByteBuffer - Oracle Incident Report 9119653 @@ -140,6 +58,7 @@ dependencies { compile 'info.picocli:picocli:4.0.1' compile 'io.dropwizard.metrics:metrics-core:3.2.3' compile 'it.unimi.dsi:fastutil:7.0.13' + compile 'com.github.jnr:jnr-ffi:2.1.1' // compile 'me.lemire.integercompression:JavaFastPFOR:0.1.11' compile 'org.slf4j:slf4j-api:1.7.22' @@ -150,7 +69,7 @@ dependencies { tasks.withType(JavaCompile) { - options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" << "-Werror" << "-h" << ("${buildDir}/../src/nativeIO/headers/" as String) + options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" << "-Werror" //options.verbose = true } @@ -172,17 +91,9 @@ processResources { } } -jar{ - dependsOn('buildDependentsNativeIO') - from("${buildDir}/libs/nativeIO/shared") { - include 'libnativeIO.*' - } -} // TODO include the cross compiled nativeIO libs as a resource. Unpack and load from jar! task fatJar(type: Jar) { - dependsOn 'buildDependentsNativeIO' - dependencies { compile 'org.apache.logging.log4j:log4j-core:2.8' compile 'org.apache.logging.log4j:log4j-slf4j-impl:2.8' @@ -196,8 +107,6 @@ task fatJar(type: Jar) { } tasks.withType(Test) { - dependsOn 'buildDependentsNativeIO' - systemProperty "java.library.path", "${buildDir}/libs/nativeIO/shared/" maxHeapSize = "2048m" // From https://stackoverflow.com/a/36130467/2136991 diff --git a/src/main/java/com/upserve/uppend/blobs/NativeIO.java b/src/main/java/com/upserve/uppend/blobs/NativeIO.java index 0e8d47e4..231e0ebe 100644 --- a/src/main/java/com/upserve/uppend/blobs/NativeIO.java +++ b/src/main/java/com/upserve/uppend/blobs/NativeIO.java @@ -1,52 +1,61 @@ package com.upserve.uppend.blobs; +import jnr.ffi.*; +import jnr.ffi.types.size_t; import org.slf4j.Logger; +import com.kenai.jffi.MemoryIO; -import java.io.*; +import java.io.IOException; import java.lang.invoke.MethodHandles; -import java.net.URL; -import java.nio.MappedByteBuffer; -import java.nio.file.Files; +import java.nio.*; public class NativeIO { private static final Logger log = org.slf4j.LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + private static final NativeC nativeC = LibraryLoader.create(NativeC.class).load("c"); + static final int pageSize = nativeC.getpagesize(); // 4096 on most Linux + public enum Advice { - Normal(0), Sequential(1), Random(2), WillNeed(3), DontNeed(4); + // These seem to be fairly stable https://github.com/torvalds/linux + // TODO add to https://github.com/jnr/jnr-constants + Normal(0), Random(1), Sequential(2), WillNeed(3), DontNeed(4); private final int value; Advice(int val) { this.value = val; } } - public static native void madvise(MappedByteBuffer buffer, Advice advise) throws IOException; - - static { - log.info("loading nativeIO libbrary..."); - - try{ - System.loadLibrary("nativeIO"); - log.info("System.loadLibrary(\"nativeIO\") - successful!"); - } catch (UnsatisfiedLinkError e) { - - String libName = "libnativeIO.dylib"; // The name of the file in resources/ dir - URL url = NativeIO.class.getResource("/" + libName); - File tmpDir = null; - try { - tmpDir = Files.createTempDirectory("nativeIO-lib").toFile(); - } catch (IOException ex) { - throw new UncheckedIOException("Could not create tmpdir for nativeIO library", ex); - } - tmpDir.deleteOnExit(); - File nativeLibTmpFile = new File(tmpDir, libName); - nativeLibTmpFile.deleteOnExit(); - try (InputStream in = url.openStream()) { - Files.copy(in, nativeLibTmpFile.toPath()); - } catch (IOException ex) { - throw new UncheckedIOException("Could not copy libnativeIO", ex); - } - System.load(nativeLibTmpFile.getAbsolutePath()); - log.info("System.load(nativeLibTmpFile.getAbsolutePath()); - successful!"); + public interface NativeC { + int madvise(@size_t long address, @size_t long size, int advice); + int getpagesize(); + } + + static long alignedAddress(long address) { + return address & (- pageSize); + } + + static long alignedSize(long address, int capacity) { + long end = address + capacity; + end = (end + pageSize - 1) & (-pageSize); + return end - alignedAddress(address); + } + + public static void madvise(MappedByteBuffer buffer, Advice advice) throws IOException { + + final long address = MemoryIO.getInstance().getDirectBufferAddress(buffer); + final int capacity = buffer.capacity(); + + long alignedAddress = alignedAddress(address); + long alignedSize = alignedSize(alignedAddress, capacity); + + log.debug( + "Page size {}; Address: raw - {}, aligned - {}; Size: raw - {}, aligned - {}", + pageSize, address, alignedAddress, capacity, alignedSize + ); + int val = nativeC.madvise(alignedAddress, alignedSize, advice.value); + + if (val != 0) { + throw new IOException(String.format("System call madvise failed with code: %d", val)); } } } diff --git a/src/nativeIO/c/com_upserve_uppend_blobs_NativeIO.c b/src/nativeIO/c/com_upserve_uppend_blobs_NativeIO.c deleted file mode 100644 index 2d221e2b..00000000 --- a/src/nativeIO/c/com_upserve_uppend_blobs_NativeIO.c +++ /dev/null @@ -1,90 +0,0 @@ -#include "com_upserve_uppend_blobs_NativeIO.h" -#include -#include -#include // posix_madvise, madvise -#include // errno -#include // strerror - -// TODO add Lucene license and changes -// https://github.com/apache/lucene-solr/tree/master/lucene/misc/src/java/org/apache/lucene/store - -/* - * Class: com_upserve_uppend_blobs_NativeIO - * Method: madvise - * Signature: (Ljava/nio/MappedByteBuffer;Lcom/upserve/uppend/blobs/NativeIO/Advice;)V - */ -JNIEXPORT void JNICALL Java_com_upserve_uppend_blobs_NativeIO_madvise (JNIEnv *env, jclass _ignore, jobject buffer, jobject advice) { - char exBuffer[80]; - jclass class_npe = (*env)->FindClass(env, "java/lang/NullPointerException"); - jclass class_ioex = (*env)->FindClass(env, "java/io/IOException"); - jclass class_argEx = (*env)->FindClass(env, "java/lang/IllegalArgumentException"); - - if ((*env)->IsSameObject(env, buffer, NULL)) { - (*env)->ThrowNew(env, class_npe, "buffer argument can not be null"); - return; - } - - if ((*env)->IsSameObject(env, advice, NULL)) { - (*env)->ThrowNew(env, class_npe, "advice argument can not be null"); - return; - } - - // Parse the advice and lookup POSIX value (See man page for madvise) - jclass adviceClass = (*env)->GetObjectClass(env, advice); - jfieldID fidNumber = (*env)->GetFieldID(env, adviceClass, "value", "I"); - jint intAdvice = (*env)->GetIntField(env, advice, fidNumber); - - int osAdvice; - switch(intAdvice) { - case 0: - osAdvice = MADV_NORMAL; - break; - case 1: - osAdvice = POSIX_MADV_SEQUENTIAL; - break; - case 2: - osAdvice = POSIX_MADV_RANDOM; - break; - case 3: - osAdvice = POSIX_MADV_WILLNEED; - break; - case 4: - osAdvice = POSIX_MADV_DONTNEED; - break; - default: - sprintf(exBuffer, "invalid advice value: '%d'", intAdvice); - (*env)->ThrowNew(env, class_argEx, exBuffer); - return ; - } - - void *p = (*env)->GetDirectBufferAddress(env, buffer); - if (p == NULL) { - (*env)->ThrowNew(env, class_ioex, strerror(errno)); - return; - } - - size_t size = (size_t) (*env)->GetDirectBufferCapacity(env, buffer); - if (size <= 0) { - (*env)->ThrowNew(env, class_ioex, strerror(errno)); - return; - } - - int page = getpagesize(); - - // round start down to start of page - long long start = (long long) p; - start = start & (~(page-1)); - - // round end up to start of page - long long end = start + size; - end = (end + page-1)&(~(page-1)); - size = (end-start); - - //printf("\nDO madvise: page=%d p=0x%lx 0x%lx size=0x%lx\n", page, p, start, size); - - int result = madvise((void *) start, size, osAdvice); - if (result != 0) { - sprintf(exBuffer, "system madvice call failed: '%d'", result); - (*env)->ThrowNew(env, class_ioex, exBuffer); - } -} diff --git a/src/nativeIO/headers/com_upserve_uppend_blobs_NativeIO.h b/src/nativeIO/headers/com_upserve_uppend_blobs_NativeIO.h deleted file mode 100644 index a68b273c..00000000 --- a/src/nativeIO/headers/com_upserve_uppend_blobs_NativeIO.h +++ /dev/null @@ -1,21 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class com_upserve_uppend_blobs_NativeIO */ - -#ifndef _Included_com_upserve_uppend_blobs_NativeIO -#define _Included_com_upserve_uppend_blobs_NativeIO -#ifdef __cplusplus -extern "C" { -#endif -/* - * Class: com_upserve_uppend_blobs_NativeIO - * Method: madvise - * Signature: (Ljava/nio/MappedByteBuffer;Lcom/upserve/uppend/blobs/NativeIO/Advice;)V - */ -JNIEXPORT void JNICALL Java_com_upserve_uppend_blobs_NativeIO_madvise - (JNIEnv *, jclass, jobject, jobject); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/test/java/com/upserve/uppend/blobs/NativeIOTest.java b/src/test/java/com/upserve/uppend/blobs/NativeIOTest.java new file mode 100644 index 00000000..275e733d --- /dev/null +++ b/src/test/java/com/upserve/uppend/blobs/NativeIOTest.java @@ -0,0 +1,80 @@ +package com.upserve.uppend.blobs; + +import com.upserve.uppend.util.SafeDeleting; +import org.junit.*; + +import java.io.IOException; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.*; + +import static junit.framework.TestCase.assertEquals; + +public class NativeIOTest { + + String fname = "test_file"; + Path rootPath = Paths.get("build/test/blobs/virtual_page_file"); + Path path = rootPath.resolve(fname); + + FileChannel fc; + + @Before + public void setUp() throws IOException { + Files.createDirectories(rootPath); + fc = FileChannel.open(path, StandardOpenOption.CREATE_NEW, StandardOpenOption.READ, StandardOpenOption.WRITE); + } + + @After + public void tearDown() throws IOException { + fc.close(); + SafeDeleting.removeDirectory(rootPath); + } + + @Test + public void test_madvise() throws IOException { + MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_WRITE, 57, 5329); + NativeIO.madvise(buffer, NativeIO.Advice.Normal); + NativeIO.madvise(buffer, NativeIO.Advice.Random); + NativeIO.madvise(buffer, NativeIO.Advice.Sequential); + NativeIO.madvise(buffer, NativeIO.Advice.WillNeed); + NativeIO.madvise(buffer, NativeIO.Advice.DontNeed); + } + + @Test + public void test_alignedAddress() { + long result; + + result = NativeIO.alignedAddress(2102L); + assertEquals(0L, result); + assertEquals(0, result % NativeIO.pageSize); + + result = NativeIO.alignedAddress(NativeIO.pageSize); + assertEquals(NativeIO.pageSize, result); + assertEquals(0, result % NativeIO.pageSize); + + result = NativeIO.alignedAddress(4345290809L); + assertEquals(4345290752L, result); + assertEquals(0, result % NativeIO.pageSize); + + result = NativeIO.alignedAddress(4517752889L); + assertEquals(4517752832L,result); + assertEquals(0, result % NativeIO.pageSize); + } + + @Test + public void test_alignedSize() { + long result; + + result = NativeIO.alignedSize(0L, NativeIO.pageSize); + assertEquals(NativeIO.pageSize, result); + + result = NativeIO.alignedSize(NativeIO.pageSize - 1, 2); + assertEquals(2 * NativeIO.pageSize, result); + + result = NativeIO.alignedSize(100 * NativeIO.pageSize + 12, NativeIO.pageSize - 11); + assertEquals(2 * NativeIO.pageSize, result); + + result = NativeIO.alignedSize(4517752889L, 5087); + assertEquals(2 * NativeIO.pageSize, result); + } +} From 98b53ec9bec8c15b0a00bb36a9ed870e4247ef1b Mon Sep 17 00:00:00 2001 From: David Stuebe Date: Tue, 19 Nov 2019 16:04:33 -0500 Subject: [PATCH 7/7] Backward incompatible file format changes to enforce headers aligne to system page size --- .../uppend/AppendOnlyStoreBuilder.java | 11 +++++++++- .../java/com/upserve/uppend/BlockedLongs.java | 14 ++++++++++--- .../com/upserve/uppend/FileStoreBuilder.java | 21 +++++++++++++++++-- .../com/upserve/uppend/blobs/NativeIO.java | 2 +- .../upserve/uppend/blobs/VirtualPageFile.java | 9 +++++++- .../com/upserve/uppend/BlockedLongsTest.java | 2 +- .../java/com/upserve/uppend/TestHelper.java | 7 ++++--- .../uppend/blobs/VirtualPageFileTest.java | 10 ++++----- 8 files changed, 59 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/upserve/uppend/AppendOnlyStoreBuilder.java b/src/main/java/com/upserve/uppend/AppendOnlyStoreBuilder.java index cd98adb6..676c9cde 100644 --- a/src/main/java/com/upserve/uppend/AppendOnlyStoreBuilder.java +++ b/src/main/java/com/upserve/uppend/AppendOnlyStoreBuilder.java @@ -1,5 +1,6 @@ package com.upserve.uppend; +import com.upserve.uppend.blobs.NativeIO; import com.upserve.uppend.metrics.*; public class AppendOnlyStoreBuilder extends FileStoreBuilder { @@ -8,7 +9,7 @@ public class AppendOnlyStoreBuilder extends FileStoreBuilder> { // Long lookup Cache Options public static final int DEFAULT_PARTITION_COUNT = 0; public static final int DEFAULT_LOOKUP_HASH_COUNT = 256; - public static final int DEFAULT_LOOKUP_PAGE_SIZE = 256 * 1024; + public static final int DEFAULT_LOOKUP_PAGE_SIZE = NativeIO.pageSize * 64; public static final int TARGET_PRODUCTION_BUFFER_SIZE = Integer.MAX_VALUE; - public static final int DEFAULT_METADATA_PAGE_SIZE = 4096; + public static final int DEFAULT_METADATA_PAGE_SIZE = NativeIO.pageSize; public static final int DEFAULT_METADATA_TTL = 0; // Off by default! private String storeName = ""; @@ -56,12 +57,28 @@ public T withLongLookupHashCount(int longLookupHashCount) { @SuppressWarnings("unchecked") public T withLookupPageSize(int lookupPageSize) { + if (lookupPageSize % NativeIO.pageSize != 0) { + throw new IllegalArgumentException( + String.format( + "Illegal lookupPageSize %d; Must be a multiple of the host system page size: %d", + lookupPageSize, NativeIO.pageSize + ) + ); + } this.lookupPageSize = lookupPageSize; return (T) this; } @SuppressWarnings("unchecked") public T withMetadataPageSize(int metadataPageSize) { + if (metadataPageSize % NativeIO.pageSize != 0){ + throw new IllegalArgumentException( + String.format( + "Illegal metadataPageSize %d; Must be a multiple of the host system page size: %d", + metadataPageSize, NativeIO.pageSize + ) + ); + } this.metadataPageSize = metadataPageSize; return (T) this; } diff --git a/src/main/java/com/upserve/uppend/blobs/NativeIO.java b/src/main/java/com/upserve/uppend/blobs/NativeIO.java index 231e0ebe..7774cf2e 100644 --- a/src/main/java/com/upserve/uppend/blobs/NativeIO.java +++ b/src/main/java/com/upserve/uppend/blobs/NativeIO.java @@ -13,7 +13,7 @@ public class NativeIO { private static final Logger log = org.slf4j.LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private static final NativeC nativeC = LibraryLoader.create(NativeC.class).load("c"); - static final int pageSize = nativeC.getpagesize(); // 4096 on most Linux + public static final int pageSize = nativeC.getpagesize(); // 4096 on most Linux public enum Advice { // These seem to be fairly stable https://github.com/torvalds/linux diff --git a/src/main/java/com/upserve/uppend/blobs/VirtualPageFile.java b/src/main/java/com/upserve/uppend/blobs/VirtualPageFile.java index f6040755..7d0ba0b2 100644 --- a/src/main/java/com/upserve/uppend/blobs/VirtualPageFile.java +++ b/src/main/java/com/upserve/uppend/blobs/VirtualPageFile.java @@ -174,6 +174,10 @@ long nextAlignedPosition(long position, int lowBound, int highBound) { } } + int roundUpto(int size, int incriments) { + return (size + incriments - 1) & (-incriments); + } + long getPosition(int virtualFileNumber) { if (readOnly) { return getHeaderVirtualFilePosition(virtualFileNumber); @@ -332,7 +336,10 @@ public VirtualPageFile(Path filePath, int virtualFiles, int pageSize, int target throw new UncheckedIOException("Unable to read, write, map or get the size of " + getFilePath(), e); } - totalHeaderSize = headerSize + tableSize + SELF_DESCRIBING_HEADER_SIZE + PAGE_TABLE_BLOCK_LOCATION_HEADER_SIZE; + totalHeaderSize = roundUpto( + headerSize + tableSize + SELF_DESCRIBING_HEADER_SIZE + PAGE_TABLE_BLOCK_LOCATION_HEADER_SIZE, + NativeIO.pageSize + ); try { headerBuffer = channel.map(mapMode, SELF_DESCRIBING_HEADER_SIZE + PAGE_TABLE_BLOCK_LOCATION_HEADER_SIZE, headerSize); diff --git a/src/test/java/com/upserve/uppend/BlockedLongsTest.java b/src/test/java/com/upserve/uppend/BlockedLongsTest.java index 106d6966..0f0b4593 100644 --- a/src/test/java/com/upserve/uppend/BlockedLongsTest.java +++ b/src/test/java/com/upserve/uppend/BlockedLongsTest.java @@ -85,7 +85,7 @@ public void testAppendAtNonStartingBlock() throws Exception { v.append(pos1, i); } int blockSize = 16 + 10 * 8; // mirrors BlockedLongs.blockSize - v.append(blockSize * 2, 21); + v.append(v.HEADER_BYTES + blockSize * 2, 21); } @Test(expected = IllegalStateException.class) diff --git a/src/test/java/com/upserve/uppend/TestHelper.java b/src/test/java/com/upserve/uppend/TestHelper.java index e2a5836c..37dfada3 100644 --- a/src/test/java/com/upserve/uppend/TestHelper.java +++ b/src/test/java/com/upserve/uppend/TestHelper.java @@ -1,5 +1,6 @@ package com.upserve.uppend; +import com.upserve.uppend.blobs.NativeIO; import org.junit.*; import org.slf4j.LoggerFactory; import java.io.*; @@ -88,11 +89,11 @@ public static AppendOnlyStoreBuilder getDefaultAppendStoreTestBuilder() { public static AppendOnlyStoreBuilder getDefaultAppendStoreTestBuilder(ExecutorService testService) { return new AppendOnlyStoreBuilder() .withStoreName("test") - .withBlobPageSize(64 * 1024) + .withBlobPageSize(8 * NativeIO.pageSize) .withBlobsPerBlock(30) .withTargetBufferSize(16*1024*1024) .withLongLookupHashCount(16) - .withLookupPageSize(16 * 1024) + .withLookupPageSize(4 * NativeIO.pageSize) .withMetadataTTL(0); } @@ -100,7 +101,7 @@ public static CounterStoreBuilder getDefaultCounterStoreTestBuilder() { return new CounterStoreBuilder() .withStoreName("test") .withTargetBufferSize(16*1024*1024) - .withMetadataPageSize(1024) + .withMetadataPageSize(NativeIO.pageSize) .withLongLookupHashCount(16) .withLookupPageSize(16 * 1024); } diff --git a/src/test/java/com/upserve/uppend/blobs/VirtualPageFileTest.java b/src/test/java/com/upserve/uppend/blobs/VirtualPageFileTest.java index db8e1f2d..d2121aaa 100644 --- a/src/test/java/com/upserve/uppend/blobs/VirtualPageFileTest.java +++ b/src/test/java/com/upserve/uppend/blobs/VirtualPageFileTest.java @@ -81,13 +81,13 @@ public void testReadOnlyTruncation() throws IOException { page = instance.getExistingPage(5,0); - assertEquals(313016, instance.getFileSize()); + assertEquals(315392, instance.getFileSize()); instance.close(); // We can open the file in read only after truncation VirtualPageFile roInstance = new VirtualPageFile(path, 36, 1024, 16384, true); - assertEquals(313016, roInstance.getFileSize()); + assertEquals(315392, roInstance.getFileSize()); page = roInstance.getExistingPage(5,0); @@ -97,20 +97,20 @@ public void testReadOnlyTruncation() throws IOException { // Make a new page - check that file is extended again. instance = new VirtualPageFile(path, 36, 1024, 16384, false); - assertEquals(313016L, instance.getFileSize()); + assertEquals(315392L, instance.getFileSize()); page = instance.getOrCreatePage(6,0); page.put(6, "def".getBytes(), 0); page.put(900, "ghi".getBytes(), 0); page = roInstance.getExistingPage(6,0); - assertEquals(313016L, roInstance.getFileSize()); + assertEquals(315392L, roInstance.getFileSize()); page.get(6, bytes, 0); assertArrayEquals("def".getBytes(), bytes); instance.close(); - assertEquals(298680L, roInstance.getFileSize()); + assertEquals(301056L, roInstance.getFileSize()); page.get(900, bytes, 0); assertArrayEquals("ghi".getBytes(), bytes);