From e38ed04188b7f5a6fdd1d182839796fe5d0bc9ea Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Tue, 14 May 2024 15:09:27 +1200 Subject: [PATCH 01/67] [Java] Use private method to update state in PublicationImage. --- .../java/io/aeron/driver/PublicationImage.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java b/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java index f33dbd0660..e65eb7f9bb 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java +++ b/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java @@ -449,7 +449,7 @@ RawLog rawLog() void activate() { timeOfLastStateChangeNs = cachedNanoClock.nanoTime(); - state = State.ACTIVE; + state(State.ACTIVE); } /** @@ -475,7 +475,7 @@ void deactivate() timeOfLastSmNs = nowNs - smTimeoutNs - 1; } - state = State.DRAINING; + state(State.DRAINING); } } @@ -677,7 +677,7 @@ void checkEosForDrainTransition(final long nowNs) isSendingEosSm = true; timeOfLastSmNs = nowNs - smTimeoutNs - 1; - state = State.DRAINING; + state(State.DRAINING); } } } @@ -864,7 +864,7 @@ public void onTimeEvent(final long timeNs, final long timesMs, final DriverCondu timeOfLastStateChangeNs = timeNs; isReceiverReleaseTriggered = true; - state = State.LINGER; + state(State.LINGER); } break; @@ -873,7 +873,7 @@ public void onTimeEvent(final long timeNs, final long timesMs, final DriverCondu { conductor.cleanupImage(this); timeOfLastStateChangeNs = timeNs; - state = State.DONE; + state(State.DONE); } break; @@ -891,6 +891,11 @@ public boolean hasReachedEndOfLife() return hasReceiverReleased && State.DONE == state; } + private void state(final State state) + { + this.state = state; + } + private boolean isDrained() { final long rebuildPosition = this.rebuildPosition.get(); From 3eae27d45c1432d663519c3ba80e4283abe63bd6 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Mon, 20 May 2024 12:23:07 +1200 Subject: [PATCH 02/67] [Java] Add basic test and command to invalidate and image from user code. Prevent new data being inserted into an invalidated image. Add logging. --- .../java/io/aeron/agent/CmdInterceptor.java | 7 +- .../java/io/aeron/agent/DriverEventCode.java | 4 +- .../io/aeron/agent/DriverEventDissector.java | 6 + .../main/java/io/aeron/ClientConductor.java | 19 ++ .../src/main/java/io/aeron/DriverProxy.java | 38 ++++ .../src/main/java/io/aeron/Image.java | 10 + .../src/main/java/io/aeron/Subscription.java | 5 + .../aeron/command/ControlProtocolEvents.java | 7 +- .../command/InvalidateImageFlyweight.java | 198 ++++++++++++++++++ .../src/test/java/io/aeron/ImageTest.java | 33 +++ .../io/aeron/driver/ClientCommandAdapter.java | 15 ++ .../java/io/aeron/driver/DriverConductor.java | 31 +++ .../io/aeron/driver/PublicationImage.java | 17 ++ .../main/java/io/aeron/driver/Receiver.java | 11 + .../java/io/aeron/driver/ReceiverProxy.java | 12 ++ .../java/io/aeron/ImageInvalidationTest.java | 110 ++++++++++ 16 files changed, 520 insertions(+), 3 deletions(-) create mode 100644 aeron-client/src/main/java/io/aeron/command/InvalidateImageFlyweight.java create mode 100644 aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java diff --git a/aeron-agent/src/main/java/io/aeron/agent/CmdInterceptor.java b/aeron-agent/src/main/java/io/aeron/agent/CmdInterceptor.java index 9bbe4ebf2d..ba651e9081 100644 --- a/aeron-agent/src/main/java/io/aeron/agent/CmdInterceptor.java +++ b/aeron-agent/src/main/java/io/aeron/agent/CmdInterceptor.java @@ -51,7 +51,8 @@ class CmdInterceptor CMD_IN_REMOVE_RCV_DESTINATION, CMD_OUT_ON_CLIENT_TIMEOUT, CMD_IN_TERMINATE_DRIVER, - CMD_IN_REMOVE_DESTINATION_BY_ID); + CMD_IN_REMOVE_DESTINATION_BY_ID, + CMD_IN_INVALIDATE_IMAGE); @SuppressWarnings("methodlength") @Advice.OnMethodEnter @@ -158,6 +159,10 @@ static void logCmd(final int msgTypeId, final DirectBuffer buffer, final int ind case REMOVE_DESTINATION_BY_ID: LOGGER.log(CMD_IN_REMOVE_DESTINATION_BY_ID, buffer, index, length); break; + + case INVALIDATE_IMAGE: + LOGGER.log(CMD_IN_INVALIDATE_IMAGE, buffer, index, length); + break; } } } diff --git a/aeron-agent/src/main/java/io/aeron/agent/DriverEventCode.java b/aeron-agent/src/main/java/io/aeron/agent/DriverEventCode.java index af61b78bbc..e52007339c 100644 --- a/aeron-agent/src/main/java/io/aeron/agent/DriverEventCode.java +++ b/aeron-agent/src/main/java/io/aeron/agent/DriverEventCode.java @@ -215,7 +215,9 @@ public enum DriverEventCode implements EventCode /** * Remove destination by id */ - CMD_IN_REMOVE_DESTINATION_BY_ID(56, DriverEventDissector::dissectCommand); + CMD_IN_REMOVE_DESTINATION_BY_ID(56, DriverEventDissector::dissectCommand), + + CMD_IN_INVALIDATE_IMAGE(56, DriverEventDissector::dissectCommand); static final int EVENT_CODE_TYPE = EventCodeType.DRIVER.getTypeCode(); diff --git a/aeron-agent/src/main/java/io/aeron/agent/DriverEventDissector.java b/aeron-agent/src/main/java/io/aeron/agent/DriverEventDissector.java index c3af7d3a58..18779cdae0 100644 --- a/aeron-agent/src/main/java/io/aeron/agent/DriverEventDissector.java +++ b/aeron-agent/src/main/java/io/aeron/agent/DriverEventDissector.java @@ -56,6 +56,7 @@ final class DriverEventDissector private static final ClientTimeoutFlyweight CLIENT_TIMEOUT = new ClientTimeoutFlyweight(); private static final TerminateDriverFlyweight TERMINATE_DRIVER = new TerminateDriverFlyweight(); private static final DestinationByIdMessageFlyweight DESTINATION_BY_ID = new DestinationByIdMessageFlyweight(); + private static final InvalidateImageFlyweight INVALIDATE_IMAGE = new InvalidateImageFlyweight(); static final String CONTEXT = "DRIVER"; @@ -220,6 +221,11 @@ static void dissectCommand( dissectDestinationById(builder); break; + case CMD_IN_INVALIDATE_IMAGE: + INVALIDATE_IMAGE.wrap(buffer, offset + encodedLength); + dissectInvalidateImage(builder); + break; + default: builder.append("COMMAND_UNKNOWN: ").append(code); break; diff --git a/aeron-client/src/main/java/io/aeron/ClientConductor.java b/aeron-client/src/main/java/io/aeron/ClientConductor.java index 24c5fd8063..e159cf61b7 100644 --- a/aeron-client/src/main/java/io/aeron/ClientConductor.java +++ b/aeron-client/src/main/java/io/aeron/ClientConductor.java @@ -1420,6 +1420,25 @@ void onStaticCounter(final long correlationId, final int counterId) resourceByRegIdMap.put(correlationId, (Integer)counterId); } + void invalidateImage(final long correlationId, final long position, final String reason) + { + clientLock.lock(); + try + { + ensureActive(); + ensureNotReentrant(); + + // TODO, check reason length?? + + final long registrationId = driverProxy.invalidateImage(correlationId, position, reason); + awaitResponse(registrationId); + } + finally + { + clientLock.unlock(); + } + } + private void ensureActive() { if (isClosed) diff --git a/aeron-client/src/main/java/io/aeron/DriverProxy.java b/aeron-client/src/main/java/io/aeron/DriverProxy.java index 989fa6c955..adc19e8e91 100644 --- a/aeron-client/src/main/java/io/aeron/DriverProxy.java +++ b/aeron-client/src/main/java/io/aeron/DriverProxy.java @@ -39,6 +39,7 @@ public final class DriverProxy private final DestinationByIdMessageFlyweight destinationByIdMessage = new DestinationByIdMessageFlyweight(); private final CounterMessageFlyweight counterMessage = new CounterMessageFlyweight(); private final StaticCounterMessageFlyweight staticCounterMessageFlyweight = new StaticCounterMessageFlyweight(); + private final InvalidateImageFlyweight invalidateImage = new InvalidateImageFlyweight(); private final RingBuffer toDriverCommandBuffer; /** @@ -491,6 +492,43 @@ public boolean terminateDriver(final DirectBuffer tokenBuffer, final int tokenOf return false; } + /** + * Invalidate a specific image. + * + * @param imageCorrelationId of the image to be invalidated + * @param position of the image when invalidation occurred + * @param reason user supplied reason for invalidation, reported back to publication + * @return the correlationId of the request for invalidation. + */ + public long invalidateImage( + final long imageCorrelationId, + final long position, + final String reason) + { + final int length = InvalidateImageFlyweight.computeLength(reason); + final int index = toDriverCommandBuffer.tryClaim(INVALIDATE_IMAGE, length); + + if (index < 0) + { + throw new AeronException("could not write invalidate image command"); + } + + final long correlationId = toDriverCommandBuffer.nextCorrelationId(); + + invalidateImage + .wrap(toDriverCommandBuffer.buffer(), index) + .clientId(clientId) + .correlationId(correlationId) + .imageCorrelationId(imageCorrelationId) + .position(position) + .reason(reason); + + toDriverCommandBuffer.commit(index); + + return correlationId; + } + + /** * {@inheritDoc} */ diff --git a/aeron-client/src/main/java/io/aeron/Image.java b/aeron-client/src/main/java/io/aeron/Image.java index 4c5f5a8573..2909b637c3 100644 --- a/aeron-client/src/main/java/io/aeron/Image.java +++ b/aeron-client/src/main/java/io/aeron/Image.java @@ -788,6 +788,16 @@ public int rawPoll(final RawBlockHandler handler, final int blockLengthLimit) return length; } + /** + * Force the driver to disconnect this image from the remote publication. + * + * @param reason an error message to be forwarded back to the publication. + */ + public void invalidate(final String reason) + { + subscription.invalidate(correlationId, position(), reason); + } + private UnsafeBuffer activeTermBuffer(final long position) { return termBuffers[LogBufferDescriptor.indexByPosition(position, positionBitsToShift)]; diff --git a/aeron-client/src/main/java/io/aeron/Subscription.java b/aeron-client/src/main/java/io/aeron/Subscription.java index cea1b5b583..e6c6f73385 100644 --- a/aeron-client/src/main/java/io/aeron/Subscription.java +++ b/aeron-client/src/main/java/io/aeron/Subscription.java @@ -620,6 +620,11 @@ Image removeImage(final long correlationId) return removedImage; } + void invalidate(final long correlationId, final long position, final String reason) + { + conductor.invalidateImage(correlationId, position, reason); + } + /** * {@inheritDoc} */ diff --git a/aeron-client/src/main/java/io/aeron/command/ControlProtocolEvents.java b/aeron-client/src/main/java/io/aeron/command/ControlProtocolEvents.java index 759d02392c..c62c5334b4 100644 --- a/aeron-client/src/main/java/io/aeron/command/ControlProtocolEvents.java +++ b/aeron-client/src/main/java/io/aeron/command/ControlProtocolEvents.java @@ -100,10 +100,15 @@ public class ControlProtocolEvents */ public static final int ADD_STATIC_COUNTER = 0x0F; + /** + * Invalidate an image. + */ + public static final int INVALIDATE_IMAGE = 0x10; + /** * Remove a destination by registration id. */ - public static final int REMOVE_DESTINATION_BY_ID = 0x10; + public static final int REMOVE_DESTINATION_BY_ID = 0x11; // Media Driver to Clients diff --git a/aeron-client/src/main/java/io/aeron/command/InvalidateImageFlyweight.java b/aeron-client/src/main/java/io/aeron/command/InvalidateImageFlyweight.java new file mode 100644 index 0000000000..356e8077b2 --- /dev/null +++ b/aeron-client/src/main/java/io/aeron/command/InvalidateImageFlyweight.java @@ -0,0 +1,198 @@ +/* + * Copyright 2014-2024 Real Logic Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.aeron.command; + +import io.aeron.exceptions.ControlProtocolException; +import org.agrona.MutableDirectBuffer; + +import static io.aeron.ErrorCode.MALFORMED_COMMAND; +import static org.agrona.BitUtil.SIZE_OF_INT; +import static org.agrona.BitUtil.SIZE_OF_LONG; + +/** + * Control message to invalidate an image for a subscription. + * + *
+ *   0                   1                   2                   3
+ *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  |                          Client ID                            |
+ *  |                                                               |
+ *  +---------------------------------------------------------------+
+ *  |                       Correlation ID                          |
+ *  |                                                               |
+ *  +---------------------------------------------------------------+
+ *  |                    Image Correlation ID                       |
+ *  |                                                               |
+ *  +---------------------------------------------------------------+
+ *  |                           Position                            |
+ *  |                                                               |
+ *  +---------------------------------------------------------------+
+ *  |                        Reason Length                          |
+ *  +---------------------------------------------------------------+
+ *  |                        Reason (ASCII)                       ...
+ *  ...                                                             |
+ *  +---------------------------------------------------------------+
+ * 
+ */ +public class InvalidateImageFlyweight extends CorrelatedMessageFlyweight +{ + private static final int IMAGE_CORRELATION_ID_FIELD_OFFSET = CORRELATION_ID_FIELD_OFFSET + SIZE_OF_LONG; + private static final int POSITION_FIELD_OFFSET = IMAGE_CORRELATION_ID_FIELD_OFFSET + SIZE_OF_LONG; + private static final int REASON_FIELD_OFFSET = POSITION_FIELD_OFFSET + SIZE_OF_LONG; + private static final int MINIMUM_SIZE = REASON_FIELD_OFFSET + SIZE_OF_INT; + + /** + * Wrap the buffer at a given offset for updates. + * + * @param buffer to wrap. + * @param offset at which the message begins. + * @return this for a fluent API. + */ + public InvalidateImageFlyweight wrap(final MutableDirectBuffer buffer, final int offset) + { + super.wrap(buffer, offset); + return this; + } + + /** + * Get image correlation id field. + * + * @return image correlation id field. + */ + public long imageCorrelationId() + { + return buffer.getLong(offset + IMAGE_CORRELATION_ID_FIELD_OFFSET); + } + + /** + * Put image correlation id field. + * + * @param position new image correlation id value. + * @return this for a fluent API. + */ + public InvalidateImageFlyweight imageCorrelationId(final long position) + { + buffer.putLong(offset + IMAGE_CORRELATION_ID_FIELD_OFFSET, position); + return this; + } + + /** + * Get position field. + * + * @return position field. + */ + public long position() + { + return buffer.getLong(offset + POSITION_FIELD_OFFSET); + } + + /** + * Put position field. + * + * @param position new position value. + * @return this for a fluent API. + */ + public InvalidateImageFlyweight position(final long position) + { + buffer.putLong(offset + POSITION_FIELD_OFFSET, position); + return this; + } + + /** + * Put reason field as ASCII. Include the reason length in the message. + * + * @param reason for invalidating the image. + * @return this for a fluent API. + */ + public InvalidateImageFlyweight reason(final String reason) + { + buffer.putStringAscii(offset + REASON_FIELD_OFFSET, reason); + return this; + } + + + /** + * Get reason field as ASCII. + * + * @return reason for invalidating the image. + */ + public String reason() + { + return buffer.getStringAscii(offset + REASON_FIELD_OFFSET); + } + + /** + * Length of the reason text. + * + * @return length of the reason text. + */ + public int reasonBufferLength() + { + // This does make the assumption that the string is stored with the leading 4 bytes representing the length. + return buffer.getInt(offset + REASON_FIELD_OFFSET); + } + + /** + * {@inheritDoc} + */ + public InvalidateImageFlyweight clientId(final long clientId) + { + super.clientId(clientId); + return this; + } + + /** + * {@inheritDoc} + */ + public InvalidateImageFlyweight correlationId(final long correlationId) + { + super.correlationId(correlationId); + return this; + } + + /** + * Compute the length of the message based on the reason supplied. + * + * @param reason message to be return to originator. + * @return length of the message. + */ + public static int computeLength(final String reason) + { + return MINIMUM_SIZE + reason.length(); + } + + /** + * Validate buffer length is long enough for message. + * + * @param msgTypeId type of message. + * @param length of message in bytes to validate. + */ + public void validateLength(final int msgTypeId, final int length) + { + if (length < MINIMUM_SIZE) + { + throw new ControlProtocolException( + MALFORMED_COMMAND, "command=" + msgTypeId + " too short: length=" + length); + } + + if (length < MINIMUM_SIZE + reasonBufferLength()) + { + throw new ControlProtocolException( + MALFORMED_COMMAND, "command=" + msgTypeId + " too short: length=" + length); + } + } +} diff --git a/aeron-client/src/test/java/io/aeron/ImageTest.java b/aeron-client/src/test/java/io/aeron/ImageTest.java index 1eb3b276c9..ad3c9bcc36 100644 --- a/aeron-client/src/test/java/io/aeron/ImageTest.java +++ b/aeron-client/src/test/java/io/aeron/ImageTest.java @@ -35,6 +35,7 @@ import static org.agrona.BitUtil.align; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.*; @@ -609,6 +610,38 @@ void shouldPollFragmentsToBoundedFragmentHandlerWithMaxPositionAboveIntMaxValue( inOrder.verify(position).setOrdered(TERM_BUFFER_LENGTH); } + @Test + void shouldInvalidateFragment() + { + final int initialOffset = TERM_BUFFER_LENGTH - (ALIGNED_FRAME_LENGTH * 2); + final long initialPosition = computePosition( + INITIAL_TERM_ID, initialOffset, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID); + final long maxPosition = (long)Integer.MAX_VALUE + 1000; + position.setOrdered(initialPosition); + final Image image = createImage(); + + insertDataFrame(INITIAL_TERM_ID, initialOffset); + insertPaddingFrame(INITIAL_TERM_ID, initialOffset + ALIGNED_FRAME_LENGTH); + + assertEquals(initialPosition, image.position()); + + final String reason = "this is garbage"; + image.invalidate(reason); + + verify(subscription).invalidate(image.correlationId(), image.position(), reason); + +// final int fragmentsRead = image.boundedPoll( +// mockFragmentHandler, maxPosition, Integer.MAX_VALUE); +// +// assertThat(fragmentsRead, is(1)); +// +// final InOrder inOrder = Mockito.inOrder(position, mockFragmentHandler); +// inOrder.verify(mockFragmentHandler).onFragment( +// any(UnsafeBuffer.class), eq(initialOffset + HEADER_LENGTH), eq(DATA.length), any(Header.class)); +// inOrder.verify(position).setOrdered(TERM_BUFFER_LENGTH); + } + + private Image createImage() { return new Image(subscription, SESSION_ID, position, logBuffers, errorHandler, SOURCE_IDENTITY, CORRELATION_ID); diff --git a/aeron-driver/src/main/java/io/aeron/driver/ClientCommandAdapter.java b/aeron-driver/src/main/java/io/aeron/driver/ClientCommandAdapter.java index 56915446c9..e1fdb9ff3e 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/ClientCommandAdapter.java +++ b/aeron-driver/src/main/java/io/aeron/driver/ClientCommandAdapter.java @@ -44,6 +44,7 @@ final class ClientCommandAdapter implements ControlledMessageHandler private final CounterMessageFlyweight counterMsgFlyweight = new CounterMessageFlyweight(); private final StaticCounterMessageFlyweight staticCounterMessageFlyweight = new StaticCounterMessageFlyweight(); private final TerminateDriverFlyweight terminateDriverFlyweight = new TerminateDriverFlyweight(); + private final InvalidateImageFlyweight invalidateImageFlyweight = new InvalidateImageFlyweight(); private final DestinationByIdMessageFlyweight destinationByIdMessageFlyweight = new DestinationByIdMessageFlyweight(); private final DriverConductor conductor; @@ -287,6 +288,20 @@ else if (channel.startsWith(SPY_QUALIFIER)) break; } + case INVALIDATE_IMAGE: + { + invalidateImageFlyweight.wrap(buffer, index); + invalidateImageFlyweight.validateLength(msgTypeId, length); + correlationId = invalidateImageFlyweight.correlationId(); + + conductor.onInvalidateImage( + invalidateImageFlyweight.correlationId(), + invalidateImageFlyweight.imageCorrelationId(), + invalidateImageFlyweight.position(), + invalidateImageFlyweight.reason()); + break; + } + case REMOVE_DESTINATION_BY_ID: { destinationByIdMessageFlyweight.wrap(buffer, index); diff --git a/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java b/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java index 263e2d046d..05590b6043 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java +++ b/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java @@ -610,6 +610,19 @@ private PublicationImage findResponsePublicationImage(final PublicationParams pa throw new IllegalArgumentException("image.correlationId=" + params.responseCorrelationId + " not found"); } + private PublicationImage findPublicationImage(final long correlationId) + { + for (final PublicationImage publicationImage : publicationImages) + { + if (correlationId == publicationImage.correlationId()) + { + return publicationImage; + } + } + + return null; + } + void responseSetup(final long responseCorrelationId, final int responseSessionId) { for (int i = 0, subscriptionLinksSize = subscriptionLinks.size(); i < subscriptionLinksSize; i++) @@ -1458,6 +1471,24 @@ void onTerminateDriver(final DirectBuffer tokenBuffer, final int tokenOffset, fi } } + void onInvalidateImage( + final long correlationId, + final long imageCorrelationId, + final long position, + final String reason) + { + final PublicationImage publicationImage = findPublicationImage(imageCorrelationId); + + if (null == publicationImage) + { + throw new ControlProtocolException( + GENERIC_ERROR, "Unable to resolve image for correlationId=" + imageCorrelationId); + } + + receiverProxy.invalidateImage(imageCorrelationId, position, reason); + clientProxy.operationSucceeded(correlationId); + } + private void heartbeatAndCheckTimers(final long nowNs) { final long nowMs = cachedEpochClock.time(); diff --git a/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java b/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java index e65eb7f9bb..89f7093058 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java +++ b/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java @@ -87,6 +87,7 @@ class PublicationImageReceiverFields extends PublicationImagePadding2 boolean isSendingEosSm = false; long timeOfLastPacketNs; ImageConnection[] imageConnections = new ImageConnection[1]; + String invalidationReason = null; } class PublicationImagePadding3 extends PublicationImageReceiverFields @@ -586,6 +587,11 @@ int insertPacket( final int transportIndex, final InetSocketAddress srcAddress) { + if (null != invalidationReason) + { + return 0; + } + final boolean isHeartbeat = DataHeaderFlyweight.isHeartbeat(buffer, length); final long packetPosition = computePosition(termId, termOffset, positionBitsToShift, initialTermId); final long proposedPosition = isHeartbeat ? packetPosition : packetPosition + length; @@ -690,6 +696,12 @@ void checkEosForDrainTransition(final long nowNs) */ int sendPendingStatusMessage(final long nowNs) { + // TODO: Send error frame instead. + if (null != invalidationReason) + { + return 0; + } + int workCount = 0; final long changeNumber = endSmChange; final boolean hasSmTimedOut = (timeOfLastSmNs + smTimeoutNs) - nowNs < 0; @@ -891,6 +903,11 @@ public boolean hasReachedEndOfLife() return hasReceiverReleased && State.DONE == state; } + void invalidate(final String reason) + { + invalidationReason = reason; + } + private void state(final State state) { this.state = state; diff --git a/aeron-driver/src/main/java/io/aeron/driver/Receiver.java b/aeron-driver/src/main/java/io/aeron/driver/Receiver.java index 08075e0d53..d33d3fc7f1 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/Receiver.java +++ b/aeron-driver/src/main/java/io/aeron/driver/Receiver.java @@ -334,6 +334,17 @@ void onResolutionChange( channelEndpoint.updateControlAddress(transportIndex, newAddress); } + void onInvalidateImage(final long imageCorrelationId, final long position, final String reason) + { + for (final PublicationImage image : publicationImages) + { + if (imageCorrelationId == image.correlationId()) + { + image.invalidate(reason); + } + } + } + private void checkPendingSetupMessages(final long nowNs) { for (int lastIndex = pendingSetupMessages.size() - 1, i = lastIndex; i >= 0; i--) diff --git a/aeron-driver/src/main/java/io/aeron/driver/ReceiverProxy.java b/aeron-driver/src/main/java/io/aeron/driver/ReceiverProxy.java index 2d8ba4276a..58aed30ecf 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/ReceiverProxy.java +++ b/aeron-driver/src/main/java/io/aeron/driver/ReceiverProxy.java @@ -195,4 +195,16 @@ void requestSetup( offer(() -> receiver.onRequestSetup(channelEndpoint, streamId, sessionId)); } } + + void invalidateImage(final long imageCorrelationId, final long position, final String reason) + { + if (notConcurrent()) + { + receiver.onInvalidateImage(imageCorrelationId, position, reason); + } + else + { + offer(() -> receiver.onInvalidateImage(imageCorrelationId, position, reason)); + } + } } diff --git a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java new file mode 100644 index 0000000000..50f5093031 --- /dev/null +++ b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java @@ -0,0 +1,110 @@ +/* + * Copyright 2014-2024 Real Logic Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.aeron; + +import io.aeron.driver.MediaDriver; +import io.aeron.driver.ThreadingMode; +import io.aeron.test.EventLogExtension; +import io.aeron.test.InterruptingTestCallback; +import io.aeron.test.SystemTestWatcher; +import io.aeron.test.Tests; +import io.aeron.test.driver.TestMediaDriver; +import org.agrona.CloseHelper; +import org.agrona.DirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; + +import java.util.concurrent.atomic.AtomicBoolean; + +import static java.nio.charset.StandardCharsets.US_ASCII; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class }) +public class ImageInvalidationTest +{ + @RegisterExtension + final SystemTestWatcher systemTestWatcher = new SystemTestWatcher(); + + private final String channel = "aeron:udp?endpoint=localhost:10000"; + private final int streamId = 10000; + private final DirectBuffer message = new UnsafeBuffer("this is a test message".getBytes(US_ASCII)); + + private final MediaDriver.Context context = new MediaDriver.Context() + .dirDeleteOnStart(true) + .threadingMode(ThreadingMode.SHARED); + private TestMediaDriver driver; + + @AfterEach + void tearDown() + { + CloseHelper.quietClose(driver); + } + + private TestMediaDriver launch() + { + driver = TestMediaDriver.launch(context, systemTestWatcher); + return driver; + } + + @Test + void shouldInvalidateAnSubscriptionsImage() + { + final TestMediaDriver driver = launch(); + + final Aeron.Context ctx = new Aeron.Context() + .aeronDirectoryName(driver.aeronDirectoryName()); + + final AtomicBoolean imageUnavailable = new AtomicBoolean(false); + + try (Aeron aeron = Aeron.connect(ctx); + Publication pub = aeron.addPublication(channel, streamId); + Subscription sub = aeron.addSubscription( + channel, streamId, image -> {}, image -> imageUnavailable.set(true))) + { + Tests.awaitConnected(pub); + Tests.awaitConnected(sub); + + while (pub.offer(message) < 0) + { + Tests.yield(); + } + + while (0 == sub.poll((buffer, offset, length, header) -> {}, 1)) + { + Tests.yield(); + } + + final Image image = sub.imageAtIndex(0); + assertEquals(pub.position(), image.position()); + + final String reason = "Needs to be closed"; + image.invalidate(reason); + + while (pub.isConnected()) + { + Tests.yield(); + } + + while (!imageUnavailable.get()) + { + Tests.yield(); + } + } + } +} From 2b2af137e1131f5d24d4205c7b957fe2abd29ca9 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Fri, 24 May 2024 15:09:20 +1200 Subject: [PATCH 03/67] [Java] Set image invalidation test as a slow test. Early exit of image is found during invalidation on the receiver. --- aeron-driver/src/main/java/io/aeron/driver/Receiver.java | 1 + .../src/test/java/io/aeron/ImageInvalidationTest.java | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/aeron-driver/src/main/java/io/aeron/driver/Receiver.java b/aeron-driver/src/main/java/io/aeron/driver/Receiver.java index d33d3fc7f1..831641623b 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/Receiver.java +++ b/aeron-driver/src/main/java/io/aeron/driver/Receiver.java @@ -341,6 +341,7 @@ void onInvalidateImage(final long imageCorrelationId, final long position, final if (imageCorrelationId == image.correlationId()) { image.invalidate(reason); + break; } } } diff --git a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java index 50f5093031..44708f8229 100644 --- a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java +++ b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java @@ -18,7 +18,9 @@ import io.aeron.driver.MediaDriver; import io.aeron.driver.ThreadingMode; import io.aeron.test.EventLogExtension; +import io.aeron.test.InterruptAfter; import io.aeron.test.InterruptingTestCallback; +import io.aeron.test.SlowTest; import io.aeron.test.SystemTestWatcher; import io.aeron.test.Tests; import io.aeron.test.driver.TestMediaDriver; @@ -63,6 +65,8 @@ private TestMediaDriver launch() } @Test + @InterruptAfter(20) + @SlowTest void shouldInvalidateAnSubscriptionsImage() { final TestMediaDriver driver = launch(); From 23da70ae29787aff021cdc9d7693cf86509310df Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Tue, 28 May 2024 11:29:22 +1200 Subject: [PATCH 04/67] [Java] Start adding ErrorFlyweight. --- .../io/aeron/protocol/ErrorFlyweight.java | 216 ++++++++++++++++++ .../io/aeron/driver/PublicationImage.java | 14 +- .../driver/media/ReceiveChannelEndpoint.java | 20 ++ .../ReceiveChannelEndpointThreadLocals.java | 37 ++- 4 files changed, 281 insertions(+), 6 deletions(-) create mode 100644 aeron-client/src/main/java/io/aeron/protocol/ErrorFlyweight.java diff --git a/aeron-client/src/main/java/io/aeron/protocol/ErrorFlyweight.java b/aeron-client/src/main/java/io/aeron/protocol/ErrorFlyweight.java new file mode 100644 index 0000000000..102095363e --- /dev/null +++ b/aeron-client/src/main/java/io/aeron/protocol/ErrorFlyweight.java @@ -0,0 +1,216 @@ +/* + * Copyright 2014-2024 Real Logic Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.aeron.protocol; + +import io.aeron.command.ErrorResponseFlyweight; +import org.agrona.concurrent.UnsafeBuffer; + +import java.nio.ByteBuffer; + +import static java.nio.ByteOrder.LITTLE_ENDIAN; + +/** + * Flyweight for general Aeron network protocol error frame + *
+ *    0                   1                   2                   3
+ *    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ *    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * 0  |R|                 Frame Length (varies)                       |
+ *    +---------------+---------------+-------------------------------+
+ * 4  |   Version     |     Flags     |         Type (=0x04)          |
+ *    +---------------+---------------+-------------------------------+
+ * 8  |                          Session ID                           |
+ *    +---------------------------------------------------------------+
+ * 12 |                           Stream ID                           |
+ *    +---------------------------------------------------------------+
+ * 16 |                          Error Code                           |
+ *    +---------------------------------------------------------------+
+ * 20 |                     Error String Length                       |
+ *    +---------------------------------------------------------------+
+ * 24 |                         Error String                        ...
+ *    +---------------------------------------------------------------+
+ *    ...                                                             |
+ *    +---------------------------------------------------------------+
+ * 
+ */ +public class ErrorFlyweight extends HeaderFlyweight +{ + /** + * Length of the Error Header. + */ + public static final int HEADER_LENGTH = 24; + + /** + * Offset in the frame at which the session-id field begins. + */ + public static final int SESSION_ID_FIELD_OFFSET = 8; + + /** + * Offset in the frame at which the stream-id field begins. + */ + public static final int STREAM_ID_FIELD_OFFSET = 12; + + /** + * Offset in the frame at which the error code field begins. + */ + public static final int ERROR_CODE_FIELD_OFFSET = 16; + + /** + * Offset in the frame at which the error string field begins. Specifically this will be the length of the string + * using the Agrona buffer standard of using 4 bytes for the length. Followed by the variable bytes for the string. + */ + public static final int ERROR_STRING_FIELD_OFFSET = 20; + + public static final int MAX_ERROR_MESSAGE_LENGTH = 1023; + + public static final int MAX_ERROR_FRAME_LENGTH = HEADER_LENGTH + MAX_ERROR_MESSAGE_LENGTH; + + /** + * Default constructor for the ErrorFlyweight so that it can be wrapped over a buffer later. + */ + public ErrorFlyweight() + { + } + + /** + * Construct the ErrorFlyweight over an NIO ByteBuffer frame. + * + * @param buffer containing the frame. + */ + public ErrorFlyweight(final ByteBuffer buffer) + { + super(buffer); + } + + /** + * Construct the ErrorFlyweight over an UnsafeBuffer frame. + * + * @param buffer containing the frame. + */ + public ErrorFlyweight(final UnsafeBuffer buffer) + { + super(buffer); + } + + + /** + * The session-id for the stream. + * + * @return session-id for the stream. + */ + public int sessionId() + { + return getInt(SESSION_ID_FIELD_OFFSET, LITTLE_ENDIAN); + } + + /** + * Set session-id for the stream. + * + * @param sessionId session-id for the stream. + * @return this for a fluent API. + */ + public ErrorFlyweight sessionId(final int sessionId) + { + putInt(SESSION_ID_FIELD_OFFSET, sessionId, LITTLE_ENDIAN); + + return this; + } + + /** + * The stream-id for the stream. + * + * @return stream-id for the stream. + */ + public int streamId() + { + return getInt(STREAM_ID_FIELD_OFFSET, LITTLE_ENDIAN); + } + + /** + * Set stream-id for the stream. + * + * @param streamId stream-id for the stream. + * @return this for a fluent API. + */ + public ErrorFlyweight streamId(final int streamId) + { + putInt(STREAM_ID_FIELD_OFFSET, streamId, LITTLE_ENDIAN); + + return this; + } + + /** + * The error-code for the message. + * + * @return error-code for the message. + */ + public int errorCode() + { + return getInt(ERROR_CODE_FIELD_OFFSET, LITTLE_ENDIAN); + } + + /** + * Set error-code for the message. + * + * @param errorCode for the message. + * @return this for a fluent API. + */ + public ErrorFlyweight errorCode(final int errorCode) + { + putInt(ERROR_CODE_FIELD_OFFSET, errorCode, LITTLE_ENDIAN); + + return this; + } + + /** + * Get the error string for the message. + * + * @return the error string for the message. + */ + public String errorMessage() + { + return getStringUtf8(ERROR_STRING_FIELD_OFFSET); + } + + /** + * Set the error string for the message. + * + * @param errorString the error string in UTF-8. + * @return this for a fluent API. + */ + public ErrorFlyweight errorMessage(final String errorString) + { + putStringUtf8(ERROR_STRING_FIELD_OFFSET, errorString, LITTLE_ENDIAN); + return this; + } + + /** + * {@inheritDoc} + */ + public String toString() + { + return "ERROR{" + + "frame-length=" + frameLength() + + " version=" + version() + + " flags=" + String.valueOf(flagsToChars(flags())) + + " type=" + headerType() + + " session-id=" + sessionId() + + " stream-id=" + streamId() + + " error-code=" + errorCode() + + " error-message=" + errorMessage() + + "}"; + } +} diff --git a/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java b/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java index 89f7093058..5e3eaadba6 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java +++ b/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java @@ -696,17 +696,21 @@ void checkEosForDrainTransition(final long nowNs) */ int sendPendingStatusMessage(final long nowNs) { + int workCount = 0; + final long changeNumber = endSmChange; + final boolean hasSmTimedOut = (timeOfLastSmNs + smTimeoutNs) - nowNs < 0; + // TODO: Send error frame instead. if (null != invalidationReason) { - return 0; + if (hasSmTimedOut) + { + channelEndpoint.sendErrorFrame(imageConnections, sessionId, streamId, invalidationReason); + } + return workCount; } - int workCount = 0; - final long changeNumber = endSmChange; - final boolean hasSmTimedOut = (timeOfLastSmNs + smTimeoutNs) - nowNs < 0; final Integer responseSessionId; - if (hasSmTimedOut && null != (responseSessionId = this.responseSessionId)) { channelEndpoint.sendResponseSetup(imageConnections, sessionId, streamId, responseSessionId); diff --git a/aeron-driver/src/main/java/io/aeron/driver/media/ReceiveChannelEndpoint.java b/aeron-driver/src/main/java/io/aeron/driver/media/ReceiveChannelEndpoint.java index 625e7202b3..eacfa57c3c 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/media/ReceiveChannelEndpoint.java +++ b/aeron-driver/src/main/java/io/aeron/driver/media/ReceiveChannelEndpoint.java @@ -17,6 +17,7 @@ import io.aeron.CommonContext; import io.aeron.ErrorCode; +import io.aeron.command.ErrorResponseFlyweight; import io.aeron.driver.DataPacketDispatcher; import io.aeron.driver.DriverConductorProxy; import io.aeron.driver.MediaDriver; @@ -114,6 +115,7 @@ public class ReceiveChannelEndpoint extends ReceiveChannelEndpointRhsPadding private final RttMeasurementFlyweight rttMeasurementFlyweight; private final ByteBuffer responseSetupBuffer; private final ResponseSetupFlyweight responseSetupHeader; + private final ErrorFlyweight errorFlyweight; private final AtomicCounter shortSends; private final AtomicCounter possibleTtlAsymmetry; private final AtomicCounter statusIndicator; @@ -162,6 +164,7 @@ public ReceiveChannelEndpoint( rttMeasurementFlyweight = threadLocals.rttMeasurementFlyweight(); responseSetupBuffer = threadLocals.responseSetupBuffer(); responseSetupHeader = threadLocals.responseSetupHeader(); + errorFlyweight = threadLocals.errorFlyweight(); cachedNanoClock = context.receiverCachedNanoClock(); timeOfLastActivityNs = cachedNanoClock.nanoTime(); receiverId = threadLocals.nextReceiverId(); @@ -938,6 +941,23 @@ public void sendResponseSetup( send(responseSetupBuffer, ResponseSetupFlyweight.HEADER_LENGTH, controlAddresses); } + /** + * Send an error frame back to the source publications to indicate this image has errored. + * + * @param controlAddresses of the sources. + * @param sessionId for the image. + * @param streamId for the image. + * @param invalidationReason to be sent back to the publication. + */ + public void sendErrorFrame( + final ImageConnection[] controlAddresses, + final int sessionId, + final int streamId, + final String invalidationReason) + { + + } + /** * Dispatcher for the channel. * diff --git a/aeron-driver/src/main/java/io/aeron/driver/media/ReceiveChannelEndpointThreadLocals.java b/aeron-driver/src/main/java/io/aeron/driver/media/ReceiveChannelEndpointThreadLocals.java index 67c4a280ea..90b5a4960d 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/media/ReceiveChannelEndpointThreadLocals.java +++ b/aeron-driver/src/main/java/io/aeron/driver/media/ReceiveChannelEndpointThreadLocals.java @@ -15,6 +15,7 @@ */ package io.aeron.driver.media; +import io.aeron.protocol.ErrorFlyweight; import io.aeron.protocol.HeaderFlyweight; import io.aeron.protocol.NakFlyweight; import io.aeron.protocol.ResponseSetupFlyweight; @@ -44,6 +45,8 @@ public final class ReceiveChannelEndpointThreadLocals private final RttMeasurementFlyweight rttMeasurementFlyweight; private final ByteBuffer responseSetupBuffer; private final ResponseSetupFlyweight responseSetupHeader; + private final ByteBuffer errorBuffer; + private final ErrorFlyweight errorFlyweight; private long nextReceiverId; /** @@ -56,7 +59,8 @@ public ReceiveChannelEndpointThreadLocals() BitUtil.align(smLength, CACHE_LINE_LENGTH) + BitUtil.align(NakFlyweight.HEADER_LENGTH, CACHE_LINE_LENGTH) + BitUtil.align(RttMeasurementFlyweight.HEADER_LENGTH, CACHE_LINE_LENGTH) + - BitUtil.align(ResponseSetupFlyweight.HEADER_LENGTH, CACHE_LINE_LENGTH); + BitUtil.align(ResponseSetupFlyweight.HEADER_LENGTH, CACHE_LINE_LENGTH) + + BitUtil.align(ErrorFlyweight.MAX_ERROR_FRAME_LENGTH, CACHE_LINE_LENGTH); final UUID uuid = UUID.randomUUID(); nextReceiverId = uuid.getMostSignificantBits() ^ uuid.getLeastSignificantBits(); @@ -83,6 +87,12 @@ public ReceiveChannelEndpointThreadLocals() responseSetupBuffer = byteBuffer.slice(); responseSetupHeader = new ResponseSetupFlyweight(responseSetupBuffer); + final int errorOffset = responseSetupOffset + BitUtil.align( + ResponseSetupFlyweight.HEADER_LENGTH, FRAME_ALIGNMENT); + byteBuffer.limit(errorOffset + ErrorFlyweight.MAX_ERROR_FRAME_LENGTH).position(errorOffset); + errorBuffer = byteBuffer.slice(); + errorFlyweight = new ErrorFlyweight(errorBuffer); + statusMessageFlyweight .version(HeaderFlyweight.CURRENT_VERSION) .headerType(HeaderFlyweight.HDR_TYPE_SM) @@ -102,6 +112,11 @@ public ReceiveChannelEndpointThreadLocals() .version(HeaderFlyweight.CURRENT_VERSION) .headerType(HeaderFlyweight.HDR_TYPE_RSP_SETUP) .frameLength(ResponseSetupFlyweight.HEADER_LENGTH); + + errorFlyweight + .version(HeaderFlyweight.CURRENT_VERSION) + .headerType(HeaderFlyweight.HDR_TYPE_ERR) + .frameLength(ResponseSetupFlyweight.HEADER_LENGTH); } /** @@ -184,6 +199,26 @@ public ResponseSetupFlyweight responseSetupHeader() return responseSetupHeader; } + /** + * Buffer for writing the Error messages to send. + * + * @return buffer for writing the error messages to send. + */ + public ByteBuffer errorBuffer() + { + return errorBuffer; + } + + /** + * Flyweight over the {@link #errorBuffer()} + * + * @return flyweight over the {@link #errorBuffer()} + */ + public ErrorFlyweight errorFlyweight() + { + return errorFlyweight; + } + /** * Get the next receiver id to be used for a receiver channel identity. * From 0fa58934af4fe968cbfc5fbcf50bc826d4144a98 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Wed, 12 Jun 2024 15:01:59 +1200 Subject: [PATCH 05/67] [Java] Pass invalidation reason through to the source publication and have it disconnect. Allow receiver side image to linger to prevent immediately recreation of the image. Treat errors received by the NetworkPublication to behaviour like end of stream messages. --- .../io/aeron/protocol/ErrorFlyweight.java | 110 ++++++++++++++++-- .../io/aeron/protocol/ErrorFlyweightTest.java | 58 +++++++++ .../AbstractMinMulticastFlowControl.java | 18 +++ .../java/io/aeron/driver/FlowControl.java | 10 ++ .../aeron/driver/MaxMulticastFlowControl.java | 7 ++ .../aeron/driver/MinMulticastFlowControl.java | 9 ++ .../io/aeron/driver/NetworkPublication.java | 17 +++ .../io/aeron/driver/PublicationImage.java | 19 ++- .../driver/TaggedMulticastFlowControl.java | 14 +++ .../io/aeron/driver/UnicastFlowControl.java | 8 ++ .../driver/media/ControlTransportPoller.java | 7 ++ .../driver/media/ReceiveChannelEndpoint.java | 25 +++- .../driver/media/SendChannelEndpoint.java | 37 ++++++ .../TaggedMulticastFlowControlTest.java | 89 +++++++++++++- .../java/io/aeron/ImageInvalidationTest.java | 14 ++- 15 files changed, 416 insertions(+), 26 deletions(-) create mode 100644 aeron-client/src/test/java/io/aeron/protocol/ErrorFlyweightTest.java diff --git a/aeron-client/src/main/java/io/aeron/protocol/ErrorFlyweight.java b/aeron-client/src/main/java/io/aeron/protocol/ErrorFlyweight.java index 102095363e..efb30c2a68 100644 --- a/aeron-client/src/main/java/io/aeron/protocol/ErrorFlyweight.java +++ b/aeron-client/src/main/java/io/aeron/protocol/ErrorFlyweight.java @@ -15,7 +15,6 @@ */ package io.aeron.protocol; -import io.aeron.command.ErrorResponseFlyweight; import org.agrona.concurrent.UnsafeBuffer; import java.nio.ByteBuffer; @@ -36,11 +35,17 @@ * +---------------------------------------------------------------+ * 12 | Stream ID | * +---------------------------------------------------------------+ - * 16 | Error Code | + * 16 | Receiver ID | + * | | * +---------------------------------------------------------------+ - * 20 | Error String Length | + * 24 | Group Tag | + * | | * +---------------------------------------------------------------+ - * 24 | Error String ... + * 32 | Error Code | + * +---------------------------------------------------------------+ + * 36 | Error String Length | + * +---------------------------------------------------------------+ + * 40 | Error String ... * +---------------------------------------------------------------+ * ... | * +---------------------------------------------------------------+ @@ -51,7 +56,7 @@ public class ErrorFlyweight extends HeaderFlyweight /** * Length of the Error Header. */ - public static final int HEADER_LENGTH = 24; + public static final int HEADER_LENGTH = 40; /** * Offset in the frame at which the session-id field begins. @@ -63,21 +68,40 @@ public class ErrorFlyweight extends HeaderFlyweight */ public static final int STREAM_ID_FIELD_OFFSET = 12; + /** + * Offset in the frame at which the receiver-id field begins. + */ + public static final int RECEIVER_ID_FIELD_OFFSET = 16; + + /** + * Offset in the frame at which the group-tag field begins. + */ + public static final int GROUP_TAG_FIELD_OFFSET = 24; + /** * Offset in the frame at which the error code field begins. */ - public static final int ERROR_CODE_FIELD_OFFSET = 16; + public static final int ERROR_CODE_FIELD_OFFSET = 32; /** * Offset in the frame at which the error string field begins. Specifically this will be the length of the string * using the Agrona buffer standard of using 4 bytes for the length. Followed by the variable bytes for the string. */ - public static final int ERROR_STRING_FIELD_OFFSET = 20; + public static final int ERROR_STRING_FIELD_OFFSET = 36; + /** + * Maximum length that an error message can be. Can be short that this if configuration options have made the MTU + * smaller. The error message should be truncated to fit within a single MTU. + */ public static final int MAX_ERROR_MESSAGE_LENGTH = 1023; public static final int MAX_ERROR_FRAME_LENGTH = HEADER_LENGTH + MAX_ERROR_MESSAGE_LENGTH; + /** + * Flag to indicate that the group tag field is relevant, if not set the value should be ignored. + */ + public static final int HAS_GROUP_ID_FLAG = 0x08; + /** * Default constructor for the ErrorFlyweight so that it can be wrapped over a buffer later. */ @@ -152,6 +176,71 @@ public ErrorFlyweight streamId(final int streamId) return this; } + /** + * The receiver-id for the stream. + * + * @return receiver-id for the stream. + */ + public long receiverId() + { + return getLong(RECEIVER_ID_FIELD_OFFSET, LITTLE_ENDIAN); + } + + /** + * Set receiver-id for the stream. + * + * @param receiverId receiver-id for the stream. + * @return this for a fluent API. + */ + public ErrorFlyweight receiverId(final long receiverId) + { + putLong(RECEIVER_ID_FIELD_OFFSET, receiverId, LITTLE_ENDIAN); + + return this; + } + + /** + * Get the group tag for the message. + * + * @return group tag for the message. + */ + public long groupTag() + { + return getLong(GROUP_TAG_FIELD_OFFSET, LITTLE_ENDIAN); + } + + /** + * Determines if this message has the group tag flag set. + * + * @return true if the flag is set false otherwise. + */ + public boolean hasGroupTag() + { + return HAS_GROUP_ID_FLAG == (HAS_GROUP_ID_FLAG & flags()); + } + + /** + * Set an optional group tag, null indicates the value should not be set. If non-null will set HAS_GROUP_TAG flag + * on the header. A null value will clear this flag and use a value of 0. + * + * @param groupTag optional group tag to be applied to this message. + * @return this for a fluent API. + */ + public ErrorFlyweight groupTag(final Long groupTag) + { + if (null == groupTag) + { + flags((short)(~HAS_GROUP_ID_FLAG & flags())); + } + else + { + putLong(GROUP_TAG_FIELD_OFFSET, groupTag); + flags((short)(HAS_GROUP_ID_FLAG | flags())); + } + + return this; + } + /** * The error-code for the message. * @@ -188,12 +277,13 @@ public String errorMessage() /** * Set the error string for the message. * - * @param errorString the error string in UTF-8. + * @param errorMessage the error string in UTF-8. * @return this for a fluent API. */ - public ErrorFlyweight errorMessage(final String errorString) + public ErrorFlyweight errorMessage(final String errorMessage) { - putStringUtf8(ERROR_STRING_FIELD_OFFSET, errorString, LITTLE_ENDIAN); + final int headerAndMessageLength = putStringUtf8(ERROR_STRING_FIELD_OFFSET, errorMessage, LITTLE_ENDIAN); + frameLength(HEADER_LENGTH + (headerAndMessageLength - STR_HEADER_LEN)); return this; } diff --git a/aeron-client/src/test/java/io/aeron/protocol/ErrorFlyweightTest.java b/aeron-client/src/test/java/io/aeron/protocol/ErrorFlyweightTest.java new file mode 100644 index 0000000000..7331f28887 --- /dev/null +++ b/aeron-client/src/test/java/io/aeron/protocol/ErrorFlyweightTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2014-2024 Real Logic Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.aeron.protocol; + +import org.junit.jupiter.api.Test; + +import static io.aeron.protocol.ErrorFlyweight.HAS_GROUP_ID_FLAG; +import static io.aeron.protocol.ErrorFlyweight.MAX_ERROR_FRAME_LENGTH; +import static java.nio.ByteBuffer.allocate; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +class ErrorFlyweightTest +{ + @Test + void shouldCorrectlySetFlagsForGroupTag() + { + final ErrorFlyweight errorFlyweight = new ErrorFlyweight(allocate(MAX_ERROR_FRAME_LENGTH)); + + assertEquals(0, errorFlyweight.flags()); + errorFlyweight.groupTag(10L); + assertEquals(HAS_GROUP_ID_FLAG, errorFlyweight.flags()); + + errorFlyweight.groupTag(null); + assertNotEquals(HAS_GROUP_ID_FLAG, errorFlyweight.flags()); + } + + @Test + void shouldCorrectlyUpdateExistingFlagsForGroupTag() + { + final ErrorFlyweight errorFlyweight = new ErrorFlyweight(allocate(MAX_ERROR_FRAME_LENGTH)); + + final short initialFlags = (short)0x3; + + errorFlyweight.flags(initialFlags); + assertEquals(initialFlags, errorFlyweight.flags()); + errorFlyweight.groupTag(10L); + assertEquals(HAS_GROUP_ID_FLAG, HAS_GROUP_ID_FLAG & errorFlyweight.flags()); + assertEquals(initialFlags, initialFlags & errorFlyweight.flags()); + + errorFlyweight.groupTag(null); + assertEquals(0, HAS_GROUP_ID_FLAG & errorFlyweight.flags()); + assertEquals(initialFlags, errorFlyweight.flags()); + } +} \ No newline at end of file diff --git a/aeron-driver/src/main/java/io/aeron/driver/AbstractMinMulticastFlowControl.java b/aeron-driver/src/main/java/io/aeron/driver/AbstractMinMulticastFlowControl.java index bec0263223..4de4a79ba6 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/AbstractMinMulticastFlowControl.java +++ b/aeron-driver/src/main/java/io/aeron/driver/AbstractMinMulticastFlowControl.java @@ -18,6 +18,7 @@ import io.aeron.CommonContext; import io.aeron.driver.media.UdpChannel; import io.aeron.driver.status.FlowControlReceivers; +import io.aeron.protocol.ErrorFlyweight; import io.aeron.protocol.SetupFlyweight; import io.aeron.protocol.StatusMessageFlyweight; import org.agrona.CloseHelper; @@ -312,6 +313,23 @@ protected void processSendSetupTrigger( } } + protected void processError( + final ErrorFlyweight error, + final InetSocketAddress receiverAddress, + final long timeNs, + final boolean matchesTag) + { + final long receiverId = error.receiverId(); + + for (final Receiver receiver : receivers) + { + if (matchesTag && receiverId == receiver.receiverId) + { + receiver.eosFlagged = true; + } + } + } + /** * Timeout after which an inactive receiver will be dropped. * diff --git a/aeron-driver/src/main/java/io/aeron/driver/FlowControl.java b/aeron-driver/src/main/java/io/aeron/driver/FlowControl.java index b581d17380..013c8d2d1f 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/FlowControl.java +++ b/aeron-driver/src/main/java/io/aeron/driver/FlowControl.java @@ -16,6 +16,7 @@ package io.aeron.driver; import io.aeron.driver.media.UdpChannel; +import io.aeron.protocol.ErrorFlyweight; import io.aeron.protocol.SetupFlyweight; import io.aeron.protocol.StatusMessageFlyweight; import org.agrona.concurrent.status.CountersManager; @@ -100,6 +101,15 @@ long onSetup( int positionBitsToShift, long timeNs); + /** + * Update the sender flow control strategy if an error comes from one of the receivers. + * + * @param errorFlyweight over the error received. + * @param receiverAddress the address of the receiver. + * @param timeNs current time in nanoseconds + */ + void onError(ErrorFlyweight errorFlyweight, InetSocketAddress receiverAddress, long timeNs); + /** * Initialize the flow control strategy for a stream. * diff --git a/aeron-driver/src/main/java/io/aeron/driver/MaxMulticastFlowControl.java b/aeron-driver/src/main/java/io/aeron/driver/MaxMulticastFlowControl.java index 7dd650cf93..7025f99ac6 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/MaxMulticastFlowControl.java +++ b/aeron-driver/src/main/java/io/aeron/driver/MaxMulticastFlowControl.java @@ -16,6 +16,7 @@ package io.aeron.driver; import io.aeron.driver.media.UdpChannel; +import io.aeron.protocol.ErrorFlyweight; import io.aeron.protocol.SetupFlyweight; import io.aeron.protocol.StatusMessageFlyweight; import org.agrona.concurrent.status.CountersManager; @@ -120,6 +121,12 @@ public long onIdle(final long timeNs, final long senderLimit, final long senderP return senderLimit; } + /** + * {@inheritDoc} + */ + public void onError(final ErrorFlyweight errorFlyweight, final InetSocketAddress receiverAddress, final long timeNs) + { + } /** * {@inheritDoc} diff --git a/aeron-driver/src/main/java/io/aeron/driver/MinMulticastFlowControl.java b/aeron-driver/src/main/java/io/aeron/driver/MinMulticastFlowControl.java index 7e94afa529..0c816c307d 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/MinMulticastFlowControl.java +++ b/aeron-driver/src/main/java/io/aeron/driver/MinMulticastFlowControl.java @@ -15,6 +15,7 @@ */ package io.aeron.driver; +import io.aeron.protocol.ErrorFlyweight; import io.aeron.protocol.StatusMessageFlyweight; import java.net.InetSocketAddress; @@ -59,4 +60,12 @@ public void onTriggerSendSetup( { processSendSetupTrigger(flyweight, receiverAddress, timeNs, true); } + + /** + * {@inheritDoc} + */ + public void onError(final ErrorFlyweight errorFlyweight, final InetSocketAddress receiverAddress, final long timeNs) + { + processError(errorFlyweight, receiverAddress, timeNs, true); + } } diff --git a/aeron-driver/src/main/java/io/aeron/driver/NetworkPublication.java b/aeron-driver/src/main/java/io/aeron/driver/NetworkPublication.java index 71328b5112..91270f37a3 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/NetworkPublication.java +++ b/aeron-driver/src/main/java/io/aeron/driver/NetworkPublication.java @@ -24,6 +24,7 @@ import io.aeron.logbuffer.LogBufferDescriptor; import io.aeron.logbuffer.LogBufferUnblocker; import io.aeron.protocol.DataHeaderFlyweight; +import io.aeron.protocol.ErrorFlyweight; import io.aeron.protocol.RttMeasurementFlyweight; import io.aeron.protocol.SetupFlyweight; import io.aeron.protocol.StatusMessageFlyweight; @@ -480,6 +481,22 @@ public void onStatusMessage( } } + /** + * Process an error message from a receiver. + * + * @param msg flyweight over the network packet. + * @param srcAddress that the setup message has come from. + * @param conductorProxy to send messages back to the conductor. + */ + public void onError( + final ErrorFlyweight msg, + final InetSocketAddress srcAddress, + final DriverConductorProxy conductorProxy) + { + livenessTracker.onRemoteClose(msg.receiverId()); + flowControl.onError(msg, srcAddress, cachedNanoClock.nanoTime()); + } + /** * Process RTT (Round Trip Timing) message from a receiver. * diff --git a/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java b/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java index 5e3eaadba6..df09b71074 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java +++ b/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java @@ -47,6 +47,7 @@ import static io.aeron.CommonContext.UNTETHERED_RESTING_TIMEOUT_PARAM_NAME; import static io.aeron.CommonContext.UNTETHERED_WINDOW_LIMIT_TIMEOUT_PARAM_NAME; +import static io.aeron.ErrorCode.GENERIC_ERROR; import static io.aeron.driver.LossDetector.lossFound; import static io.aeron.driver.LossDetector.rebuildOffset; import static io.aeron.driver.status.SystemCounterDescriptor.*; @@ -587,8 +588,14 @@ int insertPacket( final int transportIndex, final InetSocketAddress srcAddress) { + final boolean isEndOfStream = DataHeaderFlyweight.isEndOfStream(buffer); + if (null != invalidationReason) { + if (isEndOfStream) + { + System.out.println("Invalidated end of stream"); + } return 0; } @@ -609,15 +616,15 @@ int insertPacket( timeOfLastPacketNs = nowNs; trackConnection(transportIndex, srcAddress, nowNs); - if (DataHeaderFlyweight.isEndOfStream(buffer)) + if (isEndOfStream) { imageConnections[transportIndex].eosPosition = packetPosition; imageConnections[transportIndex].isEos = true; - if (!isEndOfStream && isAllConnectedEos()) + if (!this.isEndOfStream && isAllConnectedEos()) { LogBufferDescriptor.endOfStreamPosition(rawLog.metaData(), findEosPosition()); - isEndOfStream = true; + this.isEndOfStream = true; } } @@ -700,13 +707,15 @@ int sendPendingStatusMessage(final long nowNs) final long changeNumber = endSmChange; final boolean hasSmTimedOut = (timeOfLastSmNs + smTimeoutNs) - nowNs < 0; - // TODO: Send error frame instead. if (null != invalidationReason) { if (hasSmTimedOut) { - channelEndpoint.sendErrorFrame(imageConnections, sessionId, streamId, invalidationReason); + channelEndpoint.sendErrorFrame( + imageConnections, sessionId, streamId, GENERIC_ERROR.value(), invalidationReason); + workCount++; } + return workCount; } diff --git a/aeron-driver/src/main/java/io/aeron/driver/TaggedMulticastFlowControl.java b/aeron-driver/src/main/java/io/aeron/driver/TaggedMulticastFlowControl.java index 492b73188d..c7052fae51 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/TaggedMulticastFlowControl.java +++ b/aeron-driver/src/main/java/io/aeron/driver/TaggedMulticastFlowControl.java @@ -15,6 +15,7 @@ */ package io.aeron.driver; +import io.aeron.protocol.ErrorFlyweight; import io.aeron.protocol.StatusMessageFlyweight; import java.net.InetSocketAddress; @@ -68,6 +69,14 @@ public void onTriggerSendSetup( processSendSetupTrigger(flyweight, receiverAddress, timeNs, matchesTag(flyweight)); } + /** + * {@inheritDoc} + */ + public void onError(final ErrorFlyweight errorFlyweight, final InetSocketAddress receiverAddress, final long timeNs) + { + processError(errorFlyweight, receiverAddress, timeNs, matchesTag(errorFlyweight)); + } + @SuppressWarnings("deprecation") private boolean matchesTag(final StatusMessageFlyweight flyweight) { @@ -97,4 +106,9 @@ else if (asfLength >= SIZE_OF_INT) return result; } + + private boolean matchesTag(final ErrorFlyweight errorFlyweight) + { + return errorFlyweight.hasGroupTag() && errorFlyweight.groupTag() == super.groupTag(); + } } diff --git a/aeron-driver/src/main/java/io/aeron/driver/UnicastFlowControl.java b/aeron-driver/src/main/java/io/aeron/driver/UnicastFlowControl.java index c9f0fd3aa0..38da930c63 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/UnicastFlowControl.java +++ b/aeron-driver/src/main/java/io/aeron/driver/UnicastFlowControl.java @@ -16,6 +16,7 @@ package io.aeron.driver; import io.aeron.driver.media.UdpChannel; +import io.aeron.protocol.ErrorFlyweight; import io.aeron.protocol.SetupFlyweight; import io.aeron.protocol.StatusMessageFlyweight; import org.agrona.concurrent.status.CountersManager; @@ -85,6 +86,13 @@ public long onSetup( return senderLimit; } + /** + * {@inheritDoc} + */ + public void onError(final ErrorFlyweight errorFlyweight, final InetSocketAddress receiverAddress, final long timeNs) + { + } + /** * {@inheritDoc} */ diff --git a/aeron-driver/src/main/java/io/aeron/driver/media/ControlTransportPoller.java b/aeron-driver/src/main/java/io/aeron/driver/media/ControlTransportPoller.java index 23f6ca0cd6..e2a85ccd61 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/media/ControlTransportPoller.java +++ b/aeron-driver/src/main/java/io/aeron/driver/media/ControlTransportPoller.java @@ -17,6 +17,7 @@ import io.aeron.driver.Configuration; import io.aeron.driver.DriverConductorProxy; +import io.aeron.protocol.ErrorFlyweight; import io.aeron.protocol.NakFlyweight; import io.aeron.protocol.ResponseSetupFlyweight; import io.aeron.protocol.RttMeasurementFlyweight; @@ -52,6 +53,7 @@ public final class ControlTransportPoller extends UdpTransportPoller private final StatusMessageFlyweight statusMessage = new StatusMessageFlyweight(unsafeBuffer); private final RttMeasurementFlyweight rttMeasurement = new RttMeasurementFlyweight(unsafeBuffer); private final ResponseSetupFlyweight responseSetup = new ResponseSetupFlyweight(unsafeBuffer); + private final ErrorFlyweight error = new ErrorFlyweight(unsafeBuffer); private final DriverConductorProxy conductorProxy; private final Consumer selectorPoller = (selectionKey) -> poll((SendChannelEndpoint)selectionKey.attachment()); @@ -185,6 +187,11 @@ else if (HDR_TYPE_SM == frameType) channelEndpoint.onStatusMessage( statusMessage, unsafeBuffer, bytesReceived, srcAddress, conductorProxy); } + else if (HDR_TYPE_ERR == frameType) + { + channelEndpoint.onError( + error, unsafeBuffer, bytesReceived, srcAddress, conductorProxy); + } else if (HDR_TYPE_RTTM == frameType) { channelEndpoint.onRttMeasurement(rttMeasurement, unsafeBuffer, bytesReceived, srcAddress); diff --git a/aeron-driver/src/main/java/io/aeron/driver/media/ReceiveChannelEndpoint.java b/aeron-driver/src/main/java/io/aeron/driver/media/ReceiveChannelEndpoint.java index eacfa57c3c..3086964eb8 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/media/ReceiveChannelEndpoint.java +++ b/aeron-driver/src/main/java/io/aeron/driver/media/ReceiveChannelEndpoint.java @@ -17,7 +17,6 @@ import io.aeron.CommonContext; import io.aeron.ErrorCode; -import io.aeron.command.ErrorResponseFlyweight; import io.aeron.driver.DataPacketDispatcher; import io.aeron.driver.DriverConductorProxy; import io.aeron.driver.MediaDriver; @@ -115,6 +114,7 @@ public class ReceiveChannelEndpoint extends ReceiveChannelEndpointRhsPadding private final RttMeasurementFlyweight rttMeasurementFlyweight; private final ByteBuffer responseSetupBuffer; private final ResponseSetupFlyweight responseSetupHeader; + private final ByteBuffer errorBuffer; private final ErrorFlyweight errorFlyweight; private final AtomicCounter shortSends; private final AtomicCounter possibleTtlAsymmetry; @@ -164,6 +164,7 @@ public ReceiveChannelEndpoint( rttMeasurementFlyweight = threadLocals.rttMeasurementFlyweight(); responseSetupBuffer = threadLocals.responseSetupBuffer(); responseSetupHeader = threadLocals.responseSetupHeader(); + errorBuffer = threadLocals.errorBuffer(); errorFlyweight = threadLocals.errorFlyweight(); cachedNanoClock = context.receiverCachedNanoClock(); timeOfLastActivityNs = cachedNanoClock.nanoTime(); @@ -944,18 +945,30 @@ public void sendResponseSetup( /** * Send an error frame back to the source publications to indicate this image has errored. * - * @param controlAddresses of the sources. - * @param sessionId for the image. - * @param streamId for the image. - * @param invalidationReason to be sent back to the publication. + * @param controlAddresses of the sources. + * @param sessionId for the image. + * @param streamId for the image. + * @param errorCode for the error being sent. + * @param errorMessage to be sent back to the publication. */ public void sendErrorFrame( final ImageConnection[] controlAddresses, final int sessionId, final int streamId, - final String invalidationReason) + final int errorCode, + final String errorMessage) { + errorBuffer.clear(); + errorFlyweight + .sessionId(sessionId) + .streamId(streamId) + .receiverId(receiverId) + .groupTag(groupTag) + .errorCode(errorCode) + .errorMessage(errorMessage); + errorBuffer.limit(errorFlyweight.frameLength()); + send(errorBuffer, errorFlyweight.frameLength(), controlAddresses); } /** diff --git a/aeron-driver/src/main/java/io/aeron/driver/media/SendChannelEndpoint.java b/aeron-driver/src/main/java/io/aeron/driver/media/SendChannelEndpoint.java index 3686695f5b..b91b9a6427 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/media/SendChannelEndpoint.java +++ b/aeron-driver/src/main/java/io/aeron/driver/media/SendChannelEndpoint.java @@ -26,6 +26,7 @@ import io.aeron.driver.status.MdcDestinations; import io.aeron.exceptions.ControlProtocolException; import io.aeron.protocol.DataHeaderFlyweight; +import io.aeron.protocol.ErrorFlyweight; import io.aeron.protocol.NakFlyweight; import io.aeron.protocol.ResponseSetupFlyweight; import io.aeron.protocol.RttMeasurementFlyweight; @@ -400,6 +401,42 @@ public void onStatusMessage( } } + + /** + * Callback back handler for received error messages. + * + * @param msg flyweight over the status message. + * @param buffer containing the message. + * @param length of the message. + * @param srcAddress of the message. + * @param conductorProxy to send messages back to the conductor. + */ + public void onError( + final ErrorFlyweight msg, + final UnsafeBuffer buffer, + final int length, + final InetSocketAddress srcAddress, + final DriverConductorProxy conductorProxy) + { + final int sessionId = msg.sessionId(); + final int streamId = msg.streamId(); + + // TODO: Error message counter. + statusMessagesReceived.incrementOrdered(); + + if (null != multiSndDestination) + { + // TODO: What do we need to do here??? +// multiSndDestination.onStatusMessage(msg, srcAddress); + } + + final NetworkPublication publication = publicationBySessionAndStreamId.get(compoundKey(sessionId, streamId)); + if (null != publication) + { + publication.onError(msg, srcAddress, conductorProxy); + } + } + /** * Callback back handler for received NAK messages. * diff --git a/aeron-driver/src/test/java/io/aeron/driver/TaggedMulticastFlowControlTest.java b/aeron-driver/src/test/java/io/aeron/driver/TaggedMulticastFlowControlTest.java index fa6bb54bdd..510ca494ce 100644 --- a/aeron-driver/src/test/java/io/aeron/driver/TaggedMulticastFlowControlTest.java +++ b/aeron-driver/src/test/java/io/aeron/driver/TaggedMulticastFlowControlTest.java @@ -16,6 +16,7 @@ package io.aeron.driver; import io.aeron.driver.media.UdpChannel; +import io.aeron.protocol.ErrorFlyweight; import io.aeron.protocol.StatusMessageFlyweight; import io.aeron.test.Tests; import org.agrona.concurrent.UnsafeBuffer; @@ -26,8 +27,10 @@ import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; +import java.nio.ByteBuffer; import java.util.stream.Stream; +import static io.aeron.protocol.ErrorFlyweight.MAX_ERROR_FRAME_LENGTH; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -171,6 +174,53 @@ void shouldNotIncludeReceiverMoreThanWindowSizeBehindMinPosition() assertEquals(termOffset2 + WINDOW_LENGTH, onStatusMessage(flowControl, 3, termOffset2, senderLimit, 123L)); } + @Test + void shouldRemoveEntryFromFlowControlOnEndOfStream() + { + final UdpChannel udpChannel = UdpChannel.parse( + "aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min,g:123/2"); + + flowControl.initialize( + newContext(), countersManager, udpChannel, 0, 0, 0, 0, 0); + + final int senderLimit = 5000; + final int termOffset1 = WINDOW_LENGTH; + final int termOffset2 = WINDOW_LENGTH + 1; + final int termOffset3 = WINDOW_LENGTH + 2; + + assertEquals(senderLimit, onStatusMessage(flowControl, 1, termOffset1, senderLimit, 123L)); + assertEquals(termOffset1 + WINDOW_LENGTH, onStatusMessage(flowControl, 2, termOffset2, senderLimit, 123L)); + assertEquals(termOffset1 + WINDOW_LENGTH, onStatusMessage(flowControl, 3, termOffset3, senderLimit, 123L)); + + final long publicationLimit = onEosStatusMessage(flowControl, 1, termOffset1, senderLimit, 123L); + + assertEquals(termOffset1 + WINDOW_LENGTH, publicationLimit); + assertEquals(termOffset2 + WINDOW_LENGTH, onIdle(flowControl, senderLimit)); + } + + @Test + void shouldRemoveEntryFromFlowControlOnError() + { + final UdpChannel udpChannel = UdpChannel.parse( + "aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min,g:123/2"); + + flowControl.initialize( + newContext(), countersManager, udpChannel, 0, 0, 0, 0, 0); + + final int senderLimit = 5000; + final int termOffset1 = WINDOW_LENGTH; + final int termOffset2 = WINDOW_LENGTH + 1; + final int termOffset3 = WINDOW_LENGTH + 2; + + assertEquals(senderLimit, onStatusMessage(flowControl, 1, termOffset1, senderLimit, 123L)); + assertEquals(termOffset1 + WINDOW_LENGTH, onStatusMessage(flowControl, 2, termOffset2, senderLimit, 123L)); + assertEquals(termOffset1 + WINDOW_LENGTH, onStatusMessage(flowControl, 3, termOffset3, senderLimit, 123L)); + + onErrorMessage(flowControl, 1, 123L); + + assertEquals(termOffset2 + WINDOW_LENGTH, onIdle(flowControl, senderLimit)); + } + private long onStatusMessage( final TaggedMulticastFlowControl flowControl, final long receiverId, @@ -178,13 +228,48 @@ private long onStatusMessage( final long senderLimit, final Long groupTag) { - final StatusMessageFlyweight statusMessageFlyweight = new StatusMessageFlyweight(); - statusMessageFlyweight.wrap(new byte[1024]); + return onStatusMessage(flowControl, receiverId, termOffset, senderLimit, groupTag, false); + } + + private long onEosStatusMessage( + final TaggedMulticastFlowControl flowControl, + final long receiverId, + final int termOffset, + final long senderLimit, + final Long groupTag) + { + return onStatusMessage(flowControl, receiverId, termOffset, senderLimit, groupTag, true); + } + + private void onErrorMessage( + final TaggedMulticastFlowControl flowControl, + final long receiverId, + final Long groupTag) + { + final ErrorFlyweight error = new ErrorFlyweight(ByteBuffer.allocate(MAX_ERROR_FRAME_LENGTH)); + error + .receiverId(receiverId) + .groupTag(groupTag); + + flowControl.onError(error, null, 0); + } + + private static long onStatusMessage( + final TaggedMulticastFlowControl flowControl, + final long receiverId, + final int termOffset, + final long senderLimit, + final Long groupTag, + final boolean isEos) + { + final StatusMessageFlyweight statusMessageFlyweight = new StatusMessageFlyweight(ByteBuffer.allocate(1024)); + final short flags = (isEos ? StatusMessageFlyweight.END_OF_STREAM_FLAG : 0); statusMessageFlyweight.receiverId(receiverId); statusMessageFlyweight.consumptionTermId(0); statusMessageFlyweight.consumptionTermOffset(termOffset); statusMessageFlyweight.groupTag(groupTag); + statusMessageFlyweight.flags(flags); statusMessageFlyweight.receiverWindowLength(WINDOW_LENGTH); return flowControl.onStatusMessage(statusMessageFlyweight, null, senderLimit, 0, 0, 0); diff --git a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java index 44708f8229..343baa239d 100644 --- a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java +++ b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java @@ -32,9 +32,12 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.RegisterExtension; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import static java.nio.charset.StandardCharsets.US_ASCII; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.lessThan; import static org.junit.jupiter.api.Assertions.assertEquals; @ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class }) @@ -67,8 +70,10 @@ private TestMediaDriver launch() @Test @InterruptAfter(20) @SlowTest - void shouldInvalidateAnSubscriptionsImage() + void shouldInvalidateSubscriptionsImage() { + context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3)); + final TestMediaDriver driver = launch(); final Aeron.Context ctx = new Aeron.Context() @@ -78,8 +83,7 @@ void shouldInvalidateAnSubscriptionsImage() try (Aeron aeron = Aeron.connect(ctx); Publication pub = aeron.addPublication(channel, streamId); - Subscription sub = aeron.addSubscription( - channel, streamId, image -> {}, image -> imageUnavailable.set(true))) + Subscription sub = aeron.addSubscription(channel, streamId, null, image -> imageUnavailable.set(true))) { Tests.awaitConnected(pub); Tests.awaitConnected(sub); @@ -100,10 +104,14 @@ void shouldInvalidateAnSubscriptionsImage() final String reason = "Needs to be closed"; image.invalidate(reason); + final long t0 = System.nanoTime(); while (pub.isConnected()) { Tests.yield(); } + final long t1 = System.nanoTime(); + final long value = driver.context().publicationConnectionTimeoutNs(); + assertThat(t1 - t0, lessThan(value)); while (!imageUnavailable.get()) { From 6e98c4d7f2e641d47f854a40ba6a69ce6733d7ec Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Mon, 20 May 2024 15:26:41 +1200 Subject: [PATCH 06/67] [C] Start adding invalidate image information. --- .../main/c/command/aeron_control_protocol.h | 12 +++++++++++- .../src/main/c/aeron_driver_conductor.c | 19 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/aeron-client/src/main/c/command/aeron_control_protocol.h b/aeron-client/src/main/c/command/aeron_control_protocol.h index e465c42a6b..06bd899ef8 100644 --- a/aeron-client/src/main/c/command/aeron_control_protocol.h +++ b/aeron-client/src/main/c/command/aeron_control_protocol.h @@ -35,7 +35,8 @@ #define AERON_COMMAND_REMOVE_RCV_DESTINATION (0x0D) #define AERON_COMMAND_TERMINATE_DRIVER (0x0E) #define AERON_COMMAND_ADD_STATIC_COUNTER (0x0F) -#define AERON_COMMAND_REMOVE_DESTINATION_BY_ID (0x10) +#define AERON_COMMAND_INVALIDATE_IMAGE (0x10) +#define AERON_COMMAND_REMOVE_DESTINATION_BY_ID (0x11) #define AERON_RESPONSE_ON_ERROR (0x0F01) #define AERON_RESPONSE_ON_AVAILABLE_IMAGE (0x0F02) @@ -208,6 +209,15 @@ typedef struct aeron_terminate_driver_command_stct } aeron_terminate_driver_command_t; +typedef struct aeron_invalidate_image_command_stct +{ + aeron_correlated_command_t correlated; + int64_t image_correlation_id; + int64_t position; + int32_t reason_length; +} +aeron_invalidate_image_command_t; + #pragma pack(pop) #endif //AERON_CONTROL_PROTOCOL_H diff --git a/aeron-driver/src/main/c/aeron_driver_conductor.c b/aeron-driver/src/main/c/aeron_driver_conductor.c index 255f5a37c0..a9ba59ad84 100644 --- a/aeron-driver/src/main/c/aeron_driver_conductor.c +++ b/aeron-driver/src/main/c/aeron_driver_conductor.c @@ -3317,6 +3317,25 @@ aeron_rb_read_action_t aeron_driver_conductor_on_command( break; } + case AERON_COMMAND_INVALIDATE_IMAGE: + { + aeron_invalidate_image_command_t *command = (aeron_invalidate_image_command_t *)message; + + if (length < sizeof (aeron_invalidate_image_command_t)) + { + goto malformed_command; + } + + if (length < sizeof(aeron_invalidate_image_command_t) + command->reason_length) + { + goto malformed_command; + } + + result = aeron_driver_conductor_on_invalidate_image(conductor, command); + + break; + } + case AERON_COMMAND_REMOVE_DESTINATION_BY_ID: { aeron_destination_by_id_command_t *command = (aeron_destination_by_id_command_t *)message; From 0c21ecf86c47d5b6763fe1ffe56b392243b317fe Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Fri, 24 May 2024 15:10:22 +1200 Subject: [PATCH 07/67] [C] Invalidate image on the receiver. --- .../src/main/c/aeron_driver_conductor.c | 42 ++++++++++++++++++- .../src/main/c/aeron_driver_conductor.h | 3 ++ .../src/main/c/aeron_driver_receiver.c | 20 +++++++++ .../src/main/c/aeron_driver_receiver.h | 2 + .../src/main/c/aeron_driver_receiver_proxy.c | 28 +++++++++++++ .../src/main/c/aeron_driver_receiver_proxy.h | 37 +++++++++++----- .../src/main/c/aeron_publication_image.c | 19 +++++++++ .../src/main/c/aeron_publication_image.h | 3 ++ 8 files changed, 142 insertions(+), 12 deletions(-) diff --git a/aeron-driver/src/main/c/aeron_driver_conductor.c b/aeron-driver/src/main/c/aeron_driver_conductor.c index a9ba59ad84..1466131e9b 100644 --- a/aeron-driver/src/main/c/aeron_driver_conductor.c +++ b/aeron-driver/src/main/c/aeron_driver_conductor.c @@ -1875,6 +1875,22 @@ static int aeron_driver_conductor_find_response_publication_image( return -1; } +static aeron_publication_image_t * aeron_driver_conductor_find_publication_image( + aeron_driver_conductor_t *conductor, + int64_t correlation_id) +{ + for (size_t i = 0; i < conductor->publication_images.length; i++) + { + aeron_publication_image_t *image_entry = conductor->publication_images.array[i].image; + if (aeron_publication_image_registration_id(image_entry) == correlation_id) + { + return image_entry; + } + } + + return NULL; +} + aeron_network_publication_t *aeron_driver_conductor_get_or_add_network_publication( aeron_driver_conductor_t *conductor, aeron_client_t *client, @@ -5653,7 +5669,31 @@ int aeron_driver_conductor_on_terminate_driver( return 0; } -void aeron_driver_conductor_on_create_publication_image(void *clientd, void *item) +int aeron_driver_conductor_on_invalidate_image( + aeron_driver_conductor_t *conductor, aeron_invalidate_image_command_t *command) +{ + const int64_t image_correlation_id = command->image_correlation_id; + aeron_publication_image_t *image = aeron_driver_conductor_find_publication_image( + conductor, image_correlation_id); + + if (NULL == image) + { + AERON_SET_ERR( + AERON_ERROR_CODE_GENERIC_ERROR, "Unable to resolve image for correlationId=", image_correlation_id); + return -1; + } + + const char *reason = (const char *)(command + 1); + aeron_driver_receiver_proxy_on_invalidate_image( + conductor->context->receiver_proxy, image_correlation_id, command->position, command->reason_length, reason); + + aeron_driver_conductor_on_operation_succeeded(conductor, command->correlated.correlation_id); + + return 0; +} + + + void aeron_driver_conductor_on_create_publication_image(void *clientd, void *item) { aeron_driver_conductor_t *conductor = (aeron_driver_conductor_t *)clientd; aeron_command_create_publication_image_t *command = (aeron_command_create_publication_image_t *)item; diff --git a/aeron-driver/src/main/c/aeron_driver_conductor.h b/aeron-driver/src/main/c/aeron_driver_conductor.h index 67ad3ed0bf..e16b6a880d 100644 --- a/aeron-driver/src/main/c/aeron_driver_conductor.h +++ b/aeron-driver/src/main/c/aeron_driver_conductor.h @@ -522,6 +522,9 @@ int aeron_driver_conductor_on_client_close(aeron_driver_conductor_t *conductor, int aeron_driver_conductor_on_terminate_driver( aeron_driver_conductor_t *conductor, aeron_terminate_driver_command_t *command); +int aeron_driver_conductor_on_invalidate_image( + aeron_driver_conductor_t *conductor, aeron_invalidate_image_command_t *command); + void aeron_driver_conductor_on_create_publication_image(void *clientd, void *item); void aeron_driver_conductor_on_re_resolve_endpoint(void *clientd, void *item); diff --git a/aeron-driver/src/main/c/aeron_driver_receiver.c b/aeron-driver/src/main/c/aeron_driver_receiver.c index d0cb3fa853..c243232384 100644 --- a/aeron-driver/src/main/c/aeron_driver_receiver.c +++ b/aeron-driver/src/main/c/aeron_driver_receiver.c @@ -582,6 +582,26 @@ void aeron_driver_receiver_on_resolution_change(void *clientd, void *item) aeron_receive_channel_endpoint_update_control_address(endpoint, destination, &cmd->new_addr); } +void aeron_driver_receiver_on_invalidate_image(void *clientd, void *item) +{ + aeron_driver_receiver_t *receiver = clientd; + aeron_command_receiver_invalidate_image_t *cmd = item; + const int64_t correlation_id = cmd->image_correlation_id; + const int32_t reason_length = cmd->reason_length; + const char *reason = (const char *)cmd + 1; + + for (size_t i = 0, size = receiver->images.length; i < size; i++) + { + aeron_publication_image_t *image = receiver->images.array[i].image; + // TODO: Should we pass the pointer to the image here instead of the correlation_id. + if (correlation_id == aeron_publication_image_registration_id(image)) + { + aeron_publication_image_invalidate(image, reason_length, reason); + break; + } + } +} + int aeron_driver_receiver_add_pending_setup( aeron_driver_receiver_t *receiver, aeron_receive_channel_endpoint_t *endpoint, diff --git a/aeron-driver/src/main/c/aeron_driver_receiver.h b/aeron-driver/src/main/c/aeron_driver_receiver.h index 6ab7b85040..77874ca5de 100644 --- a/aeron-driver/src/main/c/aeron_driver_receiver.h +++ b/aeron-driver/src/main/c/aeron_driver_receiver.h @@ -123,6 +123,8 @@ void aeron_driver_receiver_on_remove_matching_state(void *clientd, void *item); void aeron_driver_receiver_on_resolution_change(void *clientd, void *item); +void aeron_driver_receiver_on_invalidate_image(void *clientd, void *item); + int aeron_driver_receiver_add_pending_setup( aeron_driver_receiver_t *receiver, aeron_receive_channel_endpoint_t *endpoint, diff --git a/aeron-driver/src/main/c/aeron_driver_receiver_proxy.c b/aeron-driver/src/main/c/aeron_driver_receiver_proxy.c index 83a1426232..2c9065f485 100644 --- a/aeron-driver/src/main/c/aeron_driver_receiver_proxy.c +++ b/aeron-driver/src/main/c/aeron_driver_receiver_proxy.c @@ -351,3 +351,31 @@ void aeron_driver_receiver_proxy_on_resolution_change( aeron_driver_receiver_proxy_offer(receiver_proxy, &cmd, sizeof(cmd)); } } + +void aeron_driver_receiver_proxy_on_invalidate_image( + aeron_driver_receiver_proxy_t *receiver_proxy, + int64_t image_correlation_id, + int64_t position, + int32_t reason_length, + const char *reason) +{ + // TODO: max reason length... + uint8_t message_buffer[sizeof(aeron_command_base_t) + 1024] = { 0 }; + aeron_command_receiver_invalidate_image_t *cmd = (aeron_command_receiver_invalidate_image_t *)&message_buffer[0]; + + cmd->base.func = aeron_driver_receiver_on_invalidate_image; + cmd->base.item = NULL; + cmd->image_correlation_id = image_correlation_id; + cmd->position = position; + cmd->reason_length = reason_length; + memcpy(cmd + 1, reason, reason_length); // TODO: Ensure validated. + + if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(receiver_proxy->threading_mode)) + { + aeron_driver_receiver_on_invalidate_image(receiver_proxy->receiver, cmd); + } + else + { + aeron_driver_receiver_proxy_offer(receiver_proxy, cmd, sizeof(*cmd) + reason_length); + } +} diff --git a/aeron-driver/src/main/c/aeron_driver_receiver_proxy.h b/aeron-driver/src/main/c/aeron_driver_receiver_proxy.h index 0747f4f040..dab6007542 100644 --- a/aeron-driver/src/main/c/aeron_driver_receiver_proxy.h +++ b/aeron-driver/src/main/c/aeron_driver_receiver_proxy.h @@ -123,6 +123,26 @@ typedef struct aeron_command_on_remove_matching_state_stct } aeron_command_on_remove_matching_state_t; +typedef struct aeron_command_receiver_resolution_change_stct +{ + aeron_command_base_t base; + const char *endpoint_name; + void *endpoint; + void *destination; + struct sockaddr_storage new_addr; +} +aeron_command_receiver_resolution_change_t; + +typedef struct aeron_command_receiver_invalidate_image_stct +{ + aeron_command_base_t base; + int64_t image_correlation_id; + int64_t position; + int32_t reason_length; +} +aeron_command_receiver_invalidate_image_t; + + void aeron_driver_receiver_proxy_on_add_publication_image( aeron_driver_receiver_proxy_t *receiver_proxy, aeron_receive_channel_endpoint_t *endpoint, @@ -140,17 +160,12 @@ void aeron_driver_receiver_proxy_on_remove_init_in_progress( aeron_receive_channel_endpoint_t *endpoint, int32_t session_id, int32_t stream_id); - -typedef struct aeron_command_receiver_resolution_change_stct -{ - aeron_command_base_t base; - const char *endpoint_name; - void *endpoint; - void *destination; - struct sockaddr_storage new_addr; -} -aeron_command_receiver_resolution_change_t; - +void aeron_driver_receiver_proxy_on_invalidate_image( + aeron_driver_receiver_proxy_t *receiver_proxy, + int64_t image_correlation_id, + int64_t position, + int32_t reason_length, + const char *reason); void aeron_driver_receiver_proxy_on_resolution_change( aeron_driver_receiver_proxy_t *receiver_proxy, const char *endpoint_name, diff --git a/aeron-driver/src/main/c/aeron_publication_image.c b/aeron-driver/src/main/c/aeron_publication_image.c index c61e791399..232de74d87 100644 --- a/aeron-driver/src/main/c/aeron_publication_image.c +++ b/aeron-driver/src/main/c/aeron_publication_image.c @@ -273,6 +273,7 @@ int aeron_publication_image_create( _image->is_sending_eos_sm = false; _image->has_receiver_released = false; _image->sm_timeout_ns = (int64_t)context->status_message_timeout_ns; + _image->invalidation_reason = NULL; memcpy(&_image->source_address, source_address, sizeof(_image->source_address)); const int source_identity_length = aeron_format_source_identity( @@ -374,6 +375,7 @@ bool aeron_publication_image_free(aeron_publication_image_t *image) aeron_counter_add_ordered(image->mapped_bytes_counter, -((int64_t)image->mapped_raw_log.mapped_file.length)); aeron_free(image->log_file_name); + aeron_free((void *)image->invalidation_reason); aeron_free(image); return true; @@ -620,6 +622,11 @@ int aeron_publication_image_insert_packet( return 0; } + if (NULL != image->invalidation_reason) + { + return 0; + } + const bool is_heartbeat = aeron_publication_image_is_heartbeat(buffer, length); const int64_t packet_position = aeron_logbuffer_compute_position( term_id, term_offset, image->position_bits_to_shift, image->initial_term_id); @@ -693,6 +700,12 @@ int aeron_publication_image_on_rttm( // Called from receiver. int aeron_publication_image_send_pending_status_message(aeron_publication_image_t *image, int64_t now_ns) { + // TODO: Send error frame instead. + if (NULL != image->invalidation_reason) + { + return 0; + } + int work_count = 0; int64_t change_number; int32_t response_session_id = 0; @@ -1101,6 +1114,12 @@ void aeron_publication_image_receiver_release(aeron_publication_image_t *image) AERON_PUT_ORDERED(image->has_receiver_released, true); } +void aeron_publication_image_invalidate(aeron_publication_image_t *image, int32_t reason_length, const char *reason) +{ + aeron_alloc((void **)&image->invalidation_reason, reason_length + 1); + memcpy((void *)image->invalidation_reason, reason, reason_length); +} + void aeron_publication_image_remove_response_session_id(aeron_publication_image_t *image) { aeron_publication_image_set_response_session_id(image, AERON_PUBLICATION_RESPONSE_NULL_RESPONSE_SESSION_ID); diff --git a/aeron-driver/src/main/c/aeron_publication_image.h b/aeron-driver/src/main/c/aeron_publication_image.h index 68b3e62a18..088f3776e0 100644 --- a/aeron-driver/src/main/c/aeron_publication_image.h +++ b/aeron-driver/src/main/c/aeron_publication_image.h @@ -136,6 +136,7 @@ typedef struct aeron_publication_image_stct int64_t sm_timeout_ns; int64_t time_of_last_packet_ns; + const char *invalidation_reason; volatile int64_t response_session_id; @@ -218,6 +219,8 @@ void aeron_publication_image_on_time_event( void aeron_publication_image_receiver_release(aeron_publication_image_t *image); +void aeron_publication_image_invalidate(aeron_publication_image_t *image, int32_t reason_length, const char *reason); + inline bool aeron_publication_image_is_heartbeat(const uint8_t *buffer, size_t length) { return length == AERON_DATA_HEADER_LENGTH && 0 == ((aeron_frame_header_t *)buffer)->frame_length; From 108b9fc78666d0f024a7cbb4c21621d40a7cdd82 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Tue, 28 May 2024 10:34:15 +1200 Subject: [PATCH 08/67] [Java/C] Fix CodeQL errors. --- aeron-client/src/test/java/io/aeron/ImageTest.java | 1 - aeron-driver/src/main/c/aeron_driver_conductor.c | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/aeron-client/src/test/java/io/aeron/ImageTest.java b/aeron-client/src/test/java/io/aeron/ImageTest.java index ad3c9bcc36..96b7e7764a 100644 --- a/aeron-client/src/test/java/io/aeron/ImageTest.java +++ b/aeron-client/src/test/java/io/aeron/ImageTest.java @@ -616,7 +616,6 @@ void shouldInvalidateFragment() final int initialOffset = TERM_BUFFER_LENGTH - (ALIGNED_FRAME_LENGTH * 2); final long initialPosition = computePosition( INITIAL_TERM_ID, initialOffset, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID); - final long maxPosition = (long)Integer.MAX_VALUE + 1000; position.setOrdered(initialPosition); final Image image = createImage(); diff --git a/aeron-driver/src/main/c/aeron_driver_conductor.c b/aeron-driver/src/main/c/aeron_driver_conductor.c index 1466131e9b..9623f0c91f 100644 --- a/aeron-driver/src/main/c/aeron_driver_conductor.c +++ b/aeron-driver/src/main/c/aeron_driver_conductor.c @@ -5675,11 +5675,12 @@ int aeron_driver_conductor_on_invalidate_image( const int64_t image_correlation_id = command->image_correlation_id; aeron_publication_image_t *image = aeron_driver_conductor_find_publication_image( conductor, image_correlation_id); - + if (NULL == image) { AERON_SET_ERR( - AERON_ERROR_CODE_GENERIC_ERROR, "Unable to resolve image for correlationId=", image_correlation_id); + AERON_ERROR_CODE_GENERIC_ERROR, + "Unable to resolve image for correlationId=%" PRId64, image_correlation_id); return -1; } From 68a059f11fdbcb3389140770c6388b6c2676b72d Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Thu, 13 Jun 2024 11:47:22 +1200 Subject: [PATCH 09/67] [C] Add support for send/receiving error frames and update flow control and liveness. --- .../src/main/c/protocol/aeron_udp_protocol.h | 15 +++++++ aeron-driver/src/main/c/aeron_flow_control.c | 11 +++++ aeron-driver/src/main/c/aeron_flow_control.h | 8 ++++ .../src/main/c/aeron_min_flow_control.c | 22 ++++++++++ .../src/main/c/aeron_network_publication.c | 12 ++++++ .../src/main/c/aeron_network_publication.h | 6 +++ .../src/main/c/aeron_publication_image.c | 25 ++++++++--- .../c/media/aeron_receive_channel_endpoint.c | 41 +++++++++++++++++++ .../c/media/aeron_receive_channel_endpoint.h | 9 ++++ .../c/media/aeron_send_channel_endpoint.c | 37 +++++++++++++++++ .../c/media/aeron_send_channel_endpoint.h | 7 ++++ 11 files changed, 188 insertions(+), 5 deletions(-) diff --git a/aeron-client/src/main/c/protocol/aeron_udp_protocol.h b/aeron-client/src/main/c/protocol/aeron_udp_protocol.h index 374016ddc5..b3bf2e3e2e 100644 --- a/aeron-client/src/main/c/protocol/aeron_udp_protocol.h +++ b/aeron-client/src/main/c/protocol/aeron_udp_protocol.h @@ -79,6 +79,18 @@ typedef struct aeron_status_message_header_stct } aeron_status_message_header_t; +struct aeron_error_stct +{ + aeron_frame_header_t frame_header; + int32_t session_id; + int32_t stream_id; + int64_t receiver_id; + int64_t group_tag; + int32_t error_code; + int32_t error_length; +}; +typedef struct aeron_error_stct aeron_error_t; + typedef struct aeron_status_message_optional_header_stct { int64_t group_tag; @@ -199,6 +211,9 @@ int aeron_udp_protocol_group_tag(aeron_status_message_header_t *sm, int64_t *gro #define AERON_OPT_HDR_ALIGNMENT (4u) +#define AERON_ERROR_MAX_MESSAGE_LENGTH (1023) +#define AERON_ERROR_HAS_GROUP_TAG_FLAG (0x08) + inline size_t aeron_res_header_address_length(int8_t res_type) { return AERON_RES_HEADER_TYPE_NAME_TO_IP6_MD == res_type ? diff --git a/aeron-driver/src/main/c/aeron_flow_control.c b/aeron-driver/src/main/c/aeron_flow_control.c index 0a29119d73..f20fb67c52 100644 --- a/aeron-driver/src/main/c/aeron_flow_control.c +++ b/aeron-driver/src/main/c/aeron_flow_control.c @@ -110,6 +110,15 @@ int64_t aeron_max_flow_control_strategy_on_sm( return snd_lmt > window_edge ? snd_lmt : window_edge; } +void aeron_max_flow_control_strategy_on_error( + void *state, + const uint8_t *error, + size_t length, + struct sockaddr_storage *recv_addr, + int64_t now_ns) +{ +} + size_t aeron_flow_control_calculate_retransmission_length( size_t resend_length, size_t term_buffer_length, @@ -192,6 +201,7 @@ int aeron_max_multicast_flow_control_strategy_supplier( _strategy->on_idle = aeron_max_flow_control_strategy_on_idle; _strategy->on_status_message = aeron_max_flow_control_strategy_on_sm; _strategy->on_setup = aeron_max_flow_control_strategy_on_setup; + _strategy->on_error = aeron_max_flow_control_strategy_on_error; _strategy->fini = aeron_max_flow_control_strategy_fini; _strategy->has_required_receivers = aeron_flow_control_strategy_has_required_receivers_default; _strategy->on_trigger_send_setup = aeron_max_flow_control_strategy_on_trigger_send_setup; @@ -224,6 +234,7 @@ int aeron_unicast_flow_control_strategy_supplier( _strategy->on_idle = aeron_max_flow_control_strategy_on_idle; _strategy->on_status_message = aeron_max_flow_control_strategy_on_sm; _strategy->on_setup = aeron_max_flow_control_strategy_on_setup; + _strategy->on_error = aeron_max_flow_control_strategy_on_error; _strategy->fini = aeron_max_flow_control_strategy_fini; _strategy->has_required_receivers = aeron_flow_control_strategy_has_required_receivers_default; _strategy->on_trigger_send_setup = aeron_max_flow_control_strategy_on_trigger_send_setup; diff --git a/aeron-driver/src/main/c/aeron_flow_control.h b/aeron-driver/src/main/c/aeron_flow_control.h index 6a27fa53ba..3f5695d9d3 100644 --- a/aeron-driver/src/main/c/aeron_flow_control.h +++ b/aeron-driver/src/main/c/aeron_flow_control.h @@ -57,6 +57,13 @@ typedef int64_t (*aeron_flow_control_strategy_on_setup_func_t)( size_t position_bits_to_shift, int64_t snd_pos); +typedef void (*aeron_flow_control_strategy_on_error_func_t)( + void *state, + const uint8_t *error, + size_t length, + struct sockaddr_storage *recv_addr, + int64_t now_ns); + typedef void (*aeron_flow_control_strategy_on_trigger_send_setup_func_t)( void *state, const uint8_t *sm, @@ -80,6 +87,7 @@ typedef struct aeron_flow_control_strategy_stct aeron_flow_control_strategy_on_sm_func_t on_status_message; aeron_flow_control_strategy_on_idle_func_t on_idle; aeron_flow_control_strategy_on_setup_func_t on_setup; + aeron_flow_control_strategy_on_error_func_t on_error; aeron_flow_control_strategy_fini_func_t fini; aeron_flow_control_strategy_has_required_receivers_func_t has_required_receivers; aeron_flow_control_strategy_on_trigger_send_setup_func_t on_trigger_send_setup; diff --git a/aeron-driver/src/main/c/aeron_min_flow_control.c b/aeron-driver/src/main/c/aeron_min_flow_control.c index 7b2aad6863..47c138b811 100644 --- a/aeron-driver/src/main/c/aeron_min_flow_control.c +++ b/aeron-driver/src/main/c/aeron_min_flow_control.c @@ -295,6 +295,27 @@ int64_t aeron_min_flow_control_strategy_on_setup( return snd_lmt; } +void aeron_min_flow_control_strategy_on_error( + void *state, + const uint8_t *error, + size_t length, + struct sockaddr_storage *recv_addr, + int64_t now_ns) +{ + aeron_min_flow_control_strategy_state_t *strategy_state = (aeron_min_flow_control_strategy_state_t *)state; + aeron_error_t *error_header = (aeron_error_t *)error; + + for (size_t i = 0; i < strategy_state->receivers.length; i++) + { + aeron_min_flow_control_strategy_receiver_t *receiver = &strategy_state->receivers.array[i]; + + if (error_header->receiver_id == receiver->receiver_id) + { + receiver->eos_flagged = true; + } + } +} + int64_t aeron_tagged_flow_control_strategy_on_sm( void *state, const uint8_t *sm, @@ -513,6 +534,7 @@ int aeron_tagged_flow_control_strategy_supplier_init( aeron_tagged_flow_control_strategy_on_sm : aeron_min_flow_control_strategy_on_sm; _strategy->on_setup = is_group_tag_aware ? aeron_tagged_flow_control_strategy_on_setup : aeron_min_flow_control_strategy_on_setup; + _strategy->on_error = aeron_min_flow_control_strategy_on_error; _strategy->fini = aeron_min_flow_control_strategy_fini; _strategy->has_required_receivers = aeron_min_flow_control_strategy_has_required_receivers; _strategy->on_trigger_send_setup = is_group_tag_aware ? diff --git a/aeron-driver/src/main/c/aeron_network_publication.c b/aeron-driver/src/main/c/aeron_network_publication.c index 3ae8807f9b..1e52bb3f36 100644 --- a/aeron-driver/src/main/c/aeron_network_publication.c +++ b/aeron-driver/src/main/c/aeron_network_publication.c @@ -831,6 +831,18 @@ void aeron_network_publication_on_status_message( aeron_network_publication_has_required_receivers(publication)); } +void aeron_network_publication_on_error( + aeron_network_publication_t *publication, + const uint8_t *buffer, + size_t length, + struct sockaddr_storage *addr) +{ + aeron_error_t *error = (aeron_error_t *)buffer; + const int64_t time_ns = aeron_clock_cached_nano_time(publication->cached_clock); + aeron_network_publication_liveness_on_remote_close(publication, error->receiver_id); + publication->flow_control->on_error(publication->flow_control->state, buffer, length, addr, time_ns); +} + void aeron_network_publication_on_rttm( aeron_network_publication_t *publication, const uint8_t *buffer, size_t length, struct sockaddr_storage *addr) { diff --git a/aeron-driver/src/main/c/aeron_network_publication.h b/aeron-driver/src/main/c/aeron_network_publication.h index b774abb9ba..25b065a83a 100644 --- a/aeron-driver/src/main/c/aeron_network_publication.h +++ b/aeron-driver/src/main/c/aeron_network_publication.h @@ -181,6 +181,12 @@ void aeron_network_publication_on_status_message( size_t length, struct sockaddr_storage *addr); +void aeron_network_publication_on_error( + aeron_network_publication_t *publication, + const uint8_t *buffer, + size_t length, + struct sockaddr_storage *addr); + void aeron_network_publication_on_rttm( aeron_network_publication_t *publication, const uint8_t *buffer, size_t length, struct sockaddr_storage *addr); diff --git a/aeron-driver/src/main/c/aeron_publication_image.c b/aeron-driver/src/main/c/aeron_publication_image.c index 232de74d87..19af5e90fb 100644 --- a/aeron-driver/src/main/c/aeron_publication_image.c +++ b/aeron-driver/src/main/c/aeron_publication_image.c @@ -700,18 +700,33 @@ int aeron_publication_image_on_rttm( // Called from receiver. int aeron_publication_image_send_pending_status_message(aeron_publication_image_t *image, int64_t now_ns) { + int work_count = 0; + int64_t change_number; + AERON_GET_VOLATILE(change_number, image->end_sm_change); + const bool has_sm_timed_out = now_ns > (image->time_of_last_sm_ns + image->sm_timeout_ns); + // TODO: Send error frame instead. if (NULL != image->invalidation_reason) { + if (has_sm_timed_out) + { + for (size_t i = 0, len = image->connections.length; i < len; i++) + { + aeron_publication_image_connection_t *connection = &image->connections.array[i]; + aeron_receiver_channel_endpoint_send_error_frame( + image->endpoint, + connection->destination, + connection->control_addr, + image->session_id, + image->stream_id, + AERON_ERROR_CODE_GENERIC_ERROR, + image->invalidation_reason); + } + } return 0; } - int work_count = 0; - int64_t change_number; int32_t response_session_id = 0; - AERON_GET_VOLATILE(change_number, image->end_sm_change); - const bool has_sm_timed_out = now_ns > (image->time_of_last_sm_ns + image->sm_timeout_ns); - if (has_sm_timed_out && aeron_publication_image_check_and_get_response_session_id(image, &response_session_id)) { for (size_t i = 0, len = image->connections.length; i < len; i++) diff --git a/aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.c b/aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.c index f7c530b6b6..1e844c69f6 100644 --- a/aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.c +++ b/aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.c @@ -424,6 +424,47 @@ int aeron_receive_channel_endpoint_send_response_setup( return bytes_sent; } +int aeron_receiver_channel_endpoint_send_error_frame( + aeron_receive_channel_endpoint_t *channel_endpoint, + aeron_receive_destination_t *destination, + struct sockaddr_storage *control_addr, + int32_t session_id, + int32_t stream_id, + int32_t error_code, + const char *invalidation_reason) +{ + size_t frame_length = sizeof(aeron_error_t) + AERON_ERROR_MAX_MESSAGE_LENGTH; + uint8_t buffer[frame_length]; + aeron_error_t *error = (aeron_error_t *)buffer; + size_t error_length = strnlen(invalidation_reason, AERON_ERROR_MAX_MESSAGE_LENGTH); + struct iovec iov; + + error->frame_header.frame_length = (int32_t)frame_length; + error->frame_header.version = AERON_FRAME_HEADER_VERSION; + error->frame_header.flags = channel_endpoint->group_tag.is_present ? AERON_ERROR_HAS_GROUP_TAG_FLAG : UINT8_C(0); + error->frame_header.type = AERON_HDR_TYPE_ERR; + error->session_id = session_id; + error->stream_id = stream_id; + error->receiver_id = channel_endpoint->receiver_id; + error->group_tag = channel_endpoint->group_tag.value; + error->error_code = error_code; + error->error_length = (int32_t)error_length; + memcpy(&buffer[sizeof(aeron_error_t)], invalidation_reason, error_length); + + iov.iov_base = buffer; + iov.iov_len = frame_length; + int bytes_sent = aeron_receive_channel_endpoint_send(channel_endpoint, destination, control_addr, &iov); + if (bytes_sent != (int)iov.iov_len) + { + if (bytes_sent >= 0) + { + aeron_counter_increment(channel_endpoint->short_sends_counter, 1); + } + } + + return bytes_sent; +} + void aeron_receive_channel_endpoint_dispatch( aeron_udp_channel_data_paths_t *data_paths, aeron_udp_channel_transport_t *transport, diff --git a/aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.h b/aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.h index fc306a883e..f5bc54025c 100644 --- a/aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.h +++ b/aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.h @@ -147,6 +147,15 @@ int aeron_receive_channel_endpoint_send_response_setup( int32_t session_id, int32_t response_session_id); +int aeron_receiver_channel_endpoint_send_error_frame( + aeron_receive_channel_endpoint_t *channel_endpoint, + aeron_receive_destination_t *destination, + struct sockaddr_storage *control_addr, + int32_t session_id, + int32_t stream_id, + int32_t error_code, + const char *invalidation_reason); + void aeron_receive_channel_endpoint_dispatch( aeron_udp_channel_data_paths_t *data_paths, aeron_udp_channel_transport_t *transport, diff --git a/aeron-driver/src/main/c/media/aeron_send_channel_endpoint.c b/aeron-driver/src/main/c/media/aeron_send_channel_endpoint.c index c18acf24f7..39f82acede 100644 --- a/aeron-driver/src/main/c/media/aeron_send_channel_endpoint.c +++ b/aeron-driver/src/main/c/media/aeron_send_channel_endpoint.c @@ -414,6 +414,19 @@ void aeron_send_channel_endpoint_dispatch( } break; + case AERON_HDR_TYPE_ERR: + if (length >= sizeof(aeron_error_t) && length >= (size_t)frame_header->frame_length) + { + result = aeron_send_channel_endpoint_on_error(endpoint, conductor_proxy, buffer, length, addr); + // TODO: error messages counter. +// aeron_counter_ordered_increment(sender->error_messages_received_counter, 1); + } + else + { + aeron_counter_increment(sender->invalid_frames_counter, 1); + } + break; + case AERON_HDR_TYPE_RSP_SETUP: if (length >= sizeof(aeron_response_setup_header_t)) { @@ -512,6 +525,30 @@ int aeron_send_channel_endpoint_on_status_message( return result; } +int aeron_send_channel_endpoint_on_error( + aeron_send_channel_endpoint_t *endpoint, + aeron_driver_conductor_proxy_t *conductor_proxy, + uint8_t *buffer, + size_t length, + struct sockaddr_storage *addr) +{ + aeron_error_t *error = (aeron_error_t *)buffer; + + // TODO: handle multi-destination messages + + int64_t key_value = aeron_map_compound_key(error->stream_id, error->session_id); + aeron_network_publication_t *publication = aeron_int64_to_ptr_hash_map_get( + &endpoint->publication_dispatch_map, key_value); + int result = 0; + + if (NULL != publication) + { + aeron_network_publication_on_error(publication, buffer, length, addr); + } + + return result; +} + void aeron_send_channel_endpoint_on_rttm( aeron_send_channel_endpoint_t *endpoint, uint8_t *buffer, size_t length, struct sockaddr_storage *addr) { diff --git a/aeron-driver/src/main/c/media/aeron_send_channel_endpoint.h b/aeron-driver/src/main/c/media/aeron_send_channel_endpoint.h index f5272e6a7f..b83ea52d46 100644 --- a/aeron-driver/src/main/c/media/aeron_send_channel_endpoint.h +++ b/aeron-driver/src/main/c/media/aeron_send_channel_endpoint.h @@ -123,6 +123,13 @@ int aeron_send_channel_endpoint_on_status_message( size_t length, struct sockaddr_storage *addr); +int aeron_send_channel_endpoint_on_error( + aeron_send_channel_endpoint_t *endpoint, + aeron_driver_conductor_proxy_t *conductor_proxy, + uint8_t *buffer, + size_t length, + struct sockaddr_storage *addr); + void aeron_send_channel_endpoint_on_rttm( aeron_send_channel_endpoint_t *endpoint, uint8_t *buffer, size_t length, struct sockaddr_storage *addr); From 0c58d8e3197ae4ef0b67634b6c2112a9e2f95f06 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Wed, 12 Jun 2024 14:36:08 -1100 Subject: [PATCH 10/67] [C] Tighten stack allocation of buffer to work better with MSVC. Don't send max frame size each time. --- .../src/main/c/protocol/aeron_udp_protocol.h | 1 + .../main/c/media/aeron_receive_channel_endpoint.c | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/aeron-client/src/main/c/protocol/aeron_udp_protocol.h b/aeron-client/src/main/c/protocol/aeron_udp_protocol.h index b3bf2e3e2e..aa342ba3d0 100644 --- a/aeron-client/src/main/c/protocol/aeron_udp_protocol.h +++ b/aeron-client/src/main/c/protocol/aeron_udp_protocol.h @@ -212,6 +212,7 @@ int aeron_udp_protocol_group_tag(aeron_status_message_header_t *sm, int64_t *gro #define AERON_OPT_HDR_ALIGNMENT (4u) #define AERON_ERROR_MAX_MESSAGE_LENGTH (1023) +#define AERON_ERROR_MAX_FRAME_LENGTH (sizeof(aeron_error_t) + AERON_ERROR_MAX_MESSAGE_LENGTH) #define AERON_ERROR_HAS_GROUP_TAG_FLAG (0x08) inline size_t aeron_res_header_address_length(int8_t res_type) diff --git a/aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.c b/aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.c index 1e844c69f6..2ac5513f0d 100644 --- a/aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.c +++ b/aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.c @@ -433,12 +433,12 @@ int aeron_receiver_channel_endpoint_send_error_frame( int32_t error_code, const char *invalidation_reason) { - size_t frame_length = sizeof(aeron_error_t) + AERON_ERROR_MAX_MESSAGE_LENGTH; - uint8_t buffer[frame_length]; + uint8_t buffer[AERON_ERROR_MAX_FRAME_LENGTH]; aeron_error_t *error = (aeron_error_t *)buffer; - size_t error_length = strnlen(invalidation_reason, AERON_ERROR_MAX_MESSAGE_LENGTH); struct iovec iov; + const size_t error_message_length = strnlen(invalidation_reason, AERON_ERROR_MAX_MESSAGE_LENGTH); + const size_t frame_length = sizeof(aeron_error_t) + error_message_length; error->frame_header.frame_length = (int32_t)frame_length; error->frame_header.version = AERON_FRAME_HEADER_VERSION; error->frame_header.flags = channel_endpoint->group_tag.is_present ? AERON_ERROR_HAS_GROUP_TAG_FLAG : UINT8_C(0); @@ -448,11 +448,11 @@ int aeron_receiver_channel_endpoint_send_error_frame( error->receiver_id = channel_endpoint->receiver_id; error->group_tag = channel_endpoint->group_tag.value; error->error_code = error_code; - error->error_length = (int32_t)error_length; - memcpy(&buffer[sizeof(aeron_error_t)], invalidation_reason, error_length); + error->error_length = (int32_t)error_message_length; + memcpy(&buffer[sizeof(aeron_error_t)], invalidation_reason, error_message_length); iov.iov_base = buffer; - iov.iov_len = frame_length; + iov.iov_len = (unsigned long)frame_length; int bytes_sent = aeron_receive_channel_endpoint_send(channel_endpoint, destination, control_addr, &iov); if (bytes_sent != (int)iov.iov_len) { From 1eb13e63904676f15bd8a1ec31cbe79d1dbe9fe4 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Thu, 13 Jun 2024 14:56:39 +1200 Subject: [PATCH 11/67] [Java] New system counter to track number of error messages received. Check that we don't spam out error messages too quickly. Reset timestamp of last SM sent by the publication image when sending errors. --- .../java/io/aeron/driver/PublicationImage.java | 2 ++ .../aeron/driver/media/SendChannelEndpoint.java | 6 ++++-- .../driver/status/SystemCounterDescriptor.java | 7 ++++++- .../java/io/aeron/ImageInvalidationTest.java | 17 ++++++++++++++++- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java b/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java index df09b71074..a4511cbe19 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java +++ b/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java @@ -713,6 +713,8 @@ int sendPendingStatusMessage(final long nowNs) { channelEndpoint.sendErrorFrame( imageConnections, sessionId, streamId, GENERIC_ERROR.value(), invalidationReason); + + timeOfLastSmNs = nowNs; workCount++; } diff --git a/aeron-driver/src/main/java/io/aeron/driver/media/SendChannelEndpoint.java b/aeron-driver/src/main/java/io/aeron/driver/media/SendChannelEndpoint.java index b91b9a6427..d08159475d 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/media/SendChannelEndpoint.java +++ b/aeron-driver/src/main/java/io/aeron/driver/media/SendChannelEndpoint.java @@ -53,6 +53,7 @@ import static io.aeron.driver.media.SendChannelEndpoint.DESTINATION_TIMEOUT; import static io.aeron.driver.media.UdpChannelTransport.sendError; +import static io.aeron.driver.status.SystemCounterDescriptor.ERROR_MESSAGES_RECEIVED; import static io.aeron.driver.status.SystemCounterDescriptor.NAK_MESSAGES_RECEIVED; import static io.aeron.driver.status.SystemCounterDescriptor.STATUS_MESSAGES_RECEIVED; import static io.aeron.protocol.StatusMessageFlyweight.SEND_SETUP_FLAG; @@ -77,6 +78,7 @@ public class SendChannelEndpoint extends UdpChannelTransport private final AtomicCounter statusMessagesReceived; private final AtomicCounter nakMessagesReceived; private final AtomicCounter statusIndicator; + private final AtomicCounter errorMessagesReceived; private final boolean isChannelSendTimestampEnabled; private final EpochNanoClock sendTimestampClock; private final UnsafeBuffer bufferForTimestamping = new UnsafeBuffer(); @@ -103,6 +105,7 @@ public SendChannelEndpoint( nakMessagesReceived = context.systemCounters().get(NAK_MESSAGES_RECEIVED); statusMessagesReceived = context.systemCounters().get(STATUS_MESSAGES_RECEIVED); + errorMessagesReceived = context.systemCounters().get(ERROR_MESSAGES_RECEIVED); this.statusIndicator = statusIndicator; MultiSndDestination multiSndDestination = null; @@ -421,8 +424,7 @@ public void onError( final int sessionId = msg.sessionId(); final int streamId = msg.streamId(); - // TODO: Error message counter. - statusMessagesReceived.incrementOrdered(); + errorMessagesReceived.incrementOrdered(); if (null != multiSndDestination) { diff --git a/aeron-driver/src/main/java/io/aeron/driver/status/SystemCounterDescriptor.java b/aeron-driver/src/main/java/io/aeron/driver/status/SystemCounterDescriptor.java index e119f34623..9241f7e1aa 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/status/SystemCounterDescriptor.java +++ b/aeron-driver/src/main/java/io/aeron/driver/status/SystemCounterDescriptor.java @@ -221,7 +221,12 @@ public enum SystemCounterDescriptor /** * A count of the number of times that the retransmit pool has been overflowed. */ - RETRANSMIT_OVERFLOW(37, "Retransmit Pool Overflow count"); + RETRANSMIT_OVERFLOW(37, "Retransmit Pool Overflow count"), + + /** + * A count of the number of error messages received from a remote archive. + */ + ERROR_MESSAGES_RECEIVED(38, "Error Messages received"); /** * All system counters have the same type id, i.e. system counters are the same type. Other types can exist. diff --git a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java index 343baa239d..22be6fad69 100644 --- a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java +++ b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java @@ -17,6 +17,7 @@ import io.aeron.driver.MediaDriver; import io.aeron.driver.ThreadingMode; +import io.aeron.driver.status.SystemCounterDescriptor; import io.aeron.test.EventLogExtension; import io.aeron.test.InterruptAfter; import io.aeron.test.InterruptingTestCallback; @@ -43,6 +44,7 @@ @ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class }) public class ImageInvalidationTest { + public static final long A_VALUE_THAT_SHOWS_WE_ARENT_SPAMMING_ERROR_MESSAGES = 1000L; @RegisterExtension final SystemTestWatcher systemTestWatcher = new SystemTestWatcher(); @@ -68,7 +70,7 @@ private TestMediaDriver launch() } @Test - @InterruptAfter(20) + @InterruptAfter(10) @SlowTest void shouldInvalidateSubscriptionsImage() { @@ -88,6 +90,9 @@ void shouldInvalidateSubscriptionsImage() Tests.awaitConnected(pub); Tests.awaitConnected(sub); + final long initialErrorMessagesReceived = aeron.countersReader() + .getCounterValue(SystemCounterDescriptor.ERROR_MESSAGES_RECEIVED.id()); + while (pub.offer(message) < 0) { Tests.yield(); @@ -113,10 +118,20 @@ void shouldInvalidateSubscriptionsImage() final long value = driver.context().publicationConnectionTimeoutNs(); assertThat(t1 - t0, lessThan(value)); + while (initialErrorMessagesReceived == aeron.countersReader() + .getCounterValue(SystemCounterDescriptor.ERROR_MESSAGES_RECEIVED.id())) + { + Tests.yield(); + } + while (!imageUnavailable.get()) { Tests.yield(); } + + assertThat( + aeron.countersReader().getCounterValue(SystemCounterDescriptor.ERROR_MESSAGES_RECEIVED.id()), + lessThan(A_VALUE_THAT_SHOWS_WE_ARENT_SPAMMING_ERROR_MESSAGES)); } } } From 74c5e7e1211e84f559f98b82ef82ee50f3c8c762 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Thu, 13 Jun 2024 14:59:09 +1200 Subject: [PATCH 12/67] [C] New system counter to track number of error messages received. Check that we don't spam out error messages too quickly. Reset timestamp of last SM sent by the publication image when sending errors. --- aeron-driver/src/main/c/aeron_driver_sender.c | 2 ++ aeron-driver/src/main/c/aeron_driver_sender.h | 1 + aeron-driver/src/main/c/aeron_publication_image.c | 3 +++ aeron-driver/src/main/c/aeron_system_counters.c | 1 + aeron-driver/src/main/c/aeron_system_counters.h | 1 + aeron-driver/src/main/c/media/aeron_send_channel_endpoint.c | 3 +-- 6 files changed, 9 insertions(+), 2 deletions(-) diff --git a/aeron-driver/src/main/c/aeron_driver_sender.c b/aeron-driver/src/main/c/aeron_driver_sender.c index dd64e321c2..8d8f52a932 100644 --- a/aeron-driver/src/main/c/aeron_driver_sender.c +++ b/aeron-driver/src/main/c/aeron_driver_sender.c @@ -104,6 +104,8 @@ int aeron_driver_sender_init( system_counters, AERON_SYSTEM_COUNTER_STATUS_MESSAGES_RECEIVED); sender->nak_messages_received_counter = aeron_system_counter_addr( system_counters, AERON_SYSTEM_COUNTER_NAK_MESSAGES_RECEIVED); + sender->error_messages_received_counter = aeron_system_counter_addr( + system_counters, AERON_SYSTEM_COUNTER_ERROR_MESSAGES_RECEIVED); sender->resolution_changes_counter = aeron_system_counter_addr( system_counters, AERON_SYSTEM_COUNTER_RESOLUTION_CHANGES); sender->short_sends_counter = aeron_system_counter_addr(system_counters, AERON_SYSTEM_COUNTER_SHORT_SENDS); diff --git a/aeron-driver/src/main/c/aeron_driver_sender.h b/aeron-driver/src/main/c/aeron_driver_sender.h index 5c264ccbf0..c0c7a7e800 100644 --- a/aeron-driver/src/main/c/aeron_driver_sender.h +++ b/aeron-driver/src/main/c/aeron_driver_sender.h @@ -59,6 +59,7 @@ typedef struct aeron_driver_sender_stct volatile int64_t *invalid_frames_counter; volatile int64_t *status_messages_received_counter; volatile int64_t *nak_messages_received_counter; + volatile int64_t *error_messages_received_counter; volatile int64_t *resolution_changes_counter; volatile int64_t *short_sends_counter; diff --git a/aeron-driver/src/main/c/aeron_publication_image.c b/aeron-driver/src/main/c/aeron_publication_image.c index 19af5e90fb..2071b61565 100644 --- a/aeron-driver/src/main/c/aeron_publication_image.c +++ b/aeron-driver/src/main/c/aeron_publication_image.c @@ -722,7 +722,10 @@ int aeron_publication_image_send_pending_status_message(aeron_publication_image_ AERON_ERROR_CODE_GENERIC_ERROR, image->invalidation_reason); } + + image->time_of_last_sm_ns = now_ns; } + return 0; } diff --git a/aeron-driver/src/main/c/aeron_system_counters.c b/aeron-driver/src/main/c/aeron_system_counters.c index 75030b41d1..36e88b81ed 100644 --- a/aeron-driver/src/main/c/aeron_system_counters.c +++ b/aeron-driver/src/main/c/aeron_system_counters.c @@ -61,6 +61,7 @@ static aeron_system_counter_t system_counters[] = { "Bytes currently mapped", AERON_SYSTEM_COUNTER_BYTES_CURRENTLY_MAPPED }, { "Retransmitted bytes", AERON_SYSTEM_COUNTER_RETRANSMITTED_BYTES }, { "Retransmit Pool Overflow count", AERON_SYSTEM_COUNTER_RETRANSMIT_OVERFLOW } + { "Error Messages received", AERON_SYSTEM_COUNTER_ERROR_MESSAGES_RECEIVED }, }; static size_t num_system_counters = sizeof(system_counters) / sizeof(aeron_system_counter_t); diff --git a/aeron-driver/src/main/c/aeron_system_counters.h b/aeron-driver/src/main/c/aeron_system_counters.h index 2824447bdf..b9a038a641 100644 --- a/aeron-driver/src/main/c/aeron_system_counters.h +++ b/aeron-driver/src/main/c/aeron_system_counters.h @@ -59,6 +59,7 @@ typedef enum aeron_system_counter_enum_stct AERON_SYSTEM_COUNTER_BYTES_CURRENTLY_MAPPED = 35, AERON_SYSTEM_COUNTER_RETRANSMITTED_BYTES = 36, AERON_SYSTEM_COUNTER_RETRANSMIT_OVERFLOW = 37, + AERON_SYSTEM_COUNTER_ERROR_MESSAGES_RECEIVED = 38, // Add all new counters before this one (used for a static assertion). AERON_SYSTEM_COUNTER_DUMMY_LAST, diff --git a/aeron-driver/src/main/c/media/aeron_send_channel_endpoint.c b/aeron-driver/src/main/c/media/aeron_send_channel_endpoint.c index 39f82acede..e03cca07bb 100644 --- a/aeron-driver/src/main/c/media/aeron_send_channel_endpoint.c +++ b/aeron-driver/src/main/c/media/aeron_send_channel_endpoint.c @@ -418,8 +418,7 @@ void aeron_send_channel_endpoint_dispatch( if (length >= sizeof(aeron_error_t) && length >= (size_t)frame_header->frame_length) { result = aeron_send_channel_endpoint_on_error(endpoint, conductor_proxy, buffer, length, addr); - // TODO: error messages counter. -// aeron_counter_ordered_increment(sender->error_messages_received_counter, 1); + aeron_counter_ordered_increment(sender->error_messages_received_counter, 1); } else { From 805924ac1f683ab6b5c6bd41dec32f281f14c4c1 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Fri, 14 Jun 2024 13:26:21 +1200 Subject: [PATCH 13/67] [Java] Add additional tests for EOS and errors for MDC publications. --- .../java/io/aeron/ImageInvalidationTest.java | 82 +++++++++++++++++++ .../io/aeron/SubscriberEndOfStreamTest.java | 58 +++++++++++++ 2 files changed, 140 insertions(+) diff --git a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java index 22be6fad69..ed2101305f 100644 --- a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java +++ b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java @@ -35,6 +35,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import static java.nio.charset.StandardCharsets.US_ASCII; import static org.hamcrest.MatcherAssert.assertThat; @@ -134,4 +135,85 @@ void shouldInvalidateSubscriptionsImage() lessThan(A_VALUE_THAT_SHOWS_WE_ARENT_SPAMMING_ERROR_MESSAGES)); } } + + @Test + @InterruptAfter(10) + @SlowTest + void shouldInvalidateSubscriptionsImageManualMdc() + { + context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3)); + + final TestMediaDriver driver = launch(); + + final Aeron.Context ctx = new Aeron.Context() + .aeronDirectoryName(driver.aeronDirectoryName()); + + final AtomicInteger imageAvailable = new AtomicInteger(0); + final AtomicInteger imageUnavailable = new AtomicInteger(0); + final String mdc = "aeron:udp?control-mode=manual"; + final String channel = "aeron:udp?endpoint=localhost:10000"; + + try (Aeron aeron = Aeron.connect(ctx); + Publication pub = aeron.addPublication(mdc, streamId); + Subscription sub = aeron.addSubscription( + channel, + streamId, + (image) -> imageAvailable.incrementAndGet(), + (image) -> imageUnavailable.incrementAndGet())) + { + pub.addDestination(channel); + + Tests.awaitConnected(pub); + Tests.awaitConnected(sub); + + final long initialErrorMessagesReceived = aeron.countersReader() + .getCounterValue(SystemCounterDescriptor.ERROR_MESSAGES_RECEIVED.id()); + + while (pub.offer(message) < 0) + { + Tests.yield(); + } + + while (0 == sub.poll((buffer, offset, length, header) -> {}, 1)) + { + Tests.yield(); + } + + final Image image = sub.imageAtIndex(0); + assertEquals(pub.position(), image.position()); + + final int initialAvailable = imageAvailable.get(); + final String reason = "Needs to be closed"; + image.invalidate(reason); + + final long t0 = System.nanoTime(); + while (pub.isConnected()) + { + Tests.yield(); + } + final long t1 = System.nanoTime(); + final long value = driver.context().publicationConnectionTimeoutNs(); + assertThat(t1 - t0, lessThan(value)); + + while (initialErrorMessagesReceived == aeron.countersReader() + .getCounterValue(SystemCounterDescriptor.ERROR_MESSAGES_RECEIVED.id())) + { + Tests.yield(); + } + + while (0 == imageUnavailable.get()) + { + Tests.yield(); + } + + assertThat( + aeron.countersReader().getCounterValue(SystemCounterDescriptor.ERROR_MESSAGES_RECEIVED.id()), + lessThan(A_VALUE_THAT_SHOWS_WE_ARENT_SPAMMING_ERROR_MESSAGES)); + + while (initialAvailable != imageAvailable.get()) + { + Tests.yield(); + } + } + } } diff --git a/aeron-system-tests/src/test/java/io/aeron/SubscriberEndOfStreamTest.java b/aeron-system-tests/src/test/java/io/aeron/SubscriberEndOfStreamTest.java index cf3e107ff1..1a33e1e7e0 100644 --- a/aeron-system-tests/src/test/java/io/aeron/SubscriberEndOfStreamTest.java +++ b/aeron-system-tests/src/test/java/io/aeron/SubscriberEndOfStreamTest.java @@ -308,4 +308,62 @@ void shouldNotLingerUnicastPublicationWhenReceivingEndOfStream() assertThat((t1 - t0), lessThan(lingerTimeoutMs)); } } + + @ParameterizedTest + @ValueSource(strings = { + "aeron:udp?control-mode=manual|fc=min", + "aeron:udp?control-mode=manual|fc=max", + }) + @InterruptAfter(20) + @SlowTest + void shouldDisconnectPublicationWithEosIfSubscriptionIsClosedMdcManual(final String publicationChannel) + { + final TestMediaDriver driver = launch(); + final int streamId = 10000; + final String subscriptionChannel = "aeron:udp?endpoint=localhost:10000"; + + final Aeron.Context ctx = new Aeron.Context() + .aeronDirectoryName(driver.aeronDirectoryName()); + + final AtomicBoolean imageUnavailable = new AtomicBoolean(false); + + try (Aeron aeron = Aeron.connect(ctx); + Publication pub = aeron.addPublication(publicationChannel, streamId)) + { + pub.addDestination(subscriptionChannel); + final Subscription sub = aeron.addSubscription( + subscriptionChannel, streamId, image -> {}, image -> imageUnavailable.set(true)); + + Tests.awaitConnected(pub); + Tests.awaitConnected(sub); + + while (pub.offer(message) < 0) + { + Tests.yield(); + } + + while (0 == sub.poll((buffer, offset, length, header) -> {}, 1)) + { + Tests.yield(); + } + + final Image image = sub.imageAtIndex(0); + assertEquals(pub.position(), image.position()); + + CloseHelper.close(sub); + + final long t0 = System.nanoTime(); + while (pub.isConnected()) + { + Tests.yield(); + } + final long t1 = System.nanoTime(); + assertThat(t1 - t0, lessThan(driver.context().publicationConnectionTimeoutNs())); + + while (!imageUnavailable.get()) + { + Tests.yield(); + } + } + } } From 87ed20648bfef22c4759b4022359a91fc9047a95 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Fri, 14 Jun 2024 16:12:23 +1200 Subject: [PATCH 14/67] [Java] Limit the length of the invalidation reason. --- .../java/io/aeron/driver/DriverConductor.java | 7 +++++ .../java/io/aeron/ImageInvalidationTest.java | 31 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java b/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java index 05590b6043..dfe128dfce 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java +++ b/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java @@ -33,6 +33,7 @@ import io.aeron.logbuffer.LogBufferDescriptor; import io.aeron.protocol.DataHeaderFlyweight; import io.aeron.protocol.SetupFlyweight; +import io.aeron.protocol.ErrorFlyweight; import io.aeron.status.ChannelEndpointStatus; import org.agrona.BitUtil; import org.agrona.CloseHelper; @@ -56,6 +57,7 @@ import org.agrona.concurrent.status.UnsafeBufferPosition; import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Objects; @@ -1477,6 +1479,11 @@ void onInvalidateImage( final long position, final String reason) { + if (ErrorFlyweight.MAX_ERROR_MESSAGE_LENGTH < reason.getBytes(StandardCharsets.UTF_8).length) + { + throw new ControlProtocolException(GENERIC_ERROR, "Invalidation reason must be 1023 bytes or less"); + } + final PublicationImage publicationImage = findPublicationImage(imageCorrelationId); if (null == publicationImage) diff --git a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java index ed2101305f..b4bb89a565 100644 --- a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java +++ b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java @@ -18,6 +18,7 @@ import io.aeron.driver.MediaDriver; import io.aeron.driver.ThreadingMode; import io.aeron.driver.status.SystemCounterDescriptor; +import io.aeron.exceptions.AeronException; import io.aeron.test.EventLogExtension; import io.aeron.test.InterruptAfter; import io.aeron.test.InterruptingTestCallback; @@ -33,6 +34,8 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.RegisterExtension; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -41,6 +44,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.lessThan; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; @ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class }) public class ImageInvalidationTest @@ -216,4 +220,31 @@ void shouldInvalidateSubscriptionsImageManualMdc() } } } + + @Test + @InterruptAfter(10) + void shouldRejectInvalidationReasonThatIsTooLong() + { + TestMediaDriver.notSupportedOnCMediaDriver("Not implemented yet"); + + context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3)); + final byte[] bytes = new byte[1024]; + Arrays.fill(bytes, (byte)'x'); + final String tooLongReason = new String(bytes, US_ASCII); + + final TestMediaDriver driver = launch(); + + final Aeron.Context ctx = new Aeron.Context() + .aeronDirectoryName(driver.aeronDirectoryName()); + + try (Aeron aeron = Aeron.connect(ctx); + Publication pub = aeron.addPublication(channel, streamId); + Subscription sub = aeron.addSubscription(channel, streamId)) + { + Tests.awaitConnected(pub); + Tests.awaitConnected(sub); + + assertThrows(AeronException.class, () -> sub.imageAtIndex(0).invalidate(tooLongReason)); + } + } } From cfb36cf37a2a6340db071ce1d8cd1e3694ce73d8 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Fri, 14 Jun 2024 16:48:57 +1200 Subject: [PATCH 15/67] [C] Limit reason length for image invalidation. --- aeron-driver/src/main/c/aeron_driver_conductor.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/aeron-driver/src/main/c/aeron_driver_conductor.c b/aeron-driver/src/main/c/aeron_driver_conductor.c index 9623f0c91f..bcd7398662 100644 --- a/aeron-driver/src/main/c/aeron_driver_conductor.c +++ b/aeron-driver/src/main/c/aeron_driver_conductor.c @@ -3336,6 +3336,7 @@ aeron_rb_read_action_t aeron_driver_conductor_on_command( case AERON_COMMAND_INVALIDATE_IMAGE: { aeron_invalidate_image_command_t *command = (aeron_invalidate_image_command_t *)message; + correlation_id = command->correlated.correlation_id; if (length < sizeof (aeron_invalidate_image_command_t)) { @@ -5676,6 +5677,12 @@ int aeron_driver_conductor_on_invalidate_image( aeron_publication_image_t *image = aeron_driver_conductor_find_publication_image( conductor, image_correlation_id); + if (AERON_ERROR_MAX_MESSAGE_LENGTH < command->reason_length) + { + AERON_SET_ERR(AERON_ERROR_CODE_GENERIC_ERROR, "%s", "Invalidation reason must be 1023 bytes or less"); + return -1; + } + if (NULL == image) { AERON_SET_ERR( From 0d3a23755aeeb3fea51492d8dd04b47f730982cd Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Mon, 17 Jun 2024 06:49:13 +1200 Subject: [PATCH 16/67] [Java] Allow test to run on C media driver. --- .../src/test/java/io/aeron/ImageInvalidationTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java index b4bb89a565..bb585b1434 100644 --- a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java +++ b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java @@ -225,8 +225,6 @@ void shouldInvalidateSubscriptionsImageManualMdc() @InterruptAfter(10) void shouldRejectInvalidationReasonThatIsTooLong() { - TestMediaDriver.notSupportedOnCMediaDriver("Not implemented yet"); - context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3)); final byte[] bytes = new byte[1024]; Arrays.fill(bytes, (byte)'x'); From 8a40f02cc9bf0d43ccbe99771764dc6cae7a5c62 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Mon, 17 Jun 2024 10:43:26 +1200 Subject: [PATCH 17/67] [C/Java] Rename Error Messages to Error Frames. --- aeron-driver/src/main/c/aeron_driver_sender.c | 2 +- aeron-driver/src/main/c/aeron_system_counters.c | 2 +- aeron-driver/src/main/c/aeron_system_counters.h | 2 +- .../io/aeron/driver/media/SendChannelEndpoint.java | 4 ++-- .../driver/status/SystemCounterDescriptor.java | 2 +- .../test/java/io/aeron/ImageInvalidationTest.java | 13 ++++++------- 6 files changed, 12 insertions(+), 13 deletions(-) diff --git a/aeron-driver/src/main/c/aeron_driver_sender.c b/aeron-driver/src/main/c/aeron_driver_sender.c index 8d8f52a932..84a5088b20 100644 --- a/aeron-driver/src/main/c/aeron_driver_sender.c +++ b/aeron-driver/src/main/c/aeron_driver_sender.c @@ -105,7 +105,7 @@ int aeron_driver_sender_init( sender->nak_messages_received_counter = aeron_system_counter_addr( system_counters, AERON_SYSTEM_COUNTER_NAK_MESSAGES_RECEIVED); sender->error_messages_received_counter = aeron_system_counter_addr( - system_counters, AERON_SYSTEM_COUNTER_ERROR_MESSAGES_RECEIVED); + system_counters, AERON_SYSTEM_COUNTER_ERROR_FRAMES_RECEIVED); sender->resolution_changes_counter = aeron_system_counter_addr( system_counters, AERON_SYSTEM_COUNTER_RESOLUTION_CHANGES); sender->short_sends_counter = aeron_system_counter_addr(system_counters, AERON_SYSTEM_COUNTER_SHORT_SENDS); diff --git a/aeron-driver/src/main/c/aeron_system_counters.c b/aeron-driver/src/main/c/aeron_system_counters.c index 36e88b81ed..aae88db4a2 100644 --- a/aeron-driver/src/main/c/aeron_system_counters.c +++ b/aeron-driver/src/main/c/aeron_system_counters.c @@ -61,7 +61,7 @@ static aeron_system_counter_t system_counters[] = { "Bytes currently mapped", AERON_SYSTEM_COUNTER_BYTES_CURRENTLY_MAPPED }, { "Retransmitted bytes", AERON_SYSTEM_COUNTER_RETRANSMITTED_BYTES }, { "Retransmit Pool Overflow count", AERON_SYSTEM_COUNTER_RETRANSMIT_OVERFLOW } - { "Error Messages received", AERON_SYSTEM_COUNTER_ERROR_MESSAGES_RECEIVED }, + { "Error Frames received", AERON_SYSTEM_COUNTER_ERROR_FRAMES_RECEIVED }, }; static size_t num_system_counters = sizeof(system_counters) / sizeof(aeron_system_counter_t); diff --git a/aeron-driver/src/main/c/aeron_system_counters.h b/aeron-driver/src/main/c/aeron_system_counters.h index b9a038a641..27066b5877 100644 --- a/aeron-driver/src/main/c/aeron_system_counters.h +++ b/aeron-driver/src/main/c/aeron_system_counters.h @@ -59,7 +59,7 @@ typedef enum aeron_system_counter_enum_stct AERON_SYSTEM_COUNTER_BYTES_CURRENTLY_MAPPED = 35, AERON_SYSTEM_COUNTER_RETRANSMITTED_BYTES = 36, AERON_SYSTEM_COUNTER_RETRANSMIT_OVERFLOW = 37, - AERON_SYSTEM_COUNTER_ERROR_MESSAGES_RECEIVED = 38, + AERON_SYSTEM_COUNTER_ERROR_FRAMES_RECEIVED = 38, // Add all new counters before this one (used for a static assertion). AERON_SYSTEM_COUNTER_DUMMY_LAST, diff --git a/aeron-driver/src/main/java/io/aeron/driver/media/SendChannelEndpoint.java b/aeron-driver/src/main/java/io/aeron/driver/media/SendChannelEndpoint.java index d08159475d..295315b54a 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/media/SendChannelEndpoint.java +++ b/aeron-driver/src/main/java/io/aeron/driver/media/SendChannelEndpoint.java @@ -53,7 +53,7 @@ import static io.aeron.driver.media.SendChannelEndpoint.DESTINATION_TIMEOUT; import static io.aeron.driver.media.UdpChannelTransport.sendError; -import static io.aeron.driver.status.SystemCounterDescriptor.ERROR_MESSAGES_RECEIVED; +import static io.aeron.driver.status.SystemCounterDescriptor.ERROR_FRAMES_RECEIVED; import static io.aeron.driver.status.SystemCounterDescriptor.NAK_MESSAGES_RECEIVED; import static io.aeron.driver.status.SystemCounterDescriptor.STATUS_MESSAGES_RECEIVED; import static io.aeron.protocol.StatusMessageFlyweight.SEND_SETUP_FLAG; @@ -105,7 +105,7 @@ public SendChannelEndpoint( nakMessagesReceived = context.systemCounters().get(NAK_MESSAGES_RECEIVED); statusMessagesReceived = context.systemCounters().get(STATUS_MESSAGES_RECEIVED); - errorMessagesReceived = context.systemCounters().get(ERROR_MESSAGES_RECEIVED); + errorMessagesReceived = context.systemCounters().get(ERROR_FRAMES_RECEIVED); this.statusIndicator = statusIndicator; MultiSndDestination multiSndDestination = null; diff --git a/aeron-driver/src/main/java/io/aeron/driver/status/SystemCounterDescriptor.java b/aeron-driver/src/main/java/io/aeron/driver/status/SystemCounterDescriptor.java index 9241f7e1aa..13dc53b6bc 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/status/SystemCounterDescriptor.java +++ b/aeron-driver/src/main/java/io/aeron/driver/status/SystemCounterDescriptor.java @@ -226,7 +226,7 @@ public enum SystemCounterDescriptor /** * A count of the number of error messages received from a remote archive. */ - ERROR_MESSAGES_RECEIVED(38, "Error Messages received"); + ERROR_FRAMES_RECEIVED(38, "Error Frames received"); /** * All system counters have the same type id, i.e. system counters are the same type. Other types can exist. diff --git a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java index bb585b1434..201cc98240 100644 --- a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java +++ b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java @@ -34,7 +34,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.RegisterExtension; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -96,7 +95,7 @@ void shouldInvalidateSubscriptionsImage() Tests.awaitConnected(sub); final long initialErrorMessagesReceived = aeron.countersReader() - .getCounterValue(SystemCounterDescriptor.ERROR_MESSAGES_RECEIVED.id()); + .getCounterValue(SystemCounterDescriptor.ERROR_FRAMES_RECEIVED.id()); while (pub.offer(message) < 0) { @@ -124,7 +123,7 @@ void shouldInvalidateSubscriptionsImage() assertThat(t1 - t0, lessThan(value)); while (initialErrorMessagesReceived == aeron.countersReader() - .getCounterValue(SystemCounterDescriptor.ERROR_MESSAGES_RECEIVED.id())) + .getCounterValue(SystemCounterDescriptor.ERROR_FRAMES_RECEIVED.id())) { Tests.yield(); } @@ -135,7 +134,7 @@ void shouldInvalidateSubscriptionsImage() } assertThat( - aeron.countersReader().getCounterValue(SystemCounterDescriptor.ERROR_MESSAGES_RECEIVED.id()), + aeron.countersReader().getCounterValue(SystemCounterDescriptor.ERROR_FRAMES_RECEIVED.id()), lessThan(A_VALUE_THAT_SHOWS_WE_ARENT_SPAMMING_ERROR_MESSAGES)); } } @@ -171,7 +170,7 @@ void shouldInvalidateSubscriptionsImageManualMdc() Tests.awaitConnected(sub); final long initialErrorMessagesReceived = aeron.countersReader() - .getCounterValue(SystemCounterDescriptor.ERROR_MESSAGES_RECEIVED.id()); + .getCounterValue(SystemCounterDescriptor.ERROR_FRAMES_RECEIVED.id()); while (pub.offer(message) < 0) { @@ -200,7 +199,7 @@ void shouldInvalidateSubscriptionsImageManualMdc() assertThat(t1 - t0, lessThan(value)); while (initialErrorMessagesReceived == aeron.countersReader() - .getCounterValue(SystemCounterDescriptor.ERROR_MESSAGES_RECEIVED.id())) + .getCounterValue(SystemCounterDescriptor.ERROR_FRAMES_RECEIVED.id())) { Tests.yield(); } @@ -211,7 +210,7 @@ void shouldInvalidateSubscriptionsImageManualMdc() } assertThat( - aeron.countersReader().getCounterValue(SystemCounterDescriptor.ERROR_MESSAGES_RECEIVED.id()), + aeron.countersReader().getCounterValue(SystemCounterDescriptor.ERROR_FRAMES_RECEIVED.id()), lessThan(A_VALUE_THAT_SHOWS_WE_ARENT_SPAMMING_ERROR_MESSAGES)); while (initialAvailable != imageAvailable.get()) From 9f636640b5496a3cc8ee2c82ec2d274b45753041 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Mon, 17 Jun 2024 12:33:20 +1200 Subject: [PATCH 18/67] [Java] Log errors when error frames are received. --- .../java/io/aeron/driver/DriverConductor.java | 24 +++++++++----- .../io/aeron/driver/DriverConductorProxy.java | 8 +++++ .../io/aeron/driver/NetworkPublication.java | 5 ++- .../aeron/driver/ReceiverLivenessTracker.java | 4 +-- .../driver/ReceiverLivenessTrackerTest.java | 26 ++++++++++++++- .../java/io/aeron/ImageInvalidationTest.java | 33 ++++++++++++++----- 6 files changed, 79 insertions(+), 21 deletions(-) diff --git a/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java b/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java index dfe128dfce..c128dd4eae 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java +++ b/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java @@ -29,6 +29,7 @@ import io.aeron.driver.media.UdpChannel; import io.aeron.driver.status.*; import io.aeron.exceptions.AeronEvent; +import io.aeron.exceptions.AeronException; import io.aeron.exceptions.ControlProtocolException; import io.aeron.logbuffer.LogBufferDescriptor; import io.aeron.protocol.DataHeaderFlyweight; @@ -386,6 +387,11 @@ void onChannelEndpointError(final long statusIndicatorId, final Exception ex) clientProxy.onError(statusIndicatorId, CHANNEL_ENDPOINT_ERROR, errorMessage); } + void onPublicationError(final long registrationId, final int errorCode, final String errorMessage) + { + recordError(new AeronException(errorMessage, AeronException.Category.WARN)); + } + void onReResolveEndpoint( final String endpoint, final SendChannelEndpoint channelEndpoint, final InetSocketAddress address) { @@ -398,8 +404,7 @@ void onReResolveEndpoint( final InetSocketAddress newAddress = asyncResult.get(); if (newAddress.isUnresolved()) { - ctx.errorHandler().onError(new AeronEvent("could not re-resolve: endpoint=" + endpoint)); - errorCounter.increment(); + recordError(new AeronEvent("could not re-resolve: endpoint=" + endpoint)); } else if (!address.equals(newAddress)) { @@ -408,8 +413,7 @@ else if (!address.equals(newAddress)) } catch (final Exception ex) { - ctx.errorHandler().onError(ex); - errorCounter.increment(); + recordError(ex); } }); } @@ -429,8 +433,7 @@ void onReResolveControl( final InetSocketAddress newAddress = asyncResult.get(); if (newAddress.isUnresolved()) { - ctx.errorHandler().onError(new AeronEvent("could not re-resolve: control=" + control)); - errorCounter.increment(); + recordError(new AeronEvent("could not re-resolve: control=" + control)); } else if (!address.equals(newAddress)) { @@ -439,8 +442,7 @@ else if (!address.equals(newAddress)) } catch (final Exception ex) { - ctx.errorHandler().onError(ex); - errorCounter.increment(); + recordError(ex); } }); } @@ -2689,4 +2691,10 @@ static AsyncResult of(final Supplier supplier) } } } + + private void recordError(final Exception ex) + { + ctx.errorHandler().onError(ex); + errorCounter.increment(); + } } diff --git a/aeron-driver/src/main/java/io/aeron/driver/DriverConductorProxy.java b/aeron-driver/src/main/java/io/aeron/driver/DriverConductorProxy.java index 0bde8f7076..0a717deafa 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/DriverConductorProxy.java +++ b/aeron-driver/src/main/java/io/aeron/driver/DriverConductorProxy.java @@ -247,6 +247,14 @@ void createPublicationImage( } } + void onPublicationError(final long registrationId, final int errorCode, final String errorMessage) + { + if (notConcurrent()) + { + driverConductor.onPublicationError(registrationId, errorCode, errorMessage); + } + } + private void offer(final Runnable cmd) { if (!commandQueue.offer(cmd)) diff --git a/aeron-driver/src/main/java/io/aeron/driver/NetworkPublication.java b/aeron-driver/src/main/java/io/aeron/driver/NetworkPublication.java index 91270f37a3..adf6c30146 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/NetworkPublication.java +++ b/aeron-driver/src/main/java/io/aeron/driver/NetworkPublication.java @@ -493,8 +493,11 @@ public void onError( final InetSocketAddress srcAddress, final DriverConductorProxy conductorProxy) { - livenessTracker.onRemoteClose(msg.receiverId()); flowControl.onError(msg, srcAddress, cachedNanoClock.nanoTime()); + if (livenessTracker.onRemoteClose(msg.receiverId())) + { + conductorProxy.onPublicationError(registrationId, msg.errorCode(), msg.errorMessage()); + } } /** diff --git a/aeron-driver/src/main/java/io/aeron/driver/ReceiverLivenessTracker.java b/aeron-driver/src/main/java/io/aeron/driver/ReceiverLivenessTracker.java index bf0a34316c..be488f1edd 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/ReceiverLivenessTracker.java +++ b/aeron-driver/src/main/java/io/aeron/driver/ReceiverLivenessTracker.java @@ -33,9 +33,9 @@ public void onStatusMessage(final long receiverId, final long nowNs) lastSmTimestampNsByReceiverIdMap.put(receiverId, nowNs); } - public void onRemoteClose(final long receiverId) + public boolean onRemoteClose(final long receiverId) { - lastSmTimestampNsByReceiverIdMap.remove(receiverId); + return MISSING_VALUE != lastSmTimestampNsByReceiverIdMap.remove(receiverId); } public void onIdle(final long nowNs, final long timeoutNs) diff --git a/aeron-driver/src/test/java/io/aeron/driver/ReceiverLivenessTrackerTest.java b/aeron-driver/src/test/java/io/aeron/driver/ReceiverLivenessTrackerTest.java index 4c1b1272a0..8db2525f18 100644 --- a/aeron-driver/src/test/java/io/aeron/driver/ReceiverLivenessTrackerTest.java +++ b/aeron-driver/src/test/java/io/aeron/driver/ReceiverLivenessTrackerTest.java @@ -80,7 +80,6 @@ void shouldNotBeLiveIfReceiverTimedOutAfter() assertFalse(receiverLivenessTracker.hasReceivers()); } - @Test void shouldNotBeLiveIfReceiverRemoved() { @@ -104,4 +103,29 @@ void shouldNotBeLiveIfReceiverRemoved() assertFalse(receiverLivenessTracker.hasReceivers()); } + @Test + void shouldReturnFalseIfAlreadyRemoved() + { + final long receiverId1 = 10001; + final long receiverId2 = 10002; + final long receiverId3 = 10003; + final long nowNs = 10000000000L; + + final ReceiverLivenessTracker receiverLivenessTracker = new ReceiverLivenessTracker(); + receiverLivenessTracker.onStatusMessage(receiverId1, nowNs); + receiverLivenessTracker.onStatusMessage(receiverId2, nowNs); + receiverLivenessTracker.onStatusMessage(receiverId3, nowNs); + + assertTrue(receiverLivenessTracker.onRemoteClose(receiverId1)); + assertFalse(receiverLivenessTracker.onRemoteClose(receiverId1)); + assertTrue(receiverLivenessTracker.hasReceivers()); + + assertTrue(receiverLivenessTracker.onRemoteClose(receiverId2)); + assertFalse(receiverLivenessTracker.onRemoteClose(receiverId2)); + assertTrue(receiverLivenessTracker.hasReceivers()); + + assertTrue(receiverLivenessTracker.onRemoteClose(receiverId3)); + assertFalse(receiverLivenessTracker.onRemoteClose(receiverId3)); + assertFalse(receiverLivenessTracker.hasReceivers()); + } } \ No newline at end of file diff --git a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java index 201cc98240..54e8db50a3 100644 --- a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java +++ b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java @@ -17,7 +17,6 @@ import io.aeron.driver.MediaDriver; import io.aeron.driver.ThreadingMode; -import io.aeron.driver.status.SystemCounterDescriptor; import io.aeron.exceptions.AeronException; import io.aeron.test.EventLogExtension; import io.aeron.test.InterruptAfter; @@ -29,6 +28,7 @@ import org.agrona.CloseHelper; import org.agrona.DirectBuffer; import org.agrona.concurrent.UnsafeBuffer; +import org.agrona.concurrent.status.CountersReader; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -39,6 +39,8 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import static io.aeron.driver.status.SystemCounterDescriptor.ERRORS; +import static io.aeron.driver.status.SystemCounterDescriptor.ERROR_FRAMES_RECEIVED; import static java.nio.charset.StandardCharsets.US_ASCII; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.lessThan; @@ -78,6 +80,8 @@ private TestMediaDriver launch() @SlowTest void shouldInvalidateSubscriptionsImage() { + TestMediaDriver.notSupportedOnCMediaDriver("Not implemented yet"); + context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3)); final TestMediaDriver driver = launch(); @@ -94,8 +98,11 @@ void shouldInvalidateSubscriptionsImage() Tests.awaitConnected(pub); Tests.awaitConnected(sub); - final long initialErrorMessagesReceived = aeron.countersReader() - .getCounterValue(SystemCounterDescriptor.ERROR_FRAMES_RECEIVED.id()); + final CountersReader countersReader = aeron.countersReader(); + final long initialErrorFramesReceived = countersReader + .getCounterValue(ERROR_FRAMES_RECEIVED.id()); + final long initialErrors = countersReader + .getCounterValue(ERRORS.id()); while (pub.offer(message) < 0) { @@ -122,8 +129,14 @@ void shouldInvalidateSubscriptionsImage() final long value = driver.context().publicationConnectionTimeoutNs(); assertThat(t1 - t0, lessThan(value)); - while (initialErrorMessagesReceived == aeron.countersReader() - .getCounterValue(SystemCounterDescriptor.ERROR_FRAMES_RECEIVED.id())) + while (initialErrorFramesReceived == countersReader + .getCounterValue(ERROR_FRAMES_RECEIVED.id())) + { + Tests.yield(); + } + + while (initialErrors == countersReader + .getCounterValue(ERRORS.id())) { Tests.yield(); } @@ -134,8 +147,10 @@ void shouldInvalidateSubscriptionsImage() } assertThat( - aeron.countersReader().getCounterValue(SystemCounterDescriptor.ERROR_FRAMES_RECEIVED.id()), + countersReader.getCounterValue(ERROR_FRAMES_RECEIVED.id()) - initialErrorFramesReceived, lessThan(A_VALUE_THAT_SHOWS_WE_ARENT_SPAMMING_ERROR_MESSAGES)); + + assertEquals(1, countersReader.getCounterValue(ERRORS.id()) - initialErrors); } } @@ -170,7 +185,7 @@ void shouldInvalidateSubscriptionsImageManualMdc() Tests.awaitConnected(sub); final long initialErrorMessagesReceived = aeron.countersReader() - .getCounterValue(SystemCounterDescriptor.ERROR_FRAMES_RECEIVED.id()); + .getCounterValue(ERROR_FRAMES_RECEIVED.id()); while (pub.offer(message) < 0) { @@ -199,7 +214,7 @@ void shouldInvalidateSubscriptionsImageManualMdc() assertThat(t1 - t0, lessThan(value)); while (initialErrorMessagesReceived == aeron.countersReader() - .getCounterValue(SystemCounterDescriptor.ERROR_FRAMES_RECEIVED.id())) + .getCounterValue(ERROR_FRAMES_RECEIVED.id())) { Tests.yield(); } @@ -210,7 +225,7 @@ void shouldInvalidateSubscriptionsImageManualMdc() } assertThat( - aeron.countersReader().getCounterValue(SystemCounterDescriptor.ERROR_FRAMES_RECEIVED.id()), + aeron.countersReader().getCounterValue(ERROR_FRAMES_RECEIVED.id()), lessThan(A_VALUE_THAT_SHOWS_WE_ARENT_SPAMMING_ERROR_MESSAGES)); while (initialAvailable != imageAvailable.get()) From 03a34c57dde6b1481dd54e2fedaf8ed462bcc1db Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Mon, 17 Jun 2024 16:11:41 +1200 Subject: [PATCH 19/67] [C] Log errors when error frames are received. --- .../src/main/c/aeron_driver_conductor.c | 7 ++++ .../src/main/c/aeron_driver_conductor.h | 2 ++ .../src/main/c/aeron_driver_conductor_proxy.c | 32 ++++++++++++++++++- .../src/main/c/aeron_driver_conductor_proxy.h | 16 ++++++++++ .../src/main/c/aeron_driver_receiver.c | 2 +- .../src/main/c/aeron_driver_receiver_proxy.c | 6 ++-- .../src/main/c/aeron_network_publication.c | 18 ++++++++--- .../src/main/c/aeron_network_publication.h | 8 ++++- .../c/media/aeron_send_channel_endpoint.c | 2 +- 9 files changed, 82 insertions(+), 11 deletions(-) diff --git a/aeron-driver/src/main/c/aeron_driver_conductor.c b/aeron-driver/src/main/c/aeron_driver_conductor.c index bcd7398662..d93a8cf94b 100644 --- a/aeron-driver/src/main/c/aeron_driver_conductor.c +++ b/aeron-driver/src/main/c/aeron_driver_conductor.c @@ -6067,6 +6067,13 @@ void aeron_driver_conductor_on_response_connected(void *clientd, void *item) } } +void aeron_driver_conductor_on_publication_error(void *clientd, void *item) +{ + aeron_driver_conductor_t *conductor = clientd; + aeron_command_publication_error_t *error = item; + aeron_driver_conductor_log_explicit_error(conductor, error->error_code, (const char *)error->error_text); +} + void aeron_driver_conductor_on_release_resource(void *clientd, void *item) { aeron_command_release_resource_t *cmd = item; diff --git a/aeron-driver/src/main/c/aeron_driver_conductor.h b/aeron-driver/src/main/c/aeron_driver_conductor.h index e16b6a880d..c9515c39bf 100644 --- a/aeron-driver/src/main/c/aeron_driver_conductor.h +++ b/aeron-driver/src/main/c/aeron_driver_conductor.h @@ -537,6 +537,8 @@ void aeron_driver_conductor_on_response_setup(void *clientd, void *item); void aeron_driver_conductor_on_response_connected(void *clientd, void *item); +void aeron_driver_conductor_on_publication_error(void *clientd, void *item); + void aeron_driver_conductor_on_release_resource(void *clientd, void *item); aeron_send_channel_endpoint_t *aeron_driver_conductor_find_send_channel_endpoint_by_tag( diff --git a/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c b/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c index b6bbae5605..528a87f050 100644 --- a/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c +++ b/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c @@ -240,7 +240,7 @@ void aeron_driver_conductor_proxy_on_release_resource( void *managed_resource, aeron_driver_conductor_resource_type_t resource_type) { - aeron_command_release_resource_t cmd = + aeron_command_release_resource_t cmd = { .base = { @@ -259,3 +259,33 @@ void aeron_driver_conductor_proxy_on_release_resource( aeron_driver_conductor_proxy_offer(conductor_proxy, &cmd, sizeof(cmd)); } } + +void aeron_driver_conductor_proxy_on_publication_error( + aeron_driver_conductor_proxy_t *conductor_proxy, + const int64_t registration_id, + int32_t error_code, + int32_t error_length, + const uint8_t *error_text) +{ + uint8_t buffer[sizeof(aeron_command_publication_error_t) + AERON_ERROR_MAX_MESSAGE_LENGTH + 1]; + aeron_command_publication_error_t *error = (aeron_command_publication_error_t *)buffer; + error_length = error_length <= AERON_ERROR_MAX_MESSAGE_LENGTH ? error_length : AERON_ERROR_MAX_MESSAGE_LENGTH; + + error->base.func = aeron_driver_conductor_on_publication_error; + error->base.item = NULL; + error->registration_id = registration_id; + error->error_code = error_code; + memcpy(error->error_text, error_text, (size_t)error_length); + error->error_text[error_length] = '\0'; + size_t cmd_length = sizeof(aeron_command_publication_error_t) + error_length + 1; + + if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(conductor_proxy->threading_mode)) + { + aeron_driver_conductor_on_publication_error(conductor_proxy->conductor, error); + } + else + { + aeron_driver_conductor_proxy_offer(conductor_proxy, (void *)error, cmd_length); + } +} + diff --git a/aeron-driver/src/main/c/aeron_driver_conductor_proxy.h b/aeron-driver/src/main/c/aeron_driver_conductor_proxy.h index 9a08a7249c..1d925e7733 100644 --- a/aeron-driver/src/main/c/aeron_driver_conductor_proxy.h +++ b/aeron-driver/src/main/c/aeron_driver_conductor_proxy.h @@ -100,6 +100,16 @@ struct aeron_command_release_resource_stct }; typedef struct aeron_command_release_resource_stct aeron_command_release_resource_t; +struct aeron_command_publication_error_stct +{ + aeron_command_base_t base; + int64_t registration_id; + int32_t error_code; + int32_t error_length; + uint8_t error_text[]; +}; +typedef struct aeron_command_publication_error_stct aeron_command_publication_error_t; + void aeron_driver_conductor_proxy_on_create_publication_image_cmd( aeron_driver_conductor_proxy_t *conductor_proxy, int32_t session_id, @@ -156,5 +166,11 @@ void aeron_driver_conductor_proxy_on_release_resource( void *managed_resource, aeron_driver_conductor_resource_type_t resource_type); +void aeron_driver_conductor_proxy_on_publication_error( + aeron_driver_conductor_proxy_t *conductor_proxy, + const int64_t registration_id, + int32_t error_code, + int32_t error_length, + const uint8_t *error_text); #endif //AERON_DRIVER_CONDUCTOR_PROXY_H diff --git a/aeron-driver/src/main/c/aeron_driver_receiver.c b/aeron-driver/src/main/c/aeron_driver_receiver.c index c243232384..9769b365a2 100644 --- a/aeron-driver/src/main/c/aeron_driver_receiver.c +++ b/aeron-driver/src/main/c/aeron_driver_receiver.c @@ -588,7 +588,7 @@ void aeron_driver_receiver_on_invalidate_image(void *clientd, void *item) aeron_command_receiver_invalidate_image_t *cmd = item; const int64_t correlation_id = cmd->image_correlation_id; const int32_t reason_length = cmd->reason_length; - const char *reason = (const char *)cmd + 1; + const char *reason = (const char *)(cmd + 1); for (size_t i = 0, size = receiver->images.length; i < size; i++) { diff --git a/aeron-driver/src/main/c/aeron_driver_receiver_proxy.c b/aeron-driver/src/main/c/aeron_driver_receiver_proxy.c index 2c9065f485..8232b8946d 100644 --- a/aeron-driver/src/main/c/aeron_driver_receiver_proxy.c +++ b/aeron-driver/src/main/c/aeron_driver_receiver_proxy.c @@ -360,15 +360,15 @@ void aeron_driver_receiver_proxy_on_invalidate_image( const char *reason) { // TODO: max reason length... - uint8_t message_buffer[sizeof(aeron_command_base_t) + 1024] = { 0 }; - aeron_command_receiver_invalidate_image_t *cmd = (aeron_command_receiver_invalidate_image_t *)&message_buffer[0]; + uint8_t message_buffer[sizeof(aeron_command_base_t) + AERON_ERROR_MAX_MESSAGE_LENGTH + 1] = { 0 }; + aeron_command_receiver_invalidate_image_t *cmd = (aeron_command_receiver_invalidate_image_t *)message_buffer; cmd->base.func = aeron_driver_receiver_on_invalidate_image; cmd->base.item = NULL; cmd->image_correlation_id = image_correlation_id; cmd->position = position; cmd->reason_length = reason_length; - memcpy(cmd + 1, reason, reason_length); // TODO: Ensure validated. + memcpy((cmd + 1), reason, reason_length); // TODO: Ensure validated. if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(receiver_proxy->threading_mode)) { diff --git a/aeron-driver/src/main/c/aeron_network_publication.c b/aeron-driver/src/main/c/aeron_network_publication.c index 1e52bb3f36..24578cc64e 100644 --- a/aeron-driver/src/main/c/aeron_network_publication.c +++ b/aeron-driver/src/main/c/aeron_network_publication.c @@ -40,11 +40,12 @@ struct mmsghdr }; #endif -static inline void aeron_network_publication_liveness_on_remote_close( +static inline bool aeron_network_publication_liveness_on_remote_close( aeron_network_publication_t *publication, int64_t receiver_id) { - aeron_int64_counter_map_remove(&publication->receiver_liveness_tracker, receiver_id); + int64_t missing_value = publication->receiver_liveness_tracker.initial_value; + return missing_value != aeron_int64_counter_map_remove(&publication->receiver_liveness_tracker, receiver_id); } static inline int aeron_network_publication_liveness_on_status_message( @@ -835,12 +836,19 @@ void aeron_network_publication_on_error( aeron_network_publication_t *publication, const uint8_t *buffer, size_t length, - struct sockaddr_storage *addr) + struct sockaddr_storage *addr, + aeron_driver_conductor_proxy_t *conductor_proxy) { aeron_error_t *error = (aeron_error_t *)buffer; + const uint8_t *error_text = (const uint8_t *)(error + 1); const int64_t time_ns = aeron_clock_cached_nano_time(publication->cached_clock); - aeron_network_publication_liveness_on_remote_close(publication, error->receiver_id); publication->flow_control->on_error(publication->flow_control->state, buffer, length, addr, time_ns); + if (aeron_network_publication_liveness_on_remote_close(publication, error->receiver_id)) + { + const int64_t registration_id = aeron_network_publication_registration_id(publication); + aeron_driver_conductor_proxy_on_publication_error( + conductor_proxy, registration_id, error->error_code, error->error_length, error_text); + } } void aeron_network_publication_on_rttm( @@ -1245,3 +1253,5 @@ extern bool aeron_network_publication_has_sender_released(aeron_network_publicat extern int64_t aeron_network_publication_max_spy_position(aeron_network_publication_t *publication, int64_t snd_pos); extern bool aeron_network_publication_is_accepting_subscriptions(aeron_network_publication_t *publication); + +extern inline int64_t aeron_network_publication_registration_id(aeron_network_publication_t *publication); diff --git a/aeron-driver/src/main/c/aeron_network_publication.h b/aeron-driver/src/main/c/aeron_network_publication.h index 25b065a83a..760bad246d 100644 --- a/aeron-driver/src/main/c/aeron_network_publication.h +++ b/aeron-driver/src/main/c/aeron_network_publication.h @@ -185,7 +185,8 @@ void aeron_network_publication_on_error( aeron_network_publication_t *publication, const uint8_t *buffer, size_t length, - struct sockaddr_storage *addr); + struct sockaddr_storage *addr, + aeron_driver_conductor_proxy_t *pStct); void aeron_network_publication_on_rttm( aeron_network_publication_t *publication, const uint8_t *buffer, size_t length, struct sockaddr_storage *addr); @@ -327,4 +328,9 @@ inline bool aeron_network_publication_is_accepting_subscriptions(aeron_network_p aeron_counter_get_volatile(publication->snd_pos_position.value_addr)); } +inline int64_t aeron_network_publication_registration_id(aeron_network_publication_t *publication) +{ + return publication->log_meta_data->correlation_id; +} + #endif //AERON_NETWORK_PUBLICATION_H diff --git a/aeron-driver/src/main/c/media/aeron_send_channel_endpoint.c b/aeron-driver/src/main/c/media/aeron_send_channel_endpoint.c index e03cca07bb..a79c632193 100644 --- a/aeron-driver/src/main/c/media/aeron_send_channel_endpoint.c +++ b/aeron-driver/src/main/c/media/aeron_send_channel_endpoint.c @@ -542,7 +542,7 @@ int aeron_send_channel_endpoint_on_error( if (NULL != publication) { - aeron_network_publication_on_error(publication, buffer, length, addr); + aeron_network_publication_on_error(publication, buffer, length, addr, conductor_proxy); } return result; From 04b88ceef74f32e22d9c37933cf97f02c6eb2d0a Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Mon, 17 Jun 2024 16:12:19 +1200 Subject: [PATCH 20/67] [Java] Enable test for C driver. --- .../src/test/java/io/aeron/ImageInvalidationTest.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java index 54e8db50a3..8b6db98b37 100644 --- a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java +++ b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java @@ -18,6 +18,7 @@ import io.aeron.driver.MediaDriver; import io.aeron.driver.ThreadingMode; import io.aeron.exceptions.AeronException; +import io.aeron.samples.ErrorStat; import io.aeron.test.EventLogExtension; import io.aeron.test.InterruptAfter; import io.aeron.test.InterruptingTestCallback; @@ -29,11 +30,13 @@ import org.agrona.DirectBuffer; import org.agrona.concurrent.UnsafeBuffer; import org.agrona.concurrent.status.CountersReader; +import org.hamcrest.Matcher; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.RegisterExtension; +import java.io.IOException; import java.util.Arrays; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -43,6 +46,7 @@ import static io.aeron.driver.status.SystemCounterDescriptor.ERROR_FRAMES_RECEIVED; import static java.nio.charset.StandardCharsets.US_ASCII; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.lessThan; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -78,10 +82,8 @@ private TestMediaDriver launch() @Test @InterruptAfter(10) @SlowTest - void shouldInvalidateSubscriptionsImage() + void shouldInvalidateSubscriptionsImage() throws IOException { - TestMediaDriver.notSupportedOnCMediaDriver("Not implemented yet"); - context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3)); final TestMediaDriver driver = launch(); @@ -151,6 +153,8 @@ void shouldInvalidateSubscriptionsImage() lessThan(A_VALUE_THAT_SHOWS_WE_ARENT_SPAMMING_ERROR_MESSAGES)); assertEquals(1, countersReader.getCounterValue(ERRORS.id()) - initialErrors); + + SystemTests.waitForErrorToOccur(driver.aeronDirectoryName(), containsString(reason), Tests.SLEEP_1_MS); } } From 426cb5cfa50338e2f6c4a3eeb90f7d3767c29e96 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Mon, 17 Jun 2024 16:29:25 +1200 Subject: [PATCH 21/67] [Java] Checkstyle. --- .../src/test/java/io/aeron/ImageInvalidationTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java index 8b6db98b37..93801f3891 100644 --- a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java +++ b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java @@ -18,7 +18,6 @@ import io.aeron.driver.MediaDriver; import io.aeron.driver.ThreadingMode; import io.aeron.exceptions.AeronException; -import io.aeron.samples.ErrorStat; import io.aeron.test.EventLogExtension; import io.aeron.test.InterruptAfter; import io.aeron.test.InterruptingTestCallback; @@ -30,7 +29,6 @@ import org.agrona.DirectBuffer; import org.agrona.concurrent.UnsafeBuffer; import org.agrona.concurrent.status.CountersReader; -import org.hamcrest.Matcher; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; From d6eea13f1c29b048b5ebe88ca3b42d7499c07778 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Mon, 17 Jun 2024 18:37:00 +1200 Subject: [PATCH 22/67] [C] Use C++ compliant flexible array member. --- aeron-driver/src/main/c/aeron_driver_conductor_proxy.c | 2 +- aeron-driver/src/main/c/aeron_driver_conductor_proxy.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c b/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c index 528a87f050..610e049c77 100644 --- a/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c +++ b/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c @@ -267,7 +267,7 @@ void aeron_driver_conductor_proxy_on_publication_error( int32_t error_length, const uint8_t *error_text) { - uint8_t buffer[sizeof(aeron_command_publication_error_t) + AERON_ERROR_MAX_MESSAGE_LENGTH + 1]; + uint8_t buffer[offsetof(aeron_command_publication_error_t, error_text[AERON_ERROR_MAX_MESSAGE_LENGTH + 1])]; aeron_command_publication_error_t *error = (aeron_command_publication_error_t *)buffer; error_length = error_length <= AERON_ERROR_MAX_MESSAGE_LENGTH ? error_length : AERON_ERROR_MAX_MESSAGE_LENGTH; diff --git a/aeron-driver/src/main/c/aeron_driver_conductor_proxy.h b/aeron-driver/src/main/c/aeron_driver_conductor_proxy.h index 1d925e7733..151b88834c 100644 --- a/aeron-driver/src/main/c/aeron_driver_conductor_proxy.h +++ b/aeron-driver/src/main/c/aeron_driver_conductor_proxy.h @@ -106,7 +106,7 @@ struct aeron_command_publication_error_stct int64_t registration_id; int32_t error_code; int32_t error_length; - uint8_t error_text[]; + uint8_t error_text[1]; }; typedef struct aeron_command_publication_error_stct aeron_command_publication_error_t; From 9ecd94c46477b7ed5d7d2f6f80869ed36b59a3f3 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Mon, 17 Jun 2024 12:21:57 -1100 Subject: [PATCH 23/67] [C] Handle flexible array members in MSVC. --- .../src/main/c/aeron_driver_conductor_proxy.c | 12 ++++++++++-- aeron-driver/src/main/c/aeron_network_publication.c | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c b/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c index 610e049c77..24919729b0 100644 --- a/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c +++ b/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c @@ -19,6 +19,8 @@ #include "aeron_alloc.h" #include "aeron_driver_conductor.h" +#define AERON_COMMAND_PUBLICATION_ERROR_MAX_LENGTH (sizeof(aeron_command_publication_error_t) + AERON_ERROR_MAX_MESSAGE_LENGTH) + void aeron_driver_conductor_proxy_offer(aeron_driver_conductor_proxy_t *conductor_proxy, void *cmd, size_t length) { aeron_rb_write_result_t result; @@ -260,6 +262,11 @@ void aeron_driver_conductor_proxy_on_release_resource( } } +static void aeron_driver_conductor_proxy_null_terminate(uint8_t *text, int index) +{ + text[index] = '\0'; +} + void aeron_driver_conductor_proxy_on_publication_error( aeron_driver_conductor_proxy_t *conductor_proxy, const int64_t registration_id, @@ -267,7 +274,7 @@ void aeron_driver_conductor_proxy_on_publication_error( int32_t error_length, const uint8_t *error_text) { - uint8_t buffer[offsetof(aeron_command_publication_error_t, error_text[AERON_ERROR_MAX_MESSAGE_LENGTH + 1])]; + uint8_t buffer[AERON_COMMAND_PUBLICATION_ERROR_MAX_LENGTH]; aeron_command_publication_error_t *error = (aeron_command_publication_error_t *)buffer; error_length = error_length <= AERON_ERROR_MAX_MESSAGE_LENGTH ? error_length : AERON_ERROR_MAX_MESSAGE_LENGTH; @@ -276,7 +283,8 @@ void aeron_driver_conductor_proxy_on_publication_error( error->registration_id = registration_id; error->error_code = error_code; memcpy(error->error_text, error_text, (size_t)error_length); - error->error_text[error_length] = '\0'; + aeron_driver_conductor_proxy_null_terminate(error->error_text, error_length); + size_t cmd_length = sizeof(aeron_command_publication_error_t) + error_length + 1; if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(conductor_proxy->threading_mode)) diff --git a/aeron-driver/src/main/c/aeron_network_publication.c b/aeron-driver/src/main/c/aeron_network_publication.c index 24578cc64e..02282cb952 100644 --- a/aeron-driver/src/main/c/aeron_network_publication.c +++ b/aeron-driver/src/main/c/aeron_network_publication.c @@ -312,7 +312,7 @@ int aeron_network_publication_create( _pub->is_response = AERON_UDP_CHANNEL_CONTROL_MODE_RESPONSE == endpoint->conductor_fields.udp_channel->control_mode; _pub->response_correlation_id = params->response_correlation_id; - aeron_int64_counter_map_init(&_pub->receiver_liveness_tracker, AERON_NULL_VALUE, 16, 0.6); + aeron_int64_counter_map_init(&_pub->receiver_liveness_tracker, AERON_NULL_VALUE, 16, 0.6f); *publication = _pub; From f03acaa4c7e368791b752438c4093379c6d08f4d Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Tue, 18 Jun 2024 15:16:28 +1200 Subject: [PATCH 24/67] [C] Use flexible array member correctly in more places for reason text on invalidate image. --- .../src/main/c/command/aeron_control_protocol.h | 1 + .../src/main/c/protocol/aeron_udp_protocol.h | 4 ++-- aeron-client/src/main/c/util/aeron_strutil.c | 1 + aeron-client/src/main/c/util/aeron_strutil.h | 5 +++++ aeron-driver/src/main/c/aeron_driver_conductor.c | 10 +++++----- .../src/main/c/aeron_driver_conductor_proxy.c | 13 ++++--------- aeron-driver/src/main/c/aeron_driver_receiver.c | 2 +- .../src/main/c/aeron_driver_receiver_proxy.c | 7 ++++--- .../src/main/c/aeron_driver_receiver_proxy.h | 1 + .../main/c/media/aeron_receive_channel_endpoint.c | 2 +- 10 files changed, 25 insertions(+), 21 deletions(-) diff --git a/aeron-client/src/main/c/command/aeron_control_protocol.h b/aeron-client/src/main/c/command/aeron_control_protocol.h index 06bd899ef8..4d84401f58 100644 --- a/aeron-client/src/main/c/command/aeron_control_protocol.h +++ b/aeron-client/src/main/c/command/aeron_control_protocol.h @@ -215,6 +215,7 @@ typedef struct aeron_invalidate_image_command_stct int64_t image_correlation_id; int64_t position; int32_t reason_length; + uint8_t reason_text[1]; } aeron_invalidate_image_command_t; diff --git a/aeron-client/src/main/c/protocol/aeron_udp_protocol.h b/aeron-client/src/main/c/protocol/aeron_udp_protocol.h index aa342ba3d0..a7918903ee 100644 --- a/aeron-client/src/main/c/protocol/aeron_udp_protocol.h +++ b/aeron-client/src/main/c/protocol/aeron_udp_protocol.h @@ -211,8 +211,8 @@ int aeron_udp_protocol_group_tag(aeron_status_message_header_t *sm, int64_t *gro #define AERON_OPT_HDR_ALIGNMENT (4u) -#define AERON_ERROR_MAX_MESSAGE_LENGTH (1023) -#define AERON_ERROR_MAX_FRAME_LENGTH (sizeof(aeron_error_t) + AERON_ERROR_MAX_MESSAGE_LENGTH) +#define AERON_ERROR_MAX_TEXT_LENGTH (1023) +#define AERON_ERROR_MAX_FRAME_LENGTH (sizeof(aeron_error_t) + AERON_ERROR_MAX_TEXT_LENGTH) #define AERON_ERROR_HAS_GROUP_TAG_FLAG (0x08) inline size_t aeron_res_header_address_length(int8_t res_type) diff --git a/aeron-client/src/main/c/util/aeron_strutil.c b/aeron-client/src/main/c/util/aeron_strutil.c index 657c364fc6..33a486eb81 100644 --- a/aeron-client/src/main/c/util/aeron_strutil.c +++ b/aeron-client/src/main/c/util/aeron_strutil.c @@ -264,3 +264,4 @@ int getopt(int argc, char *const argv[], const char *opt_string) extern uint64_t aeron_fnv_64a_buf(uint8_t *buf, size_t len); extern bool aeron_str_length(const char *str, size_t length_bound, size_t *length); +extern void aeron_str_null_terminate(uint8_t *text, int index); diff --git a/aeron-client/src/main/c/util/aeron_strutil.h b/aeron-client/src/main/c/util/aeron_strutil.h index c5db9ca7aa..ba88705ae9 100644 --- a/aeron-client/src/main/c/util/aeron_strutil.h +++ b/aeron-client/src/main/c/util/aeron_strutil.h @@ -115,4 +115,9 @@ inline bool aeron_str_length(const char *str, size_t length_bound, size_t *lengt return result; } +inline void aeron_str_null_terminate(uint8_t *text, int index) +{ + text[index] = '\0'; +} + #endif //AERON_STRUTIL_H diff --git a/aeron-driver/src/main/c/aeron_driver_conductor.c b/aeron-driver/src/main/c/aeron_driver_conductor.c index d93a8cf94b..0af945ad75 100644 --- a/aeron-driver/src/main/c/aeron_driver_conductor.c +++ b/aeron-driver/src/main/c/aeron_driver_conductor.c @@ -3343,7 +3343,7 @@ aeron_rb_read_action_t aeron_driver_conductor_on_command( goto malformed_command; } - if (length < sizeof(aeron_invalidate_image_command_t) + command->reason_length) + if (length < offsetof(aeron_invalidate_image_command_t, reason_text) + command->reason_length) { goto malformed_command; } @@ -5677,9 +5677,9 @@ int aeron_driver_conductor_on_invalidate_image( aeron_publication_image_t *image = aeron_driver_conductor_find_publication_image( conductor, image_correlation_id); - if (AERON_ERROR_MAX_MESSAGE_LENGTH < command->reason_length) + if (AERON_ERROR_MAX_TEXT_LENGTH < command->reason_length) { - AERON_SET_ERR(AERON_ERROR_CODE_GENERIC_ERROR, "%s", "Invalidation reason must be 1023 bytes or less"); + AERON_SET_ERR(AERON_ERROR_CODE_GENERIC_ERROR, "%s", "Invalidation reason_text must be 1023 bytes or less"); return -1; } @@ -5691,9 +5691,9 @@ int aeron_driver_conductor_on_invalidate_image( return -1; } - const char *reason = (const char *)(command + 1); + const char *reason_text = (const char *)command->reason_text; aeron_driver_receiver_proxy_on_invalidate_image( - conductor->context->receiver_proxy, image_correlation_id, command->position, command->reason_length, reason); + conductor->context->receiver_proxy, image_correlation_id, command->position, command->reason_length, reason_text); aeron_driver_conductor_on_operation_succeeded(conductor, command->correlated.correlation_id); diff --git a/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c b/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c index 24919729b0..179ea36ae0 100644 --- a/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c +++ b/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c @@ -19,7 +19,7 @@ #include "aeron_alloc.h" #include "aeron_driver_conductor.h" -#define AERON_COMMAND_PUBLICATION_ERROR_MAX_LENGTH (sizeof(aeron_command_publication_error_t) + AERON_ERROR_MAX_MESSAGE_LENGTH) +#define AERON_COMMAND_PUBLICATION_ERROR_MAX_LENGTH (sizeof(aeron_command_publication_error_t) + AERON_ERROR_MAX_TEXT_LENGTH) void aeron_driver_conductor_proxy_offer(aeron_driver_conductor_proxy_t *conductor_proxy, void *cmd, size_t length) { @@ -262,11 +262,6 @@ void aeron_driver_conductor_proxy_on_release_resource( } } -static void aeron_driver_conductor_proxy_null_terminate(uint8_t *text, int index) -{ - text[index] = '\0'; -} - void aeron_driver_conductor_proxy_on_publication_error( aeron_driver_conductor_proxy_t *conductor_proxy, const int64_t registration_id, @@ -276,15 +271,15 @@ void aeron_driver_conductor_proxy_on_publication_error( { uint8_t buffer[AERON_COMMAND_PUBLICATION_ERROR_MAX_LENGTH]; aeron_command_publication_error_t *error = (aeron_command_publication_error_t *)buffer; - error_length = error_length <= AERON_ERROR_MAX_MESSAGE_LENGTH ? error_length : AERON_ERROR_MAX_MESSAGE_LENGTH; + error_length = error_length <= AERON_ERROR_MAX_TEXT_LENGTH ? error_length : AERON_ERROR_MAX_TEXT_LENGTH; error->base.func = aeron_driver_conductor_on_publication_error; error->base.item = NULL; error->registration_id = registration_id; error->error_code = error_code; memcpy(error->error_text, error_text, (size_t)error_length); - aeron_driver_conductor_proxy_null_terminate(error->error_text, error_length); - + aeron_str_null_terminate(error->error_text, error_length); + size_t cmd_length = sizeof(aeron_command_publication_error_t) + error_length + 1; if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(conductor_proxy->threading_mode)) diff --git a/aeron-driver/src/main/c/aeron_driver_receiver.c b/aeron-driver/src/main/c/aeron_driver_receiver.c index 9769b365a2..f6ba44f8f0 100644 --- a/aeron-driver/src/main/c/aeron_driver_receiver.c +++ b/aeron-driver/src/main/c/aeron_driver_receiver.c @@ -588,7 +588,7 @@ void aeron_driver_receiver_on_invalidate_image(void *clientd, void *item) aeron_command_receiver_invalidate_image_t *cmd = item; const int64_t correlation_id = cmd->image_correlation_id; const int32_t reason_length = cmd->reason_length; - const char *reason = (const char *)(cmd + 1); + const char *reason = (const char *)cmd->reason_text; for (size_t i = 0, size = receiver->images.length; i < size; i++) { diff --git a/aeron-driver/src/main/c/aeron_driver_receiver_proxy.c b/aeron-driver/src/main/c/aeron_driver_receiver_proxy.c index 8232b8946d..ffdb66958d 100644 --- a/aeron-driver/src/main/c/aeron_driver_receiver_proxy.c +++ b/aeron-driver/src/main/c/aeron_driver_receiver_proxy.c @@ -359,8 +359,8 @@ void aeron_driver_receiver_proxy_on_invalidate_image( int32_t reason_length, const char *reason) { - // TODO: max reason length... - uint8_t message_buffer[sizeof(aeron_command_base_t) + AERON_ERROR_MAX_MESSAGE_LENGTH + 1] = { 0 }; + reason_length = reason_length <= AERON_ERROR_MAX_TEXT_LENGTH ? reason_length : AERON_ERROR_MAX_TEXT_LENGTH; + uint8_t message_buffer[sizeof(aeron_command_base_t) + AERON_ERROR_MAX_TEXT_LENGTH + 1]; aeron_command_receiver_invalidate_image_t *cmd = (aeron_command_receiver_invalidate_image_t *)message_buffer; cmd->base.func = aeron_driver_receiver_on_invalidate_image; @@ -368,7 +368,8 @@ void aeron_driver_receiver_proxy_on_invalidate_image( cmd->image_correlation_id = image_correlation_id; cmd->position = position; cmd->reason_length = reason_length; - memcpy((cmd + 1), reason, reason_length); // TODO: Ensure validated. + memcpy(cmd->reason_text, reason, reason_length); + aeron_str_null_terminate(cmd->reason_text, reason_length); if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(receiver_proxy->threading_mode)) { diff --git a/aeron-driver/src/main/c/aeron_driver_receiver_proxy.h b/aeron-driver/src/main/c/aeron_driver_receiver_proxy.h index dab6007542..399e0ebc17 100644 --- a/aeron-driver/src/main/c/aeron_driver_receiver_proxy.h +++ b/aeron-driver/src/main/c/aeron_driver_receiver_proxy.h @@ -139,6 +139,7 @@ typedef struct aeron_command_receiver_invalidate_image_stct int64_t image_correlation_id; int64_t position; int32_t reason_length; + uint8_t reason_text[1]; } aeron_command_receiver_invalidate_image_t; diff --git a/aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.c b/aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.c index 2ac5513f0d..f3364fd39a 100644 --- a/aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.c +++ b/aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.c @@ -437,7 +437,7 @@ int aeron_receiver_channel_endpoint_send_error_frame( aeron_error_t *error = (aeron_error_t *)buffer; struct iovec iov; - const size_t error_message_length = strnlen(invalidation_reason, AERON_ERROR_MAX_MESSAGE_LENGTH); + const size_t error_message_length = strnlen(invalidation_reason, AERON_ERROR_MAX_TEXT_LENGTH); const size_t frame_length = sizeof(aeron_error_t) + error_message_length; error->frame_header.frame_length = (int32_t)frame_length; error->frame_header.version = AERON_FRAME_HEADER_VERSION; From 3d09285b0d31e01ffbc1f37fcc4e5a804ebadb6a Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Wed, 19 Jun 2024 14:36:39 +1200 Subject: [PATCH 25/67] [Java] First pass at surfacing error frames to the client. --- .../src/main/java/io/aeron/Aeron.java | 24 +++ .../main/java/io/aeron/ClientConductor.java | 5 + .../java/io/aeron/DriverEventsAdapter.java | 11 ++ .../main/java/io/aeron/ErrorFrameHandler.java | 37 ++++ .../aeron/command/ControlProtocolEvents.java | 5 + .../PublicationErrorFrameFlyweight.java | 160 ++++++++++++++++++ .../java/io/aeron/driver/ClientProxy.java | 12 ++ .../java/io/aeron/driver/DriverConductor.java | 1 + .../io/aeron/driver/DriverConductorProxy.java | 4 + .../java/io/aeron/ImageInvalidationTest.java | 46 ++++- 10 files changed, 304 insertions(+), 1 deletion(-) create mode 100644 aeron-client/src/main/java/io/aeron/ErrorFrameHandler.java create mode 100644 aeron-client/src/main/java/io/aeron/command/PublicationErrorFrameFlyweight.java diff --git a/aeron-client/src/main/java/io/aeron/Aeron.java b/aeron-client/src/main/java/io/aeron/Aeron.java index 57281589c6..fe9f338421 100644 --- a/aeron-client/src/main/java/io/aeron/Aeron.java +++ b/aeron-client/src/main/java/io/aeron/Aeron.java @@ -942,6 +942,7 @@ public static class Context extends CommonContext private UnavailableImageHandler unavailableImageHandler; private AvailableCounterHandler availableCounterHandler; private UnavailableCounterHandler unavailableCounterHandler; + private ErrorFrameHandler errorFrameHandler = ErrorFrameHandler.NO_OP; private Runnable closeHandler; private long keepAliveIntervalNs = Configuration.KEEPALIVE_INTERVAL_NS; private long interServiceTimeoutNs = 0; @@ -1729,6 +1730,29 @@ public ThreadFactory threadFactory() return threadFactory; } + /** + * Set a handler to receive error frames that have been received by the local driver for resources owned by + * this client. + * + * @param errorFrameHandler to be called back when an error frame is received. + * @return this for a fluent API. + */ + public Context errorFrameHandler(final ErrorFrameHandler errorFrameHandler) + { + this.errorFrameHandler = errorFrameHandler; + return this; + } + + /** + * Get the handler used to report error frame received by this driver. + * + * @return the {@link ErrorFrameHandler} to call back on to. + */ + public ErrorFrameHandler errorFrameHandler() + { + return this.errorFrameHandler; + } + /** * Clean up all resources that the client uses to communicate with the Media Driver. */ diff --git a/aeron-client/src/main/java/io/aeron/ClientConductor.java b/aeron-client/src/main/java/io/aeron/ClientConductor.java index e159cf61b7..91000e8222 100644 --- a/aeron-client/src/main/java/io/aeron/ClientConductor.java +++ b/aeron-client/src/main/java/io/aeron/ClientConductor.java @@ -269,6 +269,11 @@ else if (resource instanceof Publication) } } + void onPublicationError(final long registrationId, final ErrorCode errorCode, final String errorText) + { + ctx.errorFrameHandler().onPublicationError(registrationId, errorCode.value(), errorText); + } + void onNewPublication( final long correlationId, final long registrationId, diff --git a/aeron-client/src/main/java/io/aeron/DriverEventsAdapter.java b/aeron-client/src/main/java/io/aeron/DriverEventsAdapter.java index a80a9437b9..6ff5215763 100644 --- a/aeron-client/src/main/java/io/aeron/DriverEventsAdapter.java +++ b/aeron-client/src/main/java/io/aeron/DriverEventsAdapter.java @@ -30,6 +30,7 @@ class DriverEventsAdapter implements MessageHandler { private final ErrorResponseFlyweight errorResponse = new ErrorResponseFlyweight(); + private final PublicationErrorFrameFlyweight publicationErrorFrame = new PublicationErrorFrameFlyweight(); private final PublicationBuffersReadyFlyweight publicationReady = new PublicationBuffersReadyFlyweight(); private final SubscriptionReadyFlyweight subscriptionReady = new SubscriptionReadyFlyweight(); private final ImageBuffersReadyFlyweight imageReady = new ImageBuffersReadyFlyweight(); @@ -263,6 +264,16 @@ else if (correlationId == activeCorrelationId) } break; } + + case ON_PUBLICATION_ERROR: + { + publicationErrorFrame.wrap(buffer, index); + + conductor.onPublicationError( + publicationErrorFrame.registrationId(), + publicationErrorFrame.errorCode(), + publicationErrorFrame.errorMessage()); + } } } } diff --git a/aeron-client/src/main/java/io/aeron/ErrorFrameHandler.java b/aeron-client/src/main/java/io/aeron/ErrorFrameHandler.java new file mode 100644 index 0000000000..c5859bc4c1 --- /dev/null +++ b/aeron-client/src/main/java/io/aeron/ErrorFrameHandler.java @@ -0,0 +1,37 @@ +/* + * Copyright 2014-2024 Real Logic Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.aeron; + +/** + * Interface for handling various error frame messages from different components in the client. + */ +public interface ErrorFrameHandler +{ + ErrorFrameHandler NO_OP = new ErrorFrameHandler() + { + }; + + /** + * Called when an error frame is received by the local driver. E.g. when an image is invalidated. + * + * @param registrationId for the publication. + * @param errorCode for the error received + * @param errorText description of the error that occurred. + */ + default void onPublicationError(final long registrationId, final int errorCode, final String errorText) + { + } +} diff --git a/aeron-client/src/main/java/io/aeron/command/ControlProtocolEvents.java b/aeron-client/src/main/java/io/aeron/command/ControlProtocolEvents.java index c62c5334b4..6d64a0b4ff 100644 --- a/aeron-client/src/main/java/io/aeron/command/ControlProtocolEvents.java +++ b/aeron-client/src/main/java/io/aeron/command/ControlProtocolEvents.java @@ -168,4 +168,9 @@ public class ControlProtocolEvents * @since 1.45.0 */ public static final int ON_STATIC_COUNTER = 0x0F0B; + + /** + * Inform clients of error frame received by publication + */ + public static final int ON_PUBLICATION_ERROR = 0x0F0C; } diff --git a/aeron-client/src/main/java/io/aeron/command/PublicationErrorFrameFlyweight.java b/aeron-client/src/main/java/io/aeron/command/PublicationErrorFrameFlyweight.java new file mode 100644 index 0000000000..2ab72e9e27 --- /dev/null +++ b/aeron-client/src/main/java/io/aeron/command/PublicationErrorFrameFlyweight.java @@ -0,0 +1,160 @@ +/* + * Copyright 2014-2024 Real Logic Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.aeron.command; + +import io.aeron.ErrorCode; +import org.agrona.BitUtil; +import org.agrona.MutableDirectBuffer; + +/** + * Control message flyweight error frames received by a publication to be reported to the client. + *
+ *   0                   1                   2                   3
+ *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  |                 Publication Registration Id                   |
+ *  |                                                               |
+ *  +---------------------------------------------------------------+
+ *  |                         Error Code                            |
+ *  +---------------------------------------------------------------+
+ *  |                   Error Message Length                        |
+ *  +---------------------------------------------------------------+
+ *  |                       Error Message                          ...
+ * ...                                                              |
+ *  +---------------------------------------------------------------+
+ * 
+ */ +public class PublicationErrorFrameFlyweight +{ + private static final int OFFENDING_COMMAND_CORRELATION_ID_OFFSET = 0; + private static final int ERROR_CODE_OFFSET = OFFENDING_COMMAND_CORRELATION_ID_OFFSET + BitUtil.SIZE_OF_LONG; + private static final int ERROR_MESSAGE_OFFSET = ERROR_CODE_OFFSET + BitUtil.SIZE_OF_INT; + + private MutableDirectBuffer buffer; + private int offset; + + /** + * Wrap the buffer at a given offset for updates. + * + * @param buffer to wrap. + * @param offset at which the message begins. + * @return this for a fluent API. + */ + public final PublicationErrorFrameFlyweight wrap(final MutableDirectBuffer buffer, final int offset) + { + this.buffer = buffer; + this.offset = offset; + + return this; + } + + /** + * Return registration ID of the publication that received the error frame. + * + * @return registration ID of the publication + */ + public long registrationId() + { + return buffer.getLong(offset + OFFENDING_COMMAND_CORRELATION_ID_OFFSET); + } + + /** + * Set the registration ID of the publication that received the error frame. + * + * @param registrationId of the publication. + * @return this for a fluent API. + */ + public PublicationErrorFrameFlyweight registrationId(final long registrationId) + { + buffer.putLong(offset + OFFENDING_COMMAND_CORRELATION_ID_OFFSET, registrationId); + return this; + } + + /** + * Error code for the command. + * + * @return error code for the command. + */ + public ErrorCode errorCode() + { + return ErrorCode.get(buffer.getInt(offset + ERROR_CODE_OFFSET)); + } + + /** + * Error code value for the command. + * + * @return error code value for the command. + */ + public int errorCodeValue() + { + return buffer.getInt(offset + ERROR_CODE_OFFSET); + } + + /** + * Set the error code for the command. + * + * @param code for the error. + * @return this for a fluent API. + */ + public PublicationErrorFrameFlyweight errorCode(final ErrorCode code) + { + buffer.putInt(offset + ERROR_CODE_OFFSET, code.value()); + return this; + } + + /** + * Error message associated with the error. + * + * @return error message + */ + public String errorMessage() + { + return buffer.getStringAscii(offset + ERROR_MESSAGE_OFFSET); + } + + /** + * Append the error message to an appendable without allocation. + * + * @param appendable to append error message to. + * @return number bytes copied. + */ + public int appendMessage(final Appendable appendable) + { + return buffer.getStringAscii(offset + ERROR_MESSAGE_OFFSET, appendable); + } + + /** + * Set the error message. + * + * @param message to associate with the error. + * @return this for a fluent API. + */ + public PublicationErrorFrameFlyweight errorMessage(final String message) + { + buffer.putStringAscii(offset + ERROR_MESSAGE_OFFSET, message); + return this; + } + + /** + * Length of the error response in bytes. + * + * @return length of the error response in bytes. + */ + public int length() + { + return ERROR_MESSAGE_OFFSET + BitUtil.SIZE_OF_INT + buffer.getInt(offset + ERROR_MESSAGE_OFFSET); + } +} diff --git a/aeron-driver/src/main/java/io/aeron/driver/ClientProxy.java b/aeron-driver/src/main/java/io/aeron/driver/ClientProxy.java index 0e49d06dc0..22ec910aa9 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/ClientProxy.java +++ b/aeron-driver/src/main/java/io/aeron/driver/ClientProxy.java @@ -33,6 +33,7 @@ final class ClientProxy private final BroadcastTransmitter transmitter; private final ErrorResponseFlyweight errorResponse = new ErrorResponseFlyweight(); + private final PublicationErrorFrameFlyweight publicationErrorFrame = new PublicationErrorFrameFlyweight(); private final PublicationBuffersReadyFlyweight publicationReady = new PublicationBuffersReadyFlyweight(); private final SubscriptionReadyFlyweight subscriptionReady = new SubscriptionReadyFlyweight(); private final ImageBuffersReadyFlyweight imageReady = new ImageBuffersReadyFlyweight(); @@ -47,6 +48,7 @@ final class ClientProxy this.transmitter = transmitter; errorResponse.wrap(buffer, 0); + publicationErrorFrame.wrap(buffer, 0); imageReady.wrap(buffer, 0); publicationReady.wrap(buffer, 0); subscriptionReady.wrap(buffer, 0); @@ -67,6 +69,16 @@ void onError(final long correlationId, final ErrorCode errorCode, final String e transmit(ON_ERROR, buffer, 0, errorResponse.length()); } + void onPublicationErrorFrame(final long registrationId, final int errorCode, final String errorMessage) + { + publicationErrorFrame + .registrationId(registrationId) + .errorCode(ErrorCode.get(errorCode)) + .errorMessage(errorMessage); + + transmit(ON_PUBLICATION_ERROR, buffer, 0, publicationErrorFrame.length()); + } + void onAvailableImage( final long correlationId, final int streamId, diff --git a/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java b/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java index c128dd4eae..94eaf301d5 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java +++ b/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java @@ -390,6 +390,7 @@ void onChannelEndpointError(final long statusIndicatorId, final Exception ex) void onPublicationError(final long registrationId, final int errorCode, final String errorMessage) { recordError(new AeronException(errorMessage, AeronException.Category.WARN)); + clientProxy.onPublicationErrorFrame(registrationId, errorCode, errorMessage); } void onReResolveEndpoint( diff --git a/aeron-driver/src/main/java/io/aeron/driver/DriverConductorProxy.java b/aeron-driver/src/main/java/io/aeron/driver/DriverConductorProxy.java index 0a717deafa..0d558bcef2 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/DriverConductorProxy.java +++ b/aeron-driver/src/main/java/io/aeron/driver/DriverConductorProxy.java @@ -253,6 +253,10 @@ void onPublicationError(final long registrationId, final int errorCode, final St { driverConductor.onPublicationError(registrationId, errorCode, errorMessage); } + else + { + offer(() -> driverConductor.onPublicationError(registrationId, errorCode, errorMessage)); + } } private void offer(final Runnable cmd) diff --git a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java index 93801f3891..11e51f06d3 100644 --- a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java +++ b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java @@ -27,6 +27,7 @@ import io.aeron.test.driver.TestMediaDriver; import org.agrona.CloseHelper; import org.agrona.DirectBuffer; +import org.agrona.concurrent.OneToOneConcurrentArrayQueue; import org.agrona.concurrent.UnsafeBuffer; import org.agrona.concurrent.status.CountersReader; import org.junit.jupiter.api.AfterEach; @@ -77,17 +78,51 @@ private TestMediaDriver launch() return driver; } + private static final class ErrorFrame + { + private final long registrationId; + private final int errorCode; + private final String errorText; + + private ErrorFrame(final long registrationId, final int errorCode, final String errorText) + { + this.registrationId = registrationId; + this.errorCode = errorCode; + this.errorText = errorText; + } + } + + private static final class QueuedErrorFrameHandler implements ErrorFrameHandler + { + private final OneToOneConcurrentArrayQueue errorFrameQueue = + new OneToOneConcurrentArrayQueue<>(512); + + public void onPublicationError(final long registrationId, final int errorCode, final String errorText) + { + errorFrameQueue.offer(new ErrorFrame(registrationId, errorCode, errorText)); + } + + ErrorFrame poll() + { + return errorFrameQueue.poll(); + } + } + @Test @InterruptAfter(10) @SlowTest void shouldInvalidateSubscriptionsImage() throws IOException { + TestMediaDriver.notSupportedOnCMediaDriver("Not yet implemented"); + context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3)); final TestMediaDriver driver = launch(); + final QueuedErrorFrameHandler errorFrameHandler = new QueuedErrorFrameHandler(); final Aeron.Context ctx = new Aeron.Context() - .aeronDirectoryName(driver.aeronDirectoryName()); + .aeronDirectoryName(driver.aeronDirectoryName()) + .errorFrameHandler(errorFrameHandler); final AtomicBoolean imageUnavailable = new AtomicBoolean(false); @@ -141,6 +176,15 @@ void shouldInvalidateSubscriptionsImage() throws IOException Tests.yield(); } + ErrorFrame errorFrame; + while (null == (errorFrame = errorFrameHandler.poll())) + { + Tests.yield(); + } + + assertEquals(reason, errorFrame.errorText); + assertEquals(pub.registrationId(), errorFrame.registrationId); + while (!imageUnavailable.get()) { Tests.yield(); From 14b7fd0b8c34aeef76677448d47665a9d20023a1 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Wed, 19 Jun 2024 15:57:41 +1200 Subject: [PATCH 26/67] [C] Send publication error frames to the client. --- .../main/c/command/aeron_control_protocol.h | 10 ++++++++ .../src/main/c/aeron_driver_conductor.c | 24 +++++++++++++------ .../src/main/c/aeron_driver_conductor_proxy.c | 1 + 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/aeron-client/src/main/c/command/aeron_control_protocol.h b/aeron-client/src/main/c/command/aeron_control_protocol.h index 4d84401f58..9cc6f7739d 100644 --- a/aeron-client/src/main/c/command/aeron_control_protocol.h +++ b/aeron-client/src/main/c/command/aeron_control_protocol.h @@ -49,6 +49,7 @@ #define AERON_RESPONSE_ON_UNAVAILABLE_COUNTER (0x0F09) #define AERON_RESPONSE_ON_CLIENT_TIMEOUT (0x0F0A) #define AERON_RESPONSE_ON_STATIC_COUNTER (0x0F0B) +#define AERON_RESPONSE_ON_PUBLICATION_ERROR (0x0F0C) /* error codes */ #define AERON_ERROR_CODE_UNKNOWN_CODE_VALUE (-1) @@ -219,6 +220,15 @@ typedef struct aeron_invalidate_image_command_stct } aeron_invalidate_image_command_t; +struct aeron_publication_error_response_stct +{ + int64_t registration_id; + int32_t error_code; + int32_t error_message_length; + uint8_t error_message[1]; +}; +typedef struct aeron_publication_error_response_stct aeron_publication_error_response_t; + #pragma pack(pop) #endif //AERON_CONTROL_PROTOCOL_H diff --git a/aeron-driver/src/main/c/aeron_driver_conductor.c b/aeron-driver/src/main/c/aeron_driver_conductor.c index 0af945ad75..c926f54051 100644 --- a/aeron-driver/src/main/c/aeron_driver_conductor.c +++ b/aeron-driver/src/main/c/aeron_driver_conductor.c @@ -2568,6 +2568,23 @@ void on_error( aeron_driver_conductor_client_transmit(conductor, AERON_RESPONSE_ON_ERROR, response, response_length); } +void aeron_driver_conductor_on_publication_error(void *clientd, void *item) +{ + uint8_t buffer[sizeof(aeron_publication_error_response_t) + (AERON_ERROR_MAX_TEXT_LENGTH - 1)]; + aeron_driver_conductor_t *conductor = clientd; + aeron_command_publication_error_t *error = item; + aeron_driver_conductor_log_explicit_error(conductor, error->error_code, (const char *)error->error_text); + + aeron_publication_error_response_t *response = (aeron_publication_error_response_t *)buffer; + response->error_code = error->error_code; + response->registration_id = error->registration_id; + response->error_message_length = error->error_length; + memcpy(response->error_message, error->error_text, error->error_length); + size_t response_length = offsetof(aeron_publication_error_response_t, error_message) + response->error_message_length; + + aeron_driver_conductor_client_transmit(conductor, AERON_RESPONSE_ON_PUBLICATION_ERROR, response, response_length); +} + void aeron_driver_conductor_on_error( aeron_driver_conductor_t *conductor, int32_t errcode, @@ -6067,13 +6084,6 @@ void aeron_driver_conductor_on_response_connected(void *clientd, void *item) } } -void aeron_driver_conductor_on_publication_error(void *clientd, void *item) -{ - aeron_driver_conductor_t *conductor = clientd; - aeron_command_publication_error_t *error = item; - aeron_driver_conductor_log_explicit_error(conductor, error->error_code, (const char *)error->error_text); -} - void aeron_driver_conductor_on_release_resource(void *clientd, void *item) { aeron_command_release_resource_t *cmd = item; diff --git a/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c b/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c index 179ea36ae0..5a1c0bf949 100644 --- a/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c +++ b/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c @@ -277,6 +277,7 @@ void aeron_driver_conductor_proxy_on_publication_error( error->base.item = NULL; error->registration_id = registration_id; error->error_code = error_code; + error->error_length = error_length; memcpy(error->error_text, error_text, (size_t)error_length); aeron_str_null_terminate(error->error_text, error_length); From 8aece1d517950fe17b5546a4bdc5ed6846a3b846 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Thu, 20 Jun 2024 15:47:05 +1200 Subject: [PATCH 27/67] [Java] Enable test for C driver. --- .../src/test/java/io/aeron/ImageInvalidationTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java index 11e51f06d3..6a805e835b 100644 --- a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java +++ b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java @@ -113,8 +113,6 @@ ErrorFrame poll() @SlowTest void shouldInvalidateSubscriptionsImage() throws IOException { - TestMediaDriver.notSupportedOnCMediaDriver("Not yet implemented"); - context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3)); final TestMediaDriver driver = launch(); From b6c3863ae64bbbaf376e9bbb0f5b7f4da2a337e9 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Thu, 20 Jun 2024 16:17:21 +1200 Subject: [PATCH 28/67] [Java] Only report publication error frame to client that has the publication registered. --- .../src/main/java/io/aeron/Aeron.java | 8 +-- .../main/java/io/aeron/ClientConductor.java | 5 +- ...meHandler.java => ErrorFrameListener.java} | 4 +- .../java/io/aeron/ImageInvalidationTest.java | 62 ++++++++++++++++++- 4 files changed, 71 insertions(+), 8 deletions(-) rename aeron-client/src/main/java/io/aeron/{ErrorFrameHandler.java => ErrorFrameListener.java} (92%) diff --git a/aeron-client/src/main/java/io/aeron/Aeron.java b/aeron-client/src/main/java/io/aeron/Aeron.java index fe9f338421..7c9471dd41 100644 --- a/aeron-client/src/main/java/io/aeron/Aeron.java +++ b/aeron-client/src/main/java/io/aeron/Aeron.java @@ -942,7 +942,7 @@ public static class Context extends CommonContext private UnavailableImageHandler unavailableImageHandler; private AvailableCounterHandler availableCounterHandler; private UnavailableCounterHandler unavailableCounterHandler; - private ErrorFrameHandler errorFrameHandler = ErrorFrameHandler.NO_OP; + private ErrorFrameListener errorFrameHandler = ErrorFrameListener.NO_OP; private Runnable closeHandler; private long keepAliveIntervalNs = Configuration.KEEPALIVE_INTERVAL_NS; private long interServiceTimeoutNs = 0; @@ -1737,7 +1737,7 @@ public ThreadFactory threadFactory() * @param errorFrameHandler to be called back when an error frame is received. * @return this for a fluent API. */ - public Context errorFrameHandler(final ErrorFrameHandler errorFrameHandler) + public Context errorFrameHandler(final ErrorFrameListener errorFrameHandler) { this.errorFrameHandler = errorFrameHandler; return this; @@ -1746,9 +1746,9 @@ public Context errorFrameHandler(final ErrorFrameHandler errorFrameHandler) /** * Get the handler used to report error frame received by this driver. * - * @return the {@link ErrorFrameHandler} to call back on to. + * @return the {@link ErrorFrameListener} to call back on to. */ - public ErrorFrameHandler errorFrameHandler() + public ErrorFrameListener errorFrameHandler() { return this.errorFrameHandler; } diff --git a/aeron-client/src/main/java/io/aeron/ClientConductor.java b/aeron-client/src/main/java/io/aeron/ClientConductor.java index 91000e8222..db94e20761 100644 --- a/aeron-client/src/main/java/io/aeron/ClientConductor.java +++ b/aeron-client/src/main/java/io/aeron/ClientConductor.java @@ -271,7 +271,10 @@ else if (resource instanceof Publication) void onPublicationError(final long registrationId, final ErrorCode errorCode, final String errorText) { - ctx.errorFrameHandler().onPublicationError(registrationId, errorCode.value(), errorText); + if (resourceByRegIdMap.containsKey(registrationId)) + { + ctx.errorFrameHandler().onPublicationError(registrationId, errorCode.value(), errorText); + } } void onNewPublication( diff --git a/aeron-client/src/main/java/io/aeron/ErrorFrameHandler.java b/aeron-client/src/main/java/io/aeron/ErrorFrameListener.java similarity index 92% rename from aeron-client/src/main/java/io/aeron/ErrorFrameHandler.java rename to aeron-client/src/main/java/io/aeron/ErrorFrameListener.java index c5859bc4c1..3aa6d590b7 100644 --- a/aeron-client/src/main/java/io/aeron/ErrorFrameHandler.java +++ b/aeron-client/src/main/java/io/aeron/ErrorFrameListener.java @@ -18,9 +18,9 @@ /** * Interface for handling various error frame messages from different components in the client. */ -public interface ErrorFrameHandler +public interface ErrorFrameListener { - ErrorFrameHandler NO_OP = new ErrorFrameHandler() + ErrorFrameListener NO_OP = new ErrorFrameListener() { }; diff --git a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java index 6a805e835b..7651d66257 100644 --- a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java +++ b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java @@ -48,6 +48,8 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.lessThan; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; @ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class }) @@ -92,7 +94,7 @@ private ErrorFrame(final long registrationId, final int errorCode, final String } } - private static final class QueuedErrorFrameHandler implements ErrorFrameHandler + private static final class QueuedErrorFrameHandler implements ErrorFrameListener { private final OneToOneConcurrentArrayQueue errorFrameQueue = new OneToOneConcurrentArrayQueue<>(512); @@ -198,6 +200,64 @@ void shouldInvalidateSubscriptionsImage() throws IOException } } + @Test + @InterruptAfter(10) + @SlowTest + void shouldOnlyReceivePublicationErrorFrameOnRelevantClient() throws IOException + { + context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3)); + + final TestMediaDriver driver = launch(); + final QueuedErrorFrameHandler errorFrameHandler1 = new QueuedErrorFrameHandler(); + final QueuedErrorFrameHandler errorFrameHandler2 = new QueuedErrorFrameHandler(); + + final Aeron.Context ctx1 = new Aeron.Context() + .aeronDirectoryName(driver.aeronDirectoryName()) + .errorFrameHandler(errorFrameHandler1); + + final Aeron.Context ctx2 = new Aeron.Context() + .aeronDirectoryName(driver.aeronDirectoryName()) + .errorFrameHandler(errorFrameHandler2); + + final AtomicBoolean imageUnavailable = new AtomicBoolean(false); + + try (Aeron aeron1 = Aeron.connect(ctx1); + Aeron aeron2 = Aeron.connect(ctx2); + Publication pub = aeron1.addPublication(channel, streamId); + Subscription sub = aeron1.addSubscription(channel, streamId, null, image -> imageUnavailable.set(true))) + { + assertNotNull(aeron2); + Tests.awaitConnected(pub); + Tests.awaitConnected(sub); + + while (pub.offer(message) < 0) + { + Tests.yield(); + } + + while (0 == sub.poll((buffer, offset, length, header) -> {}, 1)) + { + Tests.yield(); + } + + final Image image = sub.imageAtIndex(0); + final String reason = "Needs to be closed"; + image.invalidate(reason); + + while (null == errorFrameHandler1.poll()) + { + Tests.yield(); + } + + final long deadlineMs = System.currentTimeMillis() + 1_000; + while (System.currentTimeMillis() < deadlineMs) + { + assertNull(errorFrameHandler2.poll(), "Aeron client without publication should not report error"); + Tests.yield(); + } + } + } + @Test @InterruptAfter(10) @SlowTest From 9aeb7c7df748aa4b4399697805e5a5205c25a7bb Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Fri, 21 Jun 2024 10:39:13 +1200 Subject: [PATCH 29/67] [Java] Start encapsulating all fields relating to an error frame in an object to be reported to the caller. --- .../main/java/io/aeron/ClientConductor.java | 10 +- .../java/io/aeron/DriverEventsAdapter.java | 5 +- .../java/io/aeron/ErrorFrameListener.java | 13 +- .../aeron/status/PublicationErrorFrame.java | 150 ++++++++++++++++++ .../java/io/aeron/ImageInvalidationTest.java | 19 ++- 5 files changed, 175 insertions(+), 22 deletions(-) create mode 100644 aeron-client/src/main/java/io/aeron/status/PublicationErrorFrame.java diff --git a/aeron-client/src/main/java/io/aeron/ClientConductor.java b/aeron-client/src/main/java/io/aeron/ClientConductor.java index db94e20761..19768ac9dc 100644 --- a/aeron-client/src/main/java/io/aeron/ClientConductor.java +++ b/aeron-client/src/main/java/io/aeron/ClientConductor.java @@ -15,9 +15,11 @@ */ package io.aeron; +import io.aeron.command.PublicationErrorFrameFlyweight; import io.aeron.exceptions.*; import io.aeron.status.ChannelEndpointStatus; import io.aeron.status.HeartbeatTimestamp; +import io.aeron.status.PublicationErrorFrame; import org.agrona.*; import org.agrona.collections.ArrayListUtil; import org.agrona.collections.Long2ObjectHashMap; @@ -84,6 +86,7 @@ final class ClientConductor implements Agent private final AgentInvoker driverAgentInvoker; private final UnsafeBuffer counterValuesBuffer; private final CountersReader countersReader; + private final PublicationErrorFrame publicationErrorFrame = new PublicationErrorFrame(); private AtomicCounter heartbeatTimestamp; ClientConductor(final Aeron.Context ctx, final Aeron aeron) @@ -269,11 +272,12 @@ else if (resource instanceof Publication) } } - void onPublicationError(final long registrationId, final ErrorCode errorCode, final String errorText) + void onPublicationError(final PublicationErrorFrameFlyweight errorFrameFlyweight) { - if (resourceByRegIdMap.containsKey(registrationId)) + if (resourceByRegIdMap.containsKey(errorFrameFlyweight.registrationId())) { - ctx.errorFrameHandler().onPublicationError(registrationId, errorCode.value(), errorText); + publicationErrorFrame.set(errorFrameFlyweight); + ctx.errorFrameHandler().onPublicationError(publicationErrorFrame); } } diff --git a/aeron-client/src/main/java/io/aeron/DriverEventsAdapter.java b/aeron-client/src/main/java/io/aeron/DriverEventsAdapter.java index 6ff5215763..6a847251ae 100644 --- a/aeron-client/src/main/java/io/aeron/DriverEventsAdapter.java +++ b/aeron-client/src/main/java/io/aeron/DriverEventsAdapter.java @@ -269,10 +269,7 @@ else if (correlationId == activeCorrelationId) { publicationErrorFrame.wrap(buffer, index); - conductor.onPublicationError( - publicationErrorFrame.registrationId(), - publicationErrorFrame.errorCode(), - publicationErrorFrame.errorMessage()); + conductor.onPublicationError(publicationErrorFrame); } } } diff --git a/aeron-client/src/main/java/io/aeron/ErrorFrameListener.java b/aeron-client/src/main/java/io/aeron/ErrorFrameListener.java index 3aa6d590b7..0a888c2e47 100644 --- a/aeron-client/src/main/java/io/aeron/ErrorFrameListener.java +++ b/aeron-client/src/main/java/io/aeron/ErrorFrameListener.java @@ -15,6 +15,8 @@ */ package io.aeron; +import io.aeron.status.PublicationErrorFrame; + /** * Interface for handling various error frame messages from different components in the client. */ @@ -25,13 +27,14 @@ public interface ErrorFrameListener }; /** - * Called when an error frame is received by the local driver. E.g. when an image is invalidated. + * Called when an error frame is received by the local driver. E.g. when an image is invalidated. This callback will + * reuse the {@link PublicationErrorFrame} instance, so data is only valid for the lifetime of the callback. If the + * user needs to pass the data onto another thread or hold in another location for use later, then the user needs to + * make use of the {@link PublicationErrorFrame#clone()} method to create a copy for their own use. * - * @param registrationId for the publication. - * @param errorCode for the error received - * @param errorText description of the error that occurred. + * @param errorFrame contain the data from the error frame received by the publication. */ - default void onPublicationError(final long registrationId, final int errorCode, final String errorText) + default void onPublicationError(final PublicationErrorFrame errorFrame) { } } diff --git a/aeron-client/src/main/java/io/aeron/status/PublicationErrorFrame.java b/aeron-client/src/main/java/io/aeron/status/PublicationErrorFrame.java new file mode 100644 index 0000000000..1cfaf53ea1 --- /dev/null +++ b/aeron-client/src/main/java/io/aeron/status/PublicationErrorFrame.java @@ -0,0 +1,150 @@ +/* + * Copyright 2014-2024 Real Logic Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.aeron.status; + +import io.aeron.CommonContext; +import io.aeron.command.PublicationErrorFrameFlyweight; + +import java.net.InetAddress; + +/** + * Encapsulates the data received when a publication receives an error frame. + */ +public class PublicationErrorFrame implements Cloneable +{ + private long registrationId; + private int sessionId; + private int streamId; + private long receiverId; + private Long groupTag; + private int errorCode; + private String errorMessage; + private InetAddress sourceAddress; + + /** + * Registration id of the publication that received the error frame. + * + * @return registration id of the publication. + */ + public long registrationId() + { + return registrationId; + } + + /** + * Session id of the publication that received the error frame. + * + * @return session id of the publication. + */ + public int sessionId() + { + return sessionId; + } + + /** + * Stream id of the publication that received the error frame. + * + * @return stream id of the publication. + */ + public int streamId() + { + return streamId; + } + + /** + * Receiver id of the source that send the error frame. + * + * @return receiver id of the source that send the error frame. + */ + public long receiverId() + { + return receiverId; + } + + /** + * Group tag of the source that sent the error frame. + * + * @return group tag of the source that sent the error frame, null if the source did not have a group + * tag set. + */ + public Long groupTag() + { + return groupTag; + } + + /** + * The error code of the error frame received. + * + * @return the error code. + */ + public int errorCode() + { + return errorCode; + } + + /** + * The error message of the error frame received. + * + * @return the error message. + */ + public String errorMessage() + { + return errorMessage; + } + + /** + * The address of the remote source that sent the error frame. + * + * @return address of the remote source. + */ + public InetAddress sourceAddress() + { + return sourceAddress; + } + + /** + * Set the fields of the publication error frame from the flyweight. + * + * @param frameFlyweight that was received from the client message buffer. + * @return this for fluent API. + */ + public PublicationErrorFrame set(final PublicationErrorFrameFlyweight frameFlyweight) + { + registrationId = frameFlyweight.registrationId(); + errorCode = frameFlyweight.errorCode().value(); + errorMessage = frameFlyweight.errorMessage(); + + return this; + } + + /** + * Return a copy of this message. Useful if a callback is reusing an instance of this class to avoid unnecessary + * allocation. + * + * @return a copy of this instance's data. + */ + public PublicationErrorFrame clone() + { + try + { + return (PublicationErrorFrame)super.clone(); + } + catch (final CloneNotSupportedException ex) + { + throw new RuntimeException(ex); + } + } +} diff --git a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java index 7651d66257..e13ea57ab4 100644 --- a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java +++ b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java @@ -18,6 +18,7 @@ import io.aeron.driver.MediaDriver; import io.aeron.driver.ThreadingMode; import io.aeron.exceptions.AeronException; +import io.aeron.status.PublicationErrorFrame; import io.aeron.test.EventLogExtension; import io.aeron.test.InterruptAfter; import io.aeron.test.InterruptingTestCallback; @@ -96,15 +97,15 @@ private ErrorFrame(final long registrationId, final int errorCode, final String private static final class QueuedErrorFrameHandler implements ErrorFrameListener { - private final OneToOneConcurrentArrayQueue errorFrameQueue = + private final OneToOneConcurrentArrayQueue errorFrameQueue = new OneToOneConcurrentArrayQueue<>(512); - public void onPublicationError(final long registrationId, final int errorCode, final String errorText) + public void onPublicationError(final PublicationErrorFrame errorFrame) { - errorFrameQueue.offer(new ErrorFrame(registrationId, errorCode, errorText)); + errorFrameQueue.offer(errorFrame.clone()); } - ErrorFrame poll() + PublicationErrorFrame poll() { return errorFrameQueue.poll(); } @@ -176,14 +177,14 @@ void shouldInvalidateSubscriptionsImage() throws IOException Tests.yield(); } - ErrorFrame errorFrame; + PublicationErrorFrame errorFrame; while (null == (errorFrame = errorFrameHandler.poll())) { Tests.yield(); } - assertEquals(reason, errorFrame.errorText); - assertEquals(pub.registrationId(), errorFrame.registrationId); + assertEquals(reason, errorFrame.errorMessage()); + assertEquals(pub.registrationId(), errorFrame.registrationId()); while (!imageUnavailable.get()) { @@ -219,12 +220,10 @@ void shouldOnlyReceivePublicationErrorFrameOnRelevantClient() throws IOException .aeronDirectoryName(driver.aeronDirectoryName()) .errorFrameHandler(errorFrameHandler2); - final AtomicBoolean imageUnavailable = new AtomicBoolean(false); - try (Aeron aeron1 = Aeron.connect(ctx1); Aeron aeron2 = Aeron.connect(ctx2); Publication pub = aeron1.addPublication(channel, streamId); - Subscription sub = aeron1.addSubscription(channel, streamId, null, image -> imageUnavailable.set(true))) + Subscription sub = aeron1.addSubscription(channel, streamId)) { assertNotNull(aeron2); Tests.awaitConnected(pub); From 999f7a9bce20eee4c61d51e8338fa953086e6ae4 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Mon, 24 Jun 2024 14:31:26 +1200 Subject: [PATCH 30/67] [Java] Add additional parameters including source address of the error frame. --- .../PublicationErrorFrameFlyweight.java | 207 +++++++++++++++++- .../aeron/status/PublicationErrorFrame.java | 11 +- .../java/io/aeron/driver/ClientProxy.java | 18 +- .../java/io/aeron/driver/DriverConductor.java | 13 +- .../io/aeron/driver/DriverConductorProxy.java | 30 ++- .../io/aeron/driver/NetworkPublication.java | 10 +- .../java/io/aeron/ImageInvalidationTest.java | 88 ++++++-- 7 files changed, 342 insertions(+), 35 deletions(-) diff --git a/aeron-client/src/main/java/io/aeron/command/PublicationErrorFrameFlyweight.java b/aeron-client/src/main/java/io/aeron/command/PublicationErrorFrameFlyweight.java index 2ab72e9e27..5c29e3b3b5 100644 --- a/aeron-client/src/main/java/io/aeron/command/PublicationErrorFrameFlyweight.java +++ b/aeron-client/src/main/java/io/aeron/command/PublicationErrorFrameFlyweight.java @@ -19,6 +19,12 @@ import org.agrona.BitUtil; import org.agrona.MutableDirectBuffer; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; + /** * Control message flyweight error frames received by a publication to be reported to the client. *
@@ -28,20 +34,48 @@
  *  |                 Publication Registration Id                   |
  *  |                                                               |
  *  +---------------------------------------------------------------+
- *  |                         Error Code                            |
+ *  |                          Session ID                           |
+ *  +---------------------------------------------------------------+
+ *  |                           Stream ID                           |
  *  +---------------------------------------------------------------+
- *  |                   Error Message Length                        |
+ *  |                          Receiver ID                          |
+ *  |                                                               |
  *  +---------------------------------------------------------------+
- *  |                       Error Message                          ...
+ *  |                           Group Tag                           |
+ *  |                                                               |
+ *  +-------------------------------+-------------------------------+
+ *  |          Address Type         |            UDP Port           |
+ *  +-------------------------------+-------------------------------+
+ *  |           IPv4 or IPv6 Address padded out to 16 bytes         |
+ *  |                                                               |
+ *  |                                                               |
+ *  |                                                               |
+ *  +---------------------------------------------------------------+
+ *  |                          Error Code                           |
+ *  +---------------------------------------------------------------+
+ *  |                      Error Message Length                     |
+ *  +---------------------------------------------------------------+
+ *  |                         Error Message                        ...
  * ...                                                              |
  *  +---------------------------------------------------------------+
  * 
*/ public class PublicationErrorFrameFlyweight { - private static final int OFFENDING_COMMAND_CORRELATION_ID_OFFSET = 0; - private static final int ERROR_CODE_OFFSET = OFFENDING_COMMAND_CORRELATION_ID_OFFSET + BitUtil.SIZE_OF_LONG; + private static final int REGISTRATION_ID_OFFSET = 0; + private static final int IPV6_ADDRESS_LENGTH = 16; + private static final int IPV4_ADDRESS_LENGTH = BitUtil.SIZE_OF_INT; + private static final int SESSION_ID_OFFSET = REGISTRATION_ID_OFFSET + BitUtil.SIZE_OF_LONG; + private static final int STREAM_ID_OFFSET = SESSION_ID_OFFSET + BitUtil.SIZE_OF_INT; + private static final int RECEIVER_ID_OFFSET = STREAM_ID_OFFSET + BitUtil.SIZE_OF_INT; + private static final int GROUP_TAG_OFFSET = RECEIVER_ID_OFFSET + BitUtil.SIZE_OF_LONG; + private static final int ADDRESS_TYPE_OFFSET = GROUP_TAG_OFFSET + BitUtil.SIZE_OF_LONG; + private static final int ADDRESS_PORT_OFFSET = ADDRESS_TYPE_OFFSET + BitUtil.SIZE_OF_SHORT; + private static final int ADDRESS_OFFSET = ADDRESS_PORT_OFFSET + BitUtil.SIZE_OF_SHORT; + private static final int ERROR_CODE_OFFSET = ADDRESS_OFFSET + IPV6_ADDRESS_LENGTH; private static final int ERROR_MESSAGE_OFFSET = ERROR_CODE_OFFSET + BitUtil.SIZE_OF_INT; + private static final short ADDRESS_TYPE_IPV4 = 1; + private static final short ADDRESS_TYPE_IPV6 = 2; private MutableDirectBuffer buffer; private int offset; @@ -68,7 +102,7 @@ public final PublicationErrorFrameFlyweight wrap(final MutableDirectBuffer buffe */ public long registrationId() { - return buffer.getLong(offset + OFFENDING_COMMAND_CORRELATION_ID_OFFSET); + return buffer.getLong(offset + REGISTRATION_ID_OFFSET); } /** @@ -79,10 +113,169 @@ public long registrationId() */ public PublicationErrorFrameFlyweight registrationId(final long registrationId) { - buffer.putLong(offset + OFFENDING_COMMAND_CORRELATION_ID_OFFSET, registrationId); + buffer.putLong(offset + REGISTRATION_ID_OFFSET, registrationId); + return this; + } + + /** + * Get the stream id field. + * + * @return stream id field. + */ + public int streamId() + { + return buffer.getInt(offset + STREAM_ID_OFFSET); + } + + /** + * Set the stream id field. + * + * @param streamId field value. + * @return this for a fluent API. + */ + public PublicationErrorFrameFlyweight streamId(final int streamId) + { + buffer.putInt(offset + STREAM_ID_OFFSET, streamId); + + return this; + } + + /** + * Get the session id field. + * + * @return session id field. + */ + public int sessionId() + { + return buffer.getInt(offset + SESSION_ID_OFFSET); + } + + /** + * Set session id field. + * + * @param sessionId field value. + * @return this for a fluent API. + */ + public PublicationErrorFrameFlyweight sessionId(final int sessionId) + { + buffer.putInt(offset + SESSION_ID_OFFSET, sessionId); + + return this; + } + + /** + * Get the receiver id field + * + * @return get the receiver id field. + */ + public long receiverId() + { + return buffer.getLong(offset + RECEIVER_ID_OFFSET); + } + + /** + * Set receiver id field + * + * @param receiverId field value. + * @return this for a fluent API. + */ + public PublicationErrorFrameFlyweight receiverId(final long receiverId) + { + buffer.putLong(offset + RECEIVER_ID_OFFSET, receiverId); + + return this; + } + + /** + * Get the group tag field. + * + * @return the group tag field. + */ + public long groupTag() + { + return buffer.getLong(offset + GROUP_TAG_OFFSET); + } + + /** + * Set the group tag field. + * + * @param groupTag the group tag value. + * @return this for a fluent API. + */ + public PublicationErrorFrameFlyweight groupTag(final long groupTag) + { + buffer.putLong(offset + GROUP_TAG_OFFSET, groupTag); + + return this; + } + + /** + * Set the source address for the error frame. Store the type (IPv4 or IPv6), port and address as bytes. + * + * @param sourceAddress of the error frame. + * @return this for a fluent API + */ + public PublicationErrorFrameFlyweight sourceAddress(final InetSocketAddress sourceAddress) + { + final short sourcePort = (short)(sourceAddress.getPort() & 0xFFFF); + final InetAddress address = sourceAddress.getAddress(); + + buffer.putShort(offset + ADDRESS_PORT_OFFSET, sourcePort); + buffer.putBytes(offset + ADDRESS_OFFSET, address.getAddress()); + if (address instanceof Inet4Address) + { + buffer.putShort(offset + ADDRESS_TYPE_OFFSET, ADDRESS_TYPE_IPV4); + buffer.setMemory( + offset + ADDRESS_OFFSET + IPV4_ADDRESS_LENGTH, IPV6_ADDRESS_LENGTH - IPV4_ADDRESS_LENGTH, (byte)0); + } + else if (address instanceof Inet6Address) + { + buffer.putShort(offset + ADDRESS_TYPE_OFFSET, ADDRESS_TYPE_IPV6); + } + else + { + throw new IllegalArgumentException("Unknown address type:" + address.getClass().getSimpleName()); + } + return this; } + /** + * Get the source address of this error frame. + * + * @return source address of the error frame. + */ + public InetSocketAddress sourceAddress() + { + final short addressType = buffer.getShort(offset + ADDRESS_TYPE_OFFSET); + final int port = buffer.getShort(offset + ADDRESS_PORT_OFFSET) & 0xFFFF; + + final byte[] address; + if (ADDRESS_TYPE_IPV4 == addressType) + { + address = new byte[IPV4_ADDRESS_LENGTH]; + } + else if (ADDRESS_TYPE_IPV6 == addressType) + { + address = new byte[IPV6_ADDRESS_LENGTH]; + } + else + { + throw new IllegalArgumentException("Unknown address type:" + addressType); + } + + buffer.getBytes(offset + ADDRESS_OFFSET, address); + try + { + return new InetSocketAddress(Inet4Address.getByAddress(address), port); + } + catch (final UnknownHostException ex) + { + throw new IllegalArgumentException("Unknown address type:" + addressType, ex); + } + + } + /** * Error code for the command. * diff --git a/aeron-client/src/main/java/io/aeron/status/PublicationErrorFrame.java b/aeron-client/src/main/java/io/aeron/status/PublicationErrorFrame.java index 1cfaf53ea1..088a6da515 100644 --- a/aeron-client/src/main/java/io/aeron/status/PublicationErrorFrame.java +++ b/aeron-client/src/main/java/io/aeron/status/PublicationErrorFrame.java @@ -15,10 +15,10 @@ */ package io.aeron.status; -import io.aeron.CommonContext; import io.aeron.command.PublicationErrorFrameFlyweight; import java.net.InetAddress; +import java.net.InetSocketAddress; /** * Encapsulates the data received when a publication receives an error frame. @@ -32,7 +32,7 @@ public class PublicationErrorFrame implements Cloneable private Long groupTag; private int errorCode; private String errorMessage; - private InetAddress sourceAddress; + private InetSocketAddress sourceAddress; /** * Registration id of the publication that received the error frame. @@ -110,7 +110,7 @@ public String errorMessage() * * @return address of the remote source. */ - public InetAddress sourceAddress() + public InetSocketAddress sourceAddress() { return sourceAddress; } @@ -124,6 +124,11 @@ public InetAddress sourceAddress() public PublicationErrorFrame set(final PublicationErrorFrameFlyweight frameFlyweight) { registrationId = frameFlyweight.registrationId(); + sessionId = frameFlyweight.sessionId(); + streamId = frameFlyweight.streamId(); + receiverId = frameFlyweight.receiverId(); + groupTag = frameFlyweight.groupTag(); + sourceAddress = frameFlyweight.sourceAddress(); errorCode = frameFlyweight.errorCode().value(); errorMessage = frameFlyweight.errorMessage(); diff --git a/aeron-driver/src/main/java/io/aeron/driver/ClientProxy.java b/aeron-driver/src/main/java/io/aeron/driver/ClientProxy.java index 22ec910aa9..258912ac34 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/ClientProxy.java +++ b/aeron-driver/src/main/java/io/aeron/driver/ClientProxy.java @@ -15,6 +15,7 @@ */ package io.aeron.driver; +import io.aeron.Aeron; import io.aeron.ErrorCode; import io.aeron.command.*; import org.agrona.DirectBuffer; @@ -22,6 +23,8 @@ import org.agrona.MutableDirectBuffer; import org.agrona.concurrent.broadcast.BroadcastTransmitter; +import java.net.InetSocketAddress; + import static io.aeron.command.ControlProtocolEvents.*; /** @@ -69,10 +72,23 @@ void onError(final long correlationId, final ErrorCode errorCode, final String e transmit(ON_ERROR, buffer, 0, errorResponse.length()); } - void onPublicationErrorFrame(final long registrationId, final int errorCode, final String errorMessage) + void onPublicationErrorFrame( + final long registrationId, + final int sessionId, + final int streamId, + final long receiverId, + final Long groupTag, + final InetSocketAddress srcAddress, + final int errorCode, + final String errorMessage) { publicationErrorFrame .registrationId(registrationId) + .sessionId(sessionId) + .streamId(streamId) + .receiverId(receiverId) + .groupTag(null == groupTag ? Aeron.NULL_VALUE : groupTag) + .sourceAddress(srcAddress) .errorCode(ErrorCode.get(errorCode)) .errorMessage(errorMessage); diff --git a/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java b/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java index 94eaf301d5..ce180d3884 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java +++ b/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java @@ -387,10 +387,19 @@ void onChannelEndpointError(final long statusIndicatorId, final Exception ex) clientProxy.onError(statusIndicatorId, CHANNEL_ENDPOINT_ERROR, errorMessage); } - void onPublicationError(final long registrationId, final int errorCode, final String errorMessage) + void onPublicationError( + final long registrationId, + final int sessionId, + final int streamId, + final long receiverId, + final Long groupId, + final InetSocketAddress srcAddress, + final int errorCode, + final String errorMessage) { recordError(new AeronException(errorMessage, AeronException.Category.WARN)); - clientProxy.onPublicationErrorFrame(registrationId, errorCode, errorMessage); + clientProxy.onPublicationErrorFrame( + registrationId, sessionId, streamId, receiverId, groupId, srcAddress, errorCode, errorMessage); } void onReResolveEndpoint( diff --git a/aeron-driver/src/main/java/io/aeron/driver/DriverConductorProxy.java b/aeron-driver/src/main/java/io/aeron/driver/DriverConductorProxy.java index 0d558bcef2..132bc23193 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/DriverConductorProxy.java +++ b/aeron-driver/src/main/java/io/aeron/driver/DriverConductorProxy.java @@ -247,15 +247,39 @@ void createPublicationImage( } } - void onPublicationError(final long registrationId, final int errorCode, final String errorMessage) + void onPublicationError( + final long registrationId, + final int sessionId, + final int streamId, + final long receiverId, + final Long groupId, + final InetSocketAddress srcAddress, + final int errorCode, + final String errorMessage) { if (notConcurrent()) { - driverConductor.onPublicationError(registrationId, errorCode, errorMessage); + driverConductor.onPublicationError( + registrationId, + sessionId, + streamId, + receiverId, + groupId, + srcAddress, + errorCode, + errorMessage); } else { - offer(() -> driverConductor.onPublicationError(registrationId, errorCode, errorMessage)); + offer(() -> driverConductor.onPublicationError( + registrationId, + sessionId, + streamId, + receiverId, + groupId, + srcAddress, + errorCode, + errorMessage)); } } diff --git a/aeron-driver/src/main/java/io/aeron/driver/NetworkPublication.java b/aeron-driver/src/main/java/io/aeron/driver/NetworkPublication.java index adf6c30146..7454996886 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/NetworkPublication.java +++ b/aeron-driver/src/main/java/io/aeron/driver/NetworkPublication.java @@ -496,7 +496,15 @@ public void onError( flowControl.onError(msg, srcAddress, cachedNanoClock.nanoTime()); if (livenessTracker.onRemoteClose(msg.receiverId())) { - conductorProxy.onPublicationError(registrationId, msg.errorCode(), msg.errorMessage()); + conductorProxy.onPublicationError( + registrationId, + msg.sessionId(), + msg.streamId(), + msg.receiverId(), + msg.groupTag(), + srcAddress, + msg.errorCode(), + msg.errorMessage()); } } diff --git a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java index e13ea57ab4..1c5c743906 100644 --- a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java +++ b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java @@ -35,8 +35,11 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import java.io.IOException; +import java.net.InetSocketAddress; import java.util.Arrays; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -81,21 +84,7 @@ private TestMediaDriver launch() return driver; } - private static final class ErrorFrame - { - private final long registrationId; - private final int errorCode; - private final String errorText; - - private ErrorFrame(final long registrationId, final int errorCode, final String errorText) - { - this.registrationId = registrationId; - this.errorCode = errorCode; - this.errorText = errorText; - } - } - - private static final class QueuedErrorFrameHandler implements ErrorFrameListener + private static final class QueuedErrorFrameListener implements ErrorFrameListener { private final OneToOneConcurrentArrayQueue errorFrameQueue = new OneToOneConcurrentArrayQueue<>(512); @@ -119,7 +108,7 @@ void shouldInvalidateSubscriptionsImage() throws IOException context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3)); final TestMediaDriver driver = launch(); - final QueuedErrorFrameHandler errorFrameHandler = new QueuedErrorFrameHandler(); + final QueuedErrorFrameListener errorFrameHandler = new QueuedErrorFrameListener(); final Aeron.Context ctx = new Aeron.Context() .aeronDirectoryName(driver.aeronDirectoryName()) @@ -209,8 +198,8 @@ void shouldOnlyReceivePublicationErrorFrameOnRelevantClient() throws IOException context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3)); final TestMediaDriver driver = launch(); - final QueuedErrorFrameHandler errorFrameHandler1 = new QueuedErrorFrameHandler(); - final QueuedErrorFrameHandler errorFrameHandler2 = new QueuedErrorFrameHandler(); + final QueuedErrorFrameListener errorFrameHandler1 = new QueuedErrorFrameListener(); + final QueuedErrorFrameListener errorFrameHandler2 = new QueuedErrorFrameListener(); final Aeron.Context ctx1 = new Aeron.Context() .aeronDirectoryName(driver.aeronDirectoryName()) @@ -362,4 +351,67 @@ void shouldRejectInvalidationReasonThatIsTooLong() assertThrows(AeronException.class, () -> sub.imageAtIndex(0).invalidate(tooLongReason)); } } + + @ParameterizedTest + @ValueSource(strings = { "127.0.0.1", "[::1]" }) + @InterruptAfter(5) + void shouldReturnAllParametersToApi(final String addressStr) + { + TestMediaDriver.notSupportedOnCMediaDriver("Not yet implemented"); + + context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3)); + + final TestMediaDriver driver = launch(); + final QueuedErrorFrameListener errorFrameHandler = new QueuedErrorFrameListener(); + + final Aeron.Context ctx = new Aeron.Context() + .aeronDirectoryName(driver.aeronDirectoryName()) + .errorFrameHandler(errorFrameHandler); + + final long groupTag = 1001; + final int port = 10001; + + final String mdc = "aeron:udp?control-mode=dynamic|control=" + addressStr + ":10000|fc=tagged,g:" + groupTag; + final String channel = + "aeron:udp?control=" + addressStr + ":10000|endpoint=" + addressStr + ":" + port + "|gtag=" + groupTag; + + try (Aeron aeron = Aeron.connect(ctx); + Publication pub = aeron.addPublication(mdc, streamId); + Subscription sub = aeron.addSubscription(channel, streamId)) + { + Tests.awaitConnected(pub); + Tests.awaitConnected(sub); + + while (pub.offer(message) < 0) + { + Tests.yield(); + } + + while (0 == sub.poll((buffer, offset, length, header) -> {}, 1)) + { + Tests.yield(); + } + + final Image image = sub.imageAtIndex(0); + assertEquals(pub.position(), image.position()); + + final String reason = "Needs to be closed"; + image.invalidate(reason); + + PublicationErrorFrame errorFrame; + while (null == (errorFrame = errorFrameHandler.poll())) + { + Tests.yield(); + } + + assertEquals(reason, errorFrame.errorMessage()); + assertEquals(pub.registrationId(), errorFrame.registrationId()); + System.out.printf("pub.streamId()=%d errorFrame.streamId()=%d%n", pub.streamId(), errorFrame.streamId()); + assertEquals(pub.streamId(), errorFrame.streamId()); + assertEquals(pub.sessionId(), errorFrame.sessionId()); + assertEquals(groupTag, errorFrame.groupTag()); + assertNotNull(errorFrame.sourceAddress()); + assertEquals(new InetSocketAddress(addressStr, port), errorFrame.sourceAddress()); + } + } } From b5269ec7d03f8bb945c64a05093ba937d73ef16c Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Mon, 24 Jun 2024 16:43:51 +1200 Subject: [PATCH 31/67] [Java] Disable test for IPv6. --- .../src/main/java/io/aeron/status/PublicationErrorFrame.java | 1 - .../src/test/java/io/aeron/ImageInvalidationTest.java | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/aeron-client/src/main/java/io/aeron/status/PublicationErrorFrame.java b/aeron-client/src/main/java/io/aeron/status/PublicationErrorFrame.java index 088a6da515..fa01f6b0bb 100644 --- a/aeron-client/src/main/java/io/aeron/status/PublicationErrorFrame.java +++ b/aeron-client/src/main/java/io/aeron/status/PublicationErrorFrame.java @@ -17,7 +17,6 @@ import io.aeron.command.PublicationErrorFrameFlyweight; -import java.net.InetAddress; import java.net.InetSocketAddress; /** diff --git a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java index 1c5c743906..f5543585eb 100644 --- a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java +++ b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java @@ -55,6 +55,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assumptions.assumeTrue; @ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class }) public class ImageInvalidationTest @@ -357,7 +358,7 @@ void shouldRejectInvalidationReasonThatIsTooLong() @InterruptAfter(5) void shouldReturnAllParametersToApi(final String addressStr) { - TestMediaDriver.notSupportedOnCMediaDriver("Not yet implemented"); + assumeTrue(System.getProperty("java.net.preferIPv4Stack") == null); context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3)); @@ -406,7 +407,6 @@ void shouldReturnAllParametersToApi(final String addressStr) assertEquals(reason, errorFrame.errorMessage()); assertEquals(pub.registrationId(), errorFrame.registrationId()); - System.out.printf("pub.streamId()=%d errorFrame.streamId()=%d%n", pub.streamId(), errorFrame.streamId()); assertEquals(pub.streamId(), errorFrame.streamId()); assertEquals(pub.sessionId(), errorFrame.sessionId()); assertEquals(groupTag, errorFrame.groupTag()); From 0d38a86208ee056622eec1a2043217958807f00b Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Mon, 24 Jun 2024 17:45:48 +1200 Subject: [PATCH 32/67] [C] Add additional parameters to user visible error frame. --- .../main/c/command/aeron_control_protocol.h | 13 ++++++-- .../src/main/c/aeron_driver_conductor.c | 32 +++++++++++++++++-- .../src/main/c/aeron_driver_conductor_proxy.c | 11 +++++++ .../src/main/c/aeron_driver_conductor_proxy.h | 10 ++++++ .../src/main/c/aeron_network_publication.c | 15 +++++++-- .../src/main/c/aeron_network_publication.h | 2 +- 6 files changed, 74 insertions(+), 9 deletions(-) diff --git a/aeron-client/src/main/c/command/aeron_control_protocol.h b/aeron-client/src/main/c/command/aeron_control_protocol.h index 9cc6f7739d..236b2274c4 100644 --- a/aeron-client/src/main/c/command/aeron_control_protocol.h +++ b/aeron-client/src/main/c/command/aeron_control_protocol.h @@ -50,6 +50,8 @@ #define AERON_RESPONSE_ON_CLIENT_TIMEOUT (0x0F0A) #define AERON_RESPONSE_ON_STATIC_COUNTER (0x0F0B) #define AERON_RESPONSE_ON_PUBLICATION_ERROR (0x0F0C) +#define AERON_RESPONSE_ADDRESS_TYPE_IPV4 (0x1) +#define AERON_RESPONSE_ADDRESS_TYPE_IPV6 (0x2) /* error codes */ #define AERON_ERROR_CODE_UNKNOWN_CODE_VALUE (-1) @@ -220,14 +222,21 @@ typedef struct aeron_invalidate_image_command_stct } aeron_invalidate_image_command_t; -struct aeron_publication_error_response_stct +struct aeron_publication_error_stct { int64_t registration_id; + int32_t session_id; + int32_t stream_id; + int64_t receiver_id; + int64_t group_tag; + int16_t address_type; + uint16_t address_port; + uint8_t address[16]; int32_t error_code; int32_t error_message_length; uint8_t error_message[1]; }; -typedef struct aeron_publication_error_response_stct aeron_publication_error_response_t; +typedef struct aeron_publication_error_stct aeron_publication_error_t; #pragma pack(pop) diff --git a/aeron-driver/src/main/c/aeron_driver_conductor.c b/aeron-driver/src/main/c/aeron_driver_conductor.c index c926f54051..3a6d1af093 100644 --- a/aeron-driver/src/main/c/aeron_driver_conductor.c +++ b/aeron-driver/src/main/c/aeron_driver_conductor.c @@ -2570,17 +2570,43 @@ void on_error( void aeron_driver_conductor_on_publication_error(void *clientd, void *item) { - uint8_t buffer[sizeof(aeron_publication_error_response_t) + (AERON_ERROR_MAX_TEXT_LENGTH - 1)]; + uint8_t buffer[sizeof(aeron_publication_error_t) + (AERON_ERROR_MAX_TEXT_LENGTH - 1)]; aeron_driver_conductor_t *conductor = clientd; aeron_command_publication_error_t *error = item; aeron_driver_conductor_log_explicit_error(conductor, error->error_code, (const char *)error->error_text); - aeron_publication_error_response_t *response = (aeron_publication_error_response_t *)buffer; + aeron_publication_error_t *response = (aeron_publication_error_t *)buffer; response->error_code = error->error_code; response->registration_id = error->registration_id; + response->session_id = error->session_id; + response->stream_id = error->stream_id; + response->receiver_id = error->receiver_id; + response->group_tag = error->group_tag; + + memset(&response->address[0], 0, sizeof(response->address)); + if (AF_INET == error->src_address.ss_family) + { + struct sockaddr_in *src_addr_in = (struct sockaddr_in *)&error->src_address; + response->address_type = AERON_RESPONSE_ADDRESS_TYPE_IPV4; + response->address_port = be16toh(src_addr_in->sin_port); + memcpy(&response->address[0], &src_addr_in->sin_addr, sizeof(src_addr_in->sin_addr)); + } + else if (AF_INET6) + { + struct sockaddr_in6 *src_addr_in6 = (struct sockaddr_in6 *)&error->src_address; + response->address_type = AERON_RESPONSE_ADDRESS_TYPE_IPV6; + response->address_port = be16toh(src_addr_in6->sin6_port); + memcpy(&response->address[0], &src_addr_in6->sin6_addr, sizeof(src_addr_in6->sin6_addr)); + } + else + { + response->address_type = 0; + response->address_port = 0; + } + response->error_message_length = error->error_length; memcpy(response->error_message, error->error_text, error->error_length); - size_t response_length = offsetof(aeron_publication_error_response_t, error_message) + response->error_message_length; + size_t response_length = offsetof(aeron_publication_error_t, error_message) + response->error_message_length; aeron_driver_conductor_client_transmit(conductor, AERON_RESPONSE_ON_PUBLICATION_ERROR, response, response_length); } diff --git a/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c b/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c index 5a1c0bf949..387c5c64d6 100644 --- a/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c +++ b/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c @@ -265,6 +265,11 @@ void aeron_driver_conductor_proxy_on_release_resource( void aeron_driver_conductor_proxy_on_publication_error( aeron_driver_conductor_proxy_t *conductor_proxy, const int64_t registration_id, + int32_t session_id, + int32_t stream_id, + int64_t receiver_id, + int64_t group_tag, + struct sockaddr_storage *src_address, int32_t error_code, int32_t error_length, const uint8_t *error_text) @@ -276,6 +281,12 @@ void aeron_driver_conductor_proxy_on_publication_error( error->base.func = aeron_driver_conductor_on_publication_error; error->base.item = NULL; error->registration_id = registration_id; + error->session_id = session_id; + error->stream_id = stream_id; + error->receiver_id = receiver_id; + error->group_tag = group_tag; + memset(&error->src_address, 0, sizeof(struct sockaddr_storage)); + memcpy(&error->src_address, src_address, AERON_ADDR_LEN(src_address)); error->error_code = error_code; error->error_length = error_length; memcpy(error->error_text, error_text, (size_t)error_length); diff --git a/aeron-driver/src/main/c/aeron_driver_conductor_proxy.h b/aeron-driver/src/main/c/aeron_driver_conductor_proxy.h index 151b88834c..32381f5d54 100644 --- a/aeron-driver/src/main/c/aeron_driver_conductor_proxy.h +++ b/aeron-driver/src/main/c/aeron_driver_conductor_proxy.h @@ -104,6 +104,11 @@ struct aeron_command_publication_error_stct { aeron_command_base_t base; int64_t registration_id; + int32_t session_id; + int32_t stream_id; + int64_t receiver_id; + int64_t group_tag; + struct sockaddr_storage src_address; int32_t error_code; int32_t error_length; uint8_t error_text[1]; @@ -169,6 +174,11 @@ void aeron_driver_conductor_proxy_on_release_resource( void aeron_driver_conductor_proxy_on_publication_error( aeron_driver_conductor_proxy_t *conductor_proxy, const int64_t registration_id, + int32_t session_id, + int32_t stream_id, + int64_t receiver_id, + int64_t group_tag, + struct sockaddr_storage *src_address, int32_t error_code, int32_t error_length, const uint8_t *error_text); diff --git a/aeron-driver/src/main/c/aeron_network_publication.c b/aeron-driver/src/main/c/aeron_network_publication.c index 02282cb952..bb6edb7238 100644 --- a/aeron-driver/src/main/c/aeron_network_publication.c +++ b/aeron-driver/src/main/c/aeron_network_publication.c @@ -836,18 +836,27 @@ void aeron_network_publication_on_error( aeron_network_publication_t *publication, const uint8_t *buffer, size_t length, - struct sockaddr_storage *addr, + struct sockaddr_storage *src_address, aeron_driver_conductor_proxy_t *conductor_proxy) { aeron_error_t *error = (aeron_error_t *)buffer; const uint8_t *error_text = (const uint8_t *)(error + 1); const int64_t time_ns = aeron_clock_cached_nano_time(publication->cached_clock); - publication->flow_control->on_error(publication->flow_control->state, buffer, length, addr, time_ns); + publication->flow_control->on_error(publication->flow_control->state, buffer, length, src_address, time_ns); if (aeron_network_publication_liveness_on_remote_close(publication, error->receiver_id)) { const int64_t registration_id = aeron_network_publication_registration_id(publication); aeron_driver_conductor_proxy_on_publication_error( - conductor_proxy, registration_id, error->error_code, error->error_length, error_text); + conductor_proxy, + registration_id, + error->session_id, + error->stream_id, + error->receiver_id, + AERON_ERROR_HAS_GROUP_TAG_FLAG & error->frame_header.flags ? error->group_tag : AERON_NULL_VALUE, + src_address, + error->error_code, + error->error_length, + error_text); } } diff --git a/aeron-driver/src/main/c/aeron_network_publication.h b/aeron-driver/src/main/c/aeron_network_publication.h index 760bad246d..7db06356a3 100644 --- a/aeron-driver/src/main/c/aeron_network_publication.h +++ b/aeron-driver/src/main/c/aeron_network_publication.h @@ -185,7 +185,7 @@ void aeron_network_publication_on_error( aeron_network_publication_t *publication, const uint8_t *buffer, size_t length, - struct sockaddr_storage *addr, + struct sockaddr_storage *src_address, aeron_driver_conductor_proxy_t *pStct); void aeron_network_publication_on_rttm( From 0288b6aabc00ae9b937169f8e5dcd89c577548bb Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Mon, 24 Jun 2024 17:50:23 +1200 Subject: [PATCH 33/67] [C] Use ntohs instead of betoh16 (fix Mac). --- aeron-driver/src/main/c/aeron_driver_conductor.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aeron-driver/src/main/c/aeron_driver_conductor.c b/aeron-driver/src/main/c/aeron_driver_conductor.c index 3a6d1af093..9b92a6e6e4 100644 --- a/aeron-driver/src/main/c/aeron_driver_conductor.c +++ b/aeron-driver/src/main/c/aeron_driver_conductor.c @@ -2588,14 +2588,14 @@ void aeron_driver_conductor_on_publication_error(void *clientd, void *item) { struct sockaddr_in *src_addr_in = (struct sockaddr_in *)&error->src_address; response->address_type = AERON_RESPONSE_ADDRESS_TYPE_IPV4; - response->address_port = be16toh(src_addr_in->sin_port); + response->address_port = ntohs(src_addr_in->sin_port); memcpy(&response->address[0], &src_addr_in->sin_addr, sizeof(src_addr_in->sin_addr)); } else if (AF_INET6) { struct sockaddr_in6 *src_addr_in6 = (struct sockaddr_in6 *)&error->src_address; response->address_type = AERON_RESPONSE_ADDRESS_TYPE_IPV6; - response->address_port = be16toh(src_addr_in6->sin6_port); + response->address_port = ntohs(src_addr_in6->sin6_port); memcpy(&response->address[0], &src_addr_in6->sin6_addr, sizeof(src_addr_in6->sin6_addr)); } else From de21229e9bb8decc4b37ec91b90519f6b6841305 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Wed, 26 Jun 2024 09:38:26 +1200 Subject: [PATCH 34/67] [Java] Allow test to run always if the parameter is an IPv4 address. --- .../src/test/java/io/aeron/ImageInvalidationTest.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java index f5543585eb..d869726c3e 100644 --- a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java +++ b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java @@ -39,7 +39,10 @@ import org.junit.jupiter.params.provider.ValueSource; import java.io.IOException; +import java.net.Inet4Address; +import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.UnknownHostException; import java.util.Arrays; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -356,9 +359,10 @@ void shouldRejectInvalidationReasonThatIsTooLong() @ParameterizedTest @ValueSource(strings = { "127.0.0.1", "[::1]" }) @InterruptAfter(5) - void shouldReturnAllParametersToApi(final String addressStr) + void shouldReturnAllParametersToApi(final String addressStr) throws UnknownHostException { - assumeTrue(System.getProperty("java.net.preferIPv4Stack") == null); + final InetAddress address = InetAddress.getByName(addressStr); + assumeTrue(address instanceof Inet4Address || System.getProperty("java.net.preferIPv4Stack") == null); context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3)); From 6766a50893dc52921bf5bd1bb55b781a5058c109 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Thu, 27 Jun 2024 16:05:01 +1200 Subject: [PATCH 35/67] [Java] Naming and consistency updates. --- aeron-client/src/main/java/io/aeron/Aeron.java | 11 ++++++----- ...rFrameListener.java => ErrorFrameHandler.java} | 15 +++++++++------ .../driver/status/SystemCounterDescriptor.java | 2 +- .../test/java/io/aeron/ImageInvalidationTest.java | 10 +++++----- 4 files changed, 21 insertions(+), 17 deletions(-) rename aeron-client/src/main/java/io/aeron/{ErrorFrameListener.java => ErrorFrameHandler.java} (58%) diff --git a/aeron-client/src/main/java/io/aeron/Aeron.java b/aeron-client/src/main/java/io/aeron/Aeron.java index 7c9471dd41..df3f499732 100644 --- a/aeron-client/src/main/java/io/aeron/Aeron.java +++ b/aeron-client/src/main/java/io/aeron/Aeron.java @@ -942,7 +942,7 @@ public static class Context extends CommonContext private UnavailableImageHandler unavailableImageHandler; private AvailableCounterHandler availableCounterHandler; private UnavailableCounterHandler unavailableCounterHandler; - private ErrorFrameListener errorFrameHandler = ErrorFrameListener.NO_OP; + private ErrorFrameHandler errorFrameHandler = ErrorFrameHandler.NO_OP; private Runnable closeHandler; private long keepAliveIntervalNs = Configuration.KEEPALIVE_INTERVAL_NS; private long interServiceTimeoutNs = 0; @@ -1737,18 +1737,19 @@ public ThreadFactory threadFactory() * @param errorFrameHandler to be called back when an error frame is received. * @return this for a fluent API. */ - public Context errorFrameHandler(final ErrorFrameListener errorFrameHandler) + public Context errorFrameHandler(final ErrorFrameHandler errorFrameHandler) { this.errorFrameHandler = errorFrameHandler; return this; } /** - * Get the handler used to report error frame received by this driver. + * Get the handler to receive error frames that have been received by the local driver for resources owned by + * this client. * - * @return the {@link ErrorFrameListener} to call back on to. + * @return the {@link ErrorFrameHandler} to call back on to. */ - public ErrorFrameListener errorFrameHandler() + public ErrorFrameHandler errorFrameHandler() { return this.errorFrameHandler; } diff --git a/aeron-client/src/main/java/io/aeron/ErrorFrameListener.java b/aeron-client/src/main/java/io/aeron/ErrorFrameHandler.java similarity index 58% rename from aeron-client/src/main/java/io/aeron/ErrorFrameListener.java rename to aeron-client/src/main/java/io/aeron/ErrorFrameHandler.java index 0a888c2e47..5f5d49ac8c 100644 --- a/aeron-client/src/main/java/io/aeron/ErrorFrameListener.java +++ b/aeron-client/src/main/java/io/aeron/ErrorFrameHandler.java @@ -20,17 +20,20 @@ /** * Interface for handling various error frame messages from different components in the client. */ -public interface ErrorFrameListener +public interface ErrorFrameHandler { - ErrorFrameListener NO_OP = new ErrorFrameListener() + ErrorFrameHandler NO_OP = new ErrorFrameHandler() { }; /** - * Called when an error frame is received by the local driver. E.g. when an image is invalidated. This callback will - * reuse the {@link PublicationErrorFrame} instance, so data is only valid for the lifetime of the callback. If the - * user needs to pass the data onto another thread or hold in another location for use later, then the user needs to - * make use of the {@link PublicationErrorFrame#clone()} method to create a copy for their own use. + * Called when an error frame received by the local driver is propagated to the clients. E.g. when an image is + * invalidated. This callback will reuse the {@link PublicationErrorFrame} instance, so data is only valid for the + * lifetime of the callback. If the user needs to pass the data onto another thread or hold in another location for + * use later, then the user needs to make use of the {@link PublicationErrorFrame#clone()} method to create a copy + * for their own use. + *

+ * This callback will be executed on the client conductor thread, similar to image availability notifications. * * @param errorFrame contain the data from the error frame received by the publication. */ diff --git a/aeron-driver/src/main/java/io/aeron/driver/status/SystemCounterDescriptor.java b/aeron-driver/src/main/java/io/aeron/driver/status/SystemCounterDescriptor.java index 13dc53b6bc..5c4cf2644b 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/status/SystemCounterDescriptor.java +++ b/aeron-driver/src/main/java/io/aeron/driver/status/SystemCounterDescriptor.java @@ -224,7 +224,7 @@ public enum SystemCounterDescriptor RETRANSMIT_OVERFLOW(37, "Retransmit Pool Overflow count"), /** - * A count of the number of error messages received from a remote archive. + * A count of the number of error frames received by this driver. */ ERROR_FRAMES_RECEIVED(38, "Error Frames received"); diff --git a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java index d869726c3e..0439756674 100644 --- a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java +++ b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java @@ -88,7 +88,7 @@ private TestMediaDriver launch() return driver; } - private static final class QueuedErrorFrameListener implements ErrorFrameListener + private static final class QueuedErrorFrameHandler implements ErrorFrameHandler { private final OneToOneConcurrentArrayQueue errorFrameQueue = new OneToOneConcurrentArrayQueue<>(512); @@ -112,7 +112,7 @@ void shouldInvalidateSubscriptionsImage() throws IOException context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3)); final TestMediaDriver driver = launch(); - final QueuedErrorFrameListener errorFrameHandler = new QueuedErrorFrameListener(); + final QueuedErrorFrameHandler errorFrameHandler = new QueuedErrorFrameHandler(); final Aeron.Context ctx = new Aeron.Context() .aeronDirectoryName(driver.aeronDirectoryName()) @@ -202,8 +202,8 @@ void shouldOnlyReceivePublicationErrorFrameOnRelevantClient() throws IOException context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3)); final TestMediaDriver driver = launch(); - final QueuedErrorFrameListener errorFrameHandler1 = new QueuedErrorFrameListener(); - final QueuedErrorFrameListener errorFrameHandler2 = new QueuedErrorFrameListener(); + final QueuedErrorFrameHandler errorFrameHandler1 = new QueuedErrorFrameHandler(); + final QueuedErrorFrameHandler errorFrameHandler2 = new QueuedErrorFrameHandler(); final Aeron.Context ctx1 = new Aeron.Context() .aeronDirectoryName(driver.aeronDirectoryName()) @@ -367,7 +367,7 @@ void shouldReturnAllParametersToApi(final String addressStr) throws UnknownHostE context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3)); final TestMediaDriver driver = launch(); - final QueuedErrorFrameListener errorFrameHandler = new QueuedErrorFrameListener(); + final QueuedErrorFrameHandler errorFrameHandler = new QueuedErrorFrameHandler(); final Aeron.Context ctx = new Aeron.Context() .aeronDirectoryName(driver.aeronDirectoryName()) From 234071e0746a52f2d99986914a91cf5dab4ce254 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Thu, 27 Jun 2024 17:17:23 +1200 Subject: [PATCH 36/67] [C] Fix compile error. --- aeron-driver/src/main/c/aeron_system_counters.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aeron-driver/src/main/c/aeron_system_counters.c b/aeron-driver/src/main/c/aeron_system_counters.c index aae88db4a2..b71a434304 100644 --- a/aeron-driver/src/main/c/aeron_system_counters.c +++ b/aeron-driver/src/main/c/aeron_system_counters.c @@ -60,8 +60,8 @@ static aeron_system_counter_t system_counters[] = { "Aeron software: version=" AERON_VERSION_TXT " commit=" AERON_VERSION_GITSHA, AERON_SYSTEM_COUNTER_AERON_VERSION }, { "Bytes currently mapped", AERON_SYSTEM_COUNTER_BYTES_CURRENTLY_MAPPED }, { "Retransmitted bytes", AERON_SYSTEM_COUNTER_RETRANSMITTED_BYTES }, - { "Retransmit Pool Overflow count", AERON_SYSTEM_COUNTER_RETRANSMIT_OVERFLOW } - { "Error Frames received", AERON_SYSTEM_COUNTER_ERROR_FRAMES_RECEIVED }, + { "Retransmit Pool Overflow count", AERON_SYSTEM_COUNTER_RETRANSMIT_OVERFLOW }, + { "Error Frames received", AERON_SYSTEM_COUNTER_ERROR_FRAMES_RECEIVED } }; static size_t num_system_counters = sizeof(system_counters) / sizeof(aeron_system_counter_t); From f305c8275c69e87015a98c50134bc1c876eb0fd7 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Fri, 28 Jun 2024 09:36:37 +1200 Subject: [PATCH 37/67] [Java] Ensure that other clients with the same publication see the error frames. --- .../main/java/io/aeron/ClientConductor.java | 13 ++++- .../java/io/aeron/ImageInvalidationTest.java | 57 +++++++++++++++++++ 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/aeron-client/src/main/java/io/aeron/ClientConductor.java b/aeron-client/src/main/java/io/aeron/ClientConductor.java index 19768ac9dc..331dfe3ca4 100644 --- a/aeron-client/src/main/java/io/aeron/ClientConductor.java +++ b/aeron-client/src/main/java/io/aeron/ClientConductor.java @@ -274,10 +274,17 @@ else if (resource instanceof Publication) void onPublicationError(final PublicationErrorFrameFlyweight errorFrameFlyweight) { - if (resourceByRegIdMap.containsKey(errorFrameFlyweight.registrationId())) + for (final Object resource : resourceByRegIdMap.values()) { - publicationErrorFrame.set(errorFrameFlyweight); - ctx.errorFrameHandler().onPublicationError(publicationErrorFrame); + if (resource instanceof Publication) + { + final Publication publication = (Publication)resource; + if (publication.originalRegistrationId() == errorFrameFlyweight.registrationId()) + { + publicationErrorFrame.set(errorFrameFlyweight); + ctx.errorFrameHandler().onPublicationError(publicationErrorFrame); + } + } } } diff --git a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java index 0439756674..f8ab1ec543 100644 --- a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java +++ b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java @@ -250,6 +250,63 @@ void shouldOnlyReceivePublicationErrorFrameOnRelevantClient() throws IOException } } + + @Test + @InterruptAfter(10) + @SlowTest + void shouldReceivePublicationErrorFramesAllRelevantClients() throws IOException + { + context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3)); + + final TestMediaDriver driver = launch(); + final QueuedErrorFrameHandler errorFrameHandler1 = new QueuedErrorFrameHandler(); + final QueuedErrorFrameHandler errorFrameHandler2 = new QueuedErrorFrameHandler(); + + final Aeron.Context ctx1 = new Aeron.Context() + .aeronDirectoryName(driver.aeronDirectoryName()) + .errorFrameHandler(errorFrameHandler1); + + final Aeron.Context ctx2 = new Aeron.Context() + .aeronDirectoryName(driver.aeronDirectoryName()) + .errorFrameHandler(errorFrameHandler2); + + try (Aeron aeron1 = Aeron.connect(ctx1); + Aeron aeron2 = Aeron.connect(ctx2); + Publication pub = aeron1.addPublication(channel, streamId); + Publication pubOther = aeron2.addPublication(channel, streamId); + Subscription sub = aeron1.addSubscription(channel, streamId)) + { + assertNotNull(aeron2); + Tests.awaitConnected(pub); + Tests.awaitConnected(pubOther); + Tests.awaitConnected(sub); + + while (pub.offer(message) < 0) + { + Tests.yield(); + } + + while (0 == sub.poll((buffer, offset, length, header) -> {}, 1)) + { + Tests.yield(); + } + + final Image image = sub.imageAtIndex(0); + final String reason = "Needs to be closed"; + image.invalidate(reason); + + while (null == errorFrameHandler1.poll()) + { + Tests.yield(); + } + + while (null == errorFrameHandler2.poll()) + { + Tests.yield(); + } + } + } + @Test @InterruptAfter(10) @SlowTest From 7e6a7d203133b2276c87929c3532f5e267a5ed11 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Mon, 8 Jul 2024 13:36:09 +1200 Subject: [PATCH 38/67] [Java] Use "reject" instead of "invalidate" for marking images for errors. --- .../java/io/aeron/agent/CmdInterceptor.java | 6 ++--- .../java/io/aeron/agent/DriverEventCode.java | 2 +- .../io/aeron/agent/DriverEventDissector.java | 18 +++++++++++---- .../main/java/io/aeron/ClientConductor.java | 4 ++-- .../src/main/java/io/aeron/DriverProxy.java | 14 ++++++------ .../src/main/java/io/aeron/Image.java | 4 ++-- .../src/main/java/io/aeron/Subscription.java | 4 ++-- .../aeron/command/ControlProtocolEvents.java | 2 +- ...yweight.java => RejectImageFlyweight.java} | 14 ++++++------ .../src/test/java/io/aeron/ImageTest.java | 6 ++--- .../io/aeron/driver/ClientCommandAdapter.java | 22 +++++++++---------- .../java/io/aeron/driver/DriverConductor.java | 4 ++-- .../io/aeron/driver/PublicationImage.java | 12 +++++----- .../main/java/io/aeron/driver/Receiver.java | 4 ++-- .../java/io/aeron/driver/ReceiverProxy.java | 6 ++--- .../java/io/aeron/ImageInvalidationTest.java | 16 +++++++------- 16 files changed, 74 insertions(+), 64 deletions(-) rename aeron-client/src/main/java/io/aeron/command/{InvalidateImageFlyweight.java => RejectImageFlyweight.java} (92%) diff --git a/aeron-agent/src/main/java/io/aeron/agent/CmdInterceptor.java b/aeron-agent/src/main/java/io/aeron/agent/CmdInterceptor.java index ba651e9081..ee8f57c0bb 100644 --- a/aeron-agent/src/main/java/io/aeron/agent/CmdInterceptor.java +++ b/aeron-agent/src/main/java/io/aeron/agent/CmdInterceptor.java @@ -52,7 +52,7 @@ class CmdInterceptor CMD_OUT_ON_CLIENT_TIMEOUT, CMD_IN_TERMINATE_DRIVER, CMD_IN_REMOVE_DESTINATION_BY_ID, - CMD_IN_INVALIDATE_IMAGE); + CMD_IN_REJECT_IMAGE); @SuppressWarnings("methodlength") @Advice.OnMethodEnter @@ -160,8 +160,8 @@ static void logCmd(final int msgTypeId, final DirectBuffer buffer, final int ind LOGGER.log(CMD_IN_REMOVE_DESTINATION_BY_ID, buffer, index, length); break; - case INVALIDATE_IMAGE: - LOGGER.log(CMD_IN_INVALIDATE_IMAGE, buffer, index, length); + case REJECT_IMAGE: + LOGGER.log(CMD_IN_REJECT_IMAGE, buffer, index, length); break; } } diff --git a/aeron-agent/src/main/java/io/aeron/agent/DriverEventCode.java b/aeron-agent/src/main/java/io/aeron/agent/DriverEventCode.java index e52007339c..56d42dceee 100644 --- a/aeron-agent/src/main/java/io/aeron/agent/DriverEventCode.java +++ b/aeron-agent/src/main/java/io/aeron/agent/DriverEventCode.java @@ -217,7 +217,7 @@ public enum DriverEventCode implements EventCode */ CMD_IN_REMOVE_DESTINATION_BY_ID(56, DriverEventDissector::dissectCommand), - CMD_IN_INVALIDATE_IMAGE(56, DriverEventDissector::dissectCommand); + CMD_IN_REJECT_IMAGE(56, DriverEventDissector::dissectCommand); static final int EVENT_CODE_TYPE = EventCodeType.DRIVER.getTypeCode(); diff --git a/aeron-agent/src/main/java/io/aeron/agent/DriverEventDissector.java b/aeron-agent/src/main/java/io/aeron/agent/DriverEventDissector.java index 18779cdae0..3487dc9342 100644 --- a/aeron-agent/src/main/java/io/aeron/agent/DriverEventDissector.java +++ b/aeron-agent/src/main/java/io/aeron/agent/DriverEventDissector.java @@ -56,7 +56,7 @@ final class DriverEventDissector private static final ClientTimeoutFlyweight CLIENT_TIMEOUT = new ClientTimeoutFlyweight(); private static final TerminateDriverFlyweight TERMINATE_DRIVER = new TerminateDriverFlyweight(); private static final DestinationByIdMessageFlyweight DESTINATION_BY_ID = new DestinationByIdMessageFlyweight(); - private static final InvalidateImageFlyweight INVALIDATE_IMAGE = new InvalidateImageFlyweight(); + private static final RejectImageFlyweight REJECT_IMAGE = new RejectImageFlyweight(); static final String CONTEXT = "DRIVER"; @@ -221,9 +221,9 @@ static void dissectCommand( dissectDestinationById(builder); break; - case CMD_IN_INVALIDATE_IMAGE: - INVALIDATE_IMAGE.wrap(buffer, offset + encodedLength); - dissectInvalidateImage(builder); + case CMD_IN_REJECT_IMAGE: + REJECT_IMAGE.wrap(buffer, offset + encodedLength); + dissectRejectImage(builder); break; default: @@ -785,4 +785,14 @@ private static void dissectDestinationById(final StringBuilder builder) .append("resourceRegistrationId=").append(DESTINATION_BY_ID.resourceRegistrationId()) .append(" destinationRegistrationId=").append(DESTINATION_BY_ID.destinationRegistrationId()); } + + private static void dissectRejectImage(final StringBuilder builder) + { + builder + .append("clientId=").append(REJECT_IMAGE.clientId()) + .append(" correlationId=").append(REJECT_IMAGE.correlationId()) + .append(" imageCorrelationId=").append(REJECT_IMAGE.imageCorrelationId()) + .append(" position=").append(REJECT_IMAGE.position()) + .append(" reason=").append(REJECT_IMAGE.reason()); + } } diff --git a/aeron-client/src/main/java/io/aeron/ClientConductor.java b/aeron-client/src/main/java/io/aeron/ClientConductor.java index 331dfe3ca4..3dfad417b6 100644 --- a/aeron-client/src/main/java/io/aeron/ClientConductor.java +++ b/aeron-client/src/main/java/io/aeron/ClientConductor.java @@ -1439,7 +1439,7 @@ void onStaticCounter(final long correlationId, final int counterId) resourceByRegIdMap.put(correlationId, (Integer)counterId); } - void invalidateImage(final long correlationId, final long position, final String reason) + void rejectImage(final long correlationId, final long position, final String reason) { clientLock.lock(); try @@ -1449,7 +1449,7 @@ void invalidateImage(final long correlationId, final long position, final String // TODO, check reason length?? - final long registrationId = driverProxy.invalidateImage(correlationId, position, reason); + final long registrationId = driverProxy.rejectImage(correlationId, position, reason); awaitResponse(registrationId); } finally diff --git a/aeron-client/src/main/java/io/aeron/DriverProxy.java b/aeron-client/src/main/java/io/aeron/DriverProxy.java index adc19e8e91..d4f8358745 100644 --- a/aeron-client/src/main/java/io/aeron/DriverProxy.java +++ b/aeron-client/src/main/java/io/aeron/DriverProxy.java @@ -39,7 +39,7 @@ public final class DriverProxy private final DestinationByIdMessageFlyweight destinationByIdMessage = new DestinationByIdMessageFlyweight(); private final CounterMessageFlyweight counterMessage = new CounterMessageFlyweight(); private final StaticCounterMessageFlyweight staticCounterMessageFlyweight = new StaticCounterMessageFlyweight(); - private final InvalidateImageFlyweight invalidateImage = new InvalidateImageFlyweight(); + private final RejectImageFlyweight rejectImage = new RejectImageFlyweight(); private final RingBuffer toDriverCommandBuffer; /** @@ -493,29 +493,29 @@ public boolean terminateDriver(final DirectBuffer tokenBuffer, final int tokenOf } /** - * Invalidate a specific image. + * Reject a specific image. * * @param imageCorrelationId of the image to be invalidated * @param position of the image when invalidation occurred * @param reason user supplied reason for invalidation, reported back to publication * @return the correlationId of the request for invalidation. */ - public long invalidateImage( + public long rejectImage( final long imageCorrelationId, final long position, final String reason) { - final int length = InvalidateImageFlyweight.computeLength(reason); - final int index = toDriverCommandBuffer.tryClaim(INVALIDATE_IMAGE, length); + final int length = RejectImageFlyweight.computeLength(reason); + final int index = toDriverCommandBuffer.tryClaim(REJECT_IMAGE, length); if (index < 0) { - throw new AeronException("could not write invalidate image command"); + throw new AeronException("could not write reject image command"); } final long correlationId = toDriverCommandBuffer.nextCorrelationId(); - invalidateImage + rejectImage .wrap(toDriverCommandBuffer.buffer(), index) .clientId(clientId) .correlationId(correlationId) diff --git a/aeron-client/src/main/java/io/aeron/Image.java b/aeron-client/src/main/java/io/aeron/Image.java index 2909b637c3..14366f3fe0 100644 --- a/aeron-client/src/main/java/io/aeron/Image.java +++ b/aeron-client/src/main/java/io/aeron/Image.java @@ -793,9 +793,9 @@ public int rawPoll(final RawBlockHandler handler, final int blockLengthLimit) * * @param reason an error message to be forwarded back to the publication. */ - public void invalidate(final String reason) + public void reject(final String reason) { - subscription.invalidate(correlationId, position(), reason); + subscription.rejectImage(correlationId, position(), reason); } private UnsafeBuffer activeTermBuffer(final long position) diff --git a/aeron-client/src/main/java/io/aeron/Subscription.java b/aeron-client/src/main/java/io/aeron/Subscription.java index e6c6f73385..80f8849b3b 100644 --- a/aeron-client/src/main/java/io/aeron/Subscription.java +++ b/aeron-client/src/main/java/io/aeron/Subscription.java @@ -620,9 +620,9 @@ Image removeImage(final long correlationId) return removedImage; } - void invalidate(final long correlationId, final long position, final String reason) + void rejectImage(final long correlationId, final long position, final String reason) { - conductor.invalidateImage(correlationId, position, reason); + conductor.rejectImage(correlationId, position, reason); } /** diff --git a/aeron-client/src/main/java/io/aeron/command/ControlProtocolEvents.java b/aeron-client/src/main/java/io/aeron/command/ControlProtocolEvents.java index 6d64a0b4ff..f06b06a01e 100644 --- a/aeron-client/src/main/java/io/aeron/command/ControlProtocolEvents.java +++ b/aeron-client/src/main/java/io/aeron/command/ControlProtocolEvents.java @@ -103,7 +103,7 @@ public class ControlProtocolEvents /** * Invalidate an image. */ - public static final int INVALIDATE_IMAGE = 0x10; + public static final int REJECT_IMAGE = 0x10; /** * Remove a destination by registration id. diff --git a/aeron-client/src/main/java/io/aeron/command/InvalidateImageFlyweight.java b/aeron-client/src/main/java/io/aeron/command/RejectImageFlyweight.java similarity index 92% rename from aeron-client/src/main/java/io/aeron/command/InvalidateImageFlyweight.java rename to aeron-client/src/main/java/io/aeron/command/RejectImageFlyweight.java index 356e8077b2..8af72f14a3 100644 --- a/aeron-client/src/main/java/io/aeron/command/InvalidateImageFlyweight.java +++ b/aeron-client/src/main/java/io/aeron/command/RejectImageFlyweight.java @@ -48,7 +48,7 @@ * +---------------------------------------------------------------+ * */ -public class InvalidateImageFlyweight extends CorrelatedMessageFlyweight +public class RejectImageFlyweight extends CorrelatedMessageFlyweight { private static final int IMAGE_CORRELATION_ID_FIELD_OFFSET = CORRELATION_ID_FIELD_OFFSET + SIZE_OF_LONG; private static final int POSITION_FIELD_OFFSET = IMAGE_CORRELATION_ID_FIELD_OFFSET + SIZE_OF_LONG; @@ -62,7 +62,7 @@ public class InvalidateImageFlyweight extends CorrelatedMessageFlyweight * @param offset at which the message begins. * @return this for a fluent API. */ - public InvalidateImageFlyweight wrap(final MutableDirectBuffer buffer, final int offset) + public RejectImageFlyweight wrap(final MutableDirectBuffer buffer, final int offset) { super.wrap(buffer, offset); return this; @@ -84,7 +84,7 @@ public long imageCorrelationId() * @param position new image correlation id value. * @return this for a fluent API. */ - public InvalidateImageFlyweight imageCorrelationId(final long position) + public RejectImageFlyweight imageCorrelationId(final long position) { buffer.putLong(offset + IMAGE_CORRELATION_ID_FIELD_OFFSET, position); return this; @@ -106,7 +106,7 @@ public long position() * @param position new position value. * @return this for a fluent API. */ - public InvalidateImageFlyweight position(final long position) + public RejectImageFlyweight position(final long position) { buffer.putLong(offset + POSITION_FIELD_OFFSET, position); return this; @@ -118,7 +118,7 @@ public InvalidateImageFlyweight position(final long position) * @param reason for invalidating the image. * @return this for a fluent API. */ - public InvalidateImageFlyweight reason(final String reason) + public RejectImageFlyweight reason(final String reason) { buffer.putStringAscii(offset + REASON_FIELD_OFFSET, reason); return this; @@ -149,7 +149,7 @@ public int reasonBufferLength() /** * {@inheritDoc} */ - public InvalidateImageFlyweight clientId(final long clientId) + public RejectImageFlyweight clientId(final long clientId) { super.clientId(clientId); return this; @@ -158,7 +158,7 @@ public InvalidateImageFlyweight clientId(final long clientId) /** * {@inheritDoc} */ - public InvalidateImageFlyweight correlationId(final long correlationId) + public RejectImageFlyweight correlationId(final long correlationId) { super.correlationId(correlationId); return this; diff --git a/aeron-client/src/test/java/io/aeron/ImageTest.java b/aeron-client/src/test/java/io/aeron/ImageTest.java index 96b7e7764a..295ff73583 100644 --- a/aeron-client/src/test/java/io/aeron/ImageTest.java +++ b/aeron-client/src/test/java/io/aeron/ImageTest.java @@ -611,7 +611,7 @@ void shouldPollFragmentsToBoundedFragmentHandlerWithMaxPositionAboveIntMaxValue( } @Test - void shouldInvalidateFragment() + void shouldRejectFragment() { final int initialOffset = TERM_BUFFER_LENGTH - (ALIGNED_FRAME_LENGTH * 2); final long initialPosition = computePosition( @@ -625,9 +625,9 @@ void shouldInvalidateFragment() assertEquals(initialPosition, image.position()); final String reason = "this is garbage"; - image.invalidate(reason); + image.reject(reason); - verify(subscription).invalidate(image.correlationId(), image.position(), reason); + verify(subscription).rejectImage(image.correlationId(), image.position(), reason); // final int fragmentsRead = image.boundedPoll( // mockFragmentHandler, maxPosition, Integer.MAX_VALUE); diff --git a/aeron-driver/src/main/java/io/aeron/driver/ClientCommandAdapter.java b/aeron-driver/src/main/java/io/aeron/driver/ClientCommandAdapter.java index e1fdb9ff3e..46d14a0e87 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/ClientCommandAdapter.java +++ b/aeron-driver/src/main/java/io/aeron/driver/ClientCommandAdapter.java @@ -44,7 +44,7 @@ final class ClientCommandAdapter implements ControlledMessageHandler private final CounterMessageFlyweight counterMsgFlyweight = new CounterMessageFlyweight(); private final StaticCounterMessageFlyweight staticCounterMessageFlyweight = new StaticCounterMessageFlyweight(); private final TerminateDriverFlyweight terminateDriverFlyweight = new TerminateDriverFlyweight(); - private final InvalidateImageFlyweight invalidateImageFlyweight = new InvalidateImageFlyweight(); + private final RejectImageFlyweight rejectImageFlyweight = new RejectImageFlyweight(); private final DestinationByIdMessageFlyweight destinationByIdMessageFlyweight = new DestinationByIdMessageFlyweight(); private final DriverConductor conductor; @@ -288,17 +288,17 @@ else if (channel.startsWith(SPY_QUALIFIER)) break; } - case INVALIDATE_IMAGE: + case REJECT_IMAGE: { - invalidateImageFlyweight.wrap(buffer, index); - invalidateImageFlyweight.validateLength(msgTypeId, length); - correlationId = invalidateImageFlyweight.correlationId(); - - conductor.onInvalidateImage( - invalidateImageFlyweight.correlationId(), - invalidateImageFlyweight.imageCorrelationId(), - invalidateImageFlyweight.position(), - invalidateImageFlyweight.reason()); + rejectImageFlyweight.wrap(buffer, index); + rejectImageFlyweight.validateLength(msgTypeId, length); + correlationId = rejectImageFlyweight.correlationId(); + + conductor.onRejectImage( + rejectImageFlyweight.correlationId(), + rejectImageFlyweight.imageCorrelationId(), + rejectImageFlyweight.position(), + rejectImageFlyweight.reason()); break; } diff --git a/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java b/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java index ce180d3884..de0f96153e 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java +++ b/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java @@ -1485,7 +1485,7 @@ void onTerminateDriver(final DirectBuffer tokenBuffer, final int tokenOffset, fi } } - void onInvalidateImage( + void onRejectImage( final long correlationId, final long imageCorrelationId, final long position, @@ -1504,7 +1504,7 @@ void onInvalidateImage( GENERIC_ERROR, "Unable to resolve image for correlationId=" + imageCorrelationId); } - receiverProxy.invalidateImage(imageCorrelationId, position, reason); + receiverProxy.rejectImage(imageCorrelationId, position, reason); clientProxy.operationSucceeded(correlationId); } diff --git a/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java b/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java index a4511cbe19..6a71b39f95 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java +++ b/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java @@ -88,7 +88,7 @@ class PublicationImageReceiverFields extends PublicationImagePadding2 boolean isSendingEosSm = false; long timeOfLastPacketNs; ImageConnection[] imageConnections = new ImageConnection[1]; - String invalidationReason = null; + String rejectionReason = null; } class PublicationImagePadding3 extends PublicationImageReceiverFields @@ -590,7 +590,7 @@ int insertPacket( { final boolean isEndOfStream = DataHeaderFlyweight.isEndOfStream(buffer); - if (null != invalidationReason) + if (null != rejectionReason) { if (isEndOfStream) { @@ -707,12 +707,12 @@ int sendPendingStatusMessage(final long nowNs) final long changeNumber = endSmChange; final boolean hasSmTimedOut = (timeOfLastSmNs + smTimeoutNs) - nowNs < 0; - if (null != invalidationReason) + if (null != rejectionReason) { if (hasSmTimedOut) { channelEndpoint.sendErrorFrame( - imageConnections, sessionId, streamId, GENERIC_ERROR.value(), invalidationReason); + imageConnections, sessionId, streamId, GENERIC_ERROR.value(), rejectionReason); timeOfLastSmNs = nowNs; workCount++; @@ -918,9 +918,9 @@ public boolean hasReachedEndOfLife() return hasReceiverReleased && State.DONE == state; } - void invalidate(final String reason) + void reject(final String reason) { - invalidationReason = reason; + rejectionReason = reason; } private void state(final State state) diff --git a/aeron-driver/src/main/java/io/aeron/driver/Receiver.java b/aeron-driver/src/main/java/io/aeron/driver/Receiver.java index 831641623b..d4901b90e8 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/Receiver.java +++ b/aeron-driver/src/main/java/io/aeron/driver/Receiver.java @@ -334,13 +334,13 @@ void onResolutionChange( channelEndpoint.updateControlAddress(transportIndex, newAddress); } - void onInvalidateImage(final long imageCorrelationId, final long position, final String reason) + void onRejectImage(final long imageCorrelationId, final long position, final String reason) { for (final PublicationImage image : publicationImages) { if (imageCorrelationId == image.correlationId()) { - image.invalidate(reason); + image.reject(reason); break; } } diff --git a/aeron-driver/src/main/java/io/aeron/driver/ReceiverProxy.java b/aeron-driver/src/main/java/io/aeron/driver/ReceiverProxy.java index 58aed30ecf..486ada901c 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/ReceiverProxy.java +++ b/aeron-driver/src/main/java/io/aeron/driver/ReceiverProxy.java @@ -196,15 +196,15 @@ void requestSetup( } } - void invalidateImage(final long imageCorrelationId, final long position, final String reason) + void rejectImage(final long imageCorrelationId, final long position, final String reason) { if (notConcurrent()) { - receiver.onInvalidateImage(imageCorrelationId, position, reason); + receiver.onRejectImage(imageCorrelationId, position, reason); } else { - offer(() -> receiver.onInvalidateImage(imageCorrelationId, position, reason)); + offer(() -> receiver.onRejectImage(imageCorrelationId, position, reason)); } } } diff --git a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java index f8ab1ec543..6a38d660dd 100644 --- a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java +++ b/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java @@ -107,7 +107,7 @@ PublicationErrorFrame poll() @Test @InterruptAfter(10) @SlowTest - void shouldInvalidateSubscriptionsImage() throws IOException + void shouldRejectSubscriptionsImage() throws IOException { context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3)); @@ -147,7 +147,7 @@ void shouldInvalidateSubscriptionsImage() throws IOException assertEquals(pub.position(), image.position()); final String reason = "Needs to be closed"; - image.invalidate(reason); + image.reject(reason); final long t0 = System.nanoTime(); while (pub.isConnected()) @@ -234,7 +234,7 @@ void shouldOnlyReceivePublicationErrorFrameOnRelevantClient() throws IOException final Image image = sub.imageAtIndex(0); final String reason = "Needs to be closed"; - image.invalidate(reason); + image.reject(reason); while (null == errorFrameHandler1.poll()) { @@ -293,7 +293,7 @@ void shouldReceivePublicationErrorFramesAllRelevantClients() throws IOException final Image image = sub.imageAtIndex(0); final String reason = "Needs to be closed"; - image.invalidate(reason); + image.reject(reason); while (null == errorFrameHandler1.poll()) { @@ -310,7 +310,7 @@ void shouldReceivePublicationErrorFramesAllRelevantClients() throws IOException @Test @InterruptAfter(10) @SlowTest - void shouldInvalidateSubscriptionsImageManualMdc() + void shouldRejectSubscriptionsImageManualMdc() { context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3)); @@ -355,7 +355,7 @@ void shouldInvalidateSubscriptionsImageManualMdc() final int initialAvailable = imageAvailable.get(); final String reason = "Needs to be closed"; - image.invalidate(reason); + image.reject(reason); final long t0 = System.nanoTime(); while (pub.isConnected()) @@ -409,7 +409,7 @@ void shouldRejectInvalidationReasonThatIsTooLong() Tests.awaitConnected(pub); Tests.awaitConnected(sub); - assertThrows(AeronException.class, () -> sub.imageAtIndex(0).invalidate(tooLongReason)); + assertThrows(AeronException.class, () -> sub.imageAtIndex(0).reject(tooLongReason)); } } @@ -458,7 +458,7 @@ void shouldReturnAllParametersToApi(final String addressStr) throws UnknownHostE assertEquals(pub.position(), image.position()); final String reason = "Needs to be closed"; - image.invalidate(reason); + image.reject(reason); PublicationErrorFrame errorFrame; while (null == (errorFrame = errorFrameHandler.poll())) From bb665c6811477a2919f2780662d0f9417a5dfd8c Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Mon, 8 Jul 2024 13:41:31 +1200 Subject: [PATCH 39/67] [Java] Javadoc. --- .../src/main/java/io/aeron/protocol/ErrorFlyweight.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/aeron-client/src/main/java/io/aeron/protocol/ErrorFlyweight.java b/aeron-client/src/main/java/io/aeron/protocol/ErrorFlyweight.java index efb30c2a68..6940adaf6c 100644 --- a/aeron-client/src/main/java/io/aeron/protocol/ErrorFlyweight.java +++ b/aeron-client/src/main/java/io/aeron/protocol/ErrorFlyweight.java @@ -95,6 +95,9 @@ public class ErrorFlyweight extends HeaderFlyweight */ public static final int MAX_ERROR_MESSAGE_LENGTH = 1023; + /** + * Maximum length of an error frame. Captures the maximum message length and the header length. + */ public static final int MAX_ERROR_FRAME_LENGTH = HEADER_LENGTH + MAX_ERROR_MESSAGE_LENGTH; /** From 2ba58e94fbdeee8b33e14026a50cdabdf1ff8fc0 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Tue, 23 Jul 2024 12:02:14 +1200 Subject: [PATCH 40/67] [Java] Remove last utf-8 encoding for error frames. --- .../src/main/java/io/aeron/protocol/ErrorFlyweight.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aeron-client/src/main/java/io/aeron/protocol/ErrorFlyweight.java b/aeron-client/src/main/java/io/aeron/protocol/ErrorFlyweight.java index 6940adaf6c..262c19e024 100644 --- a/aeron-client/src/main/java/io/aeron/protocol/ErrorFlyweight.java +++ b/aeron-client/src/main/java/io/aeron/protocol/ErrorFlyweight.java @@ -285,7 +285,7 @@ public String errorMessage() */ public ErrorFlyweight errorMessage(final String errorMessage) { - final int headerAndMessageLength = putStringUtf8(ERROR_STRING_FIELD_OFFSET, errorMessage, LITTLE_ENDIAN); + final int headerAndMessageLength = putStringAscii(ERROR_STRING_FIELD_OFFSET, errorMessage, LITTLE_ENDIAN); frameLength(HEADER_LENGTH + (headerAndMessageLength - STR_HEADER_LEN)); return this; } From ae23de5d1d119cd2dd3580135136e6c3b87fa3e0 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Tue, 23 Jul 2024 14:27:46 +1200 Subject: [PATCH 41/67] [Java] Add error frames sent counter. --- .../src/main/java/io/aeron/protocol/ErrorFlyweight.java | 2 +- .../io/aeron/driver/media/ReceiveChannelEndpoint.java | 5 +++++ .../io/aeron/driver/status/SystemCounterDescriptor.java | 7 ++++++- .../{ImageInvalidationTest.java => RejectImageTest.java} | 9 ++++++--- 4 files changed, 18 insertions(+), 5 deletions(-) rename aeron-system-tests/src/test/java/io/aeron/{ImageInvalidationTest.java => RejectImageTest.java} (97%) diff --git a/aeron-client/src/main/java/io/aeron/protocol/ErrorFlyweight.java b/aeron-client/src/main/java/io/aeron/protocol/ErrorFlyweight.java index 262c19e024..40ef4c50e1 100644 --- a/aeron-client/src/main/java/io/aeron/protocol/ErrorFlyweight.java +++ b/aeron-client/src/main/java/io/aeron/protocol/ErrorFlyweight.java @@ -274,7 +274,7 @@ public ErrorFlyweight errorCode(final int errorCode) */ public String errorMessage() { - return getStringUtf8(ERROR_STRING_FIELD_OFFSET); + return getStringAscii(ERROR_STRING_FIELD_OFFSET); } /** diff --git a/aeron-driver/src/main/java/io/aeron/driver/media/ReceiveChannelEndpoint.java b/aeron-driver/src/main/java/io/aeron/driver/media/ReceiveChannelEndpoint.java index 3086964eb8..5e991c9da0 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/media/ReceiveChannelEndpoint.java +++ b/aeron-driver/src/main/java/io/aeron/driver/media/ReceiveChannelEndpoint.java @@ -20,6 +20,7 @@ import io.aeron.driver.DataPacketDispatcher; import io.aeron.driver.DriverConductorProxy; import io.aeron.driver.MediaDriver; +import io.aeron.driver.status.SystemCounterDescriptor; import io.aeron.exceptions.AeronException; import io.aeron.exceptions.ControlProtocolException; import io.aeron.protocol.*; @@ -66,6 +67,7 @@ abstract class ReceiveChannelEndpointLhsPadding extends UdpChannelTransport abstract class ReceiveChannelEndpointHotFields extends ReceiveChannelEndpointLhsPadding { + protected final AtomicCounter errorFramesSent; long timeOfLastActivityNs; ReceiveChannelEndpointHotFields( @@ -76,6 +78,7 @@ abstract class ReceiveChannelEndpointHotFields extends ReceiveChannelEndpointLhs final MediaDriver.Context context) { super(udpChannel, endPointAddress, bindAddress, connectAddress, context); + errorFramesSent = context.systemCounters().get(SystemCounterDescriptor.ERROR_FRAMES_SENT); } } @@ -958,6 +961,8 @@ public void sendErrorFrame( final int errorCode, final String errorMessage) { + errorFramesSent.increment(); + errorBuffer.clear(); errorFlyweight .sessionId(sessionId) diff --git a/aeron-driver/src/main/java/io/aeron/driver/status/SystemCounterDescriptor.java b/aeron-driver/src/main/java/io/aeron/driver/status/SystemCounterDescriptor.java index 5c4cf2644b..c4b6ee552d 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/status/SystemCounterDescriptor.java +++ b/aeron-driver/src/main/java/io/aeron/driver/status/SystemCounterDescriptor.java @@ -226,7 +226,12 @@ public enum SystemCounterDescriptor /** * A count of the number of error frames received by this driver. */ - ERROR_FRAMES_RECEIVED(38, "Error Frames received"); + ERROR_FRAMES_RECEIVED(38, "Error Frames received"), + + /** + * A count of the number of error frames sent by this driver. + */ + ERROR_FRAMES_SENT(39, "Error Frames sent"); /** * All system counters have the same type id, i.e. system counters are the same type. Other types can exist. diff --git a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java b/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java similarity index 97% rename from aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java rename to aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java index 6a38d660dd..1240da6646 100644 --- a/aeron-system-tests/src/test/java/io/aeron/ImageInvalidationTest.java +++ b/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java @@ -50,6 +50,7 @@ import static io.aeron.driver.status.SystemCounterDescriptor.ERRORS; import static io.aeron.driver.status.SystemCounterDescriptor.ERROR_FRAMES_RECEIVED; +import static io.aeron.driver.status.SystemCounterDescriptor.ERROR_FRAMES_SENT; import static java.nio.charset.StandardCharsets.US_ASCII; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; @@ -61,7 +62,7 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue; @ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class }) -public class ImageInvalidationTest +public class RejectImageTest { public static final long A_VALUE_THAT_SHOWS_WE_ARENT_SPAMMING_ERROR_MESSAGES = 1000L; @RegisterExtension @@ -130,6 +131,8 @@ void shouldRejectSubscriptionsImage() throws IOException final CountersReader countersReader = aeron.countersReader(); final long initialErrorFramesReceived = countersReader .getCounterValue(ERROR_FRAMES_RECEIVED.id()); + final long initialErrorFramesSent = countersReader + .getCounterValue(ERROR_FRAMES_SENT.id()); final long initialErrors = countersReader .getCounterValue(ERRORS.id()); @@ -158,8 +161,8 @@ void shouldRejectSubscriptionsImage() throws IOException final long value = driver.context().publicationConnectionTimeoutNs(); assertThat(t1 - t0, lessThan(value)); - while (initialErrorFramesReceived == countersReader - .getCounterValue(ERROR_FRAMES_RECEIVED.id())) + while (initialErrorFramesReceived == countersReader.getCounterValue(ERROR_FRAMES_RECEIVED.id()) || + initialErrorFramesSent == countersReader.getCounterValue(ERROR_FRAMES_SENT.id())) { Tests.yield(); } From 1079d47b9574989e409c1046ffe84e66774988a5 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Tue, 23 Jul 2024 16:01:04 +1200 Subject: [PATCH 42/67] [C] Add errors frames sent counter. --- aeron-driver/src/main/c/aeron_system_counters.c | 3 ++- aeron-driver/src/main/c/aeron_system_counters.h | 1 + .../src/main/c/media/aeron_receive_channel_endpoint.c | 6 ++++++ .../src/main/c/media/aeron_receive_channel_endpoint.h | 1 + 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/aeron-driver/src/main/c/aeron_system_counters.c b/aeron-driver/src/main/c/aeron_system_counters.c index b71a434304..261ae536fc 100644 --- a/aeron-driver/src/main/c/aeron_system_counters.c +++ b/aeron-driver/src/main/c/aeron_system_counters.c @@ -61,7 +61,8 @@ static aeron_system_counter_t system_counters[] = { "Bytes currently mapped", AERON_SYSTEM_COUNTER_BYTES_CURRENTLY_MAPPED }, { "Retransmitted bytes", AERON_SYSTEM_COUNTER_RETRANSMITTED_BYTES }, { "Retransmit Pool Overflow count", AERON_SYSTEM_COUNTER_RETRANSMIT_OVERFLOW }, - { "Error Frames received", AERON_SYSTEM_COUNTER_ERROR_FRAMES_RECEIVED } + { "Error Frames received", AERON_SYSTEM_COUNTER_ERROR_FRAMES_RECEIVED }, + { "Error Frames sent", AERON_SYSTEM_COUNTER_ERROR_FRAMES_SENT } }; static size_t num_system_counters = sizeof(system_counters) / sizeof(aeron_system_counter_t); diff --git a/aeron-driver/src/main/c/aeron_system_counters.h b/aeron-driver/src/main/c/aeron_system_counters.h index 27066b5877..b46749230b 100644 --- a/aeron-driver/src/main/c/aeron_system_counters.h +++ b/aeron-driver/src/main/c/aeron_system_counters.h @@ -60,6 +60,7 @@ typedef enum aeron_system_counter_enum_stct AERON_SYSTEM_COUNTER_RETRANSMITTED_BYTES = 36, AERON_SYSTEM_COUNTER_RETRANSMIT_OVERFLOW = 37, AERON_SYSTEM_COUNTER_ERROR_FRAMES_RECEIVED = 38, + AERON_SYSTEM_COUNTER_ERROR_FRAMES_SENT = 39, // Add all new counters before this one (used for a static assertion). AERON_SYSTEM_COUNTER_DUMMY_LAST, diff --git a/aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.c b/aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.c index f3364fd39a..1a4e455ab4 100644 --- a/aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.c +++ b/aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.c @@ -122,6 +122,8 @@ int aeron_receive_channel_endpoint_create( _endpoint->short_sends_counter = aeron_system_counter_addr(system_counters, AERON_SYSTEM_COUNTER_SHORT_SENDS); _endpoint->possible_ttl_asymmetry_counter = aeron_system_counter_addr( system_counters, AERON_SYSTEM_COUNTER_POSSIBLE_TTL_ASYMMETRY); + _endpoint->errors_frames_sent_counter = aeron_system_counter_addr( + system_counters, AERON_SYSTEM_COUNTER_ERROR_FRAMES_SENT); _endpoint->cached_clock = context->receiver_cached_clock; @@ -461,6 +463,10 @@ int aeron_receiver_channel_endpoint_send_error_frame( aeron_counter_increment(channel_endpoint->short_sends_counter, 1); } } + else + { + aeron_counter_increment(channel_endpoint->errors_frames_sent_counter, 1); + } return bytes_sent; } diff --git a/aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.h b/aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.h index f5bc54025c..119994d4c4 100644 --- a/aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.h +++ b/aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.h @@ -81,6 +81,7 @@ typedef struct aeron_receive_channel_endpoint_stct int64_t *short_sends_counter; int64_t *possible_ttl_asymmetry_counter; + int64_t *errors_frames_sent_counter; } aeron_receive_channel_endpoint_t; From 5fe63048c589cca97723c9755fac810f62afea23 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Thu, 25 Jul 2024 11:24:10 +1200 Subject: [PATCH 43/67] [C] Start adding publication error frame information. --- aeron-client/src/main/c/aeronc.h | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/aeron-client/src/main/c/aeronc.h b/aeron-client/src/main/c/aeronc.h index 254a6135c5..5c93c43efb 100644 --- a/aeron-client/src/main/c/aeronc.h +++ b/aeron-client/src/main/c/aeronc.h @@ -85,6 +85,24 @@ typedef struct aeron_image_controlled_fragment_assembler_stct aeron_image_contro typedef struct aeron_fragment_assembler_stct aeron_fragment_assembler_t; typedef struct aeron_controlled_fragment_assembler_stct aeron_controlled_fragment_assembler_t; +struct aeron_publication_error_frame_stct +{ + int64_t registration_id; + int64_t receiver_id; + int32_t session_id; + int32_t stream_id; + struct + { + bool is_present; + int64_t value; + } + group_tag; + // TODO: How best to handle the source address? String? Do we want to have a dependency on struct sockaddr_storage + size_t message_len; + const char *message; +}; +typedef struct aeron_publication_error_frame_stct aeron_publication_error_frame_t; + /** * Environment variables and functions used for setting values of an aeron_context_t. */ @@ -130,6 +148,11 @@ const char *aeron_context_get_client_name(aeron_context_t *context); */ typedef void (*aeron_error_handler_t)(void *clientd, int errcode, const char *message); +/** + * The error frame handler to be called when the driver notifies the client about an error frame being received + */ +typedef void (*aeron_error_frame_handler_t)(void *clientd); + /** * Generalised notification callback. */ @@ -139,6 +162,10 @@ int aeron_context_set_error_handler(aeron_context_t *context, aeron_error_handle aeron_error_handler_t aeron_context_get_error_handler(aeron_context_t *context); void *aeron_context_get_error_handler_clientd(aeron_context_t *context); +int aeron_context_set_error_frame_handler(aeron_context_t *context, aeron_error_frame_handler_t handler, void *clientd); +aeron_error_frame_handler_t aeron_context_get_error_frame_handler(aeron_context_t *context); +void *aeron_context_get_error_frame_handler_clientd(aeron_context_t *context); + /** * Function called by aeron_client_t to deliver notification that the media driver has added an aeron_publication_t * or aeron_exclusive_publication_t successfully. From 58d5e84f84c0afd64839c879bec937da5770d82a Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Thu, 1 Aug 2024 13:53:00 +1200 Subject: [PATCH 44/67] [C] Basic working flow for C & C++ wrapper for error frames. --- .../src/main/c/aeron_client_conductor.c | 94 ++++++++++++++++++- .../src/main/c/aeron_client_conductor.h | 11 +++ aeron-client/src/main/c/aeron_context.c | 25 +++++ aeron-client/src/main/c/aeron_context.h | 3 + aeron-client/src/main/c/aeron_image.c | 20 ++++ aeron-client/src/main/c/aeron_subscription.c | 13 +++ aeron-client/src/main/c/aeron_subscription.h | 3 + aeron-client/src/main/c/aeronc.h | 43 +++++---- .../main/c/command/aeron_control_protocol.h | 6 +- aeron-client/src/main/cpp_wrapper/Context.h | 29 ++++++ aeron-client/src/main/cpp_wrapper/Image.h | 10 ++ .../status/PublicationErrorFrame.h | 28 ++++++ .../test/cpp_wrapper/WrapperSystemTest.cpp | 46 +++++++++ .../src/main/c/aeron_driver_conductor.c | 18 ++-- .../src/main/c/aeron_driver_conductor.h | 2 +- 15 files changed, 319 insertions(+), 32 deletions(-) create mode 100644 aeron-client/src/main/cpp_wrapper/status/PublicationErrorFrame.h diff --git a/aeron-client/src/main/c/aeron_client_conductor.c b/aeron-client/src/main/c/aeron_client_conductor.c index c598f7ebbe..0fe84cd534 100644 --- a/aeron-client/src/main/c/aeron_client_conductor.c +++ b/aeron-client/src/main/c/aeron_client_conductor.c @@ -165,6 +165,9 @@ int aeron_client_conductor_init(aeron_client_conductor_t *conductor, aeron_conte conductor->error_handler = context->error_handler; conductor->error_handler_clientd = context->error_handler_clientd; + conductor->error_frame_handler = context->error_frame_handler; + conductor->error_handler_clientd = context->error_frame_handler_clientd; + conductor->on_new_publication = context->on_new_publication; conductor->on_new_publication_clientd = context->on_new_publication_clientd; @@ -352,6 +355,19 @@ void aeron_client_conductor_on_driver_response(int32_t type_id, uint8_t *buffer, break; } + case AERON_RESPONSE_ON_PUBLICATION_ERROR: + { + aeron_publication_error_t *response = (aeron_publication_error_t *)buffer; + + if (length < sizeof(aeron_publication_error_t)) + { + goto malformed_command; + } + + result = aeron_client_conductor_on_error_frame(conductor, response); + break; + } + case AERON_RESPONSE_ON_STATIC_COUNTER: { aeron_static_counter_response_t *response = (aeron_static_counter_response_t *)buffer; @@ -367,7 +383,6 @@ void aeron_client_conductor_on_driver_response(int32_t type_id, uint8_t *buffer, default: { - AERON_CLIENT_FORMAT_BUFFER(error_message, "response=%x unknown", type_id); conductor->error_handler( conductor->error_handler_clientd, AERON_ERROR_CODE_UNKNOWN_COMMAND_TYPE_ID, error_message); @@ -2526,6 +2541,45 @@ int aeron_client_conductor_on_operation_success( return 0; } +struct aeron_client_conductor_clientd_stct +{ + aeron_client_conductor_t *conductor; + void *clientd; +}; +typedef struct aeron_client_conductor_clientd_stct aeron_client_conductor_clientd_t; + +void aeron_client_conductor_forward_error(void *clientd, int64_t key, void *value) +{ + aeron_client_conductor_clientd_t *conductor_clientd = (aeron_client_conductor_clientd_t *)clientd; + aeron_client_conductor_t *conductor = conductor_clientd->conductor; + aeron_publication_error_t *response = (aeron_publication_error_t *)conductor_clientd->clientd; + aeron_client_command_base_t *resource = (aeron_client_command_base_t *)value; + + if (AERON_CLIENT_TYPE_PUBLICATION == resource->type) + { + aeron_publication_t *publication = (aeron_publication_t *)resource; + if (response->registration_id == publication->original_registration_id) + { + // TODO: Use a union. + conductor->error_frame_handler( + conductor->error_handler_clientd, (aeron_publication_error_values_t *)response); + } + } +} + +int aeron_client_conductor_on_error_frame(aeron_client_conductor_t *conductor, aeron_publication_error_t *response) +{ + aeron_client_conductor_clientd_t clientd = { + .conductor = conductor, + .clientd = response + }; + + aeron_int64_to_ptr_hash_map_for_each( + &conductor->resource_by_id_map, aeron_client_conductor_forward_error, (void *)&clientd); + + return 0; +} + aeron_subscription_t *aeron_client_conductor_find_subscription_by_id( aeron_client_conductor_t *conductor, int64_t registration_id) { @@ -2859,6 +2913,44 @@ int aeron_client_conductor_offer_destination_command( return 0; } +int aeron_client_conductor_reject_image( + aeron_client_conductor_t *conductor, + int64_t image_correlation_id, + int64_t position, + const char *reason, + int32_t command_type) +{ + size_t reason_length = strlen(reason); + const size_t command_length = sizeof(aeron_reject_image_command_t) + reason_length; + + int rb_offer_fail_count = 0; + int32_t offset; + while ((offset = aeron_mpsc_rb_try_claim(&conductor->to_driver_buffer, command_type, command_length)) < 0) + { + if (++rb_offer_fail_count > AERON_CLIENT_COMMAND_RB_FAIL_THRESHOLD) + { + const char *err_buffer = "reject_image command could not be sent"; + conductor->error_handler(conductor->error_handler_clientd, AERON_CLIENT_ERROR_BUFFER_FULL, err_buffer); + AERON_SET_ERR(AERON_CLIENT_ERROR_BUFFER_FULL, "%s", err_buffer); + return -1; + } + + sched_yield(); + } + + uint8_t *ptr = (conductor->to_driver_buffer.buffer + offset); + aeron_reject_image_command_t *command = (aeron_reject_image_command_t *)ptr; + command->image_correlation_id = image_correlation_id; + command->position = position; + command->reason_length = reason_length; + memcpy(ptr + offsetof(aeron_reject_image_command_t, reason_text), reason, reason_length); + command->reason_text[reason_length] = '\0'; + + aeron_mpsc_rb_commit(&conductor->to_driver_buffer, offset); + + return 0; +} + extern int aeron_counter_heartbeat_timestamp_find_counter_id_by_registration_id( aeron_counters_reader_t *counters_reader, int32_t type_id, int64_t registration_id); extern bool aeron_counter_heartbeat_timestamp_is_active( diff --git a/aeron-client/src/main/c/aeron_client_conductor.h b/aeron-client/src/main/c/aeron_client_conductor.h index 64ab81e7fd..225d21bf4c 100644 --- a/aeron-client/src/main/c/aeron_client_conductor.h +++ b/aeron-client/src/main/c/aeron_client_conductor.h @@ -226,6 +226,9 @@ typedef struct aeron_client_conductor_stct aeron_error_handler_t error_handler; void *error_handler_clientd; + aeron_error_frame_handler_t error_frame_handler; + void *error_frame_handler_clientd; + aeron_on_new_publication_t on_new_publication; void *on_new_publication_clientd; @@ -384,6 +387,7 @@ int aeron_client_conductor_on_unavailable_counter( int aeron_client_conductor_on_static_counter(aeron_client_conductor_t *conductor, aeron_static_counter_response_t *response); int aeron_client_conductor_on_client_timeout(aeron_client_conductor_t *conductor, aeron_client_timeout_t *response); +int aeron_client_conductor_on_error_frame(aeron_client_conductor_t *conductor, aeron_publication_error_t *response); int aeron_client_conductor_get_or_create_log_buffer( aeron_client_conductor_t *conductor, @@ -405,6 +409,13 @@ int aeron_client_conductor_offer_destination_command( const char *uri, int64_t *correlation_id); +int aeron_client_conductor_reject_image( + aeron_client_conductor_t *conductor, + int64_t image_correlation_id, + int64_t position, + const char *reason, + int32_t command_type); + inline int aeron_counter_heartbeat_timestamp_find_counter_id_by_registration_id( aeron_counters_reader_t *counters_reader, int32_t type_id, int64_t registration_id) { diff --git a/aeron-client/src/main/c/aeron_context.c b/aeron-client/src/main/c/aeron_context.c index 32b54c61a5..f09fa70932 100644 --- a/aeron-client/src/main/c/aeron_context.c +++ b/aeron-client/src/main/c/aeron_context.c @@ -44,6 +44,10 @@ void aeron_default_error_handler(void *clientd, int errcode, const char *message exit(EXIT_FAILURE); } +void aeron_default_error_frame_handler(void *clientd, aeron_publication_error_values_t *message) +{ +} + int aeron_context_init(aeron_context_t **context) { aeron_context_t *_context = NULL; @@ -78,6 +82,7 @@ int aeron_context_init(aeron_context_t **context) _context->client_name = NULL; _context->error_handler = aeron_default_error_handler; _context->error_handler_clientd = NULL; + _context->error_frame_handler = aeron_default_error_frame_handler; _context->on_new_publication = NULL; _context->on_new_publication_clientd = NULL; _context->on_new_exclusive_publication = NULL; @@ -337,6 +342,26 @@ void *aeron_context_get_error_handler_clientd(aeron_context_t *context) return NULL != context ? context->error_handler_clientd : NULL; } +int aeron_context_set_error_frame_handler(aeron_context_t *context, aeron_error_frame_handler_t handler, void *clientd) +{ + AERON_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context); + + context->error_frame_handler = handler; + context->error_frame_handler_clientd = clientd; + return 0; +} + +aeron_error_frame_handler_t aeron_context_get_error_frame_handler(aeron_context_t *context) +{ + return NULL != context ? context->error_frame_handler : NULL; +} + +void *aeron_context_get_error_frame_handler_clientd(aeron_context_t *context) +{ + return NULL != context ? context->error_frame_handler_clientd : NULL; +} + + int aeron_context_set_on_new_publication(aeron_context_t *context, aeron_on_new_publication_t handler, void *clientd) { AERON_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context); diff --git a/aeron-client/src/main/c/aeron_context.h b/aeron-client/src/main/c/aeron_context.h index bb293b1ae4..1d2694fc85 100644 --- a/aeron-client/src/main/c/aeron_context.h +++ b/aeron-client/src/main/c/aeron_context.h @@ -55,6 +55,9 @@ typedef struct aeron_context_stct aeron_on_unavailable_counter_t on_unavailable_counter; void *on_unavailable_counter_clientd; + aeron_error_frame_handler_t error_frame_handler; + void *error_frame_handler_clientd; + aeron_agent_on_start_func_t agent_on_start_func; void *agent_on_start_state; diff --git a/aeron-client/src/main/c/aeron_image.c b/aeron-client/src/main/c/aeron_image.c index 43f4e0c3ee..90182689f6 100644 --- a/aeron-client/src/main/c/aeron_image.c +++ b/aeron-client/src/main/c/aeron_image.c @@ -805,6 +805,26 @@ bool aeron_image_is_closed(aeron_image_t *image) return is_closed; } +int aeron_image_reject(aeron_image_t *image, const char *reason) +{ + if (NULL == image) + { + AERON_SET_ERR(EINVAL, "%s", "image is null"); + return -1; + } + + int64_t position = 0; + AERON_GET_VOLATILE(position, *image->subscriber_position); + + if (aeron_subscription_reject_image(image->subscription, image->key.correlation_id, position, reason) < 0) + { + AERON_APPEND_ERR("%s", ""); + return -1; + } + + return 0; +} + extern int64_t aeron_image_removal_change_number(aeron_image_t *image); extern bool aeron_image_is_in_use_by_subscription(aeron_image_t *image, int64_t last_change_number); diff --git a/aeron-client/src/main/c/aeron_subscription.c b/aeron-client/src/main/c/aeron_subscription.c index 6c4b1d5321..6b7d8b4950 100644 --- a/aeron-client/src/main/c/aeron_subscription.c +++ b/aeron-client/src/main/c/aeron_subscription.c @@ -734,6 +734,19 @@ int aeron_subscription_try_resolve_channel_endpoint_port( return result; } +int aeron_subscription_reject_image( + aeron_subscription_t *subscription, int64_t image_correlation_id, int64_t position, const char *reason) +{ + if (aeron_client_conductor_reject_image( + subscription->conductor, image_correlation_id, position, reason, AERON_COMMAND_REJECT_IMAGE) < 0) + { + AERON_APPEND_ERR("%s", ""); + return -1; + } + + return 0; +} + extern int aeron_subscription_find_image_index(aeron_image_list_t *volatile image_list, aeron_image_t *image); extern int64_t aeron_subscription_last_image_list_change_number(aeron_subscription_t *subscription); extern void aeron_subscription_propose_last_image_change_number( diff --git a/aeron-client/src/main/c/aeron_subscription.h b/aeron-client/src/main/c/aeron_subscription.h index 143314a7ad..8ab6d428a9 100644 --- a/aeron-client/src/main/c/aeron_subscription.h +++ b/aeron-client/src/main/c/aeron_subscription.h @@ -133,4 +133,7 @@ inline void aeron_subscription_propose_last_image_change_number( } } +int aeron_subscription_reject_image( + aeron_subscription_t *subscription, int64_t image_correlation_id, int64_t position, const char *reason); + #endif //AERON_C_SUBSCRIPTION_H diff --git a/aeron-client/src/main/c/aeronc.h b/aeron-client/src/main/c/aeronc.h index 5c93c43efb..0e173bbde6 100644 --- a/aeron-client/src/main/c/aeronc.h +++ b/aeron-client/src/main/c/aeronc.h @@ -64,6 +64,22 @@ typedef struct aeron_header_values_stct size_t position_bits_to_shift; } aeron_header_values_t; + +struct aeron_publication_error_values_stct +{ + int64_t registration_id; + int32_t session_id; + int32_t stream_id; + int64_t receiver_id; + int64_t group_tag; + int16_t address_type; + uint16_t address_port; + uint8_t address[16]; + int32_t error_code; + int32_t error_message_length; + uint8_t error_message[1]; +}; +typedef struct aeron_publication_error_values_stct aeron_publication_error_values_t; #pragma pack(pop) typedef struct aeron_subscription_stct aeron_subscription_t; @@ -85,23 +101,6 @@ typedef struct aeron_image_controlled_fragment_assembler_stct aeron_image_contro typedef struct aeron_fragment_assembler_stct aeron_fragment_assembler_t; typedef struct aeron_controlled_fragment_assembler_stct aeron_controlled_fragment_assembler_t; -struct aeron_publication_error_frame_stct -{ - int64_t registration_id; - int64_t receiver_id; - int32_t session_id; - int32_t stream_id; - struct - { - bool is_present; - int64_t value; - } - group_tag; - // TODO: How best to handle the source address? String? Do we want to have a dependency on struct sockaddr_storage - size_t message_len; - const char *message; -}; -typedef struct aeron_publication_error_frame_stct aeron_publication_error_frame_t; /** * Environment variables and functions used for setting values of an aeron_context_t. @@ -151,7 +150,7 @@ typedef void (*aeron_error_handler_t)(void *clientd, int errcode, const char *me /** * The error frame handler to be called when the driver notifies the client about an error frame being received */ -typedef void (*aeron_error_frame_handler_t)(void *clientd); +typedef void (*aeron_error_frame_handler_t)(void *clientd, aeron_publication_error_values_t *error_frame); /** * Generalised notification callback. @@ -2089,6 +2088,14 @@ int aeron_image_block_poll( bool aeron_image_is_closed(aeron_image_t *image); +/** + * Force the driver to disconnect this image from the remote publication. + * + * @param image to be rejected. + * @param reason an error message to be forwarded back to the publication. + */ +int aeron_image_reject(aeron_image_t *image, const char *reason); + /** * A fragment handler that sits in a chain-of-responsibility pattern that reassembles fragmented messages * so that the next handler in the chain only sees whole messages. diff --git a/aeron-client/src/main/c/command/aeron_control_protocol.h b/aeron-client/src/main/c/command/aeron_control_protocol.h index 236b2274c4..2127efa5e7 100644 --- a/aeron-client/src/main/c/command/aeron_control_protocol.h +++ b/aeron-client/src/main/c/command/aeron_control_protocol.h @@ -35,7 +35,7 @@ #define AERON_COMMAND_REMOVE_RCV_DESTINATION (0x0D) #define AERON_COMMAND_TERMINATE_DRIVER (0x0E) #define AERON_COMMAND_ADD_STATIC_COUNTER (0x0F) -#define AERON_COMMAND_INVALIDATE_IMAGE (0x10) +#define AERON_COMMAND_REJECT_IMAGE (0x10) #define AERON_COMMAND_REMOVE_DESTINATION_BY_ID (0x11) #define AERON_RESPONSE_ON_ERROR (0x0F01) @@ -212,7 +212,7 @@ typedef struct aeron_terminate_driver_command_stct } aeron_terminate_driver_command_t; -typedef struct aeron_invalidate_image_command_stct +typedef struct aeron_reject_image_command_stct { aeron_correlated_command_t correlated; int64_t image_correlation_id; @@ -220,7 +220,7 @@ typedef struct aeron_invalidate_image_command_stct int32_t reason_length; uint8_t reason_text[1]; } -aeron_invalidate_image_command_t; +aeron_reject_image_command_t; struct aeron_publication_error_stct { diff --git a/aeron-client/src/main/cpp_wrapper/Context.h b/aeron-client/src/main/cpp_wrapper/Context.h index cb69715946..86fb7325a1 100644 --- a/aeron-client/src/main/cpp_wrapper/Context.h +++ b/aeron-client/src/main/cpp_wrapper/Context.h @@ -24,6 +24,7 @@ #include "concurrent/AgentRunner.h" #include "concurrent/CountersReader.h" #include "CncFileDescriptor.h" +#include "status/PublicationErrorFrame.h" #include "aeronc.h" @@ -133,6 +134,8 @@ typedef std::function on_close_client_t; +typedef std::function on_error_frame_t; + const static long NULL_TIMEOUT = -1; const static long DEFAULT_MEDIA_DRIVER_TIMEOUT_MS = 10000; const static long DEFAULT_RESOURCE_LINGER_MS = 5000; @@ -193,6 +196,10 @@ inline void defaultOnCloseClientHandler() { } +inline void defaultOnErrorFrameHandler(aeron::status::PublicationErrorFrame &) +{ +} + /** * This class provides configuration for the {@link Aeron} class via the {@link Aeron::Aeron} or {@link Aeron::connect} * methods and its overloads. It gives applications some control over the interactions with the Aeron Media Driver. @@ -526,6 +533,12 @@ class Context return *this; } + inline this_t &errorFrameHandler(on_error_frame_t &handler) + { + m_onErrorFrameHandler = handler; + return *this; + } + static bool requestDriverTermination( const std::string &directory, const std::uint8_t *tokenBuffer, std::size_t tokenLength) { @@ -576,6 +589,7 @@ class Context on_available_counter_t m_onAvailableCounterHandler = defaultOnAvailableCounterHandler; on_unavailable_counter_t m_onUnavailableCounterHandler = defaultOnUnavailableCounterHandler; on_close_client_t m_onCloseClientHandler = defaultOnCloseClientHandler; + on_error_frame_t m_onErrorFrameHandler = defaultOnErrorFrameHandler; void attachCallbacksToContext() { @@ -634,6 +648,14 @@ class Context { throw IllegalArgumentException(std::string(aeron_errmsg()), SOURCEINFO); } + + if (aeron_context_set_error_frame_handler( + m_context, + errorFrameHandlerCallback, + const_cast(reinterpret_cast(&m_onErrorFrameHandler))) < 0) + { + throw IllegalArgumentException(std::string(aeron_errmsg()), SOURCEINFO); + } } static void errorHandlerCallback(void *clientd, int errcode, const char *message) @@ -685,6 +707,13 @@ class Context on_close_client_t &handler = *reinterpret_cast(clientd); handler(); } + + static void errorFrameHandlerCallback(void *clientd, aeron_publication_error_values_t *error_frame) + { + on_error_frame_t &handler = *reinterpret_cast(clientd); + aeron::status::PublicationErrorFrame errorFrame{error_frame}; + handler(errorFrame); + } }; } diff --git a/aeron-client/src/main/cpp_wrapper/Image.h b/aeron-client/src/main/cpp_wrapper/Image.h index c28d6db433..b622e2d53a 100644 --- a/aeron-client/src/main/cpp_wrapper/Image.h +++ b/aeron-client/src/main/cpp_wrapper/Image.h @@ -487,6 +487,16 @@ class Image return numFragments; } + /** + * Force the driver to disconnect this image from the remote publication. + * + * @param reason an error message to be forwarded back to the publication. + */ + void reject(std::string reason) + { + aeron_image_reject(m_image, reason.c_str()); + } + private: aeron_subscription_t *m_subscription = nullptr; aeron_image_t *m_image = nullptr; diff --git a/aeron-client/src/main/cpp_wrapper/status/PublicationErrorFrame.h b/aeron-client/src/main/cpp_wrapper/status/PublicationErrorFrame.h new file mode 100644 index 0000000000..9a61227755 --- /dev/null +++ b/aeron-client/src/main/cpp_wrapper/status/PublicationErrorFrame.h @@ -0,0 +1,28 @@ +// +// Created by mike on 31/07/24. +// + +#ifndef AERON_PUBLICATIONERRORFRAME_H +#define AERON_PUBLICATIONERRORFRAME_H + +#include "aeronc.h" + +namespace aeron { namespace status { + +class PublicationErrorFrame +{ +public: + PublicationErrorFrame(aeron_publication_error_values_t *errorValues) : + m_errorValues(errorValues) + { + } + +private: + aeron_publication_error_values_t *m_errorValues; +}; + + + +}} + +#endif //AERON_PUBLICATIONERRORFRAME_H diff --git a/aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp b/aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp index ff462cd758..dcee4aaf3d 100644 --- a/aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp +++ b/aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp @@ -123,4 +123,50 @@ TEST_F(WrapperSystemTest, shouldRejectClientNameThatIsTooLong) const char *string = strstr(ex.what(), "client_name length must <= 100"); ASSERT_NE(nullptr, string) << ex.what(); } +} + +TEST_F(WrapperSystemTest, shouldRejectImage) +{ + Context ctx; + ctx.useConductorAgentInvoker(true); + + std::atomic errorFrameCount{0}; + + on_error_frame_t errorFrameHandler = + [&](aeron::status::PublicationErrorFrame &errorFrame) + { + std::atomic_fetch_add(&errorFrameCount, 1); + return; + }; + + ctx.errorFrameHandler(errorFrameHandler); + std::shared_ptr aeron = Aeron::connect(ctx); + AgentInvoker &invoker = aeron->conductorAgentInvoker(); + invoker.start(); + + std::int64_t pubId = aeron->addPublication("aeron:udp?endpoint=localhost:10000", 10000); + std::int64_t subId = aeron->addSubscription("aeron:udp?endpoint=localhost:10000", 10000); + invoker.invoke(); + + POLL_FOR_NON_NULL(pub, aeron->findPublication(pubId), invoker); + POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker); + POLL_FOR(pub->isConnected() && sub->isConnected(), invoker); + + std::string message = "Hello World!"; + + const uint8_t *data = reinterpret_cast(message.c_str()); + POLL_FOR(0 < pub->offer(data, message.length()), invoker); + POLL_FOR(0 < sub->poll( + [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header) + { + EXPECT_EQ(message, buffer.getStringWithoutLength(offset, length)); + }, + 1), invoker); + + POLL_FOR(1 == sub->imageCount(), invoker); + + const std::shared_ptr image = sub->imageByIndex(0); + image->reject("No Longer Valid"); + + POLL_FOR(0 < errorFrameCount, invoker); } \ No newline at end of file diff --git a/aeron-driver/src/main/c/aeron_driver_conductor.c b/aeron-driver/src/main/c/aeron_driver_conductor.c index 9b92a6e6e4..33c0657757 100644 --- a/aeron-driver/src/main/c/aeron_driver_conductor.c +++ b/aeron-driver/src/main/c/aeron_driver_conductor.c @@ -2570,12 +2570,12 @@ void on_error( void aeron_driver_conductor_on_publication_error(void *clientd, void *item) { - uint8_t buffer[sizeof(aeron_publication_error_t) + (AERON_ERROR_MAX_TEXT_LENGTH - 1)]; + uint8_t buffer[sizeof(aeron_publication_error_values_t) + (AERON_ERROR_MAX_TEXT_LENGTH - 1)]; aeron_driver_conductor_t *conductor = clientd; aeron_command_publication_error_t *error = item; aeron_driver_conductor_log_explicit_error(conductor, error->error_code, (const char *)error->error_text); - aeron_publication_error_t *response = (aeron_publication_error_t *)buffer; + aeron_publication_error_values_t *response = (aeron_publication_error_values_t *)buffer; response->error_code = error->error_code; response->registration_id = error->registration_id; response->session_id = error->session_id; @@ -2606,7 +2606,7 @@ void aeron_driver_conductor_on_publication_error(void *clientd, void *item) response->error_message_length = error->error_length; memcpy(response->error_message, error->error_text, error->error_length); - size_t response_length = offsetof(aeron_publication_error_t, error_message) + response->error_message_length; + size_t response_length = offsetof(aeron_publication_error_values_t, error_message) + response->error_message_length; aeron_driver_conductor_client_transmit(conductor, AERON_RESPONSE_ON_PUBLICATION_ERROR, response, response_length); } @@ -3376,17 +3376,17 @@ aeron_rb_read_action_t aeron_driver_conductor_on_command( break; } - case AERON_COMMAND_INVALIDATE_IMAGE: + case AERON_COMMAND_REJECT_IMAGE: { - aeron_invalidate_image_command_t *command = (aeron_invalidate_image_command_t *)message; + aeron_reject_image_command_t *command = (aeron_reject_image_command_t *)message; correlation_id = command->correlated.correlation_id; - if (length < sizeof (aeron_invalidate_image_command_t)) + if (length < sizeof (aeron_reject_image_command_t)) { goto malformed_command; } - if (length < offsetof(aeron_invalidate_image_command_t, reason_text) + command->reason_length) + if (length < offsetof(aeron_reject_image_command_t, reason_text) + command->reason_length) { goto malformed_command; } @@ -5714,7 +5714,7 @@ int aeron_driver_conductor_on_terminate_driver( } int aeron_driver_conductor_on_invalidate_image( - aeron_driver_conductor_t *conductor, aeron_invalidate_image_command_t *command) + aeron_driver_conductor_t *conductor, aeron_reject_image_command_t *command) { const int64_t image_correlation_id = command->image_correlation_id; aeron_publication_image_t *image = aeron_driver_conductor_find_publication_image( @@ -5744,7 +5744,7 @@ int aeron_driver_conductor_on_invalidate_image( } - void aeron_driver_conductor_on_create_publication_image(void *clientd, void *item) +void aeron_driver_conductor_on_create_publication_image(void *clientd, void *item) { aeron_driver_conductor_t *conductor = (aeron_driver_conductor_t *)clientd; aeron_command_create_publication_image_t *command = (aeron_command_create_publication_image_t *)item; diff --git a/aeron-driver/src/main/c/aeron_driver_conductor.h b/aeron-driver/src/main/c/aeron_driver_conductor.h index c9515c39bf..c09b3d409b 100644 --- a/aeron-driver/src/main/c/aeron_driver_conductor.h +++ b/aeron-driver/src/main/c/aeron_driver_conductor.h @@ -523,7 +523,7 @@ int aeron_driver_conductor_on_terminate_driver( aeron_driver_conductor_t *conductor, aeron_terminate_driver_command_t *command); int aeron_driver_conductor_on_invalidate_image( - aeron_driver_conductor_t *conductor, aeron_invalidate_image_command_t *command); + aeron_driver_conductor_t *conductor, aeron_reject_image_command_t *command); void aeron_driver_conductor_on_create_publication_image(void *clientd, void *item); From b556e449f854bf1f77f9b35f9c9e7097e0119ff2 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Thu, 1 Aug 2024 15:25:15 +1200 Subject: [PATCH 45/67] [Java] Rename to explicitly reference that the handler is dealing specifically with error frames for publications. Add test to verify that it works for exclusive publications. --- .../src/main/java/io/aeron/Aeron.java | 21 +++--- .../main/java/io/aeron/ClientConductor.java | 3 +- .../java/io/aeron/DriverEventsAdapter.java | 1 + .../main/java/io/aeron/ErrorFrameHandler.java | 43 ------------- .../aeron/PublicationErrorFrameHandler.java | 47 ++++++++++++++ .../aeron/status/PublicationErrorFrame.java | 1 + .../test/java/io/aeron/RejectImageTest.java | 64 ++++++++++++++++--- 7 files changed, 118 insertions(+), 62 deletions(-) delete mode 100644 aeron-client/src/main/java/io/aeron/ErrorFrameHandler.java create mode 100644 aeron-client/src/main/java/io/aeron/PublicationErrorFrameHandler.java diff --git a/aeron-client/src/main/java/io/aeron/Aeron.java b/aeron-client/src/main/java/io/aeron/Aeron.java index df3f499732..1fe22080c2 100644 --- a/aeron-client/src/main/java/io/aeron/Aeron.java +++ b/aeron-client/src/main/java/io/aeron/Aeron.java @@ -942,7 +942,7 @@ public static class Context extends CommonContext private UnavailableImageHandler unavailableImageHandler; private AvailableCounterHandler availableCounterHandler; private UnavailableCounterHandler unavailableCounterHandler; - private ErrorFrameHandler errorFrameHandler = ErrorFrameHandler.NO_OP; + private PublicationErrorFrameHandler publicationErrorFrameHandler = PublicationErrorFrameHandler.NO_OP; private Runnable closeHandler; private long keepAliveIntervalNs = Configuration.KEEPALIVE_INTERVAL_NS; private long interServiceTimeoutNs = 0; @@ -1731,27 +1731,28 @@ public ThreadFactory threadFactory() } /** - * Set a handler to receive error frames that have been received by the local driver for resources owned by + * Set the handler to receive error frames that have been received by the local driver for publications added by * this client. * - * @param errorFrameHandler to be called back when an error frame is received. - * @return this for a fluent API. + * @param publicationErrorFrameHandler to be called back when an error frame is received. + * @return this for a fluent API. */ - public Context errorFrameHandler(final ErrorFrameHandler errorFrameHandler) + public Context publicationErrorFrameHandler( + final PublicationErrorFrameHandler publicationErrorFrameHandler) { - this.errorFrameHandler = errorFrameHandler; + this.publicationErrorFrameHandler = publicationErrorFrameHandler; return this; } /** - * Get the handler to receive error frames that have been received by the local driver for resources owned by + * Get the handler to receive error frames that have been received by the local driver for publications added by * this client. * - * @return the {@link ErrorFrameHandler} to call back on to. + * @return the {@link PublicationErrorFrameHandler} to call back on to. */ - public ErrorFrameHandler errorFrameHandler() + public PublicationErrorFrameHandler publicationErrorFrameHandler() { - return this.errorFrameHandler; + return this.publicationErrorFrameHandler; } /** diff --git a/aeron-client/src/main/java/io/aeron/ClientConductor.java b/aeron-client/src/main/java/io/aeron/ClientConductor.java index 3dfad417b6..30730ee9a4 100644 --- a/aeron-client/src/main/java/io/aeron/ClientConductor.java +++ b/aeron-client/src/main/java/io/aeron/ClientConductor.java @@ -30,6 +30,7 @@ import org.agrona.concurrent.status.CountersReader; import org.agrona.concurrent.status.UnsafeBufferPosition; +import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; @@ -282,7 +283,7 @@ void onPublicationError(final PublicationErrorFrameFlyweight errorFrameFlyweight if (publication.originalRegistrationId() == errorFrameFlyweight.registrationId()) { publicationErrorFrame.set(errorFrameFlyweight); - ctx.errorFrameHandler().onPublicationError(publicationErrorFrame); + ctx.publicationErrorFrameHandler().onPublicationError(publicationErrorFrame); } } } diff --git a/aeron-client/src/main/java/io/aeron/DriverEventsAdapter.java b/aeron-client/src/main/java/io/aeron/DriverEventsAdapter.java index 6a847251ae..c0e12b14f0 100644 --- a/aeron-client/src/main/java/io/aeron/DriverEventsAdapter.java +++ b/aeron-client/src/main/java/io/aeron/DriverEventsAdapter.java @@ -270,6 +270,7 @@ else if (correlationId == activeCorrelationId) publicationErrorFrame.wrap(buffer, index); conductor.onPublicationError(publicationErrorFrame); + break; } } } diff --git a/aeron-client/src/main/java/io/aeron/ErrorFrameHandler.java b/aeron-client/src/main/java/io/aeron/ErrorFrameHandler.java deleted file mode 100644 index 5f5d49ac8c..0000000000 --- a/aeron-client/src/main/java/io/aeron/ErrorFrameHandler.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2014-2024 Real Logic Limited. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.aeron; - -import io.aeron.status.PublicationErrorFrame; - -/** - * Interface for handling various error frame messages from different components in the client. - */ -public interface ErrorFrameHandler -{ - ErrorFrameHandler NO_OP = new ErrorFrameHandler() - { - }; - - /** - * Called when an error frame received by the local driver is propagated to the clients. E.g. when an image is - * invalidated. This callback will reuse the {@link PublicationErrorFrame} instance, so data is only valid for the - * lifetime of the callback. If the user needs to pass the data onto another thread or hold in another location for - * use later, then the user needs to make use of the {@link PublicationErrorFrame#clone()} method to create a copy - * for their own use. - *

- * This callback will be executed on the client conductor thread, similar to image availability notifications. - * - * @param errorFrame contain the data from the error frame received by the publication. - */ - default void onPublicationError(final PublicationErrorFrame errorFrame) - { - } -} diff --git a/aeron-client/src/main/java/io/aeron/PublicationErrorFrameHandler.java b/aeron-client/src/main/java/io/aeron/PublicationErrorFrameHandler.java new file mode 100644 index 0000000000..f095b0cd85 --- /dev/null +++ b/aeron-client/src/main/java/io/aeron/PublicationErrorFrameHandler.java @@ -0,0 +1,47 @@ +/* + * Copyright 2014-2024 Real Logic Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.aeron; + +import io.aeron.status.PublicationErrorFrame; + +/** + * Interface for handling various error frame messages for publications. + */ +public interface PublicationErrorFrameHandler +{ + PublicationErrorFrameHandler NO_OP = new PublicationErrorFrameHandler() + { + public void onPublicationError(final PublicationErrorFrame errorFrame) + { + } + }; + + /** + * Called when an error frame for a publication is received by the local driver and needs to be propagated to the + * appropriate clients. E.g. when an image is invalidated. This callback will reuse the {@link + * PublicationErrorFrame} instance, so data is only valid for the lifetime of the callback. If the user needs to + * pass the data onto another thread or hold in another location for use later, then the user needs to make use of + * the {@link PublicationErrorFrame#clone()} method to create a copy for their own use. + *

+ * This callback will be executed on the client conductor thread, similar to image availability notifications. + *

+ * This notification will only be propagated to clients that have added an instance of the Publication that received + * the error frame (i.e. the originalRegistrationId matches the registrationId on the error frame). + * + * @param errorFrame containing the relevant information about the publication and the error message. + */ + void onPublicationError(PublicationErrorFrame errorFrame); +} diff --git a/aeron-client/src/main/java/io/aeron/status/PublicationErrorFrame.java b/aeron-client/src/main/java/io/aeron/status/PublicationErrorFrame.java index fa01f6b0bb..426789a5a0 100644 --- a/aeron-client/src/main/java/io/aeron/status/PublicationErrorFrame.java +++ b/aeron-client/src/main/java/io/aeron/status/PublicationErrorFrame.java @@ -15,6 +15,7 @@ */ package io.aeron.status; +import io.aeron.ErrorCode; import io.aeron.command.PublicationErrorFrameFlyweight; import java.net.InetSocketAddress; diff --git a/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java b/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java index 1240da6646..c6b736182a 100644 --- a/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java +++ b/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java @@ -16,6 +16,7 @@ package io.aeron; import io.aeron.driver.MediaDriver; +import io.aeron.driver.PublicationImage; import io.aeron.driver.ThreadingMode; import io.aeron.exceptions.AeronException; import io.aeron.status.PublicationErrorFrame; @@ -47,6 +48,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiFunction; +import java.util.function.Function; import static io.aeron.driver.status.SystemCounterDescriptor.ERRORS; import static io.aeron.driver.status.SystemCounterDescriptor.ERROR_FRAMES_RECEIVED; @@ -65,6 +68,7 @@ public class RejectImageTest { public static final long A_VALUE_THAT_SHOWS_WE_ARENT_SPAMMING_ERROR_MESSAGES = 1000L; + @RegisterExtension final SystemTestWatcher systemTestWatcher = new SystemTestWatcher(); @@ -89,7 +93,7 @@ private TestMediaDriver launch() return driver; } - private static final class QueuedErrorFrameHandler implements ErrorFrameHandler + private static final class QueuedErrorFrameHandler implements PublicationErrorFrameHandler { private final OneToOneConcurrentArrayQueue errorFrameQueue = new OneToOneConcurrentArrayQueue<>(512); @@ -117,7 +121,7 @@ void shouldRejectSubscriptionsImage() throws IOException final Aeron.Context ctx = new Aeron.Context() .aeronDirectoryName(driver.aeronDirectoryName()) - .errorFrameHandler(errorFrameHandler); + .publicationErrorFrameHandler(errorFrameHandler); final AtomicBoolean imageUnavailable = new AtomicBoolean(false); @@ -210,11 +214,11 @@ void shouldOnlyReceivePublicationErrorFrameOnRelevantClient() throws IOException final Aeron.Context ctx1 = new Aeron.Context() .aeronDirectoryName(driver.aeronDirectoryName()) - .errorFrameHandler(errorFrameHandler1); + .publicationErrorFrameHandler(errorFrameHandler1); final Aeron.Context ctx2 = new Aeron.Context() .aeronDirectoryName(driver.aeronDirectoryName()) - .errorFrameHandler(errorFrameHandler2); + .publicationErrorFrameHandler(errorFrameHandler2); try (Aeron aeron1 = Aeron.connect(ctx1); Aeron aeron2 = Aeron.connect(ctx2); @@ -253,7 +257,6 @@ void shouldOnlyReceivePublicationErrorFrameOnRelevantClient() throws IOException } } - @Test @InterruptAfter(10) @SlowTest @@ -267,11 +270,11 @@ void shouldReceivePublicationErrorFramesAllRelevantClients() throws IOException final Aeron.Context ctx1 = new Aeron.Context() .aeronDirectoryName(driver.aeronDirectoryName()) - .errorFrameHandler(errorFrameHandler1); + .publicationErrorFrameHandler(errorFrameHandler1); final Aeron.Context ctx2 = new Aeron.Context() .aeronDirectoryName(driver.aeronDirectoryName()) - .errorFrameHandler(errorFrameHandler2); + .publicationErrorFrameHandler(errorFrameHandler2); try (Aeron aeron1 = Aeron.connect(ctx1); Aeron aeron2 = Aeron.connect(ctx2); @@ -431,7 +434,7 @@ void shouldReturnAllParametersToApi(final String addressStr) throws UnknownHostE final Aeron.Context ctx = new Aeron.Context() .aeronDirectoryName(driver.aeronDirectoryName()) - .errorFrameHandler(errorFrameHandler); + .publicationErrorFrameHandler(errorFrameHandler); final long groupTag = 1001; final int port = 10001; @@ -478,4 +481,49 @@ void shouldReturnAllParametersToApi(final String addressStr) throws UnknownHostE assertEquals(new InetSocketAddress(addressStr, port), errorFrame.sourceAddress()); } } + + @ParameterizedTest + @InterruptAfter(5) + @ValueSource(booleans = { true, false }) + void shouldOnlyReceivePublicationErrorFrames(final boolean isExclusive) throws IOException + { + context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3)); + + final TestMediaDriver driver = launch(); + final QueuedErrorFrameHandler errorFrameHandler = new QueuedErrorFrameHandler(); + + final Aeron.Context ctx = new Aeron.Context() + .aeronDirectoryName(driver.aeronDirectoryName()) + .publicationErrorFrameHandler(errorFrameHandler); + + final Function addPub = (aeron) -> isExclusive ? + aeron.addExclusivePublication(channel, streamId) : aeron.addPublication(channel, streamId); + + try (Aeron aeron = Aeron.connect(ctx); + Publication pub = addPub.apply(aeron); + Subscription sub = aeron.addSubscription(channel, streamId)) + { + Tests.awaitConnected(pub); + Tests.awaitConnected(sub); + + while (pub.offer(message) < 0) + { + Tests.yield(); + } + + while (0 == sub.poll((buffer, offset, length, header) -> {}, 1)) + { + Tests.yield(); + } + + final Image image = sub.imageAtIndex(0); + final String reason = "Needs to be closed"; + image.reject(reason); + + while (null == errorFrameHandler.poll()) + { + Tests.yield(); + } + } + } } From 861d5bb72461ff7fd498b60533a4b44191de09d1 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Thu, 1 Aug 2024 15:37:34 +1200 Subject: [PATCH 46/67] [C/C++] Start adding test for validating exclusive publications receive error frames. --- .../test/cpp_wrapper/WrapperSystemTest.cpp | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp b/aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp index dcee4aaf3d..d42a1a1a9f 100644 --- a/aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp +++ b/aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp @@ -168,5 +168,51 @@ TEST_F(WrapperSystemTest, shouldRejectImage) const std::shared_ptr image = sub->imageByIndex(0); image->reject("No Longer Valid"); + POLL_FOR(0 < errorFrameCount, invoker); +} + +TEST_F(WrapperSystemTest, shouldRejectImageForExclusive) +{ + Context ctx; + ctx.useConductorAgentInvoker(true); + + std::atomic errorFrameCount{0}; + + on_error_frame_t errorFrameHandler = + [&](aeron::status::PublicationErrorFrame &errorFrame) + { + std::atomic_fetch_add(&errorFrameCount, 1); + return; + }; + + ctx.errorFrameHandler(errorFrameHandler); + std::shared_ptr aeron = Aeron::connect(ctx); + AgentInvoker &invoker = aeron->conductorAgentInvoker(); + invoker.start(); + + std::int64_t pubId = aeron->addExclusivePublication("aeron:udp?endpoint=localhost:10000", 10000); + std::int64_t subId = aeron->addSubscription("aeron:udp?endpoint=localhost:10000", 10000); + invoker.invoke(); + + POLL_FOR_NON_NULL(pub, aeron->findExclusivePublication(pubId), invoker); + POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker); + POLL_FOR(pub->isConnected() && sub->isConnected(), invoker); + + std::string message = "Hello World!"; + + const uint8_t *data = reinterpret_cast(message.c_str()); + POLL_FOR(0 < pub->offer(data, message.length()), invoker); + POLL_FOR(0 < sub->poll( + [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header) + { + EXPECT_EQ(message, buffer.getStringWithoutLength(offset, length)); + }, + 1), invoker); + + POLL_FOR(1 == sub->imageCount(), invoker); + + const std::shared_ptr image = sub->imageByIndex(0); + image->reject("No Longer Valid"); + POLL_FOR(0 < errorFrameCount, invoker); } \ No newline at end of file From 5cdaefb91db293cf0023e5051760477f51303578 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Fri, 2 Aug 2024 07:33:55 +1200 Subject: [PATCH 47/67] [C++ Wrapper] Comment out test. --- .../test/cpp_wrapper/WrapperSystemTest.cpp | 90 +++++++++---------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp b/aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp index d42a1a1a9f..516c4112e7 100644 --- a/aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp +++ b/aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp @@ -171,48 +171,48 @@ TEST_F(WrapperSystemTest, shouldRejectImage) POLL_FOR(0 < errorFrameCount, invoker); } -TEST_F(WrapperSystemTest, shouldRejectImageForExclusive) -{ - Context ctx; - ctx.useConductorAgentInvoker(true); - - std::atomic errorFrameCount{0}; - - on_error_frame_t errorFrameHandler = - [&](aeron::status::PublicationErrorFrame &errorFrame) - { - std::atomic_fetch_add(&errorFrameCount, 1); - return; - }; - - ctx.errorFrameHandler(errorFrameHandler); - std::shared_ptr aeron = Aeron::connect(ctx); - AgentInvoker &invoker = aeron->conductorAgentInvoker(); - invoker.start(); - - std::int64_t pubId = aeron->addExclusivePublication("aeron:udp?endpoint=localhost:10000", 10000); - std::int64_t subId = aeron->addSubscription("aeron:udp?endpoint=localhost:10000", 10000); - invoker.invoke(); - - POLL_FOR_NON_NULL(pub, aeron->findExclusivePublication(pubId), invoker); - POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker); - POLL_FOR(pub->isConnected() && sub->isConnected(), invoker); - - std::string message = "Hello World!"; - - const uint8_t *data = reinterpret_cast(message.c_str()); - POLL_FOR(0 < pub->offer(data, message.length()), invoker); - POLL_FOR(0 < sub->poll( - [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header) - { - EXPECT_EQ(message, buffer.getStringWithoutLength(offset, length)); - }, - 1), invoker); - - POLL_FOR(1 == sub->imageCount(), invoker); - - const std::shared_ptr image = sub->imageByIndex(0); - image->reject("No Longer Valid"); - - POLL_FOR(0 < errorFrameCount, invoker); -} \ No newline at end of file +//TEST_F(WrapperSystemTest, shouldRejectImageForExclusive) +//{ +// Context ctx; +// ctx.useConductorAgentInvoker(true); +// +// std::atomic errorFrameCount{0}; +// +// on_error_frame_t errorFrameHandler = +// [&](aeron::status::PublicationErrorFrame &errorFrame) +// { +// std::atomic_fetch_add(&errorFrameCount, 1); +// return; +// }; +// +// ctx.errorFrameHandler(errorFrameHandler); +// std::shared_ptr aeron = Aeron::connect(ctx); +// AgentInvoker &invoker = aeron->conductorAgentInvoker(); +// invoker.start(); +// +// std::int64_t pubId = aeron->addExclusivePublication("aeron:udp?endpoint=localhost:10000", 10000); +// std::int64_t subId = aeron->addSubscription("aeron:udp?endpoint=localhost:10000", 10000); +// invoker.invoke(); +// +// POLL_FOR_NON_NULL(pub, aeron->findExclusivePublication(pubId), invoker); +// POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker); +// POLL_FOR(pub->isConnected() && sub->isConnected(), invoker); +// +// std::string message = "Hello World!"; +// +// const uint8_t *data = reinterpret_cast(message.c_str()); +// POLL_FOR(0 < pub->offer(data, message.length()), invoker); +// POLL_FOR(0 < sub->poll( +// [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header) +// { +// EXPECT_EQ(message, buffer.getStringWithoutLength(offset, length)); +// }, +// 1), invoker); +// +// POLL_FOR(1 == sub->imageCount(), invoker); +// +// const std::shared_ptr image = sub->imageByIndex(0); +// image->reject("No Longer Valid"); +// +// POLL_FOR(0 < errorFrameCount, invoker); +//} From 7bca2848778aa9483864265f36585740284010a9 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Fri, 2 Aug 2024 15:34:30 +1200 Subject: [PATCH 48/67] [C] Ensure that error frames are pass to the client for exclusive publications. --- .../src/main/c/aeron_client_conductor.c | 17 ++-- .../src/main/cpp_wrapper/CMakeLists.txt | 1 + aeron-client/src/main/cpp_wrapper/Context.h | 3 +- .../test/cpp_wrapper/WrapperSystemTest.cpp | 90 +++++++++---------- 4 files changed, 57 insertions(+), 54 deletions(-) diff --git a/aeron-client/src/main/c/aeron_client_conductor.c b/aeron-client/src/main/c/aeron_client_conductor.c index 0fe84cd534..946bd8dcf9 100644 --- a/aeron-client/src/main/c/aeron_client_conductor.c +++ b/aeron-client/src/main/c/aeron_client_conductor.c @@ -2555,15 +2555,16 @@ void aeron_client_conductor_forward_error(void *clientd, int64_t key, void *valu aeron_publication_error_t *response = (aeron_publication_error_t *)conductor_clientd->clientd; aeron_client_command_base_t *resource = (aeron_client_command_base_t *)value; - if (AERON_CLIENT_TYPE_PUBLICATION == resource->type) + const bool is_publication = AERON_CLIENT_TYPE_PUBLICATION == resource->type && + ((aeron_publication_t *)resource)->original_registration_id == response->registration_id; + const bool is_exclusive_publication = AERON_CLIENT_TYPE_EXCLUSIVE_PUBLICATION == resource->type && + ((aeron_exclusive_publication_t *)resource)->original_registration_id == response->registration_id; + + if (is_publication || is_exclusive_publication) { - aeron_publication_t *publication = (aeron_publication_t *)resource; - if (response->registration_id == publication->original_registration_id) - { - // TODO: Use a union. - conductor->error_frame_handler( - conductor->error_handler_clientd, (aeron_publication_error_values_t *)response); - } + // TODO: Use a union or a copy... + conductor->error_frame_handler( + conductor->error_handler_clientd, (aeron_publication_error_values_t *)response); } } diff --git a/aeron-client/src/main/cpp_wrapper/CMakeLists.txt b/aeron-client/src/main/cpp_wrapper/CMakeLists.txt index 1cf7e99d4a..468b0b4a5a 100644 --- a/aeron-client/src/main/cpp_wrapper/CMakeLists.txt +++ b/aeron-client/src/main/cpp_wrapper/CMakeLists.txt @@ -64,6 +64,7 @@ SET(HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/status/ReadablePosition.h ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/status/StatusIndicatorReader.h ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/status/UnsafeBufferPosition.h + ${CMAKE_CURRENT_SOURCE_DIR}/status/PublicationErrorFrame.h ${CMAKE_CURRENT_SOURCE_DIR}/util/BitUtil.h ${CMAKE_CURRENT_SOURCE_DIR}/util/CommandOption.h ${CMAKE_CURRENT_SOURCE_DIR}/util/CommandOptionParser.h diff --git a/aeron-client/src/main/cpp_wrapper/Context.h b/aeron-client/src/main/cpp_wrapper/Context.h index 86fb7325a1..39e2c981ba 100644 --- a/aeron-client/src/main/cpp_wrapper/Context.h +++ b/aeron-client/src/main/cpp_wrapper/Context.h @@ -229,7 +229,8 @@ class Context m_onNewSubscriptionHandler(other.m_onNewSubscriptionHandler), m_onAvailableCounterHandler(other.m_onAvailableCounterHandler), m_onUnavailableCounterHandler(other.m_onUnavailableCounterHandler), - m_onCloseClientHandler(other.m_onCloseClientHandler) + m_onCloseClientHandler(other.m_onCloseClientHandler), + m_onErrorFrameHandler(other.m_onErrorFrameHandler) { other.m_context = nullptr; } diff --git a/aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp b/aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp index 516c4112e7..d0c81ce92d 100644 --- a/aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp +++ b/aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp @@ -171,48 +171,48 @@ TEST_F(WrapperSystemTest, shouldRejectImage) POLL_FOR(0 < errorFrameCount, invoker); } -//TEST_F(WrapperSystemTest, shouldRejectImageForExclusive) -//{ -// Context ctx; -// ctx.useConductorAgentInvoker(true); -// -// std::atomic errorFrameCount{0}; -// -// on_error_frame_t errorFrameHandler = -// [&](aeron::status::PublicationErrorFrame &errorFrame) -// { -// std::atomic_fetch_add(&errorFrameCount, 1); -// return; -// }; -// -// ctx.errorFrameHandler(errorFrameHandler); -// std::shared_ptr aeron = Aeron::connect(ctx); -// AgentInvoker &invoker = aeron->conductorAgentInvoker(); -// invoker.start(); -// -// std::int64_t pubId = aeron->addExclusivePublication("aeron:udp?endpoint=localhost:10000", 10000); -// std::int64_t subId = aeron->addSubscription("aeron:udp?endpoint=localhost:10000", 10000); -// invoker.invoke(); -// -// POLL_FOR_NON_NULL(pub, aeron->findExclusivePublication(pubId), invoker); -// POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker); -// POLL_FOR(pub->isConnected() && sub->isConnected(), invoker); -// -// std::string message = "Hello World!"; -// -// const uint8_t *data = reinterpret_cast(message.c_str()); -// POLL_FOR(0 < pub->offer(data, message.length()), invoker); -// POLL_FOR(0 < sub->poll( -// [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header) -// { -// EXPECT_EQ(message, buffer.getStringWithoutLength(offset, length)); -// }, -// 1), invoker); -// -// POLL_FOR(1 == sub->imageCount(), invoker); -// -// const std::shared_ptr image = sub->imageByIndex(0); -// image->reject("No Longer Valid"); -// -// POLL_FOR(0 < errorFrameCount, invoker); -//} +TEST_F(WrapperSystemTest, shouldRejectImageForExclusive) +{ + Context ctx; + ctx.useConductorAgentInvoker(true); + + std::atomic errorFrameCount{0}; + + on_error_frame_t errorFrameHandler = + [&](aeron::status::PublicationErrorFrame &errorFrame) + { + std::atomic_fetch_add(&errorFrameCount, 1); + return; + }; + + ctx.errorFrameHandler(errorFrameHandler); + std::shared_ptr aeron = Aeron::connect(ctx); + AgentInvoker &invoker = aeron->conductorAgentInvoker(); + invoker.start(); + + std::int64_t pubId = aeron->addExclusivePublication("aeron:udp?endpoint=localhost:10000", 10000); + std::int64_t subId = aeron->addSubscription("aeron:udp?endpoint=localhost:10000", 10000); + invoker.invoke(); + + POLL_FOR_NON_NULL(pub, aeron->findExclusivePublication(pubId), invoker); + POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker); + POLL_FOR(pub->isConnected() && sub->isConnected(), invoker); + + std::string message = "Hello World!"; + + const uint8_t *data = reinterpret_cast(message.c_str()); + POLL_FOR(0 < pub->offer(data, message.length()), invoker); + POLL_FOR(0 < sub->poll( + [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header) + { + EXPECT_EQ(message, buffer.getStringWithoutLength(offset, length)); + }, + 1), invoker); + + POLL_FOR(1 == sub->imageCount(), invoker); + + const std::shared_ptr image = sub->imageByIndex(0); + image->reject("No Longer Valid"); + + POLL_FOR(0 < errorFrameCount, invoker); +} From 7ab586b2a5597898922c6779feb82c4efe36971d Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Fri, 2 Aug 2024 16:13:35 +1200 Subject: [PATCH 49/67] [Java] Checkstyle. --- aeron-client/src/main/java/io/aeron/ClientConductor.java | 1 - .../src/main/java/io/aeron/status/PublicationErrorFrame.java | 1 - aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java | 2 -- 3 files changed, 4 deletions(-) diff --git a/aeron-client/src/main/java/io/aeron/ClientConductor.java b/aeron-client/src/main/java/io/aeron/ClientConductor.java index 30730ee9a4..4a9cdefeed 100644 --- a/aeron-client/src/main/java/io/aeron/ClientConductor.java +++ b/aeron-client/src/main/java/io/aeron/ClientConductor.java @@ -30,7 +30,6 @@ import org.agrona.concurrent.status.CountersReader; import org.agrona.concurrent.status.UnsafeBufferPosition; -import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; diff --git a/aeron-client/src/main/java/io/aeron/status/PublicationErrorFrame.java b/aeron-client/src/main/java/io/aeron/status/PublicationErrorFrame.java index 426789a5a0..fa01f6b0bb 100644 --- a/aeron-client/src/main/java/io/aeron/status/PublicationErrorFrame.java +++ b/aeron-client/src/main/java/io/aeron/status/PublicationErrorFrame.java @@ -15,7 +15,6 @@ */ package io.aeron.status; -import io.aeron.ErrorCode; import io.aeron.command.PublicationErrorFrameFlyweight; import java.net.InetSocketAddress; diff --git a/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java b/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java index c6b736182a..5e4c5b6cf7 100644 --- a/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java +++ b/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java @@ -16,7 +16,6 @@ package io.aeron; import io.aeron.driver.MediaDriver; -import io.aeron.driver.PublicationImage; import io.aeron.driver.ThreadingMode; import io.aeron.exceptions.AeronException; import io.aeron.status.PublicationErrorFrame; @@ -48,7 +47,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.BiFunction; import java.util.function.Function; import static io.aeron.driver.status.SystemCounterDescriptor.ERRORS; From 27149044b7c6c4a82fe6f3260ded0f988fc845b6 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Fri, 2 Aug 2024 16:50:26 +1200 Subject: [PATCH 50/67] [C++] Compilation warning. --- .../src/main/cpp_wrapper/status/PublicationErrorFrame.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/aeron-client/src/main/cpp_wrapper/status/PublicationErrorFrame.h b/aeron-client/src/main/cpp_wrapper/status/PublicationErrorFrame.h index 9a61227755..13ac0a6a7c 100644 --- a/aeron-client/src/main/cpp_wrapper/status/PublicationErrorFrame.h +++ b/aeron-client/src/main/cpp_wrapper/status/PublicationErrorFrame.h @@ -17,6 +17,11 @@ class PublicationErrorFrame { } + std::int64_t registrationId() + { + return m_errorValues->registration_id; + } + private: aeron_publication_error_values_t *m_errorValues; }; From 76f464f6cc0ba0e8c78e58882b3dfa75155f869e Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Mon, 5 Aug 2024 09:39:39 +1200 Subject: [PATCH 51/67] [Java] Tidy test and docs. --- .../java/io/aeron/command/RejectImageFlyweight.java | 2 +- aeron-client/src/test/java/io/aeron/ImageTest.java | 12 +----------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/aeron-client/src/main/java/io/aeron/command/RejectImageFlyweight.java b/aeron-client/src/main/java/io/aeron/command/RejectImageFlyweight.java index 8af72f14a3..adf99c2ddc 100644 --- a/aeron-client/src/main/java/io/aeron/command/RejectImageFlyweight.java +++ b/aeron-client/src/main/java/io/aeron/command/RejectImageFlyweight.java @@ -23,7 +23,7 @@ import static org.agrona.BitUtil.SIZE_OF_LONG; /** - * Control message to invalidate an image for a subscription. + * Control message to reject an image for a subscription. * *

  *   0                   1                   2                   3
diff --git a/aeron-client/src/test/java/io/aeron/ImageTest.java b/aeron-client/src/test/java/io/aeron/ImageTest.java
index 295ff73583..110605c950 100644
--- a/aeron-client/src/test/java/io/aeron/ImageTest.java
+++ b/aeron-client/src/test/java/io/aeron/ImageTest.java
@@ -624,20 +624,10 @@ void shouldRejectFragment()
 
         assertEquals(initialPosition, image.position());
 
-        final String reason = "this is garbage";
+        final String reason = "this is frame is to be rejected";
         image.reject(reason);
 
         verify(subscription).rejectImage(image.correlationId(), image.position(), reason);
-
-//        final int fragmentsRead = image.boundedPoll(
-//            mockFragmentHandler, maxPosition, Integer.MAX_VALUE);
-//
-//        assertThat(fragmentsRead, is(1));
-//
-//        final InOrder inOrder = Mockito.inOrder(position, mockFragmentHandler);
-//        inOrder.verify(mockFragmentHandler).onFragment(
-//            any(UnsafeBuffer.class), eq(initialOffset + HEADER_LENGTH), eq(DATA.length), any(Header.class));
-//        inOrder.verify(position).setOrdered(TERM_BUFFER_LENGTH);
     }
 
 

From 10d449c4368685ee47c75d997d09b945a0656e74 Mon Sep 17 00:00:00 2001
From: Michael Barker 
Date: Mon, 5 Aug 2024 16:58:25 +1200
Subject: [PATCH 52/67] [C/C++] Add support for reading the fields from a
 PublisherErrorFrame.  Also add copy and move constructors for dealing with
 the limit lifecycle of callback objects.

---
 .../src/main/c/aeron_client_conductor.c       |  30 +++
 aeron-client/src/main/c/aeronc.h              |  28 ++-
 .../main/c/command/aeron_control_protocol.h   |   2 -
 aeron-client/src/main/cpp_wrapper/Context.h   |   8 +-
 .../status/PublicationErrorFrame.h            |  74 +++++++-
 .../src/test/cpp_wrapper/CMakeLists.txt       |   1 +
 .../src/test/cpp_wrapper/RejectImageTest.cpp  | 179 ++++++++++++++++++
 .../test/cpp_wrapper/WrapperSystemTest.cpp    |  94 +--------
 .../src/main/c/aeron_driver_conductor.c       |   6 +-
 9 files changed, 315 insertions(+), 107 deletions(-)
 create mode 100644 aeron-client/src/test/cpp_wrapper/RejectImageTest.cpp

diff --git a/aeron-client/src/main/c/aeron_client_conductor.c b/aeron-client/src/main/c/aeron_client_conductor.c
index 946bd8dcf9..f731083e10 100644
--- a/aeron-client/src/main/c/aeron_client_conductor.c
+++ b/aeron-client/src/main/c/aeron_client_conductor.c
@@ -2581,6 +2581,36 @@ int aeron_client_conductor_on_error_frame(aeron_client_conductor_t *conductor, a
     return 0;
 }
 
+int aeron_publication_error_values_copy(aeron_publication_error_values_t **dst, aeron_publication_error_values_t *src)
+{
+    if (NULL == src)
+    {
+        AERON_SET_ERR(-1, "%s", "src must not be NULL");
+        return -1;
+    }
+
+    if (NULL == dst)
+    {
+        AERON_SET_ERR(-1, "%s", "dst must not be NULL");
+        return -1;
+    }
+
+    size_t error_values_size = sizeof(*src) + (size_t)src->error_message_length;
+    if (aeron_alloc((void **)dst, error_values_size) < 0)
+    {
+        AERON_APPEND_ERR("%s", "");
+        return -1;
+    }
+
+    memcpy((void *)*dst, (void *)src, error_values_size);
+    return 0;
+}
+
+void aeron_publication_error_values_delete(aeron_publication_error_values_t *to_delete)
+{
+    aeron_free(to_delete);
+}
+
 aeron_subscription_t *aeron_client_conductor_find_subscription_by_id(
     aeron_client_conductor_t *conductor, int64_t registration_id)
 {
diff --git a/aeron-client/src/main/c/aeronc.h b/aeron-client/src/main/c/aeronc.h
index 0e173bbde6..d40c6dc76b 100644
--- a/aeron-client/src/main/c/aeronc.h
+++ b/aeron-client/src/main/c/aeronc.h
@@ -34,6 +34,9 @@ extern "C"
 #define AERON_CLIENT_ERROR_BUFFER_FULL (-1003)
 #define AERON_CLIENT_MAX_LOCAL_ADDRESS_STR_LEN (64)
 
+#define AERON_RESPONSE_ADDRESS_TYPE_IPV4 (0x1)
+#define AERON_RESPONSE_ADDRESS_TYPE_IPV6 (0x2)
+
 typedef struct aeron_context_stct aeron_context_t;
 typedef struct aeron_stct aeron_t;
 typedef struct aeron_buffer_claim_stct aeron_buffer_claim_t;
@@ -73,8 +76,8 @@ struct aeron_publication_error_values_stct
     int64_t receiver_id;
     int64_t group_tag;
     int16_t address_type;
-    uint16_t address_port;
-    uint8_t address[16];
+    uint16_t source_port;
+    uint8_t source_address[16];
     int32_t error_code;
     int32_t error_message_length;
     uint8_t error_message[1];
@@ -148,10 +151,29 @@ const char *aeron_context_get_client_name(aeron_context_t *context);
 typedef void (*aeron_error_handler_t)(void *clientd, int errcode, const char *message);
 
 /**
- * The error frame handler to be called when the driver notifies the client about an error frame being received
+ * The error frame handler to be called when the driver notifies the client about an error frame being received.
+ * The data passed to this callback will only be valid for the lifetime of the callback. The user should use
+ * aeron_publication_error_values_copy if they require the data to live longer than that.
  */
 typedef void (*aeron_error_frame_handler_t)(void *clientd, aeron_publication_error_values_t *error_frame);
 
+/**
+ * Copy an existing aeron_publication_error_values_t to the supplied pointer. The caller is responsible for freeing the
+ * allocated memory using aeron_publication_error_values_delete when the copy is not longer required.
+ *
+ * @param dst to copy the values to.
+ * @param src to copy the values from.
+ * @return 0 if this is successful, -1 otherwise. Will set aeron_errcode() and aeron_errmsg() on failure.
+ */
+int aeron_publication_error_values_copy(aeron_publication_error_values_t **dst, aeron_publication_error_values_t *src);
+
+/**
+ * Delete a instance of aeron_publication_error_values_t that was created when making a copy
+ * (aeron_publication_error_values_copy). This should not be use on the pointer received via the aeron_frame_handler_t.
+ * @param to_delete to be deleted.
+ */
+void aeron_publication_error_values_delete(aeron_publication_error_values_t *to_delete);
+
 /**
  * Generalised notification callback.
  */
diff --git a/aeron-client/src/main/c/command/aeron_control_protocol.h b/aeron-client/src/main/c/command/aeron_control_protocol.h
index 2127efa5e7..c0e6f9c18f 100644
--- a/aeron-client/src/main/c/command/aeron_control_protocol.h
+++ b/aeron-client/src/main/c/command/aeron_control_protocol.h
@@ -50,8 +50,6 @@
 #define AERON_RESPONSE_ON_CLIENT_TIMEOUT (0x0F0A)
 #define AERON_RESPONSE_ON_STATIC_COUNTER (0x0F0B)
 #define AERON_RESPONSE_ON_PUBLICATION_ERROR (0x0F0C)
-#define AERON_RESPONSE_ADDRESS_TYPE_IPV4 (0x1)
-#define AERON_RESPONSE_ADDRESS_TYPE_IPV6 (0x2)
 
 /* error codes */
 #define AERON_ERROR_CODE_UNKNOWN_CODE_VALUE (-1)
diff --git a/aeron-client/src/main/cpp_wrapper/Context.h b/aeron-client/src/main/cpp_wrapper/Context.h
index 39e2c981ba..643c5804db 100644
--- a/aeron-client/src/main/cpp_wrapper/Context.h
+++ b/aeron-client/src/main/cpp_wrapper/Context.h
@@ -134,7 +134,7 @@ typedef std::function on_close_client_t;
 
-typedef std::function on_error_frame_t;
+typedef std::function on_publication_error_frame_t;
 
 const static long NULL_TIMEOUT = -1;
 const static long DEFAULT_MEDIA_DRIVER_TIMEOUT_MS = 10000;
@@ -534,7 +534,7 @@ class Context
         return *this;
     }
 
-    inline this_t &errorFrameHandler(on_error_frame_t &handler)
+    inline this_t &errorFrameHandler(on_publication_error_frame_t &handler)
     {
         m_onErrorFrameHandler = handler;
         return *this;
@@ -590,7 +590,7 @@ class Context
     on_available_counter_t m_onAvailableCounterHandler = defaultOnAvailableCounterHandler;
     on_unavailable_counter_t m_onUnavailableCounterHandler = defaultOnUnavailableCounterHandler;
     on_close_client_t m_onCloseClientHandler = defaultOnCloseClientHandler;
-    on_error_frame_t m_onErrorFrameHandler = defaultOnErrorFrameHandler;
+    on_publication_error_frame_t m_onErrorFrameHandler = defaultOnErrorFrameHandler;
 
     void attachCallbacksToContext()
     {
@@ -711,7 +711,7 @@ class Context
 
     static void errorFrameHandlerCallback(void *clientd, aeron_publication_error_values_t *error_frame)
     {
-        on_error_frame_t &handler = *reinterpret_cast(clientd);
+        on_publication_error_frame_t &handler = *reinterpret_cast(clientd);
         aeron::status::PublicationErrorFrame errorFrame{error_frame};
         handler(errorFrame);
     }
diff --git a/aeron-client/src/main/cpp_wrapper/status/PublicationErrorFrame.h b/aeron-client/src/main/cpp_wrapper/status/PublicationErrorFrame.h
index 13ac0a6a7c..91ee5816f2 100644
--- a/aeron-client/src/main/cpp_wrapper/status/PublicationErrorFrame.h
+++ b/aeron-client/src/main/cpp_wrapper/status/PublicationErrorFrame.h
@@ -13,21 +13,89 @@ class PublicationErrorFrame
 {
 public:
     PublicationErrorFrame(aeron_publication_error_values_t *errorValues) :
-        m_errorValues(errorValues)
+        m_errorValues(errorValues), m_ownsErrorValuesPtr(false)
     {
     }
 
+    PublicationErrorFrame(PublicationErrorFrame &other)
+    {
+        if (aeron_publication_error_values_copy(&this->m_errorValues, other.m_errorValues) < 0)
+        {
+            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;
+        }
+    }
+
+    PublicationErrorFrame& operator=(PublicationErrorFrame& other)
+    {
+        if (aeron_publication_error_values_copy(&this->m_errorValues, other.m_errorValues) < 0)
+        {
+            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;
+        }
+
+        m_ownsErrorValuesPtr = true;
+        return *this;
+    }
+
+    PublicationErrorFrame(PublicationErrorFrame &&other)
+        : m_errorValues(other.m_errorValues), m_ownsErrorValuesPtr(other.m_ownsErrorValuesPtr)
+    {
+        other.m_errorValues = nullptr;
+        other.m_ownsErrorValuesPtr = false;
+    }
+
+    ~PublicationErrorFrame()
+    {
+        if (m_ownsErrorValuesPtr)
+        {
+            aeron_publication_error_values_delete(this->m_errorValues);
+        }
+    }
+
     std::int64_t registrationId()
     {
         return m_errorValues->registration_id;
     }
 
+    std::int32_t sessionId()
+    {
+        return m_errorValues->session_id;
+    }
+
+    std::int32_t streamId()
+    {
+        return m_errorValues->stream_id;
+    }
+
+    std::int64_t groupTag()
+    {
+        return m_errorValues->group_tag;
+    }
+
+    std::uint16_t sourcePort()
+    {
+        return m_errorValues->source_port;
+    }
+
+    std::uint8_t* sourceAddress()
+    {
+        return m_errorValues->source_address;
+    }
+
+    std::int16_t sourceAddressType()
+    {
+        return m_errorValues->address_type;
+    }
+
+    bool isValid()
+    {
+        return nullptr != m_errorValues;
+    }
+
 private:
     aeron_publication_error_values_t *m_errorValues;
+    bool m_ownsErrorValuesPtr;
 };
 
-
-
 }}
 
 #endif //AERON_PUBLICATIONERRORFRAME_H
diff --git a/aeron-client/src/test/cpp_wrapper/CMakeLists.txt b/aeron-client/src/test/cpp_wrapper/CMakeLists.txt
index 11a6cb89e5..6647185821 100644
--- a/aeron-client/src/test/cpp_wrapper/CMakeLists.txt
+++ b/aeron-client/src/test/cpp_wrapper/CMakeLists.txt
@@ -74,5 +74,6 @@ if (AERON_UNIT_TESTS)
     aeron_client_wrapper_test(imageFragmentAssemblerTest ImageFragmentAssemblerTest.cpp)
     aeron_client_wrapper_test(controlledFragmentAssemblerTest ControlledFragmentAssemblerTest.cpp)
     aeron_client_wrapper_test(imageControlledFragmentAssemblerTest ImageControlledFragmentAssemblerTest.cpp)
+    aeron_client_wrapper_test(rejectImageTest RejectImageTest.cpp)
 
 endif ()
\ No newline at end of file
diff --git a/aeron-client/src/test/cpp_wrapper/RejectImageTest.cpp b/aeron-client/src/test/cpp_wrapper/RejectImageTest.cpp
new file mode 100644
index 0000000000..181d5a3b65
--- /dev/null
+++ b/aeron-client/src/test/cpp_wrapper/RejectImageTest.cpp
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2014-2024 Real Logic Limited.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include 
+
+#include 
+
+#include "EmbeddedMediaDriver.h"
+#include "Aeron.h"
+#include "TestUtil.h"
+#include 
+#include 
+
+using namespace aeron;
+
+class RejectImageTest : public testing::TestWithParam
+{
+public:
+    RejectImageTest()
+    {
+        m_driver.start();
+    }
+
+    ~RejectImageTest() override
+    {
+        m_driver.stop();
+    }
+
+    static std::int32_t typeId(CountersReader &reader, std::int32_t counterId)
+    {
+        const index_t offset = aeron::concurrent::CountersReader::metadataOffset(counterId);
+        return reader.metaDataBuffer().getInt32(offset + CountersReader::TYPE_ID_OFFSET);
+    }
+
+    std::string addressAsString(int16_t addressType, uint8_t *address)
+    {
+        if (AERON_RESPONSE_ADDRESS_TYPE_IPV4 == addressType)
+        {
+            char buffer[INET_ADDRSTRLEN] = {};
+            inet_ntop(AF_INET, address, buffer, sizeof(buffer));
+            return std::string{buffer};
+        }
+        else if (AERON_RESPONSE_ADDRESS_TYPE_IPV6 == addressType)
+        {
+            char buffer[INET6_ADDRSTRLEN] = {};
+            inet_ntop(AF_INET6, address, buffer, sizeof(buffer));
+            return "[" + std::string{buffer} + "]";
+        }
+
+        return "";
+    }
+
+protected:
+    EmbeddedMediaDriver m_driver;
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    RejectImageTestWithParam, RejectImageTest, testing::Values("127.0.0.1", "[::1]"));
+
+TEST_P(RejectImageTest, shouldRejectImage)
+{
+    Context ctx;
+    ctx.useConductorAgentInvoker(true);
+    std::string address = GetParam();
+    std::uint16_t port = 10000;
+    std::string control = address + ":" + std::to_string(10001);
+    std::string endpoint = address + ":" + std::to_string(port);
+
+    aeron::status::PublicationErrorFrame error{ nullptr };
+
+    on_publication_error_frame_t errorFrameHandler =
+        [&](aeron::status::PublicationErrorFrame &errorFrame)
+        {
+            error = errorFrame;
+            return;
+        };
+
+    ctx.errorFrameHandler(errorFrameHandler);
+    std::shared_ptr aeron = Aeron::connect(ctx);
+    AgentInvoker &invoker = aeron->conductorAgentInvoker();
+    invoker.start();
+
+    std::int64_t groupTag = 99999;
+    std::string mdc = "aeron:udp?control-mode=dynamic|control=" + control + "|fc=tagged,g:" + std::to_string(groupTag);
+    std::string channel = "aeron:udp?endpoint=" + endpoint + "|control=" + control + "|gtag=" + std::to_string(groupTag);
+
+    std::int64_t pubId = aeron->addPublication(mdc, 10000);
+    std::int64_t subId = aeron->addSubscription(channel, 10000);
+    invoker.invoke();
+
+    POLL_FOR_NON_NULL(pub, aeron->findPublication(pubId), invoker);
+    POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);
+    POLL_FOR(pub->isConnected() && sub->isConnected(), invoker);
+
+    std::string message = "Hello World!";
+
+    const uint8_t *data = reinterpret_cast(message.c_str());
+    POLL_FOR(0 < pub->offer(data, message.length()), invoker);
+    POLL_FOR(0 < sub->poll(
+        [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)
+        {
+            EXPECT_EQ(message, buffer.getStringWithoutLength(offset, length));
+        },
+        1), invoker);
+
+    POLL_FOR(1 == sub->imageCount(), invoker);
+
+    const std::shared_ptr image = sub->imageByIndex(0);
+    image->reject("No Longer Valid");
+
+    POLL_FOR(error.isValid(), invoker);
+    ASSERT_EQ(pubId, error.registrationId());
+    ASSERT_EQ(pub->sessionId(), error.sessionId());
+    ASSERT_EQ(pub->streamId(), error.streamId());
+    ASSERT_EQ(groupTag, error.groupTag());
+    ASSERT_EQ(port, error.sourcePort());
+    ASSERT_EQ(address, addressAsString(error.sourceAddressType(), error.sourceAddress()));
+}
+
+TEST_P(RejectImageTest, shouldRejectImageForExclusive)
+{
+    std::string address = GetParam();
+
+    Context ctx;
+    ctx.useConductorAgentInvoker(true);
+
+    std::atomic errorFrameCount{0};
+
+    on_publication_error_frame_t errorFrameHandler =
+        [&](aeron::status::PublicationErrorFrame &errorFrame)
+        {
+            std::atomic_fetch_add(&errorFrameCount, 1);
+            return;
+        };
+
+    ctx.errorFrameHandler(errorFrameHandler);
+    std::shared_ptr aeron = Aeron::connect(ctx);
+    AgentInvoker &invoker = aeron->conductorAgentInvoker();
+    invoker.start();
+
+    std::int64_t pubId = aeron->addExclusivePublication("aeron:udp?endpoint=" + address + ":10000", 10000);
+    std::int64_t subId = aeron->addSubscription("aeron:udp?endpoint=" + address + ":10000", 10000);
+    invoker.invoke();
+
+    POLL_FOR_NON_NULL(pub, aeron->findExclusivePublication(pubId), invoker);
+    POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);
+    POLL_FOR(pub->isConnected() && sub->isConnected(), invoker);
+
+    std::string message = "Hello World!";
+
+    const uint8_t *data = reinterpret_cast(message.c_str());
+    POLL_FOR(0 < pub->offer(data, message.length()), invoker);
+    POLL_FOR(0 < sub->poll(
+        [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)
+        {
+            EXPECT_EQ(message, buffer.getStringWithoutLength(offset, length));
+        },
+        1), invoker);
+
+    POLL_FOR(1 == sub->imageCount(), invoker);
+
+    const std::shared_ptr image = sub->imageByIndex(0);
+    image->reject("No Longer Valid");
+
+    POLL_FOR(0 < errorFrameCount, invoker);
+}
diff --git a/aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp b/aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp
index d0c81ce92d..cef6624b61 100644
--- a/aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp
+++ b/aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp
@@ -21,6 +21,8 @@
 #include "EmbeddedMediaDriver.h"
 #include "Aeron.h"
 #include "TestUtil.h"
+#include 
+#include 
 
 using namespace aeron;
 
@@ -124,95 +126,3 @@ TEST_F(WrapperSystemTest, shouldRejectClientNameThatIsTooLong)
         ASSERT_NE(nullptr, string) << ex.what();
     }
 }
-
-TEST_F(WrapperSystemTest, shouldRejectImage)
-{
-    Context ctx;
-    ctx.useConductorAgentInvoker(true);
-
-    std::atomic errorFrameCount{0};
-
-    on_error_frame_t errorFrameHandler =
-        [&](aeron::status::PublicationErrorFrame &errorFrame)
-        {
-            std::atomic_fetch_add(&errorFrameCount, 1);
-            return;
-        };
-
-    ctx.errorFrameHandler(errorFrameHandler);
-    std::shared_ptr aeron = Aeron::connect(ctx);
-    AgentInvoker &invoker = aeron->conductorAgentInvoker();
-    invoker.start();
-
-    std::int64_t pubId = aeron->addPublication("aeron:udp?endpoint=localhost:10000", 10000);
-    std::int64_t subId = aeron->addSubscription("aeron:udp?endpoint=localhost:10000", 10000);
-    invoker.invoke();
-
-    POLL_FOR_NON_NULL(pub, aeron->findPublication(pubId), invoker);
-    POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);
-    POLL_FOR(pub->isConnected() && sub->isConnected(), invoker);
-
-    std::string message = "Hello World!";
-
-    const uint8_t *data = reinterpret_cast(message.c_str());
-    POLL_FOR(0 < pub->offer(data, message.length()), invoker);
-    POLL_FOR(0 < sub->poll(
-        [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)
-        {
-            EXPECT_EQ(message, buffer.getStringWithoutLength(offset, length));
-        },
-        1), invoker);
-
-    POLL_FOR(1 == sub->imageCount(), invoker);
-
-    const std::shared_ptr image = sub->imageByIndex(0);
-    image->reject("No Longer Valid");
-
-    POLL_FOR(0 < errorFrameCount, invoker);
-}
-
-TEST_F(WrapperSystemTest, shouldRejectImageForExclusive)
-{
-    Context ctx;
-    ctx.useConductorAgentInvoker(true);
-
-    std::atomic errorFrameCount{0};
-
-    on_error_frame_t errorFrameHandler =
-        [&](aeron::status::PublicationErrorFrame &errorFrame)
-        {
-            std::atomic_fetch_add(&errorFrameCount, 1);
-            return;
-        };
-
-    ctx.errorFrameHandler(errorFrameHandler);
-    std::shared_ptr aeron = Aeron::connect(ctx);
-    AgentInvoker &invoker = aeron->conductorAgentInvoker();
-    invoker.start();
-
-    std::int64_t pubId = aeron->addExclusivePublication("aeron:udp?endpoint=localhost:10000", 10000);
-    std::int64_t subId = aeron->addSubscription("aeron:udp?endpoint=localhost:10000", 10000);
-    invoker.invoke();
-
-    POLL_FOR_NON_NULL(pub, aeron->findExclusivePublication(pubId), invoker);
-    POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);
-    POLL_FOR(pub->isConnected() && sub->isConnected(), invoker);
-
-    std::string message = "Hello World!";
-
-    const uint8_t *data = reinterpret_cast(message.c_str());
-    POLL_FOR(0 < pub->offer(data, message.length()), invoker);
-    POLL_FOR(0 < sub->poll(
-        [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)
-        {
-            EXPECT_EQ(message, buffer.getStringWithoutLength(offset, length));
-        },
-        1), invoker);
-
-    POLL_FOR(1 == sub->imageCount(), invoker);
-
-    const std::shared_ptr image = sub->imageByIndex(0);
-    image->reject("No Longer Valid");
-
-    POLL_FOR(0 < errorFrameCount, invoker);
-}
diff --git a/aeron-driver/src/main/c/aeron_driver_conductor.c b/aeron-driver/src/main/c/aeron_driver_conductor.c
index 33c0657757..1e66ffab93 100644
--- a/aeron-driver/src/main/c/aeron_driver_conductor.c
+++ b/aeron-driver/src/main/c/aeron_driver_conductor.c
@@ -2570,12 +2570,12 @@ void on_error(
 
 void aeron_driver_conductor_on_publication_error(void *clientd, void *item)
 {
-    uint8_t buffer[sizeof(aeron_publication_error_values_t) + (AERON_ERROR_MAX_TEXT_LENGTH - 1)];
+    uint8_t buffer[sizeof(aeron_publication_error_t) + (AERON_ERROR_MAX_TEXT_LENGTH - 1)];
     aeron_driver_conductor_t *conductor = clientd;
     aeron_command_publication_error_t *error = item;
     aeron_driver_conductor_log_explicit_error(conductor, error->error_code, (const char *)error->error_text);
 
-    aeron_publication_error_values_t *response = (aeron_publication_error_values_t *)buffer;
+    aeron_publication_error_t *response = (aeron_publication_error_t *)buffer;
     response->error_code = error->error_code;
     response->registration_id = error->registration_id;
     response->session_id = error->session_id;
@@ -2591,7 +2591,7 @@ void aeron_driver_conductor_on_publication_error(void *clientd, void *item)
         response->address_port = ntohs(src_addr_in->sin_port);
         memcpy(&response->address[0], &src_addr_in->sin_addr, sizeof(src_addr_in->sin_addr));
     }
-    else if (AF_INET6)
+    else if (AF_INET6 == error->src_address.ss_family)
     {
         struct sockaddr_in6 *src_addr_in6 = (struct sockaddr_in6 *)&error->src_address;
         response->address_type = AERON_RESPONSE_ADDRESS_TYPE_IPV6;

From 7dbef695d8ef28c2f5ec2ec1174fc327d4525f8d Mon Sep 17 00:00:00 2001
From: Michael Barker 
Date: Mon, 5 Aug 2024 17:01:50 +1200
Subject: [PATCH 53/67] [C/C++] Add comments and fix header.

---
 .../status/PublicationErrorFrame.h            | 29 +++++++++++++++----
 1 file changed, 24 insertions(+), 5 deletions(-)

diff --git a/aeron-client/src/main/cpp_wrapper/status/PublicationErrorFrame.h b/aeron-client/src/main/cpp_wrapper/status/PublicationErrorFrame.h
index 91ee5816f2..82c4bebdeb 100644
--- a/aeron-client/src/main/cpp_wrapper/status/PublicationErrorFrame.h
+++ b/aeron-client/src/main/cpp_wrapper/status/PublicationErrorFrame.h
@@ -1,6 +1,18 @@
-//
-// Created by mike on 31/07/24.
-//
+/*
+ * Copyright 2014-2024 Real Logic Limited.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 #ifndef AERON_PUBLICATIONERRORFRAME_H
 #define AERON_PUBLICATIONERRORFRAME_H
@@ -12,8 +24,15 @@ namespace aeron { namespace status {
 class PublicationErrorFrame
 {
 public:
-    PublicationErrorFrame(aeron_publication_error_values_t *errorValues) :
-        m_errorValues(errorValues), m_ownsErrorValuesPtr(false)
+    /**
+     * Constructs from a supplied C pointer to the aeron_publication_error_values_t and wraps over the top of it.
+     * By default it won't manage the underlying memory of the C structure.
+     *
+     * @param errorValues C structure holding the actual data.
+     * @param ownsErrorValuesPtr to indicate if the destructor of this class should free the underlying C memory.
+     */
+    PublicationErrorFrame(aeron_publication_error_values_t *errorValues, bool ownsErrorValuesPtr = false) :
+        m_errorValues(errorValues), m_ownsErrorValuesPtr(ownsErrorValuesPtr)
     {
     }
 

From 6ce16db847986b786affc6a3340942af56074978 Mon Sep 17 00:00:00 2001
From: Michael Barker 
Date: Mon, 5 Aug 2024 11:14:51 -1100
Subject: [PATCH 54/67] [C/C++] Fix compile error on Window.

---
 aeron-client/src/main/c/aeron_client_conductor.c        | 2 +-
 aeron-client/src/test/cpp_wrapper/RejectImageTest.cpp   | 9 ++++-----
 aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp | 2 --
 3 files changed, 5 insertions(+), 8 deletions(-)

diff --git a/aeron-client/src/main/c/aeron_client_conductor.c b/aeron-client/src/main/c/aeron_client_conductor.c
index f731083e10..9efa125654 100644
--- a/aeron-client/src/main/c/aeron_client_conductor.c
+++ b/aeron-client/src/main/c/aeron_client_conductor.c
@@ -2973,7 +2973,7 @@ int aeron_client_conductor_reject_image(
     aeron_reject_image_command_t *command = (aeron_reject_image_command_t *)ptr;
     command->image_correlation_id = image_correlation_id;
     command->position = position;
-    command->reason_length = reason_length;
+    command->reason_length = (int32_t)reason_length;
     memcpy(ptr + offsetof(aeron_reject_image_command_t, reason_text), reason, reason_length);
     command->reason_text[reason_length] = '\0';
 
diff --git a/aeron-client/src/test/cpp_wrapper/RejectImageTest.cpp b/aeron-client/src/test/cpp_wrapper/RejectImageTest.cpp
index 181d5a3b65..cdd9de722d 100644
--- a/aeron-client/src/test/cpp_wrapper/RejectImageTest.cpp
+++ b/aeron-client/src/test/cpp_wrapper/RejectImageTest.cpp
@@ -21,8 +21,7 @@
 #include "EmbeddedMediaDriver.h"
 #include "Aeron.h"
 #include "TestUtil.h"
-#include 
-#include 
+#include "aeron_socket.h"
 
 using namespace aeron;
 
@@ -45,7 +44,7 @@ class RejectImageTest : public testing::TestWithParam
         return reader.metaDataBuffer().getInt32(offset + CountersReader::TYPE_ID_OFFSET);
     }
 
-    std::string addressAsString(int16_t addressType, uint8_t *address)
+    static std::string addressAsString(int16_t addressType, uint8_t *address)
     {
         if (AERON_RESPONSE_ADDRESS_TYPE_IPV4 == addressType)
         {
@@ -107,7 +106,7 @@ TEST_P(RejectImageTest, shouldRejectImage)
 
     std::string message = "Hello World!";
 
-    const uint8_t *data = reinterpret_cast(message.c_str());
+    auto *data = reinterpret_cast(message.c_str());
     POLL_FOR(0 < pub->offer(data, message.length()), invoker);
     POLL_FOR(0 < sub->poll(
         [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)
@@ -161,7 +160,7 @@ TEST_P(RejectImageTest, shouldRejectImageForExclusive)
 
     std::string message = "Hello World!";
 
-    const uint8_t *data = reinterpret_cast(message.c_str());
+    auto *data = reinterpret_cast(message.c_str());
     POLL_FOR(0 < pub->offer(data, message.length()), invoker);
     POLL_FOR(0 < sub->poll(
         [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)
diff --git a/aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp b/aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp
index cef6624b61..b194c890dd 100644
--- a/aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp
+++ b/aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp
@@ -21,8 +21,6 @@
 #include "EmbeddedMediaDriver.h"
 #include "Aeron.h"
 #include "TestUtil.h"
-#include 
-#include 
 
 using namespace aeron;
 

From 86525fce613a968df487fc0313ecbefd0ac550f3 Mon Sep 17 00:00:00 2001
From: Michael Barker 
Date: Tue, 6 Aug 2024 13:14:55 +1200
Subject: [PATCH 55/67] [Java] Add test to verify rejection doesn't work for
 IPC.

---
 .../test/java/io/aeron/RejectImageTest.java   | 28 ++++++++++++++++++-
 1 file changed, 27 insertions(+), 1 deletion(-)

diff --git a/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java b/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java
index 5e4c5b6cf7..ee1b35269e 100644
--- a/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java
+++ b/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java
@@ -394,7 +394,7 @@ void shouldRejectSubscriptionsImageManualMdc()
 
     @Test
     @InterruptAfter(10)
-    void shouldRejectInvalidationReasonThatIsTooLong()
+    void shouldErrorIfRejectionReasonIsTooLong()
     {
         context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3));
         final byte[] bytes = new byte[1024];
@@ -417,6 +417,32 @@ void shouldRejectInvalidationReasonThatIsTooLong()
         }
     }
 
+    @Test
+    @InterruptAfter(10)
+    void shouldErrorIfUsingAndIpcChannel()
+    {
+        context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3));
+        final String rejectionReason = "Reject this";
+
+        final TestMediaDriver driver = launch();
+
+        final Aeron.Context ctx = new Aeron.Context()
+            .aeronDirectoryName(driver.aeronDirectoryName());
+
+        try (Aeron aeron = Aeron.connect(ctx);
+            Publication pub = aeron.addPublication(CommonContext.IPC_CHANNEL, streamId);
+            Subscription sub = aeron.addSubscription(CommonContext.IPC_CHANNEL, streamId))
+        {
+            Tests.awaitConnected(pub);
+            Tests.awaitConnected(sub);
+
+            final AeronException ex = assertThrows(AeronException.class, () -> sub.imageAtIndex(0)
+                .reject(rejectionReason));
+            System.out.println(ex.getMessage());
+        }
+    }
+
+
     @ParameterizedTest
     @ValueSource(strings = { "127.0.0.1", "[::1]" })
     @InterruptAfter(5)

From b348404a7a3d7f6324c7dd330944769d3caf253a Mon Sep 17 00:00:00 2001
From: Michael Barker 
Date: Thu, 8 Aug 2024 09:26:35 +1200
Subject: [PATCH 56/67] [C] Use correct client data for the publication error
 frame callback within the client conductor.

---
 aeron-client/src/main/c/aeron_client_conductor.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/aeron-client/src/main/c/aeron_client_conductor.c b/aeron-client/src/main/c/aeron_client_conductor.c
index 9efa125654..4ba5dfc577 100644
--- a/aeron-client/src/main/c/aeron_client_conductor.c
+++ b/aeron-client/src/main/c/aeron_client_conductor.c
@@ -166,7 +166,7 @@ int aeron_client_conductor_init(aeron_client_conductor_t *conductor, aeron_conte
     conductor->error_handler_clientd = context->error_handler_clientd;
 
     conductor->error_frame_handler = context->error_frame_handler;
-    conductor->error_handler_clientd = context->error_frame_handler_clientd;
+    conductor->error_frame_handler_clientd = context->error_frame_handler_clientd;
 
     conductor->on_new_publication = context->on_new_publication;
     conductor->on_new_publication_clientd = context->on_new_publication_clientd;
@@ -2564,7 +2564,7 @@ void aeron_client_conductor_forward_error(void *clientd, int64_t key, void *valu
     {
         // TODO: Use a union or a copy...
         conductor->error_frame_handler(
-            conductor->error_handler_clientd, (aeron_publication_error_values_t *)response);
+            conductor->error_frame_handler_clientd, (aeron_publication_error_values_t *)response);
     }
 }
 

From efe1e5a800dcd6431c2ba5a7a52c56c3dc29fbe0 Mon Sep 17 00:00:00 2001
From: Michael Barker 
Date: Thu, 8 Aug 2024 14:12:13 +1200
Subject: [PATCH 57/67] [C/C++] Add test to validate that publication error
 frames are passed to all clients with the same publication and not to ones
 that without it.

---
 aeron-client/src/main/cpp_wrapper/Context.h   |  7 ++
 .../src/test/cpp_wrapper/RejectImageTest.cpp  | 77 +++++++++++++++++++
 2 files changed, 84 insertions(+)

diff --git a/aeron-client/src/main/cpp_wrapper/Context.h b/aeron-client/src/main/cpp_wrapper/Context.h
index 643c5804db..b7c1420e93 100644
--- a/aeron-client/src/main/cpp_wrapper/Context.h
+++ b/aeron-client/src/main/cpp_wrapper/Context.h
@@ -534,6 +534,13 @@ class Context
         return *this;
     }
 
+    /**
+     * Set handler to receive notifications when a publication error is received for a publication that this client
+     * is interested in.
+     *
+     * @param handler
+     * @return reference to this Context instance.
+     */
     inline this_t &errorFrameHandler(on_publication_error_frame_t &handler)
     {
         m_onErrorFrameHandler = handler;
diff --git a/aeron-client/src/test/cpp_wrapper/RejectImageTest.cpp b/aeron-client/src/test/cpp_wrapper/RejectImageTest.cpp
index cdd9de722d..250cb2b4cf 100644
--- a/aeron-client/src/test/cpp_wrapper/RejectImageTest.cpp
+++ b/aeron-client/src/test/cpp_wrapper/RejectImageTest.cpp
@@ -62,6 +62,21 @@ class RejectImageTest : public testing::TestWithParam
         return "";
     }
 
+    static std::shared_ptr connectClient(std::atomic &counter)
+    {
+        Context ctx;
+        ctx.useConductorAgentInvoker(true);
+
+        on_publication_error_frame_t errorFrameHandler =
+            [&](aeron::status::PublicationErrorFrame &errorFrame)
+            {
+                std::atomic_fetch_add(&counter, 1);
+            };
+
+        ctx.errorFrameHandler(errorFrameHandler);
+        return Aeron::connect(ctx);
+    }
+
 protected:
     EmbeddedMediaDriver m_driver;
 };
@@ -176,3 +191,65 @@ TEST_P(RejectImageTest, shouldRejectImageForExclusive)
 
     POLL_FOR(0 < errorFrameCount, invoker);
 }
+
+TEST_P(RejectImageTest, shouldOnlySeePublicationErrorFramesForPublicationsAddedToTheClient)
+{
+    const std::string address = GetParam();
+    const std::string channel = "aeron:udp?endpoint=" + address + ":10000";
+    const int streamId = 10000;
+
+    std::atomic errorFrameCount0{0};
+    std::shared_ptr aeron0 = connectClient(errorFrameCount0);
+    AgentInvoker &invoker0 = aeron0->conductorAgentInvoker();
+
+    std::atomic errorFrameCount1{0};
+    std::shared_ptr aeron1 = connectClient(errorFrameCount1);
+    AgentInvoker &invoker1 = aeron1->conductorAgentInvoker();
+
+    std::atomic errorFrameCount2{0};
+    std::shared_ptr aeron2 = connectClient(errorFrameCount2);
+    AgentInvoker &invoker2 = aeron2->conductorAgentInvoker();
+
+    invoker0.start();
+    invoker1.start();
+    invoker2.start();
+
+    std::int64_t pub0Id = aeron0->addPublication(channel, streamId);
+    std::int64_t subId = aeron0->addSubscription(channel, streamId);
+    invoker0.invoke();
+
+    POLL_FOR_NON_NULL(pub0, aeron0->findPublication(pub0Id), invoker0);
+    POLL_FOR_NON_NULL(sub, aeron0->findSubscription(subId), invoker0);
+    POLL_FOR(pub0->isConnected() && sub->isConnected(), invoker0);
+    
+    std::int64_t pub1Id = aeron1->addPublication(channel, streamId);
+    invoker1.invoke();
+    POLL_FOR_NON_NULL(pub1, aeron1->findPublication(pub1Id), invoker1);
+
+    std::string message = "Hello World!";
+
+    auto *data = reinterpret_cast(message.c_str());
+    POLL_FOR(0 < pub0->offer(data, message.length()), invoker0);
+    POLL_FOR(0 < sub->poll(
+        [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)
+        {
+            EXPECT_EQ(message, buffer.getStringWithoutLength(offset, length));
+        },
+        1), invoker0);
+
+    POLL_FOR(1 == sub->imageCount(), invoker0);
+
+    const std::shared_ptr image = sub->imageByIndex(0);
+    image->reject("No Longer Valid");
+
+    POLL_FOR(0 < errorFrameCount0, invoker0);
+    POLL_FOR(0 < errorFrameCount1, invoker1);
+
+    int64_t timeout_ms = aeron_epoch_clock() + 500;
+    while (aeron_epoch_clock() < timeout_ms)
+    {
+        invoker2.invoke();
+        ASSERT_EQ(0, errorFrameCount2);
+        std::this_thread::sleep_for(std::chrono::duration(1));
+    }
+}

From ee2135349430351a5fb3c247fd522e1dddfcb6f0 Mon Sep 17 00:00:00 2001
From: Michael Barker 
Date: Thu, 29 Aug 2024 11:37:52 +1200
Subject: [PATCH 58/67] [Java] Update Javadoc.

---
 .../src/main/java/io/aeron/agent/DriverEventCode.java  |  3 +++
 .../java/io/aeron/PublicationErrorFrameHandler.java    | 10 ++++------
 2 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/aeron-agent/src/main/java/io/aeron/agent/DriverEventCode.java b/aeron-agent/src/main/java/io/aeron/agent/DriverEventCode.java
index 56d42dceee..07ca49b0ab 100644
--- a/aeron-agent/src/main/java/io/aeron/agent/DriverEventCode.java
+++ b/aeron-agent/src/main/java/io/aeron/agent/DriverEventCode.java
@@ -217,6 +217,9 @@ public enum DriverEventCode implements EventCode
      */
     CMD_IN_REMOVE_DESTINATION_BY_ID(56, DriverEventDissector::dissectCommand),
 
+    /**
+     * Reject image command received by the driver.
+     */
     CMD_IN_REJECT_IMAGE(56, DriverEventDissector::dissectCommand);
 
     static final int EVENT_CODE_TYPE = EventCodeType.DRIVER.getTypeCode();
diff --git a/aeron-client/src/main/java/io/aeron/PublicationErrorFrameHandler.java b/aeron-client/src/main/java/io/aeron/PublicationErrorFrameHandler.java
index f095b0cd85..163f637d9e 100644
--- a/aeron-client/src/main/java/io/aeron/PublicationErrorFrameHandler.java
+++ b/aeron-client/src/main/java/io/aeron/PublicationErrorFrameHandler.java
@@ -22,12 +22,10 @@
  */
 public interface PublicationErrorFrameHandler
 {
-    PublicationErrorFrameHandler NO_OP = new PublicationErrorFrameHandler()
-    {
-        public void onPublicationError(final PublicationErrorFrame errorFrame)
-        {
-        }
-    };
+    /**
+     * Default no-op error frame handler.
+     */
+    PublicationErrorFrameHandler NO_OP = errorFrame -> {};
 
     /**
      * Called when an error frame for a publication is received by the local driver and needs to be propagated to the

From ccdc6507ff0a532534faacd6eb25207911eb49cd Mon Sep 17 00:00:00 2001
From: Michael Barker 
Date: Tue, 27 Aug 2024 15:45:09 +1200
Subject: [PATCH 59/67] [Java] Include destination registration id in
 publication error frame.

---
 .../PublicationErrorFrameFlyweight.java       | 30 ++++++++++-
 .../aeron/status/PublicationErrorFrame.java   | 13 +++++
 .../java/io/aeron/driver/ClientProxy.java     |  3 +-
 .../java/io/aeron/driver/DriverConductor.java | 11 +++-
 .../io/aeron/driver/DriverConductorProxy.java |  3 ++
 .../io/aeron/driver/NetworkPublication.java   |  9 ++--
 .../driver/media/SendChannelEndpoint.java     | 54 ++++++++++++-------
 .../test/java/io/aeron/RejectImageTest.java   | 18 +++++--
 8 files changed, 114 insertions(+), 27 deletions(-)

diff --git a/aeron-client/src/main/java/io/aeron/command/PublicationErrorFrameFlyweight.java b/aeron-client/src/main/java/io/aeron/command/PublicationErrorFrameFlyweight.java
index 5c29e3b3b5..dfe0a2244a 100644
--- a/aeron-client/src/main/java/io/aeron/command/PublicationErrorFrameFlyweight.java
+++ b/aeron-client/src/main/java/io/aeron/command/PublicationErrorFrameFlyweight.java
@@ -34,6 +34,9 @@
  *  |                 Publication Registration Id                   |
  *  |                                                               |
  *  +---------------------------------------------------------------+
+ *  |                 Destination Registration Id                   |
+ *  |                                                               |
+ *  +---------------------------------------------------------------+
  *  |                          Session ID                           |
  *  +---------------------------------------------------------------+
  *  |                           Stream ID                           |
@@ -65,7 +68,8 @@ public class PublicationErrorFrameFlyweight
     private static final int REGISTRATION_ID_OFFSET = 0;
     private static final int IPV6_ADDRESS_LENGTH = 16;
     private static final int IPV4_ADDRESS_LENGTH = BitUtil.SIZE_OF_INT;
-    private static final int SESSION_ID_OFFSET = REGISTRATION_ID_OFFSET + BitUtil.SIZE_OF_LONG;
+    private static final int DESTINATION_REGISTRATION_ID_OFFSET = REGISTRATION_ID_OFFSET + BitUtil.SIZE_OF_LONG;
+    private static final int SESSION_ID_OFFSET = DESTINATION_REGISTRATION_ID_OFFSET + BitUtil.SIZE_OF_LONG;
     private static final int STREAM_ID_OFFSET = SESSION_ID_OFFSET + BitUtil.SIZE_OF_INT;
     private static final int RECEIVER_ID_OFFSET = STREAM_ID_OFFSET + BitUtil.SIZE_OF_INT;
     private static final int GROUP_TAG_OFFSET = RECEIVER_ID_OFFSET + BitUtil.SIZE_OF_LONG;
@@ -117,6 +121,30 @@ public PublicationErrorFrameFlyweight registrationId(final long registrationId)
         return this;
     }
 
+    /**
+     * Return registration id of the destination that received the error frame. This will only be set if the publication
+     * is using manual MDC.
+     *
+     * @return registration ID of the publication or {@link io.aeron.Aeron#NULL_VALUE}
+     */
+    public long destinationRegistrationId()
+    {
+        return buffer.getLong(offset + DESTINATION_REGISTRATION_ID_OFFSET);
+    }
+
+    /**
+     * Set the registration ID of the destination that received the error frame. Use {@link io.aeron.Aeron#NULL_VALUE}
+     * if not set.
+     *
+     * @param registrationId of the destination.
+     * @return this for a fluent API.
+     */
+    public PublicationErrorFrameFlyweight destinationRegistrationId(final long registrationId)
+    {
+        buffer.putLong(offset + DESTINATION_REGISTRATION_ID_OFFSET, registrationId);
+        return this;
+    }
+
     /**
      * Get the stream id field.
      *
diff --git a/aeron-client/src/main/java/io/aeron/status/PublicationErrorFrame.java b/aeron-client/src/main/java/io/aeron/status/PublicationErrorFrame.java
index fa01f6b0bb..7b4ba93277 100644
--- a/aeron-client/src/main/java/io/aeron/status/PublicationErrorFrame.java
+++ b/aeron-client/src/main/java/io/aeron/status/PublicationErrorFrame.java
@@ -28,6 +28,7 @@ public class PublicationErrorFrame implements Cloneable
     private int sessionId;
     private int streamId;
     private long receiverId;
+    private long destinationRegistrationId;
     private Long groupTag;
     private int errorCode;
     private String errorMessage;
@@ -114,6 +115,17 @@ public InetSocketAddress sourceAddress()
         return sourceAddress;
     }
 
+    /**
+     * The registrationId of the destination. Only used with manual MDC publications. Will be
+     * {@link io.aeron.Aeron#NULL_VALUE} otherwise.
+     *
+     * @return registrationId of the destination or {@link io.aeron.Aeron#NULL_VALUE}.
+     */
+    public long destinationRegistrationId()
+    {
+        return destinationRegistrationId;
+    }
+
     /**
      * Set the fields of the publication error frame from the flyweight.
      *
@@ -130,6 +142,7 @@ public PublicationErrorFrame set(final PublicationErrorFrameFlyweight frameFlywe
         sourceAddress = frameFlyweight.sourceAddress();
         errorCode = frameFlyweight.errorCode().value();
         errorMessage = frameFlyweight.errorMessage();
+        destinationRegistrationId = frameFlyweight.destinationRegistrationId();
 
         return this;
     }
diff --git a/aeron-driver/src/main/java/io/aeron/driver/ClientProxy.java b/aeron-driver/src/main/java/io/aeron/driver/ClientProxy.java
index 258912ac34..4530a0fab9 100644
--- a/aeron-driver/src/main/java/io/aeron/driver/ClientProxy.java
+++ b/aeron-driver/src/main/java/io/aeron/driver/ClientProxy.java
@@ -74,7 +74,7 @@ void onError(final long correlationId, final ErrorCode errorCode, final String e
 
     void onPublicationErrorFrame(
         final long registrationId,
-        final int sessionId,
+        final long destinationRegistrationId, final int sessionId,
         final int streamId,
         final long receiverId,
         final Long groupTag,
@@ -84,6 +84,7 @@ void onPublicationErrorFrame(
     {
         publicationErrorFrame
             .registrationId(registrationId)
+            .destinationRegistrationId(destinationRegistrationId)
             .sessionId(sessionId)
             .streamId(streamId)
             .receiverId(receiverId)
diff --git a/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java b/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java
index de0f96153e..60f62ecb94 100644
--- a/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java
+++ b/aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java
@@ -389,6 +389,7 @@ void onChannelEndpointError(final long statusIndicatorId, final Exception ex)
 
     void onPublicationError(
         final long registrationId,
+        final long destinationRegistrationId,
         final int sessionId,
         final int streamId,
         final long receiverId,
@@ -399,7 +400,15 @@ void onPublicationError(
     {
         recordError(new AeronException(errorMessage, AeronException.Category.WARN));
         clientProxy.onPublicationErrorFrame(
-            registrationId, sessionId, streamId, receiverId, groupId, srcAddress, errorCode, errorMessage);
+            registrationId,
+            destinationRegistrationId,
+            sessionId,
+            streamId,
+            receiverId,
+            groupId,
+            srcAddress,
+            errorCode,
+            errorMessage);
     }
 
     void onReResolveEndpoint(
diff --git a/aeron-driver/src/main/java/io/aeron/driver/DriverConductorProxy.java b/aeron-driver/src/main/java/io/aeron/driver/DriverConductorProxy.java
index 132bc23193..c0a2185331 100644
--- a/aeron-driver/src/main/java/io/aeron/driver/DriverConductorProxy.java
+++ b/aeron-driver/src/main/java/io/aeron/driver/DriverConductorProxy.java
@@ -249,6 +249,7 @@ void createPublicationImage(
 
     void onPublicationError(
         final long registrationId,
+        final long destinationRegistrationId,
         final int sessionId,
         final int streamId,
         final long receiverId,
@@ -261,6 +262,7 @@ void onPublicationError(
         {
             driverConductor.onPublicationError(
                 registrationId,
+                destinationRegistrationId,
                 sessionId,
                 streamId,
                 receiverId,
@@ -273,6 +275,7 @@ void onPublicationError(
         {
             offer(() -> driverConductor.onPublicationError(
                 registrationId,
+                destinationRegistrationId,
                 sessionId,
                 streamId,
                 receiverId,
diff --git a/aeron-driver/src/main/java/io/aeron/driver/NetworkPublication.java b/aeron-driver/src/main/java/io/aeron/driver/NetworkPublication.java
index 7454996886..78ba9aa3b0 100644
--- a/aeron-driver/src/main/java/io/aeron/driver/NetworkPublication.java
+++ b/aeron-driver/src/main/java/io/aeron/driver/NetworkPublication.java
@@ -484,13 +484,15 @@ public void onStatusMessage(
     /**
      * Process an error message from a receiver.
      *
-     * @param msg            flyweight over the network packet.
-     * @param srcAddress     that the setup message has come from.
-     * @param conductorProxy to send messages back to the conductor.
+     * @param msg                       flyweight over the network packet.
+     * @param srcAddress                that the setup message has come from.
+     * @param destinationRegistrationId registrationId of the relevant MDC destination or {@link Aeron#NULL_VALUE}
+     * @param conductorProxy            to send messages back to the conductor.
      */
     public void onError(
         final ErrorFlyweight msg,
         final InetSocketAddress srcAddress,
+        final long destinationRegistrationId,
         final DriverConductorProxy conductorProxy)
     {
         flowControl.onError(msg, srcAddress, cachedNanoClock.nanoTime());
@@ -498,6 +500,7 @@ public void onError(
         {
             conductorProxy.onPublicationError(
                 registrationId,
+                destinationRegistrationId,
                 msg.sessionId(),
                 msg.streamId(),
                 msg.receiverId(),
diff --git a/aeron-driver/src/main/java/io/aeron/driver/media/SendChannelEndpoint.java b/aeron-driver/src/main/java/io/aeron/driver/media/SendChannelEndpoint.java
index 295315b54a..0e8f0c2565 100644
--- a/aeron-driver/src/main/java/io/aeron/driver/media/SendChannelEndpoint.java
+++ b/aeron-driver/src/main/java/io/aeron/driver/media/SendChannelEndpoint.java
@@ -426,16 +426,13 @@ public void onError(
 
         errorMessagesReceived.incrementOrdered();
 
-        if (null != multiSndDestination)
-        {
-            // TODO: What do we need to do here???
-//            multiSndDestination.onStatusMessage(msg, srcAddress);
-        }
+        final long destinationRegistrationId = (null != multiSndDestination) ?
+            multiSndDestination.findRegistrationId(msg, srcAddress) : Aeron.NULL_VALUE;
 
         final NetworkPublication publication = publicationBySessionAndStreamId.get(compoundKey(sessionId, streamId));
         if (null != publication)
         {
-            publication.onError(msg, srcAddress, conductorProxy);
+            publication.onError(msg, srcAddress, destinationRegistrationId, conductorProxy);
         }
     }
 
@@ -738,6 +735,11 @@ else if (datagramChannel.isOpen())
 
         return bytesSent;
     }
+
+    public long findRegistrationId(final ErrorFlyweight msg, final InetSocketAddress srcAddress)
+    {
+        return Aeron.NULL_VALUE;
+    }
 }
 
 class ManualSndMultiDestination extends MultiSndDestination
@@ -754,20 +756,15 @@ void onStatusMessage(final StatusMessageFlyweight msg, final InetSocketAddress a
 
         for (final Destination destination : destinations)
         {
-            if (destination.isReceiverIdValid &&
-                receiverId == destination.receiverId &&
-                address.getPort() == destination.port)
-            {
-                destination.timeOfLastActivityNs = nowNs;
-                break;
-            }
-            else if (!destination.isReceiverIdValid &&
-                address.getPort() == destination.port &&
-                address.getAddress().equals(destination.address.getAddress()))
+            if (destination.isMatch(msg.receiverId(), address))
             {
+                if (!destination.isReceiverIdValid)
+                {
+                    destination.receiverId = receiverId;
+                    destination.isReceiverIdValid = true;
+                }
+
                 destination.timeOfLastActivityNs = nowNs;
-                destination.receiverId = receiverId;
-                destination.isReceiverIdValid = true;
                 break;
             }
         }
@@ -907,6 +904,19 @@ void updateDestination(final String endpoint, final InetSocketAddress newAddress
             }
         }
     }
+
+    public long findRegistrationId(final ErrorFlyweight msg, final InetSocketAddress address)
+    {
+        for (final Destination destination : destinations)
+        {
+            if (destination.isMatch(msg.receiverId(), address))
+            {
+                return destination.registrationId;
+            }
+        }
+
+        return Aeron.NULL_VALUE;
+    }
 }
 
 class DynamicSndMultiDestination extends MultiSndDestination
@@ -1101,4 +1111,12 @@ final class Destination extends DestinationRhsPadding
         this.port = address.getPort();
         this.registrationId = registrationId;
     }
+
+    boolean isMatch(final long receiverId, final InetSocketAddress address)
+    {
+        return
+            (isReceiverIdValid && receiverId == this.receiverId && address.getPort() == this.port) ||
+            (!isReceiverIdValid &&
+                address.getPort() == this.port && address.getAddress().equals(this.address.getAddress()));
+    }
 }
\ No newline at end of file
diff --git a/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java b/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java
index ee1b35269e..1777274b05 100644
--- a/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java
+++ b/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java
@@ -318,10 +318,12 @@ void shouldRejectSubscriptionsImageManualMdc()
     {
         context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3));
 
+        final QueuedErrorFrameHandler errorFrameHandler = new QueuedErrorFrameHandler();
         final TestMediaDriver driver = launch();
 
         final Aeron.Context ctx = new Aeron.Context()
-            .aeronDirectoryName(driver.aeronDirectoryName());
+            .aeronDirectoryName(driver.aeronDirectoryName())
+            .publicationErrorFrameHandler(errorFrameHandler);
 
         final AtomicInteger imageAvailable = new AtomicInteger(0);
         final AtomicInteger imageUnavailable = new AtomicInteger(0);
@@ -336,7 +338,7 @@ void shouldRejectSubscriptionsImageManualMdc()
                 (image) -> imageAvailable.incrementAndGet(),
                 (image) -> imageUnavailable.incrementAndGet()))
         {
-            pub.addDestination(channel);
+            final long destinationRegistrationId = pub.asyncAddDestination(channel);
 
             Tests.awaitConnected(pub);
             Tests.awaitConnected(sub);
@@ -389,6 +391,16 @@ void shouldRejectSubscriptionsImageManualMdc()
             {
                 Tests.yield();
             }
+
+            PublicationErrorFrame errorFrame;
+            while (null == (errorFrame = errorFrameHandler.poll()))
+            {
+                Tests.yield();
+            }
+
+            assertEquals(reason, errorFrame.errorMessage());
+            assertEquals(pub.registrationId(), errorFrame.registrationId());
+            assertEquals(destinationRegistrationId, errorFrame.destinationRegistrationId());
         }
     }
 
@@ -438,7 +450,7 @@ void shouldErrorIfUsingAndIpcChannel()
 
             final AeronException ex = assertThrows(AeronException.class, () -> sub.imageAtIndex(0)
                 .reject(rejectionReason));
-            System.out.println(ex.getMessage());
+            assertThat(ex.getMessage(), containsString("Unable to resolve image for correlationId"));
         }
     }
 

From e6081174b002e0fc2c6789449caeeb0f2ac5ab02 Mon Sep 17 00:00:00 2001
From: Michael Barker 
Date: Thu, 29 Aug 2024 14:31:27 +1200
Subject: [PATCH 60/67] [Java] Additional validation in RejectImageTest.

---
 aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java | 1 +
 1 file changed, 1 insertion(+)

diff --git a/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java b/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java
index 1777274b05..54e96206cf 100644
--- a/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java
+++ b/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java
@@ -510,6 +510,7 @@ void shouldReturnAllParametersToApi(final String addressStr) throws UnknownHostE
 
             assertEquals(reason, errorFrame.errorMessage());
             assertEquals(pub.registrationId(), errorFrame.registrationId());
+            assertEquals(Aeron.NULL_VALUE, errorFrame.destinationRegistrationId());
             assertEquals(pub.streamId(), errorFrame.streamId());
             assertEquals(pub.sessionId(), errorFrame.sessionId());
             assertEquals(groupTag, errorFrame.groupTag());

From 81be21754796edeee0f4af214902acab8d6aa163 Mon Sep 17 00:00:00 2001
From: Michael Barker 
Date: Thu, 29 Aug 2024 15:15:29 +1200
Subject: [PATCH 61/67] [C] Add destination registration id support for
 publication error frames.

---
 aeron-client/src/main/c/aeronc.h              |  1 +
 .../main/c/command/aeron_control_protocol.h   |  1 +
 .../src/main/c/aeron_driver_conductor.c       |  1 +
 .../src/main/c/aeron_driver_conductor_proxy.c |  2 +
 .../src/main/c/aeron_driver_conductor_proxy.h |  2 +
 .../src/main/c/aeron_network_publication.c    |  2 +
 .../src/main/c/aeron_network_publication.h    |  1 +
 .../c/media/aeron_send_channel_endpoint.c     | 10 ++-
 .../c/media/aeron_udp_destination_tracker.c   | 64 ++++++++++++++-----
 .../c/media/aeron_udp_destination_tracker.h   |  3 +
 10 files changed, 68 insertions(+), 19 deletions(-)

diff --git a/aeron-client/src/main/c/aeronc.h b/aeron-client/src/main/c/aeronc.h
index d40c6dc76b..fd701a7c89 100644
--- a/aeron-client/src/main/c/aeronc.h
+++ b/aeron-client/src/main/c/aeronc.h
@@ -71,6 +71,7 @@ aeron_header_values_t;
 struct aeron_publication_error_values_stct
 {
     int64_t registration_id;
+    int64_t destination_registration_id;
     int32_t session_id;
     int32_t stream_id;
     int64_t receiver_id;
diff --git a/aeron-client/src/main/c/command/aeron_control_protocol.h b/aeron-client/src/main/c/command/aeron_control_protocol.h
index c0e6f9c18f..75d95dacd5 100644
--- a/aeron-client/src/main/c/command/aeron_control_protocol.h
+++ b/aeron-client/src/main/c/command/aeron_control_protocol.h
@@ -223,6 +223,7 @@ aeron_reject_image_command_t;
 struct aeron_publication_error_stct
 {
     int64_t registration_id;
+    int64_t destination_registration_id;
     int32_t session_id;
     int32_t stream_id;
     int64_t receiver_id;
diff --git a/aeron-driver/src/main/c/aeron_driver_conductor.c b/aeron-driver/src/main/c/aeron_driver_conductor.c
index 1e66ffab93..ebea2302d7 100644
--- a/aeron-driver/src/main/c/aeron_driver_conductor.c
+++ b/aeron-driver/src/main/c/aeron_driver_conductor.c
@@ -2578,6 +2578,7 @@ void aeron_driver_conductor_on_publication_error(void *clientd, void *item)
     aeron_publication_error_t *response = (aeron_publication_error_t *)buffer;
     response->error_code = error->error_code;
     response->registration_id = error->registration_id;
+    response->destination_registration_id = error->destination_registration_id;
     response->session_id = error->session_id;
     response->stream_id = error->stream_id;
     response->receiver_id = error->receiver_id;
diff --git a/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c b/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c
index 387c5c64d6..a96a7a2387 100644
--- a/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c
+++ b/aeron-driver/src/main/c/aeron_driver_conductor_proxy.c
@@ -265,6 +265,7 @@ void aeron_driver_conductor_proxy_on_release_resource(
 void aeron_driver_conductor_proxy_on_publication_error(
     aeron_driver_conductor_proxy_t *conductor_proxy,
     const int64_t registration_id,
+    const int64_t destination_registration_id,
     int32_t session_id,
     int32_t stream_id,
     int64_t receiver_id,
@@ -281,6 +282,7 @@ void aeron_driver_conductor_proxy_on_publication_error(
     error->base.func = aeron_driver_conductor_on_publication_error;
     error->base.item = NULL;
     error->registration_id = registration_id;
+    error->destination_registration_id = destination_registration_id;
     error->session_id = session_id;
     error->stream_id = stream_id;
     error->receiver_id = receiver_id;
diff --git a/aeron-driver/src/main/c/aeron_driver_conductor_proxy.h b/aeron-driver/src/main/c/aeron_driver_conductor_proxy.h
index 32381f5d54..10228dbb48 100644
--- a/aeron-driver/src/main/c/aeron_driver_conductor_proxy.h
+++ b/aeron-driver/src/main/c/aeron_driver_conductor_proxy.h
@@ -104,6 +104,7 @@ struct aeron_command_publication_error_stct
 {
     aeron_command_base_t base;
     int64_t registration_id;
+    int64_t destination_registration_id;
     int32_t session_id;
     int32_t stream_id;
     int64_t receiver_id;
@@ -174,6 +175,7 @@ void aeron_driver_conductor_proxy_on_release_resource(
 void aeron_driver_conductor_proxy_on_publication_error(
     aeron_driver_conductor_proxy_t *conductor_proxy,
     const int64_t registration_id,
+    const int64_t destination_registration_id,
     int32_t session_id,
     int32_t stream_id,
     int64_t receiver_id,
diff --git a/aeron-driver/src/main/c/aeron_network_publication.c b/aeron-driver/src/main/c/aeron_network_publication.c
index bb6edb7238..b92559f525 100644
--- a/aeron-driver/src/main/c/aeron_network_publication.c
+++ b/aeron-driver/src/main/c/aeron_network_publication.c
@@ -834,6 +834,7 @@ void aeron_network_publication_on_status_message(
 
 void aeron_network_publication_on_error(
     aeron_network_publication_t *publication,
+    int64_t destination_registration_id,
     const uint8_t *buffer,
     size_t length,
     struct sockaddr_storage *src_address,
@@ -849,6 +850,7 @@ void aeron_network_publication_on_error(
         aeron_driver_conductor_proxy_on_publication_error(
             conductor_proxy,
             registration_id,
+            destination_registration_id,
             error->session_id,
             error->stream_id,
             error->receiver_id,
diff --git a/aeron-driver/src/main/c/aeron_network_publication.h b/aeron-driver/src/main/c/aeron_network_publication.h
index 7db06356a3..0a82a37406 100644
--- a/aeron-driver/src/main/c/aeron_network_publication.h
+++ b/aeron-driver/src/main/c/aeron_network_publication.h
@@ -183,6 +183,7 @@ void aeron_network_publication_on_status_message(
 
 void aeron_network_publication_on_error(
     aeron_network_publication_t *publication,
+    int64_t destination_registration_id,
     const uint8_t *buffer,
     size_t length,
     struct sockaddr_storage *src_address,
diff --git a/aeron-driver/src/main/c/media/aeron_send_channel_endpoint.c b/aeron-driver/src/main/c/media/aeron_send_channel_endpoint.c
index a79c632193..0a43071d34 100644
--- a/aeron-driver/src/main/c/media/aeron_send_channel_endpoint.c
+++ b/aeron-driver/src/main/c/media/aeron_send_channel_endpoint.c
@@ -533,7 +533,12 @@ int aeron_send_channel_endpoint_on_error(
 {
     aeron_error_t *error = (aeron_error_t *)buffer;
 
-    // TODO: handle multi-destination messages
+    int64_t destination_registration_id = AERON_NULL_VALUE;
+    if (NULL != endpoint->destination_tracker)
+    {
+        destination_registration_id = aeron_udp_destination_tracker_find_registration_id(
+            endpoint->destination_tracker, buffer, length, addr);
+    }
 
     int64_t key_value = aeron_map_compound_key(error->stream_id, error->session_id);
     aeron_network_publication_t *publication = aeron_int64_to_ptr_hash_map_get(
@@ -542,7 +547,8 @@ int aeron_send_channel_endpoint_on_error(
 
     if (NULL != publication)
     {
-        aeron_network_publication_on_error(publication, buffer, length, addr, conductor_proxy);
+        aeron_network_publication_on_error(
+            publication, destination_registration_id, buffer, length, addr, conductor_proxy);
     }
 
     return result;
diff --git a/aeron-driver/src/main/c/media/aeron_udp_destination_tracker.c b/aeron-driver/src/main/c/media/aeron_udp_destination_tracker.c
index 3cec4967dd..0af24f52b3 100644
--- a/aeron-driver/src/main/c/media/aeron_udp_destination_tracker.c
+++ b/aeron-driver/src/main/c/media/aeron_udp_destination_tracker.c
@@ -70,7 +70,7 @@ int aeron_udp_destination_tracker_close(aeron_udp_destination_tracker_t *tracker
     return 0;
 }
 
-void aeron_udp_destination_tracker_remove_inactive_destinations(
+static void aeron_udp_destination_tracker_remove_inactive_destinations(
     aeron_udp_destination_tracker_t *tracker,
     int64_t now_ns)
 {
@@ -169,7 +169,7 @@ int aeron_udp_destination_tracker_send(
     return min_bytes_sent;
 }
 
-bool aeron_udp_destination_tracker_same_port(struct sockaddr_storage *lhs, struct sockaddr_storage *rhs)
+static bool aeron_udp_destination_tracker_same_port(struct sockaddr_storage *lhs, struct sockaddr_storage *rhs)
 {
     bool result = false;
 
@@ -191,7 +191,7 @@ bool aeron_udp_destination_tracker_same_port(struct sockaddr_storage *lhs, struc
     return result;
 }
 
-bool aeron_udp_destination_tracker_same_addr(struct sockaddr_storage *lhs, struct sockaddr_storage *rhs)
+static bool aeron_udp_destination_tracker_same_addr(struct sockaddr_storage *lhs, struct sockaddr_storage *rhs)
 {
     bool result = false;
 
@@ -213,6 +213,18 @@ bool aeron_udp_destination_tracker_same_addr(struct sockaddr_storage *lhs, struc
     return result;
 }
 
+static bool aeron_udp_destination_tracker_is_match(
+    aeron_udp_destination_entry_t *entry,
+    int64_t receiver_id,
+    struct sockaddr_storage *addr)
+{
+    return
+        (entry->is_receiver_id_valid && receiver_id == entry->receiver_id &&
+            aeron_udp_destination_tracker_same_port(&entry->addr, addr)) ||
+        (!entry->is_receiver_id_valid && aeron_udp_destination_tracker_same_addr(&entry->addr, addr) &&
+            aeron_udp_destination_tracker_same_port(&entry->addr, addr));
+}
+
 int aeron_udp_destination_tracker_add_destination(
     aeron_udp_destination_tracker_t *tracker,
     int64_t receiver_id,
@@ -258,21 +270,16 @@ int aeron_udp_destination_tracker_on_status_message(
     {
         aeron_udp_destination_entry_t *entry = &tracker->destinations.array[i];
 
-        if (entry->is_receiver_id_valid && receiver_id == entry->receiver_id &&
-            aeron_udp_destination_tracker_same_port(&entry->addr, addr))
-        {
-            entry->time_of_last_activity_ns = now_ns;
-            is_existing = true;
-            break;
-        }
-        else if (!entry->is_receiver_id_valid &&
-            aeron_udp_destination_tracker_same_addr(&entry->addr, addr) &&
-            aeron_udp_destination_tracker_same_port(&entry->addr, addr))
+        is_existing = aeron_udp_destination_tracker_is_match(entry, receiver_id, addr);
+        if (is_existing)
         {
+            if (!entry->is_receiver_id_valid)
+            {
+                entry->receiver_id = receiver_id;
+                entry->is_receiver_id_valid = true;
+            }
             entry->time_of_last_activity_ns = now_ns;
-            entry->receiver_id = receiver_id;
-            entry->is_receiver_id_valid = true;
-            is_existing = true;
+
             break;
         }
     }
@@ -344,7 +351,9 @@ int aeron_udp_destination_tracker_remove_destination(
 }
 
 int aeron_udp_destination_tracker_remove_destination_by_id(
-    aeron_udp_destination_tracker_t *tracker, int64_t destination_registration_id, aeron_uri_t **removed_uri)
+    aeron_udp_destination_tracker_t *tracker,
+    int64_t destination_registration_id,
+    aeron_uri_t **removed_uri)
 {
     for (int last_index = (int)tracker->destinations.length - 1, i = last_index; i >= 0; i--)
     {
@@ -370,6 +379,27 @@ int aeron_udp_destination_tracker_remove_destination_by_id(
     return 0;
 }
 
+int64_t aeron_udp_destination_tracker_find_registration_id(
+    aeron_udp_destination_tracker_t *tracker,
+    const uint8_t *buffer,
+    size_t len,
+    struct sockaddr_storage *addr)
+{
+    aeron_error_t *error = (aeron_error_t *)buffer;
+    const int64_t receiver_id = error->receiver_id;
+
+    for (size_t i = 0, size = tracker->destinations.length; i < size; i++)
+    {
+        aeron_udp_destination_entry_t *entry = &tracker->destinations.array[i];
+        if (aeron_udp_destination_tracker_is_match(entry, receiver_id, addr))
+        {
+            return entry->registration_id;
+        }
+    }
+
+    return AERON_NULL_VALUE;
+}
+
 void aeron_udp_destination_tracker_check_for_re_resolution(
     aeron_udp_destination_tracker_t *tracker,
     aeron_send_channel_endpoint_t *endpoint,
diff --git a/aeron-driver/src/main/c/media/aeron_udp_destination_tracker.h b/aeron-driver/src/main/c/media/aeron_udp_destination_tracker.h
index ad98c3ac3a..77a0cda1d7 100644
--- a/aeron-driver/src/main/c/media/aeron_udp_destination_tracker.h
+++ b/aeron-driver/src/main/c/media/aeron_udp_destination_tracker.h
@@ -92,6 +92,9 @@ int aeron_udp_destination_tracker_remove_destination(
 int aeron_udp_destination_tracker_remove_destination_by_id(
     aeron_udp_destination_tracker_t *tracker, int64_t destination_registration_id, aeron_uri_t **removed_uri);
 
+int64_t aeron_udp_destination_tracker_find_registration_id(
+    aeron_udp_destination_tracker_t *tracker, const uint8_t *buffer, size_t len, struct sockaddr_storage *addr);
+
 void aeron_udp_destination_tracker_check_for_re_resolution(
     aeron_udp_destination_tracker_t *tracker,
     aeron_send_channel_endpoint_t *endpoint,

From 72e7a697d48bd2cd9fe81fc7eaa02a605e64c851 Mon Sep 17 00:00:00 2001
From: Michael Barker 
Date: Fri, 30 Aug 2024 06:57:42 +1200
Subject: [PATCH 62/67] [Java] Comments and cleanup.

---
 aeron-client/src/main/java/io/aeron/Aeron.java                | 2 ++
 .../src/main/java/io/aeron/PublicationErrorFrameHandler.java  | 1 +
 .../src/main/java/io/aeron/command/ControlProtocolEvents.java | 1 +
 .../java/io/aeron/command/PublicationErrorFrameFlyweight.java | 1 +
 .../src/main/java/io/aeron/protocol/ErrorFlyweight.java       | 1 +
 .../src/main/java/io/aeron/driver/PublicationImage.java       | 4 ----
 6 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/aeron-client/src/main/java/io/aeron/Aeron.java b/aeron-client/src/main/java/io/aeron/Aeron.java
index 1fe22080c2..2320f41a87 100644
--- a/aeron-client/src/main/java/io/aeron/Aeron.java
+++ b/aeron-client/src/main/java/io/aeron/Aeron.java
@@ -1736,6 +1736,7 @@ public ThreadFactory threadFactory()
          *
          * @param publicationErrorFrameHandler to be called back when an error frame is received.
          * @return this for a fluent API.
+         * @since 1.47.0
          */
         public Context publicationErrorFrameHandler(
             final PublicationErrorFrameHandler publicationErrorFrameHandler)
@@ -1749,6 +1750,7 @@ public Context publicationErrorFrameHandler(
          * this client.
          *
          * @return the {@link PublicationErrorFrameHandler} to call back on to.
+         * @since 1.47.0
          */
         public PublicationErrorFrameHandler publicationErrorFrameHandler()
         {
diff --git a/aeron-client/src/main/java/io/aeron/PublicationErrorFrameHandler.java b/aeron-client/src/main/java/io/aeron/PublicationErrorFrameHandler.java
index 163f637d9e..31fe6a425e 100644
--- a/aeron-client/src/main/java/io/aeron/PublicationErrorFrameHandler.java
+++ b/aeron-client/src/main/java/io/aeron/PublicationErrorFrameHandler.java
@@ -19,6 +19,7 @@
 
 /**
  * Interface for handling various error frame messages for publications.
+ * @since 1.47.0
  */
 public interface PublicationErrorFrameHandler
 {
diff --git a/aeron-client/src/main/java/io/aeron/command/ControlProtocolEvents.java b/aeron-client/src/main/java/io/aeron/command/ControlProtocolEvents.java
index f06b06a01e..616309887d 100644
--- a/aeron-client/src/main/java/io/aeron/command/ControlProtocolEvents.java
+++ b/aeron-client/src/main/java/io/aeron/command/ControlProtocolEvents.java
@@ -171,6 +171,7 @@ public class ControlProtocolEvents
 
     /**
      * Inform clients of error frame received by publication
+     * @since 1.47.0
      */
     public static final int ON_PUBLICATION_ERROR = 0x0F0C;
 }
diff --git a/aeron-client/src/main/java/io/aeron/command/PublicationErrorFrameFlyweight.java b/aeron-client/src/main/java/io/aeron/command/PublicationErrorFrameFlyweight.java
index dfe0a2244a..d72c187978 100644
--- a/aeron-client/src/main/java/io/aeron/command/PublicationErrorFrameFlyweight.java
+++ b/aeron-client/src/main/java/io/aeron/command/PublicationErrorFrameFlyweight.java
@@ -62,6 +62,7 @@
  * ...                                                              |
  *  +---------------------------------------------------------------+
  * 
+ * @since 1.47.0 */ public class PublicationErrorFrameFlyweight { diff --git a/aeron-client/src/main/java/io/aeron/protocol/ErrorFlyweight.java b/aeron-client/src/main/java/io/aeron/protocol/ErrorFlyweight.java index 40ef4c50e1..46dc9eae2a 100644 --- a/aeron-client/src/main/java/io/aeron/protocol/ErrorFlyweight.java +++ b/aeron-client/src/main/java/io/aeron/protocol/ErrorFlyweight.java @@ -50,6 +50,7 @@ * ... | * +---------------------------------------------------------------+ * + * @since 1.47.0 */ public class ErrorFlyweight extends HeaderFlyweight { diff --git a/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java b/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java index 6a71b39f95..7b674ab31b 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java +++ b/aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java @@ -592,10 +592,6 @@ int insertPacket( if (null != rejectionReason) { - if (isEndOfStream) - { - System.out.println("Invalidated end of stream"); - } return 0; } From fb7641ce80869b56c27add4749cbc75faa06a102 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Mon, 2 Sep 2024 14:38:27 +1200 Subject: [PATCH 63/67] [Java] Update event code for REJECT_IMAGE. --- aeron-agent/src/main/java/io/aeron/agent/DriverEventCode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aeron-agent/src/main/java/io/aeron/agent/DriverEventCode.java b/aeron-agent/src/main/java/io/aeron/agent/DriverEventCode.java index 07ca49b0ab..0e1af12356 100644 --- a/aeron-agent/src/main/java/io/aeron/agent/DriverEventCode.java +++ b/aeron-agent/src/main/java/io/aeron/agent/DriverEventCode.java @@ -220,7 +220,7 @@ public enum DriverEventCode implements EventCode /** * Reject image command received by the driver. */ - CMD_IN_REJECT_IMAGE(56, DriverEventDissector::dissectCommand); + CMD_IN_REJECT_IMAGE(57, DriverEventDissector::dissectCommand); static final int EVENT_CODE_TYPE = EventCodeType.DRIVER.getTypeCode(); From d443b272b060b852b0fa86887e582df0adc7bb24 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Mon, 2 Sep 2024 15:07:37 +1200 Subject: [PATCH 64/67] [Java] Fix Javadoc. --- .../driver/AbstractMinMulticastFlowControl.java | 12 ++++++++++-- .../aeron/driver/media/ReceiveChannelEndpoint.java | 3 +++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/aeron-driver/src/main/java/io/aeron/driver/AbstractMinMulticastFlowControl.java b/aeron-driver/src/main/java/io/aeron/driver/AbstractMinMulticastFlowControl.java index 4de4a79ba6..076e632c71 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/AbstractMinMulticastFlowControl.java +++ b/aeron-driver/src/main/java/io/aeron/driver/AbstractMinMulticastFlowControl.java @@ -313,17 +313,25 @@ protected void processSendSetupTrigger( } } + /** + * Process an error frame from a downstream receiver. + * + * @param error flyweight over the error frame. + * @param receiverAddress of the receiver. + * @param timeNs current time in nanoseconds. + * @param hasMatchingTag if the error message comes from a receiver with a tag matching the group. + */ protected void processError( final ErrorFlyweight error, final InetSocketAddress receiverAddress, final long timeNs, - final boolean matchesTag) + final boolean hasMatchingTag) { final long receiverId = error.receiverId(); for (final Receiver receiver : receivers) { - if (matchesTag && receiverId == receiver.receiverId) + if (hasMatchingTag && receiverId == receiver.receiverId) { receiver.eosFlagged = true; } diff --git a/aeron-driver/src/main/java/io/aeron/driver/media/ReceiveChannelEndpoint.java b/aeron-driver/src/main/java/io/aeron/driver/media/ReceiveChannelEndpoint.java index 5e991c9da0..cb32ee11fa 100644 --- a/aeron-driver/src/main/java/io/aeron/driver/media/ReceiveChannelEndpoint.java +++ b/aeron-driver/src/main/java/io/aeron/driver/media/ReceiveChannelEndpoint.java @@ -67,6 +67,9 @@ abstract class ReceiveChannelEndpointLhsPadding extends UdpChannelTransport abstract class ReceiveChannelEndpointHotFields extends ReceiveChannelEndpointLhsPadding { + /** + * Counter for the number of errors frames send back by this channel endpoint. + */ protected final AtomicCounter errorFramesSent; long timeOfLastActivityNs; From 5c925f1211450c09572b662fa9450efec754c43c Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Mon, 2 Sep 2024 15:55:41 +1200 Subject: [PATCH 65/67] [C] Small renames, fixes and verification for publication error frames. --- .../src/main/c/aeron_client_conductor.c | 45 ++++++++++++++++++- .../src/main/c/aeron_client_conductor.h | 2 +- aeron-client/src/main/c/aeron_context.c | 6 +-- aeron-client/src/main/c/aeron_context.h | 2 +- aeron-client/src/main/c/aeronc.h | 8 ++-- .../main/c/command/aeron_control_protocol.h | 4 +- aeron-client/src/main/cpp_wrapper/Context.h | 2 +- .../src/main/c/aeron_driver_conductor.c | 12 ++--- .../src/main/c/aeron_publication_image.c | 1 - 9 files changed, 62 insertions(+), 20 deletions(-) diff --git a/aeron-client/src/main/c/aeron_client_conductor.c b/aeron-client/src/main/c/aeron_client_conductor.c index 4ba5dfc577..7ae44a8e4f 100644 --- a/aeron-client/src/main/c/aeron_client_conductor.c +++ b/aeron-client/src/main/c/aeron_client_conductor.c @@ -2562,12 +2562,55 @@ void aeron_client_conductor_forward_error(void *clientd, int64_t key, void *valu if (is_publication || is_exclusive_publication) { - // TODO: Use a union or a copy... conductor->error_frame_handler( conductor->error_frame_handler_clientd, (aeron_publication_error_values_t *)response); } } +#ifdef _MSC_VER +#define _Static_assert static_assert +#endif + +_Static_assert( + sizeof(aeron_publication_error_t) == sizeof(aeron_publication_error_values_t), + "sizeof(aeron_publication_error_t) must be equal to sizeof(aeron_publication_error_values_t)"); +_Static_assert( + offsetof(aeron_publication_error_t, registration_id) == offsetof(aeron_publication_error_values_t, registration_id), + "offsetof(aeron_publication_error_t, registration_id) must match offsetof(aeron_publication_error_values_t, registration_id)"); +_Static_assert( + offsetof(aeron_publication_error_t, destination_registration_id) == offsetof(aeron_publication_error_values_t, destination_registration_id), + "offsetof(aeron_publication_error_t, destination_registration_id) must match offsetof(aeron_publication_error_values_t, destination_registration_id)"); +_Static_assert( + offsetof(aeron_publication_error_t, session_id) == offsetof(aeron_publication_error_values_t, session_id), + "offsetof(aeron_publication_error_t, session_id) must match offsetof(aeron_publication_error_values_t, session_id)"); +_Static_assert( + offsetof(aeron_publication_error_t, stream_id) == offsetof(aeron_publication_error_values_t, stream_id), + "offsetof(aeron_publication_error_t, stream_id) must match offsetof(aeron_publication_error_values_t, stream_id)"); +_Static_assert( + offsetof(aeron_publication_error_t, receiver_id) == offsetof(aeron_publication_error_values_t, receiver_id), + "offsetof(aeron_publication_error_t, receiver_id) must match offsetof(aeron_publication_error_values_t, receiver_id)"); +_Static_assert( + offsetof(aeron_publication_error_t, group_tag) == offsetof(aeron_publication_error_values_t, group_tag), + "offsetof(aeron_publication_error_t, group_tag) must match offsetof(aeron_publication_error_values_t, group_tag)"); +_Static_assert( + offsetof(aeron_publication_error_t, address_type) == offsetof(aeron_publication_error_values_t, address_type), + "offsetof(aeron_publication_error_t, address_type) must match offsetof(aeron_publication_error_values_t, address_type)"); +_Static_assert( + offsetof(aeron_publication_error_t, source_port) == offsetof(aeron_publication_error_values_t, source_port), + "offsetof(aeron_publication_error_t, address_port) must match offsetof(aeron_publication_error_values_t, address_port)"); +_Static_assert( + offsetof(aeron_publication_error_t, source_address) == offsetof(aeron_publication_error_values_t, source_address), + "offsetof(aeron_publication_error_t, source_address) must match offsetof(aeron_publication_error_values_t, source_address)"); +_Static_assert( + offsetof(aeron_publication_error_t, error_code) == offsetof(aeron_publication_error_values_t, error_code), + "offsetof(aeron_publication_error_t, error_code) must match offsetof(aeron_publication_error_values_t, error_code)"); +_Static_assert( + offsetof(aeron_publication_error_t, error_message_length) == offsetof(aeron_publication_error_values_t, error_message_length), + "offsetof(aeron_publication_error_t, error_message_length) must match offsetof(aeron_publication_error_values_t, error_message_length)"); +_Static_assert( + offsetof(aeron_publication_error_t, error_message) == offsetof(aeron_publication_error_values_t, error_message), + "offsetof(aeron_publication_error_t, error_message) must match offsetof(aeron_publication_error_values_t, error_message)"); + int aeron_client_conductor_on_error_frame(aeron_client_conductor_t *conductor, aeron_publication_error_t *response) { aeron_client_conductor_clientd_t clientd = { diff --git a/aeron-client/src/main/c/aeron_client_conductor.h b/aeron-client/src/main/c/aeron_client_conductor.h index 225d21bf4c..7c02ba00ea 100644 --- a/aeron-client/src/main/c/aeron_client_conductor.h +++ b/aeron-client/src/main/c/aeron_client_conductor.h @@ -226,7 +226,7 @@ typedef struct aeron_client_conductor_stct aeron_error_handler_t error_handler; void *error_handler_clientd; - aeron_error_frame_handler_t error_frame_handler; + aeron_publication_error_frame_handler_t error_frame_handler; void *error_frame_handler_clientd; aeron_on_new_publication_t on_new_publication; diff --git a/aeron-client/src/main/c/aeron_context.c b/aeron-client/src/main/c/aeron_context.c index f09fa70932..3a3f5f26cf 100644 --- a/aeron-client/src/main/c/aeron_context.c +++ b/aeron-client/src/main/c/aeron_context.c @@ -342,7 +342,7 @@ void *aeron_context_get_error_handler_clientd(aeron_context_t *context) return NULL != context ? context->error_handler_clientd : NULL; } -int aeron_context_set_error_frame_handler(aeron_context_t *context, aeron_error_frame_handler_t handler, void *clientd) +int aeron_context_set_publication_error_frame_handler(aeron_context_t *context, aeron_publication_error_frame_handler_t handler, void *clientd) { AERON_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context); @@ -351,12 +351,12 @@ int aeron_context_set_error_frame_handler(aeron_context_t *context, aeron_error_ return 0; } -aeron_error_frame_handler_t aeron_context_get_error_frame_handler(aeron_context_t *context) +aeron_publication_error_frame_handler_t aeron_context_get_publication_error_frame_handler(aeron_context_t *context) { return NULL != context ? context->error_frame_handler : NULL; } -void *aeron_context_get_error_frame_handler_clientd(aeron_context_t *context) +void *aeron_context_get_publication_error_frame_handler_clientd(aeron_context_t *context) { return NULL != context ? context->error_frame_handler_clientd : NULL; } diff --git a/aeron-client/src/main/c/aeron_context.h b/aeron-client/src/main/c/aeron_context.h index 1d2694fc85..f56638df9a 100644 --- a/aeron-client/src/main/c/aeron_context.h +++ b/aeron-client/src/main/c/aeron_context.h @@ -55,7 +55,7 @@ typedef struct aeron_context_stct aeron_on_unavailable_counter_t on_unavailable_counter; void *on_unavailable_counter_clientd; - aeron_error_frame_handler_t error_frame_handler; + aeron_publication_error_frame_handler_t error_frame_handler; void *error_frame_handler_clientd; aeron_agent_on_start_func_t agent_on_start_func; diff --git a/aeron-client/src/main/c/aeronc.h b/aeron-client/src/main/c/aeronc.h index fd701a7c89..a0599c8919 100644 --- a/aeron-client/src/main/c/aeronc.h +++ b/aeron-client/src/main/c/aeronc.h @@ -156,7 +156,7 @@ typedef void (*aeron_error_handler_t)(void *clientd, int errcode, const char *me * The data passed to this callback will only be valid for the lifetime of the callback. The user should use * aeron_publication_error_values_copy if they require the data to live longer than that. */ -typedef void (*aeron_error_frame_handler_t)(void *clientd, aeron_publication_error_values_t *error_frame); +typedef void (*aeron_publication_error_frame_handler_t)(void *clientd, aeron_publication_error_values_t *error_frame); /** * Copy an existing aeron_publication_error_values_t to the supplied pointer. The caller is responsible for freeing the @@ -184,9 +184,9 @@ int aeron_context_set_error_handler(aeron_context_t *context, aeron_error_handle aeron_error_handler_t aeron_context_get_error_handler(aeron_context_t *context); void *aeron_context_get_error_handler_clientd(aeron_context_t *context); -int aeron_context_set_error_frame_handler(aeron_context_t *context, aeron_error_frame_handler_t handler, void *clientd); -aeron_error_frame_handler_t aeron_context_get_error_frame_handler(aeron_context_t *context); -void *aeron_context_get_error_frame_handler_clientd(aeron_context_t *context); +int aeron_context_set_publication_error_frame_handler(aeron_context_t *context, aeron_publication_error_frame_handler_t handler, void *clientd); +aeron_publication_error_frame_handler_t aeron_context_get_publication_error_frame_handler(aeron_context_t *context); +void *aeron_context_get_publication_error_frame_handler_clientd(aeron_context_t *context); /** * Function called by aeron_client_t to deliver notification that the media driver has added an aeron_publication_t diff --git a/aeron-client/src/main/c/command/aeron_control_protocol.h b/aeron-client/src/main/c/command/aeron_control_protocol.h index 75d95dacd5..92f526de2c 100644 --- a/aeron-client/src/main/c/command/aeron_control_protocol.h +++ b/aeron-client/src/main/c/command/aeron_control_protocol.h @@ -229,8 +229,8 @@ struct aeron_publication_error_stct int64_t receiver_id; int64_t group_tag; int16_t address_type; - uint16_t address_port; - uint8_t address[16]; + uint16_t source_port; + uint8_t source_address[16]; int32_t error_code; int32_t error_message_length; uint8_t error_message[1]; diff --git a/aeron-client/src/main/cpp_wrapper/Context.h b/aeron-client/src/main/cpp_wrapper/Context.h index b7c1420e93..12b39a7009 100644 --- a/aeron-client/src/main/cpp_wrapper/Context.h +++ b/aeron-client/src/main/cpp_wrapper/Context.h @@ -657,7 +657,7 @@ class Context throw IllegalArgumentException(std::string(aeron_errmsg()), SOURCEINFO); } - if (aeron_context_set_error_frame_handler( + if (aeron_context_set_publication_error_frame_handler( m_context, errorFrameHandlerCallback, const_cast(reinterpret_cast(&m_onErrorFrameHandler))) < 0) diff --git a/aeron-driver/src/main/c/aeron_driver_conductor.c b/aeron-driver/src/main/c/aeron_driver_conductor.c index ebea2302d7..0fd906f665 100644 --- a/aeron-driver/src/main/c/aeron_driver_conductor.c +++ b/aeron-driver/src/main/c/aeron_driver_conductor.c @@ -2584,25 +2584,25 @@ void aeron_driver_conductor_on_publication_error(void *clientd, void *item) response->receiver_id = error->receiver_id; response->group_tag = error->group_tag; - memset(&response->address[0], 0, sizeof(response->address)); + memset(&response->source_address[0], 0, sizeof(response->source_address)); if (AF_INET == error->src_address.ss_family) { struct sockaddr_in *src_addr_in = (struct sockaddr_in *)&error->src_address; response->address_type = AERON_RESPONSE_ADDRESS_TYPE_IPV4; - response->address_port = ntohs(src_addr_in->sin_port); - memcpy(&response->address[0], &src_addr_in->sin_addr, sizeof(src_addr_in->sin_addr)); + response->source_port = ntohs(src_addr_in->sin_port); + memcpy(&response->source_address[0], &src_addr_in->sin_addr, sizeof(src_addr_in->sin_addr)); } else if (AF_INET6 == error->src_address.ss_family) { struct sockaddr_in6 *src_addr_in6 = (struct sockaddr_in6 *)&error->src_address; response->address_type = AERON_RESPONSE_ADDRESS_TYPE_IPV6; - response->address_port = ntohs(src_addr_in6->sin6_port); - memcpy(&response->address[0], &src_addr_in6->sin6_addr, sizeof(src_addr_in6->sin6_addr)); + response->source_port = ntohs(src_addr_in6->sin6_port); + memcpy(&response->source_address[0], &src_addr_in6->sin6_addr, sizeof(src_addr_in6->sin6_addr)); } else { response->address_type = 0; - response->address_port = 0; + response->source_port = 0; } response->error_message_length = error->error_length; diff --git a/aeron-driver/src/main/c/aeron_publication_image.c b/aeron-driver/src/main/c/aeron_publication_image.c index 2071b61565..73b3ef21cc 100644 --- a/aeron-driver/src/main/c/aeron_publication_image.c +++ b/aeron-driver/src/main/c/aeron_publication_image.c @@ -705,7 +705,6 @@ int aeron_publication_image_send_pending_status_message(aeron_publication_image_ AERON_GET_VOLATILE(change_number, image->end_sm_change); const bool has_sm_timed_out = now_ns > (image->time_of_last_sm_ns + image->sm_timeout_ns); - // TODO: Send error frame instead. if (NULL != image->invalidation_reason) { if (has_sm_timed_out) From 42bc8375f8d393806e8429f109ea7c8d449275d4 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Mon, 2 Sep 2024 16:55:47 +1200 Subject: [PATCH 66/67] [C] Add logging for REJECT_IMAGE. --- .../src/main/c/agent/aeron_driver_agent.c | 20 +++++++++++++++++++ .../src/main/c/agent/aeron_driver_agent.h | 5 +++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/aeron-driver/src/main/c/agent/aeron_driver_agent.c b/aeron-driver/src/main/c/agent/aeron_driver_agent.c index d734297fa0..0b453d51f4 100644 --- a/aeron-driver/src/main/c/agent/aeron_driver_agent.c +++ b/aeron-driver/src/main/c/agent/aeron_driver_agent.c @@ -131,6 +131,7 @@ static aeron_driver_agent_log_event_t log_events[] = { "SEND_NAK_MESSAGE", AERON_DRIVER_AGENT_EVENT_TYPE_OTHER, false }, { "RESEND", AERON_DRIVER_AGENT_EVENT_TYPE_OTHER, false }, { "CMD_IN_REMOVE_DESTINATION_BY_ID", AERON_DRIVER_AGENT_EVENT_TYPE_CMD_IN, false }, + { "CMD_IN_REJECT_IMAGE", AERON_DRIVER_AGENT_EVENT_TYPE_CMD_IN, false }, { "ADD_DYNAMIC_DISSECTOR", AERON_DRIVER_AGENT_EVENT_TYPE_OTHER, false }, { "DYNAMIC_DISSECTOR_EVENT", AERON_DRIVER_AGENT_EVENT_TYPE_OTHER, false }, }; @@ -601,6 +602,9 @@ static aeron_driver_agent_event_t command_id_to_driver_event_id(const int32_t ms case AERON_COMMAND_REMOVE_DESTINATION_BY_ID: return AERON_DRIVER_EVENT_CMD_IN_REMOVE_DESTINATION_BY_ID; + case AERON_COMMAND_REJECT_IMAGE: + return AERON_DRIVER_EVENT_CMD_IN_REJECT_IMAGE; + default: return AERON_DRIVER_EVENT_UNKNOWN_EVENT; } @@ -1557,6 +1561,22 @@ static const char *dissect_cmd_in(int64_t cmd_id, const void *message, size_t le break; } + case AERON_COMMAND_REJECT_IMAGE: + { + aeron_reject_image_command_t *command = (aeron_reject_image_command_t *)message; + + snprintf( + buffer, + sizeof(buffer) - 1, + "clientId=%" PRId64 " correlationId=%" PRId64 " imageCorrelationId=%" PRId64 "position=%" PRId64 " reason=%.*s", + command->correlated.client_id, + command->correlated.correlation_id, + command->image_correlation_id, + command->position, + command->reason_length, + command->reason_text); + } + default: break; } diff --git a/aeron-driver/src/main/c/agent/aeron_driver_agent.h b/aeron-driver/src/main/c/agent/aeron_driver_agent.h index 89c050e40d..bc62261876 100644 --- a/aeron-driver/src/main/c/agent/aeron_driver_agent.h +++ b/aeron-driver/src/main/c/agent/aeron_driver_agent.h @@ -79,10 +79,11 @@ typedef enum aeron_driver_agent_event_enum AERON_DRIVER_EVENT_SEND_NAK_MESSAGE = 54, AERON_DRIVER_EVENT_RESEND = 55, AERON_DRIVER_EVENT_CMD_IN_REMOVE_DESTINATION_BY_ID = 56, + AERON_DRIVER_EVENT_CMD_IN_REJECT_IMAGE = 57, // C-specific events. Note: event IDs are dynamic to avoid gaps in the sparse arrays. - AERON_DRIVER_EVENT_ADD_DYNAMIC_DISSECTOR = 57, - AERON_DRIVER_EVENT_DYNAMIC_DISSECTOR_EVENT = 58, + AERON_DRIVER_EVENT_ADD_DYNAMIC_DISSECTOR = 58, + AERON_DRIVER_EVENT_DYNAMIC_DISSECTOR_EVENT = 59, } aeron_driver_agent_event_t; From a8d3c6fc9f18baaca11a9d9c0a9ea06b4ae1b0e4 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Tue, 3 Sep 2024 07:29:53 +1200 Subject: [PATCH 67/67] [Java] Fix CodeQL and add test to RejectImageTest. --- .../test/java/io/aeron/RejectImageTest.java | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java b/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java index 54e96206cf..6f3194bddf 100644 --- a/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java +++ b/aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java @@ -93,16 +93,25 @@ private TestMediaDriver launch() private static final class QueuedErrorFrameHandler implements PublicationErrorFrameHandler { + private final AtomicInteger counter = new AtomicInteger(0); private final OneToOneConcurrentArrayQueue errorFrameQueue = new OneToOneConcurrentArrayQueue<>(512); public void onPublicationError(final PublicationErrorFrame errorFrame) { - errorFrameQueue.offer(errorFrame.clone()); + if (!errorFrameQueue.offer(errorFrame.clone())) + { + counter.incrementAndGet(); + } } PublicationErrorFrame poll() { + if (counter.get() > 0) + { + throw new RuntimeException("Failed to offer to the errorFrameQueue in the test"); + } + return errorFrameQueue.poll(); } } @@ -429,6 +438,32 @@ void shouldErrorIfRejectionReasonIsTooLong() } } + + @Test + @InterruptAfter(10) + void shouldErrorIfRejectionReasonIsTooLongForLocalBuffer() + { + context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3)); + final byte[] bytes = new byte[1024 * 1024]; + Arrays.fill(bytes, (byte)'x'); + final String tooLongReason = new String(bytes, US_ASCII); + + final TestMediaDriver driver = launch(); + + final Aeron.Context ctx = new Aeron.Context() + .aeronDirectoryName(driver.aeronDirectoryName()); + + try (Aeron aeron = Aeron.connect(ctx); + Publication pub = aeron.addPublication(channel, streamId); + Subscription sub = aeron.addSubscription(channel, streamId)) + { + Tests.awaitConnected(pub); + Tests.awaitConnected(sub); + + assertThrows(IllegalArgumentException.class, () -> sub.imageAtIndex(0).reject(tooLongReason)); + } + } + @Test @InterruptAfter(10) void shouldErrorIfUsingAndIpcChannel()