From 28a1af9c02c570c6c8af9f2923d4aed5c8ec5227 Mon Sep 17 00:00:00 2001 From: segabriel Date: Thu, 13 Jun 2019 13:21:50 +0300 Subject: [PATCH 01/16] Increased version --- pom.xml | 2 +- reactor-aeron-benchmarks/pom.xml | 2 +- reactor-aeron/pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 025046c2..42ff13b0 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ reactor-aeron-parent - 0.1.5-SNAPSHOT + 0.2.0-SNAPSHOT pom ScaleCube/scalecube-reactor-aeron diff --git a/reactor-aeron-benchmarks/pom.xml b/reactor-aeron-benchmarks/pom.xml index a79ca157..5a973957 100644 --- a/reactor-aeron-benchmarks/pom.xml +++ b/reactor-aeron-benchmarks/pom.xml @@ -16,7 +16,7 @@ io.scalecube reactor-aeron-parent - 0.1.5-SNAPSHOT + 0.2.0-SNAPSHOT reactor-aeron-benchmarks diff --git a/reactor-aeron/pom.xml b/reactor-aeron/pom.xml index 218ac4b6..05010fe0 100644 --- a/reactor-aeron/pom.xml +++ b/reactor-aeron/pom.xml @@ -5,7 +5,7 @@ io.scalecube reactor-aeron-parent - 0.1.5-SNAPSHOT + 0.2.0-SNAPSHOT reactor-aeron From 05c9a6a676914f4e2b3455a739209816bb2eef26 Mon Sep 17 00:00:00 2001 From: segabriel Date: Sun, 9 Jun 2019 22:22:55 +0300 Subject: [PATCH 02/16] Replaced on using agents --- pom.xml | 4 +- .../java/reactor/aeron/AeronPingClient.java | 11 +- .../java/reactor/aeron/AeronPongServer.java | 4 +- .../java/reactor/aeron/ClientServerSends.java | 1 + .../main/java/reactor/aeron/ServerDemo.java | 5 +- .../java/reactor/aeron/ServerServerSends.java | 9 +- .../java/reactor/aeron/ServerThroughput.java | 3 + .../reactor/aeron/AeronClientConnector.java | 93 ++++--- .../java/reactor/aeron/AeronConnection.java | 2 +- .../java/reactor/aeron/AeronEventLoop.java | 261 ++++-------------- .../main/java/reactor/aeron/AeronInbound.java | 7 +- .../java/reactor/aeron/AeronOutbound.java | 37 ++- .../java/reactor/aeron/AeronOutboundThen.java | 10 + .../java/reactor/aeron/AeronResource.java | 6 - .../java/reactor/aeron/AeronResources.java | 134 ++++----- .../reactor/aeron/AeronServerHandler.java | 49 ++-- .../reactor/aeron/DefaultAeronInbound.java | 187 ------------- .../reactor/aeron/DefaultAeronOutbound.java | 92 ------ .../reactor/aeron/DefaultFragmentMapper.java | 25 ++ .../java/reactor/aeron/DirectBufferFlux.java | 33 --- .../reactor/aeron/DirectBufferHandler.java | 9 +- .../reactor/aeron/DuplexAeronConnection.java | 8 +- .../reactor/aeron/DynamicCompositeAgent.java | 208 ++++++++++++++ .../java/reactor/aeron/FragmentMapper.java | 17 ++ .../main/java/reactor/aeron/ImageAgent.java | 229 +++++++++++++++ .../reactor/aeron/MessageSubscription.java | 76 ----- ...Publication.java => PublicationAgent.java} | 233 ++++++---------- .../java/reactor/aeron/SubscriptionAgent.java | 215 +++++++++++++++ .../java/reactor/aeron/AeronClientTest.java | 61 ++-- .../reactor/aeron/AeronConnectionTest.java | 6 +- .../java/reactor/aeron/AeronServerTest.java | 11 +- 31 files changed, 1090 insertions(+), 956 deletions(-) delete mode 100644 reactor-aeron/src/main/java/reactor/aeron/AeronResource.java delete mode 100644 reactor-aeron/src/main/java/reactor/aeron/DefaultAeronInbound.java delete mode 100644 reactor-aeron/src/main/java/reactor/aeron/DefaultAeronOutbound.java create mode 100644 reactor-aeron/src/main/java/reactor/aeron/DefaultFragmentMapper.java delete mode 100644 reactor-aeron/src/main/java/reactor/aeron/DirectBufferFlux.java create mode 100644 reactor-aeron/src/main/java/reactor/aeron/DynamicCompositeAgent.java create mode 100644 reactor-aeron/src/main/java/reactor/aeron/FragmentMapper.java create mode 100644 reactor-aeron/src/main/java/reactor/aeron/ImageAgent.java delete mode 100644 reactor-aeron/src/main/java/reactor/aeron/MessageSubscription.java rename reactor-aeron/src/main/java/reactor/aeron/{MessagePublication.java => PublicationAgent.java} (54%) create mode 100644 reactor-aeron/src/main/java/reactor/aeron/SubscriptionAgent.java diff --git a/pom.xml b/pom.xml index 42ff13b0..61569517 100644 --- a/pom.xml +++ b/pom.xml @@ -30,8 +30,8 @@ 2.11.0 3.4.2 - 2.17.0 - 5.1.0 + 2.27.0 + 5.1.1 1.3 diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPingClient.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPingClient.java index 526d0a88..aa2ef89d 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPingClient.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPingClient.java @@ -86,6 +86,7 @@ private static void roundTripMessages(AeronConnection connection, long count) { connection .inbound() .receive() + .cast(DirectBuffer.class) .take(count) .doOnNext( buffer -> { @@ -108,17 +109,9 @@ private static class NanoTimeGeneratorHandler implements DirectBufferHandler connection .outbound() - .send(connection.inbound().receive()) + .send(connection.inbound().receive().cast(DirectBuffer.class)) .then(connection.onDispose())) .bind() .block() diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ClientServerSends.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ClientServerSends.java index 113c27c0..db484edb 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ClientServerSends.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ClientServerSends.java @@ -25,6 +25,7 @@ public static void main(String[] args) { connection .inbound() .receive() + .cast(DirectBuffer.class) .as(ByteBufFlux::create) .asString() .log("receive") diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerDemo.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerDemo.java index d886eede..b46cfa85 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerDemo.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerDemo.java @@ -14,10 +14,7 @@ public static void main(String[] args) { .options("localhost", 13000, 13001) .handle( connection -> - connection - .inbound() - .receive() - .asString() + DefaultFragmentMapper.asString(connection.inbound().receive()) .log("receive") .then(connection.onDispose())) .bind() diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerServerSends.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerServerSends.java index ea6a9a79..afc27e2a 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerServerSends.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerServerSends.java @@ -44,13 +44,8 @@ static class ByteBufHandler implements DirectBufferHandler { static final ByteBufHandler defaultInstance = new ByteBufHandler(); @Override - public int estimateLength(ByteBuf buffer) { - return buffer.readableBytes(); - } - - @Override - public DirectBuffer map(ByteBuf buffer, int length) { - return new UnsafeBuffer(buffer.nioBuffer(), 0, length); + public DirectBuffer map(ByteBuf buffer) { + return new UnsafeBuffer(buffer.nioBuffer(), 0, buffer.readableBytes()); } @Override diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerThroughput.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerThroughput.java index ca03f3f3..7176bc74 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerThroughput.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerThroughput.java @@ -1,5 +1,7 @@ package reactor.aeron; +import org.agrona.DirectBuffer; + public class ServerThroughput { /** @@ -29,6 +31,7 @@ public static void main(String[] args) { connection .inbound() .receive() + .cast(DirectBuffer.class) .doOnNext(buffer -> reporter.onMessage(1, buffer.capacity())) .then(connection.onDispose())) .bind() diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronClientConnector.java b/reactor-aeron/src/main/java/reactor/aeron/AeronClientConnector.java index 9903d719..49d43d13 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronClientConnector.java +++ b/reactor-aeron/src/main/java/reactor/aeron/AeronClientConnector.java @@ -1,9 +1,12 @@ package reactor.aeron; import io.aeron.Image; +import io.aeron.Publication; import java.time.Duration; import java.util.function.Function; import java.util.function.Supplier; +import org.agrona.CloseHelper; +import org.agrona.DirectBuffer; import org.reactivestreams.Publisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,6 +31,7 @@ final class AeronClientConnector { private final AeronOptions options; private final AeronResources resources; private final Function> handler; + private final DefaultFragmentMapper mapper = new DefaultFragmentMapper(); AeronClientConnector(AeronOptions options) { this.options = options; @@ -43,9 +47,7 @@ final class AeronClientConnector { Mono start() { return Mono.defer( () -> { - AeronEventLoop eventLoop = resources.nextEventLoop(); - - return tryConnect(eventLoop) + return tryConnect() .flatMap( publication -> { // inbound->MDC(xor(sessionId))->Sub(control-endpoint, xor(sessionId)) @@ -69,7 +71,6 @@ Mono start() { .subscription( inboundChannel, STREAM_ID, - eventLoop, image -> { logger.debug( "{}: created client inbound", Integer.toHexString(sessionId)); @@ -88,19 +89,13 @@ Mono start() { Integer.toHexString(sessionId), th.toString()); // dispose outbound resource - publication.dispose(); + CloseHelper.quietClose(publication); }) .flatMap( subscription -> inboundAvailable.flatMap( image -> - newConnection( - sessionId, - image, - publication, - subscription, - disposeHook, - eventLoop))) + newConnection(sessionId, image, publication, disposeHook))) .doOnSuccess( connection -> logger.debug( @@ -112,32 +107,23 @@ Mono start() { } private Mono newConnection( - int sessionId, - Image image, - MessagePublication publication, - MessageSubscription subscription, - MonoProcessor disposeHook, - AeronEventLoop eventLoop) { - - return resources - .inbound(image, subscription, eventLoop) - .doOnError( - ex -> { - subscription.dispose(); - publication.dispose(); + int sessionId, Image image, Publication publication, MonoProcessor disposeHook) { + PublicationAgent publicationAgent = new PublicationAgent(publication); + ImageAgent imageAgent = new ImageAgent<>(image, mapper, true); + DuplexAeronConnection connection = + new DuplexAeronConnection(sessionId, imageAgent, publicationAgent, disposeHook); + return connection + .start(handler) + .doOnSuccess( + c -> { + AeronEventLoop eventLoop = resources.nextEventLoop(); + eventLoop.register(imageAgent); + eventLoop.register(publicationAgent); }) - .flatMap( - inbound -> { - DefaultAeronOutbound outbound = new DefaultAeronOutbound(publication); - - DuplexAeronConnection connection = - new DuplexAeronConnection(sessionId, inbound, outbound, disposeHook); - - return connection.start(handler).doOnError(ex -> connection.dispose()); - }); + .doOnError(ex -> connection.dispose()); } - private Mono tryConnect(AeronEventLoop eventLoop) { + private Mono tryConnect() { return Mono.defer( () -> { int retryCount = options.connectRetryCount(); @@ -145,14 +131,47 @@ private Mono tryConnect(AeronEventLoop eventLoop) { // outbound->Pub(endpoint, sessionId) return Mono.fromCallable(this::getOutboundChannel) - .flatMap(channel -> resources.publication(channel, STREAM_ID, options, eventLoop)) - .flatMap(mp -> mp.ensureConnected().doOnError(ex -> mp.dispose())) + .flatMap(channel -> resources.publication(channel, STREAM_ID)) + .flatMap( + publication -> + ensureConnected(publication) + .doOnError(ex -> CloseHelper.quietClose(publication))) .retryBackoff(retryCount, Duration.ZERO, retryInterval) .doOnError( ex -> logger.warn("aeron.Publication is not connected after several retries")); }); } + /** + * Spins (in async fashion) until {@link Publication#isConnected()} would have returned {@code + * true} or {@code connectTimeout} elapsed. + * + * @return mono result + */ + private Mono ensureConnected(Publication publication) { + return Mono.defer( + () -> { + Duration connectTimeout = options.connectTimeout(); + Duration retryInterval = Duration.ofMillis(100); + long retryCount = Math.max(connectTimeout.toMillis() / retryInterval.toMillis(), 1); + + return ensureConnected0(publication) + .retryBackoff(retryCount, retryInterval, retryInterval) + .doOnError( + ex -> logger.warn("aeron.Publication is not connected after several retries")) + .thenReturn(publication); + }); + } + + private Mono ensureConnected0(Publication publication) { + return Mono.defer( + () -> + publication.isConnected() + ? Mono.empty() + : Mono.error( + AeronExceptions.failWithPublication("aeron.Publication is not connected"))); + } + private String getOutboundChannel() { AeronChannelUriString outboundUri = options.outboundUri(); Supplier sessionIdGenerator = options.sessionIdGenerator(); diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronConnection.java b/reactor-aeron/src/main/java/reactor/aeron/AeronConnection.java index b15c7a4a..84786989 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronConnection.java +++ b/reactor-aeron/src/main/java/reactor/aeron/AeronConnection.java @@ -21,7 +21,7 @@ public interface AeronConnection extends OnDisposable { * * @return {@code AeronInbound} instance */ - AeronInbound inbound(); + AeronInbound inbound(); /** * Return the {@link AeronOutbound} write API from this connection. If {@link AeronConnection} has diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronEventLoop.java b/reactor-aeron/src/main/java/reactor/aeron/AeronEventLoop.java index 075ed6d7..adee1402 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronEventLoop.java +++ b/reactor-aeron/src/main/java/reactor/aeron/AeronEventLoop.java @@ -1,45 +1,38 @@ package reactor.aeron; import java.lang.management.ManagementFactory; -import java.util.ArrayList; -import java.util.List; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ThreadFactory; -import java.util.function.Consumer; -import java.util.stream.Collectors; import javax.management.MBeanServer; import javax.management.ObjectName; import javax.management.StandardMBean; +import org.agrona.CloseHelper; +import org.agrona.concurrent.Agent; +import org.agrona.concurrent.AgentInvoker; import org.agrona.concurrent.IdleStrategy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import reactor.core.Exceptions; import reactor.core.publisher.Mono; import reactor.core.publisher.MonoProcessor; -import reactor.core.publisher.MonoSink; -final class AeronEventLoop implements OnDisposable { +public final class AeronEventLoop implements OnDisposable { private static final Logger logger = LoggerFactory.getLogger(AeronEventLoop.class); private final IdleStrategy idleStrategy; private final String name; + private final int workerId; // worker id private final int groupId; // event loop group id - private final Queue commands = new ConcurrentLinkedQueue<>(); - private final List resources = new ArrayList<>(); - - private final MonoProcessor dispose = MonoProcessor.create(); - private final MonoProcessor onDispose = MonoProcessor.create(); - - private final Mono workerMono; + private final DynamicCompositeAgent agent; + private final AgentInvoker agentInvoker; private volatile Thread thread; - private List publications = new ArrayList<>(); - private List inbounds = new ArrayList<>(); + private final MonoProcessor dispose = MonoProcessor.create(); + private final MonoProcessor onDispose = MonoProcessor.create(); /** * Constructor. @@ -54,7 +47,13 @@ final class AeronEventLoop implements OnDisposable { this.workerId = workerId; this.groupId = groupId; this.idleStrategy = idleStrategy; - this.workerMono = Mono.fromCallable(this::createWorker).cache(); + + this.agent = new DynamicCompositeAgent(String.format("%s-%x-%d", name, groupId, workerId)); + this.agentInvoker = + new AgentInvoker( + ex -> logger.error("Unexpected exception occurred on invoker: ", ex), null, agent); + + start(); } private static ThreadFactory defaultThreadFactory(String threadName) { @@ -67,73 +66,33 @@ private static ThreadFactory defaultThreadFactory(String threadName) { }; } - private Worker createWorker() throws Exception { - final String threadName = String.format("%s-%x-%d", name, groupId, workerId); - final ThreadFactory threadFactory = defaultThreadFactory(threadName); + private void start() { + try { + final String threadName = String.format("%s-%x-%d", name, groupId, workerId); + final ThreadFactory threadFactory = defaultThreadFactory(threadName); - WorkerFlightRecorder flightRecorder = new WorkerFlightRecorder(); - MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer(); - ObjectName objectName = new ObjectName("reactor.aeron:name=" + threadName); - StandardMBean standardMBean = new StandardMBean(flightRecorder, WorkerMBean.class); - mbeanServer.registerMBean(standardMBean, objectName); + WorkerFlightRecorder flightRecorder = new WorkerFlightRecorder(); + MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer(); + ObjectName objectName = new ObjectName("reactor.aeron:name=" + threadName); + StandardMBean standardMBean = new StandardMBean(flightRecorder, WorkerMBean.class); + mbeanServer.registerMBean(standardMBean, objectName); - Worker worker = new Worker(flightRecorder); - thread = threadFactory.newThread(worker); - thread.start(); + Worker worker = new Worker(flightRecorder); - return worker; - } - - /** - * Returns {@code true} if client called this method from within worker thread of {@link - * AeronEventLoop}, and {@code false} otherwise. - * - * @return {@code true} if current thread is worker thread from event loop, and {@code false} - * otherwise - */ - boolean inEventLoop() { - return thread == Thread.currentThread(); - } - - /** - * Registers aeron resource in event loop. - * - * @param resource aeron resource - * @return mono result - */ - Mono register(R resource) { - return worker() - .flatMap( - worker -> - command( - sink -> { - if (!cancelIfDisposed(sink)) { - resources.add(resource); - resetOutboundAndInbound(); - logger.debug("Registered {}", resource); - sink.success(resource); - } - })); + thread = threadFactory.newThread(worker); + thread.start(); + } catch (Throwable th) { + throw Exceptions.propagate(th); + } } /** - * Disposes resource and remove it from event loop. + * Registers agent in event loop. * * @param resource aeron resource - * @return mono result */ - Mono dispose(AeronResource resource) { - return worker() - .flatMap( - worker -> - command( - sink -> { - resources.remove(resource); - resetOutboundAndInbound(); - logger.debug("Closing {}", resource); - Mono.fromRunnable(resource::close) - .subscribe(null, sink::error, sink::success); - })); + public void register(Agent resource) { + agent.add(resource); } @Override @@ -157,44 +116,6 @@ public boolean isDisposed() { return onDispose.isDisposed(); } - private Mono worker() { - return workerMono.takeUntilOther(listenUnavailable()); - } - - private Mono command(Consumer> consumer) { - return Mono.create(sink -> commands.add(new CommandTask<>(sink, consumer))); - } - - private Mono listenUnavailable() { - return dispose // - .map(avoid -> (T) avoid) - .switchIfEmpty(Mono.error(AeronExceptions::failWithEventLoopUnavailable)); - } - - /** - * Runnable task for submitting to {@link #commands} queue. For usage details see methods: {@link - * #register(AeronResource)} and {@link #dispose(AeronResource)}. - */ - private static class CommandTask implements Runnable { - private final MonoSink sink; - private final Consumer> consumer; - - private CommandTask(MonoSink sink, Consumer> consumer) { - this.sink = sink; - this.consumer = consumer; - } - - @Override - public void run() { - try { - consumer.accept(sink); - } catch (Exception e) { - logger.error("Exception occurred on CommandTask: ", e); - sink.error(e); - } - } - } - /** * Runnable event loop worker. * @@ -209,114 +130,38 @@ private class Worker implements Runnable { private final WorkerFlightRecorder flightRecorder; - public Worker(WorkerFlightRecorder flightRecorder) { + private Worker(WorkerFlightRecorder flightRecorder) { this.flightRecorder = flightRecorder; } @Override public void run() { flightRecorder.start(); + agentInvoker.start(); - while (!dispose.isDisposed()) { - flightRecorder.countTick(); + try { + while (!dispose.isDisposed()) { + flightRecorder.countTick(); - // Commands - processCommands(); + int workCount = agentInvoker.invoke(); - // Outbound - int o = processOutbound(); - flightRecorder.countOutbound(o); + if (workCount > 0) { + flightRecorder.countWork(workCount); + } else { + flightRecorder.countIdle(); + } - // Inbound - int i = processInbound(); - flightRecorder.countInbound(i); + // Reporting + flightRecorder.tryReport(); - int workCount = o + i; - if (workCount < 1) { - flightRecorder.countIdle(); - } else { - flightRecorder.countWork(workCount); + idleStrategy.idle(workCount); } - - // Reporting - flightRecorder.tryReport(); - - idleStrategy.idle(workCount); - } - - // Dispose everything - try { - processCommands(); - disposeResources(); + } catch (Throwable th) { + dispose.dispose(); } finally { - onDispose.onComplete(); - } - } - - private int processInbound() { - int result = 0; - //noinspection ForLoopReplaceableByForEach - for (int i = 0, n = inbounds.size(); i < n; i++) { - try { - result += inbounds.get(i).poll(); - } catch (Exception ex) { - logger.error("Unexpected exception occurred on inbound.poll(): ", ex); - } - } - return result; - } - - private int processOutbound() { - int result = 0; - //noinspection ForLoopReplaceableByForEach - for (int i = 0, n = publications.size(); i < n; i++) { - try { - result += publications.get(i).publish(); - } catch (Exception ex) { - logger.error("Unexpected exception occurred on publication.publish(): ", ex); - } - } - return result; - } - - private void processCommands() { - CommandTask task; - while ((task = commands.poll()) != null) { - task.run(); + CloseHelper.close(agentInvoker); + onDispose.dispose(); } } } - - private void disposeResources() { - for (AeronResource resource : resources) { - try { - resource.close(); - } catch (Exception ex) { - logger.warn("Exception occurred at closing AeronResource: {}, cause: {}", resource, ex); - } - } - resources.clear(); - publications.clear(); - inbounds.clear(); - } - - private boolean cancelIfDisposed(MonoSink sink) { - boolean isDisposed = dispose.isDisposed(); - if (isDisposed) { - sink.error(AeronExceptions.failWithCancel("CommandTask has been cancelled")); - } - return isDisposed; - } - - private void resetOutboundAndInbound() { - publications = filterResources(MessagePublication.class); - inbounds = filterResources(DefaultAeronInbound.class); - } - - private List filterResources(Class clazz) { - return resources.stream() - .filter(clazz::isInstance) - .map(r -> ((T) r)) - .collect(Collectors.toList()); - } } diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronInbound.java b/reactor-aeron/src/main/java/reactor/aeron/AeronInbound.java index ad3a4168..e34988f7 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronInbound.java +++ b/reactor-aeron/src/main/java/reactor/aeron/AeronInbound.java @@ -1,6 +1,9 @@ package reactor.aeron; -public interface AeronInbound { +import reactor.core.Disposable; +import reactor.core.publisher.Flux; - DirectBufferFlux receive(); +public interface AeronInbound extends Disposable { + + Flux receive(); } diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronOutbound.java b/reactor-aeron/src/main/java/reactor/aeron/AeronOutbound.java index d7a9ce57..3aa4a4b9 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronOutbound.java +++ b/reactor-aeron/src/main/java/reactor/aeron/AeronOutbound.java @@ -1,12 +1,16 @@ package reactor.aeron; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import org.agrona.DirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; +import reactor.core.Disposable; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -public interface AeronOutbound extends Publisher { +public interface AeronOutbound extends Publisher, Disposable { /** * Send data to the peer, listen for any error on write and close on terminal signal @@ -28,7 +32,9 @@ public interface AeronOutbound extends Publisher { * @return A new {@link AeronOutbound} to append further send. It will emit a complete signal upon * successful sequence write or an error during write. */ - AeronOutbound send(Publisher dataStream); + default AeronOutbound send(Publisher dataStream) { + return send(dataStream, buffer -> buffer); + } /** * Send data to the peer, listen for any error on write and close on terminal signal @@ -38,7 +44,12 @@ public interface AeronOutbound extends Publisher { * @return A new {@link AeronOutbound} to append further send. It will emit a complete signal upon * successful sequence write or an error during write. */ - AeronOutbound sendBytes(Publisher dataStream); + default AeronOutbound sendBytes(Publisher dataStream) { + if (dataStream instanceof Flux) { + return send(((Flux) dataStream).map(UnsafeBuffer::new)); + } + return send(((Mono) dataStream).map(UnsafeBuffer::new)); + } /** * Send data to the peer, listen for any error on write and close on terminal signal @@ -48,7 +59,18 @@ public interface AeronOutbound extends Publisher { * @return A new {@link AeronOutbound} to append further send. It will emit a complete signal upon * successful sequence write or an error during write. */ - AeronOutbound sendString(Publisher dataStream); + default AeronOutbound sendString(Publisher dataStream) { + if (dataStream instanceof Flux) { + return send( + ((Flux) dataStream) + .map(s -> s.getBytes(StandardCharsets.UTF_8)) + .map(UnsafeBuffer::new)); + } + return send( + ((Mono) dataStream) + .map(s -> s.getBytes(StandardCharsets.UTF_8)) + .map(UnsafeBuffer::new)); + } /** * Send data to the peer, listen for any error on write and close on terminal signal @@ -58,7 +80,12 @@ public interface AeronOutbound extends Publisher { * @return A new {@link AeronOutbound} to append further send. It will emit a complete signal upon * successful sequence write or an error during write. */ - AeronOutbound sendBuffer(Publisher dataStream); + default AeronOutbound sendBuffer(Publisher dataStream) { + if (dataStream instanceof Flux) { + return send(((Flux) dataStream).map(UnsafeBuffer::new)); + } + return send(((Mono) dataStream).map(UnsafeBuffer::new)); + } /** * Obtain a {@link Mono} of pending outbound(s) write completion. diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronOutboundThen.java b/reactor-aeron/src/main/java/reactor/aeron/AeronOutboundThen.java index d30fad82..447a2701 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronOutboundThen.java +++ b/reactor-aeron/src/main/java/reactor/aeron/AeronOutboundThen.java @@ -50,4 +50,14 @@ public AeronOutbound sendBuffer(Publisher dataStream) { public Mono then() { return thenMono; } + + @Override + public void dispose() { + source.dispose(); + } + + @Override + public boolean isDisposed() { + return source.isDisposed(); + } } diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronResource.java b/reactor-aeron/src/main/java/reactor/aeron/AeronResource.java deleted file mode 100644 index 73b1ecc3..00000000 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronResource.java +++ /dev/null @@ -1,6 +0,0 @@ -package reactor.aeron; - -interface AeronResource { - - void close(); -} diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronResources.java b/reactor-aeron/src/main/java/reactor/aeron/AeronResources.java index a941bc96..019e907e 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronResources.java +++ b/reactor-aeron/src/main/java/reactor/aeron/AeronResources.java @@ -1,6 +1,8 @@ package reactor.aeron; import io.aeron.Aeron; +import io.aeron.ChannelUri; +import io.aeron.CommonContext; import io.aeron.ExclusivePublication; import io.aeron.Image; import io.aeron.Publication; @@ -316,7 +318,7 @@ private Mono doStart() { * * @return {@code AeronEventLoop} instance */ - AeronEventLoop nextEventLoop() { + public AeronEventLoop nextEventLoop() { return eventLoopGroup.next(); } @@ -325,45 +327,83 @@ AeronEventLoop nextEventLoop() { * * @return {@code AeronEventLoop} instance */ - AeronEventLoop firstEventLoop() { + public AeronEventLoop firstEventLoop() { return eventLoopGroup.first(); } /** - * Creates and registers {@link DefaultAeronInbound}. + * Returns subscription inbound which registered in event loop. * - * @param image aeron image - * @param subscription subscription - * @param eventLoop aeron event lopop + * @param channel subscription channel + * @param streamId subscription stream id + * @param mapper mapper * @return mono result */ - Mono inbound( - Image image, MessageSubscription subscription, AeronEventLoop eventLoop) { + public Mono> inbound(String channel, int streamId, FragmentMapper mapper) { + return subscription(channel, streamId, null, null) + .map( + subscription -> { + AeronEventLoop eventLoop = nextEventLoop(); + SubscriptionAgent agent = new SubscriptionAgent<>(subscription, mapper, true); + eventLoop.register(agent); + return agent; + }); + } + + /** + * Returns image inbound which registered in event loop. + * + * @param channel image channel + * @param streamId image stream id + * @param mapper mapper + * @return mono result + */ + public Mono> imageInbound( + String channel, int streamId, FragmentMapper mapper) { return Mono.defer( () -> { - DefaultAeronInbound inbound = - new DefaultAeronInbound(image, eventLoop, subscription, pollFragmentLimit); - return eventLoop - .register(inbound) - .doOnError( - ex -> - logger.error( - "{} failed on registerInbound(), cause: {}", this, ex.toString())); + if (!ChannelUri.parse(channel).containsKey(CommonContext.SESSION_ID_PARAM_NAME)) { + throw new IllegalArgumentException("channel should be unique for image inbound"); + } + MonoProcessor imageCallback = MonoProcessor.create(); + return subscription(channel, streamId, imageCallback::onNext, null) + .flatMap(subscription -> imageCallback) + .map( + image -> { + AeronEventLoop eventLoop = nextEventLoop(); + ImageAgent agent = new ImageAgent<>(image, mapper, true); + eventLoop.register(agent); + return agent; + }); }); } /** - * Creates aeron {@link ExclusivePublication} then wraps it into {@link MessagePublication}. - * Result message publication will be assigned to event loop. + * Returns outbound which registered in event loop. + * + * @param channel target channel + * @param streamId target stream id + * @return mono result + */ + public Mono outbound(String channel, int streamId) { + return publication(channel, streamId) + .map( + publication -> { + AeronEventLoop eventLoop = nextEventLoop(); + PublicationAgent agent = new PublicationAgent(publication); + eventLoop.register(agent); + return agent; + }); + } + + /** + * Creates aeron {@link ExclusivePublication}. * * @param channel aeron channel * @param streamId aeron stream id - * @param options aeorn options - * @param eventLoop aeron event loop * @return mono result */ - Mono publication( - String channel, int streamId, AeronOptions options, AeronEventLoop eventLoop) { + Mono publication(String channel, int streamId) { return Mono.defer( () -> aeronPublication(channel, streamId) @@ -374,21 +414,7 @@ Mono publication( "{} failed on aeronPublication(), channel: {}, cause: {}", this, channel, - ex.toString())) - .flatMap( - aeronPublication -> - eventLoop - .register(new MessagePublication(aeronPublication, options, eventLoop)) - .doOnError( - ex -> { - logger.error( - "{} failed on registerPublication(), cause: {}", - this, - ex.toString()); - if (!aeronPublication.isClosed()) { - aeronPublication.close(); - } - }))); + ex.toString()))); } private Mono aeronPublication(String channel, int streamId) { @@ -407,29 +433,20 @@ private Mono aeronPublication(String channel, int streamId) { }); } - @Override - public void dispose() { - dispose.onComplete(); - } - /** - * Creates aeron {@link Subscription} then wraps it into {@link MessageSubscription}. Result - * message subscription will be assigned to event loop. + * Creates aeron {@link Subscription}. * * @param channel aeron channel * @param streamId aeron stream id - * @param eventLoop aeron event loop * @param onImageAvailable available image handler; optional * @param onImageUnavailable unavailable image handler; optional * @return mono result */ - Mono subscription( + Mono subscription( String channel, int streamId, - AeronEventLoop eventLoop, Consumer onImageAvailable, Consumer onImageUnavailable) { - return Mono.defer( () -> aeronSubscription(channel, streamId, onImageAvailable, onImageUnavailable) @@ -440,21 +457,12 @@ Mono subscription( "{} failed on aeronSubscription(), channel: {}, cause: {}", this, channel, - ex.toString())) - .flatMap( - aeronSubscription -> - eventLoop - .register(new MessageSubscription(aeronSubscription, eventLoop)) - .doOnError( - ex -> { - logger.error( - "{} failed on registerSubscription(), cause: {}", - this, - ex.toString()); - if (!aeronSubscription.isClosed()) { - aeronSubscription.close(); - } - }))); + ex.toString()))); + } + + @Override + public void dispose() { + dispose.onComplete(); } private Mono aeronSubscription( diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronServerHandler.java b/reactor-aeron/src/main/java/reactor/aeron/AeronServerHandler.java index 08e5a460..bbe46976 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronServerHandler.java +++ b/reactor-aeron/src/main/java/reactor/aeron/AeronServerHandler.java @@ -1,12 +1,15 @@ package reactor.aeron; import io.aeron.Image; +import io.aeron.Publication; +import io.aeron.Subscription; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; +import org.agrona.CloseHelper; +import org.agrona.DirectBuffer; import org.reactivestreams.Publisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,8 +37,10 @@ final class AeronServerHandler implements OnDisposable { private final AeronOptions options; private final AeronResources resources; private final Function> handler; + private final DefaultFragmentMapper mapper = new DefaultFragmentMapper(); - private volatile MessageSubscription acceptorSubscription; // server acceptor subscription + // private volatile MessageSubscription acceptorSubscription; // server acceptor subscription + private volatile Subscription acceptorSubscription; // server acceptor subscription private final Map> disposeHooks = new ConcurrentHashMap<>(); @@ -63,14 +68,9 @@ Mono start() { String acceptorChannel = options.inboundUri().asString(); logger.debug("Starting {} on: {}", this, acceptorChannel); - return resources .subscription( - acceptorChannel, - STREAM_ID, - resources.firstEventLoop(), - this::onImageAvailable, - this::onImageUnavailable) + acceptorChannel, STREAM_ID, this::onImageAvailable, this::onImageUnavailable) .doOnSuccess(s -> this.acceptorSubscription = s) .thenReturn(this) .doOnSuccess(handler -> logger.debug("Started {} on: {}", this, acceptorChannel)) @@ -99,16 +99,9 @@ private void onImageAvailable(Image image) { logger.debug( "{}: creating server connection: {}", Integer.toHexString(sessionId), outboundChannel); - AeronEventLoop eventLoop = resources.nextEventLoop(); - resources - .publication(outboundChannel, STREAM_ID, options, eventLoop) - .flatMap( - publication -> - resources - .inbound(image, null /*subscription*/, eventLoop) - .doOnError(ex -> publication.dispose()) - .flatMap(inbound -> newConnection(sessionId, publication, inbound))) + .publication(outboundChannel, STREAM_ID) + .flatMap(publication -> newConnection(sessionId, publication, image)) .doOnSuccess( connection -> logger.debug( @@ -125,19 +118,26 @@ private void onImageAvailable(Image image) { } private Mono newConnection( - int sessionId, MessagePublication publication, DefaultAeronInbound inbound) { + int sessionId, Publication publication, Image image) { // setup cleanup hook to use it onwards MonoProcessor disposeHook = MonoProcessor.create(); - disposeHooks.put(sessionId, disposeHook); - - DefaultAeronOutbound outbound = new DefaultAeronOutbound(publication); + PublicationAgent publicationAgent = new PublicationAgent(publication); + ImageAgent imageAgent = new ImageAgent<>(image, mapper, false); DuplexAeronConnection connection = - new DuplexAeronConnection(sessionId, inbound, outbound, disposeHook); + new DuplexAeronConnection(sessionId, imageAgent, publicationAgent, disposeHook); + + disposeHooks.put(sessionId, disposeHook); return connection .start(handler) + .doOnSuccess( + c -> { + AeronEventLoop eventLoop = resources.nextEventLoop(); + eventLoop.register(imageAgent); + eventLoop.register(publicationAgent); + }) .doOnError( ex -> { connection.dispose(); @@ -181,10 +181,7 @@ private Mono doDispose() { List> monos = new ArrayList<>(); // dispose server acceptor subscription - monos.add( - Optional.ofNullable(acceptorSubscription) - .map(s -> Mono.fromRunnable(s::dispose).then(s.onDispose())) - .orElse(Mono.empty())); + monos.add(Mono.fromRunnable(() -> CloseHelper.quietClose(acceptorSubscription))); // dispose all existing connections disposeHooks.values().stream().peek(MonoProcessor::onComplete).forEach(monos::add); diff --git a/reactor-aeron/src/main/java/reactor/aeron/DefaultAeronInbound.java b/reactor-aeron/src/main/java/reactor/aeron/DefaultAeronInbound.java deleted file mode 100644 index b0c767b8..00000000 --- a/reactor-aeron/src/main/java/reactor/aeron/DefaultAeronInbound.java +++ /dev/null @@ -1,187 +0,0 @@ -package reactor.aeron; - -import io.aeron.Image; -import io.aeron.ImageFragmentAssembler; -import io.aeron.logbuffer.FragmentHandler; -import io.aeron.logbuffer.Header; -import java.util.concurrent.atomic.AtomicLongFieldUpdater; -import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; -import org.agrona.DirectBuffer; -import org.agrona.concurrent.UnsafeBuffer; -import org.reactivestreams.Subscription; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.CoreSubscriber; -import reactor.core.Exceptions; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Operators; - -final class DefaultAeronInbound implements AeronInbound, AeronResource { - - private static final Logger logger = LoggerFactory.getLogger(DefaultAeronInbound.class); - - private static final AtomicLongFieldUpdater REQUESTED = - AtomicLongFieldUpdater.newUpdater(DefaultAeronInbound.class, "requested"); - - private static final AtomicReferenceFieldUpdater - DESTINATION_SUBSCRIBER = - AtomicReferenceFieldUpdater.newUpdater( - DefaultAeronInbound.class, CoreSubscriber.class, "destinationSubscriber"); - - private static final CoreSubscriber CANCELLED_SUBSCRIBER = - new CancelledSubscriber(); - - private final int fragmentLimit; - private final Image image; - private final AeronEventLoop eventLoop; - private final FluxReceive inbound = new FluxReceive(); - private final FragmentHandler fragmentHandler = - new ImageFragmentAssembler(new FragmentHandlerImpl()); - private final MessageSubscription subscription; - - private volatile long requested; - private volatile boolean fastpath; - private long produced; - private volatile CoreSubscriber destinationSubscriber; - - /** - * Constructor. - * - * @param image image - * @param eventLoop event loop - * @param subscription subscription - * @param fragmentLimit fragment limit - */ - DefaultAeronInbound( - Image image, AeronEventLoop eventLoop, MessageSubscription subscription, int fragmentLimit) { - this.image = image; - this.eventLoop = eventLoop; - this.subscription = subscription; - this.fragmentLimit = fragmentLimit; - } - - @Override - public DirectBufferFlux receive() { - return new DirectBufferFlux(inbound); - } - - int poll() { - if (destinationSubscriber == CANCELLED_SUBSCRIBER) { - return 0; - } - if (fastpath) { - return image.poll(fragmentHandler, fragmentLimit); - } - int r = (int) Math.min(requested, fragmentLimit); - int fragments = 0; - if (r > 0) { - fragments = image.poll(fragmentHandler, r); - if (produced > 0) { - Operators.produced(REQUESTED, this, produced); - produced = 0; - } - } - return fragments; - } - - @Override - public void close() { - if (!eventLoop.inEventLoop()) { - throw AeronExceptions.failWithResourceDisposal("aeron inbound"); - } - inbound.cancel(); - logger.debug("Cancelled inbound"); - } - - void dispose() { - eventLoop - .dispose(this) - .subscribe( - null, - th -> { - // no-op - }); - if (subscription != null) { - subscription.dispose(); - } - } - - private class FragmentHandlerImpl implements FragmentHandler { - - @Override - public void onFragment(DirectBuffer buffer, int offset, int length, Header header) { - produced++; - - CoreSubscriber destination = - DefaultAeronInbound.this.destinationSubscriber; - - destination.onNext(new UnsafeBuffer(buffer, offset, length)); - } - } - - private class FluxReceive extends Flux implements Subscription { - - @Override - public void request(long n) { - if (fastpath) { - return; - } - if (n == Long.MAX_VALUE) { - fastpath = true; - requested = Long.MAX_VALUE; - return; - } - Operators.addCap(REQUESTED, DefaultAeronInbound.this, n); - } - - @Override - public void cancel() { - CoreSubscriber destination = - DESTINATION_SUBSCRIBER.getAndSet(DefaultAeronInbound.this, CANCELLED_SUBSCRIBER); - if (destination != null) { - destination.onComplete(); - } - logger.debug( - "Destination subscriber on aeron inbound has been cancelled, session id {}", - Integer.toHexString(image.sessionId())); - } - - @Override - public void subscribe(CoreSubscriber destinationSubscriber) { - boolean result = - DESTINATION_SUBSCRIBER.compareAndSet( - DefaultAeronInbound.this, null, destinationSubscriber); - if (result) { - destinationSubscriber.onSubscribe(this); - } else { - // only subscriber is allowed on receive() - Operators.error(destinationSubscriber, Exceptions.duplicateOnSubscribeException()); - } - } - } - - private static class CancelledSubscriber implements CoreSubscriber { - - @Override - public void onSubscribe(Subscription s) { - // no-op - } - - @Override - public void onNext(DirectBuffer directBuffer) { - logger.warn( - "Received buffer(len={}) which will be dropped immediately due cancelled aeron inbound", - directBuffer.capacity()); - } - - @Override - public void onError(Throwable t) { - // no-op - } - - @Override - public void onComplete() { - // no-op - } - } -} diff --git a/reactor-aeron/src/main/java/reactor/aeron/DefaultAeronOutbound.java b/reactor-aeron/src/main/java/reactor/aeron/DefaultAeronOutbound.java deleted file mode 100644 index a9efefcd..00000000 --- a/reactor-aeron/src/main/java/reactor/aeron/DefaultAeronOutbound.java +++ /dev/null @@ -1,92 +0,0 @@ -package reactor.aeron; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import org.agrona.DirectBuffer; -import org.agrona.concurrent.UnsafeBuffer; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -final class DefaultAeronOutbound implements AeronOutbound { - - private final MessagePublication publication; - - /** - * Constructor. - * - * @param publication message publication - */ - DefaultAeronOutbound(MessagePublication publication) { - this.publication = publication; - } - - @Override - public AeronOutbound send( - Publisher dataStream, DirectBufferHandler bufferHandler) { - return then(publication.publish(dataStream, bufferHandler)); - } - - @Override - public AeronOutbound send(Publisher dataStream) { - return send(dataStream, DirectBufferHandlerImpl.DEFAULT_INSTANCE); - } - - @Override - public AeronOutbound sendBytes(Publisher dataStream) { - if (dataStream instanceof Flux) { - return send(((Flux) dataStream).map(UnsafeBuffer::new)); - } - return send(((Mono) dataStream).map(UnsafeBuffer::new)); - } - - @Override - public AeronOutbound sendString(Publisher dataStream) { - if (dataStream instanceof Flux) { - return send( - ((Flux) dataStream) - .map(s -> s.getBytes(StandardCharsets.UTF_8)) - .map(UnsafeBuffer::new)); - } - return send( - ((Mono) dataStream) - .map(s -> s.getBytes(StandardCharsets.UTF_8)) - .map(UnsafeBuffer::new)); - } - - @Override - public AeronOutbound sendBuffer(Publisher dataStream) { - if (dataStream instanceof Flux) { - return send(((Flux) dataStream).map(UnsafeBuffer::new)); - } - return send(((Mono) dataStream).map(UnsafeBuffer::new)); - } - - void dispose() { - publication.dispose(); - } - - /** - * Default implementation of {@link DirectBufferHandler} with aeron buffer type {@link - * DirectBuffer}. Function {@link #dispose()} does nothing. - */ - private static class DirectBufferHandlerImpl implements DirectBufferHandler { - - private static final DirectBufferHandlerImpl DEFAULT_INSTANCE = new DirectBufferHandlerImpl(); - - @Override - public int estimateLength(DirectBuffer buffer) { - return buffer.capacity(); - } - - @Override - public DirectBuffer map(DirectBuffer buffer, int length) { - return buffer; - } - - @Override - public void dispose(DirectBuffer buffer) { - // no-op - } - } -} diff --git a/reactor-aeron/src/main/java/reactor/aeron/DefaultFragmentMapper.java b/reactor-aeron/src/main/java/reactor/aeron/DefaultFragmentMapper.java new file mode 100644 index 00000000..fc7a1d92 --- /dev/null +++ b/reactor-aeron/src/main/java/reactor/aeron/DefaultFragmentMapper.java @@ -0,0 +1,25 @@ +package reactor.aeron; + +import io.aeron.logbuffer.Header; +import java.nio.charset.StandardCharsets; +import org.agrona.DirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import reactor.core.publisher.Flux; + +public class DefaultFragmentMapper implements FragmentMapper { + + @Override + public DirectBuffer apply(DirectBuffer buffer, int offset, int length, Header header) { + return new UnsafeBuffer(buffer, offset, length); + } + + public static Flux asString(Flux flux) { + return flux.cast(DirectBuffer.class) + .map( + buffer -> { + byte[] bytes = new byte[buffer.capacity()]; + buffer.getBytes(0, bytes); + return new String(bytes, StandardCharsets.UTF_8); + }); + } +} diff --git a/reactor-aeron/src/main/java/reactor/aeron/DirectBufferFlux.java b/reactor-aeron/src/main/java/reactor/aeron/DirectBufferFlux.java deleted file mode 100644 index ffbd5c40..00000000 --- a/reactor-aeron/src/main/java/reactor/aeron/DirectBufferFlux.java +++ /dev/null @@ -1,33 +0,0 @@ -package reactor.aeron; - -import java.nio.charset.StandardCharsets; -import org.agrona.DirectBuffer; -import reactor.core.CoreSubscriber; -import reactor.core.publisher.Flux; -import reactor.core.publisher.FluxOperator; - -public final class DirectBufferFlux extends FluxOperator { - - public DirectBufferFlux(Flux source) { - super(source); - } - - @Override - public void subscribe(CoreSubscriber s) { - source.subscribe(s); - } - - /** - * Applies transformation {@link DirectBuffer} to {@code String}. - * - * @return {@code Flux} instance - */ - public Flux asString() { - return map( - buffer -> { - byte[] bytes = new byte[buffer.capacity()]; - buffer.getBytes(0, bytes); - return new String(bytes, StandardCharsets.UTF_8); - }); - } -} diff --git a/reactor-aeron/src/main/java/reactor/aeron/DirectBufferHandler.java b/reactor-aeron/src/main/java/reactor/aeron/DirectBufferHandler.java index dd74bd6a..fd85ca59 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/DirectBufferHandler.java +++ b/reactor-aeron/src/main/java/reactor/aeron/DirectBufferHandler.java @@ -2,11 +2,12 @@ import org.agrona.DirectBuffer; +@FunctionalInterface public interface DirectBufferHandler { - int estimateLength(B buffer); + DirectBuffer map(B buffer); - DirectBuffer map(B buffer, int length); - - void dispose(B buffer); + default void dispose(B buffer) { + // no-op + } } diff --git a/reactor-aeron/src/main/java/reactor/aeron/DuplexAeronConnection.java b/reactor-aeron/src/main/java/reactor/aeron/DuplexAeronConnection.java index 6ea7c15d..f0edb2d2 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/DuplexAeronConnection.java +++ b/reactor-aeron/src/main/java/reactor/aeron/DuplexAeronConnection.java @@ -17,8 +17,8 @@ final class DuplexAeronConnection implements AeronConnection { private final int sessionId; - private final DefaultAeronInbound inbound; - private final DefaultAeronOutbound outbound; + private final AeronInbound inbound; + private final AeronOutbound outbound; private final MonoProcessor dispose = MonoProcessor.create(); private final MonoProcessor onDispose = MonoProcessor.create(); @@ -33,8 +33,8 @@ final class DuplexAeronConnection implements AeronConnection { */ DuplexAeronConnection( int sessionId, - DefaultAeronInbound inbound, - DefaultAeronOutbound outbound, + AeronInbound inbound, + AeronOutbound outbound, MonoProcessor disposeHook) { this.sessionId = sessionId; diff --git a/reactor-aeron/src/main/java/reactor/aeron/DynamicCompositeAgent.java b/reactor-aeron/src/main/java/reactor/aeron/DynamicCompositeAgent.java new file mode 100644 index 00000000..8adf7665 --- /dev/null +++ b/reactor-aeron/src/main/java/reactor/aeron/DynamicCompositeAgent.java @@ -0,0 +1,208 @@ +package reactor.aeron; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Objects; +import java.util.Queue; +import org.agrona.collections.ArrayListUtil; +import org.agrona.concurrent.Agent; +import org.agrona.concurrent.ManyToOneConcurrentLinkedQueue; + +/** + * Group several {@link Agent}s into one composite so they can be scheduled as a unit. + * + *

{@link Agent}s can be dynamically added and removed. + * + *

Note: This class is thread-safe for add and remove. + */ +public class DynamicCompositeAgent implements Agent { + + public enum Status { + /** Agent is being initialised and has not yet been started. */ + INIT, + + /** Agent is not active after a successful {@link #onStart()}. */ + ACTIVE, + + /** Agent has been closed. */ + CLOSED + } + + private final String roleName; + + private final Queue commands = new ManyToOneConcurrentLinkedQueue<>(); + private final ArrayList agents; + + private volatile Status status = Status.INIT; + + /** + * Construct a new composite that has no {@link Agent}s to begin with. + * + * @param roleName to be given for {@link Agent#roleName()}. + */ + public DynamicCompositeAgent(String roleName) { + this.roleName = roleName; + this.agents = new ArrayList<>(); + } + + /** + * Construct a new composite with collection of {@link Agent}s. + * + * @param roleName to be given for {@link Agent#roleName()}. + * @param agents the parts of this composite, at least one agent and no null agents allowed + * @throws NullPointerException if the array or any element is null + */ + public DynamicCompositeAgent(String roleName, Collection agents) { + this.roleName = roleName; + this.agents = new ArrayList<>(agents.size()); + for (Agent agent : agents) { + Objects.requireNonNull(agent, "agent cannot be null"); + this.agents.add(agent); + } + } + + /** + * Construct a new composite with one or several {@link Agent}s. + * + * @param roleName to be given for {@link Agent#roleName()}. + * @param agents the parts of this composite, at least one agent and no null agents allowed + * @throws NullPointerException if the array or any element is null + */ + public DynamicCompositeAgent(String roleName, Agent... agents) { + this.roleName = roleName; + this.agents = new ArrayList<>(agents.length); + for (Agent agent : agents) { + Objects.requireNonNull(agent, "agent cannot be null"); + this.agents.add(agent); + } + } + + /** + * Get the {@link Status} for the Agent. + * + * @return the {@link Status} for the Agent. + */ + public Status status() { + return status; + } + + @Override + public void onStart() { + for (Agent agent : agents) { + agent.onStart(); + } + + status = Status.ACTIVE; + } + + @Override + public int doWork() { + int workCount = 0; + + processCommands(); + + ArrayList agents = this.agents; + for (int lastIndex = agents.size() - 1, i = lastIndex; i >= 0; i--) { + Agent agent = agents.get(i); + try { + int result = agent.doWork(); + if (result > 0) { + workCount += result; + } + if (result < 0) { + ArrayListUtil.fastUnorderedRemove(agents, i, lastIndex--); + safetyClose(agent); + } + } catch (Throwable th) { + ArrayListUtil.fastUnorderedRemove(agents, i, lastIndex--); + safetyClose(agent); + } + } + + return workCount; + } + + @Override + public void onClose() { + status = Status.CLOSED; + + processCommands(); + + agents.forEach(this::safetyClose); + agents.clear(); + } + + @Override + public String roleName() { + return roleName; + } + + /** + * Add a new {@link Agent} to the composite. + * + *

The agent will be added during the next invocation of {@link #doWork()} if this operation is + * successful. If the {@link Agent#onStart()} method throws an exception then it will not be added + * and {@link Agent#onClose()} will be called. + * + * @param agent to be added to the composite. + */ + public void add(Agent agent) { + Objects.requireNonNull(agent, "agent cannot be null"); + if (Status.ACTIVE != status) { + throw new IllegalStateException("add called when not active"); + } + commands.add(() -> add0(agent)); + } + + /** + * Remove an {@link Agent} from the composite. + * + *

The agent is removed during the next {@link #doWork()} duty cycle if this operation is + * successful. The {@link Agent} is removed by identity. Only the first found is removed. + * + * @param agent to be removed. + */ + public void remove(Agent agent) { + Objects.requireNonNull(agent, "agent cannot be null"); + if (Status.ACTIVE != status) { + throw new IllegalStateException("remove called when not active"); + } + commands.add(() -> remove0(agent)); + } + + private void add0(Agent agent) { + if (Status.ACTIVE != status) { + safetyClose(agent); + return; + } + try { + agent.onStart(); + agents.add(agent); + } catch (Throwable th) { + safetyClose(agent); + } + } + + private void remove0(Agent agent) { + if (Status.ACTIVE != status) { + return; + } + if (ArrayListUtil.fastUnorderedRemove(agents, agent)) { + safetyClose(agent); + } + } + + private void safetyClose(Agent agent) { + try { + agent.onClose(); + } catch (Throwable ignored) { + // ignore + } + } + + private void processCommands() { + for (Runnable action = commands.poll(); action != null; action = commands.poll()) { + action.run(); + } + } +} diff --git a/reactor-aeron/src/main/java/reactor/aeron/FragmentMapper.java b/reactor-aeron/src/main/java/reactor/aeron/FragmentMapper.java new file mode 100644 index 00000000..6edd7432 --- /dev/null +++ b/reactor-aeron/src/main/java/reactor/aeron/FragmentMapper.java @@ -0,0 +1,17 @@ +package reactor.aeron; + +import io.aeron.logbuffer.Header; +import org.agrona.DirectBuffer; +import org.agrona.concurrent.AgentTerminationException; + +public interface FragmentMapper { + + /** + * Converts the given arguments to a T instance. Also can return the null value if the T instance + * is not ready to publish. Any thrown exception inside this method will cancel polling. If the + * mapper wished to terminate and close then a {@link AgentTerminationException} can be thrown. + * + * @return null to indicate no instance was currently available, a T instance otherwise. + */ + T apply(DirectBuffer buffer, int offset, int length, Header header); +} diff --git a/reactor-aeron/src/main/java/reactor/aeron/ImageAgent.java b/reactor-aeron/src/main/java/reactor/aeron/ImageAgent.java new file mode 100644 index 00000000..88e5f143 --- /dev/null +++ b/reactor-aeron/src/main/java/reactor/aeron/ImageAgent.java @@ -0,0 +1,229 @@ +package reactor.aeron; + +import io.aeron.Aeron; +import io.aeron.Image; +import io.aeron.ImageFragmentAssembler; +import io.aeron.logbuffer.FragmentHandler; +import io.aeron.logbuffer.Header; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import org.agrona.CloseHelper; +import org.agrona.DirectBuffer; +import org.agrona.concurrent.Agent; +import org.agrona.concurrent.AgentTerminationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Exceptions; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Operators; + +public class ImageAgent implements Agent, AeronInbound, Disposable { + + private static final Logger LOGGER = LoggerFactory.getLogger(ImageAgent.class); + + private static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(ImageAgent.class, "requested"); + + private static final AtomicReferenceFieldUpdater + DESTINATION_SUBSCRIBER = + AtomicReferenceFieldUpdater.newUpdater( + ImageAgent.class, CoreSubscriber.class, "destinationSubscriber"); + + private static final CoreSubscriber CANCELLED_SUBSCRIBER = new CancelledSubscriber(); + + private static final int FRAGMENT_LIMIT = 10; + + private final FluxReceive inbound = new FluxReceive(); + + private final Image image; + private final boolean shouldCloseSubscription; + private final long stopPosition; + + private final FragmentMapper mapper; + private final FragmentHandler fragmentHandler = + new ImageFragmentAssembler(new AgentFragmentHandler()); + + private volatile long requested; + private volatile boolean fastPath; + private long produced; + private volatile CoreSubscriber destinationSubscriber; + private Exception ex; + + public ImageAgent(Image image, FragmentMapper mapper, boolean shouldCloseSubscription) { + this.image = image; + this.mapper = mapper; + this.shouldCloseSubscription = shouldCloseSubscription; + this.stopPosition = Aeron.NULL_VALUE; + } + + public ImageAgent( + Image image, FragmentMapper mapper, boolean shouldCloseSubscription, long stopPosition) { + this.image = image; + this.mapper = mapper; + this.shouldCloseSubscription = shouldCloseSubscription; + this.stopPosition = stopPosition; + } + + @Override + public void onStart() { + // no-op + } + + @Override + public int doWork() { + if (CANCELLED_SUBSCRIBER.equals(destinationSubscriber)) { + throw new AgentTerminationException("Subscription is cancelled"); + } + if (ex != null) { + if (ex instanceof AgentTerminationException) { + throw (AgentTerminationException) ex; + } + CoreSubscriber destination = + DESTINATION_SUBSCRIBER.getAndSet(ImageAgent.this, CANCELLED_SUBSCRIBER); + if (destination != null) { + destination.onError(ex); + } + throw new AgentTerminationException(ex); + } + if (image.position() == stopPosition) { + LOGGER.debug("Image {} achieved specified stop position {}", image.sessionId(), stopPosition); + throw new AgentTerminationException(); + } + if (image.isClosed()) { + CoreSubscriber destination = + DESTINATION_SUBSCRIBER.getAndSet(ImageAgent.this, CANCELLED_SUBSCRIBER); + if (destination != null) { + destination.onError(new AgentTerminationException("Image is closed")); + } + throw new AgentTerminationException("Image is closed"); + } + if (fastPath) { + return image.poll(fragmentHandler, FRAGMENT_LIMIT); + } + int r = (int) Math.min(requested, FRAGMENT_LIMIT); + int fragments = 0; + if (r > 0) { + fragments = image.poll(fragmentHandler, r); + if (produced > 0) { + Operators.produced(REQUESTED, this, produced); + produced = 0; + } + } + return fragments; + } + + @Override + public void onClose() { + inbound.cancel(); + LOGGER.debug("Cancelled inbound"); + if (shouldCloseSubscription) { + CloseHelper.quietClose(image.subscription()); + } + } + + @Override + public String roleName() { + return ImageAgent.class.getName() + ":" + image.sessionId(); + } + + @Override + public Flux receive() { + return inbound; + } + + @Override + public void dispose() { + CoreSubscriber destination = + DESTINATION_SUBSCRIBER.getAndSet(ImageAgent.this, CANCELLED_SUBSCRIBER); + if (destination != null) { + destination.onError(new AgentTerminationException("Image Inbound has been disposed")); + } + } + + @Override + public boolean isDisposed() { + return CANCELLED_SUBSCRIBER.equals(destinationSubscriber); + } + + private class AgentFragmentHandler implements FragmentHandler { + + @Override + public void onFragment(DirectBuffer buffer, int offset, int length, Header header) { + try { + if (ex == null) { + T t = mapper.apply(buffer, offset, length, header); + if (t != null) { + produced++; + CoreSubscriber destination = ImageAgent.this.destinationSubscriber; + destination.onNext(t); + } + } + } catch (Exception e) { + ex = e; + } + } + } + + private class FluxReceive extends Flux implements org.reactivestreams.Subscription { + + @Override + public void request(long n) { + if (fastPath) { + return; + } + if (n == Long.MAX_VALUE) { + fastPath = true; + requested = Long.MAX_VALUE; + return; + } + Operators.addCap(REQUESTED, ImageAgent.this, n); + } + + @Override + public void cancel() { + CoreSubscriber destination = + DESTINATION_SUBSCRIBER.getAndSet(ImageAgent.this, CANCELLED_SUBSCRIBER); + if (destination != null) { + destination.onComplete(); + } + LOGGER.debug("Destination subscriber on aeron inbound has been cancelled"); + } + + @Override + public void subscribe(CoreSubscriber destinationSubscriber) { + boolean result = + DESTINATION_SUBSCRIBER.compareAndSet(ImageAgent.this, null, destinationSubscriber); + if (result) { + destinationSubscriber.onSubscribe(this); + } else { + // only subscriber is allowed on receive() + Operators.error(destinationSubscriber, Exceptions.duplicateOnSubscribeException()); + } + } + } + + private static class CancelledSubscriber implements CoreSubscriber { + + @Override + public void onSubscribe(org.reactivestreams.Subscription s) { + // no-op + } + + @Override + public void onNext(Object o) { + LOGGER.warn("Received ({}) which will be dropped immediately due cancelled aeron inbound", o); + } + + @Override + public void onError(Throwable t) { + // no-op + } + + @Override + public void onComplete() { + // no-op + } + } +} diff --git a/reactor-aeron/src/main/java/reactor/aeron/MessageSubscription.java b/reactor-aeron/src/main/java/reactor/aeron/MessageSubscription.java deleted file mode 100644 index ee6d60db..00000000 --- a/reactor-aeron/src/main/java/reactor/aeron/MessageSubscription.java +++ /dev/null @@ -1,76 +0,0 @@ -package reactor.aeron; - -import io.aeron.Subscription; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.Exceptions; -import reactor.core.publisher.Mono; -import reactor.core.publisher.MonoProcessor; - -class MessageSubscription implements OnDisposable, AeronResource { - - private static final Logger logger = LoggerFactory.getLogger(MessageSubscription.class); - - private final AeronEventLoop eventLoop; - private final Subscription subscription; // aeron subscription - - private final MonoProcessor onDispose = MonoProcessor.create(); - - /** - * Constructor. - * - * @param subscription aeron subscription - * @param eventLoop event loop where this {@code MessageSubscription} is assigned - */ - MessageSubscription(Subscription subscription, AeronEventLoop eventLoop) { - this.subscription = subscription; - this.eventLoop = eventLoop; - } - - @Override - public void close() { - if (!eventLoop.inEventLoop()) { - throw AeronExceptions.failWithResourceDisposal("aeron subscription"); - } - try { - subscription.close(); - logger.debug("Disposed {}", this); - } catch (Exception ex) { - logger.warn("{} failed on aeron.Subscription close(): {}", this, ex.toString()); - throw Exceptions.propagate(ex); - } finally { - onDispose.onComplete(); - } - } - - @Override - public void dispose() { - eventLoop - .dispose(this) - .subscribe( - null, - th -> { - // no-op - }); - } - - /** - * Delegates to {@link Subscription#isClosed()}. - * - * @return {@code true} if aeron {@code Subscription} is closed, {@code false} otherwise - */ - @Override - public boolean isDisposed() { - return subscription.isClosed(); - } - - @Override - public Mono onDispose() { - return onDispose; - } - - @Override - public String toString() { - return "MessageSubscription{sub=" + subscription.channel() + "}"; - } -} diff --git a/reactor-aeron/src/main/java/reactor/aeron/MessagePublication.java b/reactor-aeron/src/main/java/reactor/aeron/PublicationAgent.java similarity index 54% rename from reactor-aeron/src/main/java/reactor/aeron/MessagePublication.java rename to reactor-aeron/src/main/java/reactor/aeron/PublicationAgent.java index 9d536407..04b16485 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/MessagePublication.java +++ b/reactor-aeron/src/main/java/reactor/aeron/PublicationAgent.java @@ -2,79 +2,60 @@ import io.aeron.Publication; import java.time.Duration; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import org.agrona.CloseHelper; import org.agrona.collections.ArrayUtil; +import org.agrona.concurrent.Agent; +import org.agrona.concurrent.AgentTerminationException; import org.reactivestreams.Publisher; import org.reactivestreams.Subscription; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import reactor.core.Disposable; import reactor.core.Exceptions; import reactor.core.publisher.BaseSubscriber; import reactor.core.publisher.Mono; import reactor.core.publisher.MonoProcessor; import reactor.core.publisher.SignalType; -class MessagePublication implements OnDisposable, AeronResource { +public final class PublicationAgent implements Agent, AeronOutbound, Disposable { - private static final Logger logger = LoggerFactory.getLogger(MessagePublication.class); + private static final Logger logger = LoggerFactory.getLogger(PublicationAgent.class); - private static final AtomicReferenceFieldUpdater + private static final AtomicReferenceFieldUpdater PUBLISHER_PROCESSORS = AtomicReferenceFieldUpdater.newUpdater( - MessagePublication.class, PublisherProcessor[].class, "publisherProcessors"); + PublicationAgent.class, PublisherProcessor[].class, "publisherProcessors"); private final Publication publication; - private final AeronEventLoop eventLoop; - private final Duration connectTimeout; - private final Duration backpressureTimeout; - private final Duration adminActionTimeout; - private volatile Throwable lastError; - - private final MonoProcessor onDispose = MonoProcessor.create(); + private final Duration connectTimeout = Duration.ofSeconds(5); + private final Duration backpressureTimeout = Duration.ofSeconds(5); + private final Duration adminActionTimeout = Duration.ofSeconds(5); private volatile PublisherProcessor[] publisherProcessors = new PublisherProcessor[0]; - /** - * Constructor. - * - * @param publication aeron publication - * @param options aeron options - * @param eventLoop aeron event loop where this {@code MessagePublication} is assigned - */ - MessagePublication(Publication publication, AeronOptions options, AeronEventLoop eventLoop) { - this.publication = publication; - this.eventLoop = eventLoop; - this.connectTimeout = options.connectTimeout(); - this.backpressureTimeout = options.backpressureTimeout(); - this.adminActionTimeout = options.adminActionTimeout(); + private volatile Throwable lastError; + + private volatile boolean isDisposed = false; + + public PublicationAgent(Publication publication) { + this.publication = Objects.requireNonNull(publication, "publication cannot be null"); } - /** - * Enqueues publisher for future sending. - * - * @param publisher abstract publisher to process messages from - * @param bufferHandler abstract buffer handler - * @return mono handle - */ - Mono publish(Publisher publisher, DirectBufferHandler bufferHandler) { - return Mono.defer( - () -> { - PublisherProcessor processor = new PublisherProcessor<>(bufferHandler, this); - publisher.subscribe(processor); - return processor.onDispose(); - }); + @Override + public void onStart() { + // no-op } - /** - * Makes a progress at processing publisher processors collection. See for details {@link - * PublisherProcessor}. - * - * @return more than or equal {@code 1} - some progress was done; {@code 0} - denotes no progress - * was done - */ - int publish() { + @Override + public int doWork() throws Exception { + if (isDisposed || publication.isClosed()) { + logger.warn("aeron.Publication is CLOSED: {}", this); + throw new AgentTerminationException("aeron.Publication is CLOSED"); + } PublisherProcessor[] oldArray = this.publisherProcessors; int result = 0; @@ -97,7 +78,10 @@ int publish() { try { r = processor.publish(buffer); } catch (Exception e) { - ex = e; + // finish only the current processor with the given exception and continue + processor.cancelDueTo(e); + processor.removeSelf(); + continue; } if (r > 0) { @@ -109,14 +93,13 @@ int publish() { // Handle closed publication if (r == Publication.CLOSED) { logger.warn("aeron.Publication is CLOSED: {}", this); - ex = AeronExceptions.failWithPublication("aeron.Publication is CLOSED"); + ex = new AgentTerminationException("aeron.Publication is CLOSED"); } // Handle max position exceeded if (r == Publication.MAX_POSITION_EXCEEDED) { logger.warn("aeron.Publication received MAX_POSITION_EXCEEDED: {}", this); - ex = - AeronExceptions.failWithPublication("aeron.Publication received MAX_POSITION_EXCEEDED"); + ex = new AgentTerminationException("aeron.Publication received MAX_POSITION_EXCEEDED"); } // Handle failed connection @@ -126,8 +109,7 @@ int publish() { "aeron.Publication failed to resolve NOT_CONNECTED within {} ms, {}", connectTimeout.toMillis(), this); - ex = - AeronExceptions.failWithPublication("Failed to resolve NOT_CONNECTED within timeout"); + ex = new AgentTerminationException("Failed to resolve NOT_CONNECTED within timeout"); } } @@ -138,9 +120,7 @@ int publish() { "aeron.Publication failed to resolve BACK_PRESSURED within {} ms, {}", backpressureTimeout.toMillis(), this); - ex = - AeronExceptions.failWithPublication( - "Failed to resolve BACK_PRESSURED within timeout"); + ex = new AgentTerminationException("Failed to resolve BACK_PRESSURED within timeout"); } } @@ -151,7 +131,7 @@ int publish() { "aeron.Publication failed to resolve ADMIN_ACTION within {} ms, {}", adminActionTimeout.toMillis(), this); - ex = AeronExceptions.failWithPublication("Failed to resolve ADMIN_ACTION within timeout"); + ex = new AgentTerminationException("Failed to resolve ADMIN_ACTION within timeout"); } } @@ -162,118 +142,63 @@ int publish() { if (ex != null) { lastError = ex; - dispose(); + throw ex; } return result; } @Override - public void close() { - if (!eventLoop.inEventLoop()) { - throw AeronExceptions.failWithResourceDisposal("aeron publication"); - } - try { - publication.close(); - logger.debug("Disposed {}", this); - } catch (Exception ex) { - logger.warn("{} failed on aeron.Publication close(): {}", this, ex.toString()); - throw Exceptions.propagate(ex); - } finally { - disposeProcessors(); - onDispose.onComplete(); + public void onClose() { + isDisposed = true; + CloseHelper.quietClose(publication); + + // dispose processors + PublisherProcessor[] oldArray = this.publisherProcessors; + this.publisherProcessors = new PublisherProcessor[0]; + Throwable throwable = + Optional.ofNullable(lastError) + .orElse(new AgentTerminationException("PublisherProcessor has been cancelled")); + for (PublisherProcessor processor : oldArray) { + processor.cancelDueTo(throwable); } } - /** - * Delegates to {@link Publication#sessionId()}. - * - * @return aeron {@code Publication} sessionId. - */ - int sessionId() { - return publication.sessionId(); + @Override + public String roleName() { + return PublicationAgent.class.getName() + ":" + publication.sessionId(); } - /** - * Delegates to {@link Publication#isClosed()}. - * - * @return {@code true} if aeron {@code Publication} is closed, {@code false} otherwise - */ @Override - public boolean isDisposed() { - return publication.isClosed(); + public void dispose() { + isDisposed = true; } @Override - public void dispose() { - eventLoop - .dispose(this) - .subscribe( - null, - th -> { - // no-op - }); + public boolean isDisposed() { + return isDisposed; } @Override - public Mono onDispose() { - return onDispose; + public AeronOutbound send(Publisher dataStream, + DirectBufferHandler bufferHandler) { + return then(publish(dataStream, bufferHandler)); } - /** - * Spins (in async fashion) until {@link Publication#isConnected()} would have returned {@code - * true} or {@code connectTimeout} elapsed. See also {@link - * MessagePublication#ensureConnected0()}. - * - * @return mono result - */ - Mono ensureConnected() { + private Mono publish( + Publisher publisher, DirectBufferHandler bufferHandler) { return Mono.defer( () -> { - Duration retryInterval = Duration.ofMillis(100); - long retryCount = Math.max(connectTimeout.toMillis() / retryInterval.toMillis(), 1); - - return ensureConnected0() - .retryBackoff(retryCount, retryInterval, retryInterval) - .doOnError( - ex -> logger.warn("aeron.Publication is not connected after several retries")) - .thenReturn(this); + PublisherProcessor processor = new PublisherProcessor<>(bufferHandler, this); + publisher.subscribe(processor); + return processor.onDispose(); }); } - private Mono ensureConnected0() { - return Mono.defer( - () -> - publication.isConnected() - ? Mono.empty() - : Mono.error( - AeronExceptions.failWithPublication("aeron.Publication is not connected"))); - } - - private void disposeProcessors() { - PublisherProcessor[] oldArray = this.publisherProcessors; - this.publisherProcessors = new PublisherProcessor[0]; - for (PublisherProcessor processor : oldArray) { - try { - processor.cancel(); - processor.onParentError( - Optional.ofNullable(lastError) - .orElse(AeronExceptions.failWithCancel("PublisherProcessor has been cancelled"))); - } catch (Exception ex) { - // no-op - } - } - } - - @Override - public String toString() { - return "MessagePublication{pub=" + publication.channel() + "}"; - } - - private static class PublisherProcessor extends BaseSubscriber implements OnDisposable { + private static class PublisherProcessor extends BaseSubscriber { private final DirectBufferHandler bufferHandler; - private final MessagePublication parent; + private final PublicationAgent parent; private long start; private boolean requested; @@ -283,15 +208,13 @@ private static class PublisherProcessor extends BaseSubscriber implements private volatile B buffer; private volatile Throwable error; - PublisherProcessor( - DirectBufferHandler bufferHandler, MessagePublication messagePublication) { + PublisherProcessor(DirectBufferHandler bufferHandler, PublicationAgent parent) { this.bufferHandler = bufferHandler; - this.parent = messagePublication; + this.parent = parent; addSelf(); } - @Override - public Mono onDispose() { + private Mono onDispose() { return onDispose; } @@ -307,11 +230,6 @@ void request() { } } - void onParentError(Throwable throwable) { - resetBuffer(); - onDispose.onError(throwable); - } - void reset() { resetBuffer(); @@ -364,14 +282,23 @@ long publish(B buffer) { if (start == 0) { start = System.currentTimeMillis(); } - int length = bufferHandler.estimateLength(buffer); - return parent.publication.offer(bufferHandler.map(buffer, length)); + return parent.publication.offer(bufferHandler.map(buffer)); } boolean isTimeoutElapsed(Duration timeout) { return System.currentTimeMillis() - start > timeout.toMillis(); } + void cancelDueTo(Throwable throwable) { + try { + cancel(); + resetBuffer(); + onDispose.onError(throwable); + } catch (Exception ex) { + // no-op + } + } + private void resetBuffer() { B oldBuffer = buffer; buffer = null; diff --git a/reactor-aeron/src/main/java/reactor/aeron/SubscriptionAgent.java b/reactor-aeron/src/main/java/reactor/aeron/SubscriptionAgent.java new file mode 100644 index 00000000..06fc31ce --- /dev/null +++ b/reactor-aeron/src/main/java/reactor/aeron/SubscriptionAgent.java @@ -0,0 +1,215 @@ +package reactor.aeron; + +import io.aeron.ImageFragmentAssembler; +import io.aeron.Subscription; +import io.aeron.logbuffer.FragmentHandler; +import io.aeron.logbuffer.Header; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import org.agrona.CloseHelper; +import org.agrona.DirectBuffer; +import org.agrona.concurrent.Agent; +import org.agrona.concurrent.AgentTerminationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Exceptions; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Operators; + +public class SubscriptionAgent implements Agent, AeronInbound, Disposable { + + private static final Logger LOGGER = LoggerFactory.getLogger(SubscriptionAgent.class); + + private static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(SubscriptionAgent.class, "requested"); + + private static final AtomicReferenceFieldUpdater + DESTINATION_SUBSCRIBER = + AtomicReferenceFieldUpdater.newUpdater( + SubscriptionAgent.class, CoreSubscriber.class, "destinationSubscriber"); + + private static final CoreSubscriber CANCELLED_SUBSCRIBER = new CancelledSubscriber(); + + private static final int FRAGMENT_LIMIT = 10; + + private final FluxReceive inbound = new FluxReceive(); + + private final Subscription subscription; + private final boolean shouldCloseSubscription; + + private final FragmentMapper mapper; + private final FragmentHandler fragmentHandler = + new ImageFragmentAssembler(new AgentFragmentHandler()); + + private volatile long requested; + private volatile boolean fastPath; + private long produced; + private volatile CoreSubscriber destinationSubscriber; + private Exception ex; + + public SubscriptionAgent( + Subscription subscription, FragmentMapper mapper, boolean shouldCloseSubscription) { + this.subscription = subscription; + this.mapper = mapper; + this.shouldCloseSubscription = shouldCloseSubscription; + } + + @Override + public void onStart() { + // no-op + } + + @Override + public int doWork() { + if (CANCELLED_SUBSCRIBER.equals(destinationSubscriber)) { + throw new AgentTerminationException("Subscription is cancelled"); + } + if (ex != null) { + if (ex instanceof AgentTerminationException) { + throw (AgentTerminationException) ex; + } + CoreSubscriber destination = + DESTINATION_SUBSCRIBER.getAndSet(SubscriptionAgent.this, CANCELLED_SUBSCRIBER); + if (destination != null) { + destination.onError(ex); + } + throw new AgentTerminationException(ex); + } + if (subscription.isClosed()) { + CoreSubscriber destination = + DESTINATION_SUBSCRIBER.getAndSet(SubscriptionAgent.this, CANCELLED_SUBSCRIBER); + if (destination != null) { + destination.onError(new AgentTerminationException("Subscription is closed")); + } + throw new AgentTerminationException("Subscription is closed"); + } + if (fastPath) { + return subscription.poll(fragmentHandler, FRAGMENT_LIMIT); + } + int r = (int) Math.min(requested, FRAGMENT_LIMIT); + int fragments = 0; + if (r > 0) { + fragments = subscription.poll(fragmentHandler, r); + if (produced > 0) { + Operators.produced(REQUESTED, this, produced); + produced = 0; + } + } + return fragments; + } + + @Override + public void onClose() { + inbound.cancel(); + LOGGER.debug("Cancelled inbound"); + if (shouldCloseSubscription) { + CloseHelper.quietClose(subscription); + } + } + + @Override + public String roleName() { + return SubscriptionAgent.class.getName() + ":" + subscription.streamId(); + } + + @Override + public Flux receive() { + return inbound; + } + + @Override + public void dispose() { + CoreSubscriber destination = + DESTINATION_SUBSCRIBER.getAndSet(SubscriptionAgent.this, CANCELLED_SUBSCRIBER); + if (destination != null) { + destination.onError(new AgentTerminationException("subscription Inbound has been disposed")); + } + } + + @Override + public boolean isDisposed() { + return CANCELLED_SUBSCRIBER.equals(destinationSubscriber); + } + + private class AgentFragmentHandler implements FragmentHandler { + + @Override + public void onFragment(DirectBuffer buffer, int offset, int length, Header header) { + try { + if (ex == null) { + T t = mapper.apply(buffer, offset, length, header); + if (t != null) { + produced++; + CoreSubscriber destination = SubscriptionAgent.this.destinationSubscriber; + destination.onNext(t); + } + } + } catch (Exception e) { + ex = e; + } + } + } + + private class FluxReceive extends Flux implements org.reactivestreams.Subscription { + + @Override + public void request(long n) { + if (fastPath) { + return; + } + if (n == Long.MAX_VALUE) { + fastPath = true; + requested = Long.MAX_VALUE; + return; + } + Operators.addCap(REQUESTED, SubscriptionAgent.this, n); + } + + @Override + public void cancel() { + CoreSubscriber destination = + DESTINATION_SUBSCRIBER.getAndSet(SubscriptionAgent.this, CANCELLED_SUBSCRIBER); + if (destination != null) { + destination.onComplete(); + } + LOGGER.debug("Destination subscriber on aeron inbound has been cancelled"); + } + + @Override + public void subscribe(CoreSubscriber destinationSubscriber) { + boolean result = + DESTINATION_SUBSCRIBER.compareAndSet(SubscriptionAgent.this, null, destinationSubscriber); + if (result) { + destinationSubscriber.onSubscribe(this); + } else { + // only subscriber is allowed on receive() + Operators.error(destinationSubscriber, Exceptions.duplicateOnSubscribeException()); + } + } + } + + private static class CancelledSubscriber implements CoreSubscriber { + + @Override + public void onSubscribe(org.reactivestreams.Subscription s) { + // no-op + } + + @Override + public void onNext(Object o) { + LOGGER.warn("Received ({}) which will be dropped immediately due cancelled aeron inbound", o); + } + + @Override + public void onError(Throwable t) { + // no-op + } + + @Override + public void onComplete() { + // no-op + } + } +} diff --git a/reactor-aeron/src/test/java/reactor/aeron/AeronClientTest.java b/reactor-aeron/src/test/java/reactor/aeron/AeronClientTest.java index adc03a7a..533fbf9a 100644 --- a/reactor-aeron/src/test/java/reactor/aeron/AeronClientTest.java +++ b/reactor-aeron/src/test/java/reactor/aeron/AeronClientTest.java @@ -11,6 +11,7 @@ import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.stream.Stream; +import org.agrona.DirectBuffer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -61,7 +62,8 @@ public void testClientReceivesDataFromServer() { .then(connection.onDispose())); AeronConnection connection = createConnection(); - StepVerifier.create(connection.inbound().receive().asString().log("client")) + StepVerifier.create( + DefaultFragmentMapper.asString(connection.inbound().receive()).log("client")) .expectNext("hello1", "2", "3") .expectNoEvent(Duration.ofMillis(10)) .thenCancel() @@ -83,7 +85,8 @@ public void testClientReceivesLongDataFromServer() { AeronConnection connection = createConnection(); - StepVerifier.create(connection.inbound().receive().asString().log("client")) + StepVerifier.create( + DefaultFragmentMapper.asString(connection.inbound().receive()).log("client")) .expectNext(str, str, str) .expectNoEvent(Duration.ofMillis(10)) .thenCancel() @@ -102,13 +105,15 @@ public void testTwoClientsReceiveDataFromServer() { AeronConnection connection1 = createConnection(); AeronConnection connection2 = createConnection(); - StepVerifier.create(connection1.inbound().receive().asString().log("client-1")) + StepVerifier.create( + DefaultFragmentMapper.asString(connection1.inbound().receive()).log("client-1")) .expectNext("1", "2", "3") .expectNoEvent(Duration.ofMillis(100)) .thenCancel() .verify(); - StepVerifier.create(connection2.inbound().receive().asString().log("client-2")) + StepVerifier.create( + DefaultFragmentMapper.asString(connection2.inbound().receive()).log("client-2")) .expectNext("1", "2", "3") .expectNoEvent(Duration.ofMillis(100)) .thenCancel() @@ -125,7 +130,7 @@ public void testClientsReceiveDataFromServer200000() { AeronConnection connection1 = createConnection(); - StepVerifier.create(connection1.inbound().receive().asString()) + StepVerifier.create(DefaultFragmentMapper.asString(connection1.inbound().receive())) .expectNextCount(count) .expectNoEvent(Duration.ofMillis(100)) .thenCancel() @@ -139,14 +144,14 @@ public void testRequestResponse200000() { connection -> connection .outbound() - .send(connection.inbound().receive()) + .send(connection.inbound().receive().cast(DirectBuffer.class)) .then(connection.onDispose())); AeronConnection connection1 = createConnection(); connection1.outbound().sendString(Flux.range(0, count).map(String::valueOf)).then().subscribe(); - StepVerifier.create(connection1.inbound().receive().asString()) + StepVerifier.create(DefaultFragmentMapper.asString(connection1.inbound().receive())) .expectNextCount(count) .expectNoEvent(Duration.ofMillis(100)) .thenCancel() @@ -161,6 +166,7 @@ public void testRequestResponse200000MonoJust() { connection .inbound() .receive() + .cast(DirectBuffer.class) .flatMap(byteBuffer -> connection.outbound().send(Mono.just(byteBuffer)).then()) .then(connection.onDispose())); @@ -176,7 +182,7 @@ public void testRequestResponse200000MonoJust() { .then() .subscribe(); - StepVerifier.create(connection1.inbound().receive().asString()) + StepVerifier.create(DefaultFragmentMapper.asString(connection1.inbound().receive())) .expectNextCount(count) .expectNoEvent(Duration.ofMillis(100)) .thenCancel() @@ -191,6 +197,7 @@ public void testTwoClientsRequestResponse200000() { connection .inbound() .receive() + .cast(DirectBuffer.class) .flatMap(byteBuffer -> connection.outbound().send(Mono.just(byteBuffer)).then()) .then(connection.onDispose())); @@ -199,7 +206,7 @@ public void testTwoClientsRequestResponse200000() { createConnection( connection -> { - connection.inbound().receive().asString().subscribe(processor1); + DefaultFragmentMapper.asString(connection.inbound().receive()).subscribe(processor1); Flux.range(0, count) .flatMap( i -> connection.outbound().sendString(Mono.just("client-1 send:" + i)).then()) @@ -210,7 +217,7 @@ public void testTwoClientsRequestResponse200000() { createConnection( connection -> { - connection.inbound().receive().asString().subscribe(processor2); + DefaultFragmentMapper.asString(connection.inbound().receive()).subscribe(processor2); Flux.range(0, count) .flatMap( i -> connection.outbound().sendString(Mono.just("client-2 send:" + i)).then()) @@ -235,6 +242,7 @@ public void testTwoClientsRequestResponseWithDelaySubscriptionToInbound200000() connection .inbound() .receive() + .cast(DirectBuffer.class) .flatMap(byteBuffer -> connection.outbound().send(Mono.just(byteBuffer)).then()) .then(connection.onDispose())); @@ -254,16 +262,10 @@ public void testTwoClientsRequestResponseWithDelaySubscriptionToInbound200000() Mono.delay(Duration.ofMillis(100)) .thenMany( Flux.merge( - connection1 - .inbound() - .receive() - .asString() + DefaultFragmentMapper.asString(connection1.inbound().receive()) .take(count) .filter(response -> !response.startsWith("client-1 ")), - connection2 - .inbound() - .receive() - .asString() + DefaultFragmentMapper.asString(connection2.inbound().receive()) .take(count) .filter(response -> !response.startsWith("client-2 "))))) .expectComplete() @@ -284,13 +286,17 @@ public void testClientWith2HandlersReceiveData() { createConnection( connection -> { - connection.inbound().receive().asString().log("client-1").subscribe(processor1); + DefaultFragmentMapper.asString(connection.inbound().receive()) + .log("client-1") + .subscribe(processor1); return connection.onDispose(); }); createConnection( connection -> { - connection.inbound().receive().asString().log("client-2").subscribe(processor2); + DefaultFragmentMapper.asString(connection.inbound().receive()) + .log("client-2") + .subscribe(processor2); return connection.onDispose(); }); @@ -317,10 +323,7 @@ public void testConcurrentSendingStreams() { createServer( connection -> - connection - .inbound() - .receive() - .asString() + DefaultFragmentMapper.asString(connection.inbound().receive()) .doOnNext(clientRequests::onNext) // .log("server receive ") .then(connection.onDispose())); @@ -394,10 +397,7 @@ protected void hookOnSubscribe(Subscription subscription) { scheduler.schedule(() -> subscriber.request(request3), requestDelay3, TimeUnit.MILLISECONDS); DirectProcessor processor = DirectProcessor.create(); - connection1 - .inbound() - .receive() - .asString() + DefaultFragmentMapper.asString(connection1.inbound().receive()) .take(overall) .doOnNext(processor::onNext) .subscribe(subscriber); @@ -457,10 +457,7 @@ protected void hookOnSubscribe(Subscription subscription) { scheduler.schedule(() -> subscriber.request(request3), requestDelay3, TimeUnit.MILLISECONDS); DirectProcessor processor = DirectProcessor.create(); - connection1 - .inbound() - .receive() - .asString() + DefaultFragmentMapper.asString(connection1.inbound().receive()) .take(overall) .doOnNext(processor::onNext) .subscribe(subscriber); diff --git a/reactor-aeron/src/test/java/reactor/aeron/AeronConnectionTest.java b/reactor-aeron/src/test/java/reactor/aeron/AeronConnectionTest.java index f4cc7982..23aa4cec 100644 --- a/reactor-aeron/src/test/java/reactor/aeron/AeronConnectionTest.java +++ b/reactor-aeron/src/test/java/reactor/aeron/AeronConnectionTest.java @@ -62,7 +62,7 @@ public void testServerDisconnectsSessionAndClientHandleUnavailableImage() createServer( connection -> { connection.onDispose().doOnSuccess(aVoid -> latch.countDown()).subscribe(); - connection.inbound().receive().subscribe(processor); + connection.inbound().receive().cast(DirectBuffer.class).subscribe(processor); return connection.onDispose(); }); @@ -106,7 +106,9 @@ public void testClientClosesSessionAndServerHandleUnavailableImage() throws Exce CountDownLatch latch = new CountDownLatch(1); connection.onDispose().doOnSuccess(aVoid -> latch.countDown()).subscribe(); - connection.inbound().receive().asString().log("client").subscribe(processor); + DefaultFragmentMapper.asString(connection.inbound().receive()) + .log("client") + .subscribe(processor); processor.take(1).blockLast(Duration.ofSeconds(4)); diff --git a/reactor-aeron/src/test/java/reactor/aeron/AeronServerTest.java b/reactor-aeron/src/test/java/reactor/aeron/AeronServerTest.java index cf29de84..1453b699 100644 --- a/reactor-aeron/src/test/java/reactor/aeron/AeronServerTest.java +++ b/reactor-aeron/src/test/java/reactor/aeron/AeronServerTest.java @@ -41,7 +41,9 @@ public void testServerReceivesData() { createServer( connection -> { - connection.inbound().receive().asString().log("receive").subscribe(processor); + DefaultFragmentMapper.asString(connection.inbound().receive()) + .log("receive") + .subscribe(processor); return connection.onDispose(); }); @@ -61,7 +63,12 @@ public void testServerDisconnectsClientsUponShutdown() throws InterruptedExcepti OnDisposable server = createServer( connection -> { - connection.inbound().receive().log("receive").subscribe(processor); + connection + .inbound() + .receive() + .cast(DirectBuffer.class) + .log("receive") + .subscribe(processor); return connection.onDispose(); }); From b7eefef426cd858ce76e58ba8adee4249015e498 Mon Sep 17 00:00:00 2001 From: segabriel Date: Thu, 13 Jun 2019 13:17:03 +0300 Subject: [PATCH 03/16] Added javadocs --- .../reactor/aeron/DefaultFragmentMapper.java | 6 +++++ .../main/java/reactor/aeron/ImageAgent.java | 23 +++++++++++++++---- .../java/reactor/aeron/PublicationAgent.java | 11 ++++++--- .../java/reactor/aeron/SubscriptionAgent.java | 7 ++++++ 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/reactor-aeron/src/main/java/reactor/aeron/DefaultFragmentMapper.java b/reactor-aeron/src/main/java/reactor/aeron/DefaultFragmentMapper.java index fc7a1d92..8f405333 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/DefaultFragmentMapper.java +++ b/reactor-aeron/src/main/java/reactor/aeron/DefaultFragmentMapper.java @@ -13,6 +13,12 @@ public DirectBuffer apply(DirectBuffer buffer, int offset, int length, Header he return new UnsafeBuffer(buffer, offset, length); } + /** + * Converts incoming {@link DirectBuffer} to {@link String}. + * + * @param flux {@link DirectBuffer} stream + * @return {@link String} stream + */ public static Flux asString(Flux flux) { return flux.cast(DirectBuffer.class) .map( diff --git a/reactor-aeron/src/main/java/reactor/aeron/ImageAgent.java b/reactor-aeron/src/main/java/reactor/aeron/ImageAgent.java index 88e5f143..4abe23f5 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/ImageAgent.java +++ b/reactor-aeron/src/main/java/reactor/aeron/ImageAgent.java @@ -51,13 +51,28 @@ public class ImageAgent implements Agent, AeronInbound, Disposable { private volatile CoreSubscriber destinationSubscriber; private Exception ex; + /** + * Creates image agent. + * + * @param image image + * @param mapper fragment mapper + * @param shouldCloseSubscription should the agent close image subscription when the agent is + * closed? + */ public ImageAgent(Image image, FragmentMapper mapper, boolean shouldCloseSubscription) { - this.image = image; - this.mapper = mapper; - this.shouldCloseSubscription = shouldCloseSubscription; - this.stopPosition = Aeron.NULL_VALUE; + this(image, mapper, shouldCloseSubscription, Aeron.NULL_VALUE); } + /** + * Creates image agent. + * + * @param image image + * @param mapper fragment mapper + * @param shouldCloseSubscription should the agent close image subscription when the agent is + * closed? + * @param stopPosition expected stop position of the given image. A negative value means an + * endless stream. + */ public ImageAgent( Image image, FragmentMapper mapper, boolean shouldCloseSubscription, long stopPosition) { this.image = image; diff --git a/reactor-aeron/src/main/java/reactor/aeron/PublicationAgent.java b/reactor-aeron/src/main/java/reactor/aeron/PublicationAgent.java index 04b16485..3f245648 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/PublicationAgent.java +++ b/reactor-aeron/src/main/java/reactor/aeron/PublicationAgent.java @@ -41,6 +41,11 @@ public final class PublicationAgent implements Agent, AeronOutbound, Disposable private volatile boolean isDisposed = false; + /** + * Creates publication agent. + * + * @param publication publication + */ public PublicationAgent(Publication publication) { this.publication = Objects.requireNonNull(publication, "publication cannot be null"); } @@ -180,12 +185,12 @@ public boolean isDisposed() { } @Override - public AeronOutbound send(Publisher dataStream, - DirectBufferHandler bufferHandler) { + public AeronOutbound send( + Publisher dataStream, DirectBufferHandler bufferHandler) { return then(publish(dataStream, bufferHandler)); } - private Mono publish( + private Mono publish( Publisher publisher, DirectBufferHandler bufferHandler) { return Mono.defer( () -> { diff --git a/reactor-aeron/src/main/java/reactor/aeron/SubscriptionAgent.java b/reactor-aeron/src/main/java/reactor/aeron/SubscriptionAgent.java index 06fc31ce..6df5a35f 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/SubscriptionAgent.java +++ b/reactor-aeron/src/main/java/reactor/aeron/SubscriptionAgent.java @@ -49,6 +49,13 @@ public class SubscriptionAgent implements Agent, AeronInbound, Disposable private volatile CoreSubscriber destinationSubscriber; private Exception ex; + /** + * Creates subscription agent. + * + * @param subscription subscription + * @param mapper fragment mapper + * @param shouldCloseSubscription should the agent close subscription when the agent is closed? + */ public SubscriptionAgent( Subscription subscription, FragmentMapper mapper, boolean shouldCloseSubscription) { this.subscription = subscription; From 4f1e19dda3f50db46d6f5684b6ebb232be129614 Mon Sep 17 00:00:00 2001 From: segabriel Date: Sat, 15 Jun 2019 08:24:03 +0300 Subject: [PATCH 04/16] Polish --- .../main/java/reactor/aeron/ServerDemo.java | 9 ++- .../reactor/aeron/DefaultFragmentMapper.java | 21 +++---- .../reactor/aeron/DuplexAeronConnection.java | 2 +- .../java/reactor/aeron/AeronClientTest.java | 56 +++++++++++++------ .../reactor/aeron/AeronConnectionTest.java | 5 +- .../java/reactor/aeron/AeronServerTest.java | 6 +- 6 files changed, 63 insertions(+), 36 deletions(-) diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerDemo.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerDemo.java index b46cfa85..bab98242 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerDemo.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerDemo.java @@ -1,5 +1,9 @@ package reactor.aeron; +import static reactor.aeron.DefaultFragmentMapper.asString; + +import org.agrona.DirectBuffer; + public class ServerDemo { /** @@ -14,7 +18,10 @@ public static void main(String[] args) { .options("localhost", 13000, 13001) .handle( connection -> - DefaultFragmentMapper.asString(connection.inbound().receive()) + connection + .inbound() + .receive() + .map(asString()) .log("receive") .then(connection.onDispose())) .bind() diff --git a/reactor-aeron/src/main/java/reactor/aeron/DefaultFragmentMapper.java b/reactor-aeron/src/main/java/reactor/aeron/DefaultFragmentMapper.java index 8f405333..4d678d28 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/DefaultFragmentMapper.java +++ b/reactor-aeron/src/main/java/reactor/aeron/DefaultFragmentMapper.java @@ -2,9 +2,9 @@ import io.aeron.logbuffer.Header; import java.nio.charset.StandardCharsets; +import java.util.function.Function; import org.agrona.DirectBuffer; import org.agrona.concurrent.UnsafeBuffer; -import reactor.core.publisher.Flux; public class DefaultFragmentMapper implements FragmentMapper { @@ -14,18 +14,15 @@ public DirectBuffer apply(DirectBuffer buffer, int offset, int length, Header he } /** - * Converts incoming {@link DirectBuffer} to {@link String}. + * Return function which converts incoming {@link DirectBuffer} to {@link String}. * - * @param flux {@link DirectBuffer} stream - * @return {@link String} stream + * @return function */ - public static Flux asString(Flux flux) { - return flux.cast(DirectBuffer.class) - .map( - buffer -> { - byte[] bytes = new byte[buffer.capacity()]; - buffer.getBytes(0, bytes); - return new String(bytes, StandardCharsets.UTF_8); - }); + public static Function asString() { + return buffer -> { + byte[] bytes = new byte[buffer.capacity()]; + buffer.getBytes(0, bytes); + return new String(bytes, StandardCharsets.UTF_8); + }; } } diff --git a/reactor-aeron/src/main/java/reactor/aeron/DuplexAeronConnection.java b/reactor-aeron/src/main/java/reactor/aeron/DuplexAeronConnection.java index f0edb2d2..4bc3d172 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/DuplexAeronConnection.java +++ b/reactor-aeron/src/main/java/reactor/aeron/DuplexAeronConnection.java @@ -72,7 +72,7 @@ private void start0(Function> } @Override - public AeronInbound inbound() { + public AeronInbound inbound() { return inbound; } diff --git a/reactor-aeron/src/test/java/reactor/aeron/AeronClientTest.java b/reactor-aeron/src/test/java/reactor/aeron/AeronClientTest.java index 533fbf9a..f5c9f57e 100644 --- a/reactor-aeron/src/test/java/reactor/aeron/AeronClientTest.java +++ b/reactor-aeron/src/test/java/reactor/aeron/AeronClientTest.java @@ -1,6 +1,7 @@ package reactor.aeron; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static reactor.aeron.DefaultFragmentMapper.asString; import io.aeron.driver.Configuration; import java.time.Duration; @@ -62,8 +63,7 @@ public void testClientReceivesDataFromServer() { .then(connection.onDispose())); AeronConnection connection = createConnection(); - StepVerifier.create( - DefaultFragmentMapper.asString(connection.inbound().receive()).log("client")) + StepVerifier.create(connection.inbound().receive().map(asString()).log("client")) .expectNext("hello1", "2", "3") .expectNoEvent(Duration.ofMillis(10)) .thenCancel() @@ -85,8 +85,7 @@ public void testClientReceivesLongDataFromServer() { AeronConnection connection = createConnection(); - StepVerifier.create( - DefaultFragmentMapper.asString(connection.inbound().receive()).log("client")) + StepVerifier.create(connection.inbound().receive().map(asString()).log("client")) .expectNext(str, str, str) .expectNoEvent(Duration.ofMillis(10)) .thenCancel() @@ -106,14 +105,14 @@ public void testTwoClientsReceiveDataFromServer() { AeronConnection connection2 = createConnection(); StepVerifier.create( - DefaultFragmentMapper.asString(connection1.inbound().receive()).log("client-1")) + connection1.inbound().receive().map(asString()).log("client-1")) .expectNext("1", "2", "3") .expectNoEvent(Duration.ofMillis(100)) .thenCancel() .verify(); StepVerifier.create( - DefaultFragmentMapper.asString(connection2.inbound().receive()).log("client-2")) + connection2.inbound().receive().map(asString()).log("client-2")) .expectNext("1", "2", "3") .expectNoEvent(Duration.ofMillis(100)) .thenCancel() @@ -130,7 +129,7 @@ public void testClientsReceiveDataFromServer200000() { AeronConnection connection1 = createConnection(); - StepVerifier.create(DefaultFragmentMapper.asString(connection1.inbound().receive())) + StepVerifier.create(connection1.inbound().receive().map(asString())) .expectNextCount(count) .expectNoEvent(Duration.ofMillis(100)) .thenCancel() @@ -151,7 +150,7 @@ public void testRequestResponse200000() { connection1.outbound().sendString(Flux.range(0, count).map(String::valueOf)).then().subscribe(); - StepVerifier.create(DefaultFragmentMapper.asString(connection1.inbound().receive())) + StepVerifier.create(connection1.inbound().receive().map(asString())) .expectNextCount(count) .expectNoEvent(Duration.ofMillis(100)) .thenCancel() @@ -182,7 +181,7 @@ public void testRequestResponse200000MonoJust() { .then() .subscribe(); - StepVerifier.create(DefaultFragmentMapper.asString(connection1.inbound().receive())) + StepVerifier.create(connection1.inbound().receive().map(asString())) .expectNextCount(count) .expectNoEvent(Duration.ofMillis(100)) .thenCancel() @@ -206,7 +205,7 @@ public void testTwoClientsRequestResponse200000() { createConnection( connection -> { - DefaultFragmentMapper.asString(connection.inbound().receive()).subscribe(processor1); + connection.inbound().receive().map(asString()).subscribe(processor1); Flux.range(0, count) .flatMap( i -> connection.outbound().sendString(Mono.just("client-1 send:" + i)).then()) @@ -217,7 +216,7 @@ public void testTwoClientsRequestResponse200000() { createConnection( connection -> { - DefaultFragmentMapper.asString(connection.inbound().receive()).subscribe(processor2); + connection.inbound().receive().map(asString()).subscribe(processor2); Flux.range(0, count) .flatMap( i -> connection.outbound().sendString(Mono.just("client-2 send:" + i)).then()) @@ -262,10 +261,16 @@ public void testTwoClientsRequestResponseWithDelaySubscriptionToInbound200000() Mono.delay(Duration.ofMillis(100)) .thenMany( Flux.merge( - DefaultFragmentMapper.asString(connection1.inbound().receive()) + connection1 + .inbound() + .receive() + .map(asString()) .take(count) .filter(response -> !response.startsWith("client-1 ")), - DefaultFragmentMapper.asString(connection2.inbound().receive()) + connection2 + .inbound() + .receive() + .map(asString()) .take(count) .filter(response -> !response.startsWith("client-2 "))))) .expectComplete() @@ -286,7 +291,10 @@ public void testClientWith2HandlersReceiveData() { createConnection( connection -> { - DefaultFragmentMapper.asString(connection.inbound().receive()) + connection + .inbound() + .receive() + .map(asString()) .log("client-1") .subscribe(processor1); return connection.onDispose(); @@ -294,7 +302,10 @@ public void testClientWith2HandlersReceiveData() { createConnection( connection -> { - DefaultFragmentMapper.asString(connection.inbound().receive()) + connection + .inbound() + .receive() + .map(asString()) .log("client-2") .subscribe(processor2); return connection.onDispose(); @@ -323,7 +334,10 @@ public void testConcurrentSendingStreams() { createServer( connection -> - DefaultFragmentMapper.asString(connection.inbound().receive()) + connection + .inbound() + .receive() + .map(asString()) .doOnNext(clientRequests::onNext) // .log("server receive ") .then(connection.onDispose())); @@ -397,7 +411,10 @@ protected void hookOnSubscribe(Subscription subscription) { scheduler.schedule(() -> subscriber.request(request3), requestDelay3, TimeUnit.MILLISECONDS); DirectProcessor processor = DirectProcessor.create(); - DefaultFragmentMapper.asString(connection1.inbound().receive()) + connection1 + .inbound() + .receive() + .map(asString()) .take(overall) .doOnNext(processor::onNext) .subscribe(subscriber); @@ -457,7 +474,10 @@ protected void hookOnSubscribe(Subscription subscription) { scheduler.schedule(() -> subscriber.request(request3), requestDelay3, TimeUnit.MILLISECONDS); DirectProcessor processor = DirectProcessor.create(); - DefaultFragmentMapper.asString(connection1.inbound().receive()) + connection1 + .inbound() + .receive() + .map(asString()) .take(overall) .doOnNext(processor::onNext) .subscribe(subscriber); diff --git a/reactor-aeron/src/test/java/reactor/aeron/AeronConnectionTest.java b/reactor-aeron/src/test/java/reactor/aeron/AeronConnectionTest.java index 23aa4cec..f84e39fc 100644 --- a/reactor-aeron/src/test/java/reactor/aeron/AeronConnectionTest.java +++ b/reactor-aeron/src/test/java/reactor/aeron/AeronConnectionTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static reactor.aeron.DefaultFragmentMapper.asString; import java.nio.ByteBuffer; import java.time.Duration; @@ -106,9 +107,7 @@ public void testClientClosesSessionAndServerHandleUnavailableImage() throws Exce CountDownLatch latch = new CountDownLatch(1); connection.onDispose().doOnSuccess(aVoid -> latch.countDown()).subscribe(); - DefaultFragmentMapper.asString(connection.inbound().receive()) - .log("client") - .subscribe(processor); + connection.inbound().receive().map(asString()).log("client").subscribe(processor); processor.take(1).blockLast(Duration.ofSeconds(4)); diff --git a/reactor-aeron/src/test/java/reactor/aeron/AeronServerTest.java b/reactor-aeron/src/test/java/reactor/aeron/AeronServerTest.java index 1453b699..ccb14786 100644 --- a/reactor-aeron/src/test/java/reactor/aeron/AeronServerTest.java +++ b/reactor-aeron/src/test/java/reactor/aeron/AeronServerTest.java @@ -1,6 +1,7 @@ package reactor.aeron; import static org.junit.jupiter.api.Assertions.assertTrue; +import static reactor.aeron.DefaultFragmentMapper.asString; import java.time.Duration; import java.util.function.Function; @@ -41,7 +42,10 @@ public void testServerReceivesData() { createServer( connection -> { - DefaultFragmentMapper.asString(connection.inbound().receive()) + connection + .inbound() + .receive() + .map(asString()) .log("receive") .subscribe(processor); return connection.onDispose(); From dfdedb6e69408240b52760ec575b0ad3d171769f Mon Sep 17 00:00:00 2001 From: segabriel Date: Sat, 15 Jun 2019 08:50:12 +0300 Subject: [PATCH 05/16] Polish --- .../java/reactor/aeron/AeronPingClient.java | 4 +- .../main/java/reactor/aeron/AeronClient.java | 8 +- .../reactor/aeron/AeronClientConnector.java | 10 +-- .../java/reactor/aeron/AeronConnection.java | 4 +- .../main/java/reactor/aeron/AeronOptions.java | 7 +- .../main/java/reactor/aeron/AeronServer.java | 4 +- .../reactor/aeron/AeronServerHandler.java | 6 +- .../reactor/aeron/DuplexAeronConnection.java | 15 ++-- .../java/reactor/aeron/AeronClientTest.java | 74 ++++++++----------- .../reactor/aeron/AeronConnectionTest.java | 12 +-- .../java/reactor/aeron/AeronServerTest.java | 18 +---- 11 files changed, 73 insertions(+), 89 deletions(-) diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPingClient.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPingClient.java index aa2ef89d..d99867ac 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPingClient.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPingClient.java @@ -33,7 +33,7 @@ public static void main(String... args) { .start() .block(); - AeronConnection connection = + AeronConnection connection = AeronClient.create(resources) .options( Configurations.MDC_ADDRESS, @@ -71,7 +71,7 @@ public static void main(String... args) { connection.onDispose(resources).onDispose().block(); } - private static void roundTripMessages(AeronConnection connection, long count) { + private static void roundTripMessages(AeronConnection connection, long count) { HISTOGRAM.reset(); Disposable disp = reporter.start(); diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronClient.java b/reactor-aeron/src/main/java/reactor/aeron/AeronClient.java index b5669779..d56a64f8 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronClient.java +++ b/reactor-aeron/src/main/java/reactor/aeron/AeronClient.java @@ -2,6 +2,7 @@ import java.util.function.Function; import java.util.function.UnaryOperator; +import org.agrona.DirectBuffer; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; @@ -28,7 +29,7 @@ public static AeronClient create(AeronResources resources) { * * @return mono handle of result */ - public Mono connect() { + public Mono> connect() { return connect(s -> s); } @@ -38,7 +39,7 @@ public Mono connect() { * @param op unary opearator for performing setup of options * @return mono handle of result */ - public Mono connect(UnaryOperator op) { + public Mono> connect(UnaryOperator op) { return Mono.defer(() -> new AeronClientConnector(op.apply(options)).start()); } @@ -86,7 +87,8 @@ public AeronClient options(String address, int port, int controlPort) { * terminates. * @return new {@code AeronClient} with handler */ - public AeronClient handle(Function> handler) { + public AeronClient handle( + Function, ? extends Publisher> handler) { return new AeronClient(options.handler(handler)); } } diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronClientConnector.java b/reactor-aeron/src/main/java/reactor/aeron/AeronClientConnector.java index 49d43d13..2fcd4d8c 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronClientConnector.java +++ b/reactor-aeron/src/main/java/reactor/aeron/AeronClientConnector.java @@ -30,7 +30,7 @@ final class AeronClientConnector { private final AeronOptions options; private final AeronResources resources; - private final Function> handler; + private final Function, ? extends Publisher> handler; private final DefaultFragmentMapper mapper = new DefaultFragmentMapper(); AeronClientConnector(AeronOptions options) { @@ -44,7 +44,7 @@ final class AeronClientConnector { * * @return mono result */ - Mono start() { + Mono> start() { return Mono.defer( () -> { return tryConnect() @@ -106,12 +106,12 @@ Mono start() { }); } - private Mono newConnection( + private Mono> newConnection( int sessionId, Image image, Publication publication, MonoProcessor disposeHook) { PublicationAgent publicationAgent = new PublicationAgent(publication); ImageAgent imageAgent = new ImageAgent<>(image, mapper, true); - DuplexAeronConnection connection = - new DuplexAeronConnection(sessionId, imageAgent, publicationAgent, disposeHook); + DuplexAeronConnection connection = + new DuplexAeronConnection<>(sessionId, imageAgent, publicationAgent, disposeHook); return connection .start(handler) .doOnSuccess( diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronConnection.java b/reactor-aeron/src/main/java/reactor/aeron/AeronConnection.java index 84786989..5a6e31ce 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronConnection.java +++ b/reactor-aeron/src/main/java/reactor/aeron/AeronConnection.java @@ -13,7 +13,7 @@ * for reading and {@link #outbound()} for writing data. AeronConnection interface comes with {@link * OnDisposable} and {@link #disposeSubscriber()} function for convenient resource cleanup. */ -public interface AeronConnection extends OnDisposable { +public interface AeronConnection extends OnDisposable { /** * Return the {@link AeronInbound} read API from this connection. If {@link AeronConnection} has @@ -21,7 +21,7 @@ public interface AeronConnection extends OnDisposable { * * @return {@code AeronInbound} instance */ - AeronInbound inbound(); + AeronInbound inbound(); /** * Return the {@link AeronOutbound} write API from this connection. If {@link AeronConnection} has diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronOptions.java b/reactor-aeron/src/main/java/reactor/aeron/AeronOptions.java index 6d20552b..08891f53 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronOptions.java +++ b/reactor-aeron/src/main/java/reactor/aeron/AeronOptions.java @@ -4,6 +4,7 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; +import org.agrona.DirectBuffer; import org.reactivestreams.Publisher; /** @@ -14,7 +15,7 @@ public final class AeronOptions { private AeronResources resources; - private Function> handler; + private Function, ? extends Publisher> handler; private AeronChannelUriString inboundUri = new AeronChannelUriString(); private AeronChannelUriString outboundUri = new AeronChannelUriString(); private Duration connectTimeout = Duration.ofSeconds(5); @@ -45,12 +46,12 @@ public AeronOptions resources(AeronResources resources) { return set(s -> s.resources = resources); } - public Function> handler() { + public Function, ? extends Publisher> handler() { return handler; } public AeronOptions handler( - Function> handler) { + Function, ? extends Publisher> handler) { return set(s -> s.handler = handler); } diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronServer.java b/reactor-aeron/src/main/java/reactor/aeron/AeronServer.java index 4871e18b..7f92d617 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronServer.java +++ b/reactor-aeron/src/main/java/reactor/aeron/AeronServer.java @@ -2,6 +2,7 @@ import java.util.function.Function; import java.util.function.UnaryOperator; +import org.agrona.DirectBuffer; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; @@ -89,7 +90,8 @@ public AeronServer options(String address, int port, int controlPort) { * terminates. * @return new {@code AeronServer} with handler */ - public AeronServer handle(Function> handler) { + public AeronServer handle( + Function, ? extends Publisher> handler) { return new AeronServer(options.handler(handler)); } } diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronServerHandler.java b/reactor-aeron/src/main/java/reactor/aeron/AeronServerHandler.java index bbe46976..104d3cd3 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronServerHandler.java +++ b/reactor-aeron/src/main/java/reactor/aeron/AeronServerHandler.java @@ -36,7 +36,7 @@ final class AeronServerHandler implements OnDisposable { private final AeronOptions options; private final AeronResources resources; - private final Function> handler; + private final Function, ? extends Publisher> handler; private final DefaultFragmentMapper mapper = new DefaultFragmentMapper(); // private volatile MessageSubscription acceptorSubscription; // server acceptor subscription @@ -125,8 +125,8 @@ private Mono newConnection( PublicationAgent publicationAgent = new PublicationAgent(publication); ImageAgent imageAgent = new ImageAgent<>(image, mapper, false); - DuplexAeronConnection connection = - new DuplexAeronConnection(sessionId, imageAgent, publicationAgent, disposeHook); + DuplexAeronConnection connection = + new DuplexAeronConnection<>(sessionId, imageAgent, publicationAgent, disposeHook); disposeHooks.put(sessionId, disposeHook); diff --git a/reactor-aeron/src/main/java/reactor/aeron/DuplexAeronConnection.java b/reactor-aeron/src/main/java/reactor/aeron/DuplexAeronConnection.java index 4bc3d172..2970e706 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/DuplexAeronConnection.java +++ b/reactor-aeron/src/main/java/reactor/aeron/DuplexAeronConnection.java @@ -11,13 +11,13 @@ * Full-duplex aeron connection. Bound to certain {@code sessionId}. Implements {@link * OnDisposable} for convenient resource cleanup. */ -final class DuplexAeronConnection implements AeronConnection { +final class DuplexAeronConnection implements AeronConnection { private final Logger logger = LoggerFactory.getLogger(DuplexAeronConnection.class); private final int sessionId; - private final AeronInbound inbound; + private final AeronInbound inbound; private final AeronOutbound outbound; private final MonoProcessor dispose = MonoProcessor.create(); @@ -33,7 +33,7 @@ final class DuplexAeronConnection implements AeronConnection { */ DuplexAeronConnection( int sessionId, - AeronInbound inbound, + AeronInbound inbound, AeronOutbound outbound, MonoProcessor disposeHook) { @@ -57,12 +57,13 @@ final class DuplexAeronConnection implements AeronConnection { * * @param handler handler with application level code */ - Mono start( - Function> handler) { + Mono> start( + Function, ? extends Publisher> handler) { return Mono.fromRunnable(() -> start0(handler)).thenReturn(this); } - private void start0(Function> handler) { + private void start0( + Function, ? extends Publisher> handler) { if (handler == null) { logger.warn( "{}: connection handler function is not specified", Integer.toHexString(sessionId)); @@ -72,7 +73,7 @@ private void start0(Function> } @Override - public AeronInbound inbound() { + public AeronInbound inbound() { return inbound; } diff --git a/reactor-aeron/src/test/java/reactor/aeron/AeronClientTest.java b/reactor-aeron/src/test/java/reactor/aeron/AeronClientTest.java index f5c9f57e..889979c1 100644 --- a/reactor-aeron/src/test/java/reactor/aeron/AeronClientTest.java +++ b/reactor-aeron/src/test/java/reactor/aeron/AeronClientTest.java @@ -62,8 +62,8 @@ public void testClientReceivesDataFromServer() { .sendString(Flux.fromStream(Stream.of("hello1", "2", "3")).log("server")) .then(connection.onDispose())); - AeronConnection connection = createConnection(); - StepVerifier.create(connection.inbound().receive().map(asString()).log("client")) + AeronConnection connection = createConnection(); + StepVerifier.create(connection.inbound().receive().map(asString()).log("client")) .expectNext("hello1", "2", "3") .expectNoEvent(Duration.ofMillis(10)) .thenCancel() @@ -83,9 +83,9 @@ public void testClientReceivesLongDataFromServer() { .sendString(Flux.fromStream(Stream.of(str, str, str)).log("server")) .then(connection.onDispose())); - AeronConnection connection = createConnection(); + AeronConnection connection = createConnection(); - StepVerifier.create(connection.inbound().receive().map(asString()).log("client")) + StepVerifier.create(connection.inbound().receive().map(asString()).log("client")) .expectNext(str, str, str) .expectNoEvent(Duration.ofMillis(10)) .thenCancel() @@ -101,18 +101,16 @@ public void testTwoClientsReceiveDataFromServer() { .sendString(Flux.fromStream(Stream.of("1", "2", "3")).log("server")) .then(connection.onDispose())); - AeronConnection connection1 = createConnection(); - AeronConnection connection2 = createConnection(); + AeronConnection connection1 = createConnection(); + AeronConnection connection2 = createConnection(); - StepVerifier.create( - connection1.inbound().receive().map(asString()).log("client-1")) + StepVerifier.create(connection1.inbound().receive().map(asString()).log("client-1")) .expectNext("1", "2", "3") .expectNoEvent(Duration.ofMillis(100)) .thenCancel() .verify(); - StepVerifier.create( - connection2.inbound().receive().map(asString()).log("client-2")) + StepVerifier.create(connection2.inbound().receive().map(asString()).log("client-2")) .expectNext("1", "2", "3") .expectNoEvent(Duration.ofMillis(100)) .thenCancel() @@ -127,9 +125,9 @@ public void testClientsReceiveDataFromServer200000() { createServer( connection -> connection.outbound().sendString(payloads).then(connection.onDispose())); - AeronConnection connection1 = createConnection(); + AeronConnection connection1 = createConnection(); - StepVerifier.create(connection1.inbound().receive().map(asString())) + StepVerifier.create(connection1.inbound().receive().map(asString())) .expectNextCount(count) .expectNoEvent(Duration.ofMillis(100)) .thenCancel() @@ -146,11 +144,11 @@ public void testRequestResponse200000() { .send(connection.inbound().receive().cast(DirectBuffer.class)) .then(connection.onDispose())); - AeronConnection connection1 = createConnection(); + AeronConnection connection1 = createConnection(); connection1.outbound().sendString(Flux.range(0, count).map(String::valueOf)).then().subscribe(); - StepVerifier.create(connection1.inbound().receive().map(asString())) + StepVerifier.create(connection1.inbound().receive().map(asString())) .expectNextCount(count) .expectNoEvent(Duration.ofMillis(100)) .thenCancel() @@ -169,7 +167,7 @@ public void testRequestResponse200000MonoJust() { .flatMap(byteBuffer -> connection.outbound().send(Mono.just(byteBuffer)).then()) .then(connection.onDispose())); - AeronConnection connection1 = createConnection(); + AeronConnection connection1 = createConnection(); Flux.range(0, count) .flatMap( @@ -181,7 +179,7 @@ public void testRequestResponse200000MonoJust() { .then() .subscribe(); - StepVerifier.create(connection1.inbound().receive().map(asString())) + StepVerifier.create(connection1.inbound().receive().map(asString())) .expectNextCount(count) .expectNoEvent(Duration.ofMillis(100)) .thenCancel() @@ -205,7 +203,7 @@ public void testTwoClientsRequestResponse200000() { createConnection( connection -> { - connection.inbound().receive().map(asString()).subscribe(processor1); + connection.inbound().receive().map(asString()).subscribe(processor1); Flux.range(0, count) .flatMap( i -> connection.outbound().sendString(Mono.just("client-1 send:" + i)).then()) @@ -216,7 +214,7 @@ public void testTwoClientsRequestResponse200000() { createConnection( connection -> { - connection.inbound().receive().map(asString()).subscribe(processor2); + connection.inbound().receive().map(asString()).subscribe(processor2); Flux.range(0, count) .flatMap( i -> connection.outbound().sendString(Mono.just("client-2 send:" + i)).then()) @@ -245,13 +243,13 @@ public void testTwoClientsRequestResponseWithDelaySubscriptionToInbound200000() .flatMap(byteBuffer -> connection.outbound().send(Mono.just(byteBuffer)).then()) .then(connection.onDispose())); - AeronConnection connection1 = createConnection(); + AeronConnection connection1 = createConnection(); Flux.range(0, count) .flatMap(i -> connection1.outbound().sendString(Mono.just("client-1 send:" + i)).then()) .then() .subscribe(null, ex -> logger.error("client-1 didn't send all, cause: ", ex)); - AeronConnection connection2 = createConnection(); + AeronConnection connection2 = createConnection(); Flux.range(0, count) .flatMap(i -> connection2.outbound().sendString(Mono.just("client-2 send:" + i)).then()) .then() @@ -262,13 +260,13 @@ public void testTwoClientsRequestResponseWithDelaySubscriptionToInbound200000() .thenMany( Flux.merge( connection1 - .inbound() + .inbound() .receive() .map(asString()) .take(count) .filter(response -> !response.startsWith("client-1 ")), connection2 - .inbound() + .inbound() .receive() .map(asString()) .take(count) @@ -291,23 +289,13 @@ public void testClientWith2HandlersReceiveData() { createConnection( connection -> { - connection - .inbound() - .receive() - .map(asString()) - .log("client-1") - .subscribe(processor1); + connection.inbound().receive().map(asString()).log("client-1").subscribe(processor1); return connection.onDispose(); }); createConnection( connection -> { - connection - .inbound() - .receive() - .map(asString()) - .log("client-2") - .subscribe(processor2); + connection.inbound().receive().map(asString()).log("client-2").subscribe(processor2); return connection.onDispose(); }); @@ -335,7 +323,7 @@ public void testConcurrentSendingStreams() { createServer( connection -> connection - .inbound() + .inbound() .receive() .map(asString()) .doOnNext(clientRequests::onNext) @@ -391,7 +379,7 @@ public void testCustomClientInboundSubscriber() { return connection.onDispose(); }); - AeronConnection connection1 = createConnection(); + AeronConnection connection1 = createConnection(); BaseSubscriber subscriber = new BaseSubscriber() { @@ -412,7 +400,7 @@ protected void hookOnSubscribe(Subscription subscription) { DirectProcessor processor = DirectProcessor.create(); connection1 - .inbound() + .inbound() .receive() .map(asString()) .take(overall) @@ -454,7 +442,7 @@ public void testCustomClientInboundSubscriberWithLongMessage() { return connection.onDispose(); }); - AeronConnection connection1 = createConnection(); + AeronConnection connection1 = createConnection(); BaseSubscriber subscriber = new BaseSubscriber() { @@ -475,7 +463,7 @@ protected void hookOnSubscribe(Subscription subscription) { DirectProcessor processor = DirectProcessor.create(); connection1 - .inbound() + .inbound() .receive() .map(asString()) .take(overall) @@ -491,12 +479,12 @@ protected void hookOnSubscribe(Subscription subscription) { .verify(timeout); } - private AeronConnection createConnection() { + private AeronConnection createConnection() { return createConnection(null /*handler*/); } - private AeronConnection createConnection( - Function> handler) { + private AeronConnection createConnection( + Function, ? extends Publisher> handler) { return AeronClient.create(resources) .options("localhost", serverPort, serverControlPort) .handle(handler) @@ -505,7 +493,7 @@ private AeronConnection createConnection( } private OnDisposable createServer( - Function> handler) { + Function, ? extends Publisher> handler) { return AeronServer.create(resources) .options("localhost", serverPort, serverControlPort) .handle(handler) diff --git a/reactor-aeron/src/test/java/reactor/aeron/AeronConnectionTest.java b/reactor-aeron/src/test/java/reactor/aeron/AeronConnectionTest.java index f84e39fc..55f59154 100644 --- a/reactor-aeron/src/test/java/reactor/aeron/AeronConnectionTest.java +++ b/reactor-aeron/src/test/java/reactor/aeron/AeronConnectionTest.java @@ -63,7 +63,7 @@ public void testServerDisconnectsSessionAndClientHandleUnavailableImage() createServer( connection -> { connection.onDispose().doOnSuccess(aVoid -> latch.countDown()).subscribe(); - connection.inbound().receive().cast(DirectBuffer.class).subscribe(processor); + connection.inbound().receive().subscribe(processor); return connection.onDispose(); }); @@ -102,12 +102,12 @@ public void testClientClosesSessionAndServerHandleUnavailableImage() throws Exce ReplayProcessor processor = ReplayProcessor.create(); - AeronConnection connection = createConnection(); + AeronConnection connection = createConnection(); CountDownLatch latch = new CountDownLatch(1); connection.onDispose().doOnSuccess(aVoid -> latch.countDown()).subscribe(); - connection.inbound().receive().map(asString()).log("client").subscribe(processor); + connection.inbound().receive().map(asString()).log("client").subscribe(processor); processor.take(1).blockLast(Duration.ofSeconds(4)); @@ -143,7 +143,7 @@ public void testServerDisconnectsAndClientCleanups() throws Exception { CountDownLatch clientConnectionLatch = new CountDownLatch(2); - AeronConnection client = createConnection(); + AeronConnection client = createConnection(); client .inbound() // @@ -222,7 +222,7 @@ public void testClientDisconnectsAndServerCleanups() throws Exception { assertTrue(await, "serverConnectionLatch: " + serverConnectionLatch.getCount()); } - private AeronConnection createConnection() { + private AeronConnection createConnection() { return AeronClient.create(resources) .options("localhost", serverPort, serverControlPort) .connect() @@ -230,7 +230,7 @@ private AeronConnection createConnection() { } private OnDisposable createServer( - Function> handler) { + Function, ? extends Publisher> handler) { return AeronServer.create(resources) .options("localhost", serverPort, serverControlPort) .handle(handler) diff --git a/reactor-aeron/src/test/java/reactor/aeron/AeronServerTest.java b/reactor-aeron/src/test/java/reactor/aeron/AeronServerTest.java index ccb14786..21940f0e 100644 --- a/reactor-aeron/src/test/java/reactor/aeron/AeronServerTest.java +++ b/reactor-aeron/src/test/java/reactor/aeron/AeronServerTest.java @@ -42,12 +42,7 @@ public void testServerReceivesData() { createServer( connection -> { - connection - .inbound() - .receive() - .map(asString()) - .log("receive") - .subscribe(processor); + connection.inbound().receive().map(asString()).log("receive").subscribe(processor); return connection.onDispose(); }); @@ -67,12 +62,7 @@ public void testServerDisconnectsClientsUponShutdown() throws InterruptedExcepti OnDisposable server = createServer( connection -> { - connection - .inbound() - .receive() - .cast(DirectBuffer.class) - .log("receive") - .subscribe(processor); + connection.inbound().receive().log("receive").subscribe(processor); return connection.onDispose(); }); @@ -93,7 +83,7 @@ public void testServerDisconnectsClientsUponShutdown() throws InterruptedExcepti assertTrue(new ThreadWatcher().awaitTerminated(5000, "single-", "parallel-")); } - private AeronConnection createConnection() { + private AeronConnection createConnection() { return AeronClient.create(resources) .options("localhost", serverPort, serverControlPort) .connect() @@ -101,7 +91,7 @@ private AeronConnection createConnection() { } private OnDisposable createServer( - Function> handler) { + Function, ? extends Publisher> handler) { return AeronServer.create(resources) .options("localhost", serverPort, serverControlPort) .handle(handler) From 814acf836d1297086e12ba7e6b79162c4fa8c955 Mon Sep 17 00:00:00 2001 From: segabriel Date: Sat, 15 Jun 2019 09:12:47 +0300 Subject: [PATCH 06/16] checkstyle --- .../main/java/reactor/aeron/AeronConnection.java | 4 ++-- .../java/reactor/aeron/DuplexAeronConnection.java | 15 +++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronConnection.java b/reactor-aeron/src/main/java/reactor/aeron/AeronConnection.java index 5a6e31ce..7d7d4992 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronConnection.java +++ b/reactor-aeron/src/main/java/reactor/aeron/AeronConnection.java @@ -13,7 +13,7 @@ * for reading and {@link #outbound()} for writing data. AeronConnection interface comes with {@link * OnDisposable} and {@link #disposeSubscriber()} function for convenient resource cleanup. */ -public interface AeronConnection extends OnDisposable { +public interface AeronConnection extends OnDisposable { /** * Return the {@link AeronInbound} read API from this connection. If {@link AeronConnection} has @@ -21,7 +21,7 @@ public interface AeronConnection extends OnDisposable { * * @return {@code AeronInbound} instance */ - AeronInbound inbound(); + AeronInbound inbound(); /** * Return the {@link AeronOutbound} write API from this connection. If {@link AeronConnection} has diff --git a/reactor-aeron/src/main/java/reactor/aeron/DuplexAeronConnection.java b/reactor-aeron/src/main/java/reactor/aeron/DuplexAeronConnection.java index 2970e706..b8b32968 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/DuplexAeronConnection.java +++ b/reactor-aeron/src/main/java/reactor/aeron/DuplexAeronConnection.java @@ -11,13 +11,13 @@ * Full-duplex aeron connection. Bound to certain {@code sessionId}. Implements {@link * OnDisposable} for convenient resource cleanup. */ -final class DuplexAeronConnection implements AeronConnection { +final class DuplexAeronConnection implements AeronConnection { private final Logger logger = LoggerFactory.getLogger(DuplexAeronConnection.class); private final int sessionId; - private final AeronInbound inbound; + private final AeronInbound inbound; private final AeronOutbound outbound; private final MonoProcessor dispose = MonoProcessor.create(); @@ -33,7 +33,7 @@ final class DuplexAeronConnection implements AeronConnection { */ DuplexAeronConnection( int sessionId, - AeronInbound inbound, + AeronInbound inbound, AeronOutbound outbound, MonoProcessor disposeHook) { @@ -57,13 +57,12 @@ final class DuplexAeronConnection implements AeronConnection { * * @param handler handler with application level code */ - Mono> start( - Function, ? extends Publisher> handler) { + Mono> start( + Function, ? extends Publisher> handler) { return Mono.fromRunnable(() -> start0(handler)).thenReturn(this); } - private void start0( - Function, ? extends Publisher> handler) { + private void start0(Function, ? extends Publisher> handler) { if (handler == null) { logger.warn( "{}: connection handler function is not specified", Integer.toHexString(sessionId)); @@ -73,7 +72,7 @@ private void start0( } @Override - public AeronInbound inbound() { + public AeronInbound inbound() { return inbound; } From 241037dde3d1a91b1280b1448f05269c81a4360b Mon Sep 17 00:00:00 2001 From: segabriel Date: Sat, 15 Jun 2019 09:22:06 +0300 Subject: [PATCH 07/16] Removed redundant casting --- .../src/main/java/reactor/aeron/AeronPingClient.java | 1 - .../src/main/java/reactor/aeron/AeronPongServer.java | 4 +--- .../src/main/java/reactor/aeron/ClientServerSends.java | 1 - .../src/main/java/reactor/aeron/ServerDemo.java | 4 +--- .../src/main/java/reactor/aeron/ServerThroughput.java | 3 --- 5 files changed, 2 insertions(+), 11 deletions(-) diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPingClient.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPingClient.java index d99867ac..a1dbeb9c 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPingClient.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPingClient.java @@ -86,7 +86,6 @@ private static void roundTripMessages(AeronConnection connection, connection .inbound() .receive() - .cast(DirectBuffer.class) .take(count) .doOnNext( buffer -> { diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPongServer.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPongServer.java index e6ba3a27..055ab435 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPongServer.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPongServer.java @@ -1,7 +1,5 @@ package reactor.aeron; -import org.agrona.DirectBuffer; - public final class AeronPongServer { /** @@ -28,7 +26,7 @@ public static void main(String... args) { connection -> connection .outbound() - .send(connection.inbound().receive().cast(DirectBuffer.class)) + .send(connection.inbound().receive()) .then(connection.onDispose())) .bind() .block() diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ClientServerSends.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ClientServerSends.java index db484edb..113c27c0 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ClientServerSends.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ClientServerSends.java @@ -25,7 +25,6 @@ public static void main(String[] args) { connection .inbound() .receive() - .cast(DirectBuffer.class) .as(ByteBufFlux::create) .asString() .log("receive") diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerDemo.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerDemo.java index bab98242..63fc0f1c 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerDemo.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerDemo.java @@ -2,8 +2,6 @@ import static reactor.aeron.DefaultFragmentMapper.asString; -import org.agrona.DirectBuffer; - public class ServerDemo { /** @@ -19,7 +17,7 @@ public static void main(String[] args) { .handle( connection -> connection - .inbound() + .inbound() .receive() .map(asString()) .log("receive") diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerThroughput.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerThroughput.java index 7176bc74..ca03f3f3 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerThroughput.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerThroughput.java @@ -1,7 +1,5 @@ package reactor.aeron; -import org.agrona.DirectBuffer; - public class ServerThroughput { /** @@ -31,7 +29,6 @@ public static void main(String[] args) { connection .inbound() .receive() - .cast(DirectBuffer.class) .doOnNext(buffer -> reporter.onMessage(1, buffer.capacity())) .then(connection.onDispose())) .bind() From e23c227ec6bd6d42b89b8eff9c030d23547d2214 Mon Sep 17 00:00:00 2001 From: segabriel Date: Sun, 15 Dec 2019 16:11:25 +0200 Subject: [PATCH 08/16] Updated aeron and reactor versions --- pom.xml | 4 +- reactor-aeron-benchmarks/pom.xml | 49 ++++++------------- .../aeron/netty/ReactorNettyClientPing.java | 3 -- .../aeron/netty/ReactorNettyServerPong.java | 3 +- 4 files changed, 18 insertions(+), 41 deletions(-) diff --git a/pom.xml b/pom.xml index 61569517..48d00c96 100644 --- a/pom.xml +++ b/pom.xml @@ -23,8 +23,8 @@ - 1.18.0 - Californium-SR5 + 1.24.0 + Dysprosium-RELEASE 1.7.7 2.11.0 diff --git a/reactor-aeron-benchmarks/pom.xml b/reactor-aeron-benchmarks/pom.xml index 5a973957..73f0ff17 100644 --- a/reactor-aeron-benchmarks/pom.xml +++ b/reactor-aeron-benchmarks/pom.xml @@ -5,8 +5,8 @@ 4.0.0 - 4.1.33.Final - 0.12.2-RC2 + 4.1.37.Final + 1.0.0-RC5 0.1.12 2.1.10 1.21 @@ -21,6 +21,19 @@ reactor-aeron-benchmarks + + + + io.netty + netty-bom + ${netty.version} + pom + import + + + + + io.scalecube @@ -64,46 +77,14 @@ disruptor - - io.netty - netty-common - ${netty.version} - io.netty netty-buffer - ${netty.version} - - - io.netty - netty-codec - ${netty.version} - - - io.netty - netty-codec-http - ${netty.version} - - - io.netty - netty-handler - ${netty.version} - - - io.netty - netty-handler-proxy - ${netty.version} - - - io.netty - netty-transport - ${netty.version} io.netty netty-transport-native-epoll linux-x86_64 - ${netty.version} diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/netty/ReactorNettyClientPing.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/netty/ReactorNettyClientPing.java index 5658d6de..3136e320 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/netty/ReactorNettyClientPing.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/netty/ReactorNettyClientPing.java @@ -20,7 +20,6 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.netty.Connection; -import reactor.netty.NettyPipeline.SendOptions; import reactor.netty.channel.BootstrapHandlers; import reactor.netty.resources.ConnectionProvider; import reactor.netty.resources.LoopResources; @@ -98,14 +97,12 @@ private static void roundTripMessages(Connection connection, long count) { connection .outbound() - .options(SendOptions::flushOnEach) .sendObject(Flux.range(0, Configurations.REQUESTED)) .then() .subscribe(); connection .outbound() - .options(SendOptions::flushOnEach) .sendObject( connection .inbound() diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/netty/ReactorNettyServerPong.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/netty/ReactorNettyServerPong.java index 94a8904a..effbdff1 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/netty/ReactorNettyServerPong.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/netty/ReactorNettyServerPong.java @@ -6,7 +6,6 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.LengthFieldPrepender; import reactor.aeron.Configurations; -import reactor.netty.NettyPipeline.SendOptions; import reactor.netty.channel.BootstrapHandlers; import reactor.netty.resources.LoopResources; import reactor.netty.tcp.TcpServer; @@ -49,7 +48,7 @@ public static void main(String[] args) { })) .handle( (inbound, outbound) -> - outbound.options(SendOptions::flushOnEach).send(inbound.receive().retain())) + outbound.send(inbound.receive().retain())) .bind() .doOnSuccess( server -> From b461ddaa976663dbdb9e3f064d50ced77cd905b7 Mon Sep 17 00:00:00 2001 From: segabriel Date: Sun, 15 Dec 2019 17:37:08 +0200 Subject: [PATCH 09/16] Moved server-client mdc things to mdc package --- .../java/reactor/aeron/AeronPingClient.java | 4 ++ .../java/reactor/aeron/AeronPongServer.java | 3 + .../main/java/reactor/aeron/ClientDemo.java | 2 + .../java/reactor/aeron/ClientServerSends.java | 2 + .../java/reactor/aeron/ClientThroughput.java | 2 + .../main/java/reactor/aeron/ServerDemo.java | 3 + .../java/reactor/aeron/ServerServerSends.java | 2 + .../java/reactor/aeron/ServerThroughput.java | 3 + .../rsocket/aeron/RSocketAeronClientTps.java | 33 ++++----- .../aeron/rsocket/aeron/RSocketAeronPing.java | 33 ++++----- .../aeron/rsocket/aeron/RSocketAeronPong.java | 58 +++++++--------- .../rsocket/aeron/RSocketAeronServerTps.java | 68 ++++++++----------- .../reactor/aeron/AeronEventLoopGroup.java | 8 +-- .../{ => mdc}/AeronChannelUriString.java | 2 +- .../reactor/aeron/{ => mdc}/AeronClient.java | 2 +- .../aeron/{ => mdc}/AeronClientConnector.java | 6 +- .../aeron/{ => mdc}/AeronConnection.java | 5 +- .../aeron/{ => mdc}/AeronExceptions.java | 2 +- .../reactor/aeron/{ => mdc}/AeronOptions.java | 2 +- .../aeron/{ => mdc}/AeronResources.java | 11 ++- .../reactor/aeron/{ => mdc}/AeronServer.java | 3 +- .../aeron/{ => mdc}/AeronServerHandler.java | 7 +- .../{ => mdc}/DuplexAeronConnection.java | 5 +- .../SecureRandomSessionIdGenerator.java | 3 +- .../aeron/{ => mdc}/AeronClientTest.java | 5 +- .../aeron/{ => mdc}/AeronConnectionTest.java | 11 ++- .../aeron/{ => mdc}/AeronMultiClientTest.java | 8 ++- .../aeron/{ => mdc}/AeronServerTest.java | 13 +++- 28 files changed, 179 insertions(+), 127 deletions(-) rename reactor-aeron/src/main/java/reactor/aeron/{ => mdc}/AeronChannelUriString.java (98%) rename reactor-aeron/src/main/java/reactor/aeron/{ => mdc}/AeronClient.java (99%) rename reactor-aeron/src/main/java/reactor/aeron/{ => mdc}/AeronClientConnector.java (97%) rename reactor-aeron/src/main/java/reactor/aeron/{ => mdc}/AeronConnection.java (94%) rename reactor-aeron/src/main/java/reactor/aeron/{ => mdc}/AeronExceptions.java (98%) rename reactor-aeron/src/main/java/reactor/aeron/{ => mdc}/AeronOptions.java (99%) rename reactor-aeron/src/main/java/reactor/aeron/{ => mdc}/AeronResources.java (98%) rename reactor-aeron/src/main/java/reactor/aeron/{ => mdc}/AeronServer.java (97%) rename reactor-aeron/src/main/java/reactor/aeron/{ => mdc}/AeronServerHandler.java (96%) rename reactor-aeron/src/main/java/reactor/aeron/{ => mdc}/DuplexAeronConnection.java (95%) rename reactor-aeron/src/main/java/reactor/aeron/{ => mdc}/SecureRandomSessionIdGenerator.java (90%) rename reactor-aeron/src/test/java/reactor/aeron/{ => mdc}/AeronClientTest.java (99%) rename reactor-aeron/src/test/java/reactor/aeron/{ => mdc}/AeronConnectionTest.java (95%) rename reactor-aeron/src/test/java/reactor/aeron/{ => mdc}/AeronMultiClientTest.java (96%) rename reactor-aeron/src/test/java/reactor/aeron/{ => mdc}/AeronServerTest.java (85%) diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPingClient.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPingClient.java index a1dbeb9c..d2852d99 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPingClient.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPingClient.java @@ -8,6 +8,9 @@ import org.agrona.DirectBuffer; import org.agrona.concurrent.UnsafeBuffer; import org.agrona.console.ContinueBarrier; +import reactor.aeron.mdc.AeronClient; +import reactor.aeron.mdc.AeronConnection; +import reactor.aeron.mdc.AeronResources; import reactor.core.Disposable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -102,6 +105,7 @@ private static void roundTripMessages(AeronConnection connection, } private static class NanoTimeGeneratorHandler implements DirectBufferHandler { + private static final UnsafeBuffer OFFER_BUFFER = new UnsafeBuffer( BufferUtil.allocateDirectAligned( diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPongServer.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPongServer.java index 055ab435..433b4b30 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPongServer.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPongServer.java @@ -1,5 +1,8 @@ package reactor.aeron; +import reactor.aeron.mdc.AeronResources; +import reactor.aeron.mdc.AeronServer; + public final class AeronPongServer { /** diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ClientDemo.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ClientDemo.java index 11df89cc..401ccae5 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ClientDemo.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ClientDemo.java @@ -1,6 +1,8 @@ package reactor.aeron; import java.util.stream.Stream; +import reactor.aeron.mdc.AeronClient; +import reactor.aeron.mdc.AeronResources; import reactor.core.publisher.Flux; public class ClientDemo { diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ClientServerSends.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ClientServerSends.java index 113c27c0..547b8b0b 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ClientServerSends.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ClientServerSends.java @@ -4,6 +4,8 @@ import io.netty.buffer.Unpooled; import java.nio.charset.Charset; import org.agrona.DirectBuffer; +import reactor.aeron.mdc.AeronClient; +import reactor.aeron.mdc.AeronResources; import reactor.core.CoreSubscriber; import reactor.core.publisher.Flux; import reactor.core.publisher.FluxOperator; diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ClientThroughput.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ClientThroughput.java index 20f23591..2a8c9d4e 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ClientThroughput.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ClientThroughput.java @@ -3,6 +3,8 @@ import org.agrona.BitUtil; import org.agrona.BufferUtil; import org.agrona.concurrent.UnsafeBuffer; +import reactor.aeron.mdc.AeronClient; +import reactor.aeron.mdc.AeronResources; import reactor.core.publisher.Flux; public class ClientThroughput { diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerDemo.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerDemo.java index 63fc0f1c..43ff17f0 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerDemo.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerDemo.java @@ -2,6 +2,9 @@ import static reactor.aeron.DefaultFragmentMapper.asString; +import reactor.aeron.mdc.AeronResources; +import reactor.aeron.mdc.AeronServer; + public class ServerDemo { /** diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerServerSends.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerServerSends.java index afc27e2a..9450dec6 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerServerSends.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerServerSends.java @@ -6,6 +6,8 @@ import java.time.Duration; import org.agrona.DirectBuffer; import org.agrona.concurrent.UnsafeBuffer; +import reactor.aeron.mdc.AeronResources; +import reactor.aeron.mdc.AeronServer; import reactor.core.publisher.Flux; public class ServerServerSends { diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerThroughput.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerThroughput.java index ca03f3f3..eb2135c5 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerThroughput.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerThroughput.java @@ -1,5 +1,8 @@ package reactor.aeron; +import reactor.aeron.mdc.AeronResources; +import reactor.aeron.mdc.AeronServer; + public class ServerThroughput { /** diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/rsocket/aeron/RSocketAeronClientTps.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/rsocket/aeron/RSocketAeronClientTps.java index e45f24ff..e01ec382 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/rsocket/aeron/RSocketAeronClientTps.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/rsocket/aeron/RSocketAeronClientTps.java @@ -2,14 +2,10 @@ import io.rsocket.Payload; import io.rsocket.RSocket; -import io.rsocket.RSocketFactory; -import io.rsocket.frame.decoder.PayloadDecoder; -import io.rsocket.reactor.aeron.AeronClientTransport; import io.rsocket.util.ByteBufPayload; -import reactor.aeron.AeronClient; -import reactor.aeron.AeronResources; import reactor.aeron.Configurations; import reactor.aeron.RateReporter; +import reactor.aeron.mdc.AeronResources; public final class RSocketAeronClientTps { @@ -31,19 +27,20 @@ public static void main(String... args) { .start() .block(); - RSocket client = - RSocketFactory.connect() - .frameDecoder(PayloadDecoder.ZERO_COPY) - .transport( - () -> - new AeronClientTransport( - AeronClient.create(resources) - .options( - Configurations.MDC_ADDRESS, - Configurations.MDC_PORT, - Configurations.MDC_CONTROL_PORT))) - .start() - .block(); + RSocket client = null; + // todo io.rsocket.transport.ClientTransport was changed + // RSocketFactory.connect() + // .frameDecoder(PayloadDecoder.ZERO_COPY) + // .transport( + // () -> + // new AeronClientTransport( + // AeronClient.create(resources) + // .options( + // Configurations.MDC_ADDRESS, + // Configurations.MDC_PORT, + // Configurations.MDC_CONTROL_PORT))) + // .start() + // .block(); RateReporter reporter = new RateReporter(); diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/rsocket/aeron/RSocketAeronPing.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/rsocket/aeron/RSocketAeronPing.java index 29efcea5..054c9b91 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/rsocket/aeron/RSocketAeronPing.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/rsocket/aeron/RSocketAeronPing.java @@ -3,16 +3,12 @@ import io.netty.buffer.ByteBufAllocator; import io.rsocket.Payload; import io.rsocket.RSocket; -import io.rsocket.RSocketFactory; -import io.rsocket.frame.decoder.PayloadDecoder; -import io.rsocket.reactor.aeron.AeronClientTransport; import io.rsocket.util.ByteBufPayload; import java.util.concurrent.TimeUnit; import org.HdrHistogram.Recorder; -import reactor.aeron.AeronClient; -import reactor.aeron.AeronResources; import reactor.aeron.Configurations; import reactor.aeron.LatencyReporter; +import reactor.aeron.mdc.AeronResources; import reactor.core.Disposable; import reactor.core.publisher.Flux; @@ -41,19 +37,20 @@ public static void main(String... args) { .start() .block(); - RSocket client = - RSocketFactory.connect() - .frameDecoder(PayloadDecoder.ZERO_COPY) - .transport( - () -> - new AeronClientTransport( - AeronClient.create(resources) - .options( - Configurations.MDC_ADDRESS, - Configurations.MDC_PORT, - Configurations.MDC_CONTROL_PORT))) - .start() - .block(); + RSocket client = null; + // todo io.rsocket.transport.ClientTransport was changed + // RSocketFactory.connect() + // .frameDecoder(PayloadDecoder.ZERO_COPY) + // .transport( + // () -> + // new AeronClientTransport( + // AeronClient.create(resources) + // .options( + // Configurations.MDC_ADDRESS, + // Configurations.MDC_PORT, + // Configurations.MDC_CONTROL_PORT))) + // .start() + // .block(); Disposable report = latencyReporter.start(); diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/rsocket/aeron/RSocketAeronPong.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/rsocket/aeron/RSocketAeronPong.java index bfc96f89..6aa3e715 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/rsocket/aeron/RSocketAeronPong.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/rsocket/aeron/RSocketAeronPong.java @@ -1,14 +1,7 @@ package reactor.aeron.rsocket.aeron; -import io.rsocket.AbstractRSocket; -import io.rsocket.Payload; -import io.rsocket.RSocketFactory; -import io.rsocket.frame.decoder.PayloadDecoder; -import io.rsocket.reactor.aeron.AeronServerTransport; -import reactor.aeron.AeronResources; -import reactor.aeron.AeronServer; import reactor.aeron.Configurations; -import reactor.core.publisher.Mono; +import reactor.aeron.mdc.AeronResources; public final class RSocketAeronPong { @@ -29,30 +22,31 @@ public static void main(String... args) { .start() .block(); - RSocketFactory.receive() - .frameDecoder(PayloadDecoder.ZERO_COPY) - .acceptor( - (setupPayload, rsocket) -> - Mono.just( - new AbstractRSocket() { - @Override - public Mono requestResponse(Payload payload) { - return Mono.just(payload); - } - })) - .transport( - new AeronServerTransport( - AeronServer.create(resources) - .options( - Configurations.MDC_ADDRESS, - Configurations.MDC_PORT, - Configurations.MDC_CONTROL_PORT))) - .start() - .block() - .onClose() - .doFinally(s -> resources.dispose()) - .then(resources.onDispose()) - .block(); + // todo io.rsocket.transport.ServerTransport was changed + // RSocketFactory.receive() + // .frameDecoder(PayloadDecoder.ZERO_COPY) + // .acceptor( + // (setupPayload, rsocket) -> + // Mono.just( + // new AbstractRSocket() { + // @Override + // public Mono requestResponse(Payload payload) { + // return Mono.just(payload); + // } + // })) + // .transport( + // new AeronServerTransport( + // AeronServer.create(resources) + // .options( + // Configurations.MDC_ADDRESS, + // Configurations.MDC_PORT, + // Configurations.MDC_CONTROL_PORT))) + // .start() + // .block() + // .onClose() + // .doFinally(s -> resources.dispose()) + // .then(resources.onDispose()) + // .block(); } private static void printSettings() { diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/rsocket/aeron/RSocketAeronServerTps.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/rsocket/aeron/RSocketAeronServerTps.java index 1ee067ec..69fc617d 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/rsocket/aeron/RSocketAeronServerTps.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/rsocket/aeron/RSocketAeronServerTps.java @@ -2,18 +2,9 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; -import io.rsocket.AbstractRSocket; -import io.rsocket.Payload; -import io.rsocket.RSocketFactory; -import io.rsocket.frame.decoder.PayloadDecoder; -import io.rsocket.reactor.aeron.AeronServerTransport; -import io.rsocket.util.ByteBufPayload; import java.util.Random; -import reactor.aeron.AeronResources; -import reactor.aeron.AeronServer; import reactor.aeron.Configurations; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; +import reactor.aeron.mdc.AeronResources; public final class RSocketAeronServerTps { @@ -45,36 +36,37 @@ public static void main(String... args) { .start() .block(); - RSocketFactory.receive() - .frameDecoder(PayloadDecoder.ZERO_COPY) - .acceptor( - (setupPayload, rsocket) -> - Mono.just( - new AbstractRSocket() { - @Override - public Flux requestStream(Payload payload) { - payload.release(); + // todo io.rsocket.transport.ServerTransport was changed + // RSocketFactory.receive() + // .frameDecoder(PayloadDecoder.ZERO_COPY) + // .acceptor( + // (setupPayload, rsocket) -> + // Mono.just( + // new AbstractRSocket() { + // @Override + // public Flux requestStream(Payload payload) { + // payload.release(); - long msgNum = Configurations.NUMBER_OF_MESSAGES; - System.out.println("streaming " + msgNum + " messages ..."); + // long msgNum = Configurations.NUMBER_OF_MESSAGES; + // System.out.println("streaming " + msgNum + " messages ..."); - return Flux.range(0, Integer.MAX_VALUE) - .map(i -> ByteBufPayload.create(BUFFER.retainedSlice())); - } - })) - .transport( - new AeronServerTransport( - AeronServer.create(resources) - .options( - Configurations.MDC_ADDRESS, - Configurations.MDC_PORT, - Configurations.MDC_CONTROL_PORT))) - .start() - .block() - .onClose() - .doFinally(s -> resources.dispose()) - .then(resources.onDispose()) - .block(); + // return Flux.range(0, Integer.MAX_VALUE) + // .map(i -> ByteBufPayload.create(BUFFER.retainedSlice())); + // } + // })) + // .transport( + // new AeronServerTransport( + // AeronServer.create(resources) + // .options( + // Configurations.MDC_ADDRESS, + // Configurations.MDC_PORT, + // Configurations.MDC_CONTROL_PORT))) + // .start() + // .block() + // .onClose() + // .doFinally(s -> resources.dispose()) + // .then(resources.onDispose()) + // .block(); } private static void printSettings() { diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronEventLoopGroup.java b/reactor-aeron/src/main/java/reactor/aeron/AeronEventLoopGroup.java index 20dfed88..0b87c8ae 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronEventLoopGroup.java +++ b/reactor-aeron/src/main/java/reactor/aeron/AeronEventLoopGroup.java @@ -13,7 +13,7 @@ * Wrapper around the {@link AeronEventLoop} where the actual logic is performed. Manages grouping * of multiple instances of {@link AeronEventLoop}: round-robin iteration and grouped disposal. */ -class AeronEventLoopGroup implements OnDisposable { +public class AeronEventLoopGroup implements OnDisposable { private static final Logger logger = LoggerFactory.getLogger(AeronEventLoopGroup.class); @@ -31,7 +31,7 @@ class AeronEventLoopGroup implements OnDisposable { * @param numOfWorkers number of {@link AeronEventLoop} instances in the group * @param workerIdleStrategySupplier factory for {@link IdleStrategy} instances */ - AeronEventLoopGroup( + public AeronEventLoopGroup( String name, int numOfWorkers, Supplier workerIdleStrategySupplier) { this.eventLoops = new AeronEventLoop[numOfWorkers]; for (int i = 0; i < numOfWorkers; i++) { @@ -52,11 +52,11 @@ class AeronEventLoopGroup implements OnDisposable { * * @return instance of worker in the group */ - AeronEventLoop next() { + public AeronEventLoop next() { return eventLoops[Math.abs(idx.getAndIncrement() % eventLoops.length)]; } - AeronEventLoop first() { + public AeronEventLoop first() { return eventLoops[0]; } diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronChannelUriString.java b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronChannelUriString.java similarity index 98% rename from reactor-aeron/src/main/java/reactor/aeron/AeronChannelUriString.java rename to reactor-aeron/src/main/java/reactor/aeron/mdc/AeronChannelUriString.java index d29543b9..618b2ea5 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronChannelUriString.java +++ b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronChannelUriString.java @@ -1,4 +1,4 @@ -package reactor.aeron; +package reactor.aeron.mdc; import io.aeron.ChannelUriStringBuilder; import java.util.function.UnaryOperator; diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronClient.java b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronClient.java similarity index 99% rename from reactor-aeron/src/main/java/reactor/aeron/AeronClient.java rename to reactor-aeron/src/main/java/reactor/aeron/mdc/AeronClient.java index d56a64f8..4593bbc5 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronClient.java +++ b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronClient.java @@ -1,4 +1,4 @@ -package reactor.aeron; +package reactor.aeron.mdc; import java.util.function.Function; import java.util.function.UnaryOperator; diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronClientConnector.java b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronClientConnector.java similarity index 97% rename from reactor-aeron/src/main/java/reactor/aeron/AeronClientConnector.java rename to reactor-aeron/src/main/java/reactor/aeron/mdc/AeronClientConnector.java index 2fcd4d8c..ce8cc439 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronClientConnector.java +++ b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronClientConnector.java @@ -1,4 +1,4 @@ -package reactor.aeron; +package reactor.aeron.mdc; import io.aeron.Image; import io.aeron.Publication; @@ -10,6 +10,10 @@ import org.reactivestreams.Publisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import reactor.aeron.AeronEventLoop; +import reactor.aeron.DefaultFragmentMapper; +import reactor.aeron.ImageAgent; +import reactor.aeron.PublicationAgent; import reactor.core.publisher.Mono; import reactor.core.publisher.MonoProcessor; diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronConnection.java b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronConnection.java similarity index 94% rename from reactor-aeron/src/main/java/reactor/aeron/AeronConnection.java rename to reactor-aeron/src/main/java/reactor/aeron/mdc/AeronConnection.java index 7d7d4992..eb2a3ca6 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronConnection.java +++ b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronConnection.java @@ -1,6 +1,9 @@ -package reactor.aeron; +package reactor.aeron.mdc; import org.reactivestreams.Subscription; +import reactor.aeron.AeronInbound; +import reactor.aeron.AeronOutbound; +import reactor.aeron.OnDisposable; import reactor.core.CoreSubscriber; import reactor.core.Disposable; import reactor.core.publisher.BaseSubscriber; diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronExceptions.java b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronExceptions.java similarity index 98% rename from reactor-aeron/src/main/java/reactor/aeron/AeronExceptions.java rename to reactor-aeron/src/main/java/reactor/aeron/mdc/AeronExceptions.java index c9877032..9106e8ed 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronExceptions.java +++ b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronExceptions.java @@ -1,4 +1,4 @@ -package reactor.aeron; +package reactor.aeron.mdc; class AeronExceptions { diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronOptions.java b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronOptions.java similarity index 99% rename from reactor-aeron/src/main/java/reactor/aeron/AeronOptions.java rename to reactor-aeron/src/main/java/reactor/aeron/mdc/AeronOptions.java index 08891f53..17aa4295 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronOptions.java +++ b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronOptions.java @@ -1,4 +1,4 @@ -package reactor.aeron; +package reactor.aeron.mdc; import java.time.Duration; import java.util.function.Consumer; diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronResources.java b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronResources.java similarity index 98% rename from reactor-aeron/src/main/java/reactor/aeron/AeronResources.java rename to reactor-aeron/src/main/java/reactor/aeron/mdc/AeronResources.java index 019e907e..741f3603 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronResources.java +++ b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronResources.java @@ -1,4 +1,4 @@ -package reactor.aeron; +package reactor.aeron.mdc; import io.aeron.Aeron; import io.aeron.ChannelUri; @@ -22,6 +22,15 @@ import org.agrona.concurrent.IdleStrategy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import reactor.aeron.AeronEventLoop; +import reactor.aeron.AeronEventLoopGroup; +import reactor.aeron.AeronInbound; +import reactor.aeron.AeronOutbound; +import reactor.aeron.FragmentMapper; +import reactor.aeron.ImageAgent; +import reactor.aeron.OnDisposable; +import reactor.aeron.PublicationAgent; +import reactor.aeron.SubscriptionAgent; import reactor.core.publisher.Mono; import reactor.core.publisher.MonoProcessor; import reactor.core.scheduler.Scheduler; diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronServer.java b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronServer.java similarity index 97% rename from reactor-aeron/src/main/java/reactor/aeron/AeronServer.java rename to reactor-aeron/src/main/java/reactor/aeron/mdc/AeronServer.java index 7f92d617..c522efde 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronServer.java +++ b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronServer.java @@ -1,9 +1,10 @@ -package reactor.aeron; +package reactor.aeron.mdc; import java.util.function.Function; import java.util.function.UnaryOperator; import org.agrona.DirectBuffer; import org.reactivestreams.Publisher; +import reactor.aeron.OnDisposable; import reactor.core.publisher.Mono; public final class AeronServer { diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronServerHandler.java b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronServerHandler.java similarity index 96% rename from reactor-aeron/src/main/java/reactor/aeron/AeronServerHandler.java rename to reactor-aeron/src/main/java/reactor/aeron/mdc/AeronServerHandler.java index 104d3cd3..022405ee 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronServerHandler.java +++ b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronServerHandler.java @@ -1,4 +1,4 @@ -package reactor.aeron; +package reactor.aeron.mdc; import io.aeron.Image; import io.aeron.Publication; @@ -13,6 +13,11 @@ import org.reactivestreams.Publisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import reactor.aeron.AeronEventLoop; +import reactor.aeron.DefaultFragmentMapper; +import reactor.aeron.ImageAgent; +import reactor.aeron.OnDisposable; +import reactor.aeron.PublicationAgent; import reactor.core.publisher.Mono; import reactor.core.publisher.MonoProcessor; diff --git a/reactor-aeron/src/main/java/reactor/aeron/DuplexAeronConnection.java b/reactor-aeron/src/main/java/reactor/aeron/mdc/DuplexAeronConnection.java similarity index 95% rename from reactor-aeron/src/main/java/reactor/aeron/DuplexAeronConnection.java rename to reactor-aeron/src/main/java/reactor/aeron/mdc/DuplexAeronConnection.java index b8b32968..02bac160 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/DuplexAeronConnection.java +++ b/reactor-aeron/src/main/java/reactor/aeron/mdc/DuplexAeronConnection.java @@ -1,9 +1,12 @@ -package reactor.aeron; +package reactor.aeron.mdc; import java.util.function.Function; import org.reactivestreams.Publisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import reactor.aeron.AeronInbound; +import reactor.aeron.AeronOutbound; +import reactor.aeron.OnDisposable; import reactor.core.publisher.Mono; import reactor.core.publisher.MonoProcessor; diff --git a/reactor-aeron/src/main/java/reactor/aeron/SecureRandomSessionIdGenerator.java b/reactor-aeron/src/main/java/reactor/aeron/mdc/SecureRandomSessionIdGenerator.java similarity index 90% rename from reactor-aeron/src/main/java/reactor/aeron/SecureRandomSessionIdGenerator.java rename to reactor-aeron/src/main/java/reactor/aeron/mdc/SecureRandomSessionIdGenerator.java index fb4315c6..b6c052db 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/SecureRandomSessionIdGenerator.java +++ b/reactor-aeron/src/main/java/reactor/aeron/mdc/SecureRandomSessionIdGenerator.java @@ -1,8 +1,9 @@ -package reactor.aeron; +package reactor.aeron.mdc; import io.aeron.driver.MediaDriver.Context; import java.security.SecureRandom; import java.util.function.Supplier; +import reactor.aeron.mdc.AeronResources; /** * Session id generator (in the range {@code 0..Int.MAX_VALUE}) based on {@link SecureRandom}. diff --git a/reactor-aeron/src/test/java/reactor/aeron/AeronClientTest.java b/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronClientTest.java similarity index 99% rename from reactor-aeron/src/test/java/reactor/aeron/AeronClientTest.java rename to reactor-aeron/src/test/java/reactor/aeron/mdc/AeronClientTest.java index 889979c1..f1b78ce5 100644 --- a/reactor-aeron/src/test/java/reactor/aeron/AeronClientTest.java +++ b/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronClientTest.java @@ -1,4 +1,4 @@ -package reactor.aeron; +package reactor.aeron.mdc; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static reactor.aeron.DefaultFragmentMapper.asString; @@ -20,6 +20,9 @@ import org.reactivestreams.Subscription; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import reactor.aeron.BaseAeronTest; +import reactor.aeron.OnDisposable; +import reactor.aeron.SocketUtils; import reactor.core.publisher.BaseSubscriber; import reactor.core.publisher.DirectProcessor; import reactor.core.publisher.Flux; diff --git a/reactor-aeron/src/test/java/reactor/aeron/AeronConnectionTest.java b/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronConnectionTest.java similarity index 95% rename from reactor-aeron/src/test/java/reactor/aeron/AeronConnectionTest.java rename to reactor-aeron/src/test/java/reactor/aeron/mdc/AeronConnectionTest.java index 55f59154..8f794016 100644 --- a/reactor-aeron/src/test/java/reactor/aeron/AeronConnectionTest.java +++ b/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronConnectionTest.java @@ -1,4 +1,4 @@ -package reactor.aeron; +package reactor.aeron.mdc; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -16,6 +16,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.reactivestreams.Publisher; +import reactor.aeron.BaseAeronTest; +import reactor.aeron.OnDisposable; +import reactor.aeron.SocketUtils; +import reactor.aeron.mdc.AeronClient; +import reactor.aeron.mdc.AeronConnection; +import reactor.aeron.mdc.AeronResources; +import reactor.aeron.mdc.AeronServer; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.publisher.ReplayProcessor; @@ -113,7 +120,7 @@ public void testClientClosesSessionAndServerHandleUnavailableImage() throws Exce server.dispose(); - latch.await(IMAGE_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS); + latch.await(IMAGE_TIMEOUT.multipliedBy(2).toMillis(), TimeUnit.MILLISECONDS); assertEquals(0, latch.getCount()); } diff --git a/reactor-aeron/src/test/java/reactor/aeron/AeronMultiClientTest.java b/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronMultiClientTest.java similarity index 96% rename from reactor-aeron/src/test/java/reactor/aeron/AeronMultiClientTest.java rename to reactor-aeron/src/test/java/reactor/aeron/mdc/AeronMultiClientTest.java index b2a4c7e6..6f8f26d6 100644 --- a/reactor-aeron/src/test/java/reactor/aeron/AeronMultiClientTest.java +++ b/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronMultiClientTest.java @@ -1,4 +1,4 @@ -package reactor.aeron; +package reactor.aeron.mdc; import java.time.Duration; import java.util.ArrayList; @@ -9,6 +9,12 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import reactor.aeron.BaseAeronTest; +import reactor.aeron.OnDisposable; +import reactor.aeron.SocketUtils; +import reactor.aeron.mdc.AeronClient; +import reactor.aeron.mdc.AeronResources; +import reactor.aeron.mdc.AeronServer; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; diff --git a/reactor-aeron/src/test/java/reactor/aeron/AeronServerTest.java b/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronServerTest.java similarity index 85% rename from reactor-aeron/src/test/java/reactor/aeron/AeronServerTest.java rename to reactor-aeron/src/test/java/reactor/aeron/mdc/AeronServerTest.java index 21940f0e..484d54e8 100644 --- a/reactor-aeron/src/test/java/reactor/aeron/AeronServerTest.java +++ b/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronServerTest.java @@ -1,4 +1,4 @@ -package reactor.aeron; +package reactor.aeron.mdc; import static org.junit.jupiter.api.Assertions.assertTrue; import static reactor.aeron.DefaultFragmentMapper.asString; @@ -8,9 +8,18 @@ import java.util.stream.Stream; import org.agrona.DirectBuffer; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.reactivestreams.Publisher; +import reactor.aeron.BaseAeronTest; +import reactor.aeron.OnDisposable; +import reactor.aeron.SocketUtils; +import reactor.aeron.ThreadWatcher; +import reactor.aeron.mdc.AeronClient; +import reactor.aeron.mdc.AeronConnection; +import reactor.aeron.mdc.AeronResources; +import reactor.aeron.mdc.AeronServer; import reactor.core.publisher.Flux; import reactor.core.publisher.ReplayProcessor; import reactor.test.StepVerifier; @@ -80,7 +89,7 @@ public void testServerDisconnectsClientsUponShutdown() throws InterruptedExcepti server.dispose(); - assertTrue(new ThreadWatcher().awaitTerminated(5000, "single-", "parallel-")); + Assertions.assertTrue(new ThreadWatcher().awaitTerminated(5000, "single-", "parallel-")); } private AeronConnection createConnection() { From 68004452abcc137bf9a48176136e8e2976c9abb3 Mon Sep 17 00:00:00 2001 From: segabriel Date: Sun, 15 Dec 2019 18:13:19 +0200 Subject: [PATCH 10/16] Migrate to AgentRunner --- .../java/reactor/aeron/AeronEventLoop.java | 148 +++--------------- .../reactor/aeron/AeronEventLoopGroup.java | 47 +----- .../reactor/aeron/WorkerFlightRecorder.java | 115 -------------- .../main/java/reactor/aeron/WorkerMBean.java | 50 ------ .../reactor/aeron/mdc/AeronResources.java | 24 +-- 5 files changed, 29 insertions(+), 355 deletions(-) delete mode 100644 reactor-aeron/src/main/java/reactor/aeron/WorkerFlightRecorder.java delete mode 100644 reactor-aeron/src/main/java/reactor/aeron/WorkerMBean.java diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronEventLoop.java b/reactor-aeron/src/main/java/reactor/aeron/AeronEventLoop.java index adee1402..193594ef 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronEventLoop.java +++ b/reactor-aeron/src/main/java/reactor/aeron/AeronEventLoop.java @@ -1,89 +1,42 @@ package reactor.aeron; -import java.lang.management.ManagementFactory; -import java.util.concurrent.ThreadFactory; -import javax.management.MBeanServer; -import javax.management.ObjectName; -import javax.management.StandardMBean; import org.agrona.CloseHelper; import org.agrona.concurrent.Agent; -import org.agrona.concurrent.AgentInvoker; +import org.agrona.concurrent.AgentRunner; import org.agrona.concurrent.IdleStrategy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import reactor.core.Exceptions; -import reactor.core.publisher.Mono; -import reactor.core.publisher.MonoProcessor; -public final class AeronEventLoop implements OnDisposable { +public final class AeronEventLoop implements AutoCloseable { private static final Logger logger = LoggerFactory.getLogger(AeronEventLoop.class); - private final IdleStrategy idleStrategy; - - private final String name; - - private final int workerId; // worker id - private final int groupId; // event loop group id - private final DynamicCompositeAgent agent; - private final AgentInvoker agentInvoker; - - private volatile Thread thread; - - private final MonoProcessor dispose = MonoProcessor.create(); - private final MonoProcessor onDispose = MonoProcessor.create(); + private final AgentRunner agentRunner; /** * Constructor. * - * @param name thread name + * @param name agent name * @param workerId worker id * @param groupId id of parent {@link AeronEventLoopGroup} * @param idleStrategy {@link IdleStrategy} instance for this event loop */ AeronEventLoop(String name, int workerId, int groupId, IdleStrategy idleStrategy) { - this.name = name; - this.workerId = workerId; - this.groupId = groupId; - this.idleStrategy = idleStrategy; - - this.agent = new DynamicCompositeAgent(String.format("%s-%x-%d", name, groupId, workerId)); - this.agentInvoker = - new AgentInvoker( - ex -> logger.error("Unexpected exception occurred on invoker: ", ex), null, agent); - - start(); + this(String.format("%s-%x-%d", name, groupId, workerId), idleStrategy); } - private static ThreadFactory defaultThreadFactory(String threadName) { - return r -> { - Thread thread = new Thread(r); - thread.setName(threadName); - thread.setUncaughtExceptionHandler( - (t, e) -> logger.error("Uncaught exception occurred: ", e)); - return thread; - }; - } - - private void start() { - try { - final String threadName = String.format("%s-%x-%d", name, groupId, workerId); - final ThreadFactory threadFactory = defaultThreadFactory(threadName); - - WorkerFlightRecorder flightRecorder = new WorkerFlightRecorder(); - MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer(); - ObjectName objectName = new ObjectName("reactor.aeron:name=" + threadName); - StandardMBean standardMBean = new StandardMBean(flightRecorder, WorkerMBean.class); - mbeanServer.registerMBean(standardMBean, objectName); - - Worker worker = new Worker(flightRecorder); - - thread = threadFactory.newThread(worker); - thread.start(); - } catch (Throwable th) { - throw Exceptions.propagate(th); - } + /** + * Constructor. + * + * @param name agent name + * @param idleStrategy {@link IdleStrategy} instance for this event loop + */ + public AeronEventLoop(String name, IdleStrategy idleStrategy) { + agent = new DynamicCompositeAgent(name); + agentRunner = + new AgentRunner(idleStrategy, th -> logger.error("Exception occurred: ", th), null, agent); + AgentRunner.startOnThread(agentRunner); } /** @@ -96,72 +49,7 @@ public void register(Agent resource) { } @Override - public void dispose() { - // start disposing worker (if any) - dispose.onComplete(); - - // finish shutdown right away if no worker was created - if (thread == null) { - onDispose.onComplete(); - } - } - - @Override - public Mono onDispose() { - return onDispose; - } - - @Override - public boolean isDisposed() { - return onDispose.isDisposed(); - } - - /** - * Runnable event loop worker. - * - *
    - *
  • runs until dispose signal obtained - *
  • on run iteration makes progress on: a) commands; b) publications; c) subscriptions - *
  • idles on zero progress - *
  • collects and reports runtime stats - *
- */ - private class Worker implements Runnable { - - private final WorkerFlightRecorder flightRecorder; - - private Worker(WorkerFlightRecorder flightRecorder) { - this.flightRecorder = flightRecorder; - } - - @Override - public void run() { - flightRecorder.start(); - agentInvoker.start(); - - try { - while (!dispose.isDisposed()) { - flightRecorder.countTick(); - - int workCount = agentInvoker.invoke(); - - if (workCount > 0) { - flightRecorder.countWork(workCount); - } else { - flightRecorder.countIdle(); - } - - // Reporting - flightRecorder.tryReport(); - - idleStrategy.idle(workCount); - } - } catch (Throwable th) { - dispose.dispose(); - } finally { - CloseHelper.close(agentInvoker); - onDispose.dispose(); - } - } + public void close() { + CloseHelper.quietClose(agentRunner); } } diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronEventLoopGroup.java b/reactor-aeron/src/main/java/reactor/aeron/AeronEventLoopGroup.java index 0b87c8ae..03a3595f 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronEventLoopGroup.java +++ b/reactor-aeron/src/main/java/reactor/aeron/AeronEventLoopGroup.java @@ -1,29 +1,20 @@ package reactor.aeron; -import java.util.Arrays; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; +import org.agrona.CloseHelper; import org.agrona.concurrent.IdleStrategy; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.publisher.Mono; -import reactor.core.publisher.MonoProcessor; /** * Wrapper around the {@link AeronEventLoop} where the actual logic is performed. Manages grouping * of multiple instances of {@link AeronEventLoop}: round-robin iteration and grouped disposal. */ -public class AeronEventLoopGroup implements OnDisposable { - - private static final Logger logger = LoggerFactory.getLogger(AeronEventLoopGroup.class); +public class AeronEventLoopGroup implements AutoCloseable { private final int id = System.identityHashCode(this); private final AeronEventLoop[] eventLoops; private final AtomicInteger idx = new AtomicInteger(); - private final MonoProcessor dispose = MonoProcessor.create(); - private final MonoProcessor onDispose = MonoProcessor.create(); - /** * Constructor. * @@ -37,14 +28,6 @@ public AeronEventLoopGroup( for (int i = 0; i < numOfWorkers; i++) { eventLoops[i] = new AeronEventLoop(name, i, id, workerIdleStrategySupplier.get()); } - - dispose - .then(doDispose()) - .doFinally(s -> onDispose.onComplete()) - .subscribe( - null, - th -> logger.warn("{} failed on doDispose(): {}", this, th.toString()), - () -> logger.debug("Disposed {}", this)); } /** @@ -61,30 +44,8 @@ public AeronEventLoop first() { } @Override - public void dispose() { - dispose.onComplete(); - } - - @Override - public boolean isDisposed() { - return onDispose.isDisposed(); - } - - @Override - public Mono onDispose() { - return onDispose; - } - - private Mono doDispose() { - return Mono.defer( - () -> { - logger.debug("Disposing {}", this); - return Mono.whenDelayError( - Arrays.stream(eventLoops) - .peek(AeronEventLoop::dispose) - .map(AeronEventLoop::onDispose) - .toArray(Mono[]::new)); - }); + public void close() { + CloseHelper.quietCloseAll(eventLoops); } @Override diff --git a/reactor-aeron/src/main/java/reactor/aeron/WorkerFlightRecorder.java b/reactor-aeron/src/main/java/reactor/aeron/WorkerFlightRecorder.java deleted file mode 100644 index 53720f80..00000000 --- a/reactor-aeron/src/main/java/reactor/aeron/WorkerFlightRecorder.java +++ /dev/null @@ -1,115 +0,0 @@ -package reactor.aeron; - -final class WorkerFlightRecorder implements WorkerMBean { - - private static final int REPORT_INTERVAL = 1000; - - private long reportTime; - - private long ticks; - private long workCount; - private long idleCount; - private double outboundRate; - private double inboundRate; - private double idleRate; - - long totalTickCount; - long totalOutboundCount; - long totalInboundCount; - long totalIdleCount; - long totalWorkCount; - - private long lastTotalTickCount; - private long lastTotalOutboundCount; - private long lastTotalInboundCount; - private long lastTotalIdleCount; - private long lastTotalWorkCount; - - void start() { - reportTime = System.currentTimeMillis() + REPORT_INTERVAL; - } - - /** - * Make reporting if it's time for it. For details see method: {@link #processReporting(long, - * long, long, long, long)} - */ - void tryReport() { - long currentTime = System.currentTimeMillis(); - if (currentTime >= reportTime) { - reportTime = currentTime + REPORT_INTERVAL; - processReporting( - totalTickCount, totalOutboundCount, totalInboundCount, totalIdleCount, totalWorkCount); - } - } - - @Override - public long getTicks() { - return ticks; - } - - @Override - public long getWorkCount() { - return workCount; - } - - @Override - public long getIdleCount() { - return idleCount; - } - - @Override - public double getOutboundRate() { - return outboundRate; - } - - @Override - public double getInboundRate() { - return inboundRate; - } - - @Override - public double getIdleRate() { - return idleRate; - } - - private void processReporting( - long totalTickCount, - long totalOutboundCount, - long totalInboundCount, - long totalIdleCount, - long totalWorkCount) { - - ticks = totalTickCount - lastTotalTickCount; - workCount = totalWorkCount - lastTotalWorkCount; - idleCount = totalIdleCount - lastTotalIdleCount; - outboundRate = (double) (totalOutboundCount - lastTotalOutboundCount) / ticks; - inboundRate = (double) (totalInboundCount - lastTotalInboundCount) / ticks; - idleRate = (double) (totalIdleCount - lastTotalIdleCount) / ticks; - - lastTotalTickCount = totalTickCount; - lastTotalWorkCount = totalWorkCount; - lastTotalIdleCount = totalIdleCount; - lastTotalOutboundCount = totalOutboundCount; - lastTotalInboundCount = totalInboundCount; - } - - void countTick() { - totalTickCount++; - } - - void countOutbound(int c) { - totalOutboundCount += c; - } - - void countInbound(int c) { - totalInboundCount += c; - } - - void countIdle() { - totalIdleCount++; - } - - void countWork(int c) { - totalWorkCount += c; - } -} diff --git a/reactor-aeron/src/main/java/reactor/aeron/WorkerMBean.java b/reactor-aeron/src/main/java/reactor/aeron/WorkerMBean.java deleted file mode 100644 index 8d4df20b..00000000 --- a/reactor-aeron/src/main/java/reactor/aeron/WorkerMBean.java +++ /dev/null @@ -1,50 +0,0 @@ -package reactor.aeron; - -/** - * JMX MBean exposer class for event loop worker thread (see for details {@link AeronEventLoop}). - * Contains various runtime stats. - */ -public interface WorkerMBean { - - /** - * Returns number of ticks per last second. - * - * @return number of ticks per last seconds - */ - long getTicks(); - - /** - * Returns number of work done (counted both outbound and inbound) per last second. - * - * @return number of work done per last second. - */ - long getWorkCount(); - - /** - * Returns number of how many times event loop was idling without progress done. - * - * @return number of times being idle - */ - long getIdleCount(); - - /** - * Returns amount of outbound work done per one tick. - * - * @return amount of outbound work done per tick - */ - double getOutboundRate(); - - /** - * Returns amount of inbound work done per one tick. - * - * @return amount of inbound work done per tick - */ - double getInboundRate(); - - /** - * Returns amount of being idle per one tick. - * - * @return amount of being idle per tick - */ - double getIdleRate(); -} diff --git a/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronResources.java b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronResources.java index 741f3603..e76c90c9 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronResources.java +++ b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronResources.java @@ -56,6 +56,7 @@ public final class AeronResources implements OnDisposable { .errorHandler(th -> logger.warn("Exception occurred on MediaDriver: " + th, th)) .warnIfDirectoryExists(true) .dirDeleteOnStart(true) + .dirDeleteOnShutdown(true) // low latency settings .termBufferSparseFile(false) // explicit range of reserved session ids @@ -525,24 +526,13 @@ public Mono onDispose() { } private Mono doDispose() { - return Mono.defer( + return Mono.fromRunnable( () -> { - logger.debug("Disposing {}", this); - - return Mono // - .fromRunnable(eventLoopGroup::dispose) - .then(eventLoopGroup.onDispose()) - .doFinally( - s -> { - CloseHelper.quietClose(aeron); - - CloseHelper.quietClose(mediaDriver); - - Optional.ofNullable(aeronContext) - .ifPresent(c -> IoUtil.delete(c.aeronDirectory(), true)); - - scheduler.dispose(); - }); + CloseHelper.quietClose(eventLoopGroup); + CloseHelper.quietClose(aeron); + CloseHelper.quietClose(mediaDriver); + scheduler.dispose(); + logger.debug("Disposed {}", this); }); } From d975870fc10502355729df14f09e5ade2932ce4b Mon Sep 17 00:00:00 2001 From: segabriel Date: Sun, 15 Dec 2019 18:25:08 +0200 Subject: [PATCH 11/16] Polishing --- .../src/main/java/reactor/aeron/Configurations.java | 4 ++-- .../src/main/java/reactor/aeron/pure/ServerThroughput.java | 2 +- .../reactor/aeron/mdc/SecureRandomSessionIdGenerator.java | 1 - .../src/test/java/reactor/aeron/mdc/AeronConnectionTest.java | 4 ---- .../test/java/reactor/aeron/mdc/AeronMultiClientTest.java | 3 --- .../src/test/java/reactor/aeron/mdc/AeronServerTest.java | 5 ----- 6 files changed, 3 insertions(+), 16 deletions(-) diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/Configurations.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/Configurations.java index 7f2180e4..987c1cfb 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/Configurations.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/Configurations.java @@ -15,7 +15,7 @@ public interface Configurations { int FRAGMENT_COUNT_LIMIT = Integer.getInteger("reactor.aeron.sample.frameCountLimit", 10); int MESSAGE_LENGTH = Integer.getInteger("reactor.aeron.sample.messageLength", 128); int REQUESTED = Integer.getInteger("reactor.aeron.sample.request", 16); - + int WARMUP_NUMBER_OF_ITERATIONS = Integer.getInteger("reactor.aeron.sample.warmup.iterations", 5); long WARMUP_NUMBER_OF_MESSAGES = Long.getLong("reactor.aeron.sample.warmup.messages", 10_000); long NUMBER_OF_MESSAGES = Long.getLong("reactor.aeron.sample.messages", 100_000_000); @@ -34,7 +34,7 @@ public interface Configurations { int MDC_CONTROL_PORT = Integer.getInteger("reactor.aeron.sample.mdc.control.port", 13001); int MDC_STREAM_ID = Integer.getInteger("reactor.aeron.sample.mdc.stream.id", 0xcafe0000); int MDC_SESSION_ID = Integer.getInteger("reactor.aeron.sample.mdc.session.id", 1001); - + String IDLE_STRATEGY = System.getProperty("reactor.aeron.sample.idle.strategy", "busyspin"); long REPORT_INTERVAL = Long.getLong("reactor.aeron.sample.report.interval", 1); long TRACE_REPORTER_INTERVAL = Long.getLong("reactor.aeron.sample.report.interval", 60); diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/pure/ServerThroughput.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/pure/ServerThroughput.java index ab5404be..538f8fc8 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/pure/ServerThroughput.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/pure/ServerThroughput.java @@ -75,7 +75,7 @@ public static void main(final String[] args) { SigInt.register(() -> running.set(false)); RateReporter reporter = new RateReporter(); - + try (Aeron aeron = Aeron.connect(ctx); Subscription subscription = aeron.addSubscription(INBOUND_CHANNEL, STREAM_ID); Publication publication = diff --git a/reactor-aeron/src/main/java/reactor/aeron/mdc/SecureRandomSessionIdGenerator.java b/reactor-aeron/src/main/java/reactor/aeron/mdc/SecureRandomSessionIdGenerator.java index b6c052db..79381cca 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/mdc/SecureRandomSessionIdGenerator.java +++ b/reactor-aeron/src/main/java/reactor/aeron/mdc/SecureRandomSessionIdGenerator.java @@ -3,7 +3,6 @@ import io.aeron.driver.MediaDriver.Context; import java.security.SecureRandom; import java.util.function.Supplier; -import reactor.aeron.mdc.AeronResources; /** * Session id generator (in the range {@code 0..Int.MAX_VALUE}) based on {@link SecureRandom}. diff --git a/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronConnectionTest.java b/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronConnectionTest.java index 8f794016..fc1f7922 100644 --- a/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronConnectionTest.java +++ b/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronConnectionTest.java @@ -19,10 +19,6 @@ import reactor.aeron.BaseAeronTest; import reactor.aeron.OnDisposable; import reactor.aeron.SocketUtils; -import reactor.aeron.mdc.AeronClient; -import reactor.aeron.mdc.AeronConnection; -import reactor.aeron.mdc.AeronResources; -import reactor.aeron.mdc.AeronServer; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.publisher.ReplayProcessor; diff --git a/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronMultiClientTest.java b/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronMultiClientTest.java index 6f8f26d6..0d3ceb70 100644 --- a/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronMultiClientTest.java +++ b/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronMultiClientTest.java @@ -12,9 +12,6 @@ import reactor.aeron.BaseAeronTest; import reactor.aeron.OnDisposable; import reactor.aeron.SocketUtils; -import reactor.aeron.mdc.AeronClient; -import reactor.aeron.mdc.AeronResources; -import reactor.aeron.mdc.AeronServer; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; diff --git a/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronServerTest.java b/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronServerTest.java index 484d54e8..47658689 100644 --- a/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronServerTest.java +++ b/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronServerTest.java @@ -1,6 +1,5 @@ package reactor.aeron.mdc; -import static org.junit.jupiter.api.Assertions.assertTrue; import static reactor.aeron.DefaultFragmentMapper.asString; import java.time.Duration; @@ -16,10 +15,6 @@ import reactor.aeron.OnDisposable; import reactor.aeron.SocketUtils; import reactor.aeron.ThreadWatcher; -import reactor.aeron.mdc.AeronClient; -import reactor.aeron.mdc.AeronConnection; -import reactor.aeron.mdc.AeronResources; -import reactor.aeron.mdc.AeronServer; import reactor.core.publisher.Flux; import reactor.core.publisher.ReplayProcessor; import reactor.test.StepVerifier; From 801bbd1c37ea15dece75fcac694be96bba559e1f Mon Sep 17 00:00:00 2001 From: segabriel Date: Sun, 15 Dec 2019 18:30:35 +0200 Subject: [PATCH 12/16] Increased timeout for tests --- .../src/test/java/reactor/aeron/mdc/AeronConnectionTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronConnectionTest.java b/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronConnectionTest.java index fc1f7922..14d01688 100644 --- a/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronConnectionTest.java +++ b/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronConnectionTest.java @@ -85,7 +85,7 @@ public void testServerDisconnectsSessionAndClientHandleUnavailableImage() connection.dispose(); - latch.await(IMAGE_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS); + latch.await(IMAGE_TIMEOUT.multipliedBy(2).toMillis(), TimeUnit.MILLISECONDS); assertEquals(0, latch.getCount()); } From 6a17e69bedad31f57ac29649631d1ac44368ccff Mon Sep 17 00:00:00 2001 From: segabriel Date: Sat, 28 Dec 2019 15:12:29 +0200 Subject: [PATCH 13/16] Use ControlledFragmentHandler in polling agents --- .../main/java/reactor/aeron/ImageAgent.java | 33 ++++++++++--------- .../java/reactor/aeron/SubscriptionAgent.java | 33 ++++++++++--------- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/reactor-aeron/src/main/java/reactor/aeron/ImageAgent.java b/reactor-aeron/src/main/java/reactor/aeron/ImageAgent.java index 4abe23f5..40852b38 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/ImageAgent.java +++ b/reactor-aeron/src/main/java/reactor/aeron/ImageAgent.java @@ -1,9 +1,9 @@ package reactor.aeron; import io.aeron.Aeron; +import io.aeron.ControlledFragmentAssembler; import io.aeron.Image; -import io.aeron.ImageFragmentAssembler; -import io.aeron.logbuffer.FragmentHandler; +import io.aeron.logbuffer.ControlledFragmentHandler; import io.aeron.logbuffer.Header; import java.util.concurrent.atomic.AtomicLongFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; @@ -42,8 +42,8 @@ public class ImageAgent implements Agent, AeronInbound, Disposable { private final long stopPosition; private final FragmentMapper mapper; - private final FragmentHandler fragmentHandler = - new ImageFragmentAssembler(new AgentFragmentHandler()); + private final ControlledFragmentHandler fragmentHandler = + new ControlledFragmentAssembler(new AgentFragmentHandler()); private volatile long requested; private volatile boolean fastPath; @@ -115,12 +115,12 @@ public int doWork() { throw new AgentTerminationException("Image is closed"); } if (fastPath) { - return image.poll(fragmentHandler, FRAGMENT_LIMIT); + return image.controlledPoll(fragmentHandler, FRAGMENT_LIMIT); } int r = (int) Math.min(requested, FRAGMENT_LIMIT); int fragments = 0; if (r > 0) { - fragments = image.poll(fragmentHandler, r); + fragments = image.controlledPoll(fragmentHandler, r); if (produced > 0) { Operators.produced(REQUESTED, this, produced); produced = 0; @@ -162,22 +162,25 @@ public boolean isDisposed() { return CANCELLED_SUBSCRIBER.equals(destinationSubscriber); } - private class AgentFragmentHandler implements FragmentHandler { + private class AgentFragmentHandler implements ControlledFragmentHandler { @Override - public void onFragment(DirectBuffer buffer, int offset, int length, Header header) { + public Action onFragment(DirectBuffer buffer, int offset, int length, Header header) { try { - if (ex == null) { - T t = mapper.apply(buffer, offset, length, header); - if (t != null) { - produced++; - CoreSubscriber destination = ImageAgent.this.destinationSubscriber; - destination.onNext(t); - } + CoreSubscriber destination = ImageAgent.this.destinationSubscriber; + if (CANCELLED_SUBSCRIBER.equals(destination)) { + return Action.ABORT; + } + T t = mapper.apply(buffer, offset, length, header); + if (t != null) { + destination.onNext(t); + produced++; } } catch (Exception e) { ex = e; + return Action.ABORT; } + return Action.CONTINUE; } } diff --git a/reactor-aeron/src/main/java/reactor/aeron/SubscriptionAgent.java b/reactor-aeron/src/main/java/reactor/aeron/SubscriptionAgent.java index 6df5a35f..8698f4ba 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/SubscriptionAgent.java +++ b/reactor-aeron/src/main/java/reactor/aeron/SubscriptionAgent.java @@ -1,8 +1,8 @@ package reactor.aeron; -import io.aeron.ImageFragmentAssembler; +import io.aeron.ControlledFragmentAssembler; import io.aeron.Subscription; -import io.aeron.logbuffer.FragmentHandler; +import io.aeron.logbuffer.ControlledFragmentHandler; import io.aeron.logbuffer.Header; import java.util.concurrent.atomic.AtomicLongFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; @@ -40,8 +40,8 @@ public class SubscriptionAgent implements Agent, AeronInbound, Disposable private final boolean shouldCloseSubscription; private final FragmentMapper mapper; - private final FragmentHandler fragmentHandler = - new ImageFragmentAssembler(new AgentFragmentHandler()); + private final ControlledFragmentHandler fragmentHandler = + new ControlledFragmentAssembler(new AgentFragmentHandler()); private volatile long requested; private volatile boolean fastPath; @@ -93,12 +93,12 @@ public int doWork() { throw new AgentTerminationException("Subscription is closed"); } if (fastPath) { - return subscription.poll(fragmentHandler, FRAGMENT_LIMIT); + return subscription.controlledPoll(fragmentHandler, FRAGMENT_LIMIT); } int r = (int) Math.min(requested, FRAGMENT_LIMIT); int fragments = 0; if (r > 0) { - fragments = subscription.poll(fragmentHandler, r); + fragments = subscription.controlledPoll(fragmentHandler, r); if (produced > 0) { Operators.produced(REQUESTED, this, produced); produced = 0; @@ -140,22 +140,25 @@ public boolean isDisposed() { return CANCELLED_SUBSCRIBER.equals(destinationSubscriber); } - private class AgentFragmentHandler implements FragmentHandler { + private class AgentFragmentHandler implements ControlledFragmentHandler { @Override - public void onFragment(DirectBuffer buffer, int offset, int length, Header header) { + public Action onFragment(DirectBuffer buffer, int offset, int length, Header header) { try { - if (ex == null) { - T t = mapper.apply(buffer, offset, length, header); - if (t != null) { - produced++; - CoreSubscriber destination = SubscriptionAgent.this.destinationSubscriber; - destination.onNext(t); - } + CoreSubscriber destination = SubscriptionAgent.this.destinationSubscriber; + if (CANCELLED_SUBSCRIBER.equals(destination)) { + return Action.ABORT; + } + T t = mapper.apply(buffer, offset, length, header); + if (t != null) { + destination.onNext(t); + produced++; } } catch (Exception e) { ex = e; + return Action.ABORT; } + return Action.CONTINUE; } } From 31abba9f155f8a70ce27f4557abe143bb9b89b2a Mon Sep 17 00:00:00 2001 From: segabriel Date: Sat, 28 Dec 2019 17:11:54 +0200 Subject: [PATCH 14/16] Make inbound/outbound extended on OnDisposable --- .../main/java/reactor/aeron/AeronInbound.java | 3 +-- .../java/reactor/aeron/AeronOutbound.java | 3 +-- .../java/reactor/aeron/AeronOutboundThen.java | 5 +++++ .../main/java/reactor/aeron/ImageAgent.java | 11 ++++++++++- .../main/java/reactor/aeron/OnDisposable.java | 4 ++-- .../java/reactor/aeron/PublicationAgent.java | 19 +++++++++++++------ .../java/reactor/aeron/SubscriptionAgent.java | 11 ++++++++++- .../aeron/mdc/DuplexAeronConnection.java | 3 +-- 8 files changed, 43 insertions(+), 16 deletions(-) diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronInbound.java b/reactor-aeron/src/main/java/reactor/aeron/AeronInbound.java index e34988f7..36da63bf 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronInbound.java +++ b/reactor-aeron/src/main/java/reactor/aeron/AeronInbound.java @@ -1,9 +1,8 @@ package reactor.aeron; -import reactor.core.Disposable; import reactor.core.publisher.Flux; -public interface AeronInbound extends Disposable { +public interface AeronInbound extends OnDisposable { Flux receive(); } diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronOutbound.java b/reactor-aeron/src/main/java/reactor/aeron/AeronOutbound.java index 3aa4a4b9..d13c4915 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronOutbound.java +++ b/reactor-aeron/src/main/java/reactor/aeron/AeronOutbound.java @@ -6,11 +6,10 @@ import org.agrona.concurrent.UnsafeBuffer; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; -import reactor.core.Disposable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -public interface AeronOutbound extends Publisher, Disposable { +public interface AeronOutbound extends Publisher, OnDisposable { /** * Send data to the peer, listen for any error on write and close on terminal signal diff --git a/reactor-aeron/src/main/java/reactor/aeron/AeronOutboundThen.java b/reactor-aeron/src/main/java/reactor/aeron/AeronOutboundThen.java index 447a2701..ce6079fa 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/AeronOutboundThen.java +++ b/reactor-aeron/src/main/java/reactor/aeron/AeronOutboundThen.java @@ -60,4 +60,9 @@ public void dispose() { public boolean isDisposed() { return source.isDisposed(); } + + @Override + public Mono onDispose() { + return source.onDispose(); + } } diff --git a/reactor-aeron/src/main/java/reactor/aeron/ImageAgent.java b/reactor-aeron/src/main/java/reactor/aeron/ImageAgent.java index 40852b38..98333e4d 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/ImageAgent.java +++ b/reactor-aeron/src/main/java/reactor/aeron/ImageAgent.java @@ -17,6 +17,8 @@ import reactor.core.Disposable; import reactor.core.Exceptions; import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.publisher.MonoProcessor; import reactor.core.publisher.Operators; public class ImageAgent implements Agent, AeronInbound, Disposable { @@ -36,6 +38,7 @@ public class ImageAgent implements Agent, AeronInbound, Disposable { private static final int FRAGMENT_LIMIT = 10; private final FluxReceive inbound = new FluxReceive(); + private final MonoProcessor onDispose = MonoProcessor.create(); private final Image image; private final boolean shouldCloseSubscription; @@ -136,6 +139,7 @@ public void onClose() { if (shouldCloseSubscription) { CloseHelper.quietClose(image.subscription()); } + onDispose.onComplete(); } @Override @@ -159,7 +163,12 @@ public void dispose() { @Override public boolean isDisposed() { - return CANCELLED_SUBSCRIBER.equals(destinationSubscriber); + return onDispose.isDisposed(); + } + + @Override + public Mono onDispose() { + return onDispose; } private class AgentFragmentHandler implements ControlledFragmentHandler { diff --git a/reactor-aeron/src/main/java/reactor/aeron/OnDisposable.java b/reactor-aeron/src/main/java/reactor/aeron/OnDisposable.java index 19bbfcd5..5a5d01e7 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/OnDisposable.java +++ b/reactor-aeron/src/main/java/reactor/aeron/OnDisposable.java @@ -18,8 +18,8 @@ public interface OnDisposable extends Disposable { * @param onDispose the close event handler * @return this */ - default OnDisposable onDispose(Disposable onDispose) { + default T onDispose(Disposable onDispose) { onDispose().subscribe(null, e -> onDispose.dispose(), onDispose::dispose); - return this; + return (T) this; } } diff --git a/reactor-aeron/src/main/java/reactor/aeron/PublicationAgent.java b/reactor-aeron/src/main/java/reactor/aeron/PublicationAgent.java index 3f245648..4f74630f 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/PublicationAgent.java +++ b/reactor-aeron/src/main/java/reactor/aeron/PublicationAgent.java @@ -39,6 +39,7 @@ public final class PublicationAgent implements Agent, AeronOutbound, Disposable private volatile Throwable lastError; + private final MonoProcessor onDispose = MonoProcessor.create(); private volatile boolean isDisposed = false; /** @@ -167,6 +168,7 @@ public void onClose() { for (PublisherProcessor processor : oldArray) { processor.cancelDueTo(throwable); } + onDispose.onComplete(); } @Override @@ -181,7 +183,7 @@ public void dispose() { @Override public boolean isDisposed() { - return isDisposed; + return onDispose.isDisposed(); } @Override @@ -200,6 +202,11 @@ private Mono publish( }); } + @Override + public Mono onDispose() { + return onDispose; + } + private static class PublisherProcessor extends BaseSubscriber { private final DirectBufferHandler bufferHandler; @@ -223,7 +230,7 @@ private Mono onDispose() { return onDispose; } - void request() { + private void request() { if (requested || isDisposed()) { return; } @@ -235,7 +242,7 @@ void request() { } } - void reset() { + private void reset() { resetBuffer(); if (isDisposed()) { @@ -283,18 +290,18 @@ protected void hookFinally(SignalType type) { } } - long publish(B buffer) { + private long publish(B buffer) { if (start == 0) { start = System.currentTimeMillis(); } return parent.publication.offer(bufferHandler.map(buffer)); } - boolean isTimeoutElapsed(Duration timeout) { + private boolean isTimeoutElapsed(Duration timeout) { return System.currentTimeMillis() - start > timeout.toMillis(); } - void cancelDueTo(Throwable throwable) { + private void cancelDueTo(Throwable throwable) { try { cancel(); resetBuffer(); diff --git a/reactor-aeron/src/main/java/reactor/aeron/SubscriptionAgent.java b/reactor-aeron/src/main/java/reactor/aeron/SubscriptionAgent.java index 8698f4ba..f63d4f71 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/SubscriptionAgent.java +++ b/reactor-aeron/src/main/java/reactor/aeron/SubscriptionAgent.java @@ -16,6 +16,8 @@ import reactor.core.Disposable; import reactor.core.Exceptions; import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.publisher.MonoProcessor; import reactor.core.publisher.Operators; public class SubscriptionAgent implements Agent, AeronInbound, Disposable { @@ -35,6 +37,7 @@ public class SubscriptionAgent implements Agent, AeronInbound, Disposable private static final int FRAGMENT_LIMIT = 10; private final FluxReceive inbound = new FluxReceive(); + private final MonoProcessor onDispose = MonoProcessor.create(); private final Subscription subscription; private final boolean shouldCloseSubscription; @@ -114,6 +117,7 @@ public void onClose() { if (shouldCloseSubscription) { CloseHelper.quietClose(subscription); } + onDispose.onComplete(); } @Override @@ -137,7 +141,12 @@ public void dispose() { @Override public boolean isDisposed() { - return CANCELLED_SUBSCRIBER.equals(destinationSubscriber); + return onDispose.isDisposed(); + } + + @Override + public Mono onDispose() { + return onDispose; } private class AgentFragmentHandler implements ControlledFragmentHandler { diff --git a/reactor-aeron/src/main/java/reactor/aeron/mdc/DuplexAeronConnection.java b/reactor-aeron/src/main/java/reactor/aeron/mdc/DuplexAeronConnection.java index 02bac160..138fd712 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/mdc/DuplexAeronConnection.java +++ b/reactor-aeron/src/main/java/reactor/aeron/mdc/DuplexAeronConnection.java @@ -6,13 +6,12 @@ import org.slf4j.LoggerFactory; import reactor.aeron.AeronInbound; import reactor.aeron.AeronOutbound; -import reactor.aeron.OnDisposable; import reactor.core.publisher.Mono; import reactor.core.publisher.MonoProcessor; /** * Full-duplex aeron connection. Bound to certain {@code sessionId}. Implements {@link - * OnDisposable} for convenient resource cleanup. + * reactor.aeron.OnDisposable} for convenient resource cleanup. */ final class DuplexAeronConnection implements AeronConnection { From 29a05dbfe31f80013bdf283191ad87ef4549a675 Mon Sep 17 00:00:00 2001 From: segabriel Date: Sat, 28 Dec 2019 21:20:53 +0200 Subject: [PATCH 15/16] Refactor aeron duplex --- .../java/reactor/aeron/AeronPingClient.java | 5 +- .../AeronConnection.java => AeronDuplex.java} | 27 +---- .../reactor/aeron/DefaultAeronDuplex.java | 48 ++++++++ .../java/reactor/aeron/mdc/AeronClient.java | 7 +- .../aeron/mdc/AeronClientConnector.java | 56 ++++----- .../java/reactor/aeron/mdc/AeronOptions.java | 7 +- .../java/reactor/aeron/mdc/AeronServer.java | 3 +- .../reactor/aeron/mdc/AeronServerHandler.java | 91 ++++++-------- .../aeron/mdc/DuplexAeronConnection.java | 114 ------------------ .../reactor/aeron/mdc/AeronClientTest.java | 31 ++--- .../aeron/mdc/AeronConnectionTest.java | 21 ++-- .../reactor/aeron/mdc/AeronServerTest.java | 5 +- 12 files changed, 161 insertions(+), 254 deletions(-) rename reactor-aeron/src/main/java/reactor/aeron/{mdc/AeronConnection.java => AeronDuplex.java} (68%) create mode 100644 reactor-aeron/src/main/java/reactor/aeron/DefaultAeronDuplex.java delete mode 100644 reactor-aeron/src/main/java/reactor/aeron/mdc/DuplexAeronConnection.java diff --git a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPingClient.java b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPingClient.java index d2852d99..a754452e 100644 --- a/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPingClient.java +++ b/reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPingClient.java @@ -9,7 +9,6 @@ import org.agrona.concurrent.UnsafeBuffer; import org.agrona.console.ContinueBarrier; import reactor.aeron.mdc.AeronClient; -import reactor.aeron.mdc.AeronConnection; import reactor.aeron.mdc.AeronResources; import reactor.core.Disposable; import reactor.core.publisher.Flux; @@ -36,7 +35,7 @@ public static void main(String... args) { .start() .block(); - AeronConnection connection = + AeronDuplex connection = AeronClient.create(resources) .options( Configurations.MDC_ADDRESS, @@ -74,7 +73,7 @@ public static void main(String... args) { connection.onDispose(resources).onDispose().block(); } - private static void roundTripMessages(AeronConnection connection, long count) { + private static void roundTripMessages(AeronDuplex connection, long count) { HISTOGRAM.reset(); Disposable disp = reporter.start(); diff --git a/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronConnection.java b/reactor-aeron/src/main/java/reactor/aeron/AeronDuplex.java similarity index 68% rename from reactor-aeron/src/main/java/reactor/aeron/mdc/AeronConnection.java rename to reactor-aeron/src/main/java/reactor/aeron/AeronDuplex.java index eb2a3ca6..99140438 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronConnection.java +++ b/reactor-aeron/src/main/java/reactor/aeron/AeronDuplex.java @@ -1,11 +1,7 @@ -package reactor.aeron.mdc; +package reactor.aeron; import org.reactivestreams.Subscription; -import reactor.aeron.AeronInbound; -import reactor.aeron.AeronOutbound; -import reactor.aeron.OnDisposable; import reactor.core.CoreSubscriber; -import reactor.core.Disposable; import reactor.core.publisher.BaseSubscriber; import reactor.core.publisher.SignalType; @@ -16,35 +12,24 @@ * for reading and {@link #outbound()} for writing data. AeronConnection interface comes with {@link * OnDisposable} and {@link #disposeSubscriber()} function for convenient resource cleanup. */ -public interface AeronConnection extends OnDisposable { +public interface AeronDuplex extends OnDisposable { /** - * Return the {@link AeronInbound} read API from this connection. If {@link AeronConnection} has - * not been configured, receive operations will be unavailable. + * Return the {@link AeronInbound} read API from this connection. If {@link AeronDuplex} has not + * been configured, receive operations will be unavailable. * * @return {@code AeronInbound} instance */ AeronInbound inbound(); /** - * Return the {@link AeronOutbound} write API from this connection. If {@link AeronConnection} has - * not been configured, send operations will be unavailable. + * Return the {@link AeronOutbound} write API from this connection. If {@link AeronDuplex} has not + * been configured, send operations will be unavailable. * * @return {@code AeronOutbound} instance */ AeronOutbound outbound(); - /** - * Assign a {@link Disposable} to be invoked when the channel is closed. - * - * @param onDispose the close event handler - * @return {@code this} instance - */ - default AeronConnection onDispose(Disposable onDispose) { - onDispose().doOnTerminate(onDispose::dispose).subscribe(); - return this; - } - /** * Return a {@link CoreSubscriber} that will dispose on complete or error. * diff --git a/reactor-aeron/src/main/java/reactor/aeron/DefaultAeronDuplex.java b/reactor-aeron/src/main/java/reactor/aeron/DefaultAeronDuplex.java new file mode 100644 index 00000000..9c389233 --- /dev/null +++ b/reactor-aeron/src/main/java/reactor/aeron/DefaultAeronDuplex.java @@ -0,0 +1,48 @@ +package reactor.aeron; + +import reactor.core.publisher.Mono; + +public class DefaultAeronDuplex implements AeronDuplex { + + private final AeronInbound inbound; + private final AeronOutbound outbound; + + /** + * Constructor. + * + * @param inbound inbound + * @param outbound outbound + */ + public DefaultAeronDuplex(AeronInbound inbound, AeronOutbound outbound) { + this.inbound = inbound; + this.outbound = outbound; + inbound.onDispose(this); + outbound.onDispose(this); + } + + @Override + public AeronInbound inbound() { + return inbound; + } + + @Override + public AeronOutbound outbound() { + return outbound; + } + + @Override + public Mono onDispose() { + return Mono.whenDelayError(inbound.onDispose(), outbound.onDispose()); + } + + @Override + public void dispose() { + inbound.dispose(); + outbound.dispose(); + } + + @Override + public boolean isDisposed() { + return inbound.isDisposed() && outbound.isDisposed(); + } +} diff --git a/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronClient.java b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronClient.java index 4593bbc5..8608b24f 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronClient.java +++ b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronClient.java @@ -4,6 +4,7 @@ import java.util.function.UnaryOperator; import org.agrona.DirectBuffer; import org.reactivestreams.Publisher; +import reactor.aeron.AeronDuplex; import reactor.core.publisher.Mono; public final class AeronClient { @@ -29,7 +30,7 @@ public static AeronClient create(AeronResources resources) { * * @return mono handle of result */ - public Mono> connect() { + public Mono> connect() { return connect(s -> s); } @@ -39,7 +40,7 @@ public Mono> connect() { * @param op unary opearator for performing setup of options * @return mono handle of result */ - public Mono> connect(UnaryOperator op) { + public Mono> connect(UnaryOperator op) { return Mono.defer(() -> new AeronClientConnector(op.apply(options)).start()); } @@ -88,7 +89,7 @@ public AeronClient options(String address, int port, int controlPort) { * @return new {@code AeronClient} with handler */ public AeronClient handle( - Function, ? extends Publisher> handler) { + Function, ? extends Publisher> handler) { return new AeronClient(options.handler(handler)); } } diff --git a/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronClientConnector.java b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronClientConnector.java index ce8cc439..3f773765 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronClientConnector.java +++ b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronClientConnector.java @@ -7,10 +7,13 @@ import java.util.function.Supplier; import org.agrona.CloseHelper; import org.agrona.DirectBuffer; +import org.agrona.concurrent.Agent; import org.reactivestreams.Publisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import reactor.aeron.AeronDuplex; import reactor.aeron.AeronEventLoop; +import reactor.aeron.DefaultAeronDuplex; import reactor.aeron.DefaultFragmentMapper; import reactor.aeron.ImageAgent; import reactor.aeron.PublicationAgent; @@ -34,7 +37,7 @@ final class AeronClientConnector { private final AeronOptions options; private final AeronResources resources; - private final Function, ? extends Publisher> handler; + private final Function, ? extends Publisher> handler; private final DefaultFragmentMapper mapper = new DefaultFragmentMapper(); AeronClientConnector(AeronOptions options) { @@ -44,11 +47,11 @@ final class AeronClientConnector { } /** - * Creates and setting up {@link AeronConnection} object and everyting around it. + * Creates and setting up {@link AeronDuplex} object and everyting around it. * * @return mono result */ - Mono> start() { + Mono> start() { return Mono.defer( () -> { return tryConnect() @@ -66,8 +69,6 @@ Mono> start() { Integer.toHexString(sessionId), inboundChannel); - // setup cleanup hook to use it onwards - MonoProcessor disposeHook = MonoProcessor.create(); // setup image avaiable hook MonoProcessor inboundAvailable = MonoProcessor.create(); @@ -84,7 +85,6 @@ Mono> start() { logger.debug( "{}: client inbound became unavaliable", Integer.toHexString(sessionId)); - disposeHook.onComplete(); }) .doOnError( th -> { @@ -95,11 +95,28 @@ Mono> start() { // dispose outbound resource CloseHelper.quietClose(publication); }) - .flatMap( - subscription -> - inboundAvailable.flatMap( - image -> - newConnection(sessionId, image, publication, disposeHook))) + .flatMap(subscription -> inboundAvailable) + .map( + image -> { + PublicationAgent outbound = new PublicationAgent(publication); + ImageAgent inbound = + new ImageAgent<>(image, mapper, true); + return new DefaultAeronDuplex<>(inbound, outbound); + }) + .doOnSuccess( + connection -> { + if (handler == null) { + logger.warn( + "{}: connection handler function is not specified", + Integer.toHexString(sessionId)); + } else if (!connection.isDisposed()) { + handler.apply(connection).subscribe(connection.disposeSubscriber()); + } + + AeronEventLoop eventLoop = resources.nextEventLoop(); + eventLoop.register((Agent) connection.inbound()); + eventLoop.register((Agent) connection.outbound()); + }) .doOnSuccess( connection -> logger.debug( @@ -110,23 +127,6 @@ Mono> start() { }); } - private Mono> newConnection( - int sessionId, Image image, Publication publication, MonoProcessor disposeHook) { - PublicationAgent publicationAgent = new PublicationAgent(publication); - ImageAgent imageAgent = new ImageAgent<>(image, mapper, true); - DuplexAeronConnection connection = - new DuplexAeronConnection<>(sessionId, imageAgent, publicationAgent, disposeHook); - return connection - .start(handler) - .doOnSuccess( - c -> { - AeronEventLoop eventLoop = resources.nextEventLoop(); - eventLoop.register(imageAgent); - eventLoop.register(publicationAgent); - }) - .doOnError(ex -> connection.dispose()); - } - private Mono tryConnect() { return Mono.defer( () -> { diff --git a/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronOptions.java b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronOptions.java index 17aa4295..7100d4d9 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronOptions.java +++ b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronOptions.java @@ -6,6 +6,7 @@ import java.util.function.Supplier; import org.agrona.DirectBuffer; import org.reactivestreams.Publisher; +import reactor.aeron.AeronDuplex; /** * Immutable wrapper around options for full-duplex aeron connection between client and @@ -15,7 +16,7 @@ public final class AeronOptions { private AeronResources resources; - private Function, ? extends Publisher> handler; + private Function, ? extends Publisher> handler; private AeronChannelUriString inboundUri = new AeronChannelUriString(); private AeronChannelUriString outboundUri = new AeronChannelUriString(); private Duration connectTimeout = Duration.ofSeconds(5); @@ -46,12 +47,12 @@ public AeronOptions resources(AeronResources resources) { return set(s -> s.resources = resources); } - public Function, ? extends Publisher> handler() { + public Function, ? extends Publisher> handler() { return handler; } public AeronOptions handler( - Function, ? extends Publisher> handler) { + Function, ? extends Publisher> handler) { return set(s -> s.handler = handler); } diff --git a/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronServer.java b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronServer.java index c522efde..9ac7d873 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronServer.java +++ b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronServer.java @@ -4,6 +4,7 @@ import java.util.function.UnaryOperator; import org.agrona.DirectBuffer; import org.reactivestreams.Publisher; +import reactor.aeron.AeronDuplex; import reactor.aeron.OnDisposable; import reactor.core.publisher.Mono; @@ -92,7 +93,7 @@ public AeronServer options(String address, int port, int controlPort) { * @return new {@code AeronServer} with handler */ public AeronServer handle( - Function, ? extends Publisher> handler) { + Function, ? extends Publisher> handler) { return new AeronServer(options.handler(handler)); } } diff --git a/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronServerHandler.java b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronServerHandler.java index 022405ee..7c9fbbbc 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronServerHandler.java +++ b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronServerHandler.java @@ -1,19 +1,19 @@ package reactor.aeron.mdc; import io.aeron.Image; -import io.aeron.Publication; import io.aeron.Subscription; -import java.util.ArrayList; -import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import org.agrona.CloseHelper; import org.agrona.DirectBuffer; +import org.agrona.concurrent.Agent; import org.reactivestreams.Publisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import reactor.aeron.AeronDuplex; import reactor.aeron.AeronEventLoop; +import reactor.aeron.DefaultAeronDuplex; import reactor.aeron.DefaultFragmentMapper; import reactor.aeron.ImageAgent; import reactor.aeron.OnDisposable; @@ -41,13 +41,13 @@ final class AeronServerHandler implements OnDisposable { private final AeronOptions options; private final AeronResources resources; - private final Function, ? extends Publisher> handler; + private final Function, ? extends Publisher> + handler; private final DefaultFragmentMapper mapper = new DefaultFragmentMapper(); - // private volatile MessageSubscription acceptorSubscription; // server acceptor subscription private volatile Subscription acceptorSubscription; // server acceptor subscription - private final Map> disposeHooks = new ConcurrentHashMap<>(); + private final Map connections = new ConcurrentHashMap<>(); private final MonoProcessor dispose = MonoProcessor.create(); private final MonoProcessor onDispose = MonoProcessor.create(); @@ -88,10 +88,10 @@ Mono start() { } /** - * Setting up new {@link AeronConnection} identified by {@link Image#sessionId()}. Specifically - * creates Multi Destination Cast (MDC) message publication (aeron {@link io.aeron.Publication} - * underneath) with control-endpoint, control-mode and XOR-ed image sessionId. Essentially creates - * server-side-individual-MDC. + * Setting up new {@link AeronDuplex} identified by {@link Image#sessionId()}. + * Specifically creates Multi Destination Cast (MDC) message publication (aeron {@link + * io.aeron.Publication} underneath) with control-endpoint, control-mode and XOR-ed image + * sessionId. Essentially creates server-side-individual-MDC. * * @param image source image */ @@ -106,7 +106,28 @@ private void onImageAvailable(Image image) { resources .publication(outboundChannel, STREAM_ID) - .flatMap(publication -> newConnection(sessionId, publication, image)) + .map( + publication -> { + PublicationAgent outbound = new PublicationAgent(publication); + ImageAgent inbound = new ImageAgent<>(image, mapper, false); + return new DefaultAeronDuplex<>(inbound, outbound); + }) + .doOnSuccess( + connection -> { + if (handler == null) { + logger.warn( + "{}: connection handler function is not specified", + Integer.toHexString(sessionId)); + } else if (!connection.isDisposed()) { + handler.apply(connection).subscribe(connection.disposeSubscriber()); + } + + AeronEventLoop eventLoop = resources.nextEventLoop(); + eventLoop.register((Agent) connection.inbound()); + eventLoop.register((Agent) connection.outbound()); + connections.put(sessionId, connection); + connection.onDispose(() -> connections.remove(sessionId)); + }) .doOnSuccess( connection -> logger.debug( @@ -122,46 +143,13 @@ private void onImageAvailable(Image image) { ex.toString())); } - private Mono newConnection( - int sessionId, Publication publication, Image image) { - // setup cleanup hook to use it onwards - MonoProcessor disposeHook = MonoProcessor.create(); - - PublicationAgent publicationAgent = new PublicationAgent(publication); - ImageAgent imageAgent = new ImageAgent<>(image, mapper, false); - - DuplexAeronConnection connection = - new DuplexAeronConnection<>(sessionId, imageAgent, publicationAgent, disposeHook); - - disposeHooks.put(sessionId, disposeHook); - - return connection - .start(handler) - .doOnSuccess( - c -> { - AeronEventLoop eventLoop = resources.nextEventLoop(); - eventLoop.register(imageAgent); - eventLoop.register(publicationAgent); - }) - .doOnError( - ex -> { - connection.dispose(); - disposeHooks.remove(sessionId); - }); - } - /** - * Disposes {@link AeronConnection} corresponding to {@link Image#sessionId()}. + * Disposes {@link AeronDuplex} corresponding to {@link Image#sessionId()}. * * @param image source image */ private void onImageUnavailable(Image image) { - int sessionId = image.sessionId(); - MonoProcessor disposeHook = disposeHooks.remove(sessionId); - if (disposeHook != null) { - logger.debug("{}: server inbound became unavailable", Integer.toHexString(sessionId)); - disposeHook.onComplete(); - } + logger.debug("{}: server inbound became unavailable", Integer.toHexString(image.sessionId())); } @Override @@ -180,18 +168,13 @@ public Mono onDispose() { } private Mono doDispose() { - return Mono.defer( + return Mono.fromRunnable( () -> { logger.debug("Disposing {}", this); - List> monos = new ArrayList<>(); - // dispose server acceptor subscription - monos.add(Mono.fromRunnable(() -> CloseHelper.quietClose(acceptorSubscription))); - + CloseHelper.quietClose(acceptorSubscription); // dispose all existing connections - disposeHooks.values().stream().peek(MonoProcessor::onComplete).forEach(monos::add); - - return Mono.whenDelayError(monos).doFinally(s -> disposeHooks.clear()); + connections.forEach((sessionId, connection) -> connection.dispose()); }); } diff --git a/reactor-aeron/src/main/java/reactor/aeron/mdc/DuplexAeronConnection.java b/reactor-aeron/src/main/java/reactor/aeron/mdc/DuplexAeronConnection.java deleted file mode 100644 index 138fd712..00000000 --- a/reactor-aeron/src/main/java/reactor/aeron/mdc/DuplexAeronConnection.java +++ /dev/null @@ -1,114 +0,0 @@ -package reactor.aeron.mdc; - -import java.util.function.Function; -import org.reactivestreams.Publisher; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.aeron.AeronInbound; -import reactor.aeron.AeronOutbound; -import reactor.core.publisher.Mono; -import reactor.core.publisher.MonoProcessor; - -/** - * Full-duplex aeron connection. Bound to certain {@code sessionId}. Implements {@link - * reactor.aeron.OnDisposable} for convenient resource cleanup. - */ -final class DuplexAeronConnection implements AeronConnection { - - private final Logger logger = LoggerFactory.getLogger(DuplexAeronConnection.class); - - private final int sessionId; - - private final AeronInbound inbound; - private final AeronOutbound outbound; - - private final MonoProcessor dispose = MonoProcessor.create(); - private final MonoProcessor onDispose = MonoProcessor.create(); - - /** - * Constructor. - * - * @param sessionId session id - * @param inbound inbound - * @param outbound outbound - * @param disposeHook shutdown hook - */ - DuplexAeronConnection( - int sessionId, - AeronInbound inbound, - AeronOutbound outbound, - MonoProcessor disposeHook) { - - this.sessionId = sessionId; - this.inbound = inbound; - this.outbound = outbound; - - dispose - .or(disposeHook) - .then(doDispose()) - .doFinally(s -> logger.debug("{}: connection disposed", Integer.toHexString(sessionId))) - .doFinally(s -> onDispose.onComplete()) - .subscribe( - null, - th -> logger.warn("{} failed on doDispose(): {}", this, th.toString()), - () -> logger.debug("Disposed {}", this)); - } - - /** - * Setting up this connection by applying user provided application level handler. - * - * @param handler handler with application level code - */ - Mono> start( - Function, ? extends Publisher> handler) { - return Mono.fromRunnable(() -> start0(handler)).thenReturn(this); - } - - private void start0(Function, ? extends Publisher> handler) { - if (handler == null) { - logger.warn( - "{}: connection handler function is not specified", Integer.toHexString(sessionId)); - } else if (!isDisposed()) { - handler.apply(this).subscribe(disposeSubscriber()); - } - } - - @Override - public AeronInbound inbound() { - return inbound; - } - - @Override - public AeronOutbound outbound() { - return outbound; - } - - @Override - public void dispose() { - dispose.onComplete(); - } - - @Override - public boolean isDisposed() { - return onDispose.isDisposed(); - } - - @Override - public Mono onDispose() { - return onDispose; - } - - private Mono doDispose() { - return Mono.defer( - () -> { - logger.debug("Disposing {}", this); - return Mono.whenDelayError( - Mono.fromRunnable(inbound::dispose), Mono.fromRunnable(outbound::dispose)); - }); - } - - @Override - public String toString() { - return "DefaultAeronConnection" + Integer.toHexString(sessionId); - } -} diff --git a/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronClientTest.java b/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronClientTest.java index f1b78ce5..dd871518 100644 --- a/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronClientTest.java +++ b/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronClientTest.java @@ -20,6 +20,7 @@ import org.reactivestreams.Subscription; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import reactor.aeron.AeronDuplex; import reactor.aeron.BaseAeronTest; import reactor.aeron.OnDisposable; import reactor.aeron.SocketUtils; @@ -65,7 +66,7 @@ public void testClientReceivesDataFromServer() { .sendString(Flux.fromStream(Stream.of("hello1", "2", "3")).log("server")) .then(connection.onDispose())); - AeronConnection connection = createConnection(); + AeronDuplex connection = createConnection(); StepVerifier.create(connection.inbound().receive().map(asString()).log("client")) .expectNext("hello1", "2", "3") .expectNoEvent(Duration.ofMillis(10)) @@ -86,7 +87,7 @@ public void testClientReceivesLongDataFromServer() { .sendString(Flux.fromStream(Stream.of(str, str, str)).log("server")) .then(connection.onDispose())); - AeronConnection connection = createConnection(); + AeronDuplex connection = createConnection(); StepVerifier.create(connection.inbound().receive().map(asString()).log("client")) .expectNext(str, str, str) @@ -104,8 +105,8 @@ public void testTwoClientsReceiveDataFromServer() { .sendString(Flux.fromStream(Stream.of("1", "2", "3")).log("server")) .then(connection.onDispose())); - AeronConnection connection1 = createConnection(); - AeronConnection connection2 = createConnection(); + AeronDuplex connection1 = createConnection(); + AeronDuplex connection2 = createConnection(); StepVerifier.create(connection1.inbound().receive().map(asString()).log("client-1")) .expectNext("1", "2", "3") @@ -128,7 +129,7 @@ public void testClientsReceiveDataFromServer200000() { createServer( connection -> connection.outbound().sendString(payloads).then(connection.onDispose())); - AeronConnection connection1 = createConnection(); + AeronDuplex connection1 = createConnection(); StepVerifier.create(connection1.inbound().receive().map(asString())) .expectNextCount(count) @@ -147,7 +148,7 @@ public void testRequestResponse200000() { .send(connection.inbound().receive().cast(DirectBuffer.class)) .then(connection.onDispose())); - AeronConnection connection1 = createConnection(); + AeronDuplex connection1 = createConnection(); connection1.outbound().sendString(Flux.range(0, count).map(String::valueOf)).then().subscribe(); @@ -170,7 +171,7 @@ public void testRequestResponse200000MonoJust() { .flatMap(byteBuffer -> connection.outbound().send(Mono.just(byteBuffer)).then()) .then(connection.onDispose())); - AeronConnection connection1 = createConnection(); + AeronDuplex connection1 = createConnection(); Flux.range(0, count) .flatMap( @@ -246,13 +247,13 @@ public void testTwoClientsRequestResponseWithDelaySubscriptionToInbound200000() .flatMap(byteBuffer -> connection.outbound().send(Mono.just(byteBuffer)).then()) .then(connection.onDispose())); - AeronConnection connection1 = createConnection(); + AeronDuplex connection1 = createConnection(); Flux.range(0, count) .flatMap(i -> connection1.outbound().sendString(Mono.just("client-1 send:" + i)).then()) .then() .subscribe(null, ex -> logger.error("client-1 didn't send all, cause: ", ex)); - AeronConnection connection2 = createConnection(); + AeronDuplex connection2 = createConnection(); Flux.range(0, count) .flatMap(i -> connection2.outbound().sendString(Mono.just("client-2 send:" + i)).then()) .then() @@ -382,7 +383,7 @@ public void testCustomClientInboundSubscriber() { return connection.onDispose(); }); - AeronConnection connection1 = createConnection(); + AeronDuplex connection1 = createConnection(); BaseSubscriber subscriber = new BaseSubscriber() { @@ -445,7 +446,7 @@ public void testCustomClientInboundSubscriberWithLongMessage() { return connection.onDispose(); }); - AeronConnection connection1 = createConnection(); + AeronDuplex connection1 = createConnection(); BaseSubscriber subscriber = new BaseSubscriber() { @@ -482,12 +483,12 @@ protected void hookOnSubscribe(Subscription subscription) { .verify(timeout); } - private AeronConnection createConnection() { + private AeronDuplex createConnection() { return createConnection(null /*handler*/); } - private AeronConnection createConnection( - Function, ? extends Publisher> handler) { + private AeronDuplex createConnection( + Function, ? extends Publisher> handler) { return AeronClient.create(resources) .options("localhost", serverPort, serverControlPort) .handle(handler) @@ -496,7 +497,7 @@ private AeronConnection createConnection( } private OnDisposable createServer( - Function, ? extends Publisher> handler) { + Function, ? extends Publisher> handler) { return AeronServer.create(resources) .options("localhost", serverPort, serverControlPort) .handle(handler) diff --git a/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronConnectionTest.java b/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronConnectionTest.java index 14d01688..aa0987f1 100644 --- a/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronConnectionTest.java +++ b/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronConnectionTest.java @@ -16,6 +16,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.reactivestreams.Publisher; +import reactor.aeron.AeronDuplex; import reactor.aeron.BaseAeronTest; import reactor.aeron.OnDisposable; import reactor.aeron.SocketUtils; @@ -70,7 +71,7 @@ public void testServerDisconnectsSessionAndClientHandleUnavailableImage() return connection.onDispose(); }); - AeronConnection connection = createConnection(); + AeronDuplex connection = createConnection(); connection .outbound() .sendString( @@ -105,10 +106,10 @@ public void testClientClosesSessionAndServerHandleUnavailableImage() throws Exce ReplayProcessor processor = ReplayProcessor.create(); - AeronConnection connection = createConnection(); + AeronDuplex connection = createConnection(); CountDownLatch latch = new CountDownLatch(1); - connection.onDispose().doOnSuccess(aVoid -> latch.countDown()).subscribe(); + connection.onDispose(latch::countDown); connection.inbound().receive().map(asString()).log("client").subscribe(processor); @@ -127,9 +128,9 @@ public void testServerDisconnects() throws Exception { CountDownLatch clientConnectionLatch = new CountDownLatch(1); - AeronConnection client = createConnection(); + AeronDuplex client = createConnection(); - client.onDispose().doFinally(s -> clientConnectionLatch.countDown()).subscribe(); + client.onDispose(clientConnectionLatch::countDown); Mono // .delay(Duration.ofSeconds(1)) @@ -146,7 +147,7 @@ public void testServerDisconnectsAndClientCleanups() throws Exception { CountDownLatch clientConnectionLatch = new CountDownLatch(2); - AeronConnection client = createConnection(); + AeronDuplex client = createConnection(); client .inbound() // @@ -180,7 +181,7 @@ public void testClientDisconnects() throws Exception { createServer(c -> c.onDispose().doFinally(s -> serverConnectionLatch.countDown())); - AeronConnection client = createConnection(); + AeronDuplex client = createConnection(); Mono // .delay(Duration.ofSeconds(1)) @@ -214,7 +215,7 @@ public void testClientDisconnectsAndServerCleanups() throws Exception { return c.onDispose(); }); - AeronConnection client = createConnection(); + AeronDuplex client = createConnection(); Mono // .delay(Duration.ofSeconds(1)) @@ -225,7 +226,7 @@ public void testClientDisconnectsAndServerCleanups() throws Exception { assertTrue(await, "serverConnectionLatch: " + serverConnectionLatch.getCount()); } - private AeronConnection createConnection() { + private AeronDuplex createConnection() { return AeronClient.create(resources) .options("localhost", serverPort, serverControlPort) .connect() @@ -233,7 +234,7 @@ private AeronConnection createConnection() { } private OnDisposable createServer( - Function, ? extends Publisher> handler) { + Function, ? extends Publisher> handler) { return AeronServer.create(resources) .options("localhost", serverPort, serverControlPort) .handle(handler) diff --git a/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronServerTest.java b/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronServerTest.java index 47658689..e450b7ec 100644 --- a/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronServerTest.java +++ b/reactor-aeron/src/test/java/reactor/aeron/mdc/AeronServerTest.java @@ -11,6 +11,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.reactivestreams.Publisher; +import reactor.aeron.AeronDuplex; import reactor.aeron.BaseAeronTest; import reactor.aeron.OnDisposable; import reactor.aeron.SocketUtils; @@ -87,7 +88,7 @@ public void testServerDisconnectsClientsUponShutdown() throws InterruptedExcepti Assertions.assertTrue(new ThreadWatcher().awaitTerminated(5000, "single-", "parallel-")); } - private AeronConnection createConnection() { + private AeronDuplex createConnection() { return AeronClient.create(resources) .options("localhost", serverPort, serverControlPort) .connect() @@ -95,7 +96,7 @@ private AeronConnection createConnection() { } private OnDisposable createServer( - Function, ? extends Publisher> handler) { + Function, ? extends Publisher> handler) { return AeronServer.create(resources) .options("localhost", serverPort, serverControlPort) .handle(handler) From 498756c9419a9ad08dadea084d198e4270f0c317 Mon Sep 17 00:00:00 2001 From: segabriel Date: Sat, 28 Dec 2019 21:36:46 +0200 Subject: [PATCH 16/16] Remove redundant removing of Aeron dir --- .../java/reactor/aeron/mdc/AeronResources.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronResources.java b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronResources.java index e76c90c9..896ee9f2 100644 --- a/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronResources.java +++ b/reactor-aeron/src/main/java/reactor/aeron/mdc/AeronResources.java @@ -8,8 +8,6 @@ import io.aeron.Publication; import io.aeron.Subscription; import io.aeron.driver.MediaDriver; -import java.io.File; -import java.nio.file.Paths; import java.time.Duration; import java.util.Optional; import java.util.UUID; @@ -312,10 +310,6 @@ private Mono doStart() { eventLoopGroup = new AeronEventLoopGroup("reactor-aeron", numOfWorkers, workerIdleStrategySupplier); - Runtime.getRuntime() - .addShutdownHook( - new Thread(() -> deleteAeronDirectory(mediaDriver.aeronDirectoryName()))); - logger.debug( "{} has initialized embedded media driver, aeron directory: {}", this, @@ -536,14 +530,6 @@ private Mono doDispose() { }); } - private void deleteAeronDirectory(String aeronDirectoryName) { - File aeronDirectory = Paths.get(aeronDirectoryName).toFile(); - if (aeronDirectory.exists()) { - IoUtil.delete(aeronDirectory, true); - logger.debug("{} deleted aeron directory {}", this, aeronDirectoryName); - } - } - @Override public String toString() { return "AeronResources" + Integer.toHexString(System.identityHashCode(this));