/** * 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(); }
private boolean canTruncate(SegmentChunk segmentChunk, long truncationOffset) { // We should only truncate those SegmentChunks that are entirely before the truncationOffset. An empty SegmentChunk // that starts exactly at the truncationOffset should be spared (this means we truncate the entire Segment), as // we need that SegmentChunk to determine the actual length of the Segment. return segmentChunk.getStartOffset() < truncationOffset && segmentChunk.getLastOffset() <= truncationOffset; }
/** * Adds multiple SegmentChunks. * * @param segmentChunks The SegmentChunks to add. These SegmentChunks must be in continuity of any existing SegmentChunks. */ synchronized void addChunks(List<SegmentChunk> segmentChunks) { Preconditions.checkState(!this.sealed, "Cannot add SegmentChunks for a Sealed Handle."); long expectedOffset = 0; if (this.segmentChunks.size() > 0) { expectedOffset = this.segmentChunks.get(this.segmentChunks.size() - 1).getLastOffset(); } else if (segmentChunks.size() > 0) { expectedOffset = segmentChunks.get(0).getStartOffset(); } for (SegmentChunk s : segmentChunks) { Preconditions.checkArgument(s.getStartOffset() == expectedOffset, "Invalid SegmentChunk StartOffset. Expected %s, given %s.", expectedOffset, s.getStartOffset()); expectedOffset += s.getLength(); } this.segmentChunks.addAll(segmentChunks); this.activeChunkHandle = null; }
/** * Adds a new SegmentChunk. * * @param segmentChunk The SegmentChunk to add. This SegmentChunk must be in continuity of any existing SegmentChunks. * @param activeChunkHandle The newly added SegmentChunk's write handle. */ synchronized void addChunk(SegmentChunk segmentChunk, SegmentHandle activeChunkHandle) { Preconditions.checkState(!this.sealed, "Cannot add SegmentChunks for a Sealed Handle."); if (this.segmentChunks.size() > 0) { long expectedOffset = this.segmentChunks.get(this.segmentChunks.size() - 1).getLastOffset(); Preconditions.checkArgument(segmentChunk.getStartOffset() == expectedOffset, "Invalid SegmentChunk StartOffset. Expected %s, given %s.", expectedOffset, segmentChunk.getStartOffset()); } // Update the SegmentChunk and its Handle atomically. Preconditions.checkNotNull(activeChunkHandle, "activeChunkHandle"); Preconditions.checkArgument(!activeChunkHandle.isReadOnly(), "Active SegmentChunk handle cannot be readonly."); Preconditions.checkArgument(activeChunkHandle.getSegmentName().equals(segmentChunk.getName()), "Active SegmentChunk handle must be for the last SegmentChunk."); this.activeChunkHandle = activeChunkHandle; this.segmentChunks.add(segmentChunk); }
@SneakyThrows(StreamingException.class) private void checkTruncatedSegment(StreamingException ex, RollingSegmentHandle handle, SegmentChunk segmentChunk) { if (ex != null && (Exceptions.unwrap(ex) instanceof StreamSegmentNotExistsException) || !segmentChunk.exists()) { // We ran into a SegmentChunk that does not exist (either marked as such or due to a failed read). segmentChunk.markInexistent(); String message = String.format("Offsets %d-%d have been deleted.", segmentChunk.getStartOffset(), segmentChunk.getLastOffset()); ex = new StreamSegmentTruncatedException(handle.getSegmentName(), message, ex); } if (ex != null) { throw ex; } }
if (writeHandle.isSealed() && segmentChunk.getLastOffset() == writeHandle.length()) { expectedExists = true; } else { expectedExists = segmentChunk.getLastOffset() > truncateOffset || (segmentChunk.getStartOffset() == segmentChunk.getLastOffset() && segmentChunk.getLastOffset() == truncateOffset); AssertExtensions.assertThrows( "Not expecting a read from a truncated SegmentChunk to work.", () -> s.read(readHandle, segmentChunk.getLastOffset() - 1, new byte[1], 0, 1), ex -> ex instanceof StreamSegmentTruncatedException);
int currentIndex = CollectionHelpers.binarySearch(chunks, s -> offset < s.getStartOffset() ? -1 : (offset >= s.getLastOffset() ? 1 : 0)); assert currentIndex >= 0 : "unable to locate first SegmentChunk index.";
@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); }
() -> h.addChunk(new SegmentChunk("s2", chunk.getLastOffset() + 1), new TestHandle("s2", false)), ex -> ex instanceof IllegalArgumentException); val chunk2 = new SegmentChunk("s2", chunk.getLastOffset()); chunk2.setLength(234L); h.addChunk(chunk2, new TestHandle("s2", false));