private void checkIfEmptyAndNotSealed(StreamSegmentExistsException ex, String chunkName) throws StreamSegmentException { // SegmentChunk exists, check if it's empty and not sealed. try { val si = this.baseStorage.getStreamSegmentInfo(chunkName); if (si.getLength() > 0 || si.isSealed()) { throw ex; } } catch (StreamSegmentNotExistsException notExists) { // nothing to do. } }
@SneakyThrows private Void readCompleteCallback(SegmentProperties r, Throwable ex) { if (ex != null) { ex = Exceptions.unwrap(ex); if (ex instanceof StreamSegmentNotExistsException) { // Cannot continue anymore (segment has been deleted). synchronized (this.readBuffer) { this.isClosed = true; } } else { // Unexpected exception. throw ex; } } else if (r.isSealed() && getReadOffset() >= r.getLength()) { // Cannot continue anymore (segment has been sealed and we reached its end). synchronized (this.readBuffer) { this.isClosed = true; } } return null; } }
private RollingSegmentHandle readHeader(SegmentProperties headerInfo, SegmentHandle headerHandle) throws StreamSegmentException { byte[] readBuffer = new byte[(int) headerInfo.getLength()]; this.baseStorage.read(headerHandle, 0, readBuffer, 0, readBuffer.length); RollingSegmentHandle handle = HandleSerializer.deserialize(readBuffer, headerHandle); if (headerInfo.isSealed()) { handle.markSealed(); } return handle; }
@SneakyThrows private Void attemptReconcile(Throwable ex, String segmentName, Duration timeout) { ex = Exceptions.unwrap(ex); boolean reconciled = false; if (isPossibleEndOfSegment(ex)) { // If we get a Sealed/Merged/NotExists exception, verify that the segment really is in that state. try { SegmentProperties sp = this.streamSegmentStore.getStreamSegmentInfo(segmentName, timeout) .get(timeout.toMillis(), TimeUnit.MILLISECONDS); reconciled = sp.isSealed() || sp.isDeleted(); } catch (Throwable ex2) { reconciled = isPossibleEndOfSegment(Exceptions.unwrap(ex2)); } } if (reconciled) { return null; } else { throw ex; } }
/** * Creates a new instance of the StreamSegmentMapOperation class for a non-transaction Segment. * * @param streamSegmentProperties Information about the StreamSegment. */ public StreamSegmentMapOperation(SegmentProperties streamSegmentProperties) { this.streamSegmentId = ContainerMetadata.NO_STREAM_SEGMENT_ID; this.streamSegmentName = streamSegmentProperties.getName(); this.startOffset = streamSegmentProperties.getStartOffset(); this.length = streamSegmentProperties.getLength(); this.sealed = streamSegmentProperties.isSealed(); this.attributes = streamSegmentProperties.getAttributes(); this.pinned = false; }
/** * Updates the metadata and based on the given SegmentProperties object. * * @param segmentProperties The SegmentProperties object to update from. */ private void updateMetadata(SegmentProperties segmentProperties) { this.metadata.setStorageLength(segmentProperties.getLength()); if (segmentProperties.isSealed() && !this.metadata.isSealedInStorage()) { this.metadata.markSealed(); this.metadata.markSealedInStorage(); } }
/** * Creates a new instance of the ReconciliationFailureException. * * @param message The message to include. * @param segmentMetadata The SegmentMetadata of the Segment for which reconciliation was attempted. * @param storageInfo Information about the segment in Storage. */ ReconciliationFailureException(String message, SegmentMetadata segmentMetadata, SegmentProperties storageInfo) { super(String.format( "%s Segment = %s, Storage: Length=%d(%s), Metadata: Length=%d(%s)", message, segmentMetadata.getName(), storageInfo.getLength(), getSealedMessage(storageInfo.isSealed()), segmentMetadata.getStorageLength(), getSealedMessage(segmentMetadata.isSealedInStorage()))); }
/** * Attempts to reconcile a StreamSegmentSealOperation. * * @param storageInfo The current state of the Segment in Storage. * @param timeout Timeout for the operation. * @return A CompletableFuture containing a FlushResult with the number of bytes reconciled, or failed with a ReconciliationFailureException, * if the operation cannot be reconciled, based on the in-memory metadata or the current state of the Segment in Storage. */ private CompletableFuture<WriterFlushResult> reconcileSealOperation(SegmentProperties storageInfo, Duration timeout) { // All we need to do is verify that the Segment is actually sealed in Storage. An exception to this rule is when // the segment has a length of 0, which means it may not have been created yet. if (storageInfo.isSealed() || storageInfo.getLength() == 0) { // Seal the Attribute Index (this is an idempotent operation so it's OK if it's already sealed). return sealAttributes(timeout) .thenApplyAsync(v -> { // Update metadata and the internal state (this also pops the first Op from the operation list). updateStatePostSeal(); return new WriterFlushResult(); }, this.executor); } else { // A Seal was encountered as an Operation that should have been processed (based on its offset), // but the Segment in Storage is not sealed. return Futures.failedFuture(new ReconciliationFailureException("Segment was supposed to be sealed in storage but it is not.", this.metadata, storageInfo)); } }
private CompletableFuture<Void> waitForSegmentInStorage(SegmentProperties sp, StreamSegmentStore readOnlyStore) { if (sp.getLength() == 0) { // Empty segments may or may not exist in Storage, so don't bother complicating ourselves with this. return CompletableFuture.completedFuture(null); } TimeoutTimer timer = new TimeoutTimer(TIMEOUT); AtomicBoolean tryAgain = new AtomicBoolean(true); return Futures.loop( tryAgain::get, () -> Futures .exceptionallyExpecting(readOnlyStore.getStreamSegmentInfo(sp.getName(), TIMEOUT), ex -> ex instanceof StreamSegmentNotExistsException, StreamSegmentInformation.builder().name(sp.getName()).build()) .thenCompose(storageProps -> { if (sp.isSealed()) { tryAgain.set(!storageProps.isSealed()); } else { tryAgain.set(sp.getLength() != storageProps.getLength()); } if (tryAgain.get() && !timer.hasRemaining()) { return Futures.<Void>failedFuture(new TimeoutException( String.format("Segment %s did not complete in Storage in the allotted time.", sp.getName()))); } else { return Futures.delayedFuture(Duration.ofMillis(100), executorService()); } }), executorService()); }
private void write00(SegmentInfo s, RevisionDataOutput output) throws IOException { output.writeLong(s.getSegmentId()); SegmentProperties sp = s.getProperties(); output.writeUTF(sp.getName()); output.writeLong(sp.getLength()); output.writeLong(sp.getStartOffset()); output.writeBoolean(sp.isSealed()); // We only serialize Core Attributes. Extended Attributes can be retrieved from the AttributeIndex. output.writeMap(Attributes.getCoreNonNullAttributes(sp.getAttributes()), RevisionDataOutput::writeUUID, RevisionDataOutput::writeLong); }
private void validateProperties(String stage, String segmentName, SegmentProperties sp, long expectedLength, boolean expectedSealed) { Assert.assertNotNull("No result from GetInfoOperation (" + stage + ").", sp); Assert.assertEquals("Unexpected name (" + stage + ").", segmentName, sp.getName()); Assert.assertEquals("Unexpected length (" + stage + ").", expectedLength, sp.getLength()); Assert.assertEquals("Unexpected sealed status (" + stage + ").", expectedSealed, sp.isSealed()); }
private Void doWrite(SegmentHandle handle, long offset, InputStream data, int length) throws StreamSegmentException { Preconditions.checkArgument(!handle.isReadOnly(), "handle must not be read-only."); long traceId = LoggerHelpers.traceEnter(log, "write", handle.getSegmentName(), offset, length); SegmentProperties si = doGetStreamSegmentInfo(handle.getSegmentName()); if (si.isSealed()) { throw new StreamSegmentSealedException(handle.getSegmentName()); } if (si.getLength() != offset) { throw new BadOffsetException(handle.getSegmentName(), si.getLength(), offset); } client.putObject(this.config.getBucket(), this.config.getRoot() + handle.getSegmentName(), Range.fromOffsetLength(offset, length), data); LoggerHelpers.traceLeave(log, "write", traceId); return null; }
protected void verifyFinalWriteOperationsFail(SegmentHandle handle, Storage storage) { AssertExtensions.assertSuppliedFutureThrows( "Seal was allowed on fenced Storage.", () -> storage.seal(handle, TIMEOUT), ex -> ex instanceof StorageNotPrimaryException); val si = storage.getStreamSegmentInfo(handle.getSegmentName(), TIMEOUT).join(); Assert.assertFalse("Segment was sealed after rejected call to seal.", si.isSealed()); AssertExtensions.assertSuppliedFutureThrows( "Delete was allowed on fenced Storage.", () -> storage.delete(handle, TIMEOUT), ex -> ex instanceof StorageNotPrimaryException); boolean exists = storage.exists(handle.getSegmentName(), TIMEOUT).join(); Assert.assertTrue("Segment was deleted after rejected call to delete.", exists); }
protected void assertEquals(String message, SegmentProperties expected, SegmentProperties actual) { Assert.assertEquals(message + " getName() mismatch.", expected.getName(), actual.getName()); Assert.assertEquals(message + " isDeleted() mismatch.", expected.isDeleted(), actual.isDeleted()); Assert.assertEquals(message + " getLength() mismatch.", expected.getLength(), actual.getLength()); Assert.assertEquals(message + " isSealed() mismatch.", expected.isSealed(), actual.isSealed()); Assert.assertEquals(message + " getStartOffset() mismatch.", expected.getStartOffset(), actual.getStartOffset()); }
private CompletableFuture<Void> waitForSegmentInStorage(SegmentProperties metadataProps, TestContext context) { if (metadataProps.getLength() == 0) { // Empty segments may or may not exist in Storage, so don't bother complicating ourselves with this. return CompletableFuture.completedFuture(null); } Function<SegmentProperties, Boolean> meetsConditions = storageProps -> storageProps.isSealed() == metadataProps.isSealed() && storageProps.getLength() >= metadataProps.getLength() && context.storageFactory.truncationOffsets.getOrDefault(metadataProps.getName(), 0L) >= metadataProps.getStartOffset(); AtomicBoolean canContinue = new AtomicBoolean(true); TimeoutTimer timer = new TimeoutTimer(TIMEOUT); return Futures.loop( canContinue::get, () -> Futures.exceptionallyExpecting( context.storage.getStreamSegmentInfo(metadataProps.getName(), TIMEOUT), ex -> ex instanceof StreamSegmentNotExistsException, StreamSegmentInformation.builder().name(metadataProps.getName()).build()) .thenCompose(storageProps -> { if (meetsConditions.apply(storageProps)) { canContinue.set(false); return CompletableFuture.completedFuture(null); } else if (!timer.hasRemaining()) { return Futures.failedFuture(new TimeoutException()); } else { return Futures.delayedFuture(Duration.ofMillis(10), executorService()); } }).thenRun(Runnables.doNothing()), executorService()); }
@Override public void getStreamSegmentInfo(GetStreamSegmentInfo getStreamSegmentInfo) { String segmentName = getStreamSegmentInfo.getSegmentName(); final String operation = "getStreamSegmentInfo"; if (!verifyToken(segmentName, getStreamSegmentInfo.getRequestId(), getStreamSegmentInfo.getDelegationToken(), operation)) { return; } segmentStore.getStreamSegmentInfo(segmentName, TIMEOUT) .thenAccept(properties -> { if (properties != null) { StreamSegmentInfo result = new StreamSegmentInfo(getStreamSegmentInfo.getRequestId(), properties.getName(), true, properties.isSealed(), properties.isDeleted(), properties.getLastModified().getTime(), properties.getLength(), properties.getStartOffset()); log.trace("Read stream segment info: {}", result); connection.send(result); } else { log.trace("getStreamSegmentInfo could not find segment {}", segmentName); connection.send(new StreamSegmentInfo(getStreamSegmentInfo.getRequestId(), segmentName, false, true, true, 0, 0, 0)); } }) .exceptionally(e -> handleException(getStreamSegmentInfo.getRequestId(), segmentName, operation, e)); }
private void checkActiveSegments(SegmentContainer container, int expectedCount) { val initialActiveSegments = container.getActiveSegments(); int ignoredSegments = 0; for (SegmentProperties sp : initialActiveSegments) { if (sp.getName().equals(EXPECTED_METADATA_SEGMENT_NAME)) { ignoredSegments++; continue; } val expectedSp = container.getStreamSegmentInfo(sp.getName(), TIMEOUT).join(); Assert.assertEquals("Unexpected length (from getActiveSegments) for segment " + sp.getName(), expectedSp.getLength(), sp.getLength()); Assert.assertEquals("Unexpected sealed (from getActiveSegments) for segment " + sp.getName(), expectedSp.isSealed(), sp.isSealed()); Assert.assertEquals("Unexpected deleted (from getActiveSegments) for segment " + sp.getName(), expectedSp.isDeleted(), sp.isDeleted()); SegmentMetadataComparer.assertSameAttributes("Unexpected attributes (from getActiveSegments) for segment " + sp.getName(), expectedSp.getAttributes(), sp); } Assert.assertEquals("Unexpected result from getActiveSegments with freshly created segments.", expectedCount + ignoredSegments, initialActiveSegments.size()); }
/** * Creates a new {@link StreamSegmentInformationBuilder} with information already populated from the given SegmentProperties. * * @param base The SegmentProperties to use as a base. * @return The Builder. */ public static StreamSegmentInformationBuilder from(SegmentProperties base) { return StreamSegmentInformation.builder() .name(base.getName()) .startOffset(base.getStartOffset()) .length(base.getLength()) .sealed(base.isSealed()) .deleted(base.isDeleted()) .lastModified(base.getLastModified()) .attributes(base.getAttributes()); }
/** * Tests the getStreamSegmentInfo() method. */ @Test public void testGetStreamSegmentInfo() { @Cleanup val context = new TestContext(); context.container.startAsync().awaitRunning(); // Non-existent segment. AssertExtensions.assertSuppliedFutureThrows( "Unexpected exception when the segment does not exist.", () -> context.container.getStreamSegmentInfo(SEGMENT_NAME, TIMEOUT), ex -> ex instanceof StreamSegmentNotExistsException); // Create a segment, add some data, set some attributes, "truncate" it and then seal it. val storageInfo = context.storage.create(SEGMENT_NAME, TIMEOUT) .thenCompose(handle -> context.storage.write(handle, 0, new ByteArrayInputStream(new byte[10]), 10, TIMEOUT)) .thenCompose(v -> context.storage.getStreamSegmentInfo(SEGMENT_NAME, TIMEOUT)).join(); val expectedInfo = StreamSegmentInformation.from(storageInfo) .startOffset(storageInfo.getLength() / 2) .attributes(ImmutableMap.of(UUID.randomUUID(), 100L, Attributes.EVENT_COUNT, 1L)) .build(); // Fetch the SegmentInfo from the ReadOnlyContainer and verify it is as expected. val actual = context.container.getStreamSegmentInfo(SEGMENT_NAME, TIMEOUT).join(); Assert.assertEquals("Unexpected Name.", expectedInfo.getName(), actual.getName()); Assert.assertEquals("Unexpected Length.", expectedInfo.getLength(), actual.getLength()); Assert.assertEquals("Unexpected Sealed status.", expectedInfo.isSealed(), actual.isSealed()); }
private StreamSegmentMetadata toMetadata(long segmentId, SegmentProperties sp) { StreamSegmentMetadata sm = new StreamSegmentMetadata(sp.getName(), segmentId, CONTAINER_ID); sm.updateAttributes(sp.getAttributes()); sm.setLength(sp.getLength()); sm.setStartOffset(sp.getStartOffset()); if (sp.isSealed()) { sm.markSealed(); } if (sp.isDeleted()) { sm.markDeleted(); } return sm; }