private void ensureNotSealed(RollingSegmentHandle handle) throws StreamSegmentSealedException { if (handle.isSealed()) { throw new StreamSegmentSealedException(handle.getSegmentName()); } }
private void rollover(RollingSegmentHandle handle) throws StreamSegmentException { Preconditions.checkArgument(handle.getHeaderHandle() != null, "Cannot rollover a Segment with no header."); Preconditions.checkArgument(!handle.isReadOnly(), "Cannot rollover using a read-only handle."); Preconditions.checkArgument(!handle.isSealed(), "Cannot rollover a Sealed Segment."); log.debug("Rolling over '{}'.", handle); sealActiveChunk(handle); try { createChunk(handle); } catch (StreamSegmentExistsException ex) { // It may be possible that a concurrent rollover request using a different handle (either from this instance // or another) has already created the new chunk. Refresh the handle and try again. This is usually the case // with concurrent Storage instances trying to modify the same segment at the same time. int chunkCount = handle.chunks().size(); handle.refresh(openHandle(handle.getSegmentName(), false)); if (chunkCount == handle.chunks().size()) { // Nothing changed; re-throw the exception. throw ex; } else { // We've just refreshed the handle and picked up the latest chunk. Move on. log.warn("Aborted rollover due to concurrent rollover detected ('{}').", handle); } } }
/** * Updates the contents of this handle with information from the given one. * * @param source The RollingSegmentHandle to update from. */ synchronized void refresh(RollingSegmentHandle source) { Preconditions.checkArgument(source.getSegmentName().equals(this.getSegmentName()), "SegmentName mismatch."); if (this.readOnly == source.readOnly) { // Update the header handle, but only if both this handle and the source one have the same read-only flag. // Otherwise we risk attaching a read-only header handle to a read-write handle or vice-versa. this.headerHandle = source.headerHandle; } this.segmentChunks = new ArrayList<>(source.chunks()); setHeaderLength(source.getHeaderLength()); if (source.isSealed()) { markSealed(); } if (source.isDeleted()) { markDeleted(); } }
@Override public SegmentProperties getStreamSegmentInfo(String segmentName) throws StreamSegmentException { val handle = (RollingSegmentHandle) openRead(segmentName); return StreamSegmentInformation .builder() .name(handle.getSegmentName()) .sealed(handle.isSealed()) .length(handle.length()) .build(); }
@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); }
if (h.isReadOnly() && !h.isSealed() && offset + length > h.length()) {
Preconditions.checkState(source.isSealed(), "Cannot concat segment '%s' into '%s' because it is not sealed.", sourceSegment, target.getSegmentName()); if (source.length() == 0) {
/** * Tests the ability to truncate Sealed Segments. */ @Test public void testTruncateSealed() throws Exception { // Write small and large writes, alternatively. @Cleanup val baseStorage = new TestStorage(); @Cleanup val s = new RollingStorage(baseStorage, DEFAULT_ROLLING_POLICY); s.initialize(1); // Create a Segment, write some data, then seal it. s.create(SEGMENT_NAME); val appendHandle = (RollingSegmentHandle) s.openWrite(SEGMENT_NAME); val writeStream = new ByteArrayOutputStream(); populate(s, appendHandle, writeStream); s.seal(appendHandle); byte[] writtenData = writeStream.toByteArray(); val truncateHandle = (RollingSegmentHandle) s.openWrite(SEGMENT_NAME); Assert.assertTrue("Handle not read-only after sealing.", truncateHandle.isReadOnly()); Assert.assertTrue("Handle not sealed after sealing.", truncateHandle.isSealed()); // Test that truncate works in this scenario. testProgressiveTruncate(truncateHandle, truncateHandle, writtenData, s, baseStorage); }
/** * 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()); }
if (!h.isSealed()) { val writeHandle = h.isReadOnly() ? (RollingSegmentHandle) openWrite(handle.getSegmentName()) : h; seal(writeHandle);
if (writeHandle.isSealed() && segmentChunk.getLastOffset() == writeHandle.length()) { expectedExists = true; } else {
Assert.assertEquals("Unexpected rolling policy.", DEFAULT_ROLLING_POLICY, h.getRollingPolicy()); Assert.assertTrue("Unexpected value for isReadOnly.", h.isReadOnly()); Assert.assertFalse("Unexpected value for isSealed.", h.isSealed()); Assert.assertFalse("Unexpected value for isDeleted.", h.isDeleted()); Assert.assertTrue("Unexpected value for isSealed.", h.isSealed());