@Override public SegmentOutputStream createOutputStreamForSegment(Segment segment, Consumer<Segment> segmentSealedCallback, EventWriterConfig config, String delegationToken) { SegmentOutputStreamImpl result = new SegmentOutputStreamImpl(segment.getScopedName(), controller, cf, UUID.randomUUID(), segmentSealedCallback, getRetryFromConfig(config), delegationToken); try { result.getConnection(); } catch (RetriesExhaustedException | SegmentSealedException | NoSuchSegmentException e) { log.warn("Initial connection attempt failure. Suppressing.", e); } return result; }
/** * @see SegmentOutputStream#close() */ @Override public void close() throws SegmentSealedException { if (state.isClosed()) { return; } log.debug("Closing writer: {}", writerId); // Wait until all the inflight events are written flush(); state.setClosed(true); ClientConnection connection = state.getConnection(); if (connection != null) { connection.close(); } }
connection = Futures.getThrowingException(getConnection()); } catch (SegmentSealedException | NoSuchSegmentException e) { } catch (ConnectionFailedException e) { log.warn("Connection " + writerId + " failed due to: ", e); reconnect(); // As the message is inflight, this will perform the retransmission.
/** * @see SegmentOutputStream#flush() */ @Override public void flush() throws SegmentSealedException { int numInflight = state.getNumInflight(); log.debug("Flushing writer: {} with {} inflight events", writerId, numInflight); if (numInflight != 0) { try { ClientConnection connection = Futures.getThrowingException(getConnection()); connection.send(new KeepAlive()); } catch (Exception e) { failConnection(e); } state.waitForInflight(); Exceptions.checkNotClosed(state.isClosed(), this); /* SegmentSealedException is thrown if either of the below conditions are true - resendToSuccessorsCallback has been invoked. - the segment corresponds to an aborted Transaction. */ if (state.needSuccessors.get() || (StreamSegmentNameUtils.isTransactionSegment(segmentName) && state.isAlreadySealed())) { throw new SegmentSealedException(segmentName + " sealed for writer " + writerId); } } }
@Test(timeout = 10000) public void testSealedBeforeFlush() throws Exception { UUID cid = UUID.randomUUID(); PravegaNodeUri uri = new PravegaNodeUri("endpoint", SERVICE_PORT); MockConnectionFactoryImpl cf = new MockConnectionFactoryImpl(); cf.setExecutor(executorService()); MockController controller = new MockController(uri.getEndpoint(), uri.getPort(), cf); ClientConnection connection = mock(ClientConnection.class); cf.provideConnection(uri, connection); InOrder order = Mockito.inOrder(connection); SegmentOutputStreamImpl output = new SegmentOutputStreamImpl(SEGMENT, controller, cf, cid, segmentSealedCallback, RETRY_SCHEDULE, ""); output.reconnect(); order.verify(connection).send(new SetupAppend(1, cid, SEGMENT, "")); cf.getProcessor(uri).appendSetup(new AppendSetup(1, SEGMENT, cid, 0)); ByteBuffer data = getBuffer("test"); CompletableFuture<Void> ack = new CompletableFuture<>(); output.write(PendingEvent.withoutHeader(null, data, ack)); order.verify(connection).send(new Append(SEGMENT, cid, 1, 1, Unpooled.wrappedBuffer(data), null)); assertEquals(false, ack.isDone()); cf.getProcessor(uri).segmentIsSealed(new WireCommands.SegmentIsSealed(1, SEGMENT, "SomeException")); output.getUnackedEventsOnSeal(); // this is invoked by the segmentSealedCallback. AssertExtensions.assertThrows(SegmentSealedException.class, () -> output.flush()); }
InOrder order = Mockito.inOrder(connection); SegmentOutputStreamImpl output = new SegmentOutputStreamImpl(TXN_SEGMENT, controller, cf, cid, segmentSealedCallback, RETRY_SCHEDULE, ""); output.reconnect(); order.verify(connection).send(new SetupAppend(1, cid, TXN_SEGMENT, "")); cf.getProcessor(uri).appendSetup(new AppendSetup(1, TXN_SEGMENT, cid, 0)); output.write(PendingEvent.withoutHeader(null, data, ack1)); order.verify(connection).send(new Append(TXN_SEGMENT, cid, 1, 1, Unpooled.wrappedBuffer(data), null)); output.write(PendingEvent.withoutHeader(null, data, ack2)); AssertExtensions.assertThrows(SegmentSealedException.class, () -> output.close());
@Test public void testOverSizedWriteFails() throws ConnectionFailedException, SegmentSealedException { UUID cid = UUID.randomUUID(); PravegaNodeUri uri = new PravegaNodeUri("endpoint", SERVICE_PORT); MockConnectionFactoryImpl cf = new MockConnectionFactoryImpl(); cf.setExecutor(executorService()); MockController controller = new MockController(uri.getEndpoint(), uri.getPort(), cf); ClientConnection connection = mock(ClientConnection.class); cf.provideConnection(uri, connection); @Cleanup SegmentOutputStreamImpl output = new SegmentOutputStreamImpl(SEGMENT, controller, cf, cid, segmentSealedCallback, RETRY_SCHEDULE, ""); output.reconnect(); verify(connection).send(new SetupAppend(1, cid, SEGMENT, "")); cf.getProcessor(uri).appendSetup(new AppendSetup(1, SEGMENT, cid, 0)); ByteBuffer data = ByteBuffer.allocate(PendingEvent.MAX_WRITE_SIZE + 1); CompletableFuture<Void> acked = new CompletableFuture<>(); try { output.write(PendingEvent.withoutHeader("routingKey", data, acked)); fail("Did not throw"); } catch (IllegalArgumentException e) { // expected } assertEquals(false, acked.isDone()); verifyNoMoreInteractions(connection); }
@Test(timeout = 10000) public void testConnectAndFailedSetupAppend() throws Exception { UUID cid = UUID.randomUUID(); PravegaNodeUri uri = new PravegaNodeUri("endpoint", SERVICE_PORT); MockConnectionFactoryImpl cf = new MockConnectionFactoryImpl(); ScheduledExecutorService executor = mock(ScheduledExecutorService.class); implementAsDirectExecutor(executor); // Ensure task submitted to executor is run inline. cf.setExecutor(executor); MockController controller = new MockController(uri.getEndpoint(), uri.getPort(), cf); ClientConnection connection = mock(ClientConnection.class); doThrow(ConnectionFailedException.class).doNothing().when(connection).send(any(SetupAppend.class)); cf.provideConnection(uri, connection); @Cleanup SegmentOutputStreamImpl output = new SegmentOutputStreamImpl(SEGMENT, controller, cf, cid, segmentSealedCallback, RETRY_SCHEDULE, ""); output.reconnect(); verify(connection).send(new SetupAppend(1, cid, SEGMENT, "")); verify(connection).send(new SetupAppend(2, cid, SEGMENT, "")); }
@Test(timeout = 10000) public void testConnectAndFailedSetupAppendDueToTruncation() throws Exception { AtomicBoolean callbackInvoked = new AtomicBoolean(); Consumer<Segment> resendToSuccessorsCallback = segment -> { callbackInvoked.set(true); }; UUID cid = UUID.randomUUID(); PravegaNodeUri uri = new PravegaNodeUri("endpoint", SERVICE_PORT); MockConnectionFactoryImpl cf = new MockConnectionFactoryImpl(); ScheduledExecutorService executor = mock(ScheduledExecutorService.class); implementAsDirectExecutor(executor); // Ensure task submitted to executor is run inline. cf.setExecutor(executor); MockController controller = new MockController(uri.getEndpoint(), uri.getPort(), cf); ClientConnection connection = mock(ClientConnection.class); cf.provideConnection(uri, connection); @Cleanup SegmentOutputStreamImpl output = new SegmentOutputStreamImpl(SEGMENT, controller, cf, cid, resendToSuccessorsCallback, RETRY_SCHEDULE, ""); output.reconnect(); verify(connection).send(new SetupAppend(1, cid, SEGMENT, "")); cf.getProcessor(uri).noSuchSegment(new WireCommands.NoSuchSegment(1, SEGMENT, "SomeException")); CompletableFuture<ClientConnection> connectionFuture = output.getConnection(); assertThrows(NoSuchSegmentException.class, () -> Futures.getThrowingException(connectionFuture)); assertTrue(callbackInvoked.get()); }
private void failConnection(Throwable e) { log.info("Failing connection for writer {} with exception {}", writerId, e.toString()); state.failConnection(Exceptions.unwrap(e)); reconnect(); }
@Override public SegmentOutputStream createOutputStreamForSegment(Segment segment, EventWriterConfig config, String delegationToken) { return new SegmentOutputStreamImpl(segment.getScopedName(), controller, cf, UUID.randomUUID(), Callbacks::doNothing, getRetryFromConfig(config), delegationToken); }
private void invokeResendCallBack(WireCommand wireCommand) { if (state.needSuccessors.compareAndSet(false, true)) { Retry.indefinitelyWithExpBackoff(retrySchedule.getInitialMillis(), retrySchedule.getMultiplier(), retrySchedule.getMaxDelay(), t -> log.error(writerId + " to invoke resendToSuccessors callback: ", t)) .runInExecutor(() -> { log.debug("Invoking resendToSuccessors call back for {} on writer {}", wireCommand, writerId); resendToSuccessorsCallback.accept(Segment.fromScopedName(getSegmentName())); }, connectionFactory.getInternalExecutor()) .thenRun(() -> { log.trace("Release inflight latch for writer {}", writerId); state.waitingInflight.release(); }); } }
private void sendAndVerifyEvent(UUID cid, ClientConnection connection, SegmentOutputStreamImpl output, ByteBuffer data, int num) throws SegmentSealedException, ConnectionFailedException { CompletableFuture<Void> acked = new CompletableFuture<>(); output.write(PendingEvent.withoutHeader(null, data, acked)); verify(connection).send(new Append(SEGMENT, cid, num, 1, Unpooled.wrappedBuffer(data), null)); assertEquals(false, acked.isDone()); }
@Test(timeout = 10000) public void testNoSuchSegment() throws Exception { UUID cid = UUID.randomUUID(); PravegaNodeUri uri = new PravegaNodeUri("endpoint", SERVICE_PORT); MockConnectionFactoryImpl cf = new MockConnectionFactoryImpl(); cf.setExecutor(executorService()); MockController controller = new MockController(uri.getEndpoint(), uri.getPort(), cf); ClientConnection connection = mock(ClientConnection.class); cf.provideConnection(uri, connection); InOrder order = Mockito.inOrder(connection); SegmentOutputStreamImpl output = new SegmentOutputStreamImpl(SEGMENT, controller, cf, cid, segmentSealedCallback, RETRY_SCHEDULE, ""); output.reconnect(); order.verify(connection).send(new SetupAppend(1, cid, SEGMENT, "")); cf.getProcessor(uri).appendSetup(new AppendSetup(1, SEGMENT, cid, 0)); ByteBuffer data = getBuffer("test"); //Write an Event. CompletableFuture<Void> ack = new CompletableFuture<>(); output.write(PendingEvent.withoutHeader(null, data, ack)); order.verify(connection).send(new Append(SEGMENT, cid, 1, 1, Unpooled.wrappedBuffer(data), null)); assertEquals(false, ack.isDone()); //writer is not complete until a response from Segment Store is received. //Simulate a No Such Segment while waiting on flush. AssertExtensions.assertBlocks(() -> { AssertExtensions.assertThrows(SegmentSealedException.class, () -> output.flush()); }, () -> { cf.getProcessor(uri).noSuchSegment(new WireCommands.NoSuchSegment(1, SEGMENT, "SomeException")); output.getUnackedEventsOnSeal(); }); AssertExtensions.assertThrows(SegmentSealedException.class, () -> output.flush()); }
@Test(timeout = 10000) public void testClose() throws ConnectionFailedException, SegmentSealedException { UUID cid = UUID.randomUUID(); PravegaNodeUri uri = new PravegaNodeUri("endpoint", SERVICE_PORT); MockConnectionFactoryImpl cf = new MockConnectionFactoryImpl(); cf.setExecutor(executorService()); MockController controller = new MockController(uri.getEndpoint(), uri.getPort(), cf); ClientConnection connection = mock(ClientConnection.class); cf.provideConnection(uri, connection); SegmentOutputStreamImpl output = new SegmentOutputStreamImpl(SEGMENT, controller, cf, cid, segmentSealedCallback, RETRY_SCHEDULE, ""); output.reconnect(); verify(connection).send(new SetupAppend(1, cid, SEGMENT, "")); cf.getProcessor(uri).appendSetup(new AppendSetup(1, SEGMENT, cid, 0)); ByteBuffer data = getBuffer("test"); CompletableFuture<Void> acked = new CompletableFuture<>(); output.write(PendingEvent.withoutHeader(null, data, acked)); verify(connection).send(new Append(SEGMENT, cid, 1, 1, Unpooled.wrappedBuffer(data), null)); assertEquals(false, acked.isDone()); AssertExtensions.assertBlocks(() -> output.close(), () -> cf.getProcessor(uri).dataAppended(new WireCommands.DataAppended(cid, 1, 0))); assertEquals(false, acked.isCompletedExceptionally()); assertEquals(true, acked.isDone()); verify(connection, Mockito.atMost(1)).send(new WireCommands.KeepAlive()); verify(connection).close(); verifyNoMoreInteractions(connection); }
ClientConnection connection = mock(ClientConnection.class); cf.provideConnection(uri, connection); SegmentOutputStreamImpl output = new SegmentOutputStreamImpl(SEGMENT, controller, cf, cid, segmentSealedCallback, RETRY_SCHEDULE, ""); output.reconnect(); InOrder inOrder = Mockito.inOrder(connection); inOrder.verify(connection).send(new SetupAppend(1, cid, SEGMENT, "")); output.write(PendingEvent.withoutHeader(null, data, acked)); output.write(PendingEvent.withoutHeader(null, data, acked2)); }, () -> { cf.getProcessor(uri).appendSetup(new AppendSetup(2, SEGMENT, cid, 0));
@Test(timeout = 10000) public void testConnectAndConnectionDrop() throws Exception { UUID cid = UUID.randomUUID(); PravegaNodeUri uri = new PravegaNodeUri("endpoint", SERVICE_PORT); MockConnectionFactoryImpl cf = new MockConnectionFactoryImpl(); ScheduledExecutorService executor = mock(ScheduledExecutorService.class); implementAsDirectExecutor(executor); // Ensure task submitted to executor is run inline. cf.setExecutor(executor); MockController controller = new MockController(uri.getEndpoint(), uri.getPort(), cf); ClientConnection connection = mock(ClientConnection.class); cf.provideConnection(uri, connection); @Cleanup SegmentOutputStreamImpl output = new SegmentOutputStreamImpl(SEGMENT, controller, cf, cid, segmentSealedCallback, RETRY_SCHEDULE, ""); output.reconnect(); verify(connection).send(new SetupAppend(1, cid, SEGMENT, "")); cf.getProcessor(uri).connectionDropped(); // simulate a connection dropped //Ensure setup Append is invoked on the executor. verify(connection).send(new SetupAppend(2, cid, SEGMENT, "")); }
/** * Establish a connection and wait for it to be setup. (Retries built in) */ CompletableFuture<ClientConnection> getConnection() throws SegmentSealedException { if (state.isClosed()) { throw new IllegalStateException("SegmentOutputStream is already closed", state.getException()); } if (state.isAlreadySealed()) { throw new SegmentSealedException(this.segmentName); } if (state.getConnection() == null) { reconnect(); } CompletableFuture<ClientConnection> future = new CompletableFuture<>(); state.setupConnection.register(future); return future; }
@Override public SegmentOutputStream createOutputStreamForTransaction(Segment segment, UUID txId, EventWriterConfig config, String delegationToken) { return new SegmentOutputStreamImpl(StreamSegmentNameUtils.getTransactionNameFromId(segment.getScopedName(), txId), controller, cf, UUID.randomUUID(), nopSegmentSealedCallback, getRetryFromConfig(config), delegationToken); }
@Test(timeout = 10000) public void testSealedAfterFlush() throws Exception { UUID cid = UUID.randomUUID(); PravegaNodeUri uri = new PravegaNodeUri("endpoint", SERVICE_PORT); MockConnectionFactoryImpl cf = new MockConnectionFactoryImpl(); cf.setExecutor(executorService()); MockController controller = new MockController(uri.getEndpoint(), uri.getPort(), cf); ClientConnection connection = mock(ClientConnection.class); cf.provideConnection(uri, connection); InOrder order = Mockito.inOrder(connection); SegmentOutputStreamImpl output = new SegmentOutputStreamImpl(SEGMENT, controller, cf, cid, segmentSealedCallback, RETRY_SCHEDULE, ""); output.reconnect(); order.verify(connection).send(new SetupAppend(1, cid, SEGMENT, "")); cf.getProcessor(uri).appendSetup(new AppendSetup(1, SEGMENT, cid, 0)); ByteBuffer data = getBuffer("test"); CompletableFuture<Void> ack = new CompletableFuture<>(); output.write(PendingEvent.withoutHeader(null, data, ack)); order.verify(connection).send(new Append(SEGMENT, cid, 1, 1, Unpooled.wrappedBuffer(data), null)); assertEquals(false, ack.isDone()); AssertExtensions.assertBlocks(() -> { AssertExtensions.assertThrows(SegmentSealedException.class, () -> output.flush()); }, () -> { cf.getProcessor(uri).segmentIsSealed(new WireCommands.SegmentIsSealed(1, SEGMENT, "SomeException")); output.getUnackedEventsOnSeal(); }); AssertExtensions.assertThrows(SegmentSealedException.class, () -> output.flush()); }