/** * Gets a value indicating the current length of the Segment, in bytes. * * @return The length. */ synchronized long length() { SegmentChunk lastSegmentChunk = lastChunk(); return lastSegmentChunk == null ? 0L : lastSegmentChunk.getLastOffset(); }
/** * Sets the Active SegmentChunk handle. * * @param handle The handle. Must not be read-only and for the last SegmentChunk. */ synchronized void setActiveChunkHandle(SegmentHandle handle) { Preconditions.checkArgument(handle == null || !handle.isReadOnly(), "Active SegmentChunk handle cannot be readonly."); SegmentChunk last = lastChunk(); Preconditions.checkState(last != null, "Cannot set an Active SegmentChunk handle when there are no SegmentChunks."); Preconditions.checkArgument(handle == null || handle.getSegmentName().equals(last.getName()), "Active SegmentChunk handle must be for the last SegmentChunk."); this.activeChunkHandle = handle; }
private boolean shouldConcatNatively(RollingSegmentHandle source, RollingSegmentHandle target) { if (source.getHeaderHandle() == null) { // Source does not have a Header, hence we cannot do Header concat. return true; } SegmentChunk lastSource = source.lastChunk(); SegmentChunk lastTarget = target.lastChunk(); return lastSource != null && lastSource.getStartOffset() == 0 && lastTarget != null && !lastTarget.isSealed() && lastTarget.getLength() + lastSource.getLength() <= target.getRollingPolicy().getMaxLength(); }
private void sealActiveChunk(RollingSegmentHandle handle) throws StreamSegmentException { SegmentHandle activeChunk = handle.getActiveChunkHandle(); SegmentChunk last = handle.lastChunk(); if (activeChunk != null && !last.isSealed()) { this.baseStorage.seal(activeChunk); handle.setActiveChunkHandle(null); last.markSealed(); log.debug("Sealed active SegmentChunk '{}' for '{}'.", activeChunk.getSegmentName(), handle.getSegmentName()); } }
@Override public SegmentHandle openWrite(String segmentName) throws StreamSegmentException { long traceId = LoggerHelpers.traceEnter(log, "openWrite", segmentName); val handle = openHandle(segmentName, false); // Finally, open the Active SegmentChunk for writing. SegmentChunk last = handle.lastChunk(); if (last != null && !last.isSealed()) { val activeHandle = this.baseStorage.openWrite(last.getName()); handle.setActiveChunkHandle(activeHandle); } LoggerHelpers.traceLeave(log, "openWrite", traceId, handle); return handle; }
private void unsealLastChunkIfNecessary(RollingSegmentHandle handle) throws StreamSegmentException { SegmentChunk last = handle.lastChunk(); if (last == null || !last.isSealed()) { // Nothing to do. return; } SegmentHandle activeChunk = handle.getActiveChunkHandle(); boolean needsHandleUpdate = activeChunk == null; if (needsHandleUpdate) { // We didn't have a pointer to the active chunk's Handle because the chunk was sealed before open-write. activeChunk = this.baseStorage.openWrite(last.getName()); } try { this.baseStorage.unseal(activeChunk); } catch (UnsupportedOperationException e) { log.warn("Unable to unseal SegmentChunk '{}' since base storage does not support unsealing.", last); return; } last.markUnsealed(); if (needsHandleUpdate) { activeChunk = this.baseStorage.openWrite(last.getName()); handle.setActiveChunkHandle(activeChunk); } log.debug("Unsealed active SegmentChunk '{}' for '{}'.", activeChunk.getSegmentName(), handle.getSegmentName()); }
@Override public void write(SegmentHandle handle, long offset, InputStream data, int length) throws StreamSegmentException { val h = asWritableHandle(handle); ensureNotDeleted(h); ensureNotSealed(h); ensureOffset(h, offset); long traceId = LoggerHelpers.traceEnter(log, "write", handle, offset, length); // We run this in a loop because we may have to split the write over multiple SegmentChunks in order to avoid exceeding // any SegmentChunk's maximum length. int bytesWritten = 0; while (bytesWritten < length) { if (h.getActiveChunkHandle() == null || h.lastChunk().getLength() >= h.getRollingPolicy().getMaxLength()) { rollover(h); } SegmentChunk last = h.lastChunk(); int writeLength = (int) Math.min(length - bytesWritten, h.getRollingPolicy().getMaxLength() - last.getLength()); assert writeLength > 0 : "non-positive write length"; long chunkOffset = offset + bytesWritten - last.getStartOffset(); this.baseStorage.write(h.getActiveChunkHandle(), chunkOffset, data, writeLength); last.increaseLength(writeLength); bytesWritten += writeLength; } LoggerHelpers.traceLeave(log, "write", traceId, handle, offset, bytesWritten); }
SegmentChunk lastTarget = target.lastChunk(); if (lastTarget == null || lastTarget.isSealed()) { SegmentChunk lastSource = source.lastChunk(); this.baseStorage.concat(target.getActiveChunkHandle(), target.lastChunk().getLength(), lastSource.getName()); target.lastChunk().increaseLength(lastSource.getLength()); if (source.getHeaderHandle() != null) { try {
@Override public void truncate(SegmentHandle handle, long truncationOffset) throws StreamSegmentException { // Delete all SegmentChunks which are entirely before the truncation offset. RollingSegmentHandle h = asReadableHandle(handle); ensureNotDeleted(h); // The only acceptable case where we allow a read-only handle is if the Segment is sealed, since openWrite() will // only return a read-only handle in that case. Preconditions.checkArgument(h.isSealed() || !h.isReadOnly(), "Can only truncate with a read-only handle if the Segment is Sealed."); if (h.getHeaderHandle() == null) { // No header means the Segment is made up of a single SegmentChunk. We can't do anything. return; } long traceId = LoggerHelpers.traceEnter(log, "truncate", h, truncationOffset); Preconditions.checkArgument(truncationOffset >= 0 && truncationOffset <= h.length(), "truncationOffset must be non-negative and at most the length of the Segment."); val last = h.lastChunk(); if (last != null && canTruncate(last, truncationOffset) && !h.isSealed()) { // If we were asked to truncate the entire (non-sealed) Segment, then rollover at this point so we can delete // all existing data. rollover(h); // We are free to delete all chunks. deleteChunks(h, s -> canTruncate(s, truncationOffset)); } else { // Either we were asked not to truncate the whole segment, or we were, and the Segment is sealed. If the latter, // then the Header is also sealed, we could not have done a quick rollover; as such we have no option but to // preserve the last chunk so that we can recalculate the length of the Segment if we need it again. deleteChunks(h, s -> canTruncate(s, truncationOffset) && s.getLastOffset() < h.length()); } LoggerHelpers.traceLeave(log, "truncate", traceId, h, truncationOffset); }
/** * Tests the ability of the Handle to refresh based on information from another similar handle. */ @Test public void testRefresh() { val headerHandle = new TestHandle(HEADER_NAME, true); val target = new RollingSegmentHandle(headerHandle, DEFAULT_ROLLING_POLICY, Collections.singletonList(new SegmentChunk("s1", 0L))); val source = new RollingSegmentHandle(headerHandle, DEFAULT_ROLLING_POLICY, Arrays.asList( new SegmentChunk("s1", 0L), new SegmentChunk("s2", 100L))); source.chunks().get(0).setLength(100); source.markSealed(); source.setHeaderLength(1000); source.setActiveChunkHandle(new TestHandle(source.lastChunk().getName(), false)); target.refresh(source); Assert.assertEquals("Unexpected getHeaderLength()", source.getHeaderLength(), target.getHeaderLength()); AssertExtensions.assertListEquals("Unexpected chunks()", source.chunks(), target.chunks(), Object::equals); Assert.assertTrue("Unexpected isSealed.", target.isSealed()); Assert.assertNull("Not expecting any ActiveSegmentHandle to be copied.", target.getActiveChunkHandle()); }
AssertExtensions.assertListEquals("Unexpected contents for chunks().", Collections.singletonList(chunk), h.chunks(), Object::equals); Assert.assertEquals("Unexpected lastChunk.", chunk, h.lastChunk()); Assert.assertEquals("Unexpected value for length() after adding two SegmentChunk.", chunk2.getStartOffset() + chunk2.getLength(), h.length()); Assert.assertEquals("Unexpected lastChunk.", chunk2, h.lastChunk()); h.lastChunk().markInexistent(); Assert.assertFalse("Unexpected value from isDeleted after last SegmentChunk marked as inexistent.", h.isDeleted());
val headerHandle = new TestHandle(HEADER_NAME, true); val h = new RollingSegmentHandle(headerHandle, DEFAULT_ROLLING_POLICY, new ArrayList<>()); Assert.assertNull("Unexpected lastChunk for empty handle.", h.lastChunk()); Assert.assertEquals("Unexpected contents in chunks() for empty handle.", 0, h.chunks().size()); Assert.assertEquals("Unexpected value for length() for empty handle.", 0, h.length());