() {
142 | @Override
143 | protected void encode(ChannelHandlerContext ctx, Integer msg, ByteBuf out) {
144 | out.writeLong(System.nanoTime());
145 | out.writeBytes(PAYLOAD.slice());
146 | }
147 | });
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/reactor-aeron-benchmarks/src/main/java/reactor/aeron/netty/ReactorNettyClientTps.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron.netty;
2 |
3 | import io.netty.buffer.ByteBuf;
4 | import io.netty.buffer.ByteBufAllocator;
5 | import io.netty.channel.Channel;
6 | import io.netty.channel.ChannelOption;
7 | import io.netty.channel.ChannelPipeline;
8 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
9 | import io.netty.handler.codec.LengthFieldPrepender;
10 | import java.time.Duration;
11 | import java.util.Random;
12 | import reactor.aeron.Configurations;
13 | import reactor.core.publisher.Flux;
14 | import reactor.netty.channel.BootstrapHandlers;
15 | import reactor.netty.resources.ConnectionProvider;
16 | import reactor.netty.resources.LoopResources;
17 | import reactor.netty.tcp.TcpClient;
18 |
19 | public class ReactorNettyClientTps {
20 |
21 | private static final ByteBuf PAYLOAD =
22 | ByteBufAllocator.DEFAULT.buffer(Configurations.MESSAGE_LENGTH);
23 |
24 | static {
25 | Random random = new Random(System.nanoTime());
26 | byte[] bytes = new byte[Configurations.MESSAGE_LENGTH];
27 | random.nextBytes(bytes);
28 | PAYLOAD.writeBytes(bytes);
29 | }
30 |
31 | /**
32 | * Main runner.
33 | *
34 | * @param args program arguments.
35 | */
36 | public static void main(String[] args) {
37 | System.out.println(
38 | "message size: "
39 | + Configurations.MESSAGE_LENGTH
40 | + ", number of messages: "
41 | + Configurations.NUMBER_OF_MESSAGES
42 | + ", address: "
43 | + Configurations.MDC_ADDRESS
44 | + ", port: "
45 | + Configurations.MDC_PORT);
46 |
47 | LoopResources loopResources = LoopResources.create("reactor-netty");
48 |
49 | TcpClient.create(ConnectionProvider.newConnection())
50 | .runOn(loopResources)
51 | .host(Configurations.MDC_ADDRESS)
52 | .port(Configurations.MDC_PORT)
53 | .option(ChannelOption.TCP_NODELAY, true)
54 | .option(ChannelOption.SO_KEEPALIVE, true)
55 | .option(ChannelOption.SO_REUSEADDR, true)
56 | .doOnConnected(System.out::println)
57 | .bootstrap(
58 | b ->
59 | BootstrapHandlers.updateConfiguration(
60 | b,
61 | "channel",
62 | (connectionObserver, channel) -> {
63 | setupChannel(channel);
64 | }))
65 | .handle(
66 | (inbound, outbound) -> {
67 | int msgNum = (int) Configurations.NUMBER_OF_MESSAGES;
68 | System.out.println("streaming " + msgNum + " messages ...");
69 |
70 | return outbound.send(Flux.range(0, msgNum).map(i -> PAYLOAD.retainedSlice())).then();
71 | })
72 | .connectNow()
73 | .onDispose()
74 | .doFinally(s -> loopResources.dispose())
75 | .block(Duration.ofSeconds(120));
76 | }
77 |
78 | private static void setupChannel(Channel channel) {
79 | final int maxFrameLength = 1024 * 1024;
80 | final int lengthFieldLength = 2;
81 |
82 | ChannelPipeline pipeline = channel.pipeline();
83 | pipeline.addLast(new LengthFieldPrepender(lengthFieldLength));
84 | pipeline.addLast(
85 | new LengthFieldBasedFrameDecoder(
86 | maxFrameLength, 0, lengthFieldLength, 0, lengthFieldLength));
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/reactor-aeron-benchmarks/src/main/java/reactor/aeron/netty/ReactorNettyServerPong.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron.netty;
2 |
3 | import io.netty.channel.Channel;
4 | import io.netty.channel.ChannelOption;
5 | import io.netty.channel.ChannelPipeline;
6 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
7 | import io.netty.handler.codec.LengthFieldPrepender;
8 | import reactor.aeron.Configurations;
9 | import reactor.netty.NettyPipeline.SendOptions;
10 | import reactor.netty.channel.BootstrapHandlers;
11 | import reactor.netty.resources.LoopResources;
12 | import reactor.netty.tcp.TcpServer;
13 |
14 | public class ReactorNettyServerPong {
15 |
16 | /**
17 | * Main runner.
18 | *
19 | * @param args program arguments.
20 | */
21 | public static void main(String[] args) {
22 | System.out.println(
23 | "message size: "
24 | + Configurations.MESSAGE_LENGTH
25 | + ", number of messages: "
26 | + Configurations.NUMBER_OF_MESSAGES
27 | + ", address: "
28 | + Configurations.MDC_ADDRESS
29 | + ", port: "
30 | + Configurations.MDC_PORT);
31 |
32 | LoopResources loopResources = LoopResources.create("reactor-netty");
33 |
34 | TcpServer.create()
35 | .runOn(loopResources)
36 | .host(Configurations.MDC_ADDRESS)
37 | .port(Configurations.MDC_PORT)
38 | .option(ChannelOption.TCP_NODELAY, true)
39 | .option(ChannelOption.SO_KEEPALIVE, true)
40 | .option(ChannelOption.SO_REUSEADDR, true)
41 | .doOnConnection(System.out::println)
42 | .bootstrap(
43 | b ->
44 | BootstrapHandlers.updateConfiguration(
45 | b,
46 | "channel",
47 | (connectionObserver, channel) -> {
48 | setupChannel(channel);
49 | }))
50 | .handle(
51 | (inbound, outbound) ->
52 | outbound.options(SendOptions::flushOnEach).send(inbound.receive().retain()))
53 | .bind()
54 | .doOnSuccess(
55 | server ->
56 | System.out.println("server has been started successfully on " + server.address()))
57 | .block()
58 | .onDispose(loopResources)
59 | .onDispose()
60 | .block();
61 | }
62 |
63 | private static void setupChannel(Channel channel) {
64 | final int maxFrameLength = 1024 * 1024;
65 | final int lengthFieldLength = 2;
66 |
67 | ChannelPipeline pipeline = channel.pipeline();
68 | pipeline.addLast(new LengthFieldPrepender(lengthFieldLength));
69 | pipeline.addLast(
70 | new LengthFieldBasedFrameDecoder(
71 | maxFrameLength, 0, lengthFieldLength, 0, lengthFieldLength));
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/reactor-aeron-benchmarks/src/main/java/reactor/aeron/netty/ReactorNettyServerTps.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron.netty;
2 |
3 | import io.netty.channel.Channel;
4 | import io.netty.channel.ChannelOption;
5 | import io.netty.channel.ChannelPipeline;
6 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
7 | import io.netty.handler.codec.LengthFieldPrepender;
8 | import reactor.aeron.Configurations;
9 | import reactor.aeron.RateReporter;
10 | import reactor.netty.channel.BootstrapHandlers;
11 | import reactor.netty.resources.LoopResources;
12 | import reactor.netty.tcp.TcpServer;
13 |
14 | public class ReactorNettyServerTps {
15 |
16 | /**
17 | * Main runner.
18 | *
19 | * @param args program arguments.
20 | * @throws InterruptedException on timeout.
21 | */
22 | public static void main(String[] args) throws InterruptedException {
23 | System.out.println(
24 | "message size: "
25 | + Configurations.MESSAGE_LENGTH
26 | + ", number of messages: "
27 | + Configurations.NUMBER_OF_MESSAGES
28 | + ", address: "
29 | + Configurations.MDC_ADDRESS
30 | + ", port: "
31 | + Configurations.MDC_PORT);
32 |
33 | LoopResources loopResources = LoopResources.create("reactor-netty");
34 |
35 | RateReporter reporter = new RateReporter();
36 |
37 | TcpServer.create()
38 | .runOn(loopResources)
39 | .host(Configurations.MDC_ADDRESS)
40 | .port(Configurations.MDC_PORT)
41 | .option(ChannelOption.TCP_NODELAY, true)
42 | .option(ChannelOption.SO_KEEPALIVE, true)
43 | .option(ChannelOption.SO_REUSEADDR, true)
44 | .doOnConnection(System.out::println)
45 | .bootstrap(
46 | b ->
47 | BootstrapHandlers.updateConfiguration(
48 | b,
49 | "channel",
50 | (connectionObserver, channel) -> {
51 | setupChannel(channel);
52 | }))
53 | .handle(
54 | (inbound, outbound) ->
55 | inbound
56 | .receive()
57 | .retain()
58 | .doOnNext(
59 | buffer -> {
60 | reporter.onMessage(1, buffer.readableBytes());
61 | buffer.release();
62 | })
63 | .then())
64 | .bind()
65 | .doOnSuccess(
66 | server ->
67 | System.out.println("server has been started successfully on " + server.address()))
68 | .block()
69 | .onDispose(loopResources)
70 | .onDispose(reporter)
71 | .onDispose()
72 | .block();
73 | }
74 |
75 | private static void setupChannel(Channel channel) {
76 | final int maxFrameLength = 1024 * 1024;
77 | final int lengthFieldLength = 2;
78 |
79 | ChannelPipeline pipeline = channel.pipeline();
80 | pipeline.addLast(new LengthFieldPrepender(lengthFieldLength));
81 | pipeline.addLast(
82 | new LengthFieldBasedFrameDecoder(
83 | maxFrameLength, 0, lengthFieldLength, 0, lengthFieldLength));
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/reactor-aeron-benchmarks/src/main/java/reactor/aeron/pure/ClientThroughput.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron.pure;
2 |
3 | import io.aeron.Aeron;
4 | import io.aeron.ChannelUriStringBuilder;
5 | import io.aeron.CommonContext;
6 | import io.aeron.Image;
7 | import io.aeron.Publication;
8 | import io.aeron.Subscription;
9 | import io.aeron.driver.Configuration;
10 | import io.aeron.driver.MediaDriver;
11 | import java.util.concurrent.CountDownLatch;
12 | import org.agrona.BitUtil;
13 | import org.agrona.BufferUtil;
14 | import org.agrona.CloseHelper;
15 | import org.agrona.concurrent.IdleStrategy;
16 | import org.agrona.concurrent.UnsafeBuffer;
17 | import org.agrona.console.ContinueBarrier;
18 | import reactor.aeron.Configurations;
19 |
20 | public class ClientThroughput {
21 | private static final int STREAM_ID = Configurations.MDC_STREAM_ID;
22 | private static final int PORT = Configurations.MDC_PORT;
23 | private static final int CONTROL_PORT = Configurations.MDC_CONTROL_PORT;
24 | private static final int SESSION_ID = Configurations.MDC_SESSION_ID;
25 | private static final String OUTBOUND_CHANNEL =
26 | new ChannelUriStringBuilder()
27 | .endpoint(Configurations.MDC_ADDRESS + ':' + PORT)
28 | .sessionId(SESSION_ID)
29 | .media("udp")
30 | .reliable(Boolean.TRUE)
31 | .build();
32 | private static final String INBOUND_CHANNEL =
33 | new ChannelUriStringBuilder()
34 | .controlEndpoint(Configurations.MDC_ADDRESS + ':' + CONTROL_PORT)
35 | .controlMode(CommonContext.MDC_CONTROL_MODE_DYNAMIC)
36 | .sessionId(SESSION_ID ^ Integer.MAX_VALUE)
37 | .reliable(Boolean.TRUE)
38 | .media("udp")
39 | .build();
40 |
41 | private static final long NUMBER_OF_MESSAGES = Configurations.NUMBER_OF_MESSAGES;
42 | private static final int MESSAGE_LENGTH = Configurations.MESSAGE_LENGTH;
43 | private static final boolean EMBEDDED_MEDIA_DRIVER = Configurations.EMBEDDED_MEDIA_DRIVER;
44 | private static final boolean EXCLUSIVE_PUBLICATIONS = Configurations.EXCLUSIVE_PUBLICATIONS;
45 |
46 | private static final UnsafeBuffer OFFER_BUFFER =
47 | new UnsafeBuffer(BufferUtil.allocateDirectAligned(MESSAGE_LENGTH, BitUtil.CACHE_LINE_LENGTH));
48 | private static final CountDownLatch LATCH = new CountDownLatch(1);
49 | private static final IdleStrategy POLLING_IDLE_STRATEGY = Configurations.idleStrategy();
50 |
51 | /**
52 | * Main runner.
53 | *
54 | * @param args program arguments.
55 | */
56 | public static void main(final String[] args) throws Exception {
57 | final MediaDriver driver = EMBEDDED_MEDIA_DRIVER ? MediaDriver.launchEmbedded() : null;
58 | final Aeron.Context ctx =
59 | new Aeron.Context().availableImageHandler(ClientThroughput::availablePongImageHandler);
60 |
61 | if (EMBEDDED_MEDIA_DRIVER) {
62 | ctx.aeronDirectoryName(driver.aeronDirectoryName());
63 | }
64 |
65 | System.out.println("MediaDriver THREADING_MODE: " + Configuration.THREADING_MODE_DEFAULT);
66 | System.out.println("Publishing Ping at " + OUTBOUND_CHANNEL + " on stream Id " + STREAM_ID);
67 | System.out.println("Subscribing Pong at " + INBOUND_CHANNEL + " on stream Id " + STREAM_ID);
68 | System.out.println("Message length of " + MESSAGE_LENGTH + " bytes");
69 | System.out.println("Using exclusive publications " + EXCLUSIVE_PUBLICATIONS);
70 | System.out.println(
71 | "Using poling idle strategy "
72 | + POLLING_IDLE_STRATEGY.getClass()
73 | + "("
74 | + Configurations.IDLE_STRATEGY
75 | + ")");
76 |
77 | try (Aeron aeron = Aeron.connect(ctx);
78 | Subscription subscription = aeron.addSubscription(INBOUND_CHANNEL, STREAM_ID);
79 | Publication publication =
80 | EXCLUSIVE_PUBLICATIONS
81 | ? aeron.addExclusivePublication(OUTBOUND_CHANNEL, STREAM_ID)
82 | : aeron.addPublication(OUTBOUND_CHANNEL, STREAM_ID)) {
83 | System.out.println("Waiting for new image from Pong...");
84 | LATCH.await();
85 |
86 | while (!subscription.isConnected()) {
87 | Thread.yield();
88 | }
89 |
90 | Thread.sleep(100);
91 | final ContinueBarrier barrier = new ContinueBarrier("Execute again?");
92 |
93 | do {
94 | System.out.println("Pinging " + NUMBER_OF_MESSAGES + " messages");
95 |
96 | for (long i = 0; i < Long.MAX_VALUE; ) {
97 | OFFER_BUFFER.putLong(0, System.nanoTime());
98 | final long offeredPosition = publication.offer(OFFER_BUFFER, 0, MESSAGE_LENGTH);
99 | if (offeredPosition > 0) {
100 | i++;
101 | continue;
102 | }
103 | POLLING_IDLE_STRATEGY.idle();
104 | }
105 |
106 | System.out.println("Histogram of RTT latencies in microseconds.");
107 | } while (barrier.await());
108 | }
109 |
110 | CloseHelper.quietClose(driver);
111 | }
112 |
113 | private static void availablePongImageHandler(final Image image) {
114 | final Subscription subscription = image.subscription();
115 | System.out.format(
116 | "Available image: channel=%s streamId=%d session=%d%n",
117 | subscription.channel(), subscription.streamId(), image.sessionId());
118 |
119 | if (STREAM_ID == subscription.streamId() && INBOUND_CHANNEL.equals(subscription.channel())) {
120 | LATCH.countDown();
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/reactor-aeron-benchmarks/src/main/java/reactor/aeron/pure/MdcPong.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron.pure;
2 |
3 | import io.aeron.Aeron;
4 | import io.aeron.ChannelUriStringBuilder;
5 | import io.aeron.FragmentAssembler;
6 | import io.aeron.Publication;
7 | import io.aeron.Subscription;
8 | import io.aeron.driver.Configuration;
9 | import io.aeron.driver.MediaDriver;
10 | import java.util.concurrent.atomic.AtomicBoolean;
11 | import org.agrona.CloseHelper;
12 | import org.agrona.DirectBuffer;
13 | import org.agrona.concurrent.IdleStrategy;
14 | import org.agrona.concurrent.SigInt;
15 | import reactor.aeron.Configurations;
16 |
17 | /**
18 | * Pong component of Ping-Pong.
19 | *
20 | * Echoes back messages from {@link MdcPing}.
21 | *
22 | * @see MdcPong
23 | */
24 | public class MdcPong {
25 |
26 | private static final int STREAM_ID = Configurations.MDC_STREAM_ID;
27 | private static final int PORT = Configurations.MDC_PORT;
28 | private static final int CONTROL_PORT = Configurations.MDC_CONTROL_PORT;
29 | private static final int SESSION_ID = Configurations.MDC_SESSION_ID;
30 | private static final String OUTBOUND_CHANNEL =
31 | new ChannelUriStringBuilder()
32 | .controlEndpoint(Configurations.MDC_ADDRESS + ':' + CONTROL_PORT)
33 | .sessionId(SESSION_ID ^ Integer.MAX_VALUE)
34 | .media("udp")
35 | .reliable(Boolean.TRUE)
36 | .build();
37 | private static final String INBOUND_CHANNEL =
38 | new ChannelUriStringBuilder()
39 | .endpoint(Configurations.MDC_ADDRESS + ':' + PORT)
40 | .sessionId(SESSION_ID)
41 | .reliable(Boolean.TRUE)
42 | .media("udp")
43 | .build();
44 |
45 | private static final int FRAME_COUNT_LIMIT = Configurations.FRAGMENT_COUNT_LIMIT;
46 | private static final boolean INFO_FLAG = Configurations.INFO_FLAG;
47 | private static final boolean EMBEDDED_MEDIA_DRIVER = Configurations.EMBEDDED_MEDIA_DRIVER;
48 | private static final boolean EXCLUSIVE_PUBLICATIONS = Configurations.EXCLUSIVE_PUBLICATIONS;
49 |
50 | private static final IdleStrategy PING_HANDLER_IDLE_STRATEGY = Configurations.idleStrategy();
51 |
52 | /**
53 | * Main runner.
54 | *
55 | * @param args program arguments.
56 | */
57 | public static void main(final String[] args) {
58 | final MediaDriver driver = EMBEDDED_MEDIA_DRIVER ? MediaDriver.launchEmbedded() : null;
59 |
60 | final Aeron.Context ctx = new Aeron.Context();
61 | if (EMBEDDED_MEDIA_DRIVER) {
62 | ctx.aeronDirectoryName(driver.aeronDirectoryName());
63 | }
64 |
65 | if (INFO_FLAG) {
66 | ctx.availableImageHandler(Configurations::printAvailableImage);
67 | ctx.unavailableImageHandler(Configurations::printUnavailableImage);
68 | }
69 |
70 | final IdleStrategy idleStrategy = Configurations.idleStrategy();
71 |
72 | System.out.println("MediaDriver THREADING_MODE: " + Configuration.THREADING_MODE_DEFAULT);
73 | System.out.println("Subscribing Ping at " + INBOUND_CHANNEL + " on stream Id " + STREAM_ID);
74 | System.out.println("Publishing Pong at " + OUTBOUND_CHANNEL + " on stream Id " + STREAM_ID);
75 | System.out.println("Using exclusive publications " + EXCLUSIVE_PUBLICATIONS);
76 | System.out.println(
77 | "Using ping handler idle strategy "
78 | + PING_HANDLER_IDLE_STRATEGY.getClass()
79 | + "("
80 | + Configurations.IDLE_STRATEGY
81 | + ")");
82 |
83 | final AtomicBoolean running = new AtomicBoolean(true);
84 | SigInt.register(() -> running.set(false));
85 |
86 | try (Aeron aeron = Aeron.connect(ctx);
87 | Subscription subscription = aeron.addSubscription(INBOUND_CHANNEL, STREAM_ID);
88 | Publication publication =
89 | EXCLUSIVE_PUBLICATIONS
90 | ? aeron.addExclusivePublication(OUTBOUND_CHANNEL, STREAM_ID)
91 | : aeron.addPublication(OUTBOUND_CHANNEL, STREAM_ID)) {
92 | final FragmentAssembler dataHandler =
93 | new FragmentAssembler(
94 | (buffer, offset, length, header) -> pingHandler(publication, buffer, offset, length));
95 |
96 | while (running.get()) {
97 | idleStrategy.idle(subscription.poll(dataHandler, FRAME_COUNT_LIMIT));
98 | }
99 |
100 | System.out.println("Shutting down...");
101 | }
102 |
103 | CloseHelper.quietClose(driver);
104 | }
105 |
106 | private static void pingHandler(
107 | final Publication pongPublication,
108 | final DirectBuffer buffer,
109 | final int offset,
110 | final int length) {
111 | if (pongPublication.offer(buffer, offset, length) > 0L) {
112 | return;
113 | }
114 |
115 | PING_HANDLER_IDLE_STRATEGY.reset();
116 |
117 | while (pongPublication.offer(buffer, offset, length) < 0L) {
118 | PING_HANDLER_IDLE_STRATEGY.idle();
119 | }
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/reactor-aeron-benchmarks/src/main/java/reactor/aeron/pure/Ping.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron.pure;
2 |
3 | import io.aeron.Aeron;
4 | import io.aeron.FragmentAssembler;
5 | import io.aeron.Image;
6 | import io.aeron.Publication;
7 | import io.aeron.Subscription;
8 | import io.aeron.driver.Configuration;
9 | import io.aeron.driver.MediaDriver;
10 | import io.aeron.logbuffer.FragmentHandler;
11 | import io.aeron.logbuffer.Header;
12 | import java.time.Duration;
13 | import java.util.concurrent.CountDownLatch;
14 | import java.util.concurrent.TimeUnit;
15 | import org.HdrHistogram.Recorder;
16 | import org.agrona.BitUtil;
17 | import org.agrona.BufferUtil;
18 | import org.agrona.CloseHelper;
19 | import org.agrona.DirectBuffer;
20 | import org.agrona.concurrent.IdleStrategy;
21 | import org.agrona.concurrent.UnsafeBuffer;
22 | import org.agrona.console.ContinueBarrier;
23 | import reactor.aeron.Configurations;
24 | import reactor.aeron.LatencyReporter;
25 | import reactor.core.Disposable;
26 | import reactor.core.publisher.Mono;
27 |
28 | /**
29 | * Ping component of Ping-Pong latency test recorded to a histogram to capture full distribution..
30 | *
31 | *
Initiates messages sent to {@link Pong} and records times.
32 | *
33 | * @see Pong
34 | */
35 | public class Ping {
36 | private static final int PING_STREAM_ID = Configurations.PING_STREAM_ID;
37 | private static final int PONG_STREAM_ID = Configurations.PONG_STREAM_ID;
38 | private static final long NUMBER_OF_MESSAGES = Configurations.NUMBER_OF_MESSAGES;
39 | private static final long WARMUP_NUMBER_OF_MESSAGES = Configurations.WARMUP_NUMBER_OF_MESSAGES;
40 | private static final int WARMUP_NUMBER_OF_ITERATIONS = Configurations.WARMUP_NUMBER_OF_ITERATIONS;
41 | private static final int MESSAGE_LENGTH = Configurations.MESSAGE_LENGTH;
42 | private static final int FRAGMENT_COUNT_LIMIT = Configurations.FRAGMENT_COUNT_LIMIT;
43 | private static final boolean EMBEDDED_MEDIA_DRIVER = Configurations.EMBEDDED_MEDIA_DRIVER;
44 | private static final String PING_CHANNEL = Configurations.PING_CHANNEL;
45 | private static final String PONG_CHANNEL = Configurations.PONG_CHANNEL;
46 | private static final boolean EXCLUSIVE_PUBLICATIONS = Configurations.EXCLUSIVE_PUBLICATIONS;
47 |
48 | private static final UnsafeBuffer OFFER_BUFFER =
49 | new UnsafeBuffer(BufferUtil.allocateDirectAligned(MESSAGE_LENGTH, BitUtil.CACHE_LINE_LENGTH));
50 | private static final Recorder HISTOGRAM = new Recorder(TimeUnit.SECONDS.toNanos(10), 3);
51 | private static final LatencyReporter latencyReporter = new LatencyReporter(HISTOGRAM);
52 | private static final CountDownLatch LATCH = new CountDownLatch(1);
53 | private static final IdleStrategy POLLING_IDLE_STRATEGY = Configurations.idleStrategy();
54 |
55 | /**
56 | * Main runner.
57 | *
58 | * @param args program arguments.
59 | */
60 | public static void main(final String[] args) throws Exception {
61 | final MediaDriver driver = EMBEDDED_MEDIA_DRIVER ? MediaDriver.launchEmbedded() : null;
62 | final Aeron.Context ctx =
63 | new Aeron.Context().availableImageHandler(Ping::availablePongImageHandler);
64 | final FragmentHandler fragmentHandler = new FragmentAssembler(Ping::pongHandler);
65 |
66 | if (EMBEDDED_MEDIA_DRIVER) {
67 | ctx.aeronDirectoryName(driver.aeronDirectoryName());
68 | }
69 |
70 | System.out.println("MediaDriver THREADING_MODE: " + Configuration.THREADING_MODE_DEFAULT);
71 | System.out.println("Publishing Ping at " + PING_CHANNEL + " on stream Id " + PING_STREAM_ID);
72 | System.out.println("Subscribing Pong at " + PONG_CHANNEL + " on stream Id " + PONG_STREAM_ID);
73 | System.out.println("Message length of " + MESSAGE_LENGTH + " bytes");
74 | System.out.println("Using exclusive publications " + EXCLUSIVE_PUBLICATIONS);
75 | System.out.println(
76 | "Using poling idle strategy "
77 | + POLLING_IDLE_STRATEGY.getClass()
78 | + "("
79 | + Configurations.IDLE_STRATEGY
80 | + ")");
81 |
82 | try (Aeron aeron = Aeron.connect(ctx);
83 | Subscription subscription = aeron.addSubscription(PONG_CHANNEL, PONG_STREAM_ID);
84 | Publication publication =
85 | EXCLUSIVE_PUBLICATIONS
86 | ? aeron.addExclusivePublication(PING_CHANNEL, PING_STREAM_ID)
87 | : aeron.addPublication(PING_CHANNEL, PING_STREAM_ID)) {
88 | System.out.println("Waiting for new image from Pong...");
89 | LATCH.await();
90 |
91 | System.out.println(
92 | "Warming up... "
93 | + WARMUP_NUMBER_OF_ITERATIONS
94 | + " iterations of "
95 | + WARMUP_NUMBER_OF_MESSAGES
96 | + " messages");
97 |
98 | for (int i = 0; i < WARMUP_NUMBER_OF_ITERATIONS; i++) {
99 | roundTripMessages(
100 | fragmentHandler, publication, subscription, WARMUP_NUMBER_OF_MESSAGES, true);
101 | }
102 |
103 | Thread.sleep(100);
104 | final ContinueBarrier barrier = new ContinueBarrier("Execute again?");
105 |
106 | do {
107 | System.out.println("Pinging " + NUMBER_OF_MESSAGES + " messages");
108 | roundTripMessages(fragmentHandler, publication, subscription, NUMBER_OF_MESSAGES, false);
109 | System.out.println("Histogram of RTT latencies in microseconds.");
110 | } while (barrier.await());
111 | }
112 |
113 | CloseHelper.quietClose(driver);
114 | }
115 |
116 | private static void roundTripMessages(
117 | final FragmentHandler fragmentHandler,
118 | final Publication publication,
119 | final Subscription subscription,
120 | final long count,
121 | final boolean warmup) {
122 | while (!subscription.isConnected()) {
123 | Thread.yield();
124 | }
125 |
126 | HISTOGRAM.reset();
127 |
128 | Disposable reporter = latencyReporter.start();
129 |
130 | final Image image = subscription.imageAtIndex(0);
131 |
132 | for (long i = 0; i < count; i++) {
133 | long offeredPosition;
134 |
135 | do {
136 | OFFER_BUFFER.putLong(0, System.nanoTime());
137 | } while ((offeredPosition = publication.offer(OFFER_BUFFER, 0, MESSAGE_LENGTH)) < 0L);
138 |
139 | POLLING_IDLE_STRATEGY.reset();
140 |
141 | do {
142 | while (image.poll(fragmentHandler, FRAGMENT_COUNT_LIMIT) <= 0) {
143 | POLLING_IDLE_STRATEGY.idle();
144 | }
145 | } while (image.position() < offeredPosition);
146 | }
147 |
148 | Mono.delay(Duration.ofMillis(100)).doOnSubscribe(s -> reporter.dispose()).then().subscribe();
149 | }
150 |
151 | private static void pongHandler(
152 | final DirectBuffer buffer, final int offset, final int length, final Header header) {
153 | final long pingTimestamp = buffer.getLong(offset);
154 | final long rttNs = System.nanoTime() - pingTimestamp;
155 |
156 | HISTOGRAM.recordValue(rttNs);
157 | }
158 |
159 | private static void availablePongImageHandler(final Image image) {
160 | final Subscription subscription = image.subscription();
161 | System.out.format(
162 | "Available image: channel=%s streamId=%d session=%d%n",
163 | subscription.channel(), subscription.streamId(), image.sessionId());
164 |
165 | if (PONG_STREAM_ID == subscription.streamId() && PONG_CHANNEL.equals(subscription.channel())) {
166 | LATCH.countDown();
167 | }
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/reactor-aeron-benchmarks/src/main/java/reactor/aeron/pure/Pong.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron.pure;
2 |
3 | import io.aeron.Aeron;
4 | import io.aeron.FragmentAssembler;
5 | import io.aeron.Publication;
6 | import io.aeron.Subscription;
7 | import io.aeron.driver.Configuration;
8 | import io.aeron.driver.MediaDriver;
9 | import java.util.concurrent.atomic.AtomicBoolean;
10 | import org.agrona.CloseHelper;
11 | import org.agrona.DirectBuffer;
12 | import org.agrona.concurrent.IdleStrategy;
13 | import org.agrona.concurrent.SigInt;
14 | import reactor.aeron.Configurations;
15 |
16 | /**
17 | * Pong component of Ping-Pong.
18 | *
19 | *
Echoes back messages from {@link Ping}.
20 | *
21 | * @see Ping
22 | */
23 | public class Pong {
24 | private static final int PING_STREAM_ID = Configurations.PING_STREAM_ID;
25 | private static final int PONG_STREAM_ID = Configurations.PONG_STREAM_ID;
26 | private static final int FRAME_COUNT_LIMIT = Configurations.FRAGMENT_COUNT_LIMIT;
27 | private static final String PING_CHANNEL = Configurations.PING_CHANNEL;
28 | private static final String PONG_CHANNEL = Configurations.PONG_CHANNEL;
29 | private static final boolean INFO_FLAG = Configurations.INFO_FLAG;
30 | private static final boolean EMBEDDED_MEDIA_DRIVER = Configurations.EMBEDDED_MEDIA_DRIVER;
31 | private static final boolean EXCLUSIVE_PUBLICATIONS = Configurations.EXCLUSIVE_PUBLICATIONS;
32 |
33 | private static final IdleStrategy PING_HANDLER_IDLE_STRATEGY = Configurations.idleStrategy();
34 |
35 | /**
36 | * Main runner.
37 | *
38 | * @param args program arguments.
39 | */
40 | public static void main(final String[] args) {
41 | final MediaDriver driver = EMBEDDED_MEDIA_DRIVER ? MediaDriver.launchEmbedded() : null;
42 |
43 | final Aeron.Context ctx = new Aeron.Context();
44 | if (EMBEDDED_MEDIA_DRIVER) {
45 | ctx.aeronDirectoryName(driver.aeronDirectoryName());
46 | }
47 |
48 | if (INFO_FLAG) {
49 | ctx.availableImageHandler(Configurations::printAvailableImage);
50 | ctx.unavailableImageHandler(Configurations::printUnavailableImage);
51 | }
52 |
53 | final IdleStrategy idleStrategy = Configurations.idleStrategy();
54 |
55 | System.out.println("MediaDriver THREADING_MODE: " + Configuration.THREADING_MODE_DEFAULT);
56 | System.out.println("Subscribing Ping at " + PING_CHANNEL + " on stream Id " + PING_STREAM_ID);
57 | System.out.println("Publishing Pong at " + PONG_CHANNEL + " on stream Id " + PONG_STREAM_ID);
58 | System.out.println("Using exclusive publications " + EXCLUSIVE_PUBLICATIONS);
59 | System.out.println(
60 | "Using ping handler idle strategy "
61 | + PING_HANDLER_IDLE_STRATEGY.getClass()
62 | + "("
63 | + Configurations.IDLE_STRATEGY
64 | + ")");
65 |
66 | final AtomicBoolean running = new AtomicBoolean(true);
67 | SigInt.register(() -> running.set(false));
68 |
69 | try (Aeron aeron = Aeron.connect(ctx);
70 | Subscription subscription = aeron.addSubscription(PING_CHANNEL, PING_STREAM_ID);
71 | Publication publication =
72 | EXCLUSIVE_PUBLICATIONS
73 | ? aeron.addExclusivePublication(PONG_CHANNEL, PONG_STREAM_ID)
74 | : aeron.addPublication(PONG_CHANNEL, PONG_STREAM_ID)) {
75 | final FragmentAssembler dataHandler =
76 | new FragmentAssembler(
77 | (buffer, offset, length, header) -> pingHandler(publication, buffer, offset, length));
78 |
79 | while (running.get()) {
80 | idleStrategy.idle(subscription.poll(dataHandler, FRAME_COUNT_LIMIT));
81 | }
82 |
83 | System.out.println("Shutting down...");
84 | }
85 |
86 | CloseHelper.quietClose(driver);
87 | }
88 |
89 | private static void pingHandler(
90 | final Publication pongPublication,
91 | final DirectBuffer buffer,
92 | final int offset,
93 | final int length) {
94 | if (pongPublication.offer(buffer, offset, length) > 0L) {
95 | return;
96 | }
97 |
98 | PING_HANDLER_IDLE_STRATEGY.reset();
99 |
100 | while (pongPublication.offer(buffer, offset, length) < 0L) {
101 | PING_HANDLER_IDLE_STRATEGY.idle();
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/reactor-aeron-benchmarks/src/main/java/reactor/aeron/pure/ServerThroughput.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron.pure;
2 |
3 | import io.aeron.Aeron;
4 | import io.aeron.ChannelUriStringBuilder;
5 | import io.aeron.FragmentAssembler;
6 | import io.aeron.Publication;
7 | import io.aeron.Subscription;
8 | import io.aeron.driver.Configuration;
9 | import io.aeron.driver.MediaDriver;
10 | import java.util.concurrent.atomic.AtomicBoolean;
11 | import org.agrona.CloseHelper;
12 | import org.agrona.concurrent.IdleStrategy;
13 | import org.agrona.concurrent.SigInt;
14 | import reactor.aeron.Configurations;
15 | import reactor.aeron.RateReporter;
16 |
17 | public class ServerThroughput {
18 |
19 | private static final int STREAM_ID = Configurations.MDC_STREAM_ID;
20 | private static final int PORT = Configurations.MDC_PORT;
21 | private static final int CONTROL_PORT = Configurations.MDC_CONTROL_PORT;
22 | private static final int SESSION_ID = Configurations.MDC_SESSION_ID;
23 | private static final String OUTBOUND_CHANNEL =
24 | new ChannelUriStringBuilder()
25 | .controlEndpoint(Configurations.MDC_ADDRESS + ':' + CONTROL_PORT)
26 | .sessionId(SESSION_ID ^ Integer.MAX_VALUE)
27 | .media("udp")
28 | .reliable(Boolean.TRUE)
29 | .build();
30 | private static final String INBOUND_CHANNEL =
31 | new ChannelUriStringBuilder()
32 | .endpoint(Configurations.MDC_ADDRESS + ':' + PORT)
33 | .sessionId(SESSION_ID)
34 | .reliable(Boolean.TRUE)
35 | .media("udp")
36 | .build();
37 |
38 | private static final int FRAME_COUNT_LIMIT = Configurations.FRAGMENT_COUNT_LIMIT;
39 | private static final boolean INFO_FLAG = Configurations.INFO_FLAG;
40 | private static final boolean EMBEDDED_MEDIA_DRIVER = Configurations.EMBEDDED_MEDIA_DRIVER;
41 | private static final boolean EXCLUSIVE_PUBLICATIONS = Configurations.EXCLUSIVE_PUBLICATIONS;
42 |
43 | private static final IdleStrategy PING_HANDLER_IDLE_STRATEGY = Configurations.idleStrategy();
44 |
45 | /**
46 | * Main runner.
47 | *
48 | * @param args program arguments.
49 | */
50 | public static void main(final String[] args) {
51 | final MediaDriver driver = EMBEDDED_MEDIA_DRIVER ? MediaDriver.launchEmbedded() : null;
52 |
53 | final Aeron.Context ctx = new Aeron.Context();
54 | if (EMBEDDED_MEDIA_DRIVER) {
55 | ctx.aeronDirectoryName(driver.aeronDirectoryName());
56 | }
57 |
58 | if (INFO_FLAG) {
59 | ctx.availableImageHandler(Configurations::printAvailableImage);
60 | ctx.unavailableImageHandler(Configurations::printUnavailableImage);
61 | }
62 |
63 | final IdleStrategy idleStrategy = Configurations.idleStrategy();
64 |
65 | System.out.println("MediaDriver THREADING_MODE: " + Configuration.THREADING_MODE_DEFAULT);
66 | System.out.println("Subscribing Ping at " + INBOUND_CHANNEL + " on stream Id " + STREAM_ID);
67 | System.out.println("Publishing Pong at " + OUTBOUND_CHANNEL + " on stream Id " + STREAM_ID);
68 | System.out.println("Using exclusive publications " + EXCLUSIVE_PUBLICATIONS);
69 | System.out.println(
70 | "Using ping handler idle strategy "
71 | + PING_HANDLER_IDLE_STRATEGY.getClass()
72 | + "("
73 | + Configurations.IDLE_STRATEGY
74 | + ")");
75 |
76 | final AtomicBoolean running = new AtomicBoolean(true);
77 | SigInt.register(() -> running.set(false));
78 |
79 | RateReporter reporter = new RateReporter();
80 |
81 | try (Aeron aeron = Aeron.connect(ctx);
82 | Subscription subscription = aeron.addSubscription(INBOUND_CHANNEL, STREAM_ID);
83 | Publication publication =
84 | EXCLUSIVE_PUBLICATIONS
85 | ? aeron.addExclusivePublication(OUTBOUND_CHANNEL, STREAM_ID)
86 | : aeron.addPublication(OUTBOUND_CHANNEL, STREAM_ID)) {
87 |
88 | final FragmentAssembler dataHandler =
89 | new FragmentAssembler((buffer, offset, length, header) -> reporter.onMessage(1, length));
90 |
91 | while (running.get()) {
92 | idleStrategy.idle(subscription.poll(dataHandler, FRAME_COUNT_LIMIT));
93 | }
94 |
95 | System.out.println("Shutting down...");
96 | } finally {
97 | reporter.dispose();
98 | }
99 |
100 | CloseHelper.quietClose(driver);
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/reactor-aeron-benchmarks/src/main/java/reactor/aeron/rsocket/aeron/RSocketAeronClientTps.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron.rsocket.aeron;
2 |
3 | import io.aeron.driver.Configuration;
4 | import io.rsocket.Frame;
5 | import io.rsocket.Payload;
6 | import io.rsocket.RSocket;
7 | import io.rsocket.RSocketFactory;
8 | import io.rsocket.reactor.aeron.AeronClientTransport;
9 | import io.rsocket.util.ByteBufPayload;
10 | import reactor.aeron.AeronClient;
11 | import reactor.aeron.AeronResources;
12 | import reactor.aeron.Configurations;
13 | import reactor.aeron.RateReporter;
14 |
15 | public final class RSocketAeronClientTps {
16 |
17 | /**
18 | * Main runner.
19 | *
20 | * @param args program arguments.
21 | */
22 | public static void main(String... args) {
23 |
24 | printSettings();
25 |
26 | AeronResources resources =
27 | new AeronResources()
28 | .useTmpDir()
29 | .pollFragmentLimit(Configurations.FRAGMENT_COUNT_LIMIT)
30 | .singleWorker()
31 | .workerIdleStrategySupplier(Configurations::idleStrategy)
32 | .start()
33 | .block();
34 |
35 | RSocket client =
36 | RSocketFactory.connect()
37 | .frameDecoder(Frame::retain)
38 | .transport(
39 | () ->
40 | new AeronClientTransport(
41 | AeronClient.create(resources)
42 | .options(
43 | Configurations.MDC_ADDRESS,
44 | Configurations.MDC_PORT,
45 | Configurations.MDC_CONTROL_PORT)))
46 | .start()
47 | .block();
48 |
49 | RateReporter reporter = new RateReporter();
50 |
51 | Payload request = ByteBufPayload.create("hello");
52 |
53 | client
54 | .requestStream(request)
55 | .doOnNext(
56 | payload -> {
57 | reporter.onMessage(1, payload.sliceData().readableBytes());
58 | payload.release();
59 | })
60 | .doOnError(Throwable::printStackTrace)
61 | .doFinally(s -> reporter.dispose())
62 | .then()
63 | .block();
64 | }
65 |
66 | private static void printSettings() {
67 | System.out.println(
68 | "address: "
69 | + Configurations.MDC_ADDRESS
70 | + ", port: "
71 | + Configurations.MDC_PORT
72 | + ", controlPort: "
73 | + Configurations.MDC_CONTROL_PORT);
74 | System.out.println("MediaDriver THREADING_MODE: " + Configuration.THREADING_MODE_DEFAULT);
75 | System.out.println("pollFragmentLimit of " + Configurations.FRAGMENT_COUNT_LIMIT);
76 | System.out.println(
77 | "Using worker idle strategy "
78 | + Configurations.idleStrategy().getClass()
79 | + "("
80 | + Configurations.IDLE_STRATEGY
81 | + ")");
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/reactor-aeron-benchmarks/src/main/java/reactor/aeron/rsocket/aeron/RSocketAeronPing.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron.rsocket.aeron;
2 |
3 | import io.aeron.driver.Configuration;
4 | import io.netty.buffer.ByteBufAllocator;
5 | import io.rsocket.Frame;
6 | import io.rsocket.Payload;
7 | import io.rsocket.RSocket;
8 | import io.rsocket.RSocketFactory;
9 | import io.rsocket.reactor.aeron.AeronClientTransport;
10 | import io.rsocket.util.ByteBufPayload;
11 | import java.util.concurrent.TimeUnit;
12 | import org.HdrHistogram.Recorder;
13 | import reactor.aeron.AeronClient;
14 | import reactor.aeron.AeronResources;
15 | import reactor.aeron.Configurations;
16 | import reactor.aeron.LatencyReporter;
17 | import reactor.core.Disposable;
18 | import reactor.core.publisher.Flux;
19 |
20 | public final class RSocketAeronPing {
21 |
22 | private static final Recorder HISTOGRAM = new Recorder(TimeUnit.SECONDS.toNanos(10), 3);
23 | private static final LatencyReporter latencyReporter = new LatencyReporter(HISTOGRAM);
24 |
25 | private static final Payload PAYLOAD =
26 | ByteBufPayload.create(ByteBufAllocator.DEFAULT.buffer(Configurations.MESSAGE_LENGTH));
27 |
28 | /**
29 | * Main runner.
30 | *
31 | * @param args program arguments.
32 | */
33 | public static void main(String... args) {
34 | printSettings();
35 |
36 | AeronResources resources =
37 | new AeronResources()
38 | .useTmpDir()
39 | .pollFragmentLimit(Configurations.FRAGMENT_COUNT_LIMIT)
40 | .singleWorker()
41 | .workerIdleStrategySupplier(Configurations::idleStrategy)
42 | .start()
43 | .block();
44 |
45 | RSocket client =
46 | RSocketFactory.connect()
47 | .frameDecoder(Frame::retain)
48 | .transport(
49 | () ->
50 | new AeronClientTransport(
51 | AeronClient.create(resources)
52 | .options(
53 | Configurations.MDC_ADDRESS,
54 | Configurations.MDC_PORT,
55 | Configurations.MDC_CONTROL_PORT)))
56 | .start()
57 | .block();
58 |
59 | Disposable report = latencyReporter.start();
60 |
61 | Flux.range(1, (int) Configurations.NUMBER_OF_MESSAGES)
62 | .flatMap(
63 | i -> {
64 | long start = System.nanoTime();
65 | return client
66 | .requestResponse(PAYLOAD.retain())
67 | .doOnNext(Payload::release)
68 | .doFinally(
69 | signalType -> {
70 | long diff = System.nanoTime() - start;
71 | HISTOGRAM.recordValue(diff);
72 | });
73 | },
74 | 64)
75 | .doOnError(Throwable::printStackTrace)
76 | .doOnTerminate(
77 | () -> System.out.println("Sent " + Configurations.NUMBER_OF_MESSAGES + " messages."))
78 | .doFinally(s -> report.dispose())
79 | .then()
80 | .doFinally(s -> resources.dispose())
81 | .then(resources.onDispose())
82 | .block();
83 | }
84 |
85 | private static void printSettings() {
86 | System.out.println(
87 | "address: "
88 | + Configurations.MDC_ADDRESS
89 | + ", port: "
90 | + Configurations.MDC_PORT
91 | + ", controlPort: "
92 | + Configurations.MDC_CONTROL_PORT);
93 | System.out.println("MediaDriver THREADING_MODE: " + Configuration.THREADING_MODE_DEFAULT);
94 | System.out.println("Message length of " + Configurations.MESSAGE_LENGTH + " bytes");
95 | System.out.println("pollFragmentLimit of " + Configurations.FRAGMENT_COUNT_LIMIT);
96 | System.out.println(
97 | "Using worker idle strategy "
98 | + Configurations.idleStrategy().getClass()
99 | + "("
100 | + Configurations.IDLE_STRATEGY
101 | + ")");
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/reactor-aeron-benchmarks/src/main/java/reactor/aeron/rsocket/aeron/RSocketAeronPong.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron.rsocket.aeron;
2 |
3 | import io.aeron.driver.Configuration;
4 | import io.rsocket.AbstractRSocket;
5 | import io.rsocket.Frame;
6 | import io.rsocket.Payload;
7 | import io.rsocket.RSocketFactory;
8 | import io.rsocket.reactor.aeron.AeronServerTransport;
9 | import reactor.aeron.AeronResources;
10 | import reactor.aeron.AeronServer;
11 | import reactor.aeron.Configurations;
12 | import reactor.core.publisher.Mono;
13 |
14 | public final class RSocketAeronPong {
15 |
16 | /**
17 | * Main runner.
18 | *
19 | * @param args program arguments.
20 | */
21 | public static void main(String... args) {
22 | printSettings();
23 |
24 | AeronResources resources =
25 | new AeronResources()
26 | .useTmpDir()
27 | .pollFragmentLimit(Configurations.FRAGMENT_COUNT_LIMIT)
28 | .singleWorker()
29 | .workerIdleStrategySupplier(Configurations::idleStrategy)
30 | .start()
31 | .block();
32 |
33 | RSocketFactory.receive()
34 | .frameDecoder(Frame::retain)
35 | .acceptor(
36 | (setupPayload, rsocket) ->
37 | Mono.just(
38 | new AbstractRSocket() {
39 | @Override
40 | public Mono requestResponse(Payload payload) {
41 | return Mono.just(payload);
42 | }
43 | }))
44 | .transport(
45 | new AeronServerTransport(
46 | AeronServer.create(resources)
47 | .options(
48 | Configurations.MDC_ADDRESS,
49 | Configurations.MDC_PORT,
50 | Configurations.MDC_CONTROL_PORT)))
51 | .start()
52 | .block()
53 | .onClose()
54 | .doFinally(s -> resources.dispose())
55 | .then(resources.onDispose())
56 | .block();
57 | }
58 |
59 | private static void printSettings() {
60 | System.out.println(
61 | "address: "
62 | + Configurations.MDC_ADDRESS
63 | + ", port: "
64 | + Configurations.MDC_PORT
65 | + ", controlPort: "
66 | + Configurations.MDC_CONTROL_PORT);
67 | System.out.println("MediaDriver THREADING_MODE: " + Configuration.THREADING_MODE_DEFAULT);
68 | System.out.println("pollFragmentLimit of " + Configurations.FRAGMENT_COUNT_LIMIT);
69 | System.out.println(
70 | "Using worker idle strategy "
71 | + Configurations.idleStrategy().getClass()
72 | + "("
73 | + Configurations.IDLE_STRATEGY
74 | + ")");
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/reactor-aeron-benchmarks/src/main/java/reactor/aeron/rsocket/aeron/RSocketAeronServerTps.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron.rsocket.aeron;
2 |
3 | import io.aeron.driver.Configuration;
4 | import io.netty.buffer.ByteBuf;
5 | import io.netty.buffer.ByteBufAllocator;
6 | import io.rsocket.AbstractRSocket;
7 | import io.rsocket.Frame;
8 | import io.rsocket.Payload;
9 | import io.rsocket.RSocketFactory;
10 | import io.rsocket.reactor.aeron.AeronServerTransport;
11 | import io.rsocket.util.ByteBufPayload;
12 | import java.util.Random;
13 | import reactor.aeron.AeronResources;
14 | import reactor.aeron.AeronServer;
15 | import reactor.aeron.Configurations;
16 | import reactor.core.publisher.Flux;
17 | import reactor.core.publisher.Mono;
18 |
19 | public final class RSocketAeronServerTps {
20 |
21 | private static final ByteBuf BUFFER =
22 | ByteBufAllocator.DEFAULT.buffer(Configurations.MESSAGE_LENGTH);
23 |
24 | static {
25 | Random random = new Random(System.nanoTime());
26 | byte[] bytes = new byte[Configurations.MESSAGE_LENGTH];
27 | random.nextBytes(bytes);
28 | BUFFER.writeBytes(bytes);
29 | }
30 |
31 | /**
32 | * Main runner.
33 | *
34 | * @param args program arguments.
35 | */
36 | public static void main(String... args) {
37 |
38 | printSettings();
39 |
40 | AeronResources resources =
41 | new AeronResources()
42 | .useTmpDir()
43 | .pollFragmentLimit(Configurations.FRAGMENT_COUNT_LIMIT)
44 | .singleWorker()
45 | .workerIdleStrategySupplier(Configurations::idleStrategy)
46 | .start()
47 | .block();
48 |
49 | RSocketFactory.receive()
50 | .frameDecoder(Frame::retain)
51 | .acceptor(
52 | (setupPayload, rsocket) ->
53 | Mono.just(
54 | new AbstractRSocket() {
55 | @Override
56 | public Flux requestStream(Payload payload) {
57 | payload.release();
58 |
59 | long msgNum = Configurations.NUMBER_OF_MESSAGES;
60 | System.out.println("streaming " + msgNum + " messages ...");
61 |
62 | return Flux.range(0, Integer.MAX_VALUE)
63 | .map(i -> ByteBufPayload.create(BUFFER.retainedSlice()));
64 | }
65 | }))
66 | .transport(
67 | new AeronServerTransport(
68 | AeronServer.create(resources)
69 | .options(
70 | Configurations.MDC_ADDRESS,
71 | Configurations.MDC_PORT,
72 | Configurations.MDC_CONTROL_PORT)))
73 | .start()
74 | .block()
75 | .onClose()
76 | .doFinally(s -> resources.dispose())
77 | .then(resources.onDispose())
78 | .block();
79 | }
80 |
81 | private static void printSettings() {
82 | System.out.println(
83 | "address: "
84 | + Configurations.MDC_ADDRESS
85 | + ", port: "
86 | + Configurations.MDC_PORT
87 | + ", controlPort: "
88 | + Configurations.MDC_CONTROL_PORT);
89 | System.out.println("MediaDriver THREADING_MODE: " + Configuration.THREADING_MODE_DEFAULT);
90 | System.out.println("Message length of " + Configurations.MESSAGE_LENGTH + " bytes");
91 | System.out.println("pollFragmentLimit of " + Configurations.FRAGMENT_COUNT_LIMIT);
92 | System.out.println(
93 | "Using worker idle strategy "
94 | + Configurations.idleStrategy().getClass()
95 | + "("
96 | + Configurations.IDLE_STRATEGY
97 | + ")");
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/reactor-aeron-benchmarks/src/main/java/reactor/aeron/rsocket/netty/RSocketNettyClientTps.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron.rsocket.netty;
2 |
3 | import io.netty.channel.ChannelOption;
4 | import io.rsocket.Frame;
5 | import io.rsocket.Payload;
6 | import io.rsocket.RSocket;
7 | import io.rsocket.RSocketFactory;
8 | import io.rsocket.transport.netty.client.TcpClientTransport;
9 | import io.rsocket.util.ByteBufPayload;
10 | import reactor.aeron.Configurations;
11 | import reactor.aeron.RateReporter;
12 | import reactor.netty.resources.ConnectionProvider;
13 | import reactor.netty.resources.LoopResources;
14 | import reactor.netty.tcp.TcpClient;
15 |
16 | public final class RSocketNettyClientTps {
17 |
18 | /**
19 | * Main runner.
20 | *
21 | * @param args program arguments.
22 | */
23 | public static void main(String... args) {
24 | System.out.println(
25 | "message size: "
26 | + Configurations.MESSAGE_LENGTH
27 | + ", number of messages: "
28 | + Configurations.NUMBER_OF_MESSAGES
29 | + ", address: "
30 | + Configurations.MDC_ADDRESS
31 | + ", port: "
32 | + Configurations.MDC_PORT);
33 |
34 | RateReporter reporter = new RateReporter();
35 |
36 | LoopResources loopResources = LoopResources.create("rsocket-netty");
37 |
38 | TcpClient tcpClient =
39 | TcpClient.create(ConnectionProvider.newConnection())
40 | .runOn(loopResources)
41 | .host(Configurations.MDC_ADDRESS)
42 | .port(Configurations.MDC_PORT)
43 | .option(ChannelOption.TCP_NODELAY, true)
44 | .option(ChannelOption.SO_KEEPALIVE, true)
45 | .option(ChannelOption.SO_REUSEADDR, true)
46 | .doOnConnected(System.out::println);
47 |
48 | RSocket client =
49 | RSocketFactory.connect()
50 | .frameDecoder(Frame::retain)
51 | .transport(() -> TcpClientTransport.create(tcpClient))
52 | .start()
53 | .doOnSuccess(System.out::println)
54 | .block();
55 |
56 | Payload request = ByteBufPayload.create("hello");
57 |
58 | client
59 | .requestStream(request)
60 | .doOnNext(
61 | payload -> {
62 | reporter.onMessage(1, payload.sliceData().readableBytes());
63 | payload.release();
64 | })
65 | .doOnError(Throwable::printStackTrace)
66 | .doFinally(s -> reporter.dispose())
67 | .then()
68 | .block();
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/reactor-aeron-benchmarks/src/main/java/reactor/aeron/rsocket/netty/RSocketNettyPing.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron.rsocket.netty;
2 |
3 | import io.netty.buffer.ByteBuf;
4 | import io.netty.buffer.ByteBufAllocator;
5 | import io.netty.channel.ChannelOption;
6 | import io.rsocket.Frame;
7 | import io.rsocket.Payload;
8 | import io.rsocket.RSocket;
9 | import io.rsocket.RSocketFactory;
10 | import io.rsocket.transport.netty.client.TcpClientTransport;
11 | import io.rsocket.util.ByteBufPayload;
12 | import java.util.Random;
13 | import java.util.concurrent.TimeUnit;
14 | import java.util.function.Supplier;
15 | import org.HdrHistogram.Recorder;
16 | import reactor.aeron.Configurations;
17 | import reactor.aeron.LatencyReporter;
18 | import reactor.core.Disposable;
19 | import reactor.core.publisher.Flux;
20 | import reactor.netty.resources.ConnectionProvider;
21 | import reactor.netty.resources.LoopResources;
22 | import reactor.netty.tcp.TcpClient;
23 |
24 | public final class RSocketNettyPing {
25 |
26 | private static final Recorder HISTOGRAM = new Recorder(TimeUnit.SECONDS.toNanos(10), 3);
27 | private static final LatencyReporter latencyReporter = new LatencyReporter(HISTOGRAM);
28 |
29 | private static final ByteBuf BUFFER =
30 | ByteBufAllocator.DEFAULT.buffer(Configurations.MESSAGE_LENGTH);
31 |
32 | static {
33 | Random random = new Random(System.nanoTime());
34 | byte[] bytes = new byte[Configurations.MESSAGE_LENGTH];
35 | random.nextBytes(bytes);
36 | BUFFER.writeBytes(bytes);
37 | }
38 |
39 | /**
40 | * Main runner.
41 | *
42 | * @param args program arguments.
43 | */
44 | public static void main(String... args) {
45 | System.out.println(
46 | "message size: "
47 | + Configurations.MESSAGE_LENGTH
48 | + ", number of messages: "
49 | + Configurations.NUMBER_OF_MESSAGES
50 | + ", address: "
51 | + Configurations.MDC_ADDRESS
52 | + ", port: "
53 | + Configurations.MDC_PORT);
54 |
55 | LoopResources loopResources = LoopResources.create("rsocket-netty");
56 |
57 | TcpClient tcpClient =
58 | TcpClient.create(ConnectionProvider.newConnection())
59 | .runOn(loopResources)
60 | .host(Configurations.MDC_ADDRESS)
61 | .port(Configurations.MDC_PORT)
62 | .option(ChannelOption.TCP_NODELAY, true)
63 | .option(ChannelOption.SO_KEEPALIVE, true)
64 | .option(ChannelOption.SO_REUSEADDR, true)
65 | .doOnConnected(System.out::println);
66 |
67 | RSocket client =
68 | RSocketFactory.connect()
69 | .frameDecoder(Frame::retain)
70 | .transport(() -> TcpClientTransport.create(tcpClient))
71 | .start()
72 | .doOnSuccess(System.out::println)
73 | .block();
74 |
75 | Disposable report = latencyReporter.start();
76 |
77 | Supplier payloadSupplier = () -> ByteBufPayload.create(BUFFER.retainedSlice());
78 |
79 | Flux.range(1, (int) Configurations.NUMBER_OF_MESSAGES)
80 | .flatMap(
81 | i -> {
82 | long start = System.nanoTime();
83 | return client
84 | .requestResponse(payloadSupplier.get())
85 | .doOnNext(Payload::release)
86 | .doFinally(
87 | signalType -> {
88 | long diff = System.nanoTime() - start;
89 | HISTOGRAM.recordValue(diff);
90 | });
91 | },
92 | 64)
93 | .doOnError(Throwable::printStackTrace)
94 | .doOnTerminate(
95 | () -> System.out.println("Sent " + Configurations.NUMBER_OF_MESSAGES + " messages"))
96 | .doFinally(s -> report.dispose())
97 | .then()
98 | .block();
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/reactor-aeron-benchmarks/src/main/java/reactor/aeron/rsocket/netty/RSocketNettyPong.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron.rsocket.netty;
2 |
3 | import io.netty.channel.ChannelOption;
4 | import io.rsocket.AbstractRSocket;
5 | import io.rsocket.Frame;
6 | import io.rsocket.Payload;
7 | import io.rsocket.RSocketFactory;
8 | import io.rsocket.transport.netty.server.TcpServerTransport;
9 | import reactor.aeron.Configurations;
10 | import reactor.core.publisher.Mono;
11 | import reactor.netty.resources.LoopResources;
12 | import reactor.netty.tcp.TcpServer;
13 |
14 | public final class RSocketNettyPong {
15 |
16 | /**
17 | * Main runner.
18 | *
19 | * @param args program arguments.
20 | */
21 | public static void main(String... args) {
22 | System.out.println(
23 | "message size: "
24 | + Configurations.MESSAGE_LENGTH
25 | + ", number of messages: "
26 | + Configurations.NUMBER_OF_MESSAGES
27 | + ", address: "
28 | + Configurations.MDC_ADDRESS
29 | + ", port: "
30 | + Configurations.MDC_PORT);
31 |
32 | LoopResources loopResources = LoopResources.create("rsocket-netty");
33 |
34 | TcpServer tcpServer =
35 | TcpServer.create()
36 | .runOn(loopResources)
37 | .host(Configurations.MDC_ADDRESS)
38 | .port(Configurations.MDC_PORT)
39 | .option(ChannelOption.TCP_NODELAY, true)
40 | .option(ChannelOption.SO_KEEPALIVE, true)
41 | .option(ChannelOption.SO_REUSEADDR, true)
42 | .doOnConnection(System.out::println);
43 |
44 | RSocketFactory.receive()
45 | .frameDecoder(Frame::retain)
46 | .acceptor(
47 | (setupPayload, rsocket) -> {
48 | System.out.println(rsocket);
49 | return Mono.just(
50 | new AbstractRSocket() {
51 | @Override
52 | public Mono requestResponse(Payload payload) {
53 | return Mono.just(payload);
54 | }
55 | });
56 | })
57 | .transport(() -> TcpServerTransport.create(tcpServer))
58 | .start()
59 | .block()
60 | .onClose()
61 | .block();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/reactor-aeron-benchmarks/src/main/java/reactor/aeron/rsocket/netty/RSocketNettyServerTps.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron.rsocket.netty;
2 |
3 | import io.netty.buffer.ByteBuf;
4 | import io.netty.buffer.ByteBufAllocator;
5 | import io.netty.channel.ChannelOption;
6 | import io.rsocket.AbstractRSocket;
7 | import io.rsocket.Frame;
8 | import io.rsocket.Payload;
9 | import io.rsocket.RSocketFactory;
10 | import io.rsocket.transport.netty.server.TcpServerTransport;
11 | import io.rsocket.util.ByteBufPayload;
12 | import java.util.Random;
13 | import reactor.aeron.Configurations;
14 | import reactor.core.publisher.Flux;
15 | import reactor.core.publisher.Mono;
16 | import reactor.netty.resources.LoopResources;
17 | import reactor.netty.tcp.TcpServer;
18 |
19 | public final class RSocketNettyServerTps {
20 |
21 | private static final ByteBuf BUFFER =
22 | ByteBufAllocator.DEFAULT.buffer(Configurations.MESSAGE_LENGTH);
23 |
24 | static {
25 | Random random = new Random(System.nanoTime());
26 | byte[] bytes = new byte[Configurations.MESSAGE_LENGTH];
27 | random.nextBytes(bytes);
28 | BUFFER.writeBytes(bytes);
29 | }
30 |
31 | /**
32 | * Main runner.
33 | *
34 | * @param args program arguments.
35 | */
36 | public static void main(String... args) {
37 | System.out.println(
38 | "message size: "
39 | + Configurations.MESSAGE_LENGTH
40 | + ", number of messages: "
41 | + Configurations.NUMBER_OF_MESSAGES
42 | + ", address: "
43 | + Configurations.MDC_ADDRESS
44 | + ", port: "
45 | + Configurations.MDC_PORT);
46 |
47 | LoopResources loopResources = LoopResources.create("rsocket-netty");
48 |
49 | TcpServer tcpServer =
50 | TcpServer.create()
51 | .runOn(loopResources)
52 | .host(Configurations.MDC_ADDRESS)
53 | .port(Configurations.MDC_PORT)
54 | .option(ChannelOption.TCP_NODELAY, true)
55 | .option(ChannelOption.SO_KEEPALIVE, true)
56 | .option(ChannelOption.SO_REUSEADDR, true)
57 | .doOnConnection(System.out::println);
58 |
59 | RSocketFactory.receive()
60 | .frameDecoder(Frame::retain)
61 | .acceptor(
62 | (setupPayload, rsocket) -> {
63 | System.out.println(rsocket);
64 | return Mono.just(
65 | new AbstractRSocket() {
66 | @Override
67 | public Flux requestStream(Payload payload) {
68 | payload.release();
69 |
70 | long msgNum = Configurations.NUMBER_OF_MESSAGES;
71 | System.out.println("streaming " + msgNum + " messages ...");
72 |
73 | return Flux.range(0, Integer.MAX_VALUE)
74 | .map(i -> ByteBufPayload.create(BUFFER.retainedSlice()));
75 | }
76 | });
77 | })
78 | .transport(() -> TcpServerTransport.create(tcpServer))
79 | .start()
80 | .block()
81 | .onClose()
82 | .block();
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/reactor-aeron-benchmarks/src/main/resources/latency-report.json:
--------------------------------------------------------------------------------
1 | {
2 | "layout": {
3 | "title": "Latency Aeron vs Reactor-Aeron vs Reactor-Netty (C5.xlarge)",
4 | "xaxis": {
5 | "title": "data samples over time",
6 | "titlefont": {
7 | "family": "Courier New, monospace",
8 | "size": 12,
9 | "color": "#7f7f7f"
10 | }
11 | },
12 | "yaxis" : {
13 | "title": "latency per message size (bytes) / (microseconds)",
14 | "titlefont": {
15 | "family": "Courier New, monospace",
16 | "size": 12,
17 | "color": "#7f7f7f"
18 | }
19 | }
20 | },
21 | "traces": []
22 | }
23 |
--------------------------------------------------------------------------------
/reactor-aeron-benchmarks/src/main/resources/log4j2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | %level{length=1} %d{ISO8601} %c{1.} %m [%t]%n
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/reactor-aeron-benchmarks/src/main/resources/throughput-report.json:
--------------------------------------------------------------------------------
1 | {
2 | "layout": {
3 | "title": "Throughput Aeron vs Reactor-Aeron vs Reactor-Netty (C5.xlarge)",
4 | "xaxis": {
5 | "title": "data samples over time",
6 | "titlefont": {
7 | "family": "Courier New, monospace",
8 | "size": 12,
9 | "color": "#7f7f7f"
10 | }
11 | },
12 | "yaxis" : {
13 | "title": "Throughput per message size (bytes) / (messages per second)",
14 | "titlefont": {
15 | "family": "Courier New, monospace",
16 | "size": 12,
17 | "color": "#7f7f7f"
18 | }
19 | }
20 | },
21 | "traces": []
22 | }
23 |
--------------------------------------------------------------------------------
/reactor-aeron/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 |
6 | io.scalecube
7 | reactor-aeron-parent
8 | 0.1.6-SNAPSHOT
9 |
10 |
11 | reactor-aeron
12 |
13 |
14 |
15 | io.aeron
16 | aeron-driver
17 |
18 |
19 | io.aeron
20 | aeron-client
21 |
22 |
23 |
24 | io.projectreactor
25 | reactor-core
26 |
27 |
28 |
29 | org.slf4j
30 | slf4j-api
31 |
32 |
33 | org.apache.logging.log4j
34 | log4j-slf4j-impl
35 | test
36 |
37 |
38 | org.apache.logging.log4j
39 | log4j-core
40 | test
41 |
42 |
43 | com.lmax
44 | disruptor
45 | test
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/reactor-aeron/src/main/java/reactor/aeron/AeronChannelUriString.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | import io.aeron.ChannelUriStringBuilder;
4 | import java.util.function.UnaryOperator;
5 |
6 | /**
7 | * Immutable wrapper of {@link ChannelUriStringBuilder}. See methods: {@link #builder()}, {@link
8 | * #asString()} and {@link #uri(UnaryOperator)}.
9 | */
10 | public final class AeronChannelUriString {
11 |
12 | /**
13 | * Source builder. {@link ChannelUriStringBuilder} is mutable, hence copy will be created each
14 | * time modification is performed on it.
15 | */
16 | private final ChannelUriStringBuilder builder =
17 | new ChannelUriStringBuilder().media("udp").reliable(true);
18 |
19 | public AeronChannelUriString() {}
20 |
21 | private AeronChannelUriString(ChannelUriStringBuilder other) {
22 | builder
23 | .endpoint(other.endpoint())
24 | .controlEndpoint(other.controlEndpoint())
25 | .mtu(other.mtu())
26 | .controlMode(other.controlMode())
27 | .sessionId(other.sessionId())
28 | .tags(other.tags())
29 | .isSessionIdTagged(other.isSessionIdTagged())
30 | .media(other.media())
31 | .reliable(other.reliable())
32 | .ttl(other.ttl())
33 | .initialTermId(other.initialTermId())
34 | .termOffset(other.termOffset())
35 | .termLength(other.termLength())
36 | .termId(other.termId())
37 | .linger(other.linger())
38 | .networkInterface(other.networkInterface());
39 | }
40 |
41 | /**
42 | * Returns copy of this builder.
43 | *
44 | * @return new {@code ChannelUriStringBuilder} instance
45 | */
46 | public ChannelUriStringBuilder builder() {
47 | return new AeronChannelUriString(builder).builder;
48 | }
49 |
50 | /**
51 | * Returns result of {@link ChannelUriStringBuilder#build()}.
52 | *
53 | * @return aeron channel uri string
54 | */
55 | public String asString() {
56 | return builder().build();
57 | }
58 |
59 | /**
60 | * Applies modifier and produces new {@code AeronChannelUriString} object.
61 | *
62 | * @param o modifier operator
63 | * @return new {@code AeronChannelUriString} object
64 | */
65 | public AeronChannelUriString uri(UnaryOperator o) {
66 | return new AeronChannelUriString(o.apply(builder()));
67 | }
68 |
69 | @Override
70 | public String toString() {
71 | return "AeronChannelUriString{" + asString() + "}";
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/reactor-aeron/src/main/java/reactor/aeron/AeronClient.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | import java.util.function.Function;
4 | import java.util.function.UnaryOperator;
5 | import org.reactivestreams.Publisher;
6 | import reactor.core.publisher.Mono;
7 |
8 | public final class AeronClient {
9 |
10 | private final AeronOptions options;
11 |
12 | private AeronClient(AeronOptions options) {
13 | this.options = options;
14 | }
15 |
16 | /**
17 | * Creates {@link AeronClient}.
18 | *
19 | * @param resources aeron resources
20 | * @return new {@code AeronClient}
21 | */
22 | public static AeronClient create(AeronResources resources) {
23 | return new AeronClient(new AeronOptions().resources(resources));
24 | }
25 |
26 | /**
27 | * Connects {@link AeronClient}.
28 | *
29 | * @return mono handle of result
30 | */
31 | public Mono extends AeronConnection> connect() {
32 | return connect(s -> s);
33 | }
34 |
35 | /**
36 | * Connects {@link AeronClient} with options.
37 | *
38 | * @param op unary opearator for performing setup of options
39 | * @return mono handle of result
40 | */
41 | public Mono extends AeronConnection> connect(UnaryOperator op) {
42 | return Mono.defer(() -> new AeronClientConnector(op.apply(options)).start());
43 | }
44 |
45 | /**
46 | * Setting up {@link AeronClient} options.
47 | *
48 | * @param op unary opearator for performing setup of options
49 | * @return new {@code AeronClient} with applied options
50 | */
51 | public AeronClient options(UnaryOperator op) {
52 | return new AeronClient(op.apply(options));
53 | }
54 |
55 | /**
56 | * Shortcut client settings.
57 | *
58 | * @param address server address
59 | * @param port server port
60 | * @param controlPort server control port
61 | * @return new {@code AeronClient} with applied options
62 | */
63 | public AeronClient options(String address, int port, int controlPort) {
64 | return new AeronClient(options)
65 | .options(
66 | opts -> {
67 | String endpoint = address + ':' + port;
68 | String controlEndpoint = address + ':' + controlPort;
69 |
70 | AeronChannelUriString inboundUri =
71 | opts.inboundUri()
72 | .uri(b -> b.controlEndpoint(controlEndpoint).controlMode("dynamic"));
73 |
74 | AeronChannelUriString outboundUri = opts.outboundUri().uri(b -> b.endpoint(endpoint));
75 |
76 | return opts //
77 | .outboundUri(outboundUri) // Pub
78 | .inboundUri(inboundUri); // Sub->MDC(sessionId)
79 | });
80 | }
81 |
82 | /**
83 | * Attach IO handler to react on connected client.
84 | *
85 | * @param handler IO handler that can dispose underlying connection when {@link Publisher}
86 | * terminates.
87 | * @return new {@code AeronClient} with handler
88 | */
89 | public AeronClient handle(Function super AeronConnection, ? extends Publisher> handler) {
90 | return new AeronClient(options.handler(handler));
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/reactor-aeron/src/main/java/reactor/aeron/AeronClientConnector.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | import io.aeron.Image;
4 | import java.time.Duration;
5 | import java.util.function.Function;
6 | import java.util.function.Supplier;
7 | import org.reactivestreams.Publisher;
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 | import reactor.core.publisher.Mono;
11 | import reactor.core.publisher.MonoProcessor;
12 |
13 | /**
14 | * Full-duplex aeron client connector. Schematically can be described as:
15 | *
16 | *
17 | * Client
18 | * serverPort->outbound->Pub(endpoint, sessionId)
19 | * serverControlPort->inbound->MDC(xor(sessionId))->Sub(control-endpoint, xor(sessionId))
20 | */
21 | final class AeronClientConnector {
22 |
23 | private static final Logger logger = LoggerFactory.getLogger(AeronClientConnector.class);
24 |
25 | /** The stream ID that the server and client use for messages. */
26 | private static final int STREAM_ID = 0xcafe0000;
27 |
28 | private final AeronOptions options;
29 | private final AeronResources resources;
30 | private final Function super AeronConnection, ? extends Publisher> handler;
31 |
32 | AeronClientConnector(AeronOptions options) {
33 | this.options = options;
34 | this.resources = options.resources();
35 | this.handler = options.handler();
36 | }
37 |
38 | /**
39 | * Creates and setting up {@link AeronConnection} object and everyting around it.
40 | *
41 | * @return mono result
42 | */
43 | Mono start() {
44 | return Mono.defer(
45 | () -> {
46 | AeronEventLoop eventLoop = resources.nextEventLoop();
47 |
48 | return tryConnect(eventLoop)
49 | .flatMap(
50 | publication -> {
51 | // inbound->MDC(xor(sessionId))->Sub(control-endpoint, xor(sessionId))
52 | int sessionId = publication.sessionId();
53 | String inboundChannel =
54 | options
55 | .inboundUri()
56 | .uri(b -> b.sessionId(sessionId ^ Integer.MAX_VALUE))
57 | .asString();
58 | logger.debug(
59 | "{}: creating client connection: {}",
60 | Integer.toHexString(sessionId),
61 | inboundChannel);
62 |
63 | // setup cleanup hook to use it onwards
64 | MonoProcessor disposeHook = MonoProcessor.create();
65 | // setup image avaiable hook
66 | MonoProcessor inboundAvailable = MonoProcessor.create();
67 |
68 | return resources
69 | .subscription(
70 | inboundChannel,
71 | STREAM_ID,
72 | eventLoop,
73 | image -> {
74 | logger.debug(
75 | "{}: created client inbound", Integer.toHexString(sessionId));
76 | inboundAvailable.onNext(image);
77 | },
78 | image -> {
79 | logger.debug(
80 | "{}: client inbound became unavaliable",
81 | Integer.toHexString(sessionId));
82 | disposeHook.onComplete();
83 | })
84 | .doOnError(
85 | th -> {
86 | logger.warn(
87 | "{}: failed to create client inbound, cause: {}",
88 | Integer.toHexString(sessionId),
89 | th.toString());
90 | // dispose outbound resource
91 | publication.dispose();
92 | })
93 | .flatMap(
94 | subscription ->
95 | inboundAvailable.flatMap(
96 | image ->
97 | newConnection(
98 | sessionId,
99 | image,
100 | publication,
101 | subscription,
102 | disposeHook,
103 | eventLoop)))
104 | .doOnSuccess(
105 | connection ->
106 | logger.debug(
107 | "{}: created client connection: {}",
108 | Integer.toHexString(sessionId),
109 | inboundChannel));
110 | });
111 | });
112 | }
113 |
114 | private Mono newConnection(
115 | int sessionId,
116 | Image image,
117 | MessagePublication publication,
118 | MessageSubscription subscription,
119 | MonoProcessor disposeHook,
120 | AeronEventLoop eventLoop) {
121 |
122 | return resources
123 | .inbound(image, subscription, eventLoop)
124 | .doOnError(
125 | ex -> {
126 | subscription.dispose();
127 | publication.dispose();
128 | })
129 | .flatMap(
130 | inbound -> {
131 | DefaultAeronOutbound outbound = new DefaultAeronOutbound(publication);
132 |
133 | DuplexAeronConnection connection =
134 | new DuplexAeronConnection(sessionId, inbound, outbound, disposeHook);
135 |
136 | return connection.start(handler).doOnError(ex -> connection.dispose());
137 | });
138 | }
139 |
140 | private Mono tryConnect(AeronEventLoop eventLoop) {
141 | return Mono.defer(
142 | () -> {
143 | int retryCount = options.connectRetryCount();
144 | Duration retryInterval = options.connectTimeout();
145 |
146 | // outbound->Pub(endpoint, sessionId)
147 | return Mono.fromCallable(this::getOutboundChannel)
148 | .flatMap(channel -> resources.publication(channel, STREAM_ID, options, eventLoop))
149 | .flatMap(mp -> mp.ensureConnected().doOnError(ex -> mp.dispose()))
150 | .retryBackoff(retryCount, Duration.ZERO, retryInterval)
151 | .doOnError(
152 | ex -> logger.warn("aeron.Publication is not connected after several retries"));
153 | });
154 | }
155 |
156 | private String getOutboundChannel() {
157 | AeronChannelUriString outboundUri = options.outboundUri();
158 | Supplier sessionIdGenerator = options.sessionIdGenerator();
159 |
160 | return sessionIdGenerator != null && outboundUri.builder().sessionId() == null
161 | ? outboundUri.uri(opts -> opts.sessionId(sessionIdGenerator.get())).asString()
162 | : outboundUri.asString();
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/reactor-aeron/src/main/java/reactor/aeron/AeronConnection.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | import org.reactivestreams.Subscription;
4 | import reactor.core.CoreSubscriber;
5 | import reactor.core.Disposable;
6 | import reactor.core.publisher.BaseSubscriber;
7 | import reactor.core.publisher.SignalType;
8 |
9 | /**
10 | * Aeron connection interface.
11 | *
12 | * Configured and established connection makes available operations such as {@link #inbound()}
13 | * for reading and {@link #outbound()} for writing data. AeronConnection interface comes with {@link
14 | * OnDisposable} and {@link #disposeSubscriber()} function for convenient resource cleanup.
15 | */
16 | public interface AeronConnection extends OnDisposable {
17 |
18 | /**
19 | * Return the {@link AeronInbound} read API from this connection. If {@link AeronConnection} has
20 | * not been configured, receive operations will be unavailable.
21 | *
22 | * @return {@code AeronInbound} instance
23 | */
24 | AeronInbound inbound();
25 |
26 | /**
27 | * Return the {@link AeronOutbound} write API from this connection. If {@link AeronConnection} has
28 | * not been configured, send operations will be unavailable.
29 | *
30 | * @return {@code AeronOutbound} instance
31 | */
32 | AeronOutbound outbound();
33 |
34 | /**
35 | * Assign a {@link Disposable} to be invoked when the channel is closed.
36 | *
37 | * @param onDispose the close event handler
38 | * @return {@code this} instance
39 | */
40 | default AeronConnection onDispose(Disposable onDispose) {
41 | onDispose().doOnTerminate(onDispose::dispose).subscribe();
42 | return this;
43 | }
44 |
45 | /**
46 | * Return a {@link CoreSubscriber} that will dispose on complete or error.
47 | *
48 | * @return a {@code CoreSubscriber} that will dispose on complete or error
49 | */
50 | default CoreSubscriber disposeSubscriber() {
51 | return new ConnectionDisposer(this);
52 | }
53 |
54 | final class ConnectionDisposer extends BaseSubscriber {
55 | final OnDisposable onDisposable;
56 |
57 | public ConnectionDisposer(OnDisposable onDisposable) {
58 | this.onDisposable = onDisposable;
59 | }
60 |
61 | @Override
62 | protected void hookOnSubscribe(Subscription subscription) {
63 | request(Long.MAX_VALUE);
64 | onDisposable.onDispose(this);
65 | }
66 |
67 | @Override
68 | protected void hookFinally(SignalType type) {
69 | if (type != SignalType.CANCEL) {
70 | onDisposable.dispose();
71 | }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/reactor-aeron/src/main/java/reactor/aeron/AeronEventLoopGroup.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | import java.util.Arrays;
4 | import java.util.concurrent.atomic.AtomicInteger;
5 | import java.util.function.Supplier;
6 | import org.agrona.concurrent.IdleStrategy;
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 | import reactor.core.publisher.Mono;
10 | import reactor.core.publisher.MonoProcessor;
11 |
12 | /**
13 | * Wrapper around the {@link AeronEventLoop} where the actual logic is performed. Manages grouping
14 | * of multiple instances of {@link AeronEventLoop}: round-robin iteration and grouped disposal.
15 | */
16 | class AeronEventLoopGroup implements OnDisposable {
17 |
18 | private static final Logger logger = LoggerFactory.getLogger(AeronEventLoopGroup.class);
19 |
20 | private final int id = System.identityHashCode(this);
21 | private final AeronEventLoop[] eventLoops;
22 | private final AtomicInteger idx = new AtomicInteger();
23 |
24 | private final MonoProcessor dispose = MonoProcessor.create();
25 | private final MonoProcessor onDispose = MonoProcessor.create();
26 |
27 | /**
28 | * Constructor.
29 | *
30 | * @param name thread name
31 | * @param numOfWorkers number of {@link AeronEventLoop} instances in the group
32 | * @param workerIdleStrategySupplier factory for {@link IdleStrategy} instances
33 | */
34 | AeronEventLoopGroup(
35 | String name, int numOfWorkers, Supplier workerIdleStrategySupplier) {
36 | this.eventLoops = new AeronEventLoop[numOfWorkers];
37 | for (int i = 0; i < numOfWorkers; i++) {
38 | eventLoops[i] = new AeronEventLoop(name, i, id, workerIdleStrategySupplier.get());
39 | }
40 |
41 | dispose
42 | .then(doDispose())
43 | .doFinally(s -> onDispose.onComplete())
44 | .subscribe(
45 | null,
46 | th -> logger.warn("{} failed on doDispose(): {}", this, th.toString()),
47 | () -> logger.debug("Disposed {}", this));
48 | }
49 |
50 | /**
51 | * Get instance of worker from the group (round-robin iteration).
52 | *
53 | * @return instance of worker in the group
54 | */
55 | AeronEventLoop next() {
56 | return eventLoops[Math.abs(idx.getAndIncrement() % eventLoops.length)];
57 | }
58 |
59 | AeronEventLoop first() {
60 | return eventLoops[0];
61 | }
62 |
63 | @Override
64 | public void dispose() {
65 | dispose.onComplete();
66 | }
67 |
68 | @Override
69 | public boolean isDisposed() {
70 | return onDispose.isDisposed();
71 | }
72 |
73 | @Override
74 | public Mono onDispose() {
75 | return onDispose;
76 | }
77 |
78 | private Mono doDispose() {
79 | return Mono.defer(
80 | () -> {
81 | logger.debug("Disposing {}", this);
82 | return Mono.whenDelayError(
83 | Arrays.stream(eventLoops)
84 | .peek(AeronEventLoop::dispose)
85 | .map(AeronEventLoop::onDispose)
86 | .toArray(Mono>[]::new));
87 | });
88 | }
89 |
90 | @Override
91 | public String toString() {
92 | return "AeronEventLoopGroup" + id;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/reactor-aeron/src/main/java/reactor/aeron/AeronExceptions.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | class AeronExceptions {
4 |
5 | private AeronExceptions() {
6 | // Do not instantiate
7 | }
8 |
9 | static RuntimeException failWithCancel(String message) {
10 | return new AeronCancelException(message);
11 | }
12 |
13 | static RuntimeException failWithEventLoopUnavailable() {
14 | return new AeronEventLoopException("AeronEventLoop is unavailable");
15 | }
16 |
17 | static RuntimeException failWithPublication(String message) {
18 | return new AeronPublicationException(message);
19 | }
20 |
21 | static RuntimeException failWithResourceDisposal(String resourceName) {
22 | return new AeronResourceDisposalException(
23 | "Can only close resource (" + resourceName + ") from within event loop");
24 | }
25 |
26 | static class AeronCancelException extends RuntimeException {
27 |
28 | private static final long serialVersionUID = 1L;
29 |
30 | AeronCancelException(String message) {
31 | super(message);
32 | }
33 |
34 | @Override
35 | public synchronized Throwable fillInStackTrace() {
36 | return this;
37 | }
38 | }
39 |
40 | static class AeronEventLoopException extends RuntimeException {
41 |
42 | private static final long serialVersionUID = 1L;
43 |
44 | AeronEventLoopException(String message) {
45 | super(message);
46 | }
47 |
48 | @Override
49 | public synchronized Throwable fillInStackTrace() {
50 | return this;
51 | }
52 | }
53 |
54 | static class AeronPublicationException extends RuntimeException {
55 |
56 | private static final long serialVersionUID = 1L;
57 |
58 | AeronPublicationException(String message) {
59 | super(message);
60 | }
61 |
62 | @Override
63 | public synchronized Throwable fillInStackTrace() {
64 | return this;
65 | }
66 | }
67 |
68 | static class AeronResourceDisposalException extends IllegalStateException {
69 |
70 | private static final long serialVersionUID = 1L;
71 |
72 | AeronResourceDisposalException(String message) {
73 | super(message);
74 | }
75 |
76 | @Override
77 | public synchronized Throwable fillInStackTrace() {
78 | return this;
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/reactor-aeron/src/main/java/reactor/aeron/AeronInbound.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | public interface AeronInbound {
4 |
5 | DirectBufferFlux receive();
6 | }
7 |
--------------------------------------------------------------------------------
/reactor-aeron/src/main/java/reactor/aeron/AeronOptions.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | import java.time.Duration;
4 | import java.util.function.Consumer;
5 | import java.util.function.Function;
6 | import java.util.function.Supplier;
7 | import org.reactivestreams.Publisher;
8 |
9 | /**
10 | * Immutable wrapper around options for full-duplex aeron connection between client and
11 | * server. Note, it's mandatory to set {@code resources}, {@code inboundUri} and {@code
12 | * outboundUri}, everything rest may come with defaults.
13 | */
14 | public final class AeronOptions {
15 |
16 | private AeronResources resources;
17 | private Function super AeronConnection, ? extends Publisher> handler;
18 | private AeronChannelUriString inboundUri = new AeronChannelUriString();
19 | private AeronChannelUriString outboundUri = new AeronChannelUriString();
20 | private Duration connectTimeout = Duration.ofSeconds(5);
21 | private int connectRetryCount = 3;
22 | private Duration backpressureTimeout = Duration.ofSeconds(5);
23 | private Duration adminActionTimeout = Duration.ofSeconds(5);
24 | private Supplier sessionIdGenerator = new SecureRandomSessionIdGenerator();
25 |
26 | public AeronOptions() {}
27 |
28 | AeronOptions(AeronOptions other) {
29 | this.resources = other.resources;
30 | this.handler = other.handler;
31 | this.inboundUri = other.inboundUri;
32 | this.outboundUri = other.outboundUri;
33 | this.connectTimeout = other.connectTimeout;
34 | this.backpressureTimeout = other.backpressureTimeout;
35 | this.adminActionTimeout = other.adminActionTimeout;
36 | this.sessionIdGenerator = other.sessionIdGenerator;
37 | this.connectRetryCount = other.connectRetryCount;
38 | }
39 |
40 | public AeronResources resources() {
41 | return resources;
42 | }
43 |
44 | public AeronOptions resources(AeronResources resources) {
45 | return set(s -> s.resources = resources);
46 | }
47 |
48 | public Function super AeronConnection, ? extends Publisher> handler() {
49 | return handler;
50 | }
51 |
52 | public AeronOptions handler(
53 | Function super AeronConnection, ? extends Publisher> handler) {
54 | return set(s -> s.handler = handler);
55 | }
56 |
57 | public AeronChannelUriString inboundUri() {
58 | return inboundUri;
59 | }
60 |
61 | public AeronOptions inboundUri(AeronChannelUriString inboundUri) {
62 | return set(s -> s.inboundUri = inboundUri);
63 | }
64 |
65 | public AeronChannelUriString outboundUri() {
66 | return outboundUri;
67 | }
68 |
69 | public AeronOptions outboundUri(AeronChannelUriString outboundUri) {
70 | return set(s -> s.outboundUri = outboundUri);
71 | }
72 |
73 | public Duration connectTimeout() {
74 | return connectTimeout;
75 | }
76 |
77 | public AeronOptions connectTimeout(Duration connectTimeout) {
78 | return set(s -> s.connectTimeout = connectTimeout);
79 | }
80 |
81 | public int connectRetryCount() {
82 | return connectRetryCount;
83 | }
84 |
85 | public AeronOptions connectRetryCount(int connectRetryCount) {
86 | return set(s -> s.connectRetryCount = connectRetryCount);
87 | }
88 |
89 | public Duration backpressureTimeout() {
90 | return backpressureTimeout;
91 | }
92 |
93 | public AeronOptions backpressureTimeout(Duration backpressureTimeout) {
94 | return set(s -> s.backpressureTimeout = backpressureTimeout);
95 | }
96 |
97 | public Duration adminActionTimeout() {
98 | return adminActionTimeout;
99 | }
100 |
101 | public AeronOptions adminActionTimeout(Duration adminActionTimeout) {
102 | return set(s -> s.adminActionTimeout = adminActionTimeout);
103 | }
104 |
105 | public Supplier sessionIdGenerator() {
106 | return sessionIdGenerator;
107 | }
108 |
109 | public AeronOptions sessionIdGenerator(Supplier sessionIdGenerator) {
110 | return set(s -> s.sessionIdGenerator = sessionIdGenerator);
111 | }
112 |
113 | private AeronOptions set(Consumer c) {
114 | AeronOptions s = new AeronOptions(this);
115 | c.accept(s);
116 | return s;
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/reactor-aeron/src/main/java/reactor/aeron/AeronOutbound.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | import java.nio.ByteBuffer;
4 | import org.agrona.DirectBuffer;
5 | import org.reactivestreams.Publisher;
6 | import org.reactivestreams.Subscriber;
7 | import reactor.core.publisher.Mono;
8 |
9 | public interface AeronOutbound extends Publisher {
10 |
11 | /**
12 | * Send data to the peer, listen for any error on write and close on terminal signal
13 | * (complete|error).
14 | *
15 | * @param abstract buffer type (comes from client code)
16 | * @param dataStream the dataStream publishing items to send
17 | * @param bufferHandler abstract buffer handler for {@link DirectBuffer} buffer
18 | * @return A new {@link AeronOutbound} to append further send. It will emit a complete signal upon
19 | * successful sequence write or an error during write.
20 | */
21 | AeronOutbound send(Publisher dataStream, DirectBufferHandler super B> bufferHandler);
22 |
23 | /**
24 | * Send data to the peer, listen for any error on write and close on terminal signal
25 | * (complete|error).
26 | *
27 | * @param dataStream the dataStream publishing items to send
28 | * @return A new {@link AeronOutbound} to append further send. It will emit a complete signal upon
29 | * successful sequence write or an error during write.
30 | */
31 | AeronOutbound send(Publisher dataStream);
32 |
33 | /**
34 | * Send data to the peer, listen for any error on write and close on terminal signal
35 | * (complete|error).
36 | *
37 | * @param dataStream the dataStream publishing items to send
38 | * @return A new {@link AeronOutbound} to append further send. It will emit a complete signal upon
39 | * successful sequence write or an error during write.
40 | */
41 | AeronOutbound sendBytes(Publisher dataStream);
42 |
43 | /**
44 | * Send data to the peer, listen for any error on write and close on terminal signal
45 | * (complete|error).
46 | *
47 | * @param dataStream the dataStream publishing items to send
48 | * @return A new {@link AeronOutbound} to append further send. It will emit a complete signal upon
49 | * successful sequence write or an error during write.
50 | */
51 | AeronOutbound sendString(Publisher dataStream);
52 |
53 | /**
54 | * Send data to the peer, listen for any error on write and close on terminal signal
55 | * (complete|error).
56 | *
57 | * @param dataStream the dataStream publishing items to send
58 | * @return A new {@link AeronOutbound} to append further send. It will emit a complete signal upon
59 | * successful sequence write or an error during write.
60 | */
61 | AeronOutbound sendBuffer(Publisher dataStream);
62 |
63 | /**
64 | * Obtain a {@link Mono} of pending outbound(s) write completion.
65 | *
66 | * @return a {@link Mono} of pending outbound(s) write completion
67 | */
68 | default Mono then() {
69 | return Mono.empty();
70 | }
71 |
72 | /**
73 | * Append a {@link Publisher} task such as a Mono and return a new {@link AeronOutbound} to
74 | * sequence further send.
75 | *
76 | * @param other the {@link Publisher} to subscribe to when this pending outbound {@link #then} is
77 | * complete;
78 | * @return a new {@link AeronOutbound}
79 | */
80 | default AeronOutbound then(Publisher other) {
81 | return new AeronOutboundThen(this, other);
82 | }
83 |
84 | /**
85 | * Subscribe a {@code Void} subscriber to this outbound and trigger all eventual parent outbound
86 | * send.
87 | *
88 | * @param s the {@link Subscriber} to listen for send sequence completion/failure
89 | */
90 | @Override
91 | default void subscribe(Subscriber super Void> s) {
92 | then().subscribe(s);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/reactor-aeron/src/main/java/reactor/aeron/AeronOutboundThen.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | import java.nio.ByteBuffer;
4 | import org.agrona.DirectBuffer;
5 | import org.reactivestreams.Publisher;
6 | import reactor.core.publisher.Mono;
7 |
8 | final class AeronOutboundThen implements AeronOutbound {
9 |
10 | private final Mono thenMono;
11 | private final AeronOutbound source;
12 |
13 | AeronOutboundThen(AeronOutbound source, Publisher thenPublisher) {
14 | Mono parentMono = source.then();
15 | this.source = source;
16 | if (parentMono == Mono.empty()) {
17 | this.thenMono = Mono.from(thenPublisher);
18 | } else {
19 | this.thenMono = parentMono.thenEmpty(thenPublisher);
20 | }
21 | }
22 |
23 | @Override
24 | public AeronOutbound send(
25 | Publisher dataStream, DirectBufferHandler super B> bufferHandler) {
26 | return source.send(dataStream, bufferHandler);
27 | }
28 |
29 | @Override
30 | public AeronOutbound send(Publisher dataStream) {
31 | return source.send(dataStream);
32 | }
33 |
34 | @Override
35 | public AeronOutbound sendBytes(Publisher dataStream) {
36 | return source.sendBytes(dataStream);
37 | }
38 |
39 | @Override
40 | public AeronOutbound sendString(Publisher dataStream) {
41 | return source.sendString(dataStream);
42 | }
43 |
44 | @Override
45 | public AeronOutbound sendBuffer(Publisher dataStream) {
46 | return source.sendBuffer(dataStream);
47 | }
48 |
49 | @Override
50 | public Mono then() {
51 | return thenMono;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/reactor-aeron/src/main/java/reactor/aeron/AeronResource.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | interface AeronResource {
4 |
5 | void close();
6 | }
7 |
--------------------------------------------------------------------------------
/reactor-aeron/src/main/java/reactor/aeron/AeronServer.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | import java.util.function.Function;
4 | import java.util.function.UnaryOperator;
5 | import org.reactivestreams.Publisher;
6 | import reactor.core.publisher.Mono;
7 |
8 | public final class AeronServer {
9 |
10 | private final AeronOptions options;
11 |
12 | private AeronServer(AeronOptions options) {
13 | this.options = options;
14 | }
15 |
16 | /**
17 | * Creates {@link AeronServer}.
18 | *
19 | * @param resources aeron resources
20 | * @return new {@code AeronServer}
21 | */
22 | public static AeronServer create(AeronResources resources) {
23 | return new AeronServer(new AeronOptions().resources(resources));
24 | }
25 |
26 | /**
27 | * Binds {@link AeronServer}.
28 | *
29 | * @return mono handle of result
30 | */
31 | public Mono extends OnDisposable> bind() {
32 | return bind(s -> s);
33 | }
34 |
35 | /**
36 | * Binds {@link AeronServer} with options.
37 | *
38 | * @param op unary opearator for performing setup of options
39 | * @return mono handle of result
40 | */
41 | public Mono extends OnDisposable> bind(UnaryOperator op) {
42 | return Mono.defer(() -> new AeronServerHandler(op.apply(options)).start());
43 | }
44 |
45 | /**
46 | * Setting up {@link AeronServer} options.
47 | *
48 | * @param op unary opearator for performing setup of options
49 | * @return new {@code AeronServer} with applied options
50 | */
51 | public AeronServer options(UnaryOperator op) {
52 | return new AeronServer(op.apply(options));
53 | }
54 |
55 | /**
56 | * Shortcut server settings.
57 | *
58 | * Combination {@code address} + {@code port} shall create {@code inbound} server side entity,
59 | * by turn combination {@code address} + {@code controlPort} will result in {@code outbound}
60 | * server side component.
61 | *
62 | * @param address server address
63 | * @param port server port
64 | * @param controlPort server control port
65 | * @return new {@code AeronServer} with applied options
66 | */
67 | public AeronServer options(String address, int port, int controlPort) {
68 | return new AeronServer(options)
69 | .options(
70 | opts -> {
71 | String endpoint = address + ':' + port;
72 | String controlEndpoint = address + ':' + controlPort;
73 |
74 | AeronChannelUriString inboundUri = opts.inboundUri().uri(b -> b.endpoint(endpoint));
75 |
76 | AeronChannelUriString outboundUri =
77 | opts.outboundUri().uri(b -> b.controlEndpoint(controlEndpoint));
78 |
79 | return opts //
80 | .inboundUri(inboundUri) // Sub
81 | .outboundUri(outboundUri); // Pub->MDC(sessionId)
82 | });
83 | }
84 |
85 | /**
86 | * Attach IO handler to react on connected client.
87 | *
88 | * @param handler IO handler that can dispose underlying connection when {@link Publisher}
89 | * terminates.
90 | * @return new {@code AeronServer} with handler
91 | */
92 | public AeronServer handle(Function super AeronConnection, ? extends Publisher> handler) {
93 | return new AeronServer(options.handler(handler));
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/reactor-aeron/src/main/java/reactor/aeron/AeronServerHandler.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | import io.aeron.Image;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 | import java.util.Map;
7 | import java.util.Optional;
8 | import java.util.concurrent.ConcurrentHashMap;
9 | import java.util.function.Function;
10 | import org.reactivestreams.Publisher;
11 | import org.slf4j.Logger;
12 | import org.slf4j.LoggerFactory;
13 | import reactor.core.publisher.Mono;
14 | import reactor.core.publisher.MonoProcessor;
15 |
16 | /**
17 | * Full-duplex aeron server handler. Schematically can be described as:
18 | *
19 | *
20 | * Server
21 | * serverPort->inbound->Sub(endpoint, acceptor[onImageAvailable, onImageUnavailbe])
22 | * + onImageAvailable(Image)
23 | * sessionId->inbound->EmitterPocessor
24 | * serverControlPort->outbound->MDC(xor(sessionId))->Pub(control-endpoint, xor(sessionId))
25 | *
26 | */
27 | final class AeronServerHandler implements OnDisposable {
28 |
29 | private static final Logger logger = LoggerFactory.getLogger(AeronServerHandler.class);
30 |
31 | /** The stream ID that the server and client use for messages. */
32 | private static final int STREAM_ID = 0xcafe0000;
33 |
34 | private final AeronOptions options;
35 | private final AeronResources resources;
36 | private final Function super AeronConnection, ? extends Publisher> handler;
37 |
38 | private volatile MessageSubscription acceptorSubscription; // server acceptor subscription
39 |
40 | private final Map> disposeHooks = new ConcurrentHashMap<>();
41 |
42 | private final MonoProcessor dispose = MonoProcessor.create();
43 | private final MonoProcessor onDispose = MonoProcessor.create();
44 |
45 | AeronServerHandler(AeronOptions options) {
46 | this.options = options;
47 | this.resources = options.resources();
48 | this.handler = options.handler();
49 |
50 | dispose
51 | .then(doDispose())
52 | .doFinally(s -> onDispose.onComplete())
53 | .subscribe(
54 | null,
55 | th -> logger.warn("{} failed on doDispose(): {}", this, th.toString()),
56 | () -> logger.debug("Disposed {}", this));
57 | }
58 |
59 | Mono start() {
60 | return Mono.defer(
61 | () -> {
62 | // Sub(endpoint{address:serverPort})
63 | String acceptorChannel = options.inboundUri().asString();
64 |
65 | logger.debug("Starting {} on: {}", this, acceptorChannel);
66 |
67 | return resources
68 | .subscription(
69 | acceptorChannel,
70 | STREAM_ID,
71 | resources.firstEventLoop(),
72 | this::onImageAvailable,
73 | this::onImageUnavailable)
74 | .doOnSuccess(s -> this.acceptorSubscription = s)
75 | .thenReturn(this)
76 | .doOnSuccess(handler -> logger.debug("Started {} on: {}", this, acceptorChannel))
77 | .doOnError(
78 | ex -> {
79 | logger.error("Failed to start {} on: {}", this, acceptorChannel);
80 | dispose();
81 | });
82 | });
83 | }
84 |
85 | /**
86 | * Setting up new {@link AeronConnection} identified by {@link Image#sessionId()}. Specifically
87 | * creates Multi Destination Cast (MDC) message publication (aeron {@link io.aeron.Publication}
88 | * underneath) with control-endpoint, control-mode and XOR-ed image sessionId. Essentially creates
89 | * server-side-individual-MDC.
90 | *
91 | * @param image source image
92 | */
93 | private void onImageAvailable(Image image) {
94 | // Pub(control-endpoint{address:serverControlPort}, xor(sessionId))->MDC(xor(sessionId))
95 | int sessionId = image.sessionId();
96 | String outboundChannel =
97 | options.outboundUri().uri(b -> b.sessionId(sessionId ^ Integer.MAX_VALUE)).asString();
98 |
99 | logger.debug(
100 | "{}: creating server connection: {}", Integer.toHexString(sessionId), outboundChannel);
101 |
102 | AeronEventLoop eventLoop = resources.nextEventLoop();
103 |
104 | resources
105 | .publication(outboundChannel, STREAM_ID, options, eventLoop)
106 | .flatMap(
107 | publication ->
108 | resources
109 | .inbound(image, null /*subscription*/, eventLoop)
110 | .doOnError(ex -> publication.dispose())
111 | .flatMap(inbound -> newConnection(sessionId, publication, inbound)))
112 | .doOnSuccess(
113 | connection ->
114 | logger.debug(
115 | "{}: created server connection: {}",
116 | Integer.toHexString(sessionId),
117 | outboundChannel))
118 | .subscribe(
119 | null,
120 | ex ->
121 | logger.warn(
122 | "{}: failed to create server outbound, cause: {}",
123 | Integer.toHexString(sessionId),
124 | ex.toString()));
125 | }
126 |
127 | private Mono extends AeronConnection> newConnection(
128 | int sessionId, MessagePublication publication, DefaultAeronInbound inbound) {
129 | // setup cleanup hook to use it onwards
130 | MonoProcessor disposeHook = MonoProcessor.create();
131 |
132 | disposeHooks.put(sessionId, disposeHook);
133 |
134 | DefaultAeronOutbound outbound = new DefaultAeronOutbound(publication);
135 |
136 | DuplexAeronConnection connection =
137 | new DuplexAeronConnection(sessionId, inbound, outbound, disposeHook);
138 |
139 | return connection
140 | .start(handler)
141 | .doOnError(
142 | ex -> {
143 | connection.dispose();
144 | disposeHooks.remove(sessionId);
145 | });
146 | }
147 |
148 | /**
149 | * Disposes {@link AeronConnection} corresponding to {@link Image#sessionId()}.
150 | *
151 | * @param image source image
152 | */
153 | private void onImageUnavailable(Image image) {
154 | int sessionId = image.sessionId();
155 | MonoProcessor disposeHook = disposeHooks.remove(sessionId);
156 | if (disposeHook != null) {
157 | logger.debug("{}: server inbound became unavailable", Integer.toHexString(sessionId));
158 | disposeHook.onComplete();
159 | }
160 | }
161 |
162 | @Override
163 | public void dispose() {
164 | dispose.onComplete();
165 | }
166 |
167 | @Override
168 | public boolean isDisposed() {
169 | return onDispose.isDisposed();
170 | }
171 |
172 | @Override
173 | public Mono onDispose() {
174 | return onDispose;
175 | }
176 |
177 | private Mono doDispose() {
178 | return Mono.defer(
179 | () -> {
180 | logger.debug("Disposing {}", this);
181 | List> monos = new ArrayList<>();
182 |
183 | // dispose server acceptor subscription
184 | monos.add(
185 | Optional.ofNullable(acceptorSubscription)
186 | .map(s -> Mono.fromRunnable(s::dispose).then(s.onDispose()))
187 | .orElse(Mono.empty()));
188 |
189 | // dispose all existing connections
190 | disposeHooks.values().stream().peek(MonoProcessor::onComplete).forEach(monos::add);
191 |
192 | return Mono.whenDelayError(monos).doFinally(s -> disposeHooks.clear());
193 | });
194 | }
195 |
196 | @Override
197 | public String toString() {
198 | return "AeronServerHandler" + Integer.toHexString(System.identityHashCode(this));
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/reactor-aeron/src/main/java/reactor/aeron/DefaultAeronInbound.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | import io.aeron.Image;
4 | import io.aeron.ImageFragmentAssembler;
5 | import io.aeron.logbuffer.FragmentHandler;
6 | import io.aeron.logbuffer.Header;
7 | import java.util.concurrent.atomic.AtomicLongFieldUpdater;
8 | import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
9 | import org.agrona.DirectBuffer;
10 | import org.agrona.concurrent.UnsafeBuffer;
11 | import org.reactivestreams.Subscription;
12 | import org.slf4j.Logger;
13 | import org.slf4j.LoggerFactory;
14 | import reactor.core.CoreSubscriber;
15 | import reactor.core.Exceptions;
16 | import reactor.core.publisher.Flux;
17 | import reactor.core.publisher.Operators;
18 |
19 | final class DefaultAeronInbound implements AeronInbound, AeronResource {
20 |
21 | private static final Logger logger = LoggerFactory.getLogger(DefaultAeronInbound.class);
22 |
23 | private static final AtomicLongFieldUpdater REQUESTED =
24 | AtomicLongFieldUpdater.newUpdater(DefaultAeronInbound.class, "requested");
25 |
26 | private static final AtomicReferenceFieldUpdater
27 | DESTINATION_SUBSCRIBER =
28 | AtomicReferenceFieldUpdater.newUpdater(
29 | DefaultAeronInbound.class, CoreSubscriber.class, "destinationSubscriber");
30 |
31 | private static final CoreSubscriber super DirectBuffer> CANCELLED_SUBSCRIBER =
32 | new CancelledSubscriber();
33 |
34 | private final int fragmentLimit;
35 | private final Image image;
36 | private final AeronEventLoop eventLoop;
37 | private final FluxReceive inbound = new FluxReceive();
38 | private final FragmentHandler fragmentHandler =
39 | new ImageFragmentAssembler(new FragmentHandlerImpl());
40 | private final MessageSubscription subscription;
41 |
42 | private volatile long requested;
43 | private volatile boolean fastpath;
44 | private long produced;
45 | private volatile CoreSubscriber super DirectBuffer> destinationSubscriber;
46 |
47 | /**
48 | * Constructor.
49 | *
50 | * @param image image
51 | * @param eventLoop event loop
52 | * @param subscription subscription
53 | * @param fragmentLimit fragment limit
54 | */
55 | DefaultAeronInbound(
56 | Image image, AeronEventLoop eventLoop, MessageSubscription subscription, int fragmentLimit) {
57 | this.image = image;
58 | this.eventLoop = eventLoop;
59 | this.subscription = subscription;
60 | this.fragmentLimit = fragmentLimit;
61 | }
62 |
63 | @Override
64 | public DirectBufferFlux receive() {
65 | return new DirectBufferFlux(inbound);
66 | }
67 |
68 | int poll() {
69 | if (destinationSubscriber == CANCELLED_SUBSCRIBER) {
70 | return 0;
71 | }
72 | if (fastpath) {
73 | return image.poll(fragmentHandler, fragmentLimit);
74 | }
75 | int r = (int) Math.min(requested, fragmentLimit);
76 | int fragments = 0;
77 | if (r > 0) {
78 | fragments = image.poll(fragmentHandler, r);
79 | if (produced > 0) {
80 | Operators.produced(REQUESTED, this, produced);
81 | produced = 0;
82 | }
83 | }
84 | return fragments;
85 | }
86 |
87 | @Override
88 | public void close() {
89 | if (!eventLoop.inEventLoop()) {
90 | throw AeronExceptions.failWithResourceDisposal("aeron inbound");
91 | }
92 | inbound.cancel();
93 | logger.debug("Cancelled inbound");
94 | }
95 |
96 | void dispose() {
97 | eventLoop
98 | .dispose(this)
99 | .subscribe(
100 | null,
101 | th -> {
102 | // no-op
103 | });
104 | if (subscription != null) {
105 | subscription.dispose();
106 | }
107 | }
108 |
109 | private class FragmentHandlerImpl implements FragmentHandler {
110 |
111 | @Override
112 | public void onFragment(DirectBuffer buffer, int offset, int length, Header header) {
113 | produced++;
114 |
115 | CoreSubscriber super DirectBuffer> destination =
116 | DefaultAeronInbound.this.destinationSubscriber;
117 |
118 | destination.onNext(new UnsafeBuffer(buffer, offset, length));
119 | }
120 | }
121 |
122 | private class FluxReceive extends Flux implements Subscription {
123 |
124 | @Override
125 | public void request(long n) {
126 | if (fastpath) {
127 | return;
128 | }
129 | if (n == Long.MAX_VALUE) {
130 | fastpath = true;
131 | requested = Long.MAX_VALUE;
132 | return;
133 | }
134 | Operators.addCap(REQUESTED, DefaultAeronInbound.this, n);
135 | }
136 |
137 | @Override
138 | public void cancel() {
139 | CoreSubscriber destination =
140 | DESTINATION_SUBSCRIBER.getAndSet(DefaultAeronInbound.this, CANCELLED_SUBSCRIBER);
141 | if (destination != null) {
142 | destination.onComplete();
143 | }
144 | logger.debug(
145 | "Destination subscriber on aeron inbound has been cancelled, session id {}",
146 | Integer.toHexString(image.sessionId()));
147 | }
148 |
149 | @Override
150 | public void subscribe(CoreSubscriber super DirectBuffer> destinationSubscriber) {
151 | boolean result =
152 | DESTINATION_SUBSCRIBER.compareAndSet(
153 | DefaultAeronInbound.this, null, destinationSubscriber);
154 | if (result) {
155 | destinationSubscriber.onSubscribe(this);
156 | } else {
157 | // only subscriber is allowed on receive()
158 | Operators.error(destinationSubscriber, Exceptions.duplicateOnSubscribeException());
159 | }
160 | }
161 | }
162 |
163 | private static class CancelledSubscriber implements CoreSubscriber {
164 |
165 | @Override
166 | public void onSubscribe(Subscription s) {
167 | // no-op
168 | }
169 |
170 | @Override
171 | public void onNext(DirectBuffer directBuffer) {
172 | logger.warn(
173 | "Received buffer(len={}) which will be dropped immediately due cancelled aeron inbound",
174 | directBuffer.capacity());
175 | }
176 |
177 | @Override
178 | public void onError(Throwable t) {
179 | // no-op
180 | }
181 |
182 | @Override
183 | public void onComplete() {
184 | // no-op
185 | }
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/reactor-aeron/src/main/java/reactor/aeron/DefaultAeronOutbound.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | import java.nio.ByteBuffer;
4 | import java.nio.charset.StandardCharsets;
5 | import org.agrona.DirectBuffer;
6 | import org.agrona.concurrent.UnsafeBuffer;
7 | import org.reactivestreams.Publisher;
8 | import reactor.core.publisher.Flux;
9 | import reactor.core.publisher.Mono;
10 |
11 | final class DefaultAeronOutbound implements AeronOutbound {
12 |
13 | private final MessagePublication publication;
14 |
15 | /**
16 | * Constructor.
17 | *
18 | * @param publication message publication
19 | */
20 | DefaultAeronOutbound(MessagePublication publication) {
21 | this.publication = publication;
22 | }
23 |
24 | @Override
25 | public AeronOutbound send(
26 | Publisher dataStream, DirectBufferHandler super B> bufferHandler) {
27 | return then(publication.publish(dataStream, bufferHandler));
28 | }
29 |
30 | @Override
31 | public AeronOutbound send(Publisher dataStream) {
32 | return send(dataStream, DirectBufferHandlerImpl.DEFAULT_INSTANCE);
33 | }
34 |
35 | @Override
36 | public AeronOutbound sendBytes(Publisher dataStream) {
37 | if (dataStream instanceof Flux) {
38 | return send(((Flux) dataStream).map(UnsafeBuffer::new));
39 | }
40 | return send(((Mono) dataStream).map(UnsafeBuffer::new));
41 | }
42 |
43 | @Override
44 | public AeronOutbound sendString(Publisher dataStream) {
45 | if (dataStream instanceof Flux) {
46 | return send(
47 | ((Flux) dataStream)
48 | .map(s -> s.getBytes(StandardCharsets.UTF_8))
49 | .map(UnsafeBuffer::new));
50 | }
51 | return send(
52 | ((Mono) dataStream)
53 | .map(s -> s.getBytes(StandardCharsets.UTF_8))
54 | .map(UnsafeBuffer::new));
55 | }
56 |
57 | @Override
58 | public AeronOutbound sendBuffer(Publisher dataStream) {
59 | if (dataStream instanceof Flux) {
60 | return send(((Flux) dataStream).map(UnsafeBuffer::new));
61 | }
62 | return send(((Mono) dataStream).map(UnsafeBuffer::new));
63 | }
64 |
65 | void dispose() {
66 | publication.dispose();
67 | }
68 |
69 | /**
70 | * Default implementation of {@link DirectBufferHandler} with aeron buffer type {@link
71 | * DirectBuffer}. Function {@link #dispose()} does nothing.
72 | */
73 | private static class DirectBufferHandlerImpl implements DirectBufferHandler {
74 |
75 | private static final DirectBufferHandlerImpl DEFAULT_INSTANCE = new DirectBufferHandlerImpl();
76 |
77 | @Override
78 | public int estimateLength(DirectBuffer buffer) {
79 | return buffer.capacity();
80 | }
81 |
82 | @Override
83 | public DirectBuffer map(DirectBuffer buffer, int length) {
84 | return buffer;
85 | }
86 |
87 | @Override
88 | public void dispose(DirectBuffer buffer) {
89 | // no-op
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/reactor-aeron/src/main/java/reactor/aeron/DirectBufferFlux.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | import java.nio.charset.StandardCharsets;
4 | import org.agrona.DirectBuffer;
5 | import reactor.core.CoreSubscriber;
6 | import reactor.core.publisher.Flux;
7 | import reactor.core.publisher.FluxOperator;
8 |
9 | public final class DirectBufferFlux extends FluxOperator {
10 |
11 | public DirectBufferFlux(Flux extends DirectBuffer> source) {
12 | super(source);
13 | }
14 |
15 | @Override
16 | public void subscribe(CoreSubscriber super DirectBuffer> s) {
17 | source.subscribe(s);
18 | }
19 |
20 | /**
21 | * Applies transformation {@link DirectBuffer} to {@code String}.
22 | *
23 | * @return {@code Flux} instance
24 | */
25 | public Flux asString() {
26 | return map(
27 | buffer -> {
28 | byte[] bytes = new byte[buffer.capacity()];
29 | buffer.getBytes(0, bytes);
30 | return new String(bytes, StandardCharsets.UTF_8);
31 | });
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/reactor-aeron/src/main/java/reactor/aeron/DirectBufferHandler.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | import org.agrona.DirectBuffer;
4 |
5 | public interface DirectBufferHandler {
6 |
7 | int estimateLength(B buffer);
8 |
9 | DirectBuffer map(B buffer, int length);
10 |
11 | void dispose(B buffer);
12 | }
13 |
--------------------------------------------------------------------------------
/reactor-aeron/src/main/java/reactor/aeron/DuplexAeronConnection.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | import java.util.function.Function;
4 | import org.reactivestreams.Publisher;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import reactor.core.publisher.Mono;
8 | import reactor.core.publisher.MonoProcessor;
9 |
10 | /**
11 | * Full-duplex aeron connection. Bound to certain {@code sessionId}. Implements {@link
12 | * OnDisposable} for convenient resource cleanup.
13 | */
14 | final class DuplexAeronConnection implements AeronConnection {
15 |
16 | private final Logger logger = LoggerFactory.getLogger(DuplexAeronConnection.class);
17 |
18 | private final int sessionId;
19 |
20 | private final DefaultAeronInbound inbound;
21 | private final DefaultAeronOutbound outbound;
22 |
23 | private final MonoProcessor dispose = MonoProcessor.create();
24 | private final MonoProcessor onDispose = MonoProcessor.create();
25 |
26 | /**
27 | * Constructor.
28 | *
29 | * @param sessionId session id
30 | * @param inbound inbound
31 | * @param outbound outbound
32 | * @param disposeHook shutdown hook
33 | */
34 | DuplexAeronConnection(
35 | int sessionId,
36 | DefaultAeronInbound inbound,
37 | DefaultAeronOutbound outbound,
38 | MonoProcessor disposeHook) {
39 |
40 | this.sessionId = sessionId;
41 | this.inbound = inbound;
42 | this.outbound = outbound;
43 |
44 | dispose
45 | .or(disposeHook)
46 | .then(doDispose())
47 | .doFinally(s -> logger.debug("{}: connection disposed", Integer.toHexString(sessionId)))
48 | .doFinally(s -> onDispose.onComplete())
49 | .subscribe(
50 | null,
51 | th -> logger.warn("{} failed on doDispose(): {}", this, th.toString()),
52 | () -> logger.debug("Disposed {}", this));
53 | }
54 |
55 | /**
56 | * Setting up this connection by applying user provided application level handler.
57 | *
58 | * @param handler handler with application level code
59 | */
60 | Mono start(
61 | Function super AeronConnection, ? extends Publisher> handler) {
62 | return Mono.fromRunnable(() -> start0(handler)).thenReturn(this);
63 | }
64 |
65 | private void start0(Function super AeronConnection, ? extends Publisher> handler) {
66 | if (handler == null) {
67 | logger.warn(
68 | "{}: connection handler function is not specified", Integer.toHexString(sessionId));
69 | } else if (!isDisposed()) {
70 | handler.apply(this).subscribe(disposeSubscriber());
71 | }
72 | }
73 |
74 | @Override
75 | public AeronInbound inbound() {
76 | return inbound;
77 | }
78 |
79 | @Override
80 | public AeronOutbound outbound() {
81 | return outbound;
82 | }
83 |
84 | @Override
85 | public void dispose() {
86 | dispose.onComplete();
87 | }
88 |
89 | @Override
90 | public boolean isDisposed() {
91 | return onDispose.isDisposed();
92 | }
93 |
94 | @Override
95 | public Mono onDispose() {
96 | return onDispose;
97 | }
98 |
99 | private Mono doDispose() {
100 | return Mono.defer(
101 | () -> {
102 | logger.debug("Disposing {}", this);
103 | return Mono.whenDelayError(
104 | Mono.fromRunnable(inbound::dispose), Mono.fromRunnable(outbound::dispose));
105 | });
106 | }
107 |
108 | @Override
109 | public String toString() {
110 | return "DefaultAeronConnection" + Integer.toHexString(sessionId);
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/reactor-aeron/src/main/java/reactor/aeron/MessageSubscription.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | import io.aeron.Subscription;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import reactor.core.Exceptions;
7 | import reactor.core.publisher.Mono;
8 | import reactor.core.publisher.MonoProcessor;
9 |
10 | class MessageSubscription implements OnDisposable, AeronResource {
11 |
12 | private static final Logger logger = LoggerFactory.getLogger(MessageSubscription.class);
13 |
14 | private final AeronEventLoop eventLoop;
15 | private final Subscription subscription; // aeron subscription
16 |
17 | private final MonoProcessor onDispose = MonoProcessor.create();
18 |
19 | /**
20 | * Constructor.
21 | *
22 | * @param subscription aeron subscription
23 | * @param eventLoop event loop where this {@code MessageSubscription} is assigned
24 | */
25 | MessageSubscription(Subscription subscription, AeronEventLoop eventLoop) {
26 | this.subscription = subscription;
27 | this.eventLoop = eventLoop;
28 | }
29 |
30 | @Override
31 | public void close() {
32 | if (!eventLoop.inEventLoop()) {
33 | throw AeronExceptions.failWithResourceDisposal("aeron subscription");
34 | }
35 | try {
36 | subscription.close();
37 | logger.debug("Disposed {}", this);
38 | } catch (Exception ex) {
39 | logger.warn("{} failed on aeron.Subscription close(): {}", this, ex.toString());
40 | throw Exceptions.propagate(ex);
41 | } finally {
42 | onDispose.onComplete();
43 | }
44 | }
45 |
46 | @Override
47 | public void dispose() {
48 | eventLoop
49 | .dispose(this)
50 | .subscribe(
51 | null,
52 | th -> {
53 | // no-op
54 | });
55 | }
56 |
57 | /**
58 | * Delegates to {@link Subscription#isClosed()}.
59 | *
60 | * @return {@code true} if aeron {@code Subscription} is closed, {@code false} otherwise
61 | */
62 | @Override
63 | public boolean isDisposed() {
64 | return subscription.isClosed();
65 | }
66 |
67 | @Override
68 | public Mono onDispose() {
69 | return onDispose;
70 | }
71 |
72 | @Override
73 | public String toString() {
74 | return "MessageSubscription{sub=" + subscription.channel() + "}";
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/reactor-aeron/src/main/java/reactor/aeron/OnDisposable.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | import reactor.core.Disposable;
4 | import reactor.core.publisher.Mono;
5 |
6 | public interface OnDisposable extends Disposable {
7 |
8 | /**
9 | * Assign a {@link Disposable} to be invoked when the this has been disposed.
10 | *
11 | * @return a Mono succeeding when this has been disposed
12 | */
13 | Mono onDispose();
14 |
15 | /**
16 | * Assign a {@link Disposable} to be invoked when the connection is closed.
17 | *
18 | * @param onDispose the close event handler
19 | * @return this
20 | */
21 | default OnDisposable onDispose(Disposable onDispose) {
22 | onDispose().subscribe(null, e -> onDispose.dispose(), onDispose::dispose);
23 | return this;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/reactor-aeron/src/main/java/reactor/aeron/SecureRandomSessionIdGenerator.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | import io.aeron.driver.MediaDriver.Context;
4 | import java.security.SecureRandom;
5 | import java.util.function.Supplier;
6 |
7 | /**
8 | * Session id generator (in the range {@code 0..Int.MAX_VALUE}) based on {@link SecureRandom}.
9 | *
10 | * NOTE: this session id generator aligns with defaults (that comes from {@link AeronResources}
11 | * object) for {@link Context#publicationReservedSessionIdLow()} and {@link
12 | * Context#publicationReservedSessionIdHigh()}.
13 | */
14 | public final class SecureRandomSessionIdGenerator implements Supplier {
15 |
16 | private final SecureRandom random = new SecureRandom();
17 |
18 | @Override
19 | public Integer get() {
20 | return random.nextInt(Integer.MAX_VALUE);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/reactor-aeron/src/main/java/reactor/aeron/WorkerFlightRecorder.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | final class WorkerFlightRecorder implements WorkerMBean {
4 |
5 | private static final int REPORT_INTERVAL = 1000;
6 |
7 | private long reportTime;
8 |
9 | private long ticks;
10 | private long workCount;
11 | private long idleCount;
12 | private double outboundRate;
13 | private double inboundRate;
14 | private double idleRate;
15 |
16 | long totalTickCount;
17 | long totalOutboundCount;
18 | long totalInboundCount;
19 | long totalIdleCount;
20 | long totalWorkCount;
21 |
22 | private long lastTotalTickCount;
23 | private long lastTotalOutboundCount;
24 | private long lastTotalInboundCount;
25 | private long lastTotalIdleCount;
26 | private long lastTotalWorkCount;
27 |
28 | void start() {
29 | reportTime = System.currentTimeMillis() + REPORT_INTERVAL;
30 | }
31 |
32 | /**
33 | * Make reporting if it's time for it. For details see method: {@link #processReporting(long,
34 | * long, long, long, long)}
35 | */
36 | void tryReport() {
37 | long currentTime = System.currentTimeMillis();
38 | if (currentTime >= reportTime) {
39 | reportTime = currentTime + REPORT_INTERVAL;
40 | processReporting(
41 | totalTickCount, totalOutboundCount, totalInboundCount, totalIdleCount, totalWorkCount);
42 | }
43 | }
44 |
45 | @Override
46 | public long getTicks() {
47 | return ticks;
48 | }
49 |
50 | @Override
51 | public long getWorkCount() {
52 | return workCount;
53 | }
54 |
55 | @Override
56 | public long getIdleCount() {
57 | return idleCount;
58 | }
59 |
60 | @Override
61 | public double getOutboundRate() {
62 | return outboundRate;
63 | }
64 |
65 | @Override
66 | public double getInboundRate() {
67 | return inboundRate;
68 | }
69 |
70 | @Override
71 | public double getIdleRate() {
72 | return idleRate;
73 | }
74 |
75 | private void processReporting(
76 | long totalTickCount,
77 | long totalOutboundCount,
78 | long totalInboundCount,
79 | long totalIdleCount,
80 | long totalWorkCount) {
81 |
82 | ticks = totalTickCount - lastTotalTickCount;
83 | workCount = totalWorkCount - lastTotalWorkCount;
84 | idleCount = totalIdleCount - lastTotalIdleCount;
85 | outboundRate = (double) (totalOutboundCount - lastTotalOutboundCount) / ticks;
86 | inboundRate = (double) (totalInboundCount - lastTotalInboundCount) / ticks;
87 | idleRate = (double) (totalIdleCount - lastTotalIdleCount) / ticks;
88 |
89 | lastTotalTickCount = totalTickCount;
90 | lastTotalWorkCount = totalWorkCount;
91 | lastTotalIdleCount = totalIdleCount;
92 | lastTotalOutboundCount = totalOutboundCount;
93 | lastTotalInboundCount = totalInboundCount;
94 | }
95 |
96 | void countTick() {
97 | totalTickCount++;
98 | }
99 |
100 | void countOutbound(int c) {
101 | totalOutboundCount += c;
102 | }
103 |
104 | void countInbound(int c) {
105 | totalInboundCount += c;
106 | }
107 |
108 | void countIdle() {
109 | totalIdleCount++;
110 | }
111 |
112 | void countWork(int c) {
113 | totalWorkCount += c;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/reactor-aeron/src/main/java/reactor/aeron/WorkerMBean.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | /**
4 | * JMX MBean exposer class for event loop worker thread (see for details {@link AeronEventLoop}).
5 | * Contains various runtime stats.
6 | */
7 | public interface WorkerMBean {
8 |
9 | /**
10 | * Returns number of ticks per last second.
11 | *
12 | * @return number of ticks per last seconds
13 | */
14 | long getTicks();
15 |
16 | /**
17 | * Returns number of work done (counted both outbound and inbound) per last second.
18 | *
19 | * @return number of work done per last second.
20 | */
21 | long getWorkCount();
22 |
23 | /**
24 | * Returns number of how many times event loop was idling without progress done.
25 | *
26 | * @return number of times being idle
27 | */
28 | long getIdleCount();
29 |
30 | /**
31 | * Returns amount of outbound work done per one tick.
32 | *
33 | * @return amount of outbound work done per tick
34 | */
35 | double getOutboundRate();
36 |
37 | /**
38 | * Returns amount of inbound work done per one tick.
39 | *
40 | * @return amount of inbound work done per tick
41 | */
42 | double getInboundRate();
43 |
44 | /**
45 | * Returns amount of being idle per one tick.
46 | *
47 | * @return amount of being idle per tick
48 | */
49 | double getIdleRate();
50 | }
51 |
--------------------------------------------------------------------------------
/reactor-aeron/src/test/java/reactor/aeron/AeronServerTest.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertTrue;
4 |
5 | import java.time.Duration;
6 | import java.util.function.Function;
7 | import java.util.stream.Stream;
8 | import org.agrona.DirectBuffer;
9 | import org.junit.jupiter.api.AfterEach;
10 | import org.junit.jupiter.api.BeforeEach;
11 | import org.junit.jupiter.api.Test;
12 | import org.reactivestreams.Publisher;
13 | import reactor.core.publisher.Flux;
14 | import reactor.core.publisher.ReplayProcessor;
15 | import reactor.test.StepVerifier;
16 |
17 | class AeronServerTest extends BaseAeronTest {
18 |
19 | private int serverPort;
20 | private int serverControlPort;
21 | private AeronResources resources;
22 |
23 | @BeforeEach
24 | void beforeEach() {
25 | serverPort = SocketUtils.findAvailableUdpPort();
26 | serverControlPort = SocketUtils.findAvailableUdpPort();
27 | resources = new AeronResources().useTmpDir().singleWorker().start().block();
28 | }
29 |
30 | @AfterEach
31 | void afterEach() {
32 | if (resources != null) {
33 | resources.dispose();
34 | resources.onDispose().block(TIMEOUT);
35 | }
36 | }
37 |
38 | @Test
39 | public void testServerReceivesData() {
40 | ReplayProcessor processor = ReplayProcessor.create();
41 |
42 | createServer(
43 | connection -> {
44 | connection.inbound().receive().asString().log("receive").subscribe(processor);
45 | return connection.onDispose();
46 | });
47 |
48 | createConnection()
49 | .outbound()
50 | .sendString(Flux.fromStream(Stream.of("Hello", "world!")).log("send"))
51 | .then()
52 | .subscribe();
53 |
54 | StepVerifier.create(processor).expectNext("Hello", "world!").thenCancel().verify();
55 | }
56 |
57 | @Test
58 | public void testServerDisconnectsClientsUponShutdown() throws InterruptedException {
59 | ReplayProcessor processor = ReplayProcessor.create();
60 |
61 | OnDisposable server =
62 | createServer(
63 | connection -> {
64 | connection.inbound().receive().log("receive").subscribe(processor);
65 | return connection.onDispose();
66 | });
67 |
68 | createConnection()
69 | .outbound()
70 | .sendString(
71 | Flux.range(1, 100)
72 | .delayElements(Duration.ofSeconds(1))
73 | .map(String::valueOf)
74 | .log("send"))
75 | .then()
76 | .subscribe();
77 |
78 | processor.blockFirst();
79 |
80 | server.dispose();
81 |
82 | assertTrue(new ThreadWatcher().awaitTerminated(5000, "single-", "parallel-"));
83 | }
84 |
85 | private AeronConnection createConnection() {
86 | return AeronClient.create(resources)
87 | .options("localhost", serverPort, serverControlPort)
88 | .connect()
89 | .block(TIMEOUT);
90 | }
91 |
92 | private OnDisposable createServer(
93 | Function super AeronConnection, ? extends Publisher> handler) {
94 | return AeronServer.create(resources)
95 | .options("localhost", serverPort, serverControlPort)
96 | .handle(handler)
97 | .bind()
98 | .block(TIMEOUT);
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/reactor-aeron/src/test/java/reactor/aeron/BaseAeronTest.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | import java.time.Duration;
4 | import org.junit.jupiter.api.AfterEach;
5 | import org.junit.jupiter.api.BeforeEach;
6 | import org.junit.jupiter.api.TestInfo;
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 |
10 | public class BaseAeronTest {
11 |
12 | public static final Logger logger = LoggerFactory.getLogger(BaseAeronTest.class);
13 |
14 | public static final Duration TIMEOUT = Duration.ofSeconds(30);
15 |
16 | @BeforeEach
17 | public final void baseSetUp(TestInfo testInfo) {
18 | logger.info("***** Test started : " + testInfo.getDisplayName() + " *****");
19 | }
20 |
21 | @AfterEach
22 | public final void baseTearDown(TestInfo testInfo) {
23 | logger.info("***** Test finished : " + testInfo.getDisplayName() + " *****");
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/reactor-aeron/src/test/java/reactor/aeron/ThreadWatcher.java:
--------------------------------------------------------------------------------
1 | package reactor.aeron;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 | import java.util.concurrent.TimeUnit;
6 | import java.util.function.Predicate;
7 | import java.util.stream.Collectors;
8 |
9 | public class ThreadWatcher {
10 |
11 | private List beforeThreadNames;
12 |
13 | public ThreadWatcher() {
14 | this.beforeThreadNames = takeThreadNamesSnapshot();
15 | }
16 |
17 | public boolean awaitTerminated(long timeoutMillis) throws InterruptedException {
18 | return awaitTerminated(timeoutMillis, new String[] {});
19 | }
20 |
21 | /**
22 | * Await termination.
23 | *
24 | * @param timeoutMillis timeout
25 | * @param excludedPrefixes prefixes to exclude
26 | * @return tru or false
27 | * @throws InterruptedException exception
28 | */
29 | public boolean awaitTerminated(long timeoutMillis, String... excludedPrefixes)
30 | throws InterruptedException {
31 | List liveThreadNames;
32 | long startTime = System.nanoTime();
33 | while ((liveThreadNames = getLiveThreadNames(excludedPrefixes)).size() > 0) {
34 | Thread.sleep(100);
35 |
36 | if (System.nanoTime() - startTime > TimeUnit.MILLISECONDS.toNanos(timeoutMillis)) {
37 | System.err.println("Ouch! These threads were not terminated: " + liveThreadNames);
38 | return false;
39 | }
40 | }
41 | return true;
42 | }
43 |
44 | private List getLiveThreadNames(String[] excludedPrefixes) {
45 | List afterThreadNames = takeThreadNamesSnapshot();
46 | afterThreadNames.removeAll(beforeThreadNames);
47 | return afterThreadNames.stream()
48 | .filter(
49 | new Predicate() {
50 | @Override
51 | public boolean test(String s) {
52 | for (String prefix : excludedPrefixes) {
53 | if (s.startsWith(prefix)) {
54 | return false;
55 | }
56 | }
57 | return true;
58 | }
59 | })
60 | .collect(Collectors.toList());
61 | }
62 |
63 | private List takeThreadNamesSnapshot() {
64 | Thread[] tarray;
65 | int activeCount;
66 | int actualCount;
67 | do {
68 | activeCount = Thread.activeCount();
69 | tarray = new Thread[activeCount];
70 | actualCount = Thread.enumerate(tarray);
71 | } while (activeCount != actualCount);
72 |
73 | List threadNames = new ArrayList<>();
74 | for (int i = 0; i < actualCount; i++) {
75 | threadNames.add(tarray[i].getName());
76 | }
77 |
78 | return threadNames;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/reactor-aeron/src/test/resources/log4j2-test.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | %level{length=1} %d{ISO8601} %c{1.} %m [%t]%n
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | requests==2.20.1
2 |
--------------------------------------------------------------------------------