use of io.pravega.segmentstore.contracts.ReadResult in project pravega by pravega.
the class ContainerReadIndexTests method testCacheEviction.
/**
* Tests the ability to evict entries from the ReadIndex under various conditions:
* * If an entry is aged out
* * If an entry is pushed out because of cache space pressure.
* <p>
* This also verifies that certain entries, such as RedirectReadIndexEntries and entries after the Storage Offset are
* not removed.
* <p>
* The way this test goes is as follows (it's pretty subtle, because there aren't many ways to hook into the ReadIndex and see what it's doing)
* 1. It creates a bunch of segments, and populates them in storage (each) up to offset N/2-1 (this is called pre-storage)
* 2. It populates the ReadIndex for each of those segments from offset N/2 to offset N-1 (this is called post-storage)
* 3. It loads all the data from Storage into the ReadIndex, in entries of size equal to those already loaded in step #2.
* 3a. At this point, all the entries added in step #2 have Generations 0..A/4-1, and step #3 have generations A/4..A-1
* 4. Append more data at the end. This forces the generation to increase to 1.25A.
* 4a. Nothing should be evicted from the cache now, since the earliest items are all post-storage.
* 5. We 'touch' (read) the first 1/3 of pre-storage entries (offsets 0..N/4).
* 5a. At this point, those entries (offsets 0..N/6) will have the newest generations (1.25A..1.5A)
* 6. We append more data (equivalent to the data we touched)
* 6a. Nothing should be evicted, since those generations that were just eligible for removal were touched and bumped up.
* 7. We forcefully increase the current generation by 1 (without touching the ReadIndex)
* 7a. At this point, we expect all the pre-storage items, except the touched ones, to be evicted. This is generations 0.25A-0.75A.
* 8. Update the metadata and indicate that all the post-storage entries are now pre-storage and bump the generation by 0.75A.
* 8a. At this point, we expect all former post-storage items and pre-storage items to be evicted (in this order).
* <p>
* The final order of eviction (in terms of offsets, for each segment), is:
* * 0.25N-0.75N, 0.75N..N, N..1.25N, 0..0.25N, 1.25N..1.5N (remember that we added quite a bunch of items after the initial run).
*/
@Test
@SuppressWarnings("checkstyle:CyclomaticComplexity")
public void testCacheEviction() throws Exception {
// Create a CachePolicy with a set number of generations and a known max size.
// Each generation contains exactly one entry, so the number of generations is also the number of entries.
final int appendSize = 100;
// This also doubles as number of generations (each generation, we add one append for each segment).
final int entriesPerSegment = 100;
final int cacheMaxSize = SEGMENT_COUNT * entriesPerSegment * appendSize;
// 25% of the entries are beyond the StorageOffset
final int postStorageEntryCount = entriesPerSegment / 4;
// 75% of the entries are before the StorageOffset.
final int preStorageEntryCount = entriesPerSegment - postStorageEntryCount;
CachePolicy cachePolicy = new CachePolicy(cacheMaxSize, Duration.ofMillis(1000 * 2 * entriesPerSegment), Duration.ofMillis(1000));
// To properly test this, we want predictable storage reads.
ReadIndexConfig config = ConfigHelpers.withInfiniteCachePolicy(ReadIndexConfig.builder().with(ReadIndexConfig.STORAGE_READ_ALIGNMENT, appendSize)).build();
ArrayList<CacheKey> removedKeys = new ArrayList<>();
@Cleanup TestContext context = new TestContext(config, cachePolicy);
// Record every cache removal.
context.cacheFactory.cache.removeCallback = removedKeys::add;
// Create the segments (metadata + storage).
ArrayList<Long> segmentIds = createSegments(context);
createSegmentsInStorage(context);
// Populate the Storage with appropriate data.
byte[] preStorageData = new byte[preStorageEntryCount * appendSize];
for (long segmentId : segmentIds) {
UpdateableSegmentMetadata sm = context.metadata.getStreamSegmentMetadata(segmentId);
val handle = context.storage.openWrite(sm.getName()).join();
context.storage.write(handle, 0, new ByteArrayInputStream(preStorageData), preStorageData.length, TIMEOUT).join();
sm.setStorageLength(preStorageData.length);
sm.setLength(preStorageData.length);
}
// Callback that appends one entry at the end of the given segment id.
Consumer<Long> appendOneEntry = segmentId -> {
UpdateableSegmentMetadata sm = context.metadata.getStreamSegmentMetadata(segmentId);
byte[] data = new byte[appendSize];
long offset = sm.getLength();
sm.setLength(offset + data.length);
try {
context.readIndex.append(segmentId, offset, data);
} catch (StreamSegmentNotExistsException ex) {
throw new CompletionException(ex);
}
};
// Populate the ReadIndex with the Append entries (post-StorageOffset)
for (int i = 0; i < postStorageEntryCount; i++) {
segmentIds.forEach(appendOneEntry);
// Each time we make a round of appends (one per segment), we increment the generation in the CacheManager.
context.cacheManager.applyCachePolicy();
}
// Read all the data from Storage, making sure we carefully associate them with the proper generation.
for (int i = 0; i < preStorageEntryCount; i++) {
long offset = i * appendSize;
for (long segmentId : segmentIds) {
@Cleanup ReadResult result = context.readIndex.read(segmentId, offset, appendSize, TIMEOUT);
ReadResultEntry resultEntry = result.next();
Assert.assertEquals("Unexpected type of ReadResultEntry when trying to load up data into the ReadIndex Cache.", ReadResultEntryType.Storage, resultEntry.getType());
resultEntry.requestContent(TIMEOUT);
ReadResultEntryContents contents = resultEntry.getContent().get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
Assert.assertFalse("Not expecting more data to be available for reading.", result.hasNext());
Assert.assertEquals("Unexpected ReadResultEntry length when trying to load up data into the ReadIndex Cache.", appendSize, contents.getLength());
}
context.cacheManager.applyCachePolicy();
}
Assert.assertEquals("Not expecting any removed Cache entries at this point (cache is not full).", 0, removedKeys.size());
// Append more data (equivalent to all post-storage entries), and verify that NO entries are being evicted (we cannot evict post-storage entries).
for (int i = 0; i < postStorageEntryCount; i++) {
segmentIds.forEach(appendOneEntry);
context.cacheManager.applyCachePolicy();
}
Assert.assertEquals("Not expecting any removed Cache entries at this point (only eligible entries were post-storage).", 0, removedKeys.size());
// 'Touch' the first few entries read from storage. This should move them to the back of the queue (they won't be the first ones to be evicted).
int touchCount = preStorageEntryCount / 3;
for (int i = 0; i < touchCount; i++) {
long offset = i * appendSize;
for (long segmentId : segmentIds) {
@Cleanup ReadResult result = context.readIndex.read(segmentId, offset, appendSize, TIMEOUT);
ReadResultEntry resultEntry = result.next();
Assert.assertEquals("Unexpected type of ReadResultEntry when trying to load up data into the ReadIndex Cache.", ReadResultEntryType.Cache, resultEntry.getType());
}
}
// Append more data (equivalent to the amount of data we 'touched'), and verify that the entries we just touched are not being removed..
for (int i = 0; i < touchCount; i++) {
segmentIds.forEach(appendOneEntry);
context.cacheManager.applyCachePolicy();
}
Assert.assertEquals("Not expecting any removed Cache entries at this point (we touched old entries and they now have the newest generation).", 0, removedKeys.size());
// Increment the generations so that we are caught up to just before the generation where the "touched" items now live.
context.cacheManager.applyCachePolicy();
// We expect all but the 'touchCount' pre-Storage entries to be removed.
int expectedRemovalCount = (preStorageEntryCount - touchCount) * SEGMENT_COUNT;
Assert.assertEquals("Unexpected number of removed entries after having forced out all pre-storage entries.", expectedRemovalCount, removedKeys.size());
// Now update the metadata and indicate that all the post-storage data has been moved to storage.
segmentIds.forEach(segmentId -> {
UpdateableSegmentMetadata sm = context.metadata.getStreamSegmentMetadata(segmentId);
sm.setStorageLength(sm.getLength());
});
// We add one artificial entry, which we'll be touching forever and ever; this forces the CacheManager to
// update its current generation every time. We will be ignoring this entry for our test.
SegmentMetadata readSegment = context.metadata.getStreamSegmentMetadata(segmentIds.get(0));
appendOneEntry.accept(readSegment.getId());
// Now evict everything (whether by size of by aging out).
for (int i = 0; i < cachePolicy.getMaxGenerations(); i++) {
@Cleanup ReadResult result = context.readIndex.read(readSegment.getId(), readSegment.getLength() - appendSize, appendSize, TIMEOUT);
result.next();
context.cacheManager.applyCachePolicy();
}
int expectedRemovalCountPerSegment = entriesPerSegment + touchCount + postStorageEntryCount;
int expectedTotalRemovalCount = SEGMENT_COUNT * expectedRemovalCountPerSegment;
Assert.assertEquals("Unexpected number of removed entries after having forced out all the entries.", expectedTotalRemovalCount, removedKeys.size());
// Finally, verify that the evicted items are in the correct order (for each segment). See this test's description for details.
for (long segmentId : segmentIds) {
List<CacheKey> segmentRemovedKeys = removedKeys.stream().filter(key -> key.getStreamSegmentId() == segmentId).collect(Collectors.toList());
Assert.assertEquals("Unexpected number of removed entries for segment " + segmentId, expectedRemovalCountPerSegment, segmentRemovedKeys.size());
// The correct order of eviction (N=entriesPerSegment) is: 0.25N-0.75N, 0.75N..N, N..1.25N, 0..0.25N, 1.25N..1.5N.
// This is equivalent to the following tests
// 0.25N-1.25N
checkOffsets(segmentRemovedKeys, segmentId, 0, entriesPerSegment, entriesPerSegment * appendSize / 4, appendSize);
// 0..0.25N
checkOffsets(segmentRemovedKeys, segmentId, entriesPerSegment, entriesPerSegment / 4, 0, appendSize);
// 1.25N..1.5N
checkOffsets(segmentRemovedKeys, segmentId, entriesPerSegment + entriesPerSegment / 4, entriesPerSegment / 4, (int) (entriesPerSegment * appendSize * 1.25), appendSize);
}
}
use of io.pravega.segmentstore.contracts.ReadResult in project pravega by pravega.
the class ContainerReadIndexTests method testBatchedRead.
/**
* Tests the ability for the ReadIndex to batch multiple index entries together into a bigger read. This test
* writes a lot of very small appends to the index, then issues a full read (from the beginning) while configuring
* the read index to return results of no less than a particular size. As an added bonus, it also forces a Storage
* Read towards the end to make sure the ReadIndex doesn't coalesce those into the result as well.
*/
@Test
public void testBatchedRead() throws Exception {
final int totalAppendLength = 500 * 1000;
final int maxAppendLength = 100;
final int minReadLength = 16 * 1024;
final byte[] segmentData = new byte[totalAppendLength];
final Random rnd = new Random(0);
rnd.nextBytes(segmentData);
final ReadIndexConfig config = ConfigHelpers.withInfiniteCachePolicy(ReadIndexConfig.builder().with(ReadIndexConfig.MEMORY_READ_MIN_LENGTH, minReadLength)).build();
@Cleanup TestContext context = new TestContext(config, config.getCachePolicy());
// Create the segment in Storage and populate it with all the data (one segment is sufficient for this test).
final long segmentId = createSegment(0, context);
createSegmentsInStorage(context);
final UpdateableSegmentMetadata segmentMetadata = context.metadata.getStreamSegmentMetadata(segmentId);
val writeHandle = context.storage.openWrite(segmentMetadata.getName()).join();
context.storage.write(writeHandle, 0, new ByteArrayInputStream(segmentData), segmentData.length, TIMEOUT).join();
segmentMetadata.setStorageLength(segmentData.length);
// Add the contents of the segment to the read index using very small appends (same data as in Storage).
int writtenLength = 0;
int remainingLength = totalAppendLength;
int lastCacheOffset = -1;
while (remainingLength > 0) {
int appendLength = rnd.nextInt(maxAppendLength) + 1;
if (appendLength < remainingLength) {
// Make another append.
byte[] appendData = new byte[appendLength];
System.arraycopy(segmentData, writtenLength, appendData, 0, appendLength);
appendSingleWrite(segmentId, appendData, context);
writtenLength += appendLength;
remainingLength -= appendLength;
} else {
// This would be the last append. Don't add it, so force the read index to load it from Storage.
lastCacheOffset = writtenLength;
appendLength = remainingLength;
writtenLength += appendLength;
remainingLength = 0;
segmentMetadata.setLength(writtenLength);
}
}
// Check all the appended data.
@Cleanup ReadResult readResult = context.readIndex.read(segmentId, 0, totalAppendLength, TIMEOUT);
long expectedCurrentOffset = 0;
boolean encounteredStorageRead = false;
while (readResult.hasNext()) {
ReadResultEntry entry = readResult.next();
if (entry.getStreamSegmentOffset() < lastCacheOffset) {
Assert.assertEquals("Expecting only a Cache entry before switch offset.", ReadResultEntryType.Cache, entry.getType());
} else {
Assert.assertEquals("Expecting only a Storage entry on or after switch offset.", ReadResultEntryType.Storage, entry.getType());
entry.requestContent(TIMEOUT);
entry.getContent().get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
encounteredStorageRead = true;
}
// Check the entry contents.
byte[] entryData = new byte[entry.getContent().join().getLength()];
StreamHelpers.readAll(entry.getContent().join().getData(), entryData, 0, entryData.length);
AssertExtensions.assertArrayEquals("Unexpected data read at offset " + expectedCurrentOffset, segmentData, (int) expectedCurrentOffset, entryData, 0, entryData.length);
expectedCurrentOffset += entryData.length;
// cut short by the storage entry.
if (expectedCurrentOffset < lastCacheOffset) {
AssertExtensions.assertGreaterThanOrEqual("Expecting a ReadResultEntry of a minimum length for cache hit.", minReadLength, entryData.length);
}
}
Assert.assertEquals("Not encountered any storage reads, even though one was forced.", lastCacheOffset > 0, encounteredStorageRead);
}
use of io.pravega.segmentstore.contracts.ReadResult in project pravega by pravega.
the class ContainerReadIndexTests method testStorageReadTransactionNoCache.
// region Scenario-based tests
/**
* Tests the following Scenario, where the ReadIndex would either read from a bad offset or fail with an invalid offset
* when reading in certain conditions:
* * A segment has a transaction, which has N bytes written to it.
* * The transaction is merged into its parent segment at offset M > N.
* * At least one byte of the transaction is evicted from the cache
* * A read is issued to the parent segment for that byte that was evicted
* * The ReadIndex is supposed to issue a Storage Read with an offset inside the transaction range (so translate
* from the parent's offset to the transaction's offset). However, after the read, it is supposed to look like the
* data was read from the parent segment, so it should not expose the adjusted offset at all.
* <p>
* This very specific unit test is a result of a regression found during testing.
*/
@Test
public void testStorageReadTransactionNoCache() throws Exception {
CachePolicy cachePolicy = new CachePolicy(1, Duration.ZERO, Duration.ofMillis(1));
@Cleanup TestContext context = new TestContext(DEFAULT_CONFIG, cachePolicy);
// Create parent segment and one transaction
long parentId = createSegment(0, context);
UpdateableSegmentMetadata parentMetadata = context.metadata.getStreamSegmentMetadata(parentId);
long transactionId = createTransaction(parentMetadata, 1, context);
UpdateableSegmentMetadata transactionMetadata = context.metadata.getStreamSegmentMetadata(transactionId);
createSegmentsInStorage(context);
ByteArrayOutputStream writtenStream = new ByteArrayOutputStream();
// Write something to the transaction, and make sure it also makes its way to Storage.
byte[] transactionWriteData = getAppendData(transactionMetadata.getName(), transactionId, 0, 0);
appendSingleWrite(transactionId, transactionWriteData, context);
val handle = context.storage.openWrite(transactionMetadata.getName()).join();
context.storage.write(handle, 0, new ByteArrayInputStream(transactionWriteData), transactionWriteData.length, TIMEOUT).join();
transactionMetadata.setStorageLength(transactionMetadata.getLength());
// Write some data to the parent, and make sure it is more than what we write to the transaction (hence the 10).
for (int i = 0; i < 10; i++) {
byte[] parentWriteData = getAppendData(parentMetadata.getName(), parentId, i, i);
appendSingleWrite(parentId, parentWriteData, context);
writtenStream.write(parentWriteData);
}
// Seal & Begin-merge the transaction (do not seal in storage).
transactionMetadata.markSealed();
long mergeOffset = parentMetadata.getLength();
parentMetadata.setLength(mergeOffset + transactionMetadata.getLength());
context.readIndex.beginMerge(parentId, mergeOffset, transactionId);
transactionMetadata.markMerged();
writtenStream.write(transactionWriteData);
// Clear the cache.
context.cacheManager.applyCachePolicy();
// Issue read from the parent.
ReadResult rr = context.readIndex.read(parentId, mergeOffset, transactionWriteData.length, TIMEOUT);
Assert.assertTrue("Parent Segment read indicates no data available.", rr.hasNext());
ByteArrayOutputStream readStream = new ByteArrayOutputStream();
long expectedOffset = mergeOffset;
while (rr.hasNext()) {
ReadResultEntry entry = rr.next();
Assert.assertEquals("Unexpected offset for read result entry.", expectedOffset, entry.getStreamSegmentOffset());
Assert.assertEquals("Served read result entry is not from storage.", ReadResultEntryType.Storage, entry.getType());
// Request contents and store for later use.
entry.requestContent(TIMEOUT);
ReadResultEntryContents contents = entry.getContent().get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
byte[] readBuffer = new byte[contents.getLength()];
StreamHelpers.readAll(contents.getData(), readBuffer, 0, readBuffer.length);
readStream.write(readBuffer);
expectedOffset += contents.getLength();
}
byte[] readData = readStream.toByteArray();
Assert.assertArrayEquals("Unexpected data read back.", transactionWriteData, readData);
}
use of io.pravega.segmentstore.contracts.ReadResult in project pravega by pravega.
the class ContainerReadIndexTests method checkReadIndex.
private void checkReadIndex(String testId, HashMap<Long, ByteArrayOutputStream> segmentContents, TestContext context) throws Exception {
for (long segmentId : segmentContents.keySet()) {
long startOffset = context.metadata.getStreamSegmentMetadata(segmentId).getStartOffset();
long segmentLength = context.metadata.getStreamSegmentMetadata(segmentId).getLength();
byte[] expectedData = segmentContents.get(segmentId).toByteArray();
if (startOffset > 0) {
@Cleanup ReadResult truncatedResult = context.readIndex.read(segmentId, 0, 1, TIMEOUT);
val first = truncatedResult.next();
Assert.assertEquals("Read request for a truncated offset did not start with a Truncated ReadResultEntryType.", ReadResultEntryType.Truncated, first.getType());
AssertExtensions.assertThrows("Truncate ReadResultEntryType did not throw when getContent() was invoked.", () -> {
first.requestContent(TIMEOUT);
return first.getContent();
}, ex -> ex instanceof StreamSegmentTruncatedException);
}
long expectedCurrentOffset = startOffset;
@Cleanup ReadResult readResult = context.readIndex.read(segmentId, expectedCurrentOffset, (int) (segmentLength - expectedCurrentOffset), TIMEOUT);
Assert.assertTrue(testId + ": Empty read result for segment " + segmentId, readResult.hasNext());
while (readResult.hasNext()) {
ReadResultEntry readEntry = readResult.next();
AssertExtensions.assertGreaterThan(testId + ": getRequestedReadLength should be a positive integer for segment " + segmentId, 0, readEntry.getRequestedReadLength());
Assert.assertEquals(testId + ": Unexpected value from getStreamSegmentOffset for segment " + segmentId, expectedCurrentOffset, readEntry.getStreamSegmentOffset());
// Since this is a non-sealed segment, we only expect Cache or Storage read result entries.
Assert.assertTrue(testId + ": Unexpected type of ReadResultEntry for non-sealed segment " + segmentId, readEntry.getType() == ReadResultEntryType.Cache || readEntry.getType() == ReadResultEntryType.Storage);
if (readEntry.getType() == ReadResultEntryType.Cache) {
Assert.assertTrue(testId + ": getContent() did not return a completed future (ReadResultEntryType.Cache) for segment" + segmentId, readEntry.getContent().isDone() && !readEntry.getContent().isCompletedExceptionally());
} else if (readEntry.getType() == ReadResultEntryType.Storage) {
Assert.assertFalse(testId + ": getContent() did not return a non-completed future (ReadResultEntryType.Storage) for segment" + segmentId, readEntry.getContent().isDone() && !readEntry.getContent().isCompletedExceptionally());
}
// Request content, in case it wasn't returned yet.
readEntry.requestContent(TIMEOUT);
ReadResultEntryContents readEntryContents = readEntry.getContent().get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
AssertExtensions.assertGreaterThan(testId + ": getContent() returned an empty result entry for segment " + segmentId, 0, readEntryContents.getLength());
byte[] actualData = new byte[readEntryContents.getLength()];
StreamHelpers.readAll(readEntryContents.getData(), actualData, 0, actualData.length);
AssertExtensions.assertArrayEquals(testId + ": Unexpected data read from segment " + segmentId + " at offset " + expectedCurrentOffset, expectedData, (int) expectedCurrentOffset, actualData, 0, readEntryContents.getLength());
expectedCurrentOffset += readEntryContents.getLength();
}
Assert.assertTrue(testId + ": ReadResult was not closed post-full-consumption for segment" + segmentId, readResult.isClosed());
}
}
use of io.pravega.segmentstore.contracts.ReadResult in project pravega by pravega.
the class ContainerReadIndexTests method testConcurrentReadTransactionStorageMerge.
/**
* Tests the following scenario, where the Read Index has a read from a portion in a parent segment where a transaction
* was just merged (fully in storage), but the read request might result in either an ObjectClosedException or
* StreamSegmentNotExistsException:
* * A Parent Segment has a Transaction with some data in it, and at least 1 byte of data not in cache.
* * The Transaction is begin-merged in the parent (Tier 1 only).
* * A Read Request is issued to the Parent for the range of data from the Transaction, which includes the 1 byte not in cache.
* * The Transaction is fully merged (Tier 2).
* * The Read Request is invoked and its content requested. This should correctly retrieve the data from the Parent
* Segment in Storage, and not attempt to access the now-defunct Transaction segment.
*/
@Test
public void testConcurrentReadTransactionStorageMerge() throws Exception {
CachePolicy cachePolicy = new CachePolicy(1, Duration.ZERO, Duration.ofMillis(1));
@Cleanup TestContext context = new TestContext(DEFAULT_CONFIG, cachePolicy);
// Create parent segment and one transaction
long parentId = createSegment(0, context);
UpdateableSegmentMetadata parentMetadata = context.metadata.getStreamSegmentMetadata(parentId);
long transactionId = createTransaction(parentMetadata, 1, context);
UpdateableSegmentMetadata transactionMetadata = context.metadata.getStreamSegmentMetadata(transactionId);
createSegmentsInStorage(context);
// Write something to the transaction, and make sure it also makes its way to Storage.
byte[] writeData = getAppendData(transactionMetadata.getName(), transactionId, 0, 0);
appendSingleWrite(transactionId, writeData, context);
val transactionWriteHandle = context.storage.openWrite(transactionMetadata.getName()).join();
context.storage.write(transactionWriteHandle, 0, new ByteArrayInputStream(writeData), writeData.length, TIMEOUT).join();
transactionMetadata.setStorageLength(transactionMetadata.getLength());
// Seal & Begin-merge the transaction (do not seal in storage).
transactionMetadata.markSealed();
parentMetadata.setLength(transactionMetadata.getLength());
context.readIndex.beginMerge(parentId, 0, transactionId);
transactionMetadata.markMerged();
// Clear the cache.
context.cacheManager.applyCachePolicy();
// Issue read from the parent and fetch the first entry (there should only be one).
ReadResult rr = context.readIndex.read(parentId, 0, writeData.length, TIMEOUT);
Assert.assertTrue("Parent Segment read indicates no data available.", rr.hasNext());
ReadResultEntry entry = rr.next();
Assert.assertEquals("Unexpected offset for read result entry.", 0, entry.getStreamSegmentOffset());
Assert.assertEquals("Served read result entry is not from storage.", ReadResultEntryType.Storage, entry.getType());
// Merge the transaction in storage & complete-merge it.
transactionMetadata.markSealed();
transactionMetadata.markSealedInStorage();
transactionMetadata.markDeleted();
context.storage.seal(transactionWriteHandle, TIMEOUT).join();
val parentWriteHandle = context.storage.openWrite(parentMetadata.getName()).join();
context.storage.concat(parentWriteHandle, 0, transactionWriteHandle.getSegmentName(), TIMEOUT).join();
parentMetadata.setStorageLength(parentMetadata.getLength());
context.readIndex.completeMerge(parentId, transactionId);
// Attempt to extract data from the read.
entry.requestContent(TIMEOUT);
ReadResultEntryContents contents = entry.getContent().get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
byte[] readData = new byte[contents.getLength()];
StreamHelpers.readAll(contents.getData(), readData, 0, readData.length);
Assert.assertArrayEquals("Unexpected data read from parent segment.", writeData, readData);
}
Aggregations