Support direct buffer decompression for reads

Patch by tjake; reviewed by Branimir Lambov for CASSANDRA-8464
This commit is contained in:
T Jake Luciani 2015-01-16 09:37:06 -05:00
parent 3dd9c38a14
commit 895ec3ea1e
19 changed files with 399 additions and 81 deletions

View File

@ -1,4 +1,5 @@
3.0
* Support direct buffer decompression for reads (CASSANDRA-8464)
* DirectByteBuffer compatible LZ4 methods (CASSANDRA-7039)
* Add role based access control (CASSANDRA-7653)
* Group sstables for anticompaction correctly (CASSANDRA-8578)

View File

@ -251,6 +251,7 @@ fi
# uncomment to have Cassandra JVM log internal method compilation (developers only)
# JVM_OPTS="$JVM_OPTS -XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation"
# JVM_OPTS="$JVM_OPTS -XX:+UnlockCommercialFeatures -XX:+FlightRecorder"
# Prefer binding to IPv4 network intefaces (when net.ipv6.bindv6only=1). See
# http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6342561 (short version:

View File

@ -19,14 +19,22 @@ package org.apache.cassandra.io.compress;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.zip.Adler32;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import com.google.common.primitives.Ints;
import org.apache.cassandra.config.Config;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.io.FSReadError;
import org.apache.cassandra.io.sstable.CorruptSSTableException;
import org.apache.cassandra.io.util.CompressedPoolingSegmentedFile;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.io.util.PoolingSegmentedFile;
import org.apache.cassandra.io.util.RandomAccessReader;
import org.apache.cassandra.utils.FBUtilities;
@ -37,6 +45,8 @@ import org.apache.cassandra.utils.FBUtilities;
*/
public class CompressedRandomAccessReader extends RandomAccessReader
{
private static final boolean useMmap = DatabaseDescriptor.getDiskAccessMode() == Config.DiskAccessMode.mmap;
public static CompressedRandomAccessReader open(String path, CompressionMetadata metadata, CompressedPoolingSegmentedFile owner)
{
try
@ -61,33 +71,96 @@ public class CompressedRandomAccessReader extends RandomAccessReader
}
}
private TreeMap<Long, MappedByteBuffer> chunkSegments;
private int MAX_SEGMENT_SIZE = Integer.MAX_VALUE;
private final CompressionMetadata metadata;
// we read the raw compressed bytes into this buffer, then move the uncompressed ones into super.buffer.
private ByteBuffer compressed;
// re-use single crc object
private final Checksum checksum;
private final Adler32 checksum;
// raw checksum bytes
private final ByteBuffer checksumBytes = ByteBuffer.wrap(new byte[4]);
private ByteBuffer checksumBytes;
protected CompressedRandomAccessReader(String dataFilePath, CompressionMetadata metadata, PoolingSegmentedFile owner) throws FileNotFoundException
{
super(new File(dataFilePath), metadata.chunkLength(), owner);
super(new File(dataFilePath), metadata.chunkLength(), metadata.compressor().useDirectOutputByteBuffers(), owner);
this.metadata = metadata;
checksum = metadata.hasPostCompressionAdlerChecksums ? new Adler32() : new CRC32();
compressed = ByteBuffer.wrap(new byte[metadata.compressor().initialCompressedBufferLength(metadata.chunkLength())]);
checksum = new Adler32();
if (!useMmap)
{
compressed = ByteBuffer.wrap(new byte[metadata.compressor().initialCompressedBufferLength(metadata.chunkLength())]);
checksumBytes = ByteBuffer.wrap(new byte[4]);
}
else
{
try
{
createMappedSegments();
}
catch (IOException e)
{
throw new IOError(e);
}
}
}
protected ByteBuffer allocateBuffer(int bufferSize)
private void createMappedSegments() throws IOException
{
chunkSegments = new TreeMap<>();
long offset = 0;
long lastSegmentOffset = 0;
long segmentSize = 0;
while (offset < metadata.dataLength)
{
CompressionMetadata.Chunk chunk = metadata.chunkFor(offset);
//Reached a new mmap boundary
if (segmentSize + chunk.length + 4 > MAX_SEGMENT_SIZE)
{
chunkSegments.put(lastSegmentOffset, channel.map(FileChannel.MapMode.READ_ONLY, lastSegmentOffset, segmentSize));
lastSegmentOffset += segmentSize;
segmentSize = 0;
}
segmentSize += chunk.length + 4; //checksum
offset += metadata.chunkLength();
}
if (segmentSize > 0)
chunkSegments.put(lastSegmentOffset, channel.map(FileChannel.MapMode.READ_ONLY, lastSegmentOffset, segmentSize));
}
protected ByteBuffer allocateBuffer(int bufferSize, boolean useDirect)
{
assert Integer.bitCount(bufferSize) == 1;
return ByteBuffer.allocate(bufferSize);
return useMmap && useDirect
? ByteBuffer.allocateDirect(bufferSize)
: ByteBuffer.allocate(bufferSize);
}
@Override
protected void reBuffer()
public void deallocate()
{
super.deallocate();
if (chunkSegments != null)
{
for (Map.Entry<Long, MappedByteBuffer> entry : chunkSegments.entrySet())
{
FileUtils.clean(entry.getValue());
}
}
chunkSegments = null;
}
private void reBufferStandard()
{
try
{
@ -126,14 +199,7 @@ public class CompressedRandomAccessReader extends RandomAccessReader
if (metadata.parameters.getCrcCheckChance() > ThreadLocalRandom.current().nextDouble())
{
if (metadata.hasPostCompressionAdlerChecksums)
{
checksum.update(compressed.array(), 0, chunk.length);
}
else
{
checksum.update(buffer.array(), 0, decompressedBytes);
}
checksum.update(compressed.array(), 0, chunk.length);
if (checksum(chunk) != (int) checksum.getValue())
throw new CorruptBlockException(getPath(), chunk);
@ -156,6 +222,81 @@ public class CompressedRandomAccessReader extends RandomAccessReader
}
}
private void reBufferMmap()
{
try
{
long position = current();
assert position < metadata.dataLength;
CompressionMetadata.Chunk chunk = metadata.chunkFor(position);
Map.Entry<Long, MappedByteBuffer> entry = chunkSegments.floorEntry(chunk.offset);
long segmentOffset = entry.getKey();
int chunkOffset = Ints.checkedCast(chunk.offset - segmentOffset);
MappedByteBuffer compressedChunk = entry.getValue();
compressedChunk.position(chunkOffset);
compressedChunk.limit(chunkOffset + chunk.length);
compressedChunk.mark();
buffer.clear();
int decompressedBytes;
try
{
decompressedBytes = metadata.compressor().uncompress(compressedChunk, buffer);
buffer.limit(decompressedBytes);
}
catch (IOException e)
{
throw new CorruptBlockException(getPath(), chunk);
}
finally
{
compressedChunk.limit(compressedChunk.capacity());
}
if (metadata.parameters.getCrcCheckChance() > ThreadLocalRandom.current().nextDouble())
{
compressedChunk.reset();
compressedChunk.limit(chunkOffset + chunk.length);
FBUtilities.directCheckSum(checksum, compressedChunk);
compressedChunk.limit(compressedChunk.capacity());
if (compressedChunk.getInt() != (int) checksum.getValue())
throw new CorruptBlockException(getPath(), chunk);
// reset checksum object back to the original (blank) state
checksum.reset();
}
// buffer offset is always aligned
bufferOffset = position & ~(buffer.capacity() - 1);
buffer.position((int) (position - bufferOffset));
}
catch (CorruptBlockException e)
{
throw new CorruptSSTableException(e, getPath());
}
}
@Override
protected void reBuffer()
{
if (useMmap)
{
reBufferMmap();
}
else
{
reBufferStandard();
}
}
private int checksum(CompressionMetadata.Chunk chunk) throws IOException
{
assert channel.position() == chunk.offset + chunk.length;
@ -167,7 +308,7 @@ public class CompressedRandomAccessReader extends RandomAccessReader
public int getTotalBufferSize()
{
return super.getTotalBufferSize() + compressed.capacity();
return super.getTotalBufferSize() + (useMmap ? 0 : compressed.capacity());
}
@Override

View File

@ -62,7 +62,6 @@ public class CompressionMetadata
{
public final long dataLength;
public final long compressedFileLength;
public final boolean hasPostCompressionAdlerChecksums;
private final Memory chunkOffsets;
private final long chunkOffsetsSize;
public final String indexFilePath;
@ -82,14 +81,13 @@ public class CompressionMetadata
public static CompressionMetadata create(String dataFilePath)
{
Descriptor desc = Descriptor.fromFilename(dataFilePath);
return new CompressionMetadata(desc.filenameFor(Component.COMPRESSION_INFO), new File(dataFilePath).length(), desc.version.hasPostCompressionAdlerChecksums());
return new CompressionMetadata(desc.filenameFor(Component.COMPRESSION_INFO), new File(dataFilePath).length());
}
@VisibleForTesting
CompressionMetadata(String indexFilePath, long compressedLength, boolean hasPostCompressionAdlerChecksums)
CompressionMetadata(String indexFilePath, long compressedLength)
{
this.indexFilePath = indexFilePath;
this.hasPostCompressionAdlerChecksums = hasPostCompressionAdlerChecksums;
DataInputStream stream;
try
@ -137,13 +135,12 @@ public class CompressionMetadata
this.chunkOffsetsSize = chunkOffsets.size();
}
private CompressionMetadata(String filePath, CompressionParameters parameters, RefCountedMemory offsets, long offsetsSize, long dataLength, long compressedLength, boolean hasPostCompressionAdlerChecksums)
private CompressionMetadata(String filePath, CompressionParameters parameters, RefCountedMemory offsets, long offsetsSize, long dataLength, long compressedLength)
{
this.indexFilePath = filePath;
this.parameters = parameters;
this.dataLength = dataLength;
this.compressedFileLength = compressedLength;
this.hasPostCompressionAdlerChecksums = hasPostCompressionAdlerChecksums;
this.chunkOffsets = offsets;
offsets.reference();
this.chunkOffsetsSize = offsetsSize;
@ -342,7 +339,7 @@ public class CompressionMetadata
default:
throw new AssertionError();
}
return new CompressionMetadata(filePath, parameters, offsets, count * 8L, dataLength, compressedLength, latestVersion.hasPostCompressionAdlerChecksums());
return new CompressionMetadata(filePath, parameters, offsets, count * 8L, dataLength, compressedLength);
}
/**

View File

@ -17,7 +17,10 @@
*/
package org.apache.cassandra.io.compress;
import org.apache.cassandra.utils.ByteBufferUtil;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
@ -113,4 +116,18 @@ public class DeflateCompressor implements ICompressor
throw new IOException(e);
}
}
public int uncompress(ByteBuffer input_, ByteBuffer output) throws IOException
{
if (!output.hasArray())
throw new IllegalArgumentException("DeflateCompressor doesn't work with direct byte buffers");
byte[] input = ByteBufferUtil.getArray(input_);
return uncompress(input, 0, input.length, output.array(), output.arrayOffset() + output.position());
}
public boolean useDirectOutputByteBuffers()
{
return false;
}
}

View File

@ -18,6 +18,7 @@
package org.apache.cassandra.io.compress;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Set;
public interface ICompressor
@ -28,6 +29,17 @@ public interface ICompressor
public int uncompress(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset) throws IOException;
/**
* Decompression for DirectByteBuffers
*/
public int uncompress(ByteBuffer input, ByteBuffer output) throws IOException;
/**
* Notifies user if this compressor will wants/requires a direct byte buffers to
* decompress direct byteBuffers
*/
public boolean useDirectOutputByteBuffers();
public Set<String> supportedOptions();
/**

View File

@ -18,6 +18,7 @@
package org.apache.cassandra.io.compress;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
@ -25,10 +26,10 @@ import java.util.Set;
import net.jpountz.lz4.LZ4Exception;
import net.jpountz.lz4.LZ4Factory;
import org.apache.cassandra.utils.FastByteOperations;
public class LZ4Compressor implements ICompressor
{
private static final int INTEGER_BYTES = 4;
private static final LZ4Compressor instance = new LZ4Compressor();
@ -38,13 +39,13 @@ public class LZ4Compressor implements ICompressor
}
private final net.jpountz.lz4.LZ4Compressor compressor;
private final net.jpountz.lz4.LZ4Decompressor decompressor;
private final net.jpountz.lz4.LZ4FastDecompressor decompressor;
private LZ4Compressor()
{
final LZ4Factory lz4Factory = LZ4Factory.fastestInstance();
compressor = lz4Factory.fastCompressor();
decompressor = lz4Factory.decompressor();
decompressor = lz4Factory.fastDecompressor();
}
public int initialCompressedBufferLength(int chunkLength)
@ -97,8 +98,42 @@ public class LZ4Compressor implements ICompressor
return decompressedLength;
}
public int uncompress(ByteBuffer input, ByteBuffer output) throws IOException
{
int pos = input.position();
final int decompressedLength = (input.get(pos) & 0xFF)
| ((input.get(pos + 1) & 0xFF) << 8)
| ((input.get(pos + 2) & 0xFF) << 16)
| ((input.get(pos + 3) & 0xFF) << 24);
int inputLength = input.remaining() - INTEGER_BYTES;
final int compressedLength;
try
{
compressedLength = decompressor.decompress(input, input.position() + INTEGER_BYTES, output, output.position(), decompressedLength);
}
catch (LZ4Exception e)
{
throw new IOException(e);
}
if (compressedLength != inputLength)
{
throw new IOException("Compressed lengths mismatch: "+compressedLength+" vs "+inputLength);
}
return decompressedLength;
}
@Override
public boolean useDirectOutputByteBuffers()
{
return false;
}
public Set<String> supportedOptions()
{
return new HashSet<String>(Arrays.asList(CompressionParameters.CRC_CHECK_CHANCE));
return new HashSet<>(Arrays.asList(CompressionParameters.CRC_CHECK_CHANCE));
}
}

View File

@ -18,10 +18,12 @@
package org.apache.cassandra.io.compress;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xerial.snappy.Snappy;
@ -95,4 +97,15 @@ public class SnappyCompressor implements ICompressor
{
return Snappy.rawUncompress(input, inputOffset, inputLength, output, outputOffset);
}
public int uncompress(ByteBuffer input, ByteBuffer output) throws IOException
{
return Snappy.uncompress(input, output);
}
@Override
public boolean useDirectOutputByteBuffers()
{
return true;
}
}

View File

@ -40,8 +40,6 @@ public abstract class Version
public abstract boolean isLatestVersion();
public abstract boolean hasPostCompressionAdlerChecksums();
public abstract boolean hasSamplingLevel();
public abstract boolean hasNewStatsFile();

View File

@ -126,16 +126,8 @@ public class BigFormat implements SSTableFormat
static class BigVersion extends Version
{
public static final String current_version = "la";
public static final String earliest_supported_version = "ja";
public static final String earliest_supported_version = "jb";
// ja (2.0.0): super columns are serialized as composites (note that there is no real format change,
// this is mostly a marker to know if we should expect super columns or not. We do need
// a major version bump however, because we should not allow streaming of super columns
// into this new format)
// tracks max local deletiontime in sstable metadata
// records bloom_filter_fp_chance in metadata component
// remove data size and column count from data file (CASSANDRA-4180)
// tracks max/min column values (according to comparator)
// jb (2.0.1): switch from crc32 to adler32 for compression checksums
// checksum the compressed data
// ka (2.1.0): new Statistics.db file format
@ -145,7 +137,6 @@ public class BigFormat implements SSTableFormat
// la (3.0.0): new file name format
private final boolean isLatestVersion;
private final boolean hasPostCompressionAdlerChecksums;
private final boolean hasSamplingLevel;
private final boolean newStatsFile;
private final boolean hasAllAdlerChecksums;
@ -158,7 +149,6 @@ public class BigFormat implements SSTableFormat
super(instance,version);
isLatestVersion = version.compareTo(current_version) == 0;
hasPostCompressionAdlerChecksums = version.compareTo("jb") >= 0;
hasSamplingLevel = version.compareTo("ka") >= 0;
newStatsFile = version.compareTo("ka") >= 0;
hasAllAdlerChecksums = version.compareTo("ka") >= 0;
@ -173,12 +163,6 @@ public class BigFormat implements SSTableFormat
return isLatestVersion;
}
@Override
public boolean hasPostCompressionAdlerChecksums()
{
return hasPostCompressionAdlerChecksums;
}
@Override
public boolean hasSamplingLevel()
{

View File

@ -278,9 +278,10 @@ public class FileUtils
return canCleanDirectBuffers;
}
public static void clean(MappedByteBuffer buffer)
public static void clean(ByteBuffer buffer)
{
((DirectBuffer) buffer).cleaner().clean();
if (isCleanerAvailable() && buffer.isDirect())
((DirectBuffer)buffer).cleaner().clean();
}
public static void createDirectory(String directory)

View File

@ -30,6 +30,7 @@ import org.slf4j.LoggerFactory;
import org.apache.cassandra.io.FSReadError;
import org.apache.cassandra.io.sstable.format.SSTableWriter;
import org.apache.cassandra.utils.JVMStabilityInspector;
import sun.nio.ch.DirectBuffer;
public class MmappedSegmentedFile extends SegmentedFile
{

View File

@ -52,6 +52,11 @@ public class RandomAccessReader extends AbstractDataInput implements FileDataInp
protected final PoolingSegmentedFile owner;
protected RandomAccessReader(File file, int bufferSize, PoolingSegmentedFile owner) throws FileNotFoundException
{
this(file, bufferSize, false, owner);
}
protected RandomAccessReader(File file, int bufferSize, boolean useDirectBuffer, PoolingSegmentedFile owner) throws FileNotFoundException
{
this.owner = owner;
@ -79,13 +84,16 @@ public class RandomAccessReader extends AbstractDataInput implements FileDataInp
{
throw new FSReadError(e, filePath);
}
buffer = allocateBuffer(bufferSize);
buffer = allocateBuffer(bufferSize, useDirectBuffer);
buffer.limit(0);
}
protected ByteBuffer allocateBuffer(int bufferSize)
protected ByteBuffer allocateBuffer(int bufferSize, boolean useDirectBuffer)
{
return ByteBuffer.allocate((int) Math.min(fileLength, bufferSize));
int size = (int) Math.min(fileLength, bufferSize);
return useDirectBuffer
? ByteBuffer.allocate(size)
: ByteBuffer.allocateDirect(size);
}
public static RandomAccessReader open(File file, PoolingSegmentedFile owner)
@ -239,6 +247,8 @@ public class RandomAccessReader extends AbstractDataInput implements FileDataInp
public void deallocate()
{
bufferOffset += buffer.position();
FileUtils.clean(buffer);
buffer = null; // makes sure we don't use this after it's ostensibly closed
try

View File

@ -62,17 +62,15 @@ public class CompressedInputStream extends InputStream
private static final byte[] POISON_PILL = new byte[0];
private long totalCompressedBytesRead;
private final boolean hasPostCompressionAdlerChecksums;
/**
* @param source Input source to read compressed data from
* @param info Compression info
*/
public CompressedInputStream(InputStream source, CompressionInfo info, boolean hasPostCompressionAdlerChecksums)
public CompressedInputStream(InputStream source, CompressionInfo info)
{
this.info = info;
this.checksum = hasPostCompressionAdlerChecksums ? new Adler32() : new CRC32();
this.hasPostCompressionAdlerChecksums = hasPostCompressionAdlerChecksums;
this.checksum = new Adler32();
this.buffer = new byte[info.parameters.chunkLength()];
// buffer is limited to store up to 1024 chunks
this.dataBuffer = new ArrayBlockingQueue<byte[]>(Math.min(info.chunks.length, 1024));
@ -117,14 +115,7 @@ public class CompressedInputStream extends InputStream
// validate crc randomly
if (info.parameters.getCrcCheckChance() > ThreadLocalRandom.current().nextDouble())
{
if (hasPostCompressionAdlerChecksums)
{
checksum.update(compressed, 0, compressed.length - checksumBytes.length);
}
else
{
checksum.update(buffer, 0, validBufferBytes);
}
checksum.update(compressed, 0, compressed.length - checksumBytes.length);
System.arraycopy(compressed, compressed.length - checksumBytes.length, checksumBytes, 0, checksumBytes.length);
if (Ints.fromByteArray(checksumBytes) != (int) checksum.getValue())

View File

@ -76,7 +76,7 @@ public class CompressedStreamReader extends StreamReader
SSTableWriter writer = createWriter(cfs, totalSize, repairedAt, format);
CompressedInputStream cis = new CompressedInputStream(Channels.newInputStream(channel), compressionInfo, inputVersion.hasPostCompressionAdlerChecksums());
CompressedInputStream cis = new CompressedInputStream(Channels.newInputStream(channel), compressionInfo);
BytesReadTracker in = new BytesReadTracker(new DataInputStream(cis));
try
{

View File

@ -19,6 +19,8 @@ package org.apache.cassandra.utils;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.net.*;
import java.nio.ByteBuffer;
@ -26,10 +28,12 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.*;
import java.util.zip.Adler32;
import java.util.zip.Checksum;
import com.google.common.base.Joiner;
import com.google.common.collect.AbstractIterator;
import com.google.common.primitives.Ints;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -44,6 +48,8 @@ import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.io.IVersionedSerializer;
import org.apache.cassandra.io.compress.CompressedRandomAccessReader;
import org.apache.cassandra.io.compress.CompressionParameters;
import org.apache.cassandra.io.util.DataOutputBuffer;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.io.util.IAllocator;
@ -634,6 +640,67 @@ public class FBUtilities
checksum.update((v >>> 0) & 0xFF);
}
private static Method directUpdate;
static
{
try
{
directUpdate = Adler32.class.getDeclaredMethod("update", new Class[]{ByteBuffer.class});
directUpdate.setAccessible(true);
} catch (NoSuchMethodException e)
{
logger.warn("JVM doesn't support Adler32 byte buffer access");
directUpdate = null;
}
}
private static final ThreadLocal<byte[]> localDigestBuffer = new ThreadLocal<byte[]>()
{
@Override
protected byte[] initialValue()
{
return new byte[CompressionParameters.DEFAULT_CHUNK_LENGTH];
}
};
//Java 7 has this method but it's private till Java 8. Thanks JDK!
public static boolean supportsDirectChecksum()
{
return directUpdate != null;
}
public static void directCheckSum(Adler32 checksum, ByteBuffer bb)
{
if (directUpdate != null)
{
try
{
directUpdate.invoke(checksum, bb);
return;
} catch (IllegalAccessException e)
{
directUpdate = null;
logger.warn("JVM doesn't support Adler32 byte buffer access");
}
catch (InvocationTargetException e)
{
throw new RuntimeException(e);
}
}
//Fallback
byte[] buffer = localDigestBuffer.get();
int remaining;
while ((remaining = bb.remaining()) > 0)
{
remaining = Math.min(remaining, buffer.length);
ByteBufferUtil.arrayCopy(bb, bb.position(), buffer, 0, remaining);
bb.position(bb.position() + remaining);
checksum.update(buffer, 0, remaining);
}
}
public static long abs(long index)
{
long negbit = index >> 63;

View File

@ -80,7 +80,7 @@ public class CompressedRandomAccessReaderTest
writer.write("x".getBytes());
writer.close();
CompressedRandomAccessReader reader = CompressedRandomAccessReader.open(filename, new CompressionMetadata(filename + ".metadata", f.length(), true));
CompressedRandomAccessReader reader = CompressedRandomAccessReader.open(filename, new CompressionMetadata(filename + ".metadata", f.length()));
String res = reader.readLine();
assertEquals(res, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
assertEquals(40, res.length());
@ -123,7 +123,7 @@ public class CompressedRandomAccessReaderTest
assert f.exists();
RandomAccessReader reader = compressed
? CompressedRandomAccessReader.open(filename, new CompressionMetadata(filename + ".metadata", f.length(), true))
? CompressedRandomAccessReader.open(filename, new CompressionMetadata(filename + ".metadata", f.length()))
: RandomAccessReader.open(f);
String expected = "The quick brown fox jumps over the lazy dog";
assertEquals(expected.length(), reader.length());
@ -160,7 +160,7 @@ public class CompressedRandomAccessReaderTest
writer.close();
// open compression metadata and get chunk information
CompressionMetadata meta = new CompressionMetadata(metadata.getPath(), file.length(), true);
CompressionMetadata meta = new CompressionMetadata(metadata.getPath(), file.length());
CompressionMetadata.Chunk chunk = meta.chunkFor(0);
RandomAccessReader reader = CompressedRandomAccessReader.open(file.getPath(), meta);

View File

@ -17,30 +17,48 @@
*/
package org.apache.cassandra.io.compress;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.Random;
import com.google.common.io.Files;
import org.apache.cassandra.io.compress.ICompressor.WrappedArray;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
public class LZ4CompressorTest
public class CompressorTest
{
ICompressor compressor;
LZ4Compressor compressor;
ICompressor[] compressors = new ICompressor[] {
LZ4Compressor.create(Collections.<String, String>emptyMap()),
DeflateCompressor.create(Collections.<String, String>emptyMap()),
SnappyCompressor.create(Collections.<String, String>emptyMap())
};
@Before
public void setUp()
@Test
public void testAllCompressors() throws IOException
{
compressor = LZ4Compressor.create(Collections.<String, String>emptyMap());
for (ICompressor compressor : compressors)
{
this.compressor = compressor;
testEmptyArray();
testLongArray();
testShortArray();
testMappedFile();
}
}
public void test(byte[] data, int off, int len) throws IOException
{
final int outOffset = 3;
@ -61,19 +79,16 @@ public class LZ4CompressorTest
test(data, 0, data.length);
}
@Test
public void testEmptyArray() throws IOException
{
test(new byte[0]);
}
@Test
public void testShortArray() throws UnsupportedEncodingException, IOException
{
test("Cassandra".getBytes("UTF-8"), 1, 7);
}
@Test
public void testLongArray() throws UnsupportedEncodingException, IOException
{
byte[] data = new byte[1 << 20];
@ -81,4 +96,38 @@ public class LZ4CompressorTest
new Random(0).nextBytes(data);
test(data, 13, 1 << 19);
}
public void testMappedFile() throws IOException
{
byte[] data = new byte[1 << 20];
new Random().nextBytes(data);
//create a temp file
File temp = File.createTempFile("tempfile", ".tmp");
temp.deleteOnExit();
//Prepend some random bytes to the output and compress
final int outOffset = 3;
final WrappedArray out = new WrappedArray(new byte[outOffset + compressor.initialCompressedBufferLength(data.length)]);
new Random().nextBytes(out.buffer);
final int compressedLength = compressor.compress(data, 0, data.length, out, outOffset);
Files.write(out.buffer, temp);
MappedByteBuffer mappedData = Files.map(temp);
mappedData.position(outOffset);
mappedData.limit(compressedLength+outOffset);
ByteBuffer result = compressor.useDirectOutputByteBuffers()
? ByteBuffer.allocateDirect(data.length + 100)
: ByteBuffer.allocate(data.length + 100);
int length = compressor.uncompress(mappedData, result);
Assert.assertEquals(data.length, length);
for (int i = 0; i < length; i++)
{
Assert.assertEquals("Decompression mismatch at byte "+i, data[i], result.get());
}
}
}

View File

@ -111,7 +111,7 @@ public class CompressedInputStreamTest
// read buffer using CompressedInputStream
CompressionInfo info = new CompressionInfo(chunks, param);
CompressedInputStream input = new CompressedInputStream(new ByteArrayInputStream(toRead), info, true);
CompressedInputStream input = new CompressedInputStream(new ByteArrayInputStream(toRead), info);
DataInputStream in = new DataInputStream(input);
for (int i = 0; i < sections.size(); i++)