diff --git a/RELEASE.md b/RELEASE.md index 533ed191..9a0222b1 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,4 +1,4 @@ #### Port Aeron.NET has been ported against Java version: -- Agrona: v0.9.6 (b6904e5) -- Aeron: v1.3.0 (baa52e0) +- Agrona: v0.9.7 (e3408d6) +- Aeron: v1.4.0 (cc48906) diff --git a/driver/media-driver.jar b/driver/media-driver.jar index b87fceac..507fc74f 100644 Binary files a/driver/media-driver.jar and b/driver/media-driver.jar differ diff --git a/driver/start-media-driver.bat b/driver/start-media-driver.bat index 6c59bc05..c0f0854f 100644 --- a/driver/start-media-driver.bat +++ b/driver/start-media-driver.bat @@ -1,6 +1,6 @@ @echo off -echo Starting Media Driver... +echo Media Driver Started... java -cp media-driver.jar ^ - io.aeron.driver.MediaDriver -Daeron.threading.mode=SHARED + io.aeron.driver.MediaDriver ipc.properties echo Media Driver Stopped. pause \ No newline at end of file diff --git a/src/Adaptive.Aeron.Tests/BufferBuilderTests.cs b/src/Adaptive.Aeron.Tests/BufferBuilderTests.cs index f34768ae..08aa7b97 100644 --- a/src/Adaptive.Aeron.Tests/BufferBuilderTests.cs +++ b/src/Adaptive.Aeron.Tests/BufferBuilderTests.cs @@ -34,15 +34,15 @@ public void Setup() [Test] public void ShouldInitialiseToDefaultValues() { - Assert.That(_bufferBuilder.Capacity(), Is.EqualTo(BufferBuilder.INITIAL_CAPACITY)); - Assert.That(_bufferBuilder.Buffer().Capacity, Is.EqualTo(BufferBuilder.INITIAL_CAPACITY)); + Assert.That(_bufferBuilder.Capacity(), Is.EqualTo(BufferBuilder.MIN_ALLOCATED_CAPACITY)); + Assert.That(_bufferBuilder.Buffer().Capacity, Is.EqualTo(BufferBuilder.MIN_ALLOCATED_CAPACITY)); Assert.That(_bufferBuilder.Limit(), Is.EqualTo(0)); } [Test] public void ShouldAppendNothingForZeroLength() { - UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[BufferBuilder.INITIAL_CAPACITY]); + UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[BufferBuilder.MIN_ALLOCATED_CAPACITY]); _bufferBuilder.Append(srcBuffer, 0, 0); @@ -52,7 +52,7 @@ public void ShouldAppendNothingForZeroLength() [Test] public void ShouldGrowToMultipleOfInitialCapaity() { - int srcCapacity = BufferBuilder.INITIAL_CAPACITY * 5; + int srcCapacity = BufferBuilder.MIN_ALLOCATED_CAPACITY * 5; UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[srcCapacity]); _bufferBuilder.Append(srcBuffer, 0, srcBuffer.Capacity); @@ -64,7 +64,7 @@ public void ShouldGrowToMultipleOfInitialCapaity() [Test] public void ShouldAppendThenReset() { - UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[BufferBuilder.INITIAL_CAPACITY]); + UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[BufferBuilder.MIN_ALLOCATED_CAPACITY]); _bufferBuilder.Append(srcBuffer, 0, srcBuffer.Capacity); @@ -78,7 +78,7 @@ public void ShouldAppendThenReset() [Test] public void ShouldAppendOneBufferWithoutResizing() { - var srcBuffer = new UnsafeBuffer(new byte[BufferBuilder.INITIAL_CAPACITY]); + var srcBuffer = new UnsafeBuffer(new byte[BufferBuilder.MIN_ALLOCATED_CAPACITY]); var bytes = Encoding.UTF8.GetBytes("Hello World"); srcBuffer.PutBytes(0, bytes, 0, bytes.Length); @@ -88,14 +88,14 @@ public void ShouldAppendOneBufferWithoutResizing() _bufferBuilder.Buffer().GetBytes(0, temp, 0, bytes.Length); Assert.That(_bufferBuilder.Limit(), Is.EqualTo(bytes.Length)); - Assert.That(_bufferBuilder.Capacity(), Is.EqualTo(BufferBuilder.INITIAL_CAPACITY)); + Assert.That(_bufferBuilder.Capacity(), Is.EqualTo(BufferBuilder.MIN_ALLOCATED_CAPACITY)); Assert.That(temp, Is.EqualTo(bytes)); } [Test] public void ShouldAppendTwoBuffersWithoutResizing() { - UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[BufferBuilder.INITIAL_CAPACITY]); + UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[BufferBuilder.MIN_ALLOCATED_CAPACITY]); byte[] bytes = Encoding.UTF8.GetBytes("1111111122222222"); srcBuffer.PutBytes(0, bytes, 0, bytes.Length); @@ -106,7 +106,7 @@ public void ShouldAppendTwoBuffersWithoutResizing() _bufferBuilder.Buffer().GetBytes(0, temp, 0, bytes.Length); Assert.That(_bufferBuilder.Limit(), Is.EqualTo(bytes.Length)); - Assert.That(_bufferBuilder.Capacity(), Is.EqualTo(BufferBuilder.INITIAL_CAPACITY)); + Assert.That(_bufferBuilder.Capacity(), Is.EqualTo(BufferBuilder.MIN_ALLOCATED_CAPACITY)); Assert.That(temp, Is.EqualTo(bytes)); } @@ -176,7 +176,7 @@ public void ShouldAppendTwoBuffersAndResize() [Test] public void ShouldCompactBufferToLowerLimit() { - int bufferLength = BufferBuilder.INITIAL_CAPACITY / 2; + int bufferLength = BufferBuilder.MIN_ALLOCATED_CAPACITY / 2; byte[] buffer = new byte[bufferLength]; UnsafeBuffer srcBuffer = new UnsafeBuffer(buffer); diff --git a/src/Adaptive.Aeron.Tests/ClientConductorTest.cs b/src/Adaptive.Aeron.Tests/ClientConductorTest.cs index 711d655f..bd7bd0c8 100644 --- a/src/Adaptive.Aeron.Tests/ClientConductorTest.cs +++ b/src/Adaptive.Aeron.Tests/ClientConductorTest.cs @@ -53,6 +53,9 @@ public class ClientConductorTest private const long INTER_SERVICE_TIMEOUT_MS = 1000; private const long PUBLICATION_CONNECTION_TIMEOUT_MS = 5000; + private const int SUBSCRIBER_POSITION_ID = 2; + private const int SUBSCRIBER_POSITION_REGISTRATION_ID = 4001; + private const string SOURCE_INFO = "127.0.0.1:40789"; private PublicationBuffersReadyFlyweight PublicationReady; @@ -77,7 +80,6 @@ public class ClientConductorTest private UnavailableImageHandler MockUnavailableImageHandler; private ILogBuffersFactory LogBuffersFactory; private ILock mockClientLock = A.Fake(); - private Dictionary SubscriberPositionMap; private bool SuppressPrintError = false; @@ -107,9 +109,7 @@ public void SetUp() MockUnavailableImageHandler = A.Fake(); LogBuffersFactory = A.Fake(); - - SubscriberPositionMap = new Dictionary(); // should return -1 when element does not exist - + DriverProxy = A.Fake(); A.CallTo(() => mockClientLock.TryLock()).Returns(true); @@ -145,12 +145,11 @@ public void SetUp() ErrorResponse.Wrap(ErrorMessageBuffer, 0); PublicationReady.CorrelationId(CORRELATION_ID); + PublicationReady.RegistrationId(CORRELATION_ID); PublicationReady.SessionId(SESSION_ID_1); PublicationReady.StreamId(STREAM_ID_1); PublicationReady.LogFileName(SESSION_ID_1 + "-log"); - - SubscriberPositionMap.Add(CORRELATION_ID, 0); - + CorrelatedMessage.CorrelationId(CLOSE_CORRELATION_ID); var termBuffersSession1 = new UnsafeBuffer[LogBufferDescriptor.PARTITION_COUNT]; @@ -325,6 +324,7 @@ public void ClosingPublicationDoesNotRemoveOtherPublications() PublicationReady.SessionId(SESSION_ID_2); PublicationReady.LogFileName(SESSION_ID_2 + "-log"); PublicationReady.CorrelationId(CORRELATION_ID_2); + PublicationReady.RegistrationId(CORRELATION_ID_2); return PublicationReady.Length(); }); @@ -344,6 +344,7 @@ public void ShouldNotMapBuffersForUnknownCorrelationId() WhenReceiveBroadcastOnMessage(ControlProtocolEvents.ON_PUBLICATION_READY, PublicationReadyBuffer, buffer => { PublicationReady.CorrelationId(UNKNOWN_CORRELATION_ID); + PublicationReady.RegistrationId(UNKNOWN_CORRELATION_ID); return PublicationReady.Length(); }); @@ -432,9 +433,16 @@ public void ClientNotifiedOfNewImageShouldMapLogFile() return CorrelatedMessageFlyweight.LENGTH; }); - Conductor.AddSubscription(CHANNEL, STREAM_ID_1); + Subscription subscription = Conductor.AddSubscription(CHANNEL, STREAM_ID_1); - Conductor.OnAvailableImage(STREAM_ID_1, SESSION_ID_1, SubscriberPositionMap, SESSION_ID_1 + "-log", SOURCE_INFO, CORRELATION_ID); + Conductor.OnAvailableImage( + CORRELATION_ID, + STREAM_ID_1, + SESSION_ID_1, + subscription.RegistrationId, + SUBSCRIBER_POSITION_ID, + SESSION_ID_1 + "-log", + SOURCE_INFO); A.CallTo(() => LogBuffersFactory.Map(SESSION_ID_1 + "-log", A._)).MustHaveHappened(); } @@ -450,13 +458,20 @@ public void ClientNotifiedOfNewAndInactiveImages() var subscription = Conductor.AddSubscription(CHANNEL, STREAM_ID_1); - Conductor.OnAvailableImage(STREAM_ID_1, SESSION_ID_1, SubscriberPositionMap, SESSION_ID_1 + "-log", SOURCE_INFO, CORRELATION_ID); + Conductor.OnAvailableImage( + CORRELATION_ID, + STREAM_ID_1, + SESSION_ID_1, + subscription.RegistrationId, + SUBSCRIBER_POSITION_ID, + SESSION_ID_1 + "-log", + SOURCE_INFO); Assert.False(subscription.HasNoImages()); A.CallTo(() => MockAvailableImageHandler(A._)).MustHaveHappened(); - Conductor.OnUnavailableImage(STREAM_ID_1, CORRELATION_ID); + Conductor.OnUnavailableImage(CORRELATION_ID, STREAM_ID_1); A.CallTo(() => MockUnavailableImageHandler(A._)).MustHaveHappened(); @@ -466,7 +481,14 @@ public void ClientNotifiedOfNewAndInactiveImages() [Test] public void ShouldIgnoreUnknownNewImage() { - Conductor.OnAvailableImage(STREAM_ID_2, SESSION_ID_2, SubscriberPositionMap, SESSION_ID_2 + "-log", SOURCE_INFO, CORRELATION_ID_2); + Conductor.OnAvailableImage( + CORRELATION_ID_2, + STREAM_ID_2, + SESSION_ID_2, + SUBSCRIBER_POSITION_REGISTRATION_ID, + SUBSCRIBER_POSITION_ID, + SESSION_ID_2 + "-log", + SOURCE_INFO); A.CallTo(() => LogBuffersFactory.Map(A._, A._)).MustNotHaveHappened(); A.CallTo(() => MockAvailableImageHandler(A._)).MustNotHaveHappened(); @@ -475,7 +497,7 @@ public void ShouldIgnoreUnknownNewImage() [Test] public void ShouldIgnoreUnknownInactiveImage() { - Conductor.OnUnavailableImage(STREAM_ID_2, CORRELATION_ID_2); + Conductor.OnUnavailableImage(CORRELATION_ID_2, STREAM_ID_2); A.CallTo(() => LogBuffersFactory.Map(A._, A._)).MustNotHaveHappened(); A.CallTo(() => MockAvailableImageHandler(A._)).MustNotHaveHappened(); @@ -493,6 +515,8 @@ public void ShouldTimeoutInterServiceIfTooLongBetweenDoWorkCalls() Conductor.DoWork(); A.CallTo(() => MockClientErrorHandler(A._)).MustHaveHappened(); + + Assert.True(Conductor.IsClosed()); } private void WhenReceiveBroadcastOnMessage(int msgTypeId, IMutableDirectBuffer buffer, Func filler) diff --git a/src/Adaptive.Aeron.Tests/DriverProxyTests.cs b/src/Adaptive.Aeron.Tests/DriverProxyTests.cs index 584b099a..c52a534f 100644 --- a/src/Adaptive.Aeron.Tests/DriverProxyTests.cs +++ b/src/Adaptive.Aeron.Tests/DriverProxyTests.cs @@ -28,13 +28,14 @@ public class DriverProxyTest public void Setup() { conductorBuffer = new ManyToOneRingBuffer(new UnsafeBuffer(new byte[RingBufferDescriptor.TrailerLength + 1024])); - conductor = new DriverProxy(conductorBuffer); + conductor = new DriverProxy(conductorBuffer, CLIENT_ID); } public const string CHANNEL = "aeron:udp?interface=localhost:40123|endpoint=localhost:40124"; private const int STREAM_ID = 1; private const long CORRELATION_ID = 3; + private const long CLIENT_ID = 7; private IRingBuffer conductorBuffer; private DriverProxy conductor; diff --git a/src/Adaptive.Aeron.Tests/ImageTest.cs b/src/Adaptive.Aeron.Tests/ImageTest.cs index 9fda9a88..2de238b0 100644 --- a/src/Adaptive.Aeron.Tests/ImageTest.cs +++ b/src/Adaptive.Aeron.Tests/ImageTest.cs @@ -51,7 +51,7 @@ static ImageTest() private UnsafeBuffer RcvBuffer; private DataHeaderFlyweight DataHeader; private FragmentHandler MockFragmentHandler; - private IControlledFragmentHandler MockControlledFragmentHandler; + private ControlledFragmentHandler MockControlledFragmentHandler; private IPosition Position; private LogBuffers LogBuffers; private ErrorHandler ErrorHandler; @@ -65,7 +65,7 @@ public void SetUp() RcvBuffer = new UnsafeBuffer(new byte[ALIGNED_FRAME_LENGTH]); DataHeader = new DataHeaderFlyweight(); MockFragmentHandler = A.Fake(); - MockControlledFragmentHandler = A.Fake(); + MockControlledFragmentHandler = A.Fake(); Position = A.Fake(options => options.Wrapping(new AtomicLongPosition())); LogBuffers = A.Fake(); ErrorHandler = A.Fake(); @@ -96,6 +96,7 @@ public void ShouldHandleClosedImage() Assert.True(image.Closed); Assert.AreEqual(0, image.Poll(MockFragmentHandler, int.MaxValue)); + Assert.AreEqual(0, image.Position()); } [Test] @@ -183,13 +184,13 @@ public void ShouldPollOneFragmentToControlledFragmentHandlerOnContinue() InsertDataFrame(INITIAL_TERM_ID, OffsetForFrame(0)); - A.CallTo(() => MockControlledFragmentHandler.OnFragment(A._, A._, A._, A
._)).Returns(ControlledFragmentHandlerAction.CONTINUE); + A.CallTo(() => MockControlledFragmentHandler.Invoke(A._, A._, A._, A
._)).Returns(ControlledFragmentHandlerAction.CONTINUE); var fragmentsRead = image.ControlledPoll(MockControlledFragmentHandler, int.MaxValue); Assert.AreEqual(1, fragmentsRead); - A.CallTo(() => MockControlledFragmentHandler.OnFragment(A._, DataHeaderFlyweight.HEADER_LENGTH, DATA.Length, A
._)).MustHaveHappened().Then( + A.CallTo(() => MockControlledFragmentHandler(A._, DataHeaderFlyweight.HEADER_LENGTH, DATA.Length, A
._)).MustHaveHappened().Then( A.CallTo(() => Position.SetOrdered(initialPosition + ALIGNED_FRAME_LENGTH)).MustHaveHappened()); } @@ -202,7 +203,7 @@ public void ShouldUpdatePositionOnRethrownExceptionInControlledPoll() InsertDataFrame(INITIAL_TERM_ID, OffsetForFrame(0)); - A.CallTo(() => MockControlledFragmentHandler.OnFragment(A._, A._, A._, A
._)).Throws(new Exception()); + A.CallTo(() => MockControlledFragmentHandler.Invoke(A._, A._, A._, A
._)).Throws(new Exception()); A.CallTo(ErrorHandler).Throws(new Exception()); @@ -220,7 +221,7 @@ public void ShouldUpdatePositionOnRethrownExceptionInControlledPoll() Assert.True(thrown); Assert.AreEqual(initialPosition + ALIGNED_FRAME_LENGTH, image.Position()); - A.CallTo(() => MockControlledFragmentHandler.OnFragment(A._, DataHeaderFlyweight.HEADER_LENGTH, DATA.Length, A
._)).MustHaveHappened(); + A.CallTo(() => MockControlledFragmentHandler.Invoke(A._, DataHeaderFlyweight.HEADER_LENGTH, DATA.Length, A
._)).MustHaveHappened(); } [Test] @@ -263,14 +264,14 @@ public void ShouldNotPollOneFragmentToControlledFragmentHandlerOnAbort() InsertDataFrame(INITIAL_TERM_ID, OffsetForFrame(0)); - A.CallTo(() => MockControlledFragmentHandler.OnFragment(A._, A._, A._, A
._)).Returns(ControlledFragmentHandlerAction.ABORT); + A.CallTo(() => MockControlledFragmentHandler.Invoke(A._, A._, A._, A
._)).Returns(ControlledFragmentHandlerAction.ABORT); var fragmentsRead = image.ControlledPoll(MockControlledFragmentHandler, int.MaxValue); Assert.AreEqual(0, fragmentsRead); Assert.AreEqual(initialPosition, image.Position()); - A.CallTo(() => MockControlledFragmentHandler.OnFragment(A._, DataHeaderFlyweight.HEADER_LENGTH, DATA.Length, A
._)).MustHaveHappened(); + A.CallTo(() => MockControlledFragmentHandler.Invoke(A._, DataHeaderFlyweight.HEADER_LENGTH, DATA.Length, A
._)).MustHaveHappened(); } [Test] @@ -283,13 +284,13 @@ public void ShouldPollOneFragmentToControlledFragmentHandlerOnBreak() InsertDataFrame(INITIAL_TERM_ID, OffsetForFrame(0)); InsertDataFrame(INITIAL_TERM_ID, OffsetForFrame(1)); - A.CallTo(() => MockControlledFragmentHandler.OnFragment(A._, A._, A._, A
._)).Returns(ControlledFragmentHandlerAction.BREAK); + A.CallTo(() => MockControlledFragmentHandler.Invoke(A._, A._, A._, A
._)).Returns(ControlledFragmentHandlerAction.BREAK); var fragmentsRead = image.ControlledPoll(MockControlledFragmentHandler, int.MaxValue); Assert.AreEqual(1, fragmentsRead); - A.CallTo(() => MockControlledFragmentHandler.OnFragment(A._, DataHeaderFlyweight.HEADER_LENGTH, DATA.Length, A
._)).MustHaveHappened().Then( + A.CallTo(() => MockControlledFragmentHandler.Invoke(A._, DataHeaderFlyweight.HEADER_LENGTH, DATA.Length, A
._)).MustHaveHappened().Then( A.CallTo(() => Position.SetOrdered(initialPosition + ALIGNED_FRAME_LENGTH)).MustHaveHappened()); } @@ -303,15 +304,15 @@ public void ShouldPollFragmentsToControlledFragmentHandlerOnCommit() InsertDataFrame(INITIAL_TERM_ID, OffsetForFrame(0)); InsertDataFrame(INITIAL_TERM_ID, OffsetForFrame(1)); - A.CallTo(() => MockControlledFragmentHandler.OnFragment(A._, A._, A._, A
._)).Returns(ControlledFragmentHandlerAction.COMMIT); + A.CallTo(() => MockControlledFragmentHandler.Invoke(A._, A._, A._, A
._)).Returns(ControlledFragmentHandlerAction.COMMIT); var fragmentsRead = image.ControlledPoll(MockControlledFragmentHandler, int.MaxValue); Assert.AreEqual(2, fragmentsRead); - A.CallTo(() => MockControlledFragmentHandler.OnFragment(A._, DataHeaderFlyweight.HEADER_LENGTH, DATA.Length, A
._)).MustHaveHappened() + A.CallTo(() => MockControlledFragmentHandler.Invoke(A._, DataHeaderFlyweight.HEADER_LENGTH, DATA.Length, A
._)).MustHaveHappened() .Then(A.CallTo(() => Position.SetOrdered(initialPosition + ALIGNED_FRAME_LENGTH)).MustHaveHappened()) - .Then(A.CallTo(() => MockControlledFragmentHandler.OnFragment(A._, ALIGNED_FRAME_LENGTH + DataHeaderFlyweight.HEADER_LENGTH, DATA.Length, A
._)).MustHaveHappened()) + .Then(A.CallTo(() => MockControlledFragmentHandler.Invoke(A._, ALIGNED_FRAME_LENGTH + DataHeaderFlyweight.HEADER_LENGTH, DATA.Length, A
._)).MustHaveHappened()) .Then(A.CallTo(() => Position.SetOrdered(initialPosition + ALIGNED_FRAME_LENGTH*2)).MustHaveHappened()); } @@ -326,17 +327,17 @@ public void ShouldUpdatePositionToEndOfCommittedFragmentOnCommit() InsertDataFrame(INITIAL_TERM_ID, OffsetForFrame(1)); InsertDataFrame(INITIAL_TERM_ID, OffsetForFrame(2)); - A.CallTo(() => MockControlledFragmentHandler.OnFragment(A._, A._, A._, A
._)) + A.CallTo(() => MockControlledFragmentHandler.Invoke(A._, A._, A._, A
._)) .ReturnsNextFromSequence(ControlledFragmentHandlerAction.CONTINUE, ControlledFragmentHandlerAction.COMMIT, ControlledFragmentHandlerAction.CONTINUE); var fragmentsRead = image.ControlledPoll(MockControlledFragmentHandler, int.MaxValue); Assert.AreEqual(3, fragmentsRead); - A.CallTo(() => MockControlledFragmentHandler.OnFragment(A._, DataHeaderFlyweight.HEADER_LENGTH, DATA.Length, A
._)).MustHaveHappened() - .Then(A.CallTo(() => MockControlledFragmentHandler.OnFragment(A._, ALIGNED_FRAME_LENGTH + DataHeaderFlyweight.HEADER_LENGTH, DATA.Length, A
._)).MustHaveHappened()) + A.CallTo(() => MockControlledFragmentHandler.Invoke(A._, DataHeaderFlyweight.HEADER_LENGTH, DATA.Length, A
._)).MustHaveHappened() + .Then(A.CallTo(() => MockControlledFragmentHandler.Invoke(A._, ALIGNED_FRAME_LENGTH + DataHeaderFlyweight.HEADER_LENGTH, DATA.Length, A
._)).MustHaveHappened()) .Then(A.CallTo(() => Position.SetOrdered(initialPosition + ALIGNED_FRAME_LENGTH * 2)).MustHaveHappened()) - .Then(A.CallTo(() => MockControlledFragmentHandler.OnFragment(A._, 2 * ALIGNED_FRAME_LENGTH + DataHeaderFlyweight.HEADER_LENGTH, DATA.Length, A
._)).MustHaveHappened()) + .Then(A.CallTo(() => MockControlledFragmentHandler.Invoke(A._, 2 * ALIGNED_FRAME_LENGTH + DataHeaderFlyweight.HEADER_LENGTH, DATA.Length, A
._)).MustHaveHappened()) .Then(A.CallTo(() => Position.SetOrdered(initialPosition + ALIGNED_FRAME_LENGTH * 3)).MustHaveHappened()); } @@ -350,14 +351,14 @@ public void ShouldPollFragmentsToControlledFragmentHandlerOnContinue() InsertDataFrame(INITIAL_TERM_ID, OffsetForFrame(0)); InsertDataFrame(INITIAL_TERM_ID, OffsetForFrame(1)); - A.CallTo(() => MockControlledFragmentHandler.OnFragment(A._, A._, A._, A
._)).Returns(ControlledFragmentHandlerAction.CONTINUE); + A.CallTo(() => MockControlledFragmentHandler.Invoke(A._, A._, A._, A
._)).Returns(ControlledFragmentHandlerAction.CONTINUE); var fragmentsRead = image.ControlledPoll(MockControlledFragmentHandler, int.MaxValue); Assert.AreEqual(2, fragmentsRead); - A.CallTo(() => MockControlledFragmentHandler.OnFragment(A._, DataHeaderFlyweight.HEADER_LENGTH, DATA.Length, A
._)).MustHaveHappened() - .Then(A.CallTo(() => MockControlledFragmentHandler.OnFragment(A._, ALIGNED_FRAME_LENGTH + DataHeaderFlyweight.HEADER_LENGTH, DATA.Length, A
._)).MustHaveHappened()) + A.CallTo(() => MockControlledFragmentHandler.Invoke(A._, DataHeaderFlyweight.HEADER_LENGTH, DATA.Length, A
._)).MustHaveHappened() + .Then(A.CallTo(() => MockControlledFragmentHandler.Invoke(A._, ALIGNED_FRAME_LENGTH + DataHeaderFlyweight.HEADER_LENGTH, DATA.Length, A
._)).MustHaveHappened()) .Then(A.CallTo(() => Position.SetOrdered(initialPosition + ALIGNED_FRAME_LENGTH * 2)).MustHaveHappened()); } diff --git a/src/Adaptive.Aeron.Tests/LogBuffer/TermAppenderTest.cs b/src/Adaptive.Aeron.Tests/LogBuffer/TermAppenderTest.cs index 06036ae5..55e6f9e0 100644 --- a/src/Adaptive.Aeron.Tests/LogBuffer/TermAppenderTest.cs +++ b/src/Adaptive.Aeron.Tests/LogBuffer/TermAppenderTest.cs @@ -67,10 +67,10 @@ public void ShouldPackResult() const int termId = 7; const int termOffset = -1; - long result = TermAppender.Pack(termId, termOffset); + long result = LogBufferDescriptor.PackTail(termId, termOffset); - Assert.That(TermAppender.TermId(result), Is.EqualTo(termId)); - Assert.That(TermAppender.TermOffset(result), Is.EqualTo(termOffset)); + Assert.That(LogBufferDescriptor.TermId(result), Is.EqualTo(termId)); + Assert.That(LogBufferDescriptor.TermOffset(result), Is.EqualTo(termOffset)); } [Test] @@ -83,11 +83,11 @@ public void ShouldAppendFrameToEmptyLog() int alignedFrameLength = BitUtil.Align(frameLength, FrameDescriptor.FRAME_ALIGNMENT); const int tail = 0; - _logMetaDataBuffer.PutLong(TermTailCounterOffset, TermAppender.Pack(TermID, tail)); + _logMetaDataBuffer.PutLong(TermTailCounterOffset, LogBufferDescriptor.PackTail(TermID, tail)); Assert.That(_termAppender.AppendUnfragmentedMessage(_headerWriter, buffer, 0, msgLength, RVS), Is.EqualTo((long) alignedFrameLength)); - Assert.AreEqual(LogBufferDescriptor.RawTailVolatile(_logMetaDataBuffer, PartionIndex), TermAppender.Pack(TermID, tail + alignedFrameLength)); + Assert.AreEqual(LogBufferDescriptor.RawTailVolatile(_logMetaDataBuffer, PartionIndex), LogBufferDescriptor.PackTail(TermID, tail + alignedFrameLength)); A.CallTo(() => _headerWriter.Write(_termBuffer, tail, frameLength, TermID)).MustHaveHappened() .Then(A.CallTo(() => _termBuffer.PutBytes(headerLength, buffer, 0, msgLength)).MustHaveHappened()) @@ -105,12 +105,12 @@ public void ShouldAppendFrameTwiceToLog() int alignedFrameLength = BitUtil.Align(frameLength, FrameDescriptor.FRAME_ALIGNMENT); int tail = 0; - _logMetaDataBuffer.PutLong(TermTailCounterOffset, TermAppender.Pack(TermID, tail)); + _logMetaDataBuffer.PutLong(TermTailCounterOffset, LogBufferDescriptor.PackTail(TermID, tail)); Assert.That(_termAppender.AppendUnfragmentedMessage(_headerWriter, buffer, 0, msgLength, RVS), Is.EqualTo((long) alignedFrameLength)); Assert.That(_termAppender.AppendUnfragmentedMessage(_headerWriter, buffer, 0, msgLength, RVS), Is.EqualTo((long) alignedFrameLength*2)); - Assert.AreEqual(LogBufferDescriptor.RawTailVolatile(_logMetaDataBuffer, PartionIndex), TermAppender.Pack(TermID, tail + alignedFrameLength * 2)); + Assert.AreEqual(LogBufferDescriptor.RawTailVolatile(_logMetaDataBuffer, PartionIndex), LogBufferDescriptor.PackTail(TermID, tail + alignedFrameLength * 2)); A.CallTo(() => _headerWriter.Write(_termBuffer, tail, frameLength, TermID)).MustHaveHappened() .Then(A.CallTo(() => _termBuffer.PutBytes(headerLength, buffer, 0, msgLength)).MustHaveHappened()) @@ -132,12 +132,12 @@ public void ShouldPadLogAndTripWhenAppendingWithInsufficientRemainingCapacity() UnsafeBuffer buffer = new UnsafeBuffer(new byte[128]); int frameLength = TermBufferLength - tailValue; - _logMetaDataBuffer.PutLong(TermTailCounterOffset, TermAppender.Pack(TermID, tailValue)); + _logMetaDataBuffer.PutLong(TermTailCounterOffset, LogBufferDescriptor.PackTail(TermID, tailValue)); - long expectResult = TermAppender.Pack(TermID, TermAppender.TRIPPED); + long expectResult = LogBufferDescriptor.PackTail(TermID, TermAppender.TRIPPED); Assert.That(_termAppender.AppendUnfragmentedMessage(_headerWriter, buffer, 0, msgLength, RVS), Is.EqualTo(expectResult)); - Assert.AreEqual(LogBufferDescriptor.RawTailVolatile(_logMetaDataBuffer, PartionIndex), TermAppender.Pack(TermID, tailValue + requiredFrameSize)); + Assert.AreEqual(LogBufferDescriptor.RawTailVolatile(_logMetaDataBuffer, PartionIndex), LogBufferDescriptor.PackTail(TermID, tailValue + requiredFrameSize)); A.CallTo(() => _headerWriter.Write(_termBuffer, tailValue, frameLength, TermID)).MustHaveHappened() .Then(A.CallTo(() => _termBuffer.PutShort(FrameDescriptor.TypeOffset(tailValue), (short) FrameDescriptor.PADDING_FRAME_TYPE)).MustHaveHappened()) @@ -154,11 +154,11 @@ public void ShouldFragmentMessageOverTwoFrames() UnsafeBuffer buffer = new UnsafeBuffer(new byte[msgLength]); int tail = 0; - _logMetaDataBuffer.PutLong(TermTailCounterOffset, TermAppender.Pack(TermID, tail)); + _logMetaDataBuffer.PutLong(TermTailCounterOffset, LogBufferDescriptor.PackTail(TermID, tail)); Assert.That(_termAppender.AppendFragmentedMessage(_headerWriter, buffer, 0, msgLength, MaxPayloadLength, RVS), Is.EqualTo((long) requiredCapacity)); - Assert.AreEqual(LogBufferDescriptor.RawTailVolatile(_logMetaDataBuffer, PartionIndex), TermAppender.Pack(TermID, tail + requiredCapacity)); + Assert.AreEqual(LogBufferDescriptor.RawTailVolatile(_logMetaDataBuffer, PartionIndex), LogBufferDescriptor.PackTail(TermID, tail + requiredCapacity)); A.CallTo(() => _headerWriter.Write(_termBuffer, tail, MaxFrameLength, TermID)).MustHaveHappened() .Then(A.CallTo(() => _termBuffer.PutBytes(tail + headerLength, buffer, 0, MaxPayloadLength)).MustHaveHappened()) @@ -184,7 +184,7 @@ public void ShouldClaimRegionForZeroCopyEncoding() A.CallTo(() => _termBuffer.PutIntOrdered(A._, A._)); - _logMetaDataBuffer.PutLong(TermTailCounterOffset, TermAppender.Pack(TermID, tail)); + _logMetaDataBuffer.PutLong(TermTailCounterOffset, LogBufferDescriptor.PackTail(TermID, tail)); Assert.That(_termAppender.Claim(_headerWriter, msgLength, bufferClaim), Is.EqualTo((long) alignedFrameLength)); @@ -195,7 +195,7 @@ public void ShouldClaimRegionForZeroCopyEncoding() bufferClaim.Commit(); - Assert.AreEqual(LogBufferDescriptor.RawTailVolatile(_logMetaDataBuffer, PartionIndex), TermAppender.Pack(TermID, tail + alignedFrameLength)); + Assert.AreEqual(LogBufferDescriptor.RawTailVolatile(_logMetaDataBuffer, PartionIndex), LogBufferDescriptor.PackTail(TermID, tail + alignedFrameLength)); A.CallTo(() => _headerWriter.Write(_termBuffer, tail, frameLength, TermID)).MustHaveHappened(); } diff --git a/src/Adaptive.Aeron.Tests/PublicationTest.cs b/src/Adaptive.Aeron.Tests/PublicationTest.cs index 98d0c2df..a5b07366 100644 --- a/src/Adaptive.Aeron.Tests/PublicationTest.cs +++ b/src/Adaptive.Aeron.Tests/PublicationTest.cs @@ -69,7 +69,15 @@ public void SetUp() _termBuffers[i] = new UnsafeBuffer(new byte[LogBufferDescriptor.TERM_MIN_LENGTH]); } - _publication = new Publication(_conductor, Channel, StreamID1, SessionID1, _publicationLimit, _logBuffers, CorrelationID); + _publication = new Publication( + _conductor, + Channel, + StreamID1, + SessionID1, + _publicationLimit, + _logBuffers, + CorrelationID, + CorrelationID); _publication.IncRef(); diff --git a/src/Adaptive.Aeron.sln.DotSettings b/src/Adaptive.Aeron.sln.DotSettings index 4ac96da7..050e688d 100644 --- a/src/Adaptive.Aeron.sln.DotSettings +++ b/src/Adaptive.Aeron.sln.DotSettings @@ -1,3 +1,5 @@  <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> \ No newline at end of file + <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> \ No newline at end of file diff --git a/src/Adaptive.Aeron/Adaptive.Aeron.csproj b/src/Adaptive.Aeron/Adaptive.Aeron.csproj index def04336..fd046eb2 100644 --- a/src/Adaptive.Aeron/Adaptive.Aeron.csproj +++ b/src/Adaptive.Aeron/Adaptive.Aeron.csproj @@ -63,6 +63,8 @@ + + @@ -78,8 +80,9 @@ - + + @@ -88,7 +91,7 @@ - + @@ -99,8 +102,8 @@ - - + + diff --git a/src/Adaptive.Aeron/Aeron.cs b/src/Adaptive.Aeron/Aeron.cs index 17769fe6..fa2eb53d 100644 --- a/src/Adaptive.Aeron/Aeron.cs +++ b/src/Adaptive.Aeron/Aeron.cs @@ -16,6 +16,7 @@ using System; using System.IO; +using System.Text; using System.Threading; using Adaptive.Aeron.Exceptions; using Adaptive.Agrona; @@ -31,9 +32,12 @@ namespace Adaptive.Aeron /// /// Aeron entry point for communicating to the Media Driver for creating s and s. /// Use an to configure the Aeron object. - /// + /// /// A client application requires only one Aeron object per Media Driver. - /// + /// + /// Note: If is not set and a + /// occurs then the process will face the wrath of . See . + /// /// public sealed class Aeron : IDisposable { @@ -87,7 +91,9 @@ public sealed class Aeron : IDisposable */ public const long PublicationConnectionTimeoutMs = 5000; + private readonly long _clientId; private readonly ILock _clientLock; + private readonly Context _ctx; private readonly ClientConductor _conductor; private readonly AgentRunner _conductorRunner; private readonly AgentInvoker _conductorInvoker; @@ -97,6 +103,8 @@ internal Aeron(Context ctx) { ctx.Conclude(); + _ctx = ctx; + _clientId = ctx.ClientId(); _clientLock = ctx.ClientLock(); _commandBuffer = ctx.ToDriverBuffer(); _conductor = new ClientConductor(ctx); @@ -105,7 +113,6 @@ internal Aeron(Context ctx) { _conductorInvoker = new AgentInvoker(ctx.ErrorHandler(), null, _conductor); _conductorRunner = null; - ctx.ConductorAgentInvoker(_conductorInvoker); } else { @@ -116,7 +123,7 @@ internal Aeron(Context ctx) /// /// Create an Aeron instance and connect to the media driver with a default . - /// + /// /// Threads required for interacting with the media driver are created and managed within the Aeron instance. /// /// @@ -143,10 +150,14 @@ public static Aeron Connect(Context ctx) var aeron = new Aeron(ctx); if (ctx.UseConductorAgentInvoker()) { - return aeron; + aeron.ConductorAgentInvoker().Start(); + } + else + { + aeron.Start(ctx.ThreadFactory()); } - return aeron.Start(ctx.ThreadFactory()); + return aeron; } catch (Exception) { @@ -155,6 +166,33 @@ public static Aeron Connect(Context ctx) } } + /// + /// Get the that is used by this client. + /// + /// the that is use by this client. + public Context Ctx() + { + return _ctx; + } + + /// + /// Get the client identity that has been allocated for communicating with the media driver. + /// + /// the client identity that has been allocated for communicating with the media driver. + public long ClientId() + { + return _clientId; + } + + /// + /// Get the for the client conductor. + /// + /// the for the client conductor. + public AgentInvoker ConductorAgentInvoker() + { + return _conductorInvoker; + } + /// /// Clean up and release all Aeron internal resources and shutdown threads. /// @@ -171,6 +209,8 @@ public void Dispose() { _conductorInvoker.Dispose(); } + + _ctx.Dispose(); } finally { @@ -240,8 +280,8 @@ public Subscription AddSubscription(string channel, int streamId) /// Add a new for subscribing to messages from publishers. /// /// This method will override the default handlers from the , i.e. - /// and - /// . Null values are valid and will + /// and + /// . Null values are valid and will /// result in no action being taken. /// /// for receiving the messages known to the media layer. @@ -274,7 +314,7 @@ public Subscription AddSubscription(string channel, int streamId, AvailableImage /// next correlation id that is unique for the Media Driver. public long NextCorrelationId() { - if (_conductor.Status != ClientConductor.ClientConductorStatus.ACTIVE) + if (_conductor.IsClosed()) { throw new InvalidOperationException("Client is closed"); } @@ -288,14 +328,12 @@ public long NextCorrelationId() /// new for the Aeron media driver in use. public CountersReader CountersReader() { - if (_conductor.Status != ClientConductor.ClientConductorStatus.ACTIVE) + if (_conductor.IsClosed()) { throw new InvalidOperationException("Client is closed"); } - - Context ctx = _conductor.Context(); - - return new CountersReader(ctx.CountersMetaDataBuffer(), ctx.CountersValuesBuffer()); + + return new CountersReader(_ctx.CountersMetaDataBuffer(), _ctx.CountersValuesBuffer(), Encoding.ASCII); } private Aeron Start(IThreadFactory threadFactory) @@ -306,19 +344,19 @@ private Aeron Start(IThreadFactory threadFactory) } /// - /// This class provides configuration for the class via the + /// This class provides configuration for the class via the /// method and its overloads. It gives applications some control over the interactions with the Aeron Media Driver. /// It can also set up error handling as well as application callbacks for image information from the /// Media Driver. /// /// A number of the properties are for testing and should not be set by end users. /// + /// Note: Do not reuse instances of the context across different clients. /// public class Context : IDisposable { + private long _clientId; private bool _useConductorAgentInvoker = false; - private AgentInvoker _conductorAgentInvoker; - private AgentInvoker _driverAgentInvoker; private ILock _clientLock; private IEpochClock _epochClock; private INanoClock _nanoClock; @@ -336,7 +374,7 @@ public class Context : IDisposable private FileInfo _cncFile; private string _aeronDirectoryName; private DirectoryInfo _aeronDirectory; - private long _driverTimeoutMs = DEFAULT_DRIVER_TIMEOUT_MS; + private long _driverTimeoutMs = DRIVER_TIMEOUT_MS; private MappedByteBuffer _cncByteBuffer; private UnsafeBuffer _cncMetaDataBuffer; private UnsafeBuffer _countersMetaDataBuffer; @@ -360,10 +398,27 @@ public class Context : IDisposable /// public const string IPC_CHANNEL = "aeron:ipc"; + /// + /// URI used for Spy s whereby an outgoing unicast or multicast publication can be spied on + /// by IPC without receiving it again via the network. + /// + public const string SPY_PREFIX = "aeron-spy:"; + + /// + /// The address and port used for a UDP channel. For the publisher it is the socket to send to, + /// for the subscriber it is the socket to receive from. + /// + public const string ENDPOINT_PARAM_NAME = "endpoint"; + + /// + /// The network interface via which the socket will be routed. + /// + public const string INTERFACE_PARAM_NAME = "interface"; + /// /// Timeout in which the driver is expected to respond. /// - public const long DEFAULT_DRIVER_TIMEOUT_MS = 10000; + public const long DRIVER_TIMEOUT_MS = 10000; /// /// Initial term id to be used when creating an . @@ -384,6 +439,26 @@ public class Context : IDisposable /// The param name to be used for the term length as a channel URI param. /// public const string TERM_LENGTH_PARAM_NAME = "term-length"; + + /// + /// MTU length parameter name for using as a channel URI param. + /// + public const string MTU_LENGTH_PARAM_NAME = "mtu"; + + /// + /// Time To Live param for a multicast datagram. + /// + public const string TTL_PARAM_NAME = "ttl"; + + /// + /// The param for the control channel IP address and port for multi-destination-cast semantics. + /// + public const string MDC_CONTROL_PARAM_NAME = "control"; + + /// + /// Key for the mode of control that such be used for multi-destination-cast semantics. + /// + public const string MDC_CONTROL_MODE_PARAM_NAME = "control-mode"; /// /// MTU length parameter name for using as a channel URI param. @@ -517,12 +592,22 @@ public Context Conclude() if (null == _driverProxy) { - _driverProxy = new DriverProxy(ToDriverBuffer()); + _clientId = _toDriverBuffer.NextCorrelationId(); + _driverProxy = new DriverProxy(ToDriverBuffer(), _clientId); } return this; } + /// + /// Get the client identity that has been allocated for communicating with the media driver. + /// + /// the client identity that has been allocated for communicating with the media driver. + public long ClientId() + { + return _clientId; + } + /// /// Get the command and control file. /// @@ -554,48 +639,13 @@ public bool UseConductorAgentInvoker() return _useConductorAgentInvoker; } - public Context ConductorAgentInvoker(AgentInvoker conductorAgentInvoker) - { - _conductorAgentInvoker = conductorAgentInvoker; - return this; - } - - /// - /// Get the that is used to run the - /// - /// the that is used to run the - public AgentInvoker ConductorAgentInvoker() - { - return _conductorAgentInvoker; - } - /// - /// Set the for the Media Driver to be used while awaiting a synchronous response. + /// The that is used to provide mutual exclusion in the Aeron client. /// - /// Useful for when running on a low thread count scenario. + /// If the is set and only one thread accesses the client + /// then the lock can be set to to elide the lock overhead. /// /// - /// to be invoked while awaiting a response in the client. - /// this for a fluent API. - public Context DriverAgentInvoker(AgentInvoker driverAgentInvoker) - { - _driverAgentInvoker = driverAgentInvoker; - return this; - } - - - /// - /// Get the that is used to run the Media Driver while awaiting a synchronous response. - /// - /// the that is used for running the Media Driver. - public AgentInvoker DriverAgentInvoker() - { - return _driverAgentInvoker; - } - - /// - /// The that is used to provide mutual exclusion in the Aeron client. - /// /// that is used to provide mutual exclusion in the Aeron client. /// this for a fluent API. public Context ClientLock(ILock @lock) @@ -794,7 +844,7 @@ public Context AvailableImageHandler(AvailableImageHandler handler) /// Get the default callback handler for notifying when s become available. /// /// the callback handler for notifying when s become available. - public virtual AvailableImageHandler AvailableImageHandler() + public AvailableImageHandler AvailableImageHandler() { return _availableImageHandler; } @@ -814,13 +864,14 @@ public Context UnavailableImageHandler(UnavailableImageHandler handler) /// Get the callback handler for when an is unavailable. /// /// the callback handler for when an is unavailable. - public virtual UnavailableImageHandler unavailableImageHandler() + public UnavailableImageHandler UnavailableImageHandler() { return _unavailableImageHandler; } /// - /// Get the buffer containing the counter meta data. + /// Get the buffer containing the counter meta data. These counters are R/W for the driver, read only for all + /// other users. /// /// The buffer storing the counter meta data. public UnsafeBuffer CountersMetaDataBuffer() @@ -829,7 +880,7 @@ public UnsafeBuffer CountersMetaDataBuffer() } /// - /// Set the buffer containing the counter meta data. + /// Set the buffer containing the counter meta data. Testing/internal purposes only. /// /// The new counter meta data buffer. /// this Object for method chaining. @@ -841,7 +892,7 @@ public Context CountersMetaDataBuffer(UnsafeBuffer countersMetaDataBuffer) /// - /// Get the buffer containing the counters. + /// Get the buffer containing the counters. These counters are R/W for the driver, read only for all other users. /// /// The buffer storing the counters. public UnsafeBuffer CountersValuesBuffer() @@ -850,7 +901,7 @@ public UnsafeBuffer CountersValuesBuffer() } /// - /// Set the buffer containing the counters + /// Set the buffer containing the counters. Testing/internal purposes only. /// /// The new counters buffer. /// this Object for method chaining. @@ -1062,7 +1113,7 @@ private void ConnectToDriver() _cncMetaDataBuffer = CncFileDescriptor.CreateMetaDataBuffer(_cncByteBuffer); int cncVersion; - while (0 == (cncVersion = _cncMetaDataBuffer.GetInt(CncFileDescriptor.CncVersionOffset(0)))) + while (0 == (cncVersion = _cncMetaDataBuffer.GetIntVolatile(CncFileDescriptor.CncVersionOffset(0)))) { if (_epochClock.Time() > (startTimeMs + DriverTimeoutMs())) { @@ -1148,20 +1199,20 @@ public MappedByteBuffer MapExistingCncFile(Action logProgress) /// /// to check /// for the driver liveness check. - /// for feedback as liveness checked. + /// for feedback as liveness checked. /// true if a driver is active or false if not. - public static bool IsDriverActive(DirectoryInfo directory, long driverTimeoutMs, Action logProgress) + public static bool IsDriverActive(DirectoryInfo directory, long driverTimeoutMs, Action logger) { FileInfo cncFile = new FileInfo(Path.Combine(directory.FullName, CncFileDescriptor.CNC_FILE)); if (cncFile.Exists) { - logProgress("INFO: Aeron CnC file " + cncFile + " exists"); + logger("INFO: Aeron CnC file " + cncFile + " exists"); var cncByteBuffer = IoUtil.MapExistingFile(cncFile, "CnC file"); try { - return IsDriverActive(driverTimeoutMs, logProgress, cncByteBuffer); + return IsDriverActive(driverTimeoutMs, logger, cncByteBuffer); } finally { @@ -1176,14 +1227,14 @@ public static bool IsDriverActive(DirectoryInfo directory, long driverTimeoutMs, /// Is a media driver active in the current Aeron directory? /// /// for the driver liveness check. - /// for feedback as liveness checked. + /// for feedback as liveness checked. /// true if a driver is active or false if not. - public bool IsDriverActive(long driverTimeoutMs, Action logHandler) + public bool IsDriverActive(long driverTimeoutMs, Action logger) { - var cncByteBuffer = MapExistingCncFile(logHandler); + var cncByteBuffer = MapExistingCncFile(logger); try { - return IsDriverActive(driverTimeoutMs, logHandler, cncByteBuffer); + return IsDriverActive(driverTimeoutMs, logger, cncByteBuffer); } finally { @@ -1195,20 +1246,29 @@ public bool IsDriverActive(long driverTimeoutMs, Action logHandler) /// Is a media driver active in the current mapped CnC buffer? /// /// for the driver liveness check. - /// for feedback as liveness checked. + /// for feedback as liveness checked. /// for the existing CnC file. /// true if a driver is active or false if not. - public static bool IsDriverActive(long driverTimeoutMs, Action logProgress, - MappedByteBuffer cncByteBuffer) + public static bool IsDriverActive(long driverTimeoutMs, Action logger, MappedByteBuffer cncByteBuffer) { - if (null == cncByteBuffer) { return false; } UnsafeBuffer cncMetaDataBuffer = CncFileDescriptor.CreateMetaDataBuffer(cncByteBuffer); - int cncVersion = cncMetaDataBuffer.GetInt(CncFileDescriptor.CncVersionOffset(0)); + + long startTimeMs = UnixTimeConverter.CurrentUnixTimeMillis(); + int cncVersion; + while (0 == (cncVersion = cncMetaDataBuffer.GetIntVolatile(CncFileDescriptor.CncVersionOffset(0)))) + { + if (UnixTimeConverter.CurrentUnixTimeMillis() > (startTimeMs + driverTimeoutMs)) + { + throw new DriverTimeoutException("CnC file is created but not initialised."); + } + + Sleep(1); + } if (CncFileDescriptor.CNC_VERSION != cncVersion) { @@ -1223,7 +1283,7 @@ public static bool IsDriverActive(long driverTimeoutMs, Action logProgre long now = DateTime.Now.ToFileTimeUtc(); long diff = now - timestamp; - logProgress("INFO: Aeron toDriver consumer heartbeat is " + diff + "ms old"); + logger("INFO: Aeron toDriver consumer heartbeat is " + diff + "ms old"); return diff <= driverTimeoutMs; } @@ -1266,8 +1326,7 @@ public int SaveErrorLog(StreamWriter writer, MappedByteBuffer cncByteBuffer) if (CncFileDescriptor.CNC_VERSION != cncVersion) { throw new InvalidOperationException( - "Aeron CnC version does not match: version=" + cncVersion + " required=" + - CncFileDescriptor.CNC_VERSION); + "Aeron CnC version does not match: required=" + CncFileDescriptor.CNC_VERSION + " version=" + cncVersion); } UnsafeBuffer buffer = CncFileDescriptor.CreateErrorLogBuffer(cncByteBuffer, cncMetaDataBuffer); diff --git a/src/Adaptive.Aeron/BufferBuilder.cs b/src/Adaptive.Aeron/BufferBuilder.cs index 7443857f..c96cc2cf 100644 --- a/src/Adaptive.Aeron/BufferBuilder.cs +++ b/src/Adaptive.Aeron/BufferBuilder.cs @@ -31,14 +31,14 @@ namespace Adaptive.Aeron public class BufferBuilder { /// - /// Maximum capcity to which the array can grow + /// Maximum capcity to which the buffer can grow. /// public const int MAX_CAPACITY = int.MaxValue - 8; /// - /// Initial capcity for the internal buffer. + /// Initial minimum capacity for the internal buffer when used, zero if not used. /// - public const int INITIAL_CAPACITY = 4096; + public const int MIN_ALLOCATED_CAPACITY = 4096; private readonly UnsafeBuffer _mutableDirectBuffer; @@ -47,9 +47,9 @@ public class BufferBuilder private int _capacity; /// - /// Construct a buffer builder with a default growth increment of + /// Construct a buffer builder with a default growth increment of /// - public BufferBuilder() : this(INITIAL_CAPACITY) + public BufferBuilder() : this(MIN_ALLOCATED_CAPACITY) { } @@ -122,7 +122,7 @@ public BufferBuilder Reset() /// the builder for fluent API usage. public BufferBuilder Compact() { - _capacity = Math.Max(INITIAL_CAPACITY, BitUtil.FindNextPositivePowerOfTwo(_limit)); + _capacity = Math.Max(MIN_ALLOCATED_CAPACITY, BitUtil.FindNextPositivePowerOfTwo(_limit)); _buffer = CopyOf(_buffer, _capacity); _mutableDirectBuffer.Wrap(_buffer); @@ -171,7 +171,7 @@ private static int FindSuitableCapacity(int capacity, int requiredCapacity) { do { - int newCapacity = capacity + (capacity >> 1); + int newCapacity = Math.Max(capacity + (capacity >> 1), MIN_ALLOCATED_CAPACITY); if (newCapacity < 0 || newCapacity > MAX_CAPACITY) { diff --git a/src/Adaptive.Aeron/ChannelUri.cs b/src/Adaptive.Aeron/ChannelUri.cs new file mode 100644 index 00000000..859c125b --- /dev/null +++ b/src/Adaptive.Aeron/ChannelUri.cs @@ -0,0 +1,310 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Adaptive.Aeron +{ + /// + /// Parser for Aeron channel URIs. The format is: + ///
+    /// aeron-uri = "aeron:" media [ "?" param *( "|" param ) ]
+    /// media     = *( "[^?:]" )
+    /// param     = key "=" value
+    /// key       = *( "[^=]" )
+    /// value     = *( "[^|]" )
+    /// 
+ /// + /// Multiple params with the same key are allowed, the last value specified takes precedence. + /// + ///
+ /// + public class ChannelUri + { + private enum State + { + MEDIA, + PARAMS_KEY, + PARAMS_VALUE + } + + /// + /// URI Scheme for Aeron channels. + /// + public const string AERON_SCHEME = "aeron"; + + /// + /// Qualifier for spy subscriptions. + /// + public const string SPY_QUALIFIER = "aeron-spy"; + + private static readonly string AERON_PREFIX = AERON_SCHEME + ":"; + + private readonly string _prefix; + private readonly string _media; + private readonly IDictionary _params; + + /// + /// Construct with the components provided to avoid parsing. + /// + /// empty if no prefix is required otherwise expected to be 'aeron-spy' + /// for the channel which is typically "udp" or "ipc". + /// for the query string as key value pairs. + public ChannelUri(string prefix, string media, IDictionary @params) + { + _prefix = prefix; + _media = media; + _params = @params; + } + + /// + /// Construct with the components provided to avoid parsing. + /// + /// for the channel which is typically "udp" or "ipc". + /// for the query string as key value pairs. + public ChannelUri(string media, IDictionary @params) : this("", media, @params) + { + } + + /// + /// The prefix for the channel. + /// + /// the prefix for the channel. + public string Prefix() + { + return _prefix; + } + + /// + /// The media over which the channel operates. + /// + /// the media over which the channel operates. + public string Media() + { + return _media; + } + + /// + /// The scheme for the URI. Must be "aeron". + /// + /// the scheme for the URI. + public string Scheme() + { + return AERON_SCHEME; + } + + /// + /// Get a value for a given parameter key. + /// + /// to lookup. + /// the value if set for the key otherwise null. + public string Get(string key) + { + return _params[key]; + } + + /// + /// Get the value for a given parameter key or the default value provided if the key does not exist. + /// + /// to lookup. + /// to be returned if no key match is found. + /// the value if set for the key otherwise the default value provided. + public string Get(string key, string defaultValue) + { + string value = _params[key]; + if (null != value) + { + return value; + } + + return defaultValue; + } + + /// + /// Put a key and value pair in the map of params. + /// + /// of the param to be put. + /// of the param to be put. + /// the existing value otherwise null. + public string Put(string key, string value) + { + return _params[key] = value; + } + + /// + /// Does the URI contain a value for the given key. + /// + /// to be lookup. + /// true if the key has a value otherwise false. + public bool ContainsKey(string key) + { + return _params.ContainsKey(key); + } + + /// + /// Generate a String representation of the URI that is valid for an Aeron channel. + /// + /// a String representation of the URI that is valid for an Aeron channel. + public string ToString() + { + StringBuilder sb; + if (ReferenceEquals(_prefix, null) || "".Equals(_prefix)) + { + sb = new StringBuilder((_params.Count * 20) + 10); + } + else + { + sb = new StringBuilder(_params.Count * 20 + 20); + sb.Append(Aeron.Context.SPY_PREFIX); + } + + sb.Append(AERON_PREFIX).Append(_media); + + if (_params.Count > 0) + { + sb.Append('?'); + + foreach (KeyValuePair entry in _params) + { + sb.Append(entry.Key).Append('=').Append(entry.Value).Append('|'); + } + + sb.Length = sb.Length - 1; + } + + return sb.ToString(); + } + + /// + /// Parse a which contains an Aeron URI. + /// + /// to be parsed. + /// a new representing the URI string. + public static ChannelUri Parse(string cs) + { + int position = 0; + string prefix; + if (StartsWith(cs, Aeron.Context.SPY_PREFIX)) + { + prefix = SPY_QUALIFIER; + position = Aeron.Context.SPY_PREFIX.Length; + } + else + { + prefix = ""; + } + + if (!StartsWith(cs, position, AERON_PREFIX)) + { + throw new System.ArgumentException("Aeron URIs must start with 'aeron:', found: '" + cs + "'"); + } + else + { + position += AERON_PREFIX.Length; + } + + var builder = new StringBuilder(); + var @params = new Dictionary(); + string media = null; + string key = null; + + State state = State.MEDIA; + for (int i = position; i < cs.Length; i++) + { + char c = cs[i]; + + switch (state) + { + case State.MEDIA: + switch (c) + { + case '?': + media = builder.ToString(); + builder.Length = 0; + state = State.PARAMS_KEY; + break; + + case ':': + throw new ArgumentException("Encountered ':' within media definition"); + + default: + builder.Append(c); + break; + } + break; + + case State.PARAMS_KEY: + switch (c) + { + case '=': + key = builder.ToString(); + builder.Length = 0; + state = State.PARAMS_VALUE; + break; + + default: + builder.Append(c); + break; + } + break; + + case State.PARAMS_VALUE: + switch (c) + { + case '|': + @params[key] = builder.ToString(); + builder.Length = 0; + state = State.PARAMS_KEY; + break; + + default: + builder.Append(c); + break; + } + break; + + default: + throw new InvalidOperationException("Que? state=" + state); + } + } + + switch (state) + { + case State.MEDIA: + media = builder.ToString(); + break; + + case State.PARAMS_VALUE: + @params[key] = builder.ToString(); + break; + + default: + throw new ArgumentException("No more input found, but was in state: " + state); + } + + return new ChannelUri(prefix, media, @params); + } + + private static bool StartsWith(string input, int position, string prefix) + { + if (input.Length - position < prefix.Length) + { + return false; + } + + for (int i = 0; i < prefix.Length; i++) + { + if (input[position + i] != prefix[i]) + { + return false; + } + } + + return true; + } + + private static bool StartsWith(string input, string prefix) + { + return StartsWith(input, 0, prefix); + } + } +} \ No newline at end of file diff --git a/src/Adaptive.Aeron/ChannelUriStringBuilder.cs b/src/Adaptive.Aeron/ChannelUriStringBuilder.cs new file mode 100644 index 00000000..0ef4dd2e --- /dev/null +++ b/src/Adaptive.Aeron/ChannelUriStringBuilder.cs @@ -0,0 +1,488 @@ +using System; +using System.Text; +using Adaptive.Aeron.LogBuffer; + +namespace Adaptive.Aeron +{ + /// + /// Type safe means of building a channel URI associated with a or . + /// + /// + /// + /// + public class ChannelUriStringBuilder + { + private readonly StringBuilder _sb = new StringBuilder(64); + + private string _prefix; + private string _media; + private string _endpoint; + private string _networkInterface; + private string _controlEndpoint; + private string _controlMode; + private bool? _reliable; + private int? _ttl; + private int? _mtu; + private int? _termLength; + private int? _initialTermId; + private int? _termId; + private int? _termOffset; + + /// + /// Clear out all the values thus setting back to the initial state. + /// + /// this for a fluent API. + public ChannelUriStringBuilder Clear() + { + _prefix = null; + _media = null; + _endpoint = null; + _networkInterface = null; + _controlEndpoint = null; + _controlMode = null; + _reliable = null; + _ttl = null; + _mtu = null; + _termLength = null; + _initialTermId = null; + _termId = null; + _termOffset = null; + + return this; + } + + /// + /// Validates that the collection of set parameters are valid together. + /// + /// this for a fluent API. + /// if the combination of params is invalid. + public ChannelUriStringBuilder Validate() + { + if (null == _media) + { + throw new InvalidOperationException("media type is mandatory"); + } + + if ("udp".Equals(_media) && (null == _endpoint && null == _controlEndpoint)) + { + throw new InvalidOperationException("Either 'endpoint' or 'control' must be specified for UDP."); + } + + int count = 0; + count += null == _initialTermId ? 0 : 1; + count += null == _termId ? 0 : 1; + count += null == _termOffset ? 0 : 1; + + if (count > 0 && count < 3) + { + throw new InvalidOperationException( + "If any of then a complete set of 'initialTermId', 'termId', and 'termOffset' must be provided"); + } + + return this; + } + + /// + /// Set the prefix for taking an addition action such as spying on an outgoing publication with "aeron-spy". + /// + /// to be applied to the URI before the the scheme. + /// this for a fluent API. + public ChannelUriStringBuilder Prefix(string prefix) + { + if (null != prefix && !prefix.Equals("") && !prefix.Equals(ChannelUri.SPY_QUALIFIER)) + { + throw new ArgumentException("Invalid prefix: " + prefix); + } + + _prefix = prefix; + return this; + } + + /// + /// Get the prefix for the additional action to be taken on the request. + /// + /// the prefix for the additional action to be taken on the request. + public string Prefix() + { + return _prefix; + } + + /// + /// Set the media for this channel. Valid values are "udp" and "ipc". + /// + /// for this channel. + /// this for a fluent API. + public ChannelUriStringBuilder Media(string media) + { + switch (media) + { + case "udp": + case "ipc": + break; + + default: + throw new ArgumentException("Invalid media: " + media); + } + + _media = media; + return this; + } + + /// + /// The media over which the channel transmits. + /// + /// the media over which the channel transmits. + public string Media() + { + return _media; + } + + /// + /// Set the endpoint address:port pairing for the channel. This is the address the publication sends to and the + /// address the subscription receives from. + /// + /// address and port for the channel. + /// this for a fluent API. + public ChannelUriStringBuilder Endpoint(string endpoint) + { + _endpoint = endpoint; + return this; + } + + /// + /// Get the endpoint address:port pairing for the channel. + /// + /// the endpoint address:port pairing for the channel. + public string Endpoint() + { + return _endpoint; + } + + /// + /// Set the address of the local interface in the form host:[port]/[subnet mask] for routing traffic. + /// + /// for routing traffic. + /// this for a fluent API. + public ChannelUriStringBuilder NetworkInterface(string networkInterface) + { + _networkInterface = networkInterface; + return this; + } + + /// + /// Get the address of the local interface in the form host:[port]/[subnet mask] for routing traffic. + /// + /// the address of the local interface in the form host:[port]/[subnet mask] for routing traffic. + public string NetworkInterface() + { + return _networkInterface; + } + + /// + /// Set the control address:port pair for dynamically joining a multi-destination-cast publication. + /// + /// for joining a MDC control socket. + /// this for a fluent API. + public ChannelUriStringBuilder ControlEndpoint(string controlEndpoint) + { + _controlEndpoint = controlEndpoint; + return this; + } + + /// + /// Get the control address:port pair for dynamically joining a multi-destination-cast publication. + /// + /// the control address:port pair for dynamically joining a multi-destination-cast publication. + public string ControlEndpoint() + { + return _controlEndpoint; + } + + /// + /// Set the control mode for multi-destination-cast. Set to "manual" for allowing control from the publication API. + /// + /// for taking control of MDC. + /// this for a fluent API. + /// + /// + public ChannelUriStringBuilder ControlMode(string controlMode) + { + if (null != controlMode && !controlMode.Equals(Aeron.Context.MDC_CONTROL_MODE_MANUAL)) + { + throw new ArgumentException("Invalid control mode: " + controlMode); + } + + _controlMode = controlMode; + return this; + } + + /// + /// Get the control mode for multi-destination-cast. + /// + /// the control mode for multi-destination-cast. + public string ControlMode() + { + return _controlMode; + } + + /// + /// Set the subscription semantics for if loss is acceptable, or not, for a reliable message delivery. + /// + /// false if loss can be be gap filled. + /// this for a fluent API. + public ChannelUriStringBuilder Reliable(bool? isReliable) + { + _reliable = isReliable; + return this; + } + + /// + /// Get the subscription semantics for if loss is acceptable, or not, for a reliable message delivery. + /// + /// the subscription semantics for if loss is acceptable, or not, for a reliable message delivery. + public bool? Reliable() + { + return _reliable; + } + + /// + /// Set the Time To Live (TTL) for a multicast datagram. Valid values are 0-255 for the number of hops the datagram + /// can progress along. + /// + /// value for a multicast datagram. + /// this for a fluent API. + public ChannelUriStringBuilder Ttl(int? ttl) + { + if (null != ttl && (ttl < 0 || ttl > 255)) + { + throw new ArgumentException("TTL not in range 0-255: " + ttl); + } + + _ttl = ttl; + return this; + } + + /// + /// Get the Time To Live (TTL) for a multicast datagram. + /// + /// the Time To Live (TTL) for a multicast datagram. + public int? Ttl() + { + return _ttl; + } + + /// + /// Set the maximum transmission unit (MTU) including Aeron header for a datagram payload. + /// + /// the maximum transmission unit including Aeron header for a datagram payload. + /// this for a fluent API. + public ChannelUriStringBuilder Mtu(int? mtu) + { + if (null != mtu) + { + if (mtu < 32 || mtu > 65504) + { + throw new ArgumentException("MTU not in range 32-65504: " + mtu); + } + + if ((mtu & (FrameDescriptor.FRAME_ALIGNMENT - 1)) != 0) + { + throw new ArgumentException("MTU not a multiple of FRAME_ALIGNMENT: mtu=" + mtu); + } + } + + _mtu = mtu; + return this; + } + + /// + /// Get the maximum transmission unit (MTU) including Aeron header for a datagram payload. + /// + /// the maximum transmission unit (MTU) including Aeron header for a datagram payload. + public int? Mtu() + { + return _mtu; + } + + /// + /// Set the length of buffer used for each term of the log. Valid values are powers of 2 in the 64K - 1G range. + /// + /// of the buffer used for each term of the log. + /// this for a fluent API. + public ChannelUriStringBuilder TermLength(int? termLength) + { + if (null != termLength) + { + LogBufferDescriptor.CheckTermLength(termLength.Value); + } + + _termLength = termLength; + return this; + } + + /// + /// Get the length of buffer used for each term of the log. + /// + /// the length of buffer used for each term of the log. + public int? TermLength() + { + return _termLength; + } + + /// + /// Set the initial term id at which a publication will start. + /// + /// the initial term id at which a publication will start. + /// this for a fluent API. + public ChannelUriStringBuilder InitialTermId(int? initialTermId) + { + _initialTermId = initialTermId; + return this; + } + + /// + /// the initial term id at which a publication will start. + /// + /// the initial term id at which a publication will start. + public int? InitialTermId() + { + return _initialTermId; + } + + /// + /// Set the current term id at which a publication will start. This when combined with the initial term can + /// establish a starting position. + /// + /// at which a publication will start. + /// this for a fluent API. + public ChannelUriStringBuilder TermId(int? termId) + { + _termId = termId; + return this; + } + + /// + /// Get the current term id at which a publication will start. + /// + /// the current term id at which a publication will start. + public int? TermId() + { + return _termId; + } + + /// + /// Set the offset within a term at which a publication will start. This when combined with the term id can establish + /// a starting position. + /// + /// within a term at which a publication will start. + /// this for a fluent API. + public ChannelUriStringBuilder TermOffset(int? termOffset) + { + if (null != termOffset) + { + if ((termOffset < 0 || termOffset > LogBufferDescriptor.TERM_MAX_LENGTH)) + { + throw new ArgumentException("Term offset not in range 0-1g: " + termOffset); + } + + if (0 != (termOffset & (FrameDescriptor.FRAME_ALIGNMENT - 1))) + { + throw new ArgumentException("Term offset not multiple of FRAME_ALIGNMENT: " + termOffset); + } + } + + _termOffset = termOffset; + return this; + } + + /// + /// Get the offset within a term at which a publication will start. + /// + /// the offset within a term at which a publication will start. + public int? TermOffset() + { + return _termOffset; + } + + /// + /// Build a channel URI String for the given parameters. + /// + /// a channel URI String for the given parameters. + public string Build() + { + _sb.Length = 0; + + if (null != _prefix && !"".Equals(_prefix)) + { + _sb.Append(_prefix).Append(':'); + } + + _sb.Append(ChannelUri.AERON_SCHEME).Append(':').Append(_media).Append('?'); + + if (null != _endpoint) + { + _sb.Append(Aeron.Context.ENDPOINT_PARAM_NAME).Append('=').Append(_endpoint).Append('|'); + } + + if (null != _networkInterface) + { + _sb.Append(Aeron.Context.INTERFACE_PARAM_NAME).Append('=').Append(_networkInterface).Append('|'); + } + + if (null != _controlEndpoint) + { + _sb.Append(Aeron.Context.MDC_CONTROL_PARAM_NAME).Append('=') + .Append(_controlEndpoint).Append('|'); + } + + if (null != _controlMode) + { + _sb.Append(Aeron.Context.MDC_CONTROL_MODE_PARAM_NAME).Append('=').Append(_controlMode).Append('|'); + } + + if (null != _reliable) + { + _sb.Append(Aeron.Context.RELIABLE_STREAM_PARAM_NAME).Append('=').Append(_reliable).Append('|'); + } + + if (null != _ttl) + { + _sb.Append(Aeron.Context.TTL_PARAM_NAME).Append('=').Append(_ttl.Value).Append('|'); + } + + if (null != _mtu) + { + _sb.Append(Aeron.Context.MTU_LENGTH_PARAM_NAME).Append('=').Append(_mtu.Value).Append('|'); + } + + if (null != _termLength) + { + _sb.Append(Aeron.Context.TERM_LENGTH_PARAM_NAME).Append('=').Append(_termLength.Value).Append('|'); + } + + if (null != _initialTermId) + { + _sb.Append(Aeron.Context.INITIAL_TERM_ID_PARAM_NAME).Append('=').Append(_initialTermId.Value) + .Append('|'); + } + + if (null != _termId) + { + _sb.Append(Aeron.Context.TERM_ID_PARAM_NAME).Append('=').Append(_termId.Value).Append('|'); + } + + if (null != _termOffset) + { + _sb.Append(Aeron.Context.TERM_OFFSET_PARAM_NAME).Append('=').Append(_termOffset.Value).Append('|'); + } + + char lastChar = _sb[_sb.Length - 1]; + if (lastChar == '|' || lastChar == '?') + { + _sb.Length = _sb.Length - 1; + } + + return _sb.ToString(); + } + } +} \ No newline at end of file diff --git a/src/Adaptive.Aeron/ClientConductor.cs b/src/Adaptive.Aeron/ClientConductor.cs index 863d2278..4d0e661e 100644 --- a/src/Adaptive.Aeron/ClientConductor.cs +++ b/src/Adaptive.Aeron/ClientConductor.cs @@ -17,7 +17,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Threading; using Adaptive.Aeron.Exceptions; using Adaptive.Agrona; using Adaptive.Agrona.Collections; @@ -27,18 +26,11 @@ namespace Adaptive.Aeron { /// - /// Client conductor takes responses and notifications from Media Driver and acts on them in addition to forwarding - /// commands from the various Client APIs to the Media Driver. + /// Client conductor receives responses and notifications from Media Driver and acts on them in addition to forwarding + /// commands from the Client API to the Media Driver conductor. /// - internal class ClientConductor : IAgent, IDriverListener + internal class ClientConductor : IAgent, IDriverEventsListener { - public enum ClientConductorStatus - { - ACTIVE, - CLOSING, - CLOSED - } - private const long NO_CORRELATION_ID = -1; private static readonly long RESOURCE_TIMEOUT_NS = 1; private static readonly long RESOURCE_LINGER_NS = 3; @@ -48,39 +40,35 @@ public enum ClientConductorStatus private readonly long _driverTimeoutNs; private readonly long _interServiceTimeoutNs; private readonly long _publicationConnectionTimeoutMs; - private long _timeOfLastKeepaliveNs; - private long _timeOfLastCheckResourcesNs; - private long _timeOfLastWorkNs; - private bool _isDriverActive = true; - private volatile ClientConductorStatus _status = ClientConductorStatus.ACTIVE; + private long _timeOfLastKeepAliveNs; + private long _timeOfLastResourcesCheckNs; + private long _timeOfLastServiceNs; + private volatile bool _isClosed; private readonly ILock _clientLock; - private readonly Aeron.Context _ctx; private readonly IEpochClock _epochClock; private readonly MapMode _imageMapMode; private readonly INanoClock _nanoClock; - private readonly DriverListenerAdapter _driverListener; + private readonly DriverEventsAdapter _driverEventsAdapter; private readonly ILogBuffersFactory _logBuffersFactory; private readonly ActivePublications _activePublications = new ActivePublications(); private readonly ConcurrentDictionary _activeExclusivePublications = new ConcurrentDictionary(); private readonly ActiveSubscriptions _activeSubscriptions = new ActiveSubscriptions(); private readonly List _lingeringResources = new List(); + private readonly UnavailableImageHandler _defaultUnavailableImageHandler; + private readonly AvailableImageHandler _defaultAvailableImageHandler; private readonly UnsafeBuffer _counterValuesBuffer; private readonly DriverProxy _driverProxy; private readonly ErrorHandler _errorHandler; - private readonly AgentInvoker _driverAgentInvoker; private RegistrationException _driverException; internal ClientConductor() { - } internal ClientConductor(Aeron.Context ctx) { - _ctx = ctx; - _clientLock = ctx.ClientLock(); _epochClock = ctx.EpochClock(); _nanoClock = ctx.NanoClock(); @@ -94,15 +82,16 @@ internal ClientConductor(Aeron.Context ctx) _driverTimeoutNs = _driverTimeoutMs * 1000000; _interServiceTimeoutNs = ctx.InterServiceTimeout(); _publicationConnectionTimeoutMs = ctx.PublicationConnectionTimeout(); - _driverListener = new DriverListenerAdapter(ctx.ToClientBuffer(), this); - _driverAgentInvoker = ctx.DriverAgentInvoker(); + _defaultAvailableImageHandler = ctx.AvailableImageHandler(); + _defaultUnavailableImageHandler = ctx.UnavailableImageHandler(); + _driverEventsAdapter = new DriverEventsAdapter(ctx.ToClientBuffer(), this); long nowNs = _nanoClock.NanoTime(); - _timeOfLastKeepaliveNs = nowNs; - _timeOfLastCheckResourcesNs = nowNs; - _timeOfLastWorkNs = nowNs; + _timeOfLastKeepAliveNs = nowNs; + _timeOfLastResourcesCheckNs = nowNs; + _timeOfLastServiceNs = nowNs; } - + public void OnStart() { // Do Nothing @@ -110,30 +99,24 @@ public void OnStart() public void OnClose() { - if (ClientConductorStatus.ACTIVE == _status) + if (!_isClosed) { - _status = ClientConductorStatus.CLOSING; + _isClosed = true; + + int lingeringResourcesSize = _lingeringResources.Count; + ForceClosePublicationsAndSubscriptions(); - foreach (ExclusivePublication publication in _activeExclusivePublications.Values) + if (_lingeringResources.Count > lingeringResourcesSize) { - publication.ForceClose(); + Aeron.Sleep(1); } - _activeExclusivePublications.Clear(); - - _activePublications.Dispose(); - _activeSubscriptions.Dispose(); - - Thread.Yield(); for (int i = 0, size = _lingeringResources.Count; i < size; i++) { _lingeringResources[i].Delete(); } - _lingeringResources.Clear(); - - _ctx.Dispose(); - _status = ClientConductorStatus.CLOSED; + _lingeringResources.Clear(); } } @@ -145,10 +128,12 @@ public int DoWork() { try { - if (ClientConductorStatus.ACTIVE == _status) + if (_isClosed) { - workCount = DoWork(NO_CORRELATION_ID, null); + throw new AgentTerminationException(); } + + workCount = Service(NO_CORRELATION_ID, null); } finally { @@ -164,13 +149,11 @@ public string RoleName() return "aeron-client-conductor"; } - public Aeron.Context Context() + public bool IsClosed() { - return _ctx; + return _isClosed; } - public ClientConductorStatus Status => _status; - internal virtual ILock ClientLock() { return _clientLock; @@ -183,7 +166,10 @@ internal void HandleError(Exception ex) internal Publication AddPublication(string channel, int streamId) { - VerifyActive(); + if (_isClosed) + { + throw new InvalidOperationException("Aeron client is closed"); + } Publication publication = _activePublications.Get(channel, streamId); if (null == publication) @@ -199,7 +185,10 @@ internal Publication AddPublication(string channel, int streamId) internal ExclusivePublication AddExclusivePublication(string channel, int streamId) { - VerifyActive(); + if (_isClosed) + { + throw new InvalidOperationException("Aeron client is closed"); + } long registrationId = _driverProxy.AddExclusivePublication(channel, streamId); AwaitResponse(registrationId, channel); @@ -209,7 +198,10 @@ internal ExclusivePublication AddExclusivePublication(string channel, int stream internal virtual void ReleasePublication(Publication publication) { - VerifyActive(); + if (_isClosed) + { + throw new InvalidOperationException("Aeron client is closed"); + } if (publication == _activePublications.Remove(publication.Channel, publication.StreamId)) { @@ -220,7 +212,10 @@ internal virtual void ReleasePublication(Publication publication) internal void ReleasePublication(ExclusivePublication publication) { - VerifyActive(); + if (_isClosed) + { + throw new InvalidOperationException("Aeron client is closed"); + } ExclusivePublication publicationToRemove; @@ -240,20 +235,15 @@ internal void AsyncReleasePublication(long registrationId) internal Subscription AddSubscription(string channel, int streamId) { - VerifyActive(); - - long correlationId = _driverProxy.AddSubscription(channel, streamId); - Subscription subscription = new Subscription(this, channel, streamId, correlationId, _ctx.AvailableImageHandler(), _ctx.unavailableImageHandler()); - _activeSubscriptions.Add(subscription); - - AwaitResponse(correlationId, channel); - - return subscription; + return AddSubscription(channel, streamId, _defaultAvailableImageHandler, _defaultUnavailableImageHandler); } internal Subscription AddSubscription(string channel, int streamId, AvailableImageHandler availableImageHandler, UnavailableImageHandler unavailableImageHandler) { - VerifyActive(); + if (_isClosed) + { + throw new InvalidOperationException("Aeron client is closed"); + } long correlationId = _driverProxy.AddSubscription(channel, streamId); Subscription subscription = new Subscription(this, channel, streamId, correlationId, availableImageHandler, unavailableImageHandler); @@ -266,7 +256,10 @@ internal Subscription AddSubscription(string channel, int streamId, AvailableIma internal virtual void ReleaseSubscription(Subscription subscription) { - VerifyActive(); + if (_isClosed) + { + throw new InvalidOperationException("Aeron client is closed"); + } AwaitResponse(_driverProxy.RemoveSubscription(subscription.RegistrationId), null); @@ -280,48 +273,97 @@ internal void AsyncReleaseSubscription(Subscription subscription) internal void AddDestination(long registrationId, string endpointChannel) { - VerifyActive(); + if (_isClosed) + { + throw new InvalidOperationException("Aeron client is closed"); + } AwaitResponse(_driverProxy.AddDestination(registrationId, endpointChannel), null); } internal void RemoveDestination(long registrationId, string endpointChannel) { - VerifyActive(); + if (_isClosed) + { + throw new InvalidOperationException("Aeron client is closed"); + } AwaitResponse(_driverProxy.RemoveDestination(registrationId, endpointChannel), null); } - public void OnError(ErrorCode errorCode, string message, long correlationId) + public void OnError(long correlationId, ErrorCode errorCode, string message) { _driverException = new RegistrationException(errorCode, message); } - public void OnNewPublication(string channel, int streamId, int sessionId, int publicationLimitId, string logFileName, long correlationId) - { - Publication publication = new Publication(this, channel, streamId, sessionId, new UnsafeBufferPosition(_counterValuesBuffer, publicationLimitId), _logBuffersFactory.Map(logFileName, MapMode.ReadWrite), correlationId); + public void OnNewPublication( + long correlationId, + long registrationId, + int streamId, + int sessionId, + int publicationLimitId, + string channel, + string logFileName) + { + Publication publication = new Publication( + this, + channel, + streamId, + sessionId, + new UnsafeBufferPosition(_counterValuesBuffer, publicationLimitId), + _logBuffersFactory.Map(logFileName, MapMode.ReadWrite), + registrationId, + correlationId); _activePublications.Put(channel, streamId, publication); } - public void OnNewExclusivePublication(string channel, int streamId, int sessionId, int publicationLimitId, string logFileName, long correlationId) - { - ExclusivePublication publication = new ExclusivePublication(this, channel, streamId, sessionId, new UnsafeBufferPosition(_counterValuesBuffer, publicationLimitId), _logBuffersFactory.Map(logFileName, MapMode.ReadWrite), correlationId); + public void OnNewExclusivePublication( + long correlationId, + long registrationid, + int streamId, + int sessionId, + int publicationLimitId, + string channel, + string logFileName) + { + ExclusivePublication publication = new ExclusivePublication( + this, + channel, + streamId, + sessionId, + new UnsafeBufferPosition(_counterValuesBuffer, publicationLimitId), + _logBuffersFactory.Map(logFileName, MapMode.ReadWrite), + registrationid, + correlationId); _activeExclusivePublications[correlationId] = publication; } - public void OnAvailableImage(int streamId, int sessionId, IDictionary subscriberPositionMap, string logFileName, string sourceIdentity, long correlationId) + public void OnAvailableImage( + long correlationId, + int streamId, + int sessionId, + long subscriberRegistrationId, + int subscriberPositionId, + string logFileName, + string sourceIdentity) { - _activeSubscriptions.ForEach(streamId, (subscription) => - { - if (!subscription.HasImage(correlationId)) + _activeSubscriptions.ForEach( + streamId, + subscription => { - long positionId = subscriberPositionMap[subscription.RegistrationId]; - if (Adaptive.Aeron.DriverListenerAdapter.MISSING_REGISTRATION_ID != positionId) + if (subscription.RegistrationId == subscriberRegistrationId && !subscription.HasImage(correlationId)) { - Image image = new Image(subscription, sessionId, new UnsafeBufferPosition(_counterValuesBuffer, (int)positionId), _logBuffersFactory.Map(logFileName, _imageMapMode), _errorHandler, sourceIdentity, correlationId); - + Image image = new Image( + subscription, + sessionId, + new UnsafeBufferPosition(_counterValuesBuffer, (int) subscriberPositionId), + _logBuffersFactory.Map(logFileName, _imageMapMode), + _errorHandler, + sourceIdentity, + correlationId); + try { AvailableImageHandler handler = subscription.AvailableImageHandler(); @@ -337,36 +379,36 @@ public void OnAvailableImage(int streamId, int sessionId, IDictionary - { - Image image = subscription.RemoveImage(correlationId); - if (null != image) + _activeSubscriptions.ForEach(streamId, + (subscription) => { - try + Image image = subscription.RemoveImage(correlationId); + if (null != image) { - UnavailableImageHandler handler = subscription.UnavailableImageHandler(); - if (null != handler) + try { - handler(image); + UnavailableImageHandler handler = subscription.UnavailableImageHandler(); + if (null != handler) + { + handler(image); + } + } + catch (Exception ex) + { + _errorHandler(ex); } } - catch (Exception ex) - { - _errorHandler(ex); - } - } - }); + }); } - internal DriverListenerAdapter DriverListenerAdapter() + internal DriverEventsAdapter DriverListenerAdapter() { - return _driverListener; + return _driverEventsAdapter; } internal void LingerResource(IManagedResource managedResource) @@ -380,22 +422,21 @@ internal virtual bool IsPublicationConnected(long timeOfLastStatusMessageMs) return _epochClock.Time() <= (timeOfLastStatusMessageMs + _publicationConnectionTimeoutMs); } - private int DoWork(long correlationId, string expectedChannel) + private int Service(long correlationId, string expectedChannel) { int workCount = 0; try { workCount += OnCheckTimeouts(); - workCount += _driverListener.PollMessage(correlationId, expectedChannel); + workCount += _driverEventsAdapter.Receive(correlationId, expectedChannel); } catch (Exception throwable) { _errorHandler(throwable); - if (correlationId != NO_CORRELATION_ID) + if (IsClientApiCall(correlationId)) { - // has been called from a user thread and not the conductor duty cycle. throw; } } @@ -403,25 +444,23 @@ private int DoWork(long correlationId, string expectedChannel) return workCount; } - private void AwaitResponse(long correlationId, string expectedChannel) + private static bool IsClientApiCall(long correlationId) + { + return correlationId != NO_CORRELATION_ID; + } + + private void AwaitResponse(long correlationId, string expectedChannel) { _driverException = null; var deadlineNs = _nanoClock.NanoTime() + _driverTimeoutNs; do { - if (null == _driverAgentInvoker) - { - Aeron.Sleep(1); - } - else - { - _driverAgentInvoker.Invoke(); - } + Aeron.Sleep(1); - DoWork(correlationId, expectedChannel); + Service(correlationId, expectedChannel); - if (_driverListener.LastReceivedCorrelationId() == correlationId) + if (_driverEventsAdapter.LastReceivedCorrelationId() == correlationId) { if (null != _driverException) { @@ -432,20 +471,7 @@ private void AwaitResponse(long correlationId, string expectedChannel) } } while (_nanoClock.NanoTime() < deadlineNs); - throw new DriverTimeoutException("No response from driver wihtout timeout"); - } - - private void VerifyActive() - { - if (!_isDriverActive) - { - throw new DriverTimeoutException("MediaDriver is inactive"); - } - - if (ClientConductorStatus.CLOSED == _status) - { - throw new InvalidOperationException("Aeron client is closed"); - } + throw new DriverTimeoutException("No response from MediaDriver within (ms):" + _driverTimeoutMs); } private int OnCheckTimeouts() @@ -453,35 +479,66 @@ private int OnCheckTimeouts() int workCount = 0; long nowNs = _nanoClock.NanoTime(); - if (nowNs < (_timeOfLastWorkNs + Aeron.IdleSleepNs)) + if (nowNs > (_timeOfLastServiceNs + Aeron.IdleSleepNs)) { - return workCount; + checkServiceInterval(nowNs); + _timeOfLastServiceNs = nowNs; + + workCount += checkLiveness(nowNs); + workCount += checkLingeringResources(nowNs); } - if (nowNs > (_timeOfLastWorkNs + _interServiceTimeoutNs)) + return workCount; + } + + private void checkServiceInterval(long nowNs) + { + if (nowNs > (_timeOfLastServiceNs + _interServiceTimeoutNs)) { + int lingeringResourcesSize = _lingeringResources.Count; + + ForceClosePublicationsAndSubscriptions(); + + if (_lingeringResources.Count > lingeringResourcesSize) + { + Aeron.Sleep(1000); + } + OnClose(); - throw new ConductorServiceTimeoutException("Timeout between service calls over " + _interServiceTimeoutNs + "ns"); + throw new ConductorServiceTimeoutException("Exceeded (ns): " + _interServiceTimeoutNs); } + } - _timeOfLastWorkNs = nowNs; - - if (nowNs > (_timeOfLastKeepaliveNs + _keepAliveIntervalNs)) + private int checkLiveness(long nowNs) + { + if (nowNs > (_timeOfLastKeepAliveNs + _keepAliveIntervalNs)) { + if (_epochClock.Time() > (_driverProxy.TimeOfLastDriverKeepaliveMs() + _driverTimeoutMs)) + { + OnClose(); + + throw new DriverTimeoutException("MediaDriver keepalive older than (ms): " + _driverTimeoutMs); + } + _driverProxy.SendClientKeepalive(); - CheckDriverHeartbeat(); + _timeOfLastKeepAliveNs = nowNs; - _timeOfLastKeepaliveNs = nowNs; - workCount++; + return 1; } - if (nowNs > (_timeOfLastCheckResourcesNs + RESOURCE_TIMEOUT_NS)) + return 0; + } + + private int checkLingeringResources(long nowNs) + { + if (nowNs > (_timeOfLastResourcesCheckNs + RESOURCE_TIMEOUT_NS)) { List lingeringResources = _lingeringResources; for (int lastIndex = lingeringResources.Count - 1, i = lastIndex; i >= 0; i--) { IManagedResource resource = lingeringResources[i]; + if (nowNs > (resource.TimeOfLastStateChange() + RESOURCE_LINGER_NS)) { ListUtil.FastUnorderedRemove(lingeringResources, i, lastIndex); @@ -490,21 +547,25 @@ private int OnCheckTimeouts() } } - _timeOfLastCheckResourcesNs = nowNs; - workCount++; + _timeOfLastResourcesCheckNs = nowNs; + + return 1; } - return workCount; + return 0; } - private void CheckDriverHeartbeat() + private void ForceClosePublicationsAndSubscriptions() { - long deadlineMs = _driverProxy.TimeOfLastDriverKeepaliveMs() + _driverTimeoutMs; - if (_isDriverActive && (_epochClock.Time() > deadlineMs)) + foreach (ExclusivePublication publication in _activeExclusivePublications.Values) { - _isDriverActive = false; - _errorHandler(new DriverTimeoutException("MediaDriver has been inactive for over " + _driverTimeoutMs + "ms")); + publication.ForceClose(); } + + _activeExclusivePublications.Clear(); + + _activePublications.Dispose(); + _activeSubscriptions.Dispose(); } } } \ No newline at end of file diff --git a/src/Adaptive.Aeron/CncFileDescriptor.cs b/src/Adaptive.Aeron/CncFileDescriptor.cs index 7d225b26..cc13e234 100644 --- a/src/Adaptive.Aeron/CncFileDescriptor.cs +++ b/src/Adaptive.Aeron/CncFileDescriptor.cs @@ -34,9 +34,9 @@ namespace Adaptive.Aeron /// +----------------------------+ /// | to-clients Buffer | /// +----------------------------+ - /// | Counter Metadata Buffer | + /// | Counters Metadata Buffer | /// +----------------------------+ - /// | Counter Values Buffer | + /// | Counters Values Buffer | /// +----------------------------+ /// | Error Log | /// +----------------------------+ @@ -44,59 +44,55 @@ namespace Adaptive.Aeron /// /// Meta Data Layout (CnC Version 6) ///
-    ///  +----------------------------+
-    ///  |   to-driver buffer length  |
-    ///  +----------------------------+
-    ///  |  to-clients buffer length  |
-    ///  +----------------------------+
-    ///  |   metadata buffer length   |
-    ///  +----------------------------+
-    ///  |    values buffer length    |
-    ///  +----------------------------+
-    ///  |   Client Liveness Timeout  |
-    ///  |                            |
-    ///  +----------------------------+
-    ///  |      Error Log length      |
-    ///  +----------------------------+
+    ///  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
+    /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+    /// |                      Aeron CnC Version                        |
+    /// +---------------------------------------------------------------+
+    /// |                   to-driver buffer length                     |
+    /// +---------------------------------------------------------------+
+    /// |                  to-clients buffer length                     |
+    /// +---------------------------------------------------------------+
+    /// |               Counters Metadata buffer length                 |
+    /// +---------------------------------------------------------------+
+    /// |                Counters Values buffer length                  |
+    /// +---------------------------------------------------------------+
+    /// |                   Error Log buffer length                     |
+    /// +---------------------------------------------------------------+
+    /// |                   Client Liveness Timeout                     |
+    /// |                                                               |
+    /// +---------------------------------------------------------------+
     /// 
/// public class CncFileDescriptor { public const string CNC_FILE = "cnc.dat"; - public const int CNC_VERSION = 6; + public const int CNC_VERSION = 7; public static readonly int CNC_VERSION_FIELD_OFFSET; - public static readonly int CNC_METADATA_OFFSET; - - /* Meta Data Offsets (offsets within the meta data section) */ - public static readonly int TO_DRIVER_BUFFER_LENGTH_FIELD_OFFSET; public static readonly int TO_CLIENTS_BUFFER_LENGTH_FIELD_OFFSET; public static readonly int COUNTERS_METADATA_BUFFER_LENGTH_FIELD_OFFSET; public static readonly int COUNTERS_VALUES_BUFFER_LENGTH_FIELD_OFFSET; public static readonly int CLIENT_LIVENESS_TIMEOUT_FIELD_OFFSET; public static readonly int ERROR_LOG_BUFFER_LENGTH_FIELD_OFFSET; + public static readonly int META_DATA_LENGTH; + public static readonly int END_OF_METADATA_OFFSET; static CncFileDescriptor() { CNC_VERSION_FIELD_OFFSET = 0; - CNC_METADATA_OFFSET = CNC_VERSION_FIELD_OFFSET + BitUtil.SIZE_OF_INT; - - TO_DRIVER_BUFFER_LENGTH_FIELD_OFFSET = 0; + TO_DRIVER_BUFFER_LENGTH_FIELD_OFFSET = CNC_VERSION_FIELD_OFFSET + BitUtil.SIZE_OF_INT; TO_CLIENTS_BUFFER_LENGTH_FIELD_OFFSET = TO_DRIVER_BUFFER_LENGTH_FIELD_OFFSET + BitUtil.SIZE_OF_INT; COUNTERS_METADATA_BUFFER_LENGTH_FIELD_OFFSET = TO_CLIENTS_BUFFER_LENGTH_FIELD_OFFSET + BitUtil.SIZE_OF_INT; COUNTERS_VALUES_BUFFER_LENGTH_FIELD_OFFSET = COUNTERS_METADATA_BUFFER_LENGTH_FIELD_OFFSET + BitUtil.SIZE_OF_INT; - CLIENT_LIVENESS_TIMEOUT_FIELD_OFFSET = COUNTERS_VALUES_BUFFER_LENGTH_FIELD_OFFSET + BitUtil.SIZE_OF_INT; - ERROR_LOG_BUFFER_LENGTH_FIELD_OFFSET = CLIENT_LIVENESS_TIMEOUT_FIELD_OFFSET + BitUtil.SIZE_OF_LONG; - META_DATA_LENGTH = ERROR_LOG_BUFFER_LENGTH_FIELD_OFFSET + BitUtil.SIZE_OF_INT; - END_OF_METADATA_OFFSET = BitUtil.Align(BitUtil.SIZE_OF_INT + META_DATA_LENGTH, (BitUtil.CACHE_LINE_LENGTH*2)); + ERROR_LOG_BUFFER_LENGTH_FIELD_OFFSET = COUNTERS_VALUES_BUFFER_LENGTH_FIELD_OFFSET + BitUtil.SIZE_OF_INT; + CLIENT_LIVENESS_TIMEOUT_FIELD_OFFSET = ERROR_LOG_BUFFER_LENGTH_FIELD_OFFSET + BitUtil.SIZE_OF_INT; + META_DATA_LENGTH = CLIENT_LIVENESS_TIMEOUT_FIELD_OFFSET + BitUtil.SIZE_OF_LONG; + END_OF_METADATA_OFFSET = BitUtil.Align(META_DATA_LENGTH, BitUtil.CACHE_LINE_LENGTH * 2); } - - public static readonly int META_DATA_LENGTH; - - public static readonly int END_OF_METADATA_OFFSET; - + /// /// Compute the length of the cnc file and return it. /// @@ -114,32 +110,32 @@ public static int CncVersionOffset(int baseOffset) public static int ToDriverBufferLengthOffset(int baseOffset) { - return baseOffset + CNC_METADATA_OFFSET + TO_DRIVER_BUFFER_LENGTH_FIELD_OFFSET; + return baseOffset + TO_DRIVER_BUFFER_LENGTH_FIELD_OFFSET; } public static int ToClientsBufferLengthOffset(int baseOffset) { - return baseOffset + CNC_METADATA_OFFSET + TO_CLIENTS_BUFFER_LENGTH_FIELD_OFFSET; + return baseOffset + TO_CLIENTS_BUFFER_LENGTH_FIELD_OFFSET; } public static int CountersMetaDataBufferLengthOffset(int baseOffset) { - return baseOffset + CNC_METADATA_OFFSET + COUNTERS_METADATA_BUFFER_LENGTH_FIELD_OFFSET; + return baseOffset + COUNTERS_METADATA_BUFFER_LENGTH_FIELD_OFFSET; } public static int CountersValuesBufferLengthOffset(int baseOffset) { - return baseOffset + CNC_METADATA_OFFSET + COUNTERS_VALUES_BUFFER_LENGTH_FIELD_OFFSET; + return baseOffset + COUNTERS_VALUES_BUFFER_LENGTH_FIELD_OFFSET; } public static int ClientLivenessTimeoutOffset(int baseOffset) { - return baseOffset + CNC_METADATA_OFFSET + CLIENT_LIVENESS_TIMEOUT_FIELD_OFFSET; + return baseOffset + CLIENT_LIVENESS_TIMEOUT_FIELD_OFFSET; } public static int ErrorLogBufferLengthOffset(int baseOffset) { - return baseOffset + CNC_METADATA_OFFSET + ERROR_LOG_BUFFER_LENGTH_FIELD_OFFSET; + return baseOffset + ERROR_LOG_BUFFER_LENGTH_FIELD_OFFSET; } public static void FillMetaData(UnsafeBuffer cncMetaDataBuffer, int toDriverBufferLength, int toClientsBufferLength, int counterMetaDataBufferLength, int counterValuesBufferLength, long clientLivenessTimeout, int errorLogBufferLength) @@ -148,14 +144,18 @@ public static void FillMetaData(UnsafeBuffer cncMetaDataBuffer, int toDriverBuff cncMetaDataBuffer.PutInt(ToClientsBufferLengthOffset(0), toClientsBufferLength); cncMetaDataBuffer.PutInt(CountersMetaDataBufferLengthOffset(0), counterMetaDataBufferLength); cncMetaDataBuffer.PutInt(CountersValuesBufferLengthOffset(0), counterValuesBufferLength); - cncMetaDataBuffer.PutLong(ClientLivenessTimeoutOffset(0), clientLivenessTimeout); cncMetaDataBuffer.PutInt(ErrorLogBufferLengthOffset(0), errorLogBufferLength); + cncMetaDataBuffer.PutLong(ClientLivenessTimeoutOffset(0), clientLivenessTimeout); + } + + public static void SignalCncReady(UnsafeBuffer cncMetaDataBuffer) + { cncMetaDataBuffer.PutIntVolatile(CncVersionOffset(0), CNC_VERSION); } public static UnsafeBuffer CreateMetaDataBuffer(MappedByteBuffer buffer) { - return new UnsafeBuffer(buffer.Pointer, 0, BitUtil.SIZE_OF_INT + META_DATA_LENGTH); + return new UnsafeBuffer(buffer.Pointer, 0, META_DATA_LENGTH); } public static UnsafeBuffer CreateToDriverBuffer(MappedByteBuffer buffer, IDirectBuffer metaDataBuffer) diff --git a/src/Adaptive.Aeron/Command/DestinationMessageFlyweight.cs b/src/Adaptive.Aeron/Command/DestinationMessageFlyweight.cs index 3bf3cb16..d8c6ddbc 100644 --- a/src/Adaptive.Aeron/Command/DestinationMessageFlyweight.cs +++ b/src/Adaptive.Aeron/Command/DestinationMessageFlyweight.cs @@ -18,6 +18,28 @@ namespace Adaptive.Aeron.Command { + /// + /// Control message for adding or removing a destination for a Publication in multi-destination-cast. + /// + /// 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 | + /// | | + /// +---------------------------------------------------------------+ + /// | Command Correlation ID | + /// | | + /// +---------------------------------------------------------------+ + /// | Registration Correlation ID | + /// | | + /// +---------------------------------------------------------------+ + /// | Channel Length | + /// +---------------------------------------------------------------+ + /// | Channel(ASCII) ... + /// .. | + /// +---------------------------------------------------------------+ + /// + /// public class DestinationMessageFlyweight : CorrelatedMessageFlyweight { private static readonly int REGISTRATION_CORRELATION_ID_OFFSET = CORRELATION_ID_FIELD_OFFSET + BitUtil.SIZE_OF_LONG; diff --git a/src/Adaptive.Aeron/Command/ImageBuffersReadyFlyweight.cs b/src/Adaptive.Aeron/Command/ImageBuffersReadyFlyweight.cs index c837fd34..74a39814 100644 --- a/src/Adaptive.Aeron/Command/ImageBuffersReadyFlyweight.cs +++ b/src/Adaptive.Aeron/Command/ImageBuffersReadyFlyweight.cs @@ -19,71 +19,47 @@ namespace Adaptive.Aeron.Command { /// - /// Message to denote that new buffers have been added for a subscription. + /// Message to denote that new buffers for a publication image are ready for a subscription. /// /// NOTE: Layout should be SBE compliant /// /// /// - /// 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 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 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - /// | Correlation ID | + /// | Correlation ID | /// | | /// +---------------------------------------------------------------+ - /// | Session ID | + /// | Session ID | /// +---------------------------------------------------------------+ - /// | Stream ID | + /// | Stream ID | /// +---------------------------------------------------------------+ - /// | Subscriber Position Block Length | - /// +---------------------------------------------------------------+ - /// | Subscriber Position Count | - /// +---------------------------------------------------------------+ - /// | Subscriber Position Id 0 | - /// +---------------------------------------------------------------+ - /// | Registration Id 0 | + /// | Subscriber Registration Id | /// | | /// +---------------------------------------------------------------+ - /// | Subscriber Position Id 1 | - /// +---------------------------------------------------------------+ - /// | Registration Id 1 | - /// | | + /// | Subscriber Position Id | /// +---------------------------------------------------------------+ - /// | ... - /// ... Up to "Position Indicators Count" entries of this form | + /// | Log File Length | /// +---------------------------------------------------------------+ - /// | Log File Length | + /// | Log File Name(ASCII) .. + /// .. | /// +---------------------------------------------------------------+ - /// | Log File Name (ASCII) ... - /// ... | + /// | Source identity Length | /// +---------------------------------------------------------------+ - /// | Source identity Length | + /// | Source identity(ASCII) .. + /// .. | /// +---------------------------------------------------------------+ - /// | Source identity (ASCII) ... - /// ... | - /// +---------------------------------------------------------------+ + /// public class ImageBuffersReadyFlyweight { - private static readonly int CORRELATION_ID_OFFSET; - private static readonly int SESSION_ID_OFFSET; - private static readonly int STREAM_ID_FIELD_OFFSET; - private static readonly int SUBSCRIBER_POSITION_BLOCK_LENGTH_OFFSET; - private static readonly int SUBSCRIBER_POSITION_COUNT_OFFSET; - private static readonly int SUBSCRIBER_POSITIONS_OFFSET; - - private static readonly int SUBSCRIBER_POSITION_BLOCK_LENGTH; - - static ImageBuffersReadyFlyweight() - { - CORRELATION_ID_OFFSET = 0; - SESSION_ID_OFFSET = CORRELATION_ID_OFFSET + BitUtil.SIZE_OF_LONG; - STREAM_ID_FIELD_OFFSET = SESSION_ID_OFFSET + BitUtil.SIZE_OF_INT; - SUBSCRIBER_POSITION_BLOCK_LENGTH_OFFSET = STREAM_ID_FIELD_OFFSET + BitUtil.SIZE_OF_INT; - SUBSCRIBER_POSITION_COUNT_OFFSET = SUBSCRIBER_POSITION_BLOCK_LENGTH_OFFSET + BitUtil.SIZE_OF_INT; - SUBSCRIBER_POSITIONS_OFFSET = SUBSCRIBER_POSITION_COUNT_OFFSET + BitUtil.SIZE_OF_INT; - SUBSCRIBER_POSITION_BLOCK_LENGTH = BitUtil.SIZE_OF_LONG + BitUtil.SIZE_OF_INT; - } - + private static readonly int CORRELATION_ID_OFFSET = 0; + private static readonly int SESSION_ID_OFFSET = CORRELATION_ID_OFFSET + BitUtil.SIZE_OF_LONG; + private static readonly int STREAM_ID_FIELD_OFFSET = SESSION_ID_OFFSET + BitUtil.SIZE_OF_INT; + private static readonly int SUBSCRIBER_REGISTRATION_ID_OFFSET = STREAM_ID_FIELD_OFFSET + BitUtil.SIZE_OF_INT; + private static readonly int SUBSCRIBER_POSITION_ID_OFFSET = SUBSCRIBER_REGISTRATION_ID_OFFSET + BitUtil.SIZE_OF_LONG; + private static readonly int LOG_FILE_NAME_OFFSET = SUBSCRIBER_POSITION_ID_OFFSET + BitUtil.SIZE_OF_INT; + private IMutableDirectBuffer _buffer; private int _offset; @@ -95,8 +71,8 @@ static ImageBuffersReadyFlyweight() /// for fluent API public ImageBuffersReadyFlyweight Wrap(IMutableDirectBuffer buffer, int offset) { - this._buffer = buffer; - this._offset = offset; + _buffer = buffer; + _offset = offset; return this; } @@ -162,38 +138,15 @@ public ImageBuffersReadyFlyweight StreamId(int streamId) return this; } - - /// - /// return the number of position indicators - /// - /// the number of position indicators - public int SubscriberPositionCount() - { - return _buffer.GetInt(_offset + SUBSCRIBER_POSITION_COUNT_OFFSET); - } - - /// - /// set the number of position indicators - /// - /// the number of position indicators - /// flyweight - public ImageBuffersReadyFlyweight SubscriberPositionCount(int value) - { - _buffer.PutInt(_offset + SUBSCRIBER_POSITION_BLOCK_LENGTH_OFFSET, SUBSCRIBER_POSITION_BLOCK_LENGTH); - _buffer.PutInt(_offset + SUBSCRIBER_POSITION_COUNT_OFFSET, value); - - return this; - } - + /// /// Set the position Id for the subscriber /// - /// for the subscriber position /// for the subscriber position /// flyweight - public ImageBuffersReadyFlyweight SubscriberPositionId(int index, int id) + public ImageBuffersReadyFlyweight SubscriberPositionId(int id) { - _buffer.PutInt(_offset + SubscriberPositionOffset(index), id); + _buffer.PutInt(_offset + SUBSCRIBER_POSITION_ID_OFFSET, id); return this; } @@ -201,22 +154,20 @@ public ImageBuffersReadyFlyweight SubscriberPositionId(int index, int id) /// /// Return the position Id for the subscriber /// - /// for the subscriber position /// position Id for the subscriber - public int SubscriberPositionId(int index) + public int SubscriberPositionId() { - return _buffer.GetInt(_offset + SubscriberPositionOffset(index)); + return _buffer.GetInt(_offset + SUBSCRIBER_POSITION_ID_OFFSET); } /// /// Set the registration Id for the subscriber position /// - /// for the subscriber position /// for the subscriber position /// flyweight - public ImageBuffersReadyFlyweight PositionIndicatorRegistrationId(int index, long id) + public ImageBuffersReadyFlyweight SubscriberRegistrationId(long id) { - _buffer.PutLong(_offset + SubscriberPositionOffset(index) + BitUtil.SIZE_OF_INT, id); + _buffer.PutLong(_offset + SUBSCRIBER_REGISTRATION_ID_OFFSET, id); return this; } @@ -224,11 +175,10 @@ public ImageBuffersReadyFlyweight PositionIndicatorRegistrationId(int index, lon /// /// Return the registration Id for the subscriber position /// - /// for the subscriber position /// registration Id for the subscriber position - public long PositionIndicatorRegistrationId(int index) + public long SubscriberRegistrationId() { - return _buffer.GetLong(_offset + SubscriberPositionOffset(index) + BitUtil.SIZE_OF_INT); + return _buffer.GetLong(_offset + SUBSCRIBER_REGISTRATION_ID_OFFSET); } /// @@ -237,7 +187,7 @@ public long PositionIndicatorRegistrationId(int index) /// log filename public string LogFileName() { - return _buffer.GetStringAscii(_offset + LogFileNameOffset()); + return _buffer.GetStringAscii(_offset + LOG_FILE_NAME_OFFSET); } /// @@ -247,7 +197,7 @@ public string LogFileName() /// flyweight public ImageBuffersReadyFlyweight LogFileName(string logFileName) { - _buffer.PutStringAscii(_offset + LogFileNameOffset(), logFileName); + _buffer.PutStringAscii(_offset + LOG_FILE_NAME_OFFSET, logFileName); return this; } @@ -282,21 +232,10 @@ public int Length() int sourceIdentityOffset = SourceIdentityOffset(); return sourceIdentityOffset + _buffer.GetInt(_offset + sourceIdentityOffset) + BitUtil.SIZE_OF_INT; } - - private int SubscriberPositionOffset(int index) - { - return SUBSCRIBER_POSITIONS_OFFSET + (index*SUBSCRIBER_POSITION_BLOCK_LENGTH); - } - - private int LogFileNameOffset() - { - return SubscriberPositionOffset(SubscriberPositionCount()); - } - + private int SourceIdentityOffset() { - int logFileNameOffset = LogFileNameOffset(); - return logFileNameOffset + _buffer.GetInt(_offset + logFileNameOffset) + BitUtil.SIZE_OF_INT; + return LOG_FILE_NAME_OFFSET + _buffer.GetInt(_offset + LOG_FILE_NAME_OFFSET) + BitUtil.SIZE_OF_INT; } } } \ No newline at end of file diff --git a/src/Adaptive.Aeron/Command/PublicationBuffersReadyFlyweight.cs b/src/Adaptive.Aeron/Command/PublicationBuffersReadyFlyweight.cs index 554325ce..7e3de02d 100644 --- a/src/Adaptive.Aeron/Command/PublicationBuffersReadyFlyweight.cs +++ b/src/Adaptive.Aeron/Command/PublicationBuffersReadyFlyweight.cs @@ -23,27 +23,31 @@ namespace Adaptive.Aeron.Command /// /// /// - /// 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 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 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Correlation ID | /// | | /// +---------------------------------------------------------------+ + /// | Registration ID | + /// | | + /// +---------------------------------------------------------------+ /// | Session ID | /// +---------------------------------------------------------------+ /// | Stream ID | /// +---------------------------------------------------------------+ - /// | Publication Limit Offset | + /// | Publication Limit Counter Id | + /// +---------------------------------------------------------------+ + /// | Log File Length | /// +---------------------------------------------------------------+ - /// | Log File Length | + /// | Log File Name(ASCII) ... + /// ... | /// +---------------------------------------------------------------+ - /// | Log File Name (ASCII) ... - /// ... | - /// +---------------------------------------------------------------+ public class PublicationBuffersReadyFlyweight { private const int CORRELATION_ID_OFFSET = 0; - private static readonly int SESSION_ID_OFFSET = CORRELATION_ID_OFFSET + BitUtil.SIZE_OF_LONG; + private static readonly int REGISTRATION_ID_OFFSET = CORRELATION_ID_OFFSET + BitUtil.SIZE_OF_LONG; + private static readonly int SESSION_ID_OFFSET = REGISTRATION_ID_OFFSET + BitUtil.SIZE_OF_LONG; private static readonly int STREAM_ID_FIELD_OFFSET = SESSION_ID_OFFSET + BitUtil.SIZE_OF_INT; private static readonly int PUBLICATION_LIMIT_COUNTER_ID_OFFSET = STREAM_ID_FIELD_OFFSET + BitUtil.SIZE_OF_INT; private static readonly int LOGFILE_FIELD_OFFSET = PUBLICATION_LIMIT_COUNTER_ID_OFFSET + BitUtil.SIZE_OF_INT; @@ -59,14 +63,14 @@ public class PublicationBuffersReadyFlyweight /// for fluent API public PublicationBuffersReadyFlyweight Wrap(IMutableDirectBuffer buffer, int offset) { - this._buffer = buffer; - this._offset = offset; + _buffer = buffer; + _offset = offset; return this; } /// - /// return correlation id field + /// Get the correlation id field /// /// correlation id field public long CorrelationId() @@ -75,7 +79,7 @@ public long CorrelationId() } /// - /// set correlation id field + /// Set the correlation id field /// /// field value /// flyweight @@ -87,7 +91,28 @@ public PublicationBuffersReadyFlyweight CorrelationId(long correlationId) } /// - /// return session id field + /// Get the registration id field + /// + /// correlation id field + public long RegistrationId() + { + return _buffer.GetLong(_offset + REGISTRATION_ID_OFFSET); + } + + /// + /// Set the correlation id field + /// + /// field value + /// flyweight + public PublicationBuffersReadyFlyweight RegistrationId(long registrationId) + { + _buffer.PutLong(_offset + REGISTRATION_ID_OFFSET, registrationId); + + return this; + } + + /// + /// Get the session id field /// /// session id field public int SessionId() @@ -96,7 +121,7 @@ public int SessionId() } /// - /// set session id field + /// Set the session id field /// /// field value /// flyweight @@ -108,7 +133,7 @@ public PublicationBuffersReadyFlyweight SessionId(int sessionId) } /// - /// return stream id field + /// Get the stream id field /// /// stream id field public int StreamId() @@ -117,7 +142,7 @@ public int StreamId() } /// - /// set stream id field + /// Set the stream id field /// /// field value /// flyweight diff --git a/src/Adaptive.Aeron/Command/RemoveMessageFlyweight.cs b/src/Adaptive.Aeron/Command/RemoveMessageFlyweight.cs index d588088a..44f9692f 100644 --- a/src/Adaptive.Aeron/Command/RemoveMessageFlyweight.cs +++ b/src/Adaptive.Aeron/Command/RemoveMessageFlyweight.cs @@ -26,10 +26,13 @@ namespace Adaptive.Aeron.Command /// 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 | + /// | | /// +---------------------------------------------------------------+ /// | Command Correlation ID | + /// | | /// +---------------------------------------------------------------+ /// | Registration ID | + /// | | /// +---------------------------------------------------------------+ /// /// diff --git a/src/Adaptive.Aeron/Command/SubscriptionMessageFlyweight.cs b/src/Adaptive.Aeron/Command/SubscriptionMessageFlyweight.cs index 64cd1be1..ce15a250 100644 --- a/src/Adaptive.Aeron/Command/SubscriptionMessageFlyweight.cs +++ b/src/Adaptive.Aeron/Command/SubscriptionMessageFlyweight.cs @@ -25,8 +25,10 @@ namespace Adaptive.Aeron.Command /// 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 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Command Correlation ID | + /// | | /// +---------------------------------------------------------------+ /// | Registration Correlation ID | + /// | | /// +---------------------------------------------------------------+ /// | Stream Id | /// +---------------------------------------------------------------+ diff --git a/src/Adaptive.Aeron/ControlledFragmentAssembler.cs b/src/Adaptive.Aeron/ControlledFragmentAssembler.cs index c834db19..01cfb8f9 100644 --- a/src/Adaptive.Aeron/ControlledFragmentAssembler.cs +++ b/src/Adaptive.Aeron/ControlledFragmentAssembler.cs @@ -14,15 +14,14 @@ * limitations under the License. */ -using System; using System.Collections.Generic; using Adaptive.Aeron.LogBuffer; -using Adaptive.Agrona; +using Adaptive.Agrona.Concurrent; namespace Adaptive.Aeron { /// - /// A that sits in a chain-of-responsibility pattern that reassembles fragmented + /// A that sits in a chain-of-responsibility pattern that reassembles fragmented /// messages so that the next handler in the chain only sees whole messages. /// /// Unfragmented messages are delegated without copy. Fragmented messages are copied to a temporary @@ -37,10 +36,10 @@ namespace Adaptive.Aeron /// /// /// - public class ControlledFragmentAssembler : IControlledFragmentHandler + public class ControlledFragmentAssembler { private readonly int _initialBufferLength; - private readonly IControlledFragmentHandler _delegate; + private readonly ControlledFragmentHandler _delegate; private readonly IDictionary _builderBySessionIdMap = new Dictionary(); /// @@ -48,7 +47,7 @@ public class ControlledFragmentAssembler : IControlledFragmentHandler /// /// onto which whole messages are forwarded. /// to be used for each session. - public ControlledFragmentAssembler(IControlledFragmentHandler @delegate, int initialBufferLength = BufferBuilder.INITIAL_CAPACITY) + public ControlledFragmentAssembler(ControlledFragmentHandler @delegate, int initialBufferLength = BufferBuilder.MIN_ALLOCATED_CAPACITY) { _initialBufferLength = initialBufferLength; _delegate = @delegate; @@ -58,7 +57,7 @@ public ControlledFragmentAssembler(IControlledFragmentHandler @delegate, int ini /// Get the delegate unto which assembled messages are delegated. /// /// the delegate unto which assembled messages are delegated. - public virtual IControlledFragmentHandler Delegate() + public virtual ControlledFragmentHandler Delegate() { return _delegate; } @@ -70,7 +69,7 @@ public virtual IControlledFragmentHandler Delegate() /// at which the data begins. /// of the data in bytes. /// representing the meta data for the data. - public ControlledFragmentHandlerAction OnFragment(IDirectBuffer buffer, int offset, int length, Header header) + public ControlledFragmentHandlerAction OnFragment(UnsafeBuffer buffer, int offset, int length, Header header) { byte flags = header.Flags; @@ -78,7 +77,7 @@ public ControlledFragmentHandlerAction OnFragment(IDirectBuffer buffer, int offs if ((flags & FrameDescriptor.UNFRAGMENTED) == FrameDescriptor.UNFRAGMENTED) { - action = _delegate.OnFragment(buffer, offset, length, header); + action = _delegate(buffer, offset, length, header); } else { @@ -103,7 +102,7 @@ public ControlledFragmentHandlerAction OnFragment(IDirectBuffer buffer, int offs if ((flags & FrameDescriptor.END_FRAG_FLAG) == FrameDescriptor.END_FRAG_FLAG) { int msgLength = builder.Limit(); - action = _delegate.OnFragment(builder.Buffer(), 0, msgLength, header); + action = _delegate(builder.Buffer(), 0, msgLength, header); if (ControlledFragmentHandlerAction.ABORT == action) { diff --git a/src/Adaptive.Aeron/DriverListenerAdapter.cs b/src/Adaptive.Aeron/DriverEventsAdapter.cs similarity index 74% rename from src/Adaptive.Aeron/DriverListenerAdapter.cs rename to src/Adaptive.Aeron/DriverEventsAdapter.cs index 7d9e0660..c418f0e8 100644 --- a/src/Adaptive.Aeron/DriverListenerAdapter.cs +++ b/src/Adaptive.Aeron/DriverEventsAdapter.cs @@ -14,10 +14,9 @@ * limitations under the License. */ -using System.Collections.Generic; using Adaptive.Aeron.Command; using Adaptive.Agrona; -using Adaptive.Agrona.Collections; +using Adaptive.Agrona.Concurrent; using Adaptive.Agrona.Concurrent.Broadcast; namespace Adaptive.Aeron @@ -25,10 +24,8 @@ namespace Adaptive.Aeron /// /// Analogue of the on the client side /// - internal class DriverListenerAdapter + internal class DriverEventsAdapter { - public const long MISSING_REGISTRATION_ID = -1L; - private readonly CopyBroadcastReceiver _broadcastReceiver; private readonly ErrorResponseFlyweight _errorResponse = new ErrorResponseFlyweight(); @@ -36,26 +33,28 @@ internal class DriverListenerAdapter private readonly ImageBuffersReadyFlyweight _imageReady = new ImageBuffersReadyFlyweight(); private readonly CorrelatedMessageFlyweight _correlatedMessage = new CorrelatedMessageFlyweight(); private readonly ImageMessageFlyweight _imageMessage = new ImageMessageFlyweight(); - private readonly IDriverListener _listener; - private readonly IDictionary _subscriberPositionMap = new DefaultDictionary(MISSING_REGISTRATION_ID); + private readonly IDriverEventsListener _listener; + private readonly MessageHandler _messageHandler; private long _activeCorrelationId; private long _lastReceivedCorrelationId; private string _expectedChannel; + - internal DriverListenerAdapter(CopyBroadcastReceiver broadcastReceiver, IDriverListener listener) + internal DriverEventsAdapter(CopyBroadcastReceiver broadcastReceiver, IDriverEventsListener listener) { _broadcastReceiver = broadcastReceiver; _listener = listener; + _messageHandler = OnMessage; } - public int PollMessage(long activeCorrelationId, string expectedChannel) + public int Receive(long activeCorrelationId, string expectedChannel) { _activeCorrelationId = activeCorrelationId; _lastReceivedCorrelationId = -1; _expectedChannel = expectedChannel; - - return _broadcastReceiver.Receive(OnMessage); + + return _broadcastReceiver.Receive(_messageHandler); } public long LastReceivedCorrelationId() @@ -74,7 +73,7 @@ public void OnMessage(int msgTypeId, IMutableDirectBuffer buffer, int index, int long correlationId = _errorResponse.OffendingCommandCorrelationId(); if (correlationId == _activeCorrelationId) { - _listener.OnError(_errorResponse.ErrorCode(), _errorResponse.ErrorMessage(), correlationId); + _listener.OnError(correlationId, _errorResponse.ErrorCode(), _errorResponse.ErrorMessage()); _lastReceivedCorrelationId = correlationId; } @@ -85,22 +84,14 @@ public void OnMessage(int msgTypeId, IMutableDirectBuffer buffer, int index, int { _imageReady.Wrap(buffer, index); - _subscriberPositionMap.Clear(); - for (int i = 0, max = _imageReady.SubscriberPositionCount(); i < max; i++) - { - long registrationId = _imageReady.PositionIndicatorRegistrationId(i); - int positionId = _imageReady.SubscriberPositionId(i); - - _subscriberPositionMap.Add(registrationId, positionId); - } - _listener.OnAvailableImage( + _imageReady.CorrelationId(), _imageReady.StreamId(), - _imageReady.SessionId(), - _subscriberPositionMap, + _imageReady.SessionId(), + _imageReady.SubscriberRegistrationId(), + _imageReady.SubscriberPositionId(), _imageReady.LogFileName(), - _imageReady.SourceIdentity(), - _imageReady.CorrelationId()); + _imageReady.SourceIdentity()); break; } @@ -113,12 +104,13 @@ public void OnMessage(int msgTypeId, IMutableDirectBuffer buffer, int index, int if (correlationId == _activeCorrelationId) { _listener.OnNewPublication( - _expectedChannel, + correlationId, + _publicationReady.RegistrationId(), _publicationReady.StreamId(), _publicationReady.SessionId(), _publicationReady.PublicationLimitCounterId(), - _publicationReady.LogFileName(), - correlationId); + _expectedChannel, + _publicationReady.LogFileName()); _lastReceivedCorrelationId = correlationId; } @@ -141,7 +133,7 @@ public void OnMessage(int msgTypeId, IMutableDirectBuffer buffer, int index, int { _imageMessage.Wrap(buffer, index); - _listener.OnUnavailableImage(_imageMessage.StreamId(), _imageMessage.CorrelationId()); + _listener.OnUnavailableImage(_imageMessage.CorrelationId(), _imageMessage.StreamId()); break; } @@ -153,12 +145,13 @@ public void OnMessage(int msgTypeId, IMutableDirectBuffer buffer, int index, int if (correlationId == _activeCorrelationId) { _listener.OnNewExclusivePublication( - _expectedChannel, + correlationId, + _publicationReady.RegistrationId(), _publicationReady.StreamId(), _publicationReady.SessionId(), _publicationReady.PublicationLimitCounterId(), - _publicationReady.LogFileName(), - correlationId); + _expectedChannel, + _publicationReady.LogFileName()); _lastReceivedCorrelationId = correlationId; } diff --git a/src/Adaptive.Aeron/DriverProxy.cs b/src/Adaptive.Aeron/DriverProxy.cs index 7f9bb7a6..19245a82 100644 --- a/src/Adaptive.Aeron/DriverProxy.cs +++ b/src/Adaptive.Aeron/DriverProxy.cs @@ -32,7 +32,8 @@ namespace Adaptive.Aeron public class DriverProxy { /// - /// Maximum capacity of the write buffer + /// Maximum capacity of the write buffer + /// public const int MSG_BUFFER_CAPACITY = 1024; private readonly UnsafeBuffer _buffer = new UnsafeBuffer(BufferUtil.AllocateDirectAligned(MSG_BUFFER_CAPACITY,BitUtil.CACHE_LINE_LENGTH * 2)); @@ -43,7 +44,7 @@ public class DriverProxy private readonly DestinationMessageFlyweight _destinationMessage = new DestinationMessageFlyweight(); private readonly IRingBuffer _toDriverCommandBuffer; - public DriverProxy(IRingBuffer toDriverCommandBuffer) + public DriverProxy(IRingBuffer toDriverCommandBuffer, long clientId) { if (toDriverCommandBuffer == null) throw new ArgumentNullException(nameof(toDriverCommandBuffer)); @@ -56,7 +57,6 @@ public DriverProxy(IRingBuffer toDriverCommandBuffer) _removeMessage.Wrap(_buffer, 0); _destinationMessage.Wrap(_buffer, 0); - var clientId = toDriverCommandBuffer.NextCorrelationId(); _correlatedMessage.ClientId(clientId); } diff --git a/src/Adaptive.Aeron/EndOfStreamHandler.cs b/src/Adaptive.Aeron/EndOfStreamHandler.cs new file mode 100644 index 00000000..3b662c3e --- /dev/null +++ b/src/Adaptive.Aeron/EndOfStreamHandler.cs @@ -0,0 +1,8 @@ +namespace Adaptive.Aeron +{ + /// + /// Delegeate for delivery of End of Stream image notification to a + /// + /// that has reached End Of Stream. + public delegate void EndOfStreamHandler(Image image); +} \ No newline at end of file diff --git a/src/Adaptive.Aeron/Exceptions/ConductorServiceTimeoutException.cs b/src/Adaptive.Aeron/Exceptions/ConductorServiceTimeoutException.cs index 7e8aae5c..7be38162 100644 --- a/src/Adaptive.Aeron/Exceptions/ConductorServiceTimeoutException.cs +++ b/src/Adaptive.Aeron/Exceptions/ConductorServiceTimeoutException.cs @@ -21,7 +21,7 @@ namespace Adaptive.Aeron.Exceptions /// /// A timeout has occurred between service calls for the client conductor. /// - public class ConductorServiceTimeoutException : Exception + public class ConductorServiceTimeoutException : TimeoutException { public ConductorServiceTimeoutException(string message) : base(message) { diff --git a/src/Adaptive.Aeron/Exceptions/DriverTimeoutException.cs b/src/Adaptive.Aeron/Exceptions/DriverTimeoutException.cs index 399a0d11..08ba751d 100644 --- a/src/Adaptive.Aeron/Exceptions/DriverTimeoutException.cs +++ b/src/Adaptive.Aeron/Exceptions/DriverTimeoutException.cs @@ -21,7 +21,7 @@ namespace Adaptive.Aeron.Exceptions /// /// A timeout has occurred while waiting on the media driver responding to an operation. /// - public class DriverTimeoutException : Exception + public class DriverTimeoutException : TimeoutException { public DriverTimeoutException(string message) : base(message) { diff --git a/src/Adaptive.Aeron/ExclusivePublication.cs b/src/Adaptive.Aeron/ExclusivePublication.cs index a94926da..c6f67e0b 100644 --- a/src/Adaptive.Aeron/ExclusivePublication.cs +++ b/src/Adaptive.Aeron/ExclusivePublication.cs @@ -26,7 +26,7 @@ namespace Adaptive.Aeron { /// - /// Aeron Publisher API for sending messages to subscribers of a given channel and streamId pair. ExclusivePublications + /// Aeron publisher API for sending messages to subscribers of a given channel and streamId pair. ExclusivePublications /// each get their own session id so multiple can be concurrently active on the same media driver as independent streams. /// /// s are create via the method, @@ -37,7 +37,7 @@ namespace Adaptive.Aeron /// /// The APIs used try claim and offer are non-blocking. /// - /// Note: ExclusivePublication instances are NOT threadsafe for offer and try claim method but are for position. + /// Note: Instances are NOT threadsafe for offer and try claim methods but are for the others. /// /// /// @@ -55,36 +55,52 @@ public class ExclusivePublication : IDisposable /// /// The offer failed due to an administration action and should be retried. + /// The action is an operation such as log rotation which is likely to have succeeded by the next retry attempt. /// public const long ADMIN_ACTION = -3; /// - /// The has been closed and should no longer be used. + /// The has been closed and should no longer be used. /// public const long CLOSED = -4; - private int _refCount; + /// + /// The offer failed due to reaching the maximum position of the stream given term buffer length times the total + /// possible number of terms. + /// + /// If this happen then the publication should be closed and a new one added. To make it less likely to happen then + /// increase the term buffer length. + /// + /// + public const long MAX_POSITION_EXCEEDED = -5; + + private readonly long _originalRegistrationId; + private readonly long _maxPossiblePosition; + private readonly int _termBufferLength; private readonly int _positionBitsToShift; private volatile bool _isClosed; + private long _termBeginPosition; + private int _activePartitionIndex; + private int _termId; + private int _termOffset; + + private readonly ExclusiveTermAppender[] _termAppenders = + new ExclusiveTermAppender[LogBufferDescriptor.PARTITION_COUNT]; - private readonly ExclusiveTermAppender[] _termAppenders = new ExclusiveTermAppender[LogBufferDescriptor.PARTITION_COUNT]; private readonly IReadablePosition _positionLimit; private readonly UnsafeBuffer _logMetaDataBuffer; private readonly HeaderWriter _headerWriter; private readonly LogBuffers _logBuffers; private readonly ClientConductor _conductor; - private long _termBeginPosition; - private int _activePartitionIndex; - private int _termId; - private int _termOffset; internal ExclusivePublication( - ClientConductor clientConductor, - string channel, - int streamId, - int sessionId, + ClientConductor clientConductor, + string channel, + int streamId, + int sessionId, IReadablePosition positionLimit, - LogBuffers logBuffers, + LogBuffers logBuffers, + long originalRegistrationId, long registrationId) { var buffers = logBuffers.TermBuffers(); @@ -96,25 +112,32 @@ internal ExclusivePublication( } var termLength = logBuffers.TermLength(); + _termBufferLength = termLength; MaxPayloadLength = LogBufferDescriptor.MtuLength(logMetaDataBuffer) - DataHeaderFlyweight.HEADER_LENGTH; MaxMessageLength = FrameDescriptor.ComputeExclusiveMaxMessageLength(termLength); + _maxPossiblePosition = termLength * (1L << 31); _conductor = clientConductor; Channel = channel; StreamId = streamId; SessionId = sessionId; - InitialTermId = LogBufferDescriptor.InitialTermId(logMetaDataBuffer); + _logMetaDataBuffer = logMetaDataBuffer; + _originalRegistrationId = originalRegistrationId; RegistrationId = registrationId; _positionLimit = positionLimit; _logBuffers = logBuffers; _positionBitsToShift = IntUtil.NumberOfTrailingZeros(termLength); _headerWriter = new HeaderWriter(LogBufferDescriptor.DefaultFrameHeader(logMetaDataBuffer)); - this._activePartitionIndex = LogBufferDescriptor.ActivePartitionIndex(logMetaDataBuffer); + InitialTermId = LogBufferDescriptor.InitialTermId(logMetaDataBuffer); - long rawTail = _termAppenders[_activePartitionIndex].RawTail(); + var activeIndex = LogBufferDescriptor.ActivePartitionIndex(logMetaDataBuffer); + _activePartitionIndex = activeIndex; + + long rawTail = LogBufferDescriptor.RawTail(_logMetaDataBuffer, activeIndex); _termId = LogBufferDescriptor.TermId(rawTail); _termOffset = LogBufferDescriptor.TermOffset(rawTail, termLength); - _termBeginPosition = LogBufferDescriptor.ComputeTermBeginPosition(_termId, _positionBitsToShift, InitialTermId); + _termBeginPosition = + LogBufferDescriptor.ComputeTermBeginPosition(_termId, _positionBitsToShift, InitialTermId); } /// @@ -123,6 +146,18 @@ internal ExclusivePublication( /// the length in bytes for each term partition in the log buffer. public int TermBufferLength => _logBuffers.TermLength(); + /// + /// The maximum possible position this stream can reach due to its term buffer length. + /// + /// Maximum possible position is term-length times 2^31 in bytes. + /// + /// + /// the maximum possible position this stream can reach due to it term buffer length. + public long MaxPossiblePosition() + { + return _maxPossiblePosition; + } + /// /// Media address for delivery to the channel. /// @@ -167,7 +202,29 @@ internal ExclusivePublication( public int MaxPayloadLength { get; } /// - /// Return the registration id used to register this Publication with the media driver. + /// Get the original registration used to register this Publication with the media driver by the first publisher. + /// + /// original registration id + public long OriginalRegistrationId() + { + return _originalRegistrationId; + } + + /// + /// Is this Publication the original instance added to the driver? If not then it was added after another client + /// has already added the publication. + /// + /// true if this instance is the first added otherwise false. + public bool IsOriginal() + { + return _originalRegistrationId == RegistrationId; + } + + /// + /// Get the registration id used to register this Publication with the media driver. + /// + /// If this value is different from the then another client has previously added + /// this Publication. In the case of an exclusive publication this should never happen. /// /// registration id public long RegistrationId { get; } @@ -176,7 +233,9 @@ internal ExclusivePublication( /// Has the seen an active Subscriber recently? /// /// true if this has seen an active subscriber otherwise false. - public bool IsConnected => !_isClosed && _conductor.IsPublicationConnected(LogBufferDescriptor.TimeOfLastStatusMessage(_logMetaDataBuffer)); + public bool IsConnected => !_isClosed && + _conductor.IsPublicationConnected( + LogBufferDescriptor.TimeOfLastStatusMessage(_logMetaDataBuffer)); /// /// Release resources used by this Publication. @@ -234,7 +293,8 @@ public long Position var rawTail = LogBufferDescriptor.RawTailVolatile(_logMetaDataBuffer); var termOffset = LogBufferDescriptor.TermOffset(rawTail, _logBuffers.TermLength()); - return LogBufferDescriptor.ComputePosition(LogBufferDescriptor.TermId(rawTail), termOffset, _positionBitsToShift, InitialTermId); + return LogBufferDescriptor.ComputePosition(LogBufferDescriptor.TermId(rawTail), termOffset, + _positionBitsToShift, InitialTermId); } } @@ -262,7 +322,7 @@ public long PositionLimit /// /// containing message. /// The new stream position, otherwise , , - /// or . + /// , or . [MethodImpl(MethodImplOptions.AggressiveInlining)] public long Offer(UnsafeBuffer buffer) { @@ -275,13 +335,14 @@ public long Offer(UnsafeBuffer buffer) /// containing message. /// offset in the buffer at which the encoded message begins. /// in bytes of the encoded message. + /// for the frame. /// The new stream position, otherwise a negative error value , , - /// or . + /// , or . [MethodImpl(MethodImplOptions.AggressiveInlining)] public long Offer( - UnsafeBuffer buffer, - int offset, - int length, + UnsafeBuffer buffer, + int offset, + int length, ReservedValueSupplier reservedValueSupplier = null) { var newPosition = CLOSED; @@ -289,30 +350,28 @@ public long Offer( { var limit = _positionLimit.Volatile; ExclusiveTermAppender termAppender = _termAppenders[_activePartitionIndex]; - long position = _termBeginPosition + this._termOffset; + long position = _termBeginPosition + _termOffset; if (position < limit) { int result; if (length <= MaxPayloadLength) { - result = termAppender.AppendUnfragmentedMessage(_termId, _termOffset, _headerWriter, buffer, offset, length, reservedValueSupplier); + result = termAppender.AppendUnfragmentedMessage(_termId, _termOffset, _headerWriter, buffer, + offset, length, reservedValueSupplier); } else { CheckForMaxMessageLength(length); - result = termAppender.AppendFragmentedMessage(_termId, _termOffset, _headerWriter, buffer, offset, length, MaxPayloadLength, reservedValueSupplier); + result = termAppender.AppendFragmentedMessage(_termId, _termOffset, _headerWriter, buffer, + offset, length, MaxPayloadLength, reservedValueSupplier); } newPosition = NewPosition(result); } - else if (_conductor.IsPublicationConnected(LogBufferDescriptor.TimeOfLastStatusMessage(_logMetaDataBuffer))) - { - newPosition = BACK_PRESSURED; - } else { - newPosition = NOT_CONNECTED; + newPosition = BackPressureStatus(position, length); } } @@ -349,7 +408,7 @@ public long Offer( /// of the range to claim, in bytes.. /// to be populated if the claim succeeds. /// The new stream position, otherwise , , - /// or . + /// , or . /// if the length is greater than max payload length within an MTU. /// /// @@ -370,19 +429,48 @@ public long TryClaim(int length, ExclusiveBufferClaim bufferClaim) int result = termAppender.Claim(_termId, _termOffset, _headerWriter, length, bufferClaim); newPosition = NewPosition(result); } - else if (_conductor.IsPublicationConnected(LogBufferDescriptor.TimeOfLastStatusMessage(_logMetaDataBuffer))) + else + { + newPosition = BackPressureStatus(position, length); + } + } + + return newPosition; + } + + /// + /// Append a padding record log of a given length to make up the log to a position. + /// + /// of the range to claim, in bytes.. + /// The new stream position, otherwise a negative error value of , + /// , , , or . + /// if the length is greater than . + public virtual long AppendPadding(int length) + { + CheckForMaxMessageLength(length); + long newPosition = CLOSED; + + if (!_isClosed) + { + long limit = _positionLimit.Volatile; + ExclusiveTermAppender termAppender = _termAppenders[_activePartitionIndex]; + long position = _termBeginPosition + _termOffset; + + if (position < limit) { - newPosition = BACK_PRESSURED; + int result = termAppender.AppendPadding(_termId, _termOffset, _headerWriter, length); + newPosition = NewPosition(result); } else { - newPosition = NOT_CONNECTED; + newPosition = BackPressureStatus(position, length); } } return newPosition; } + /// /// Add a destination manually to a multi-destination-cast Publication. /// @@ -416,7 +504,7 @@ public void RemoveDestination(string endpointChannel) _conductor.ClientLock().Unlock(); } } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] private long NewPosition(int resultingOffset) { @@ -428,21 +516,43 @@ private long NewPosition(int resultingOffset) } else { + if ((_termBeginPosition + _termBufferLength) >= MaxPossiblePosition()) + { + return MAX_POSITION_EXCEEDED; + } + int nextIndex = LogBufferDescriptor.NextPartitionIndex(_activePartitionIndex); int nextTermId = _termId + 1; _activePartitionIndex = nextIndex; _termOffset = 0; _termId = nextTermId; - _termBeginPosition = LogBufferDescriptor.ComputeTermBeginPosition(nextTermId, _positionBitsToShift, InitialTermId); + _termBeginPosition = + LogBufferDescriptor.ComputeTermBeginPosition(nextTermId, _positionBitsToShift, InitialTermId); - _termAppenders[nextIndex].TailTermId(nextTermId); + LogBufferDescriptor.InitialiseTailWithTermId(_logMetaDataBuffer, nextIndex, nextTermId); LogBufferDescriptor.ActivePartitionIndexOrdered(_logMetaDataBuffer, nextIndex); return ADMIN_ACTION; } } + private long BackPressureStatus(long currentPosition, int messageLength) + { + long status = NOT_CONNECTED; + + if ((currentPosition + messageLength) >= _maxPossiblePosition) + { + status = MAX_POSITION_EXCEEDED; + } + else if (_conductor.IsPublicationConnected(LogBufferDescriptor.TimeOfLastStatusMessage(_logMetaDataBuffer))) + { + status = BACK_PRESSURED; + } + + return status; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void CheckForMaxPayloadLength(int length) { diff --git a/src/Adaptive.Aeron/FragmentAssembler.cs b/src/Adaptive.Aeron/FragmentAssembler.cs index 5eb67fce..65fd40ef 100644 --- a/src/Adaptive.Aeron/FragmentAssembler.cs +++ b/src/Adaptive.Aeron/FragmentAssembler.cs @@ -49,7 +49,7 @@ public class FragmentAssembler /// Construct an adapter to reassemble message fragments and delegate on whole messages. /// /// onto which whole messages are forwarded. - public FragmentAssembler(FragmentHandler fragmentHandler) : this(fragmentHandler, BufferBuilder.INITIAL_CAPACITY) + public FragmentAssembler(FragmentHandler fragmentHandler) : this(fragmentHandler, BufferBuilder.MIN_ALLOCATED_CAPACITY) { } diff --git a/src/Adaptive.Aeron/IDriverListener.cs b/src/Adaptive.Aeron/IDriverEventsListener.cs similarity index 68% rename from src/Adaptive.Aeron/IDriverListener.cs rename to src/Adaptive.Aeron/IDriverEventsListener.cs index 42672fcb..f67faed0 100644 --- a/src/Adaptive.Aeron/IDriverListener.cs +++ b/src/Adaptive.Aeron/IDriverEventsListener.cs @@ -14,42 +14,43 @@ * limitations under the License. */ -using System.Collections.Generic; - namespace Adaptive.Aeron { /// /// Callback interface for dispatching command responses from the driver on the control protocol. /// - internal interface IDriverListener + internal interface IDriverEventsListener { - void OnError(ErrorCode errorCode, string message, long correlationId); + void OnError(long correlationId, ErrorCode errorCode, string message); void OnAvailableImage( + long correlationId, int streamId, - int sessionId, - IDictionary subscriberPositionMap, + int sessionId, + long subscriberRegistrationId, + int subscriberPositionId, string logFileName, - string sourceIdentity, - long correlationId); + string sourceIdentity); void OnNewPublication( - string channel, + long correlationId, + long registrationId, int streamId, int sessionId, int publicationLimitId, - string logFileName, - long correlationId); + string channel, + string logFileName); - void OnUnavailableImage(int streamId, long correlationId); + void OnUnavailableImage(long correlationId, int streamId); void OnNewExclusivePublication( - string channel, + long correlationId, + long registrationid, int streamId, int sessionId, int publicationLimitId, - string logFileName, - long correlationId); + string channel, + string logFileName); } } \ No newline at end of file diff --git a/src/Adaptive.Aeron/Image.cs b/src/Adaptive.Aeron/Image.cs index 3f91ba2d..4a198e72 100644 --- a/src/Adaptive.Aeron/Image.cs +++ b/src/Adaptive.Aeron/Image.cs @@ -39,11 +39,13 @@ namespace Adaptive.Aeron /// public class Image { - private readonly long _joiningPosition; + private readonly long _joinPosition; + private long _finalPosition; private readonly int _initialTermId; private readonly int _termLengthMask; private readonly int _positionBitsToShift; + private bool _isEos; private volatile bool _isClosed; private readonly IPosition _subscriberPosition; @@ -75,7 +77,7 @@ public Image(Subscription subscription, int sessionId, IPosition subscriberPosit _errorHandler = errorHandler; SourceIdentity = sourceIdentity; CorrelationId = correlationId; - _joiningPosition = subscriberPosition.Get(); + _joinPosition = subscriberPosition.Get(); _termBuffers = logBuffers.TermBuffers(); @@ -104,7 +106,15 @@ public Image(Subscription subscription, int sessionId, IPosition subscriberPosit /// source identity of the sending publisher as an abstract concept appropriate for the media. public string SourceIdentity { get; } - + /// + /// The length in bytes of the MTU (Maximum Transmission Unit) the Sender used for the datagram. + /// + /// length in bytes of the MTU (Maximum Transmission Unit) the Sender used for the datagram. + public int MtuLength() + { + return LogBufferDescriptor.MtuLength(_logBuffers.MetaDataBuffer()); + } + /// /// The initial term at which the stream started for this session. /// @@ -112,9 +122,9 @@ public Image(Subscription subscription, int sessionId, IPosition subscriberPosit public int InitialTermId => _initialTermId; /// - /// The correlationId for identification of the image with the media driver. + /// The originalRegistrationId for identification of the image with the media driver. /// - /// the correlationId for identification of the image with the media driver. + /// the originalRegistrationId for identification of the image with the media driver. public long CorrelationId { get; } /// @@ -133,9 +143,9 @@ public Image(Subscription subscription, int sessionId, IPosition subscriberPosit /// Get the position the subscriber joined this stream at. /// /// the position the subscriber joined this stream at. - public long JoiningPosition() + public long JoinPosition() { - return _joiningPosition; + return _joinPosition; } /// @@ -147,7 +157,7 @@ public long Position() { if (_isClosed) { - return 0; + return _finalPosition; } return _subscriberPosition.Get(); @@ -170,6 +180,20 @@ public void Position(long newPosition) _subscriberPosition.SetOrdered(newPosition); } + /// + /// Is the current consumed position at the end of the stream? + /// + /// true if at the end of the stream or false if not. + public virtual bool IsEndOfStream() + { + if (_isClosed) + { + return _isEos; + } + + return _subscriberPosition.Get() >= LogBufferDescriptor.EndOfStreamPosition(_logBuffers.MetaDataBuffer()); + } + ///// ///// The to the raw log of the Image. ///// @@ -226,7 +250,7 @@ public int Poll(FragmentHandler fragmentHandler, int fragmentLimit) /// the number of fragments that have been consumed. /// /// - public int ControlledPoll(IControlledFragmentHandler fragmentHandler, int fragmentLimit) + public int ControlledPoll(ControlledFragmentHandler fragmentHandler, int fragmentLimit) { if (_isClosed) { @@ -261,7 +285,7 @@ public int ControlledPoll(IControlledFragmentHandler fragmentHandler, int fragme } _header.Offset = frameOffset; - var action = fragmentHandler.OnFragment( + var action = fragmentHandler( termBuffer, frameOffset + DataHeaderFlyweight.HEADER_LENGTH, length - DataHeaderFlyweight.HEADER_LENGTH, @@ -317,7 +341,7 @@ public int ControlledPoll(IControlledFragmentHandler fragmentHandler, int fragme /// the resulting position after the scan terminates which is a complete message. /// /// - public virtual long ControlledPeek(long initialPosition, IControlledFragmentHandler fragmentHandler, long limitPosition) + public virtual long ControlledPeek(long initialPosition, ControlledFragmentHandler fragmentHandler, long limitPosition) { if (_isClosed) { @@ -356,7 +380,7 @@ public virtual long ControlledPeek(long initialPosition, IControlledFragmentHand _header.Offset = frameOffset; - var action = fragmentHandler.OnFragment( + var action = fragmentHandler( termBuffer, frameOffset + DataHeaderFlyweight.HEADER_LENGTH, length - DataHeaderFlyweight.HEADER_LENGTH, @@ -396,7 +420,7 @@ public virtual long ControlledPeek(long initialPosition, IControlledFragmentHand /// to which block is delivered. /// up to which a block may be in length. /// the number of bytes that have been consumed. - public int BlockPoll(IBlockHandler blockHandler, int blockLengthLimit) + public int BlockPoll(BlockHandler blockHandler, int blockLengthLimit) { if (_isClosed) { @@ -417,7 +441,7 @@ public int BlockPoll(IBlockHandler blockHandler, int blockLengthLimit) { var termId = termBuffer.GetInt(termOffset + DataHeaderFlyweight.TERM_ID_FIELD_OFFSET); - blockHandler.OnBlock(termBuffer, termOffset, bytesConsumed, SessionId, termId); + blockHandler(termBuffer, termOffset, bytesConsumed, SessionId, termId); } catch (Exception t) { @@ -500,7 +524,10 @@ private void ValidatePosition(long newPosition) internal IManagedResource ManagedResource() { + _finalPosition = _subscriberPosition.Volatile; + _isEos = _finalPosition >= LogBufferDescriptor.EndOfStreamPosition(_logBuffers.MetaDataBuffer()); _isClosed = true; + return new ImageManagedResource(this); } diff --git a/src/Adaptive.Aeron/ImageControlledFragmentHandler.cs b/src/Adaptive.Aeron/ImageControlledFragmentHandler.cs index 2f9b0ba0..3a09e7d3 100644 --- a/src/Adaptive.Aeron/ImageControlledFragmentHandler.cs +++ b/src/Adaptive.Aeron/ImageControlledFragmentHandler.cs @@ -1,5 +1,6 @@ using Adaptive.Aeron.LogBuffer; using Adaptive.Agrona; +using Adaptive.Agrona.Concurrent; namespace Adaptive.Aeron { @@ -15,9 +16,9 @@ namespace Adaptive.Aeron /// /// /// - public class ImageControlledFragmentAssembler : IControlledFragmentHandler + public class ImageControlledFragmentAssembler { - private readonly IControlledFragmentHandler _delegate; + private readonly ControlledFragmentHandler _delegate; private readonly BufferBuilder _builder; /// @@ -25,7 +26,7 @@ public class ImageControlledFragmentAssembler : IControlledFragmentHandler /// /// onto which whole messages are forwarded. /// to be used for each session. - public ImageControlledFragmentAssembler(IControlledFragmentHandler @delegate, int initialBufferLength = BufferBuilder.INITIAL_CAPACITY) + public ImageControlledFragmentAssembler(ControlledFragmentHandler @delegate, int initialBufferLength = BufferBuilder.MIN_ALLOCATED_CAPACITY) { _delegate = @delegate; _builder = new BufferBuilder(initialBufferLength); @@ -35,7 +36,7 @@ public ImageControlledFragmentAssembler(IControlledFragmentHandler @delegate, in /// Get the delegate unto which assembled messages are delegated. /// /// the delegate unto which assembled messages are delegated. - public virtual IControlledFragmentHandler Delegate() + public virtual ControlledFragmentHandler Delegate() { return _delegate; } @@ -50,13 +51,13 @@ public BufferBuilder Builder() } /// - /// The implementation of that reassembles and forwards whole messages. + /// The implementation of that reassembles and forwards whole messages. /// /// containing the data. /// at which the data begins. /// of the data in bytes. /// representing the meta data for the data. - public ControlledFragmentHandlerAction OnFragment(IDirectBuffer buffer, int offset, int length, Header header) + public ControlledFragmentHandlerAction OnFragment(UnsafeBuffer buffer, int offset, int length, Header header) { byte flags = header.Flags; @@ -64,7 +65,7 @@ public ControlledFragmentHandlerAction OnFragment(IDirectBuffer buffer, int offs if ((flags & FrameDescriptor.UNFRAGMENTED) == FrameDescriptor.UNFRAGMENTED) { - action = _delegate.OnFragment(buffer, offset, length, header); + action = _delegate(buffer, offset, length, header); } else { @@ -80,7 +81,7 @@ public ControlledFragmentHandlerAction OnFragment(IDirectBuffer buffer, int offs if ((flags & FrameDescriptor.END_FRAG_FLAG) == FrameDescriptor.END_FRAG_FLAG) { int msgLength = _builder.Limit(); - action = _delegate.OnFragment(_builder.Buffer(), 0, msgLength, header); + action = _delegate(_builder.Buffer(), 0, msgLength, header); if (ControlledFragmentHandlerAction.ABORT == action) { diff --git a/src/Adaptive.Aeron/ImageFragmentAssembler.cs b/src/Adaptive.Aeron/ImageFragmentAssembler.cs index 01fededa..beda576f 100644 --- a/src/Adaptive.Aeron/ImageFragmentAssembler.cs +++ b/src/Adaptive.Aeron/ImageFragmentAssembler.cs @@ -47,7 +47,7 @@ public class ImageFragmentAssembler /// Construct an adapter to reassemble message fragments and delegate on whole messages. /// /// onto which whole messages are forwarded. - public ImageFragmentAssembler(FragmentHandler fragmentHandler) : this(fragmentHandler, BufferBuilder.INITIAL_CAPACITY) + public ImageFragmentAssembler(FragmentHandler fragmentHandler) : this(fragmentHandler, BufferBuilder.MIN_ALLOCATED_CAPACITY) { } diff --git a/src/Adaptive.Aeron/LogBuffer/BlockHandler.cs b/src/Adaptive.Aeron/LogBuffer/BlockHandler.cs new file mode 100644 index 00000000..0f536976 --- /dev/null +++ b/src/Adaptive.Aeron/LogBuffer/BlockHandler.cs @@ -0,0 +1,31 @@ +/* + * Copyright 2014 - 2017 Adaptive Financial Consulting Ltd + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0S + * + * 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. + */ + +using Adaptive.Agrona; + +namespace Adaptive.Aeron.LogBuffer +{ + /// + /// Callback for handling a block of message fragments scanned from the log. + /// + /// containing the block of message fragments. + /// at which the block begins, including any frame headers. + /// of the block in bytes, including any frame headers that is aligned up to + /// . + /// of the stream containing this block of message fragments. + /// of the stream containing this block of message fragments. + public delegate void BlockHandler(IDirectBuffer buffer, int offset, int length, int sessionId, int termId); +} \ No newline at end of file diff --git a/src/Adaptive.Aeron/LogBuffer/IControlledFragmentHandler.cs b/src/Adaptive.Aeron/LogBuffer/ControlledFragmentHandler.cs similarity index 55% rename from src/Adaptive.Aeron/LogBuffer/IControlledFragmentHandler.cs rename to src/Adaptive.Aeron/LogBuffer/ControlledFragmentHandler.cs index ac14c277..2ac9c6c5 100644 --- a/src/Adaptive.Aeron/LogBuffer/IControlledFragmentHandler.cs +++ b/src/Adaptive.Aeron/LogBuffer/ControlledFragmentHandler.cs @@ -14,8 +14,7 @@ * limitations under the License. */ -using System.Runtime.Remoting.Messaging; -using Adaptive.Agrona; +using Adaptive.Agrona.Concurrent; namespace Adaptive.Aeron.LogBuffer { @@ -23,17 +22,10 @@ namespace Adaptive.Aeron.LogBuffer /// Handler for reading data that is coming from a log buffer. The frame will either contain a whole message /// or a fragment of a message to be reassembled. Messages are fragmented if greater than the frame for MTU in length. /// - public interface IControlledFragmentHandler - { - - /// - /// Callback for handling fragments of data being read from a log. - /// - /// containing the data. - /// at which the data begins. - /// of the data in bytes. - /// representing the meta data for the data. - /// The action to be taken with regard to the stream position after the callback. - ControlledFragmentHandlerAction OnFragment(IDirectBuffer buffer, int offset, int length, Header header); - } + /// containing the data. + /// at which the data begins. + /// of the data in bytes. + /// representing the meta data for the data. + /// The action to be taken with regard to the stream position after the callback. + public delegate ControlledFragmentHandlerAction ControlledFragmentHandler(UnsafeBuffer buffer, int offset, int length, Header header); } \ No newline at end of file diff --git a/src/Adaptive.Aeron/LogBuffer/ControlledFragmentHandlerAction.cs b/src/Adaptive.Aeron/LogBuffer/ControlledFragmentHandlerAction.cs index 61ec3ba2..bce6eef0 100644 --- a/src/Adaptive.Aeron/LogBuffer/ControlledFragmentHandlerAction.cs +++ b/src/Adaptive.Aeron/LogBuffer/ControlledFragmentHandlerAction.cs @@ -14,8 +14,6 @@ * limitations under the License. */ -using Adaptive.Agrona; - namespace Adaptive.Aeron.LogBuffer { public enum ControlledFragmentHandlerAction diff --git a/src/Adaptive.Aeron/LogBuffer/ExclusiveTermAppender.cs b/src/Adaptive.Aeron/LogBuffer/ExclusiveTermAppender.cs index 602dd2bc..5df66e15 100644 --- a/src/Adaptive.Aeron/LogBuffer/ExclusiveTermAppender.cs +++ b/src/Adaptive.Aeron/LogBuffer/ExclusiveTermAppender.cs @@ -41,13 +41,8 @@ public class ExclusiveTermAppender /// The append operation tripped the end of the buffer and needs to rotate. /// public const int TRIPPED = -1; - - /// - /// The append operation went past the end of the buffer and failed. - /// - public const int FAILED = -2; - - private readonly long tailAddressOffset; + + private readonly long _tailAddressOffset; private readonly UnsafeBuffer _termBuffer; private readonly UnsafeBuffer _metaDataBuffer; @@ -63,27 +58,7 @@ public ExclusiveTermAppender(UnsafeBuffer termBuffer, UnsafeBuffer metaDataBuffe metaDataBuffer.BoundsCheck(tailCounterOffset, BitUtil.SIZE_OF_LONG); _termBuffer = termBuffer; _metaDataBuffer = metaDataBuffer; - tailAddressOffset = tailCounterOffset; // TODO divergence - } - - /// - /// Get the raw current tail value in a volatile memory ordering fashion. - /// - /// the current tail value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long RawTail() - { - return _metaDataBuffer.GetLong((int)tailAddressOffset); - } - - /// - /// Set the value for the tail counter. - /// - /// for the tail counter - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void TailTermId(int termId) - { - _metaDataBuffer.PutLong((int)tailAddressOffset, ((long)termId) << 32); + _tailAddressOffset = tailCounterOffset; // TODO divergence } /// @@ -94,8 +69,7 @@ public void TailTermId(int termId) /// for writing the default header. /// of the message to be written. /// to be updated with the claimed region. - /// the resulting offset of the term after the append on success otherwise - /// or packed with the termId if a padding record was inserted at the end. + /// the resulting offset of the term after the append on success otherwise . [MethodImpl(MethodImplOptions.AggressiveInlining)] public int Claim( int termId, @@ -108,10 +82,45 @@ public int Claim( int alignedLength = BitUtil.Align(frameLength, FrameDescriptor.FRAME_ALIGNMENT); UnsafeBuffer termBuffer = _termBuffer; int termLength = termBuffer.Capacity; + + int resultingOffset = termOffset + alignedLength; + PutRawTailOrdered(termId, resultingOffset); + + if (resultingOffset > termLength) + { + resultingOffset = HandleEndOfLogCondition(termBuffer, termOffset, header, termLength, termId); + } + else + { + header.Write(termBuffer, termOffset, frameLength, termId); + bufferClaim.Wrap(termBuffer, termOffset, frameLength); + } + + return resultingOffset; + } - PutRawTailOrdered(termId, termOffset + alignedLength); + /// + /// Pad a length of the term buffer with a padding record. + /// + /// for the current term. + /// in the term at which to append. + /// for writing the default header. + /// of the padding to be written. + /// the resulting offset of the term after success otherwise . + public int AppendPadding( + int termId, + int termOffset, + HeaderWriter header, + int length) + { + int frameLength = length + DataHeaderFlyweight.HEADER_LENGTH; + int alignedLength = BitUtil.Align(frameLength, FrameDescriptor.FRAME_ALIGNMENT); + UnsafeBuffer termBuffer = _termBuffer; + int termLength = termBuffer.Capacity; int resultingOffset = termOffset + alignedLength; + PutRawTailOrdered(termId, resultingOffset); + if (resultingOffset > termLength) { resultingOffset = HandleEndOfLogCondition(termBuffer, termOffset, header, termLength, termId); @@ -119,7 +128,8 @@ public int Claim( else { header.Write(termBuffer, termOffset, frameLength, termId); - bufferClaim.Wrap(termBuffer, termOffset, frameLength); + FrameDescriptor.FrameType(termBuffer, termOffset, FrameDescriptor.PADDING_FRAME_TYPE); + FrameDescriptor.FrameLengthOrdered(termBuffer, termOffset, frameLength); } return resultingOffset; @@ -135,8 +145,7 @@ public int Claim( /// at which the message begins. /// of the message in the source buffer. /// for the frame - /// the resulting offset of the term after the append on success otherwise or - /// packed with the termId if a padding record was inserted at the end. + /// the resulting offset of the term after the append on success otherwise . [MethodImpl(MethodImplOptions.AggressiveInlining)] #if DEBUG public virtual int AppendUnfragmentedMessage( @@ -163,10 +172,10 @@ public int AppendUnfragmentedMessage( UnsafeBuffer termBuffer = _termBuffer; int termLength = termBuffer.Capacity; - - PutRawTailOrdered(termId, termOffset + alignedLength); int resultingOffset = termOffset + alignedLength; + PutRawTailOrdered(termId, resultingOffset); + if (resultingOffset > termLength) { resultingOffset = HandleEndOfLogCondition(termBuffer, termOffset, header, termLength, termId); @@ -201,8 +210,7 @@ public int AppendUnfragmentedMessage( /// of the message in the source buffer. /// that the message will be fragmented into. /// /// for the frame - /// the resulting offset of the term after the append on success otherwise - /// or packed with the termId if a padding record was inserted at the end. + /// the resulting offset of the term after the append on success otherwise . [MethodImpl(MethodImplOptions.AggressiveInlining)] public int AppendFragmentedMessage( int termId, @@ -223,10 +231,10 @@ public int AppendFragmentedMessage( lastFrameLength; UnsafeBuffer termBuffer = _termBuffer; int termLength = termBuffer.Capacity; - - PutRawTailOrdered(termId, termOffset + requiredLength); int resultingOffset = termOffset + requiredLength; + PutRawTailOrdered(termId, resultingOffset); + if (resultingOffset > termLength) { resultingOffset = HandleEndOfLogCondition(termBuffer, termOffset, header, termLength, termId); @@ -293,7 +301,7 @@ private int HandleEndOfLogCondition( [MethodImpl(MethodImplOptions.AggressiveInlining)] private void PutRawTailOrdered(int termId, int termOffset) { - _metaDataBuffer.PutLongOrdered((int)tailAddressOffset, LogBufferDescriptor.PackTail(termId, termOffset)); + _metaDataBuffer.PutLongOrdered((int)_tailAddressOffset, LogBufferDescriptor.PackTail(termId, termOffset)); } } } \ No newline at end of file diff --git a/src/Adaptive.Aeron/LogBuffer/FragmentHandler.cs b/src/Adaptive.Aeron/LogBuffer/FragmentHandler.cs index a6028183..011ecfd5 100644 --- a/src/Adaptive.Aeron/LogBuffer/FragmentHandler.cs +++ b/src/Adaptive.Aeron/LogBuffer/FragmentHandler.cs @@ -14,7 +14,6 @@ * limitations under the License. */ -using Adaptive.Agrona; using Adaptive.Agrona.Concurrent; namespace Adaptive.Aeron.LogBuffer diff --git a/src/Adaptive.Aeron/LogBuffer/FrameDescriptor.cs b/src/Adaptive.Aeron/LogBuffer/FrameDescriptor.cs index 6c6da416..f242bd4e 100644 --- a/src/Adaptive.Aeron/LogBuffer/FrameDescriptor.cs +++ b/src/Adaptive.Aeron/LogBuffer/FrameDescriptor.cs @@ -26,7 +26,6 @@ namespace Adaptive.Aeron.LogBuffer /// All messages are logged in frames that have a minimum header layout as follows plus a reserve then /// the encoded message follows: /// - ///
     ///   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
     ///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
@@ -42,10 +41,9 @@ namespace Adaptive.Aeron.LogBuffer
     ///  |                        Encoded Message                       ...
     /// ...                                                              |
     ///  +---------------------------------------------------------------+
-    /// 
/// /// The (B)egin and (E)nd flags are used for message fragmentation. R is for reserved bit. - /// Both are set for a message that does not span frames. + /// Both (B)egin and (E)nd flags are set for a message that does not span frames. ///
public class FrameDescriptor { @@ -100,24 +98,24 @@ public class FrameDescriptor public const int PADDING_FRAME_TYPE = HeaderFlyweight.HDR_TYPE_PAD; /// - /// Compute the maximum supported message length for a buffer of given capacity. + /// Compute the maximum supported message length for a buffer of given termLength. /// - /// of the log buffer. + /// of the log buffer. /// the maximum supported length for a message. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int ComputeMaxMessageLength(int capacity) + public static int ComputeMaxMessageLength(int termLength) { - return capacity/8; + return termLength / 8; } /// - /// Compute the maximum supported message length for a buffer of given capacity when the publication is exclusive. + /// Compute the maximum supported message length for a buffer of given termLength when the publication is exclusive. /// - /// of the log buffer. + /// of the log buffer. /// the maximum supported length for a message. - public static int ComputeExclusiveMaxMessageLength(int capacity) + public static int ComputeExclusiveMaxMessageLength(int termLength) { - return capacity / 4; + return termLength / 4; } /// @@ -198,6 +196,18 @@ public static int FrameVersion(IAtomicBuffer buffer, int termOffset) return buffer.GetByte(VersionOffset(termOffset)); } + /// + /// Get the flags field for a frame. + /// + /// containing the frame. + /// at which a frame begins. + /// the value of the frame type header. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int FrameFlags(IAtomicBuffer buffer, int termOffset) + { + return buffer.GetByte(FlagsOffset(termOffset)); + } + /// /// Read the type of of the frame from header. /// diff --git a/src/Adaptive.Aeron/LogBuffer/HeaderWriter.cs b/src/Adaptive.Aeron/LogBuffer/HeaderWriter.cs index 3060354a..56646e47 100644 --- a/src/Adaptive.Aeron/LogBuffer/HeaderWriter.cs +++ b/src/Adaptive.Aeron/LogBuffer/HeaderWriter.cs @@ -15,7 +15,6 @@ */ using System.Runtime.CompilerServices; -using System.Threading; using Adaptive.Aeron.Protocol; using Adaptive.Agrona; using Adaptive.Agrona.Concurrent; diff --git a/src/Adaptive.Aeron/LogBuffer/IBlockHandler.cs b/src/Adaptive.Aeron/LogBuffer/IBlockHandler.cs deleted file mode 100644 index 3e83c0f2..00000000 --- a/src/Adaptive.Aeron/LogBuffer/IBlockHandler.cs +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2014 - 2017 Adaptive Financial Consulting Ltd - * - * 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 - * - * http://www.apache.org/licenses/LICENSE-2.0S - * - * 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. - */ - -using Adaptive.Agrona; - -namespace Adaptive.Aeron.LogBuffer -{ - /// - /// Function for handling a block of message fragments scanned from the log. - /// - public interface IBlockHandler - { - /// - /// Callback for handling a block of messages being read from a log. - /// - /// containing the block of message fragments. - /// at which the block begins, including any frame headers. - /// of the block in bytes, including any frame headers that is aligned up to - /// . - /// of the stream containing this block of message fragments. - /// of the stream containing this block of message fragments. - void OnBlock(IDirectBuffer buffer, int offset, int length, int sessionId, int termId); - } -} \ No newline at end of file diff --git a/src/Adaptive.Aeron/LogBuffer/LogBufferDescriptor.cs b/src/Adaptive.Aeron/LogBuffer/LogBufferDescriptor.cs index 31c90568..6120f848 100644 --- a/src/Adaptive.Aeron/LogBuffer/LogBufferDescriptor.cs +++ b/src/Adaptive.Aeron/LogBuffer/LogBufferDescriptor.cs @@ -56,6 +56,11 @@ public class LogBufferDescriptor /// public const int TERM_MIN_LENGTH = 64 * 1024; + /// + /// Maximum buffer length for a log term + /// + public const int TERM_MAX_LENGTH = 1024 * 1024 * 1024; + static LogBufferDescriptor() { var offset = 0; @@ -66,8 +71,9 @@ static LogBufferDescriptor() offset = (BitUtil.CACHE_LINE_LENGTH * 2); LOG_TIME_OF_LAST_SM_OFFSET = offset; + LOG_END_OF_STREAM_POSITION_OFFSET = LOG_TIME_OF_LAST_SM_OFFSET + BitUtil.SIZE_OF_LONG; - offset += (BitUtil.CACHE_LINE_LENGTH*2); + offset += (BitUtil.CACHE_LINE_LENGTH * 2); LOG_CORRELATION_ID_OFFSET = offset; LOG_INITIAL_TERM_ID_OFFSET = LOG_CORRELATION_ID_OFFSET + BitUtil.SIZE_OF_LONG; LOG_DEFAULT_FRAME_HEADER_LENGTH_OFFSET = LOG_INITIAL_TERM_ID_OFFSET + BitUtil.SIZE_OF_INT; @@ -98,6 +104,11 @@ static LogBufferDescriptor() /// public static readonly int LOG_TIME_OF_LAST_SM_OFFSET; + /// + /// Offset within the log meta data where the position of the End of Stream is stored. + /// + public static readonly int LOG_END_OF_STREAM_POSITION_OFFSET; + /// /// Offset within the log meta data where the active term id is stored. /// @@ -126,7 +137,7 @@ static LogBufferDescriptor() /// /// Maximum length of a frame header /// - public static readonly int LOG_DEFAULT_FRAME_HEADER_MAX_LENGTH = BitUtil.CACHE_LINE_LENGTH*2; + public static readonly int LOG_DEFAULT_FRAME_HEADER_MAX_LENGTH = BitUtil.CACHE_LINE_LENGTH * 2; /// @@ -156,6 +167,9 @@ static LogBufferDescriptor() /// | Time of Last Status Message | /// | | /// +---------------------------------------------------------------+ + /// | End of Stream Position | + /// | | + /// +---------------------------------------------------------------+ /// | Cache Line Padding ... /// ... | /// +---------------------------------------------------------------+ @@ -188,15 +202,19 @@ public static void CheckTermLength(int termLength) { if (termLength < TERM_MIN_LENGTH) { - string s = $"Term length less than min length of {TERM_MIN_LENGTH:D}, length={termLength:D}"; - ThrowHelper.ThrowInvalidOperationException(s); - return; + ThrowHelper.ThrowInvalidOperationException( + $"Term length less than min length of {TERM_MIN_LENGTH:D}, length={termLength:D}"); + } + + if (termLength > TERM_MAX_LENGTH) + { + ThrowHelper.ThrowInvalidOperationException( + $"Term length more than max length of {TERM_MAX_LENGTH:D}: length = {termLength:D}"); } - if ((termLength & (FrameDescriptor.FRAME_ALIGNMENT - 1)) != 0) + if (!BitUtil.IsPowerOfTwo(termLength)) { - string s = $"Term length not a multiple of {FrameDescriptor.FRAME_ALIGNMENT:D}, length={termLength:D}"; - ThrowHelper.ThrowInvalidOperationException(s); + ThrowHelper.ThrowInvalidOperationException("Term length not a power of 2: length=" + termLength); } } @@ -289,6 +307,27 @@ public static void TimeOfLastStatusMessage(UnsafeBuffer logMetaDataBuffer, long logMetaDataBuffer.PutLongOrdered(LOG_TIME_OF_LAST_SM_OFFSET, timeInMillis); } + /// + /// Get the value of the end of stream position. + /// + /// containing the meta data. + /// the value of end of stream position + public static long EndOfStreamPosition(UnsafeBuffer logMetaDataBuffer) + { + return logMetaDataBuffer.GetLongVolatile(LOG_END_OF_STREAM_POSITION_OFFSET); + } + + /// + /// Set the value of the end of stream position. + /// + /// containing the meta data. + /// value of the end of stream position + public static void EndOfStreamPosition(UnsafeBuffer logMetaDataBuffer, long position) + { + logMetaDataBuffer.PutLongOrdered(LOG_END_OF_STREAM_POSITION_OFFSET, position); + } + + /// /// Get the value of the active partition index used by the producer of this log. Consumers may have a different active /// index if they are running behind. The read is done with volatile semantics. @@ -321,7 +360,7 @@ public static void ActivePartitionIndex(UnsafeBuffer logMetaDataBuffer, int acti { logMetaDataBuffer.PutInt(LOG_ACTIVE_PARTITION_INDEX_OFFSET, activePartitionIndex); } - + /// /// Rotate to the next partition in sequence for the term id. /// @@ -330,7 +369,7 @@ public static void ActivePartitionIndex(UnsafeBuffer logMetaDataBuffer, int acti [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int NextPartitionIndex(int currentIndex) { - return (currentIndex + 1)%PARTITION_COUNT; + return (currentIndex + 1) % PARTITION_COUNT; } /// @@ -342,7 +381,7 @@ public static int NextPartitionIndex(int currentIndex) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int IndexByTerm(int initialTermId, int activeTermId) { - return (activeTermId - initialTermId)%PARTITION_COUNT; + return (activeTermId - initialTermId) % PARTITION_COUNT; } /// @@ -353,7 +392,7 @@ public static int IndexByTerm(int initialTermId, int activeTermId) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int IndexByTermCount(long termCount) { - return (int)(termCount%PARTITION_COUNT); + return (int) (termCount % PARTITION_COUNT); } /// @@ -365,7 +404,7 @@ public static int IndexByTermCount(long termCount) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int IndexByPosition(long position, int positionBitsToShift) { - return (int) (((long) ((ulong) position >> positionBitsToShift))%PARTITION_COUNT); + return (int) (((long) ((ulong) position >> positionBitsToShift)) % PARTITION_COUNT); } /// @@ -434,7 +473,7 @@ public static int ComputeTermOffsetFromPosition(long position, int positionBitsT [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long ComputeLogLength(int termLength) { - return (termLength * PARTITION_COUNT) + LOG_META_DATA_LENGTH; + return ((long)termLength * PARTITION_COUNT) + LOG_META_DATA_LENGTH; } /// @@ -445,7 +484,7 @@ public static long ComputeLogLength(int termLength) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int ComputeTermLength(long logLength) { - return (int)((logLength - LOG_META_DATA_LENGTH) / PARTITION_COUNT); + return (int) ((logLength - LOG_META_DATA_LENGTH) / PARTITION_COUNT); } /// @@ -460,12 +499,13 @@ public static void StoreDefaultFrameHeader(UnsafeBuffer logMetaDataBuffer, IDire if (defaultHeader.Capacity != DataHeaderFlyweight.HEADER_LENGTH) { ThrowHelper.ThrowArgumentException( - $"Default header of {defaultHeader.Capacity:D} not equal to {DataHeaderFlyweight.HEADER_LENGTH:D}"); + $"Default header capacity not equal to HEADER_LENGTH: length={defaultHeader.Capacity:D}"); return; } logMetaDataBuffer.PutInt(LOG_DEFAULT_FRAME_HEADER_LENGTH_OFFSET, DataHeaderFlyweight.HEADER_LENGTH); - logMetaDataBuffer.PutBytes(LOG_DEFAULT_FRAME_HEADER_OFFSET, defaultHeader, 0, DataHeaderFlyweight.HEADER_LENGTH); + logMetaDataBuffer.PutBytes(LOG_DEFAULT_FRAME_HEADER_OFFSET, defaultHeader, 0, + DataHeaderFlyweight.HEADER_LENGTH); } /// @@ -476,7 +516,8 @@ public static void StoreDefaultFrameHeader(UnsafeBuffer logMetaDataBuffer, IDire [MethodImpl(MethodImplOptions.AggressiveInlining)] public static UnsafeBuffer DefaultFrameHeader(UnsafeBuffer logMetaDataBuffer) { - return new UnsafeBuffer(logMetaDataBuffer, LOG_DEFAULT_FRAME_HEADER_OFFSET, DataHeaderFlyweight.HEADER_LENGTH); + return new UnsafeBuffer(logMetaDataBuffer, LOG_DEFAULT_FRAME_HEADER_OFFSET, + DataHeaderFlyweight.HEADER_LENGTH); } /// @@ -488,16 +529,16 @@ public static UnsafeBuffer DefaultFrameHeader(UnsafeBuffer logMetaDataBuffer) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ApplyDefaultHeader(UnsafeBuffer logMetaDataBuffer, UnsafeBuffer termBuffer, int termOffset) { - termBuffer.PutBytes(termOffset, logMetaDataBuffer, LOG_DEFAULT_FRAME_HEADER_OFFSET, DataHeaderFlyweight.HEADER_LENGTH); + termBuffer.PutBytes(termOffset, logMetaDataBuffer, LOG_DEFAULT_FRAME_HEADER_OFFSET, + DataHeaderFlyweight.HEADER_LENGTH); } /// /// Rotate the log and update the default headers for the new term. /// - /// for the partitions of the log. /// for the meta data. - /// current active index. - /// to be used in the default headers. + /// current active index. + /// to be used in the default headers. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void RotateLog(UnsafeBuffer logMetaDataBuffer, int activePartitionIndex, int termId) { @@ -509,12 +550,13 @@ public static void RotateLog(UnsafeBuffer logMetaDataBuffer, int activePartition /// /// Set the initial value for the termId in the upper bits of the tail counter. /// - /// contain the tail counter. - /// to be set. + /// contain the tail counter. + /// to be intialized. + /// to be set. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void InitialiseTailWithTermId(UnsafeBuffer logMetaData, int partitionIndex, int termId) { - logMetaData.PutLong(TERM_TAIL_COUNTERS_OFFSET + partitionIndex * BitUtil.SIZE_OF_LONG, (long)termId << 32); + logMetaData.PutLong(TERM_TAIL_COUNTERS_OFFSET + partitionIndex * BitUtil.SIZE_OF_LONG, PackTail(termId, 0)); } /// @@ -525,7 +567,7 @@ public static void InitialiseTailWithTermId(UnsafeBuffer logMetaData, int partit [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int TermId(long rawTail) { - return (int) ((long) ((ulong) rawTail >> 32)); + return (int) (rawTail >> 32); } /// @@ -533,7 +575,7 @@ public static int TermId(long rawTail) /// /// containing the termOffset. /// that the offset cannot exceed. - /// the termOffset value. + /// the termOffset value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int TermOffset(long rawTail, long termLength) { @@ -542,15 +584,27 @@ public static int TermOffset(long rawTail, long termLength) return (int) Math.Min(tail, termLength); } + /// + /// The termOffset as a result of the append + /// + /// into which the termOffset value has been packed. + /// the termOffset after the append + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int TermOffset(long result) + { + return (int) result; + } + /// /// Pack a termId and termOffset into a raw tail value. /// /// to be packed. /// to be packed. /// the packed value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long PackTail(int termId, int termOffset) { - return (((long)termId) << 32) + termOffset; + return ((long) termId << 32) | (termOffset & 0xFFFFFFFFL); } /// @@ -564,6 +618,18 @@ public static void RawTail(UnsafeBuffer logMetaDataBuffer, int partitionIndex, l logMetaDataBuffer.PutLong(TERM_TAIL_COUNTERS_OFFSET + (BitUtil.SIZE_OF_LONG * partitionIndex), rawTail); } + /// + /// Get the raw value of the tail for the given partition. + /// + /// containing the tail counters. + /// for the tail counter. + /// the raw value of the tail for the current active partition. + public static long RawTail(UnsafeBuffer logMetaDataBuffer, int partitionIndex) + { + return logMetaDataBuffer.GetLong(TERM_TAIL_COUNTERS_OFFSET + (BitUtil.SIZE_OF_LONG * partitionIndex)); + } + + /// /// Set the raw value of the tail for the given partition. /// @@ -572,9 +638,10 @@ public static void RawTail(UnsafeBuffer logMetaDataBuffer, int partitionIndex, l /// to be stored public static void RawTailVolatile(UnsafeBuffer logMetaDataBuffer, int partitionIndex, long rawTail) { - logMetaDataBuffer.PutLongVolatile(TERM_TAIL_COUNTERS_OFFSET + (BitUtil.SIZE_OF_LONG * partitionIndex), rawTail); + logMetaDataBuffer.PutLongVolatile(TERM_TAIL_COUNTERS_OFFSET + (BitUtil.SIZE_OF_LONG * partitionIndex), + rawTail); } - + /// /// Get the raw value of the tail for the given partition. /// @@ -584,7 +651,7 @@ public static void RawTailVolatile(UnsafeBuffer logMetaDataBuffer, int partition [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long RawTailVolatile(UnsafeBuffer logMetaDataBuffer, int partitionIndex) { - return logMetaDataBuffer.GetLongVolatile(TERM_TAIL_COUNTERS_OFFSET + BitUtil.SIZE_OF_LONG*partitionIndex); + return logMetaDataBuffer.GetLongVolatile(TERM_TAIL_COUNTERS_OFFSET + BitUtil.SIZE_OF_LONG * partitionIndex); } /// diff --git a/src/Adaptive.Aeron/LogBuffer/TermAppender.cs b/src/Adaptive.Aeron/LogBuffer/TermAppender.cs index 2c5dabf1..78d04821 100644 --- a/src/Adaptive.Aeron/LogBuffer/TermAppender.cs +++ b/src/Adaptive.Aeron/LogBuffer/TermAppender.cs @@ -47,7 +47,7 @@ public class TermAppender /// public const int FAILED = -2; - private readonly long tailAddressOffset; + private readonly long _tailAddressOffset; private readonly UnsafeBuffer _termBuffer; private readonly UnsafeBuffer _metaDataBuffer; @@ -63,7 +63,7 @@ public TermAppender(UnsafeBuffer termBuffer, UnsafeBuffer metaDataBuffer, int pa metaDataBuffer.BoundsCheck(tailCounterOffset, BitUtil.SIZE_OF_LONG); _termBuffer = termBuffer; _metaDataBuffer = metaDataBuffer; - tailAddressOffset = tailCounterOffset; // TODO divergence + _tailAddressOffset = tailCounterOffset; // TODO divergence } /// @@ -73,17 +73,7 @@ public TermAppender(UnsafeBuffer termBuffer, UnsafeBuffer metaDataBuffer, int pa [MethodImpl(MethodImplOptions.AggressiveInlining)] public long RawTailVolatile() { - return _metaDataBuffer.GetLongVolatile((int)tailAddressOffset); - } - - /// - /// Set the value for the tail counter. - /// - /// for the tail counter - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void TailTermId(int termId) - { - _metaDataBuffer.PutLong((int)tailAddressOffset, ((long) termId) << 32); + return _metaDataBuffer.GetLongVolatile((int)_tailAddressOffset); } /// @@ -108,12 +98,12 @@ public long Claim(HeaderWriter header, int length, BufferClaim bufferClaim) long resultingOffset = termOffset + alignedLength; if (resultingOffset > termLength) { - resultingOffset = HandleEndOfLogCondition(termBuffer, termOffset, header, termLength, TermId(rawTail)); + resultingOffset = HandleEndOfLogCondition(termBuffer, termOffset, header, termLength, LogBufferDescriptor.TermId(rawTail)); } else { int offset = (int) termOffset; - header.Write(termBuffer, offset, frameLength, TermId(rawTail)); + header.Write(termBuffer, offset, frameLength, LogBufferDescriptor.TermId(rawTail)); bufferClaim.Wrap(termBuffer, offset, frameLength); } @@ -148,12 +138,12 @@ public long AppendUnfragmentedMessage(HeaderWriter header, UnsafeBuffer srcBuffe long resultingOffset = termOffset + alignedLength; if (resultingOffset > termLength) { - resultingOffset = HandleEndOfLogCondition(termBuffer, termOffset, header, termLength, TermId(rawTail)); + resultingOffset = HandleEndOfLogCondition(termBuffer, termOffset, header, termLength, LogBufferDescriptor.TermId(rawTail)); } else { int offset = (int) termOffset; - header.Write(termBuffer, offset, frameLength, TermId(rawTail)); + header.Write(termBuffer, offset, frameLength, LogBufferDescriptor.TermId(rawTail)); termBuffer.PutBytes(offset + DataHeaderFlyweight.HEADER_LENGTH, srcBuffer, srcOffset, length); if (null != reservedValueSupplier) @@ -193,7 +183,7 @@ public long AppendFragmentedMessage(HeaderWriter header, UnsafeBuffer srcBuffer, int requiredLength = (numMaxPayloads*(maxPayloadLength + DataHeaderFlyweight.HEADER_LENGTH)) + lastFrameLength; long rawTail = GetAndAddRawTail(requiredLength); - int termId = TermId(rawTail); + int termId = LogBufferDescriptor.TermId(rawTail); long termOffset = rawTail & 0xFFFFFFFFL; UnsafeBuffer termBuffer = _termBuffer; @@ -242,42 +232,7 @@ public long AppendFragmentedMessage(HeaderWriter header, UnsafeBuffer srcBuffer, return resultingOffset; } - - - /// - /// Pack the values for termOffset and termId into a long for returning on the stack. - /// - /// value to be packed. - /// value to be packed. - /// a long with both ints packed into it. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long Pack(int termId, int termOffset) - { - return ((long) termId << 32) | (termOffset & 0xFFFFFFFFL); - } - - /// - /// The termOffset as a result of the append - /// - /// into which the termOffset value has been packed. - /// the termOffset after the append - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int TermOffset(long result) - { - return (int) result; - } - - /// - /// The termId in which the append operation took place. - /// - /// into which the termId value has been packed. - /// the termId in which the append operation took place. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int TermId(long result) - { - return (int)((long)((ulong)result >> 32)); - } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] private long HandleEndOfLogCondition(UnsafeBuffer termBuffer, long termOffset, HeaderWriter header, int termLength, int termId) @@ -298,13 +253,13 @@ private long HandleEndOfLogCondition(UnsafeBuffer termBuffer, long termOffset, H } } - return Pack(termId, resultingOffset); + return LogBufferDescriptor.PackTail(termId, resultingOffset); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private long GetAndAddRawTail(int alignedLength) { - return _metaDataBuffer.GetAndAddLong((int)tailAddressOffset, alignedLength); + return _metaDataBuffer.GetAndAddLong((int)_tailAddressOffset, alignedLength); } } } \ No newline at end of file diff --git a/src/Adaptive.Aeron/LogBuffer/TermReader.cs b/src/Adaptive.Aeron/LogBuffer/TermReader.cs index 88b6b95f..c0d1ab2a 100644 --- a/src/Adaptive.Aeron/LogBuffer/TermReader.cs +++ b/src/Adaptive.Aeron/LogBuffer/TermReader.cs @@ -25,9 +25,8 @@ namespace Adaptive.Aeron.LogBuffer { /// /// A term buffer reader. - /// + /// /// Note: Reading from the term is thread safe, but each thread needs its own instance of this class. - /// /// public class TermReader { diff --git a/src/Adaptive.Aeron/Protocol/DataHeaderFlyweight.cs b/src/Adaptive.Aeron/Protocol/DataHeaderFlyweight.cs index cfa7d5c1..529e22c5 100644 --- a/src/Adaptive.Aeron/Protocol/DataHeaderFlyweight.cs +++ b/src/Adaptive.Aeron/Protocol/DataHeaderFlyweight.cs @@ -16,6 +16,8 @@ using System; using System.Text; +using Adaptive.Aeron.LogBuffer; +using Adaptive.Agrona; using Adaptive.Agrona.Concurrent; namespace Adaptive.Aeron.Protocol @@ -47,6 +49,16 @@ public class DataHeaderFlyweight : HeaderFlyweight /// public static readonly short BEGIN_AND_END_FLAGS = BEGIN_FLAG | END_FLAG; + /// + /// End of Stream Flag + /// + public const short EOS_FLAG = 0x20; + + /// + /// Begin, End, and End of Stream Flags + /// + public static readonly short BEGIN_END_AND_EOS_FLAGS = BEGIN_FLAG | END_FLAG | EOS_FLAG; + public const long DEFAULT_RESERVE_VALUE = 0L; public const int TERM_OFFSET_FIELD_OFFSET = 8; @@ -187,7 +199,7 @@ public int DataOffset() /// byte array containing the header public static UnsafeBuffer CreateDefaultHeader(int sessionId, int streamId, int termId) { - var buffer = new UnsafeBuffer(new byte[HEADER_LENGTH]); + var buffer = new UnsafeBuffer(BufferUtil.AllocateDirectAligned(HEADER_LENGTH, FrameDescriptor.FRAME_ALIGNMENT)); buffer.PutByte(VERSION_FIELD_OFFSET, CURRENT_VERSION); buffer.PutByte(FLAGS_FIELD_OFFSET, (byte)BEGIN_AND_END_FLAGS); diff --git a/src/Adaptive.Aeron/Protocol/HeaderFlyweight.cs b/src/Adaptive.Aeron/Protocol/HeaderFlyweight.cs index 586ef767..8a8bd058 100644 --- a/src/Adaptive.Aeron/Protocol/HeaderFlyweight.cs +++ b/src/Adaptive.Aeron/Protocol/HeaderFlyweight.cs @@ -38,39 +38,48 @@ public class HeaderFlyweight : UnsafeBuffer public static readonly byte[] EMPTY_BUFFER = new byte[0]; /// - /// header type PAD + /// header type PAD + /// public const int HDR_TYPE_PAD = 0x00; /// - /// header type DATA + /// header type DATA + /// public const int HDR_TYPE_DATA = 0x01; /// - /// header type NAK + /// header type NAK + /// public const int HDR_TYPE_NAK = 0x02; /// - /// header type SM + /// header type SM + /// public const int HDR_TYPE_SM = 0x03; /// - /// header type ERR + /// header type ERR + /// public const int HDR_TYPE_ERR = 0x04; /// - /// header type SETUP + /// header type SETUP + /// public const int HDR_TYPE_SETUP = 0x05; /// - /// header type RTT Measurement + /// header type RTT Measurement + /// public const int HDR_TYPE_RTTM = 0x06; /// - /// header type EXT + /// header type EXT + /// public const int HDR_TYPE_EXT = 0xFFFF; /// - /// default version + /// default version + /// public const byte CURRENT_VERSION = 0x0; public const int FRAME_LENGTH_FIELD_OFFSET = 0; @@ -79,11 +88,11 @@ public class HeaderFlyweight : UnsafeBuffer public const int TYPE_FIELD_OFFSET = 6; public static readonly int HEADER_LENGTH = TYPE_FIELD_OFFSET + BitUtil.SIZE_OF_SHORT; - public HeaderFlyweight() : base(EMPTY_BUFFER) + public HeaderFlyweight() { } - public HeaderFlyweight(IDirectBuffer buffer) : base(buffer) + public HeaderFlyweight(UnsafeBuffer buffer) : base(buffer) { } diff --git a/src/Adaptive.Aeron/Protocol/RttMeasurementFlyweight.cs b/src/Adaptive.Aeron/Protocol/RttMeasurementFlyweight.cs index 1f4569df..ea6888a9 100644 --- a/src/Adaptive.Aeron/Protocol/RttMeasurementFlyweight.cs +++ b/src/Adaptive.Aeron/Protocol/RttMeasurementFlyweight.cs @@ -141,6 +141,5 @@ public override string ToString() return sb.ToString(); } - -} + } } \ No newline at end of file diff --git a/src/Adaptive.Aeron/Publication.cs b/src/Adaptive.Aeron/Publication.cs index 3ab0b819..06c362e0 100644 --- a/src/Adaptive.Aeron/Publication.cs +++ b/src/Adaptive.Aeron/Publication.cs @@ -26,12 +26,12 @@ namespace Adaptive.Aeron { /// - /// Aeron Publisher API for sending messages to subscribers of a given channel and streamId pair. Publishers + /// Aeron publishing API for sending messages to subscribers of a given channel and streamId pair. s /// are created via the method, and messages are sent via one of the - /// methods, or a and + /// methods, or a and /// method combination. /// - /// The APIs used to send are all non-blocking. + /// The APIs used to send are all non-blocking and thread safe. /// /// Note: Publication instances are threadsafe and can be shared between publishing threads. /// @@ -51,6 +51,7 @@ public class Publication : IDisposable /// /// The offer failed due to an administration action and should be retried. + /// The action is an operation such as log rotation which is likely to have succeeded by the next retry attempt. /// public const long ADMIN_ACTION = -3; @@ -59,6 +60,18 @@ public class Publication : IDisposable /// public const long CLOSED = -4; + /// + /// The offer failed due to reaching the maximum position of the stream given term buffer length times the total + /// possible number of terms. + /// + /// If this happen then the publication should be closed and a new one added. To make it less likely to happen then + /// increase the term buffer length. + /// + /// + public const long MAX_POSITION_EXCEEDED = -5; + + private readonly long _originalRegistrationId; + private readonly long _maxPossiblePosition; private int _refCount; private readonly int _positionBitsToShift; private volatile bool _isClosed; @@ -70,7 +83,15 @@ public class Publication : IDisposable private readonly LogBuffers _logBuffers; private readonly ClientConductor _conductor; - internal Publication(ClientConductor clientConductor, string channel, int streamId, int sessionId, IReadablePosition positionLimit, LogBuffers logBuffers, long registrationId) + internal Publication( + ClientConductor clientConductor, + string channel, + int streamId, + int sessionId, + IReadablePosition positionLimit, + LogBuffers logBuffers, + long originalRegistrationId, + long registrationId) { var buffers = logBuffers.TermBuffers(); var logMetaDataBuffer = logBuffers.MetaDataBuffer(); @@ -83,12 +104,14 @@ internal Publication(ClientConductor clientConductor, string channel, int stream var termLength = logBuffers.TermLength(); MaxPayloadLength = LogBufferDescriptor.MtuLength(logMetaDataBuffer) - DataHeaderFlyweight.HEADER_LENGTH; MaxMessageLength = FrameDescriptor.ComputeMaxMessageLength(termLength); + _maxPossiblePosition = termLength * (1L << 31); _conductor = clientConductor; Channel = channel; StreamId = streamId; SessionId = sessionId; InitialTermId = LogBufferDescriptor.InitialTermId(logMetaDataBuffer); _logMetaDataBuffer = logMetaDataBuffer; + _originalRegistrationId = originalRegistrationId; RegistrationId = registrationId; _positionLimit = positionLimit; _logBuffers = logBuffers; @@ -101,21 +124,31 @@ internal Publication(ClientConductor clientConductor, string channel, int stream /// /// the length in bytes for each term partition in the log buffer. public int TermBufferLength => _logBuffers.TermLength(); + + /// + /// The maximum possible position this stream can reach due to its term buffer length. + /// + /// Maximum possible position is term-length times 2^31 in bytes. + /// + /// + /// the maximum possible position this stream can reach due to it term buffer length. + public long MaxPossiblePosition() + { + return _maxPossiblePosition; + } /// /// Media address for delivery to the channel. /// /// Media address for delivery to the channel. public string Channel { get; } - - + /// /// Stream identity for scoping within the channel media address. /// /// Stream identity for scoping within the channel media address. public int StreamId { get; } - - + /// /// Session under which messages are published. Identifies this Publication instance. /// @@ -146,7 +179,29 @@ internal Publication(ClientConductor clientConductor, string channel, int stream public int MaxPayloadLength { get; } /// - /// Return the registration id used to register this Publication with the media driver. + /// Get the original registration used to register this Publication with the media driver by the first publisher. + /// + /// original registration id + public long OriginalRegistrationId() + { + return _originalRegistrationId; + } + + /// + /// Is this Publication the original instance added to the driver? If not then it was added after another client + /// has already added the publication. + /// + /// true if this instance is the first added otherwise false. + public bool IsOriginal() + { + return _originalRegistrationId == RegistrationId; + } + + /// + /// Get the registration id used to register this Publication with the media driver. + /// + /// If this value is different from the then another client has previously added + /// this Publication. In the case of an exclusive publication this should never happen. /// /// registration id public long RegistrationId { get; } @@ -243,7 +298,7 @@ public long PositionLimit /// /// containing message. /// The new stream position, otherwise , , - /// or . + /// , or . [MethodImpl(MethodImplOptions.AggressiveInlining)] public long Offer(UnsafeBuffer buffer) { @@ -256,8 +311,9 @@ public long Offer(UnsafeBuffer buffer) /// containing message. /// offset in the buffer at which the encoded message begins. /// in bytes of the encoded message. + /// /// for the frame. /// The new stream position, otherwise a negative error value , , - /// or . + /// , or . [MethodImpl(MethodImplOptions.AggressiveInlining)] public long Offer( UnsafeBuffer buffer, @@ -290,13 +346,9 @@ public long Offer( newPosition = NewPosition(partitionIndex, (int) termOffset, position, result); } - else if (_conductor.IsPublicationConnected(LogBufferDescriptor.TimeOfLastStatusMessage(_logMetaDataBuffer))) - { - newPosition = BACK_PRESSURED; - } else { - newPosition = NOT_CONNECTED; + newPosition = BackPressureStatus(position, length); } } @@ -333,7 +385,7 @@ public long Offer( /// of the range to claim, in bytes.. /// to be populated if the claim succeeds. /// The new stream position, otherwise , , - /// or . + /// , or . /// if the length is greater than max payload length within an MTU. /// /// @@ -357,13 +409,9 @@ public long TryClaim(int length, BufferClaim bufferClaim) var result = termAppender.Claim(_headerWriter, length, bufferClaim); newPosition = NewPosition(partitionIndex, (int) termOffset, position, result); } - else if (_conductor.IsPublicationConnected(LogBufferDescriptor.TimeOfLastStatusMessage(_logMetaDataBuffer))) - { - newPosition = BACK_PRESSURED; - } else { - newPosition = NOT_CONNECTED; + newPosition = BackPressureStatus(position, length); } } @@ -415,22 +463,43 @@ internal void IncRef() private long NewPosition(int index, int currentTail, long position, long result) { var newPosition = ADMIN_ACTION; - var termOffset = TermAppender.TermOffset(result); + var termOffset = LogBufferDescriptor.TermOffset(result); if (termOffset > 0) { newPosition = (position - currentTail) + termOffset; } + else if ((position + currentTail) > _maxPossiblePosition) + { + newPosition = MAX_POSITION_EXCEEDED; + } else if (termOffset == TermAppender.TRIPPED) { var nextIndex = LogBufferDescriptor.NextPartitionIndex(index); - - _termAppenders[nextIndex].TailTermId(TermAppender.TermId(result) + 1); + + LogBufferDescriptor.InitialiseTailWithTermId(_logMetaDataBuffer, nextIndex, LogBufferDescriptor.TermId(result) + 1); LogBufferDescriptor.ActivePartitionIndexOrdered(_logMetaDataBuffer, nextIndex); } return newPosition; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private long BackPressureStatus(long currentPosition, int messageLength) + { + long status = NOT_CONNECTED; + + if ((currentPosition + messageLength) >= _maxPossiblePosition) + { + status = MAX_POSITION_EXCEEDED; + } + else if (_conductor.IsPublicationConnected(LogBufferDescriptor.TimeOfLastStatusMessage(_logMetaDataBuffer))) + { + status = BACK_PRESSURED; + } + + return status; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void CheckForMaxPayloadLength(int length) { diff --git a/src/Adaptive.Aeron/Subscription.cs b/src/Adaptive.Aeron/Subscription.cs index dcd69a2d..83f6a465 100644 --- a/src/Adaptive.Aeron/Subscription.cs +++ b/src/Adaptive.Aeron/Subscription.cs @@ -139,6 +139,24 @@ public UnavailableImageHandler UnavailableImageHandler() return _fields.unavailableImageHandler; } + + public int PollEndOfStreams(EndOfStreamHandler endOfStreamHandler) + { + int numberEndOfStreams = 0; + + foreach (var image in Images) + { + if (image.IsEndOfStream()) + { + numberEndOfStreams++; + endOfStreamHandler(image); + } + } + + return numberEndOfStreams; + } + + /// /// Poll the s under the subscription for available message fragments. /// @@ -194,8 +212,8 @@ public int Poll(FragmentHandler fragmentHandler, int fragmentLimit) /// callback for handling each message fragment as it is read. /// number of message fragments to limit for the poll operation across multiple s. /// the number of fragments received - /// - public int ControlledPoll(IControlledFragmentHandler fragmentHandler, int fragmentLimit) + /// + public int ControlledPoll(ControlledFragmentHandler fragmentHandler, int fragmentLimit) { var images = _fields.images; var length = images.Length; @@ -230,7 +248,7 @@ public int ControlledPoll(IControlledFragmentHandler fragmentHandler, int fragme /// to receive a block of fragments from each . /// for each polled. /// the number of bytes consumed. - public long BlockPoll(IBlockHandler blockHandler, int blockLengthLimit) + public long BlockPoll(BlockHandler blockHandler, int blockLengthLimit) { long bytesConsumed = 0; foreach (var image in _fields.images) @@ -299,6 +317,16 @@ public Image ImageBySessionId(int sessionId) return result; } + /// + /// Get the image at the given index from the images array. + /// + /// in the array + /// image at given index + public Image ImageAtIndex(int index) + { + return Images[index]; + } + /// /// Get a of active s that match this subscription. /// @@ -308,25 +336,15 @@ public Image ImageBySessionId(int sessionId) /// /// Iterate over the s for this subscription. /// - /// to handle each . - public void ForEachImage(Action imageConsumer) + /// to handle each . + public void ForEachImage(Action consumer) { foreach (var image in _fields.images) { - imageConsumer(image); + consumer(image); } } - /// - /// Get the image at the given index from the images array. - /// - /// in the array - /// image at given index - public Image GetImage(int index) - { - return Images[index]; - } - /// /// Close the Subscription so that associated s can be released. /// @@ -336,7 +354,7 @@ public Image GetImage(int index) #if DEBUG public virtual void Dispose() #else - public void Dispose() + public void Dispose() #endif { _fields.clientConductor.ClientLock().Lock() ; diff --git a/src/Adaptive.Agrona.Tests/Concurrent/CountersManagerTest.cs b/src/Adaptive.Agrona.Tests/Concurrent/CountersManagerTest.cs index a89fccbd..91a69c1b 100644 --- a/src/Adaptive.Agrona.Tests/Concurrent/CountersManagerTest.cs +++ b/src/Adaptive.Agrona.Tests/Concurrent/CountersManagerTest.cs @@ -15,6 +15,7 @@ */ using System; +using System.Text; using Adaptive.Agrona.Collections; using Adaptive.Agrona.Concurrent; using Adaptive.Agrona.Concurrent.Status; @@ -41,15 +42,55 @@ public void Setup() _consumer = A.Fake>(); _metaData = A.Fake(); - _labelsBuffer = new UnsafeBuffer(new byte[NumberOfCounters*CountersReader.METADATA_LENGTH]); - _counterBuffer = new UnsafeBuffer(new byte[NumberOfCounters*CountersReader.COUNTER_LENGTH]); + _labelsBuffer = new UnsafeBuffer(new byte[NumberOfCounters * CountersReader.METADATA_LENGTH]); + _counterBuffer = new UnsafeBuffer(new byte[NumberOfCounters * CountersReader.COUNTER_LENGTH]); - _manager = new CountersManager(_labelsBuffer, _counterBuffer); - _otherManager = new CountersManager(_labelsBuffer, _counterBuffer); + _manager = new CountersManager(_labelsBuffer, _counterBuffer, Encoding.ASCII); + _otherManager = new CountersManager(_labelsBuffer, _counterBuffer, Encoding.ASCII); } [Test] - public void ManagerShouldStoreLabels() + public void ShouldTruncateLongLabel() + { + int labelLength = CountersReader.MAX_LABEL_LENGTH + 10; + var sb = new StringBuilder(labelLength); + + for (int i = 0; i < labelLength; i++) + { + sb.Append('x'); + } + + var label = sb.ToString(); + int counterId = _manager.Allocate(label); + + _otherManager.ForEach(_consumer); + A.CallTo(() => _consumer(counterId, label.Substring(0, CountersReader.MAX_LABEL_LENGTH))).MustHaveHappened(); + } + + [Test] + public void ShouldCopeWithExceptionKeyFunc() + { + var ex = new Exception(); + + try + { + _manager.Allocate("label", CountersManager.DEFAULT_TYPE_ID, _ => { throw ex; }); + } + catch (Exception caught) + { + Assert.AreEqual(ex, caught); + + var counter = _manager.NewCounter("new label"); + Assert.AreEqual(0, counter.Id); + + return; + } + + Assert.Fail("Should have thrown exception."); + } + + [Test] + public void ShouldStoreLabels() { var counterId = _manager.Allocate("abc"); _otherManager.ForEach(_consumer); @@ -58,7 +99,7 @@ public void ManagerShouldStoreLabels() } [Test] - public void ManagerShouldStoreMultipleLabels() + public void ShouldStoreMultipleLabels() { var abc = _manager.Allocate("abc"); var def = _manager.Allocate("def"); @@ -92,8 +133,8 @@ public void ShouldFreeAndReuseCounters() } [Test] - [ExpectedException(typeof(ArgumentException))] - public void ManagerShouldNotOverAllocateCounters() + [ExpectedException(typeof(InvalidOperationException))] + public void ShouldNotOverAllocateCounters() { _manager.Allocate("abc"); _manager.Allocate("def"); @@ -104,7 +145,7 @@ public void ManagerShouldNotOverAllocateCounters() [Test] - public void AllocatedCountersCanBeMapped() + public void ShouldMapAllocatedCounters() { _manager.Allocate("def"); @@ -138,6 +179,34 @@ public void ShouldStoreMetaData() A.CallTo(() => _metaData(A._, A._, A._, A._)).MustHaveHappened(Repeated.Exactly.Twice); } + [Test] + public void ShouldStoreRawData() + { + const int typeIdOne = 333; + const long keyOne = 777L; + + var keyOneBuffer = new UnsafeBuffer(new byte[8]); + keyOneBuffer.PutLong(0, keyOne); + var labelOneBuffer = new UnsafeBuffer(Encoding.ASCII.GetBytes("Test Label One")); + + const int typeIdTwo = 222; + const long keyTwo = 444; + var keyTwoBuffer = new UnsafeBuffer(new byte[8]); + keyTwoBuffer.PutLong(0, keyTwo); + var labelTwoBuffer = new UnsafeBuffer(Encoding.ASCII.GetBytes("Test Label Two")); + + int counterIdOne = _manager.Allocate( + typeIdOne, keyOneBuffer, 0, keyOneBuffer.Capacity, labelOneBuffer, 0, labelOneBuffer.Capacity); + + int counterIdTwo = _manager.Allocate( + typeIdTwo, keyTwoBuffer, 0, keyTwoBuffer.Capacity, labelTwoBuffer, 0, labelTwoBuffer.Capacity); + + _manager.ForEach(_metaData); + + A.CallTo(() => _metaData(counterIdOne, typeIdOne, A.That.Matches(d => d.GetLong(0) == keyOne), "Test Label One")).MustHaveHappened() + .Then(A.CallTo(() => _metaData(counterIdTwo, typeIdTwo, A.That.Matches(d => d.GetLong(0) == keyTwo), "Test Label Two")).MustHaveHappened()); + } + [Test] public void ShouldStoreAndLoadValue() { diff --git a/src/Adaptive.Agrona/Adaptive.Agrona.csproj b/src/Adaptive.Agrona/Adaptive.Agrona.csproj index 50d30752..c4bc5133 100644 --- a/src/Adaptive.Agrona/Adaptive.Agrona.csproj +++ b/src/Adaptive.Agrona/Adaptive.Agrona.csproj @@ -73,6 +73,7 @@ + diff --git a/src/Adaptive.Agrona/Collections/IntObjConsumer.cs b/src/Adaptive.Agrona/Collections/IntObjConsumer.cs index 74d0ecd2..f56e7a7e 100644 --- a/src/Adaptive.Agrona/Collections/IntObjConsumer.cs +++ b/src/Adaptive.Agrona/Collections/IntObjConsumer.cs @@ -19,6 +19,6 @@ namespace Adaptive.Agrona.Collections /// /// This is an (int, Object) primitive specialisation of a BiConsumer. /// - public delegate void IntObjConsumer(int i, T v); + public delegate void IntObjConsumer(int i, T v); } \ No newline at end of file diff --git a/src/Adaptive.Agrona/Concurrent/AgentInvoker.cs b/src/Adaptive.Agrona/Concurrent/AgentInvoker.cs index d3887ef2..53f00f53 100644 --- a/src/Adaptive.Agrona/Concurrent/AgentInvoker.cs +++ b/src/Adaptive.Agrona/Concurrent/AgentInvoker.cs @@ -15,12 +15,31 @@ namespace Adaptive.Agrona.Concurrent /// public class AgentInvoker : IDisposable { - private bool _closed = false; + /// + /// Has the been closed? + /// + public bool IsClosed { get; private set; } = false; + + /// + /// Has the been started? + /// + public bool IsStarted { get; private set; } = false; + + /// + /// Has the been running? + /// + public bool IsRunning { get; private set; } = false; private readonly AtomicCounter _errorCounter; private readonly ErrorHandler _errorHandler; private readonly IAgent _agent; + /// + /// Create an agent and initialise it. + /// + /// to be called if an is encountered + /// to be incremented each time an exception is encountered. This may be null. + /// to be run in this thread. public AgentInvoker( ErrorHandler errorHandler, AtomicCounter errorCounter, @@ -44,11 +63,46 @@ public IAgent Agent() return _agent; } + /// + /// Mark the invoker as started and call the method. + /// + /// Startup logic will only be performed once. + /// + /// + public void Start() + { + try + { + if (!IsStarted) + { + IsStarted = true; + _agent.OnStart(); + IsRunning = true; + } + } + catch (Exception exception) + { + HandleError(exception); + Dispose(); + } + } + + /// + /// Invoke the method and return the work count. + /// + /// If an error occurs then the will be called on the errorCounter if not null + /// and the will be passed to the method. If the error + /// is an then will be called after the error handler. + /// + /// If not successfully started or after closed then this method will return without invoking the . + /// + /// + /// the work count for the method. public int Invoke() { int workCount = 0; - if (!_closed) + if (IsRunning) { try { @@ -56,7 +110,12 @@ public int Invoke() } catch (ThreadInterruptedException) { - + Dispose(); + } + catch (AgentTerminationException ex) + { + HandleError(ex); + Dispose(); } catch (Exception exception) { @@ -74,11 +133,30 @@ public int Invoke() public void Dispose() { - if (!_closed) + try + { + if (!IsClosed) + { + IsRunning = false; + IsClosed = true; + _agent.OnClose(); + } + } + catch (Exception exception) + { + HandleError(exception); + } + + } + + private void HandleError(Exception exception) + { + if (null != _errorCounter) { - _closed = true; - _agent.OnClose(); + _errorCounter.Increment(); } + + _errorHandler(exception); } } } \ No newline at end of file diff --git a/src/Adaptive.Agrona/Concurrent/AgentRunner.cs b/src/Adaptive.Agrona/Concurrent/AgentRunner.cs index 753ed2ca..536daaa4 100644 --- a/src/Adaptive.Agrona/Concurrent/AgentRunner.cs +++ b/src/Adaptive.Agrona/Concurrent/AgentRunner.cs @@ -31,10 +31,16 @@ public class AgentRunner : IDisposable /// /// Indicates that the runner is being closed. /// - public static readonly Thread Tombstone = null; + private static readonly Thread TOMBSTONE = null; - private volatile bool _running = true; - private volatile bool _done = false; + private static readonly int RETRY_CLOSE_TIMEOUT_MS = 3000; + + private volatile bool _isRunning = true; + + /// + /// Has the been closed? + /// + public bool IsClosed { get; private set; } private readonly AtomicCounter _errorCounter; private readonly ErrorHandler _errorHandler; @@ -122,30 +128,37 @@ public void Run() var idleStrategy = _idleStrategy; var agent = _agent; - while (_running) + + try { - try - { - idleStrategy.Idle(agent.DoWork()); - } - catch (ThreadInterruptedException) + agent.OnStart(); + } + catch (Exception ex) + { + HandleError(ex); + _isRunning = false; + } + + while (_isRunning) + { + if (DoDutyCycle(idleStrategy, agent)) { break; } - catch (Exception ex) - { - if (_running) - { - _errorCounter?.Increment(); + } - _errorHandler(ex); - } - } + try + { + agent.OnClose(); + } + catch (Exception ex) + { + HandleError(ex); } } finally { - _done = true; + IsClosed = true; } } @@ -158,36 +171,63 @@ public void Run() /// public void Dispose() { - _running = false; + _isRunning = false; - var thread = _thread.GetAndSet(Tombstone); - if (Tombstone != thread) + var thread = _thread.GetAndSet(TOMBSTONE); + if (TOMBSTONE != thread && null != thread) { - if (null != thread) + while (true) { - while (true) + try { - try - { - thread.Join(1000); - - if (!thread.IsAlive || _done) - { - break; - } + thread.Join(RETRY_CLOSE_TIMEOUT_MS); - Console.Error.WriteLine($"Timeout waitinf for {_agent.RoleName()}. Retrying..."); - - thread.Interrupt(); - } - catch (ThreadInterruptedException) + if (!thread.IsAlive || IsClosed) { return; } + + Console.Error.WriteLine($"Timeout waiting for agent '{_agent.RoleName()}' to close, Retrying..."); + + thread.Interrupt(); + } + catch (ThreadInterruptedException) + { + return; } } + } + } + + private bool DoDutyCycle(IIdleStrategy idleStrategy, IAgent agent) + { + try + { + idleStrategy.Idle(agent.DoWork()); + } + catch (ThreadInterruptedException) + { + return true; + } + catch (AgentTerminationException ex) + { + HandleError(ex); + return true; + } + catch (Exception ex) + { + HandleError(ex); + } + + return false; + } - _agent.OnClose(); + private void HandleError(Exception exception) + { + if (_isRunning) + { + _errorCounter?.Increment(); + _errorHandler(exception); } } } diff --git a/src/Adaptive.Agrona/Concurrent/AgentTerminationException.cs b/src/Adaptive.Agrona/Concurrent/AgentTerminationException.cs new file mode 100644 index 00000000..95f16f39 --- /dev/null +++ b/src/Adaptive.Agrona/Concurrent/AgentTerminationException.cs @@ -0,0 +1,8 @@ +using System; + +namespace Adaptive.Agrona.Concurrent +{ + public class AgentTerminationException : Exception + { + } +} \ No newline at end of file diff --git a/src/Adaptive.Agrona/Concurrent/CompositeAgent.cs b/src/Adaptive.Agrona/Concurrent/CompositeAgent.cs index bc92b794..0302e29f 100644 --- a/src/Adaptive.Agrona/Concurrent/CompositeAgent.cs +++ b/src/Adaptive.Agrona/Concurrent/CompositeAgent.cs @@ -11,13 +11,13 @@ public class CompositeAgent : IAgent { private readonly IAgent[] _agents; private readonly string _roleName; + private int _workIndex = 0; /// the parts of this composite, at least one agent and no null agents allowed /// if an empty array of agents is provided /// if the array or any element is null public CompositeAgent(List agents) : this(agents.ToArray()) { - } /// the parts of this composite, at least one agent and no null agents allowed @@ -25,22 +25,26 @@ public CompositeAgent(List agents) : this(agents.ToArray()) /// if the array or any element is null public CompositeAgent(params IAgent[] agents) { + if (agents.Length == 0) + { + throw new ArgumentException("CompsiteAgent requires at least one sub-agent"); + } + _agents = agents; var sb = new StringBuilder(agents.Length * 16); sb.Append('['); - + foreach (var agent in agents) { Objects.RequireNonNull(agent, "Agent cannot be null"); sb.Append(agent.RoleName()).Append(','); - } sb[sb.Length - 1] = ']'; _roleName = sb.ToString(); } - + /// public string RoleName() { @@ -50,11 +54,29 @@ public string RoleName() /// /// Note that one agent throwing an exception on start may result in other agents not being started. + /// + /// if any sub-agent throws an exception onClose. The first agent exception is collected as the inner exception of the thrown exception. public void OnStart() { - foreach (var agent in _agents) + Exception ce = null; + foreach (IAgent agent in _agents) { - agent.OnStart(); + try + { + agent.OnStart(); + } + catch (Exception ex) + { + if (ce == null) + { + ce = new Exception("CompositeAgent: underlying agent error on start", ex); + } + } + } + + if (ce != null) + { + throw ce; } } @@ -63,22 +85,45 @@ public int DoWork() { int workCount = 0; - foreach (var agent in _agents) + IAgent[] agents = _agents; + + while (_workIndex < agents.Length) { + var agent = agents[_workIndex++]; workCount += agent.DoWork(); } + _workIndex = 0; + return workCount; } /// - /// Note that one agent throwing an exception on close may result in other agents not being closed. + /// Note that one agent throwing an exception on close will not prevent other agents from being closed. + /// + /// if any sub-agent throws an exception onClose. The first agent exception is collected as the inner exception of the thrown exception. public void OnClose() { - foreach (var agent in _agents) + Exception ce = null; + foreach (IAgent agent in _agents) + { + try + { + agent.OnClose(); + } + catch (Exception ex) + { + if (ce == null) + { + ce = new Exception("CompositeAgent: underlying agent error on close", ex); + } + } + } + + if (ce != null) { - agent.OnClose(); + throw ce; } } } -} +} \ No newline at end of file diff --git a/src/Adaptive.Agrona/Concurrent/IAgent.cs b/src/Adaptive.Agrona/Concurrent/IAgent.cs index e7ddb2ac..174c78d6 100644 --- a/src/Adaptive.Agrona/Concurrent/IAgent.cs +++ b/src/Adaptive.Agrona/Concurrent/IAgent.cs @@ -20,6 +20,10 @@ namespace Adaptive.Agrona.Concurrent { /// /// An Agent is scheduled to do work on a thread on a duty cycle. Each Agent should have a defined role in a system. + /// + /// , , and will all be called by the same thread and in a + /// threadsafe manner. + /// /// public interface IAgent { @@ -33,15 +37,17 @@ public interface IAgent /// In Java this is optional to implement (default method) C# doesn't have the same construct for interfaces. /// void OnStart(); - + /// /// An agent should implement this method to do its work. /// /// The return value is used for implementing a backoff strategy that can be employed when no work is /// currently available for the agent to process. + /// + /// If the Agent should terminate and close then a can be thrown. /// - /// if an error has occurred /// 0 to indicate no work was currently available, a positive value otherwise. + /// if an error has occurred int DoWork(); /// diff --git a/src/Adaptive.Agrona/Concurrent/Status/AtomicCounter.cs b/src/Adaptive.Agrona/Concurrent/Status/AtomicCounter.cs index 510a8773..1cc87e6f 100644 --- a/src/Adaptive.Agrona/Concurrent/Status/AtomicCounter.cs +++ b/src/Adaptive.Agrona/Concurrent/Status/AtomicCounter.cs @@ -23,7 +23,7 @@ namespace Adaptive.Agrona.Concurrent.Status /// public class AtomicCounter : IDisposable { - private readonly int _counterId; + public int Id { get; } private readonly int _offset; private readonly IAtomicBuffer _buffer; private readonly CountersManager _countersManager; @@ -31,7 +31,7 @@ public class AtomicCounter : IDisposable internal AtomicCounter(IAtomicBuffer buffer, int counterId, CountersManager countersManager) { _buffer = buffer; - _counterId = counterId; + Id = counterId; _countersManager = countersManager; _offset = CountersReader.CounterOffset(counterId); buffer.PutLong(_offset, 0); @@ -113,7 +113,7 @@ public long Get() /// public void Dispose() { - _countersManager.Free(_counterId); + _countersManager.Free(Id); } } } \ No newline at end of file diff --git a/src/Adaptive.Agrona/Concurrent/Status/CountersManager.cs b/src/Adaptive.Agrona/Concurrent/Status/CountersManager.cs index 7b4e43df..59b12f34 100644 --- a/src/Adaptive.Agrona/Concurrent/Status/CountersManager.cs +++ b/src/Adaptive.Agrona/Concurrent/Status/CountersManager.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; +using System.Text; namespace Adaptive.Agrona.Concurrent.Status { @@ -55,7 +56,7 @@ namespace Adaptive.Agrona.Concurrent.Status /// +-+-------------------------------------------------------------+ /// |R| Label Length | /// +-+-------------------------------------------------------------+ - /// | 380 bytes of Label in UTF-8 ... + /// | 380 bytes of Label ... /// ... | /// +---------------------------------------------------------------+ /// | Repeats to end of buffer ... @@ -90,29 +91,53 @@ public CountersManager(IAtomicBuffer metaDataBuffer, IAtomicBuffer valuesBuffer) } /// - /// Allocate a new counter with a given label. + /// Create a new counter buffer manager over two buffers. /// - /// to describe the counter. + /// containing the types, keys, and labels for the counters. + /// containing the values of the counters themselves. + /// for the label encoding. + public CountersManager(IAtomicBuffer metaDataBuffer, IAtomicBuffer valuesBuffer, Encoding labelCharset) : base(metaDataBuffer, valuesBuffer, labelCharset) + { + valuesBuffer.VerifyAlignment(); + + if (metaDataBuffer.Capacity < (valuesBuffer.Capacity * 2)) + { + throw new ArgumentException("Meta data buffer not sufficiently large"); + } + } + + /// + /// Allocate a new counter with a given label and type. + /// + /// to describe the counter. + /// for the type of counter. /// the id allocated for the counter. - public int Allocate(string label) + public int Allocate(string label, int typeId = DEFAULT_TYPE_ID) { int counterId = NextCounterId(); if ((CounterOffset(counterId) + COUNTER_LENGTH) > ValuesBuffer.Capacity) { - throw new ArgumentException("Unable to allocated counter, values buffer is full"); + throw new InvalidOperationException("Unable to allocated counter, values buffer is full"); } int recordOffset = MetaDataOffset(counterId); if ((recordOffset + METADATA_LENGTH) > MetaDataBuffer.Capacity) { - throw new ArgumentException("Unable to allocate counter, labels buffer is full"); + throw new InvalidOperationException("Unable to allocate counter, labels buffer is full"); } - - MetaDataBuffer.PutInt(recordOffset + TYPE_ID_OFFSET, DEFAULT_TYPE_ID); - MetaDataBuffer.PutStringUtf8(recordOffset + LABEL_OFFSET, label, MAX_LABEL_LENGTH); - - MetaDataBuffer.PutIntOrdered(recordOffset, RECORD_ALLOCATED); - + + try + { + MetaDataBuffer.PutInt(recordOffset + TYPE_ID_OFFSET, typeId); + PutLabel(recordOffset, label); + MetaDataBuffer.PutIntOrdered(recordOffset, RECORD_ALLOCATED); + } + catch (Exception) + { + _freeList.Enqueue(counterId); + throw; + } + return counterId; } @@ -131,26 +156,93 @@ public int Allocate(string label, int typeId, Action keyFu var counterId = NextCounterId(); if (CounterOffset(counterId) + COUNTER_LENGTH > ValuesBuffer.Capacity) { - throw new ArgumentException("Unable to allocated counter, values buffer is full"); + throw new InvalidOperationException("Unable to allocated counter, values buffer is full"); } var recordOffset = MetaDataOffset(counterId); if (recordOffset + METADATA_LENGTH > MetaDataBuffer.Capacity) { - throw new ArgumentException("Unable to allocate counter, labels buffer is full"); + throw new InvalidOperationException("Unable to allocate counter, labels buffer is full"); } - MetaDataBuffer.PutInt(recordOffset + TYPE_ID_OFFSET, typeId); - keyFunc(new UnsafeBuffer(MetaDataBuffer, recordOffset + KEY_OFFSET, MAX_KEY_LENGTH)); - MetaDataBuffer.PutStringUtf8(recordOffset + LABEL_OFFSET, label, MAX_LABEL_LENGTH); - MetaDataBuffer.PutIntOrdered(recordOffset, RECORD_ALLOCATED); + try + { + MetaDataBuffer.PutInt(recordOffset + TYPE_ID_OFFSET, typeId); + keyFunc(new UnsafeBuffer(MetaDataBuffer, recordOffset + KEY_OFFSET, MAX_KEY_LENGTH)); + PutLabel(recordOffset, label); + MetaDataBuffer.PutIntOrdered(recordOffset, RECORD_ALLOCATED); + } + catch (Exception) + { + _freeList.Enqueue(counterId); + throw; + } + return counterId; } /// - /// Allocate a counter record and wrap it with a new for use. + /// Allocate a counter with the minimum of allocation by allowing the label an key to be provided and copied. + /// + /// If the keyBuffer is null then a copy of the key is not attempted. + /// + /// + /// + /// for the counter. + /// containing the optional key for the counter. + /// within the keyBuffer at which the key begins. + /// of the key in the keyBuffer. + /// containing the mandatory label for the counter. + /// within the labelBuffer at which the label begins. + /// of the label in the labelBuffer. + /// the id allocated for the counter. + public int Allocate(int typeId, IDirectBuffer keyBuffer, int keyOffset, int keyLength, IDirectBuffer labelBuffer, int labelOffset, int labelLength) + { + int counterId = NextCounterId(); + if ((CounterOffset(counterId) + COUNTER_LENGTH) > ValuesBuffer.Capacity) + { + throw new InvalidOperationException("Unable to allocated counter, values buffer is full"); + } + + int recordOffset = MetaDataOffset(counterId); + if ((recordOffset + METADATA_LENGTH) > MetaDataBuffer.Capacity) + { + throw new InvalidOperationException("Unable to allocate counter, labels buffer is full"); + } + + try + { + MetaDataBuffer.PutInt(recordOffset + TYPE_ID_OFFSET, typeId); + + int length; + + if (null != keyBuffer) + { + length = Math.Min(keyLength, MAX_KEY_LENGTH); + MetaDataBuffer.PutBytes(recordOffset + KEY_OFFSET, keyBuffer, keyOffset, length); + } + + length = Math.Min(labelLength, MAX_LABEL_LENGTH); + MetaDataBuffer.PutInt(recordOffset + LABEL_OFFSET, length); + MetaDataBuffer.PutBytes(recordOffset + LABEL_OFFSET + BitUtil.SIZE_OF_INT, labelBuffer, labelOffset, length); + + MetaDataBuffer.PutIntOrdered(recordOffset, RECORD_ALLOCATED); + } + catch (Exception) + { + _freeList.Enqueue(counterId); + throw; + } + + return counterId; + } + + + /// + /// Allocate a counter record and wrap it with a new for use with a default type + /// of /// /// to describe the counter. /// a newly allocated @@ -159,19 +251,57 @@ public AtomicCounter NewCounter(string label) return new AtomicCounter(ValuesBuffer, Allocate(label), this); } + /// + /// Allocate a counter record and wrap it with a new for use. + /// + /// to describe the counter. + /// for the type of counter. + /// a newly allocated + public AtomicCounter NewCounter(string label, int typeId) + { + return new AtomicCounter(ValuesBuffer, Allocate(label, typeId), this); + } + /// /// Allocate a counter record and wrap it with a new for use. /// /// to describe the counter. /// for the type of counter. - /// for setting the key value for the counter. - /// + /// for setting the key value for the counter. /// a newly allocated public AtomicCounter NewCounter(string label, int typeId, Action keyFunc) { return new AtomicCounter(ValuesBuffer, Allocate(label, typeId, keyFunc), this); } + /// + /// Allocate a counter record and wrap it with a new for use. + /// + /// If the keyBuffer is null then a copy of the key is not attempted. + /// + /// + /// + /// for the counter. + /// containing the optional key for the counter. + /// within the keyBuffer at which the key begins. + /// of the key in the keyBuffer. + /// containing the mandatory label for the counter. + /// within the labelBuffer at which the label begins. + /// of the label in the labelBuffer. + /// the id allocated for the counter. + public virtual AtomicCounter NewCounter( + int typeId, + IDirectBuffer keyBuffer, + int keyOffset, + int keyLength, + IDirectBuffer labelBuffer, + int labelOffset, + int labelLength) + { + return new AtomicCounter(ValuesBuffer, Allocate(typeId, keyBuffer, keyOffset, keyLength, labelBuffer, labelOffset, labelLength), this); + } + + /// /// Free the counter identified by counterId. /// @@ -204,5 +334,28 @@ private int NextCounterId() return counterId; } + + private void PutLabel(int recordOffset, string label) + { + if (Encoding.ASCII.Equals(LabelCharset)) + { + MetaDataBuffer.PutStringAscii(recordOffset + LABEL_OFFSET, label.Length > MAX_LABEL_LENGTH ? label.Substring(0, MAX_LABEL_LENGTH) : label); + } + else + { + byte[] bytes = LabelCharset.GetBytes(label); + + if (bytes.Length > MAX_LABEL_LENGTH) + { + MetaDataBuffer.PutInt(recordOffset + LABEL_OFFSET, MAX_LABEL_LENGTH); + MetaDataBuffer.PutBytes(recordOffset + LABEL_OFFSET + BitUtil.SIZE_OF_INT, bytes, 0, MAX_LABEL_LENGTH); + } + else + { + MetaDataBuffer.PutInt(recordOffset + LABEL_OFFSET, bytes.Length); + MetaDataBuffer.PutBytes(recordOffset + LABEL_OFFSET + BitUtil.SIZE_OF_INT, bytes); + } + } + } } } \ No newline at end of file diff --git a/src/Adaptive.Agrona/Concurrent/Status/CountersReader.cs b/src/Adaptive.Agrona/Concurrent/Status/CountersReader.cs index 309ca885..3079f514 100644 --- a/src/Adaptive.Agrona/Concurrent/Status/CountersReader.cs +++ b/src/Adaptive.Agrona/Concurrent/Status/CountersReader.cs @@ -14,6 +14,7 @@ * limitations under the License. */ +using System.Text; using Adaptive.Agrona.Collections; namespace Adaptive.Agrona.Concurrent.Status @@ -54,7 +55,7 @@ namespace Adaptive.Agrona.Concurrent.Status /// +-+-------------------------------------------------------------+ /// |R| Label Length | /// +-+-------------------------------------------------------------+ - /// | 380 bytes of Label in UTF-8 ... + /// | 380 bytes of Label ... /// ... | /// +---------------------------------------------------------------+ /// | Repeats to end of buffer ... @@ -65,6 +66,15 @@ namespace Adaptive.Agrona.Concurrent.Status /// public class CountersReader { + /// + /// Accept a metadata record. + /// + /// of the counter. + /// of the counter. + /// for the counter. + /// for the counter. + public delegate void MetaData(int counterId, int typeId, IDirectBuffer keyBuffer, string label); + /// /// Record has not been used. /// @@ -119,9 +129,12 @@ public class CountersReader /// Length of the space allocated to a counter that includes padding to avoid false sharing. /// public static readonly int COUNTER_LENGTH = BitUtil.CACHE_LINE_LENGTH*2; - + /// /// Construct a reader over buffers containing the values and associated metadata. + /// + /// Counter labels default to + /// /// /// containing the counter metadata. /// containing the counter values. @@ -131,6 +144,20 @@ public CountersReader(IAtomicBuffer metaDataBuffer, IAtomicBuffer valuesBuffer) MetaDataBuffer = metaDataBuffer; } + /// + /// Construct a reader over buffers containing the values and associated metadata. + /// + /// + /// containing the counter metadata. + /// containing the counter values. + /// for the label encoding + public CountersReader(IAtomicBuffer metaDataBuffer, IAtomicBuffer valuesBuffer, Encoding encoding) + { + ValuesBuffer = valuesBuffer; + MetaDataBuffer = metaDataBuffer; + LabelCharset = encoding; + } + /// /// Get the buffer containing the metadata for the counters. /// @@ -143,6 +170,12 @@ public CountersReader(IAtomicBuffer metaDataBuffer, IAtomicBuffer valuesBuffer) /// the buffer containing the values for the counters. public IAtomicBuffer ValuesBuffer { get; } + /// + /// The used for the encoded label. + /// + /// the used for the encoded label. + public Encoding LabelCharset { get; } + /// /// The offset in the counter buffer for a given counterId. /// @@ -174,14 +207,14 @@ public void ForEach(IntObjConsumer consumer) for (int i = 0, capacity = MetaDataBuffer.Capacity; i < capacity; i += METADATA_LENGTH) { var recordStatus = MetaDataBuffer.GetIntVolatile(i); - if (RECORD_UNUSED == recordStatus) + if (RECORD_ALLOCATED == recordStatus) { - break; + var label = LabelValue(i); + consumer(counterId, label); } - else if (RECORD_ALLOCATED == recordStatus) + else if (RECORD_UNUSED == recordStatus) { - var label = MetaDataBuffer.GetStringUtf8(i + LABEL_OFFSET); - consumer(counterId, label); + break; } counterId++; @@ -199,18 +232,18 @@ public void ForEach(MetaData metaData) for (int i = 0, capacity = MetaDataBuffer.Capacity; i < capacity; i += METADATA_LENGTH) { var recordStatus = MetaDataBuffer.GetIntVolatile(i); - if (RECORD_UNUSED == recordStatus) - { - break; - } if (RECORD_ALLOCATED == recordStatus) { var typeId = MetaDataBuffer.GetInt(i + TYPE_ID_OFFSET); - var label = MetaDataBuffer.GetStringUtf8(i + LABEL_OFFSET); + var label = LabelValue(i); IDirectBuffer keyBuffer = new UnsafeBuffer(MetaDataBuffer, i + KEY_OFFSET, MAX_KEY_LENGTH); metaData(counterId, typeId, keyBuffer, label); } + else if (RECORD_UNUSED == recordStatus) + { + break; + } counterId++; } @@ -226,9 +259,13 @@ public long GetCounterValue(int counterId) return ValuesBuffer.GetLongVolatile(CounterOffset(counterId)); } - /// - /// Callback function for consuming metadata records of counters. - /// - public delegate void MetaData(int counterId, int typeId, IDirectBuffer keyBuffer, string label); + private string LabelValue(int recordOffset) + { + int labelLength = MetaDataBuffer.GetInt(recordOffset + LABEL_OFFSET); + byte[] stringInBytes = new byte[labelLength]; + MetaDataBuffer.GetBytes(recordOffset + LABEL_OFFSET + BitUtil.SIZE_OF_INT, stringInBytes); + + return LabelCharset.GetString(stringInBytes); + } } } \ No newline at end of file diff --git a/src/Adaptive.Agrona/Concurrent/Status/UnsafeBufferPosition.cs b/src/Adaptive.Agrona/Concurrent/Status/UnsafeBufferPosition.cs index f1ec6d17..11b19d4c 100644 --- a/src/Adaptive.Agrona/Concurrent/Status/UnsafeBufferPosition.cs +++ b/src/Adaptive.Agrona/Concurrent/Status/UnsafeBufferPosition.cs @@ -23,6 +23,7 @@ namespace Adaptive.Agrona.Concurrent.Status /// public class UnsafeBufferPosition : IPosition { + public bool IsClosed { get; private set; } = false; private readonly int _counterId; private readonly int _offset; private readonly UnsafeBuffer _buffer; @@ -115,7 +116,12 @@ public override bool ProposeMaxOrdered(long proposedValue) public override void Dispose() { - _countersManager?.Free(_counterId); + if (!IsClosed) + { + IsClosed = true; + + _countersManager?.Free(_counterId); + } } } } \ No newline at end of file diff --git a/src/Adaptive.Agrona/ErrorHandler.cs b/src/Adaptive.Agrona/ErrorHandler.cs index ef22e7f5..5132b0d2 100644 --- a/src/Adaptive.Agrona/ErrorHandler.cs +++ b/src/Adaptive.Agrona/ErrorHandler.cs @@ -20,6 +20,9 @@ namespace Adaptive.Agrona { /// /// Callback to notify of an error that has occurred when processing an operation or event. + /// + /// This method is assumed non-throwing, so rethrowing the exception or triggering further exceptions would be a bug. + /// /// exception that occurred while processing an operation or event. /// public delegate void ErrorHandler(Exception exception);