├── .editorconfig ├── .gitattributes ├── .github └── workflows │ ├── branch-ci.yml │ ├── pre-release-ci.yml │ └── release-ci.yml ├── .gitignore ├── .yamllint.yml ├── LICENSE.txt ├── README.md ├── pom.xml ├── reactor-aeron-benchmarks ├── pom.xml ├── scripts │ ├── benchmarks.json │ ├── benchmarks.sh │ ├── create-chart.sh │ ├── netty │ │ ├── netty-client-tps.sh │ │ ├── netty-ping.sh │ │ ├── netty-pong.sh │ │ └── netty-server-tps.sh │ ├── pure │ │ ├── mdc-client-simple-tps.sh │ │ ├── mdc-client-tps.sh │ │ ├── mdc-ping-async.sh │ │ ├── mdc-ping.sh │ │ ├── mdc-pong.sh │ │ ├── mdc-server-simple-tps.sh │ │ ├── mdc-server-tps.sh │ │ ├── ping.sh │ │ └── pong.sh │ ├── reactor │ │ ├── aeron-client-simple-tps.sh │ │ ├── aeron-client-tps.sh │ │ ├── aeron-ping.sh │ │ ├── aeron-pong.sh │ │ ├── aeron-server-simple-tps.sh │ │ └── aeron-server-tps.sh │ ├── rsocket │ │ ├── aeron │ │ │ ├── rsocket-aeron-client-simple-tps.sh │ │ │ ├── rsocket-aeron-client-tps.sh │ │ │ ├── rsocket-aeron-ping.sh │ │ │ ├── rsocket-aeron-pong.sh │ │ │ ├── rsocket-aeron-server-simple-tps.sh │ │ │ └── rsocket-aeron-server-tps.sh │ │ └── netty │ │ │ ├── rsocket-netty-client-tps.sh │ │ │ ├── rsocket-netty-ping.sh │ │ │ ├── rsocket-netty-pong.sh │ │ │ └── rsocket-netty-server-tps.sh │ └── setup-env.sh └── src │ └── main │ ├── java │ └── reactor │ │ └── aeron │ │ ├── AeronPingClient.java │ │ ├── AeronPongServer.java │ │ ├── ClientDemo.java │ │ ├── ClientServerSends.java │ │ ├── ClientThroughput.java │ │ ├── Configurations.java │ │ ├── LatencyReporter.java │ │ ├── RateReporter.java │ │ ├── ServerDemo.java │ │ ├── ServerServerSends.java │ │ ├── ServerThroughput.java │ │ ├── netty │ │ ├── ReactorNettyClientPing.java │ │ ├── ReactorNettyClientTps.java │ │ ├── ReactorNettyServerPong.java │ │ └── ReactorNettyServerTps.java │ │ ├── pure │ │ ├── ClientThroughput.java │ │ ├── MdcPing.java │ │ ├── MdcPingAsync.java │ │ ├── MdcPong.java │ │ ├── Ping.java │ │ ├── Pong.java │ │ └── ServerThroughput.java │ │ └── rsocket │ │ ├── aeron │ │ ├── RSocketAeronClientTps.java │ │ ├── RSocketAeronPing.java │ │ ├── RSocketAeronPong.java │ │ └── RSocketAeronServerTps.java │ │ └── netty │ │ ├── RSocketNettyClientTps.java │ │ ├── RSocketNettyPing.java │ │ ├── RSocketNettyPong.java │ │ └── RSocketNettyServerTps.java │ └── resources │ ├── latency-report.json │ ├── log4j2.xml │ └── throughput-report.json ├── reactor-aeron ├── pom.xml └── src │ ├── main │ └── java │ │ └── reactor │ │ └── aeron │ │ ├── AeronChannelUriString.java │ │ ├── AeronClient.java │ │ ├── AeronClientConnector.java │ │ ├── AeronConnection.java │ │ ├── AeronEventLoop.java │ │ ├── AeronEventLoopGroup.java │ │ ├── AeronExceptions.java │ │ ├── AeronInbound.java │ │ ├── AeronOptions.java │ │ ├── AeronOutbound.java │ │ ├── AeronOutboundThen.java │ │ ├── AeronResource.java │ │ ├── AeronResources.java │ │ ├── AeronServer.java │ │ ├── AeronServerHandler.java │ │ ├── DefaultAeronInbound.java │ │ ├── DefaultAeronOutbound.java │ │ ├── DirectBufferFlux.java │ │ ├── DirectBufferHandler.java │ │ ├── DuplexAeronConnection.java │ │ ├── MessagePublication.java │ │ ├── MessageSubscription.java │ │ ├── OnDisposable.java │ │ ├── SecureRandomSessionIdGenerator.java │ │ ├── WorkerFlightRecorder.java │ │ └── WorkerMBean.java │ └── test │ ├── java │ └── reactor │ │ └── aeron │ │ ├── AeronClientTest.java │ │ ├── AeronConnectionTest.java │ │ ├── AeronMultiClientTest.java │ │ ├── AeronServerTest.java │ │ ├── BaseAeronTest.java │ │ ├── SocketUtils.java │ │ └── ThreadWatcher.java │ └── resources │ └── log4j2-test.xml └── requirements.txt /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | *.txt text 4 | *.sh text eol=lf 5 | *.html text eol=lf diff=html 6 | *.css text eol=lf 7 | *.js text eol=lf 8 | *.jpg -text 9 | *.pdf -text 10 | *.java text diff=java 11 | -------------------------------------------------------------------------------- /.github/workflows/branch-ci.yml: -------------------------------------------------------------------------------- 1 | name: Branch CI 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '.github/workflows/**' 7 | - '*.md' 8 | - '*.txt' 9 | branches-ignore: 10 | - 'release*' 11 | 12 | jobs: 13 | build: 14 | name: Branch CI 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: actions/cache@v1 19 | with: 20 | path: ~/.m2/repository 21 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 22 | restore-keys: | 23 | ${{ runner.os }}-maven- 24 | - name: Set up JDK 1.8 25 | uses: actions/setup-java@v1 26 | with: 27 | java-version: 1.8 28 | server-id: github 29 | server-username: GITHUB_ACTOR 30 | server-password: GITHUB_TOKEN 31 | - name: Maven Build 32 | run: mvn clean install -DskipTests=true -Dmaven.javadoc.skip=true -Ddockerfile.skip=true -B -V 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.ORGANIZATION_TOKEN }} 35 | - name: Maven Verify 36 | run: mvn verify -B 37 | -------------------------------------------------------------------------------- /.github/workflows/pre-release-ci.yml: -------------------------------------------------------------------------------- 1 | name: Pre-release CI 2 | 3 | on: 4 | release: 5 | types: [prereleased] 6 | 7 | jobs: 8 | build: 9 | name: Pre-release CI 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/cache@v1 14 | with: 15 | path: ~/.m2/repository 16 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 17 | restore-keys: | 18 | ${{ runner.os }}-maven- 19 | - name: Set up JDK 1.8 20 | uses: actions/setup-java@v1 21 | with: 22 | java-version: 1.8 23 | server-id: github 24 | server-username: GITHUB_ACTOR 25 | server-password: GITHUB_TOKEN 26 | - name: Deploy pre-release version 27 | run: | 28 | pre_release_version=${{ github.event.release.tag_name }} 29 | echo Pre-release version $pre_release_version 30 | mvn versions:set -DnewVersion=$pre_release_version -DgenerateBackupPoms=false 31 | mvn versions:commit 32 | mvn clean deploy -B -V 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.ORGANIZATION_TOKEN }} 35 | - name: Rollback pre-release (remove tag) 36 | if: failure() 37 | run: git push origin :refs/tags/${{ github.event.release.tag_name }} 38 | -------------------------------------------------------------------------------- /.github/workflows/release-ci.yml: -------------------------------------------------------------------------------- 1 | name: Release CI 2 | 3 | on: 4 | release: 5 | types: [released] 6 | 7 | jobs: 8 | build: 9 | name: Release CI 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | with: 14 | fetch-depth: 0 15 | - run: git checkout ${{ github.event.release.target_commitish }} 16 | - uses: actions/cache@v1 17 | with: 18 | path: ~/.m2/repository 19 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 20 | restore-keys: | 21 | ${{ runner.os }}-maven- 22 | - name: Set up JDK 1.8 23 | uses: actions/setup-java@v1 24 | with: 25 | java-version: 1.8 26 | server-id: github 27 | server-username: GITHUB_ACTOR 28 | server-password: GITHUB_TOKEN 29 | - name: Maven Build 30 | run: mvn clean install -DskipTests=true -Ddockerfile.skip=true -B -V 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.ORGANIZATION_TOKEN }} 33 | - name: Maven Verify 34 | run: mvn verify -B 35 | - name: Configure git 36 | run: | 37 | git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com" 38 | git config --global user.name "${GITHUB_ACTOR}" 39 | - name: Prepare release 40 | id: prepare_release 41 | run: | 42 | mvn -B build-helper:parse-version release:prepare \ 43 | -DreleaseVersion=\${parsedVersion.majorVersion}.\${parsedVersion.minorVersion}.\${parsedVersion.incrementalVersion} \ 44 | -Darguments="-DskipTests=true -Ddockerfile.skip=true" 45 | echo ::set-output name=release_tag::$(git describe --tags --abbrev=0) 46 | - name: Perform release 47 | run: mvn -B release:perform -Darguments="-DskipTests=true -Ddockerfile.skip=true" 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | GITHUB_REPOSITORY: ${{ secrets.GITHUB_REPOSITORY }} 51 | - name: Rollback release 52 | if: failure() 53 | run: | 54 | mvn release:rollback || echo "nothing to rollback" 55 | git push origin :refs/tags/${{ github.event.release.tag_name }} 56 | if [ ! -z "${{ steps.prepare_release.outputs.release_tag }}" ] 57 | then 58 | git tag -d ${{ steps.prepare_release.outputs.release_tag }} 59 | git push origin :refs/tags/${{ steps.prepare_release.outputs.release_tag }} 60 | fi 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | !.gitignore 3 | !.gitattributes 4 | !.github 5 | !.editorconfig 6 | !.*.yml 7 | !.env.example 8 | **/target/ 9 | *.iml 10 | **/logs/*.log 11 | *.db 12 | *.csv 13 | *.log 14 | -------------------------------------------------------------------------------- /.yamllint.yml: -------------------------------------------------------------------------------- 1 | extends: default 2 | rules: 3 | document-start: 4 | present: false 5 | truthy: disable 6 | comments: 7 | min-spaces-from-content: 1 8 | line-length: 9 | max: 150 10 | braces: 11 | min-spaces-inside: 0 12 | max-spaces-inside: 0 13 | brackets: 14 | min-spaces-inside: 0 15 | max-spaces-inside: 0 16 | indentation: 17 | indent-sequences: consistent 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Reactor Aeron 2 | 3 | `Reactor Aeron` offers non-blocking and backpressure-ready relable `UDP` 4 | clients & servers based on [Aeron](https://github.com/real-logic/aeron) Efficient reliable UDP unicast, UDP multicast, and IPC message transport. it is inspired by [reactor-netty](https://github.com/reactor/reactor-netty) 5 | 6 | ## Getting it 7 | `Reactor Aeron` requires Java 8 or + to run. 8 | 9 | With `Maven` from `Maven Central` repositories (stable releases only): 10 | 11 | ```xml 12 | 13 | 14 | io.scalecube 15 | reactor-aeron 16 | x.y.z 17 | pom 18 | 19 | 20 | ``` 21 | 22 | ## Getting Started 23 | 24 | Here is a very simple server and the corresponding client example 25 | 26 | ```java 27 | AeronResources resources = new AeronResources().useTmpDir().start().block(); 28 | 29 | AeronServer.create(resources) 30 | .options("localhost", 13000, 13001) 31 | .handle( 32 | connection -> 33 | connection 34 | .inbound() 35 | .receive() 36 | .asString() 37 | .log("receive") 38 | .then(connection.onDispose())) 39 | .bind() 40 | .block() 41 | .onDispose(resources) 42 | .onDispose() 43 | .block(); 44 | 45 | ``` 46 | 47 | ```java 48 | AeronResources resources = new AeronResources().useTmpDir().start().block(); 49 | 50 | AeronClient.create(resources) 51 | .options("localhost", 13000, 13001) 52 | .handle( 53 | connection1 -> { 54 | System.out.println("Handler invoked"); 55 | return connection1 56 | .outbound() 57 | .sendString(Flux.fromStream(Stream.of("Hello", "world!")).log("send")) 58 | .then(connection1.onDispose()); 59 | }) 60 | .connect() 61 | .block() 62 | .onDispose(resources) 63 | .onDispose() 64 | .block(); 65 | ``` 66 | 67 | ## Building from Source 68 | 69 | ```console 70 | $ git clone git@github.com:scalecube/reactor-aeron.git 71 | $ cd reactor-aeron 72 | $ mvn clean install 73 | ``` 74 | 75 | ## Performance results 76 | 77 | Performance is the key focus. Aeron is designed to be the highest throughput with the lowest and most predictable latency possible of any messaging system. 78 | 79 | Benchmark: `reactor-aeron` vs `reactor-netty` vs `pure-aeron` vs `rsocket` running on `AWS` `C5.xlarge` 80 | 81 | - [Latency (C5.xlarge) - Aeron vs Reactor-Aeron vs Reactor-Netty vs RSocket-Aeron vs RSocket-netty](https://chart-studio.plotly.com/~ronenhamias/17.embed) 82 | 83 | - [Throughput (C5.xlarge) - Aeron vs Reactor-Aeron vs Reactor-Netty vs RSocket-Aeron vs RSocket-netty](https://chart-studio.plotly.com/~ronenhamias/15.embed) 84 | 85 | ## Code style 86 | 87 | [See the reference on scalecube site](https://github.com/scalecube/scalecube-parent/blob/develop/DEVELOPMENT.md#setting-up-development-environment) 88 | 89 | ## License 90 | 91 | `Reactor Aeron` is Open Source Software released under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) 92 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | 5 | io.scalecube 6 | scalecube-parent-pom 7 | 0.2.17 8 | 9 | 10 | reactor-aeron-parent 11 | 0.1.6-SNAPSHOT 12 | pom 13 | ScaleCube/ReactorAeron 14 | 15 | 16 | 17 | github 18 | GitHub Packages 19 | https://maven.pkg.github.com/scalecube/packages 20 | 21 | false 22 | 23 | 24 | 25 | 26 | 27 | 28 | github 29 | GitHub Packages 30 | https://maven.pkg.github.com/scalecube/reactor-aeron 31 | 32 | 33 | 34 | 35 | https://github.com/scalecube/scalecube 36 | scm:git:https://github.com/scalecube/reactor-aeron.git 37 | scm:git:https://github.com/scalecube/reactor-aeron.git 38 | 39 | HEAD 40 | 41 | 42 | 43 | 1.15.3 44 | Californium-SR5 45 | 46 | 1.7.7 47 | 2.11.0 48 | 3.4.2 49 | 50 | 2.17.0 51 | 5.1.0 52 | 1.3 53 | 54 | 55 | 56 | reactor-aeron 57 | reactor-aeron-benchmarks 58 | 59 | 60 | 61 | 62 | 63 | 64 | io.aeron 65 | aeron-driver 66 | ${aeron.version} 67 | 68 | 69 | io.aeron 70 | aeron-client 71 | ${aeron.version} 72 | 73 | 74 | 75 | 76 | io.projectreactor 77 | reactor-bom 78 | ${reactor.version} 79 | pom 80 | import 81 | 82 | 83 | 84 | 85 | org.slf4j 86 | slf4j-api 87 | ${slf4j.version} 88 | 89 | 90 | org.apache.logging.log4j 91 | log4j-slf4j-impl 92 | ${log4j.version} 93 | 94 | 95 | org.apache.logging.log4j 96 | log4j-core 97 | ${log4j.version} 98 | 99 | 100 | 101 | com.lmax 102 | disruptor 103 | ${disruptor.version} 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | org.junit.jupiter 112 | junit-jupiter-engine 113 | ${junit-jupiter.version} 114 | test 115 | 116 | 117 | org.junit.jupiter 118 | junit-jupiter-params 119 | ${junit-jupiter.version} 120 | test 121 | 122 | 123 | org.mockito 124 | mockito-junit-jupiter 125 | ${mockito-junit-jupiter.version} 126 | test 127 | 128 | 129 | org.hamcrest 130 | hamcrest-all 131 | ${hamcrest.version} 132 | test 133 | 134 | 135 | org.hamcrest 136 | hamcrest-core 137 | ${hamcrest.version} 138 | test 139 | 140 | 141 | io.projectreactor 142 | reactor-test 143 | test 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | 4.1.33.Final 7 | 0.11.18 8 | 0.1.12 9 | 2.1.10 10 | 1.21 11 | 12 | 13 | 14 | io.scalecube 15 | reactor-aeron-parent 16 | 0.1.6-SNAPSHOT 17 | 18 | 19 | reactor-aeron-benchmarks 20 | 21 | 22 | 23 | io.scalecube 24 | reactor-aeron 25 | ${project.version} 26 | 27 | 28 | 29 | io.scalecube 30 | trace-reporter 31 | 0.0.7 32 | 33 | 34 | 35 | org.hdrhistogram 36 | HdrHistogram 37 | ${hdrhistogram.version} 38 | 39 | 40 | 41 | org.openjdk.jmh 42 | jmh-core 43 | ${jmh.version} 44 | 45 | 46 | org.openjdk.jmh 47 | jmh-generator-annprocess 48 | ${jmh.version} 49 | 50 | 51 | 52 | org.apache.logging.log4j 53 | log4j-slf4j-impl 54 | 55 | 56 | org.apache.logging.log4j 57 | log4j-core 58 | 59 | 60 | com.lmax 61 | disruptor 62 | 63 | 64 | 65 | io.netty 66 | netty-common 67 | ${netty.version} 68 | 69 | 70 | io.netty 71 | netty-buffer 72 | ${netty.version} 73 | 74 | 75 | io.netty 76 | netty-codec 77 | ${netty.version} 78 | 79 | 80 | io.netty 81 | netty-codec-http 82 | ${netty.version} 83 | 84 | 85 | io.netty 86 | netty-handler 87 | ${netty.version} 88 | 89 | 90 | io.netty 91 | netty-handler-proxy 92 | ${netty.version} 93 | 94 | 95 | io.netty 96 | netty-transport 97 | ${netty.version} 98 | 99 | 100 | io.netty 101 | netty-transport-native-epoll 102 | linux-x86_64 103 | ${netty.version} 104 | 105 | 106 | 107 | io.rsocket 108 | rsocket-core 109 | ${rsocket.version} 110 | 111 | 112 | io.netty 113 | netty-buffer 114 | 115 | 116 | 117 | 118 | 119 | io.rsocket 120 | rsocket-transport-netty 121 | ${rsocket.version} 122 | 123 | 124 | io.netty 125 | netty-common 126 | 127 | 128 | io.netty 129 | netty-buffer 130 | 131 | 132 | io.netty 133 | netty-handler 134 | 135 | 136 | io.netty 137 | netty-codec-http 138 | 139 | 140 | io.netty 141 | netty-handler-proxy 142 | 143 | 144 | io.netty 145 | netty-transport-native-epoll 146 | 147 | 148 | 149 | 150 | 151 | io.scalecube 152 | rsocket-transport-aeron 153 | ${rsocket-transport-aeron.version} 154 | 155 | 156 | io.rsocket 157 | rsocket-core 158 | 159 | 160 | io.scalecube 161 | reactor-aeron 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | maven-jar-plugin 171 | 172 | 173 | maven-dependency-plugin 174 | 175 | 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/benchmarks.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #This script executes benchmarks from benchmarks.json 4 | 5 | TESTS_DATA=$(cat benchmarks.json) 6 | 7 | for test in $(echo "${TESTS_DATA}" | jq -r '.[] | @base64'); do 8 | _jq() { 9 | echo ${test} | base64 --decode |jq -r ${1} 10 | } 11 | 12 | echo "Starting $(_jq '.title')" 13 | 14 | if [ ! "$(_jq '.title')" == null ] 15 | then 16 | export JVM_OPTS="-Dreactor.aeron.report.name=$(_jq '.title')" 17 | fi 18 | 19 | # Add JVM_OPTS to tests if they exist 20 | if [ ! "$(_jq '.args')" == null ] 21 | then 22 | for row in $(_jq '.args[]'); do 23 | JVM_OPTS+=" -D$row" 24 | done 25 | fi 26 | 27 | echo $JVM_OPTS 28 | 29 | $(_jq '.server') > /dev/null 2>&1 & 30 | SERVER_PID=$! 31 | 32 | sleep 2 33 | 34 | $(_jq '.client') > /dev/null 2>&1 & 35 | CLIENT_PID=$! 36 | 37 | sleep 73 38 | 39 | # kill test processes with their childs 40 | pkill -TERM -P $SERVER_PID 41 | pkill -TERM -P $CLIENT_PID 42 | 43 | echo Finished $(_jq '.title') 44 | 45 | done 46 | 47 | echo "All tests are passed" -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/create-chart.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # possible environment variables: 4 | # $TRACES_FOLDER : folder where traces files are generated 5 | # $CHARTS_FOLDER : folder where chart reports is created 6 | # $CHART_TEMPLATE : based on which chart is created 7 | 8 | cd $(dirname $0) 9 | cd ../ 10 | 11 | if [ ! -f ./target/trace-reporter.jar ]; then 12 | echo "trace-reporter.jar File not found! ... downloading...." 13 | LATEST_VERSION_JAR=https://github.com/scalecube/trace-reporter/blob/latest/release/trace-reporter-jar-with-dependencies.jar?raw=true 14 | wget ${LATEST_VERSION_JAR} -O ./target/trace-reporter.jar 15 | fi 16 | 17 | 18 | FOLDER_LATENCY=./target/traces/reports/latency/ 19 | FOLDER_THROUGHPUT=./target/traces/reports/throughput/ 20 | FOLDER_OUTPUT=./target/traces/reports/charts/ 21 | 22 | java \ 23 | -jar ./target/trace-reporter.jar -i ${FOLDER_LATENCY} -o ${FOLDER_OUTPUT} -t ./src/main/resources/latency-report.json 24 | 25 | java \ 26 | -jar ./target/trace-reporter.jar -i ${FOLDER_THROUGHPUT} -o ${FOLDER_OUTPUT} -t ./src/main/resources/throughput-report.json -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/netty/netty-client-tps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:+UnlockDiagnosticVMOptions \ 11 | -XX:GuaranteedSafepointInterval=300000 \ 12 | -Dreactor.aeron.report.name=reactor-netty-128 \ 13 | ${JVM_OPTS} reactor.aeron.netty.ReactorNettyClientTps 14 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/netty/netty-ping.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:+UnlockDiagnosticVMOptions \ 11 | -XX:GuaranteedSafepointInterval=300000 \ 12 | -Dreactor.aeron.sample.messageLength=128 \ 13 | -Dreactor.aeron.sample.request=16 \ 14 | -Dreactor.aeron.report.name=reactor-netty-16x128 \ 15 | ${JVM_OPTS} reactor.aeron.netty.ReactorNettyClientPing 16 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/netty/netty-pong.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:+UnlockDiagnosticVMOptions \ 11 | -XX:GuaranteedSafepointInterval=300000 \ 12 | ${JVM_OPTS} reactor.aeron.netty.ReactorNettyServerPong 13 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/netty/netty-server-tps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:+UnlockDiagnosticVMOptions \ 11 | -XX:GuaranteedSafepointInterval=300000 \ 12 | -Dreactor.aeron.report.name=reactor-netty-128 \ 13 | ${JVM_OPTS} reactor.aeron.netty.ReactorNettyServerTps 14 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/pure/mdc-client-simple-tps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:BiasedLockingStartupDelay=0 \ 11 | -Djava.net.preferIPv4Stack=true \ 12 | -Daeron.term.buffer.sparse.file=false \ 13 | -Daeron.threading.mode=SHARED \ 14 | -Dagrona.disable.bounds.checks=true \ 15 | -Dreactor.aeron.sample.embeddedMediaDriver=true \ 16 | -Dreactor.aeron.sample.exclusive.publications=true \ 17 | -Dreactor.aeron.sample.idle.strategy=yielding \ 18 | -Dreactor.aeron.sample.frameCountLimit=16384 \ 19 | -Dreactor.aeron.sample.messageLength=1024 \ 20 | -Daeron.mtu.length=16k \ 21 | ${JVM_OPTS} reactor.aeron.pure.ClientThroughput 22 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/pure/mdc-client-tps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:BiasedLockingStartupDelay=0 \ 11 | -Djava.net.preferIPv4Stack=true \ 12 | -Daeron.term.buffer.sparse.file=false \ 13 | -Daeron.threading.mode=SHARED \ 14 | -Dagrona.disable.bounds.checks=true \ 15 | -Dreactor.aeron.sample.embeddedMediaDriver=true \ 16 | -Dreactor.aeron.sample.exclusive.publications=true \ 17 | -Dreactor.aeron.sample.idle.strategy=yielding \ 18 | -Dreactor.aeron.sample.frameCountLimit=16384 \ 19 | -Dreactor.aeron.sample.messageLength=2048 \ 20 | -Daeron.mtu.length=16k \ 21 | -Daeron.socket.so_sndbuf=2m \ 22 | -Daeron.socket.so_rcvbuf=2m \ 23 | -Daeron.rcv.initial.window.length=2m \ 24 | ${JVM_OPTS} reactor.aeron.pure.ClientThroughput 25 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/pure/mdc-ping-async.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:+UnlockDiagnosticVMOptions \ 11 | -XX:GuaranteedSafepointInterval=300000 \ 12 | -Daeron.threading.mode=SHARED \ 13 | -Dagrona.disable.bounds.checks=true \ 14 | -Dreactor.aeron.sample.embeddedMediaDriver=true \ 15 | -Dreactor.aeron.sample.exclusive.publications=true \ 16 | -Dreactor.aeron.sample.idle.strategy=yielding \ 17 | -Dreactor.aeron.sample.frameCountLimit=16384 \ 18 | -Dreactor.aeron.sample.messageLength=16 \ 19 | -Dreactor.aeron.sample.request=128 \ 20 | -Daeron.mtu.length=16k \ 21 | ${JVM_OPTS} reactor.aeron.pure.MdcPingAsync 22 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/pure/mdc-ping.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:+UnlockDiagnosticVMOptions \ 11 | -XX:GuaranteedSafepointInterval=300000 \ 12 | -Daeron.threading.mode=SHARED \ 13 | -Dagrona.disable.bounds.checks=true \ 14 | -Dreactor.aeron.sample.embeddedMediaDriver=true \ 15 | -Dreactor.aeron.sample.exclusive.publications=true \ 16 | -Dreactor.aeron.sample.idle.strategy=yielding \ 17 | -Dreactor.aeron.sample.frameCountLimit=16384 \ 18 | -Dreactor.aeron.sample.messageLength=32 \ 19 | ${JVM_OPTS} reactor.aeron.pure.MdcPing 20 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/pure/mdc-pong.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:+UnlockDiagnosticVMOptions \ 11 | -XX:GuaranteedSafepointInterval=300000 \ 12 | -Daeron.threading.mode=SHARED \ 13 | -Dagrona.disable.bounds.checks=true \ 14 | -Dreactor.aeron.sample.embeddedMediaDriver=true \ 15 | -Dreactor.aeron.sample.exclusive.publications=true \ 16 | -Dreactor.aeron.sample.idle.strategy=yielding \ 17 | -Dreactor.aeron.sample.frameCountLimit=16384 \ 18 | -Daeron.mtu.length=16k \ 19 | ${JVM_OPTS} reactor.aeron.pure.MdcPong 20 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/pure/mdc-server-simple-tps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:BiasedLockingStartupDelay=0 \ 11 | -Djava.net.preferIPv4Stack=true \ 12 | -Daeron.term.buffer.sparse.file=false \ 13 | -Daeron.threading.mode=SHARED \ 14 | -Dagrona.disable.bounds.checks=true \ 15 | -Dreactor.aeron.sample.embeddedMediaDriver=true \ 16 | -Dreactor.aeron.sample.idle.strategy=yielding \ 17 | -Dreactor.aeron.sample.frameCountLimit=16384 \ 18 | -Daeron.mtu.length=16k \ 19 | ${JVM_OPTS} reactor.aeron.pure.ServerThroughput 20 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/pure/mdc-server-tps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:BiasedLockingStartupDelay=0 \ 11 | -Djava.net.preferIPv4Stack=true \ 12 | -Daeron.term.buffer.sparse.file=false \ 13 | -Daeron.threading.mode=SHARED \ 14 | -Dagrona.disable.bounds.checks=true \ 15 | -Dreactor.aeron.sample.embeddedMediaDriver=true \ 16 | -Dreactor.aeron.sample.idle.strategy=yielding \ 17 | -Dreactor.aeron.sample.frameCountLimit=16384 \ 18 | -Daeron.mtu.length=16k \ 19 | -Daeron.socket.so_sndbuf=2m \ 20 | -Daeron.socket.so_rcvbuf=2m \ 21 | -Daeron.rcv.initial.window.length=2m \ 22 | ${JVM_OPTS} reactor.aeron.pure.ServerThroughput 23 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/pure/ping.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:+UnlockDiagnosticVMOptions \ 11 | -XX:GuaranteedSafepointInterval=300000 \ 12 | -Daeron.threading.mode=SHARED \ 13 | -Dagrona.disable.bounds.checks=true \ 14 | -Dreactor.aeron.sample.embeddedMediaDriver=true \ 15 | -Dreactor.aeron.sample.exclusive.publications=true \ 16 | -Dreactor.aeron.sample.idle.strategy=yielding \ 17 | -Dreactor.aeron.sample.frameCountLimit=16384 \ 18 | -Dreactor.aeron.sample.messageLength=32 \ 19 | ${JVM_OPTS} reactor.aeron.pure.Ping 20 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/pure/pong.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:+UnlockDiagnosticVMOptions \ 11 | -XX:GuaranteedSafepointInterval=300000 \ 12 | -Daeron.threading.mode=SHARED \ 13 | -Dagrona.disable.bounds.checks=true \ 14 | -Dreactor.aeron.sample.embeddedMediaDriver=true \ 15 | -Dreactor.aeron.sample.exclusive.publications=true \ 16 | -Dreactor.aeron.sample.idle.strategy=yielding \ 17 | -Dreactor.aeron.sample.frameCountLimit=16384 \ 18 | ${JVM_OPTS} reactor.aeron.pure.Pong 19 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/reactor/aeron-client-simple-tps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:BiasedLockingStartupDelay=0 \ 11 | -Djava.net.preferIPv4Stack=true \ 12 | -Daeron.term.buffer.sparse.file=false \ 13 | -Daeron.threading.mode=SHARED \ 14 | -Dagrona.disable.bounds.checks=true \ 15 | -Dreactor.aeron.sample.idle.strategy=yielding \ 16 | -Dreactor.aeron.sample.frameCountLimit=16384 \ 17 | -Dreactor.aeron.sample.messageLength=1024 \ 18 | -Daeron.mtu.length=16k \ 19 | ${JVM_OPTS} reactor.aeron.ClientThroughput 20 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/reactor/aeron-client-tps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:BiasedLockingStartupDelay=0 \ 11 | -Djava.net.preferIPv4Stack=true \ 12 | -Daeron.term.buffer.sparse.file=false \ 13 | -Daeron.threading.mode=SHARED \ 14 | -Dagrona.disable.bounds.checks=true \ 15 | -Dreactor.aeron.sample.idle.strategy=yielding \ 16 | -Dreactor.aeron.sample.frameCountLimit=16384 \ 17 | -Dreactor.aeron.sample.messageLength=2048 \ 18 | -Daeron.mtu.length=16k \ 19 | -Daeron.socket.so_sndbuf=2m \ 20 | -Daeron.socket.so_rcvbuf=2m \ 21 | -Daeron.rcv.initial.window.length=2m \ 22 | ${JVM_OPTS} reactor.aeron.ClientThroughput 23 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/reactor/aeron-ping.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:+UnlockDiagnosticVMOptions \ 11 | -XX:GuaranteedSafepointInterval=300000 \ 12 | -Daeron.threading.mode=SHARED \ 13 | -Dagrona.disable.bounds.checks=true \ 14 | -Dreactor.aeron.sample.idle.strategy=yielding \ 15 | -Dreactor.aeron.sample.frameCountLimit=16384 \ 16 | -Dreactor.aeron.sample.messageLength=16 \ 17 | -Dreactor.aeron.sample.request=128 \ 18 | -Daeron.mtu.length=16k \ 19 | ${JVM_OPTS} reactor.aeron.AeronPingClient 20 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/reactor/aeron-pong.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:+UnlockDiagnosticVMOptions \ 11 | -XX:GuaranteedSafepointInterval=300000 \ 12 | -Daeron.threading.mode=SHARED \ 13 | -Dagrona.disable.bounds.checks=true \ 14 | -Dreactor.aeron.sample.idle.strategy=yielding \ 15 | -Dreactor.aeron.sample.frameCountLimit=16384 \ 16 | -Daeron.mtu.length=16k \ 17 | ${JVM_OPTS} reactor.aeron.AeronPongServer 18 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/reactor/aeron-server-simple-tps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:BiasedLockingStartupDelay=0 \ 11 | -Djava.net.preferIPv4Stack=true \ 12 | -Daeron.term.buffer.sparse.file=false \ 13 | -Daeron.threading.mode=SHARED \ 14 | -Dagrona.disable.bounds.checks=true \ 15 | -Dreactor.aeron.sample.idle.strategy=yielding \ 16 | -Dreactor.aeron.sample.frameCountLimit=16384 \ 17 | -Daeron.mtu.length=16k \ 18 | ${JVM_OPTS} reactor.aeron.ServerThroughput 19 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/reactor/aeron-server-tps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:BiasedLockingStartupDelay=0 \ 11 | -Djava.net.preferIPv4Stack=true \ 12 | -Daeron.term.buffer.sparse.file=false \ 13 | -Daeron.threading.mode=SHARED \ 14 | -Dagrona.disable.bounds.checks=true \ 15 | -Dreactor.aeron.sample.idle.strategy=yielding \ 16 | -Dreactor.aeron.sample.frameCountLimit=16384 \ 17 | -Daeron.mtu.length=16k \ 18 | -Daeron.socket.so_sndbuf=2m \ 19 | -Daeron.socket.so_rcvbuf=2m \ 20 | -Daeron.rcv.initial.window.length=2m \ 21 | ${JVM_OPTS} reactor.aeron.ServerThroughput 22 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/rsocket/aeron/rsocket-aeron-client-simple-tps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:BiasedLockingStartupDelay=0 \ 11 | -Djava.net.preferIPv4Stack=true \ 12 | -Daeron.term.buffer.sparse.file=false \ 13 | -Daeron.threading.mode=SHARED \ 14 | -Dagrona.disable.bounds.checks=true \ 15 | -Dreactor.aeron.sample.idle.strategy=yielding \ 16 | -Dreactor.aeron.sample.frameCountLimit=16384 \ 17 | -Daeron.mtu.length=16k \ 18 | ${JVM_OPTS} reactor.aeron.rsocket.aeron.RSocketAeronClientTps 19 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/rsocket/aeron/rsocket-aeron-client-tps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:BiasedLockingStartupDelay=0 \ 11 | -Djava.net.preferIPv4Stack=true \ 12 | -Daeron.term.buffer.sparse.file=false \ 13 | -Daeron.threading.mode=SHARED \ 14 | -Dagrona.disable.bounds.checks=true \ 15 | -Dreactor.aeron.sample.idle.strategy=yielding \ 16 | -Dreactor.aeron.sample.frameCountLimit=16384 \ 17 | -Daeron.mtu.length=16k \ 18 | -Daeron.socket.so_sndbuf=2m \ 19 | -Daeron.socket.so_rcvbuf=2m \ 20 | -Daeron.rcv.initial.window.length=2m \ 21 | ${JVM_OPTS} reactor.aeron.rsocket.aeron.RSocketAeronClientTps 22 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/rsocket/aeron/rsocket-aeron-ping.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:+UnlockDiagnosticVMOptions \ 11 | -XX:GuaranteedSafepointInterval=300000 \ 12 | -Daeron.threading.mode=SHARED \ 13 | -Dagrona.disable.bounds.checks=true \ 14 | -Dreactor.aeron.sample.idle.strategy=yielding \ 15 | -Dreactor.aeron.sample.frameCountLimit=16384 \ 16 | -Dreactor.aeron.sample.messageLength=16 \ 17 | -Dreactor.aeron.sample.request=128 \ 18 | -Daeron.mtu.length=16k \ 19 | ${JVM_OPTS} reactor.aeron.rsocket.aeron.RSocketAeronPing 20 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/rsocket/aeron/rsocket-aeron-pong.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:+UnlockDiagnosticVMOptions \ 11 | -XX:GuaranteedSafepointInterval=300000 \ 12 | -Daeron.threading.mode=SHARED \ 13 | -Dagrona.disable.bounds.checks=true \ 14 | -Dreactor.aeron.sample.idle.strategy=yielding \ 15 | -Dreactor.aeron.sample.frameCountLimit=16384 \ 16 | -Daeron.mtu.length=16k \ 17 | ${JVM_OPTS} reactor.aeron.rsocket.aeron.RSocketAeronPong 18 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/rsocket/aeron/rsocket-aeron-server-simple-tps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:BiasedLockingStartupDelay=0 \ 11 | -Djava.net.preferIPv4Stack=true \ 12 | -Daeron.term.buffer.sparse.file=false \ 13 | -Daeron.threading.mode=SHARED \ 14 | -Dagrona.disable.bounds.checks=true \ 15 | -Dreactor.aeron.sample.idle.strategy=yielding \ 16 | -Dreactor.aeron.sample.frameCountLimit=16384 \ 17 | -Dreactor.aeron.sample.messageLength=1024 \ 18 | -Daeron.mtu.length=16k \ 19 | ${JVM_OPTS} reactor.aeron.rsocket.aeron.RSocketAeronServerTps 20 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/rsocket/aeron/rsocket-aeron-server-tps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:BiasedLockingStartupDelay=0 \ 11 | -Djava.net.preferIPv4Stack=true \ 12 | -Daeron.term.buffer.sparse.file=false \ 13 | -Daeron.threading.mode=SHARED \ 14 | -Dagrona.disable.bounds.checks=true \ 15 | -Dreactor.aeron.sample.idle.strategy=yielding \ 16 | -Dreactor.aeron.sample.frameCountLimit=16384 \ 17 | -Dreactor.aeron.sample.messageLength=1024 \ 18 | -Daeron.mtu.length=16k \ 19 | -Daeron.socket.so_sndbuf=2m \ 20 | -Daeron.socket.so_rcvbuf=2m \ 21 | -Daeron.rcv.initial.window.length=2m \ 22 | ${JVM_OPTS} reactor.aeron.rsocket.aeron.RSocketAeronServerTps 23 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/rsocket/netty/rsocket-netty-client-tps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:+UnlockDiagnosticVMOptions \ 11 | -XX:GuaranteedSafepointInterval=300000 \ 12 | ${JVM_OPTS} reactor.aeron.rsocket.netty.RSocketNettyClientTps 13 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/rsocket/netty/rsocket-netty-ping.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:+UnlockDiagnosticVMOptions \ 11 | -XX:GuaranteedSafepointInterval=300000 \ 12 | -Dreactor.aeron.sample.messageLength=1024 \ 13 | ${JVM_OPTS} reactor.aeron.rsocket.netty.RSocketNettyPing 14 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/rsocket/netty/rsocket-netty-pong.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:+UnlockDiagnosticVMOptions \ 11 | -XX:GuaranteedSafepointInterval=300000 \ 12 | ${JVM_OPTS} reactor.aeron.rsocket.netty.RSocketNettyPong 13 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/rsocket/netty/rsocket-netty-server-tps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | cd ../../../ 5 | 6 | JAR_FILE=$(ls target/reactor-aeron-benchmarks*.jar |grep jar) 7 | 8 | java \ 9 | -cp ${JAR_FILE}:target/lib/* \ 10 | -XX:+UnlockDiagnosticVMOptions \ 11 | -XX:GuaranteedSafepointInterval=300000 \ 12 | -Dreactor.aeron.sample.messageLength=1024 \ 13 | ${JVM_OPTS} reactor.aeron.rsocket.netty.RSocketNettyServerTps 14 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/scripts/setup-env.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | declare -a addresses=("ip1", "ip2") 4 | 5 | CERT_PATH= 6 | SRC_PATH= 7 | USER_NAME= 8 | 9 | cd $SRC_PATH 10 | 11 | mvn clean install -DskipTests 12 | 13 | for addr in ${addresses[@]} 14 | do 15 | echo "####### Setting up for: #######" 16 | echo "$addr" 17 | ssh -oStrictHostKeyChecking=no -i $CERT_PATH $USER_NAME@$addr 'sudo rm -rf /tmp/*' 18 | ssh -oStrictHostKeyChecking=no -i $CERT_PATH $USER_NAME@$addr 'mkdir -p /tmp/reactor-aeron/scripts/' 19 | ssh -oStrictHostKeyChecking=no -i $CERT_PATH $USER_NAME@$addr 'mkdir -p /tmp/reactor-aeron/target/lib' 20 | scp -r -i $CERT_PATH $SRC_PATH/reactor-aeron-benchmarks/target/*.jar $USER_NAME@$addr:/tmp/reactor-aeron/target/ 21 | scp -r -i $CERT_PATH $SRC_PATH/reactor-aeron-benchmarks/target/lib/* $USER_NAME@$addr:/tmp/reactor-aeron/target/lib/ 22 | scp -r -i $CERT_PATH $SRC_PATH/reactor-aeron-benchmarks/scripts/* $USER_NAME@$addr:/tmp/reactor-aeron/scripts/ 23 | done 24 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPingClient.java: -------------------------------------------------------------------------------- 1 | package reactor.aeron; 2 | 3 | import io.aeron.driver.Configuration; 4 | import java.time.Duration; 5 | import java.util.concurrent.TimeUnit; 6 | import org.HdrHistogram.Recorder; 7 | import org.agrona.BitUtil; 8 | import org.agrona.BufferUtil; 9 | import org.agrona.DirectBuffer; 10 | import org.agrona.concurrent.UnsafeBuffer; 11 | import org.agrona.console.ContinueBarrier; 12 | import reactor.core.Disposable; 13 | import reactor.core.publisher.Flux; 14 | import reactor.core.publisher.Mono; 15 | 16 | public final class AeronPingClient { 17 | 18 | private static final Recorder HISTOGRAM = new Recorder(TimeUnit.SECONDS.toNanos(10), 3); 19 | private static final LatencyReporter reporter = new LatencyReporter(HISTOGRAM); 20 | 21 | /** 22 | * Main runner. 23 | * 24 | * @param args program arguments. 25 | */ 26 | public static void main(String... args) { 27 | 28 | AeronResources resources = 29 | new AeronResources() 30 | .useTmpDir() 31 | .pollFragmentLimit(Configurations.FRAGMENT_COUNT_LIMIT) 32 | .singleWorker() 33 | .workerIdleStrategySupplier(Configurations::idleStrategy) 34 | .start() 35 | .block(); 36 | 37 | AeronConnection connection = 38 | AeronClient.create(resources) 39 | .options( 40 | Configurations.MDC_ADDRESS, 41 | Configurations.MDC_PORT, 42 | Configurations.MDC_CONTROL_PORT) 43 | .connect() 44 | .block(); 45 | 46 | System.out.println( 47 | "address: " 48 | + Configurations.MDC_ADDRESS 49 | + ", port: " 50 | + Configurations.MDC_PORT 51 | + ", controlPort: " 52 | + Configurations.MDC_CONTROL_PORT); 53 | System.out.println("MediaDriver THREADING_MODE: " + Configuration.THREADING_MODE_DEFAULT); 54 | System.out.println("Message length of " + Configurations.MESSAGE_LENGTH + " bytes"); 55 | System.out.println("pollFragmentLimit of " + Configurations.FRAGMENT_COUNT_LIMIT); 56 | System.out.println( 57 | "Using worker idle strategy " 58 | + Configurations.idleStrategy().getClass() 59 | + "(" 60 | + Configurations.IDLE_STRATEGY 61 | + ")"); 62 | System.out.println("Request " + Configurations.REQUESTED); 63 | 64 | ContinueBarrier barrier = new ContinueBarrier("Execute again?"); 65 | do { 66 | System.out.println("Pinging " + Configurations.NUMBER_OF_MESSAGES + " messages"); 67 | roundTripMessages(connection, Configurations.NUMBER_OF_MESSAGES); 68 | System.out.println("Histogram of RTT latencies in microseconds."); 69 | } while (barrier.await()); 70 | 71 | connection.dispose(); 72 | 73 | connection.onDispose(resources).onDispose().block(); 74 | } 75 | 76 | private static void roundTripMessages(AeronConnection connection, long count) { 77 | HISTOGRAM.reset(); 78 | 79 | Disposable disp = reporter.start(); 80 | 81 | NanoTimeGeneratorHandler handler = new NanoTimeGeneratorHandler(); 82 | 83 | connection.outbound().send(Flux.range(0, Configurations.REQUESTED), handler).then().subscribe(); 84 | 85 | connection 86 | .outbound() 87 | .send( 88 | connection 89 | .inbound() 90 | .receive() 91 | .take(count) 92 | .doOnNext( 93 | buffer -> { 94 | long start = buffer.getLong(0); 95 | long diff = System.nanoTime() - start; 96 | HISTOGRAM.recordValue(diff); 97 | }), 98 | handler) 99 | .then( 100 | Mono.defer( 101 | () -> Mono.delay(Duration.ofMillis(100)).doOnSubscribe(s -> disp.dispose()).then())) 102 | .then() 103 | .block(); 104 | } 105 | 106 | private static class NanoTimeGeneratorHandler implements DirectBufferHandler { 107 | private static final UnsafeBuffer OFFER_BUFFER = 108 | new UnsafeBuffer( 109 | BufferUtil.allocateDirectAligned( 110 | Configurations.MESSAGE_LENGTH, BitUtil.CACHE_LINE_LENGTH)); 111 | 112 | @Override 113 | public int estimateLength(Object ignore) { 114 | return Configurations.MESSAGE_LENGTH; 115 | } 116 | 117 | @Override 118 | public DirectBuffer map(Object ignore, int length) { 119 | OFFER_BUFFER.putLong(0, System.nanoTime()); 120 | return OFFER_BUFFER; 121 | } 122 | 123 | @Override 124 | public void dispose(Object ignore) {} 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/src/main/java/reactor/aeron/AeronPongServer.java: -------------------------------------------------------------------------------- 1 | package reactor.aeron; 2 | 3 | import io.aeron.driver.Configuration; 4 | 5 | public final class AeronPongServer { 6 | 7 | /** 8 | * Main runner. 9 | * 10 | * @param args program arguments. 11 | */ 12 | public static void main(String... args) { 13 | printSettings(); 14 | 15 | AeronResources resources = 16 | new AeronResources() 17 | .useTmpDir() 18 | .pollFragmentLimit(Configurations.FRAGMENT_COUNT_LIMIT) 19 | .singleWorker() 20 | .workerIdleStrategySupplier(Configurations::idleStrategy) 21 | .start() 22 | .block(); 23 | 24 | AeronServer.create(resources) 25 | .options( 26 | Configurations.MDC_ADDRESS, Configurations.MDC_PORT, Configurations.MDC_CONTROL_PORT) 27 | .handle( 28 | connection -> 29 | connection 30 | .outbound() 31 | .send(connection.inbound().receive()) 32 | .then(connection.onDispose())) 33 | .bind() 34 | .block() 35 | .onDispose(resources) 36 | .onDispose() 37 | .block(); 38 | } 39 | 40 | private static void printSettings() { 41 | System.out.println( 42 | "address: " 43 | + Configurations.MDC_ADDRESS 44 | + ", port: " 45 | + Configurations.MDC_PORT 46 | + ", controlPort: " 47 | + Configurations.MDC_CONTROL_PORT); 48 | System.out.println("MediaDriver THREADING_MODE: " + Configuration.THREADING_MODE_DEFAULT); 49 | System.out.println("pollFragmentLimit of " + Configurations.FRAGMENT_COUNT_LIMIT); 50 | System.out.println( 51 | "Using worker idle strategy " 52 | + Configurations.idleStrategy().getClass() 53 | + "(" 54 | + Configurations.IDLE_STRATEGY 55 | + ")"); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/src/main/java/reactor/aeron/ClientDemo.java: -------------------------------------------------------------------------------- 1 | package reactor.aeron; 2 | 3 | import java.util.stream.Stream; 4 | import reactor.core.publisher.Flux; 5 | 6 | public class ClientDemo { 7 | 8 | /** 9 | * Main runner. 10 | * 11 | * @param args program arguments. 12 | */ 13 | public static void main(String[] args) { 14 | AeronResources resources = new AeronResources().useTmpDir().start().block(); 15 | 16 | AeronClient.create(resources) 17 | .options("localhost", 13000, 13001) 18 | .handle( 19 | connection1 -> { 20 | System.out.println("Handler invoked"); 21 | return connection1 22 | .outbound() 23 | .sendString(Flux.fromStream(Stream.of("Hello", "world!")).log("send")) 24 | .then(connection1.onDispose()); 25 | }) 26 | .connect() 27 | .block() 28 | .onDispose(resources) 29 | .onDispose() 30 | .block(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/src/main/java/reactor/aeron/ClientServerSends.java: -------------------------------------------------------------------------------- 1 | package reactor.aeron; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import java.nio.charset.Charset; 6 | import org.agrona.DirectBuffer; 7 | import reactor.core.CoreSubscriber; 8 | import reactor.core.publisher.Flux; 9 | import reactor.core.publisher.FluxOperator; 10 | 11 | public class ClientServerSends { 12 | 13 | /** 14 | * Main runner. 15 | * 16 | * @param args program arguments. 17 | */ 18 | public static void main(String[] args) { 19 | AeronResources resources = new AeronResources().useTmpDir().start().block(); 20 | 21 | AeronClient.create(resources) 22 | .options("localhost", 13000, 13001) 23 | .handle( 24 | connection -> 25 | connection 26 | .inbound() 27 | .receive() 28 | .as(ByteBufFlux::create) 29 | .asString() 30 | .log("receive") 31 | .then(connection.onDispose())) 32 | .connect() 33 | .block() 34 | .onDispose(resources) 35 | .onDispose() 36 | .block(); 37 | } 38 | 39 | static class ByteBufFlux extends FluxOperator { 40 | 41 | public ByteBufFlux(Flux source) { 42 | super(source); 43 | } 44 | 45 | public static ByteBufFlux create(Flux directBufferFlux) { 46 | return new ByteBufFlux( 47 | directBufferFlux.map( 48 | buffer -> { 49 | byte[] bytes = new byte[buffer.capacity()]; 50 | buffer.getBytes(0, bytes); 51 | return Unpooled.copiedBuffer(bytes); 52 | })); 53 | } 54 | 55 | @Override 56 | public void subscribe(CoreSubscriber actual) { 57 | source.subscribe(actual); 58 | } 59 | 60 | public Flux asString() { 61 | return map(buffer -> buffer.toString(Charset.defaultCharset())); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/src/main/java/reactor/aeron/ClientThroughput.java: -------------------------------------------------------------------------------- 1 | package reactor.aeron; 2 | 3 | import io.aeron.driver.Configuration; 4 | import org.agrona.BitUtil; 5 | import org.agrona.BufferUtil; 6 | import org.agrona.concurrent.UnsafeBuffer; 7 | import reactor.core.publisher.Flux; 8 | 9 | public class ClientThroughput { 10 | 11 | private static final UnsafeBuffer OFFER_BUFFER = 12 | new UnsafeBuffer( 13 | BufferUtil.allocateDirectAligned( 14 | Configurations.MESSAGE_LENGTH, BitUtil.CACHE_LINE_LENGTH)); 15 | 16 | /** 17 | * Main runner. 18 | * 19 | * @param args program arguments. 20 | */ 21 | public static void main(String[] args) { 22 | 23 | System.out.println( 24 | "address: " 25 | + Configurations.MDC_ADDRESS 26 | + ", port: " 27 | + Configurations.MDC_PORT 28 | + ", controlPort: " 29 | + Configurations.MDC_CONTROL_PORT); 30 | System.out.println("MediaDriver THREADING_MODE: " + Configuration.THREADING_MODE_DEFAULT); 31 | System.out.println("Message length of " + Configurations.MESSAGE_LENGTH + " bytes"); 32 | System.out.println("pollFragmentLimit of " + Configurations.FRAGMENT_COUNT_LIMIT); 33 | System.out.println( 34 | "Using worker idle strategy " 35 | + Configurations.idleStrategy().getClass() 36 | + "(" 37 | + Configurations.IDLE_STRATEGY 38 | + ")"); 39 | 40 | AeronResources aeronResources = 41 | new AeronResources() 42 | .useTmpDir() 43 | .pollFragmentLimit(Configurations.FRAGMENT_COUNT_LIMIT) 44 | .singleWorker() 45 | .workerIdleStrategySupplier(Configurations::idleStrategy) 46 | .start() 47 | .block(); 48 | 49 | AeronClient.create(aeronResources) 50 | .options( 51 | Configurations.MDC_ADDRESS, Configurations.MDC_PORT, Configurations.MDC_CONTROL_PORT) 52 | .handle( 53 | connection -> 54 | connection 55 | .outbound() 56 | .send( 57 | Flux.range(0, Byte.MAX_VALUE) 58 | .repeat(Integer.MAX_VALUE) 59 | .map(i -> OFFER_BUFFER)) 60 | .then(connection.onDispose())) 61 | .connect() 62 | .block() 63 | .onDispose() 64 | .doFinally(s -> aeronResources.dispose()) 65 | .then(aeronResources.onDispose()) 66 | .block(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/src/main/java/reactor/aeron/Configurations.java: -------------------------------------------------------------------------------- 1 | package reactor.aeron; 2 | 3 | import io.aeron.Image; 4 | import io.aeron.Subscription; 5 | import org.agrona.concurrent.BackoffIdleStrategy; 6 | import org.agrona.concurrent.BusySpinIdleStrategy; 7 | import org.agrona.concurrent.IdleStrategy; 8 | import org.agrona.concurrent.NoOpIdleStrategy; 9 | import org.agrona.concurrent.SleepingIdleStrategy; 10 | import org.agrona.concurrent.SleepingMillisIdleStrategy; 11 | import org.agrona.concurrent.YieldingIdleStrategy; 12 | 13 | /** Configuration used for samples with defaults which can be overridden by system properties. */ 14 | public interface Configurations { 15 | int FRAGMENT_COUNT_LIMIT = Integer.getInteger("reactor.aeron.sample.frameCountLimit", 10); 16 | int MESSAGE_LENGTH = Integer.getInteger("reactor.aeron.sample.messageLength", 128); 17 | int REQUESTED = Integer.getInteger("reactor.aeron.sample.request", 16); 18 | 19 | int WARMUP_NUMBER_OF_ITERATIONS = Integer.getInteger("reactor.aeron.sample.warmup.iterations", 5); 20 | long WARMUP_NUMBER_OF_MESSAGES = Long.getLong("reactor.aeron.sample.warmup.messages", 10_000); 21 | long NUMBER_OF_MESSAGES = Long.getLong("reactor.aeron.sample.messages", 100_000_000); 22 | boolean EXCLUSIVE_PUBLICATIONS = 23 | Boolean.getBoolean("reactor.aeron.sample.exclusive.publications"); 24 | boolean EMBEDDED_MEDIA_DRIVER = Boolean.getBoolean("reactor.aeron.sample.embeddedMediaDriver"); 25 | boolean INFO_FLAG = Boolean.getBoolean("reactor.aeron.sample.info"); 26 | int PING_STREAM_ID = Integer.getInteger("reactor.aeron.sample.ping.streamId", 10); 27 | int PONG_STREAM_ID = Integer.getInteger("reactor.aeron.sample.pong.streamId", 10); 28 | String PING_CHANNEL = 29 | System.getProperty("reactor.aeron.sample.ping.channel", "aeron:udp?endpoint=localhost:40123"); 30 | String PONG_CHANNEL = 31 | System.getProperty("reactor.aeron.sample.pong.channel", "aeron:udp?endpoint=localhost:40124"); 32 | String MDC_ADDRESS = System.getProperty("reactor.aeron.sample.mdc.address", "localhost"); 33 | int MDC_PORT = Integer.getInteger("reactor.aeron.sample.mdc.port", 13000); 34 | int MDC_CONTROL_PORT = Integer.getInteger("reactor.aeron.sample.mdc.control.port", 13001); 35 | int MDC_STREAM_ID = Integer.getInteger("reactor.aeron.sample.mdc.stream.id", 0xcafe0000); 36 | int MDC_SESSION_ID = Integer.getInteger("reactor.aeron.sample.mdc.session.id", 1001); 37 | 38 | String IDLE_STRATEGY = System.getProperty("reactor.aeron.sample.idle.strategy", "busyspin"); 39 | long REPORT_INTERVAL = Long.getLong("reactor.aeron.sample.report.interval", 1); 40 | long TRACE_REPORTER_INTERVAL = Long.getLong("reactor.aeron.sample.report.interval", 60); 41 | long WARMUP_REPORT_DELAY = Long.getLong("reactor.aeron.sample.report.delay", REPORT_INTERVAL); 42 | String TARGET_FOLDER_FOLDER_LATENCY = 43 | System.getProperty( 44 | "reactor.aeron.report.traces.folder.latency", "./target/traces/reports/latency/"); 45 | String TARGET_FOLDER_FOLDER_THROUGHPUT = 46 | System.getProperty( 47 | "reactor.aeron.report.traces.folder.throughput", "./target/traces/reports/throughput/"); 48 | String REPORT_NAME = 49 | System.getProperty("reactor.aeron.report.name", String.valueOf(System.nanoTime())); 50 | 51 | /** 52 | * Returns idle strategy. 53 | * 54 | *

Examples: 55 | * 56 | *

    57 | *
  • {@link BackoffIdleStrategy} - backoff/1/1/1/100 58 | *
  • {@link BusySpinIdleStrategy} - busyspin 59 | *
  • {@link SleepingIdleStrategy} - sleeping/100 60 | *
  • {@link SleepingMillisIdleStrategy} - sleepingmillis/1 61 | *
  • {@link YieldingIdleStrategy} - yielding 62 | *
  • {@link NoOpIdleStrategy} - noop 63 | *
64 | * 65 | * @return idle strategy, {@link BusySpinIdleStrategy} - by default 66 | */ 67 | static IdleStrategy idleStrategy() { 68 | String[] chunks = IDLE_STRATEGY.split("/"); 69 | switch (chunks[0].toLowerCase()) { 70 | case "backoff": 71 | return new BackoffIdleStrategy( 72 | Long.parseLong(chunks[1]), 73 | Long.parseLong(chunks[2]), 74 | Long.parseLong(chunks[3]), 75 | Long.parseLong(chunks[4])); 76 | case "busyspin": 77 | return new BusySpinIdleStrategy(); 78 | case "sleeping": 79 | return new SleepingIdleStrategy(Long.parseLong(chunks[1])); 80 | case "sleepingmillis": 81 | return new SleepingMillisIdleStrategy(Long.parseLong(chunks[1])); 82 | case "yielding": 83 | return new YieldingIdleStrategy(); 84 | case "noop": 85 | return new NoOpIdleStrategy(); 86 | default: 87 | return new BusySpinIdleStrategy(); 88 | } 89 | } 90 | 91 | /** 92 | * Print the information for an available image to stdout. 93 | * 94 | * @param image that has been created 95 | */ 96 | static void printAvailableImage(final Image image) { 97 | final Subscription subscription = image.subscription(); 98 | System.out.println( 99 | String.format( 100 | "Available image on %s streamId=%d sessionId=%d from %s", 101 | subscription.channel(), 102 | subscription.streamId(), 103 | image.sessionId(), 104 | image.sourceIdentity())); 105 | } 106 | 107 | /** 108 | * Print the information for an unavailable image to stdout. 109 | * 110 | * @param image that has gone inactive 111 | */ 112 | static void printUnavailableImage(final Image image) { 113 | final Subscription subscription = image.subscription(); 114 | System.out.println( 115 | String.format( 116 | "Unavailable image on %s streamId=%d sessionId=%d", 117 | subscription.channel(), subscription.streamId(), image.sessionId())); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/src/main/java/reactor/aeron/LatencyReporter.java: -------------------------------------------------------------------------------- 1 | package reactor.aeron; 2 | 3 | import io.scalecube.trace.TraceReporter; 4 | import java.time.Duration; 5 | import org.HdrHistogram.Histogram; 6 | import org.HdrHistogram.Recorder; 7 | import reactor.core.Disposable; 8 | import reactor.core.Disposables; 9 | import reactor.core.publisher.Flux; 10 | import reactor.core.scheduler.Schedulers; 11 | 12 | public class LatencyReporter { 13 | 14 | private static final TraceReporter reporter = new TraceReporter(); 15 | 16 | private final Recorder histogram; 17 | private final String name; 18 | 19 | public LatencyReporter(Recorder histogram) { 20 | this(histogram, Configurations.REPORT_NAME); 21 | } 22 | 23 | public LatencyReporter(Recorder histogram, String name) { 24 | this.histogram = histogram; 25 | this.name = name; 26 | } 27 | 28 | public Disposable start() { 29 | return Disposables.composite(startReport(), startCollect()); 30 | } 31 | 32 | private Disposable startCollect() { 33 | return Flux.interval( 34 | Duration.ofSeconds(Configurations.WARMUP_REPORT_DELAY), 35 | Duration.ofSeconds(Configurations.REPORT_INTERVAL)) 36 | .publishOn(Schedulers.single()) 37 | .doOnNext(i -> this.collect()) 38 | .subscribe(); 39 | } 40 | 41 | private Disposable startReport() { 42 | return startReport(Configurations.TARGET_FOLDER_FOLDER_LATENCY); 43 | } 44 | 45 | private Disposable startReport(String folder) { 46 | return Flux.interval( 47 | Duration.ofSeconds(Configurations.WARMUP_REPORT_DELAY), 48 | Duration.ofSeconds(getReportInterval())) 49 | .publishOn(Schedulers.single()) 50 | .doOnNext(i -> this.report(folder)) 51 | .subscribe(); 52 | } 53 | 54 | private long getReportInterval() { 55 | if (reporter.isActive()) { 56 | return Configurations.TRACE_REPORTER_INTERVAL; 57 | } else { 58 | return Configurations.REPORT_INTERVAL; 59 | } 60 | } 61 | 62 | private void report(String folder) { 63 | if (reporter.isActive()) { 64 | reporter 65 | .sendToJsonbin() 66 | .subscribe( 67 | res -> { 68 | if (res.success()) { 69 | reporter.dumpToFile(folder, res.name(), res).subscribe(); 70 | } 71 | }); 72 | } else { 73 | System.out.println("---- PING/PONG HISTO ----"); 74 | histogram.getIntervalHistogram().outputPercentileDistribution(System.out, 5, 1000.0, false); 75 | System.out.println("---- PING/PONG HISTO ----"); 76 | } 77 | } 78 | 79 | private void collect() { 80 | Histogram h = histogram.getIntervalHistogram(); 81 | 82 | if (reporter.isActive()) { 83 | reporter.addY(this.name, h.getMean() / 1000.0); 84 | } else { 85 | System.out.println("---- PING/PONG HISTO ----"); 86 | h.outputPercentileDistribution(System.out, 5, 1000.0, false); 87 | System.out.println("---- PING/PONG HISTO ----"); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/src/main/java/reactor/aeron/RateReporter.java: -------------------------------------------------------------------------------- 1 | package reactor.aeron; 2 | 3 | import io.scalecube.trace.TraceReporter; 4 | import java.time.Duration; 5 | import java.util.concurrent.TimeUnit; 6 | import java.util.concurrent.atomic.LongAdder; 7 | import reactor.core.Disposable; 8 | import reactor.core.scheduler.Schedulers; 9 | 10 | /** Tracker and reporter of throughput rates. */ 11 | public class RateReporter implements Runnable, Disposable { 12 | 13 | private final long reportIntervalNs; 14 | private final Reporter reporter; 15 | private final Disposable disposable; 16 | 17 | private final LongAdder totalBytes = new LongAdder(); 18 | private final LongAdder totalMessages = new LongAdder(); 19 | 20 | private long lastTotalBytes; 21 | private long lastTotalMessages; 22 | private long lastTimestamp; 23 | private String name; 24 | 25 | private static final TraceReporter traceReporter = new TraceReporter(); 26 | 27 | public RateReporter() { 28 | this(Configurations.REPORT_NAME); 29 | } 30 | 31 | public RateReporter(String name) { 32 | this(name, Configurations.TARGET_FOLDER_FOLDER_THROUGHPUT); 33 | } 34 | 35 | public RateReporter(String name, String location) { 36 | this(RateReporter::printRate, name, location); 37 | } 38 | 39 | /** 40 | * Create rate reporter. 41 | * 42 | * @param reporter reporter function 43 | */ 44 | private RateReporter(Reporter reporter, String name, String location) { 45 | this.name = name; 46 | long reportDelayNs = Duration.ofSeconds(Configurations.WARMUP_REPORT_DELAY).toNanos(); 47 | this.reportIntervalNs = Duration.ofSeconds(Configurations.REPORT_INTERVAL).toNanos(); 48 | this.reporter = reporter; 49 | disposable = 50 | Schedulers.single() 51 | .schedulePeriodically(this, reportDelayNs, reportIntervalNs, TimeUnit.NANOSECONDS); 52 | 53 | if (traceReporter.isActive()) { 54 | traceReporter.scheduleDumpTo( 55 | Duration.ofSeconds(Configurations.TRACE_REPORTER_INTERVAL), location); 56 | } 57 | } 58 | 59 | @Override 60 | public void run() { 61 | long currentTotalMessages = totalMessages.longValue(); 62 | long currentTotalBytes = totalBytes.longValue(); 63 | long currentTimestamp = System.nanoTime(); 64 | 65 | long timeSpanNs = currentTimestamp - lastTimestamp; 66 | double messagesPerSec = 67 | ((currentTotalMessages - lastTotalMessages) * (double) reportIntervalNs) 68 | / (double) timeSpanNs; 69 | final double bytesPerSec = 70 | ((currentTotalBytes - lastTotalBytes) * (double) reportIntervalNs) / (double) timeSpanNs; 71 | 72 | if (traceReporter.isActive()) { 73 | traceReporter.addY(name, messagesPerSec); 74 | } 75 | reporter.onReport(messagesPerSec, bytesPerSec, currentTotalMessages, currentTotalBytes); 76 | 77 | lastTotalBytes = currentTotalBytes; 78 | lastTotalMessages = currentTotalMessages; 79 | lastTimestamp = currentTimestamp; 80 | } 81 | 82 | @Override 83 | public void dispose() { 84 | disposable.dispose(); 85 | } 86 | 87 | @Override 88 | public boolean isDisposed() { 89 | return disposable.isDisposed(); 90 | } 91 | 92 | /** 93 | * Notify rate reporter of number of messages and bytes received, sent, etc. 94 | * 95 | * @param messages received, sent, etc. 96 | * @param bytes received, sent, etc. 97 | */ 98 | public void onMessage(final long messages, final long bytes) { 99 | totalBytes.add(bytes); 100 | totalMessages.add(messages); 101 | } 102 | 103 | private static void printRate( 104 | final double messagesPerSec, 105 | final double bytesPerSec, 106 | final long totalFragments, 107 | final long totalBytes) { 108 | 109 | System.out.format( 110 | "%.07g msgs/sec, %.07g MB/sec, totals %d messages %d MB payloads%n", 111 | messagesPerSec, bytesPerSec / (1024 * 1024), totalFragments, totalBytes / (1024 * 1024)); 112 | } 113 | 114 | /** Interface for reporting of rate information. */ 115 | @FunctionalInterface 116 | public interface Reporter { 117 | /** 118 | * Called for a rate report. 119 | * 120 | * @param messagesPerSec since last report 121 | * @param bytesPerSec since last report 122 | * @param totalMessages since beginning of reporting 123 | * @param totalBytes since beginning of reporting 124 | */ 125 | void onReport(double messagesPerSec, double bytesPerSec, long totalMessages, long totalBytes); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerDemo.java: -------------------------------------------------------------------------------- 1 | package reactor.aeron; 2 | 3 | public class ServerDemo { 4 | 5 | /** 6 | * Main runner. 7 | * 8 | * @param args program arguments. 9 | */ 10 | public static void main(String[] args) { 11 | AeronResources resources = new AeronResources().useTmpDir().start().block(); 12 | 13 | AeronServer.create(resources) 14 | .options("localhost", 13000, 13001) 15 | .handle( 16 | connection -> 17 | connection 18 | .inbound() 19 | .receive() 20 | .asString() 21 | .log("receive") 22 | .then(connection.onDispose())) 23 | .bind() 24 | .block() 25 | .onDispose(resources) 26 | .onDispose() 27 | .block(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerServerSends.java: -------------------------------------------------------------------------------- 1 | package reactor.aeron; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import java.nio.charset.Charset; 6 | import java.time.Duration; 7 | import org.agrona.DirectBuffer; 8 | import org.agrona.concurrent.UnsafeBuffer; 9 | import reactor.core.publisher.Flux; 10 | 11 | public class ServerServerSends { 12 | 13 | /** 14 | * Main runner. 15 | * 16 | * @param args program arguments. 17 | */ 18 | public static void main(String[] args) { 19 | AeronResources resources = new AeronResources().useTmpDir().start().block(); 20 | 21 | AeronServer.create(resources) 22 | .options("localhost", 13000, 13001) 23 | .handle( 24 | connection -> 25 | connection 26 | .outbound() 27 | .send( 28 | Flux.range(1, 10000) 29 | .delayElements(Duration.ofMillis(250)) 30 | .map(String::valueOf) 31 | .log("send") 32 | .map(s -> Unpooled.copiedBuffer(s, Charset.defaultCharset())), 33 | ByteBufHandler.defaultInstance) 34 | .then(connection.onDispose())) 35 | .bind() 36 | .block() 37 | .onDispose(resources) 38 | .onDispose() 39 | .block(); 40 | } 41 | 42 | static class ByteBufHandler implements DirectBufferHandler { 43 | 44 | static final ByteBufHandler defaultInstance = new ByteBufHandler(); 45 | 46 | @Override 47 | public int estimateLength(ByteBuf buffer) { 48 | return buffer.readableBytes(); 49 | } 50 | 51 | @Override 52 | public DirectBuffer map(ByteBuf buffer, int length) { 53 | return new UnsafeBuffer(buffer.nioBuffer(), 0, length); 54 | } 55 | 56 | @Override 57 | public void dispose(ByteBuf buffer) { 58 | buffer.release(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/src/main/java/reactor/aeron/ServerThroughput.java: -------------------------------------------------------------------------------- 1 | package reactor.aeron; 2 | 3 | import io.aeron.driver.Configuration; 4 | 5 | public class ServerThroughput { 6 | 7 | /** 8 | * Main runner. 9 | * 10 | * @param args program arguments. 11 | */ 12 | public static void main(String[] args) { 13 | printSettings(); 14 | 15 | AeronResources aeronResources = 16 | new AeronResources() 17 | .useTmpDir() 18 | .pollFragmentLimit(Configurations.FRAGMENT_COUNT_LIMIT) 19 | .singleWorker() 20 | .workerIdleStrategySupplier(Configurations::idleStrategy) 21 | .start() 22 | .block(); 23 | 24 | RateReporter reporter = new RateReporter(); 25 | 26 | AeronServer.create(aeronResources) 27 | .options( 28 | Configurations.MDC_ADDRESS, Configurations.MDC_PORT, Configurations.MDC_CONTROL_PORT) 29 | .handle( 30 | connection -> 31 | connection 32 | .inbound() 33 | .receive() 34 | .doOnNext(buffer -> reporter.onMessage(1, buffer.capacity())) 35 | .then(connection.onDispose())) 36 | .bind() 37 | .block() 38 | .onDispose(reporter) 39 | .onDispose(aeronResources) 40 | .onDispose() 41 | .block(); 42 | } 43 | 44 | private static void printSettings() { 45 | System.out.println( 46 | "address: " 47 | + Configurations.MDC_ADDRESS 48 | + ", port: " 49 | + Configurations.MDC_PORT 50 | + ", controlPort: " 51 | + Configurations.MDC_CONTROL_PORT); 52 | System.out.println("MediaDriver THREADING_MODE: " + Configuration.THREADING_MODE_DEFAULT); 53 | System.out.println("pollFragmentLimit of " + Configurations.FRAGMENT_COUNT_LIMIT); 54 | System.out.println( 55 | "Using worker idle strategy " 56 | + Configurations.idleStrategy().getClass() 57 | + "(" 58 | + Configurations.IDLE_STRATEGY 59 | + ")"); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /reactor-aeron-benchmarks/src/main/java/reactor/aeron/netty/ReactorNettyClientPing.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.ChannelHandlerContext; 7 | import io.netty.channel.ChannelOption; 8 | import io.netty.channel.ChannelPipeline; 9 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 10 | import io.netty.handler.codec.LengthFieldPrepender; 11 | import io.netty.handler.codec.MessageToByteEncoder; 12 | import java.time.Duration; 13 | import java.util.Random; 14 | import java.util.concurrent.TimeUnit; 15 | import org.HdrHistogram.Recorder; 16 | import org.agrona.console.ContinueBarrier; 17 | import reactor.aeron.Configurations; 18 | import reactor.aeron.LatencyReporter; 19 | import reactor.core.Disposable; 20 | import reactor.core.publisher.Flux; 21 | import reactor.core.publisher.Mono; 22 | import reactor.netty.Connection; 23 | import reactor.netty.NettyPipeline.SendOptions; 24 | import reactor.netty.channel.BootstrapHandlers; 25 | import reactor.netty.resources.ConnectionProvider; 26 | import reactor.netty.resources.LoopResources; 27 | import reactor.netty.tcp.TcpClient; 28 | 29 | public class ReactorNettyClientPing { 30 | 31 | private static final Recorder HISTOGRAM = new Recorder(TimeUnit.SECONDS.toNanos(10), 3); 32 | 33 | private static final LatencyReporter latencyReporter = new LatencyReporter(HISTOGRAM); 34 | 35 | private static final ByteBuf PAYLOAD = 36 | ByteBufAllocator.DEFAULT.buffer(Configurations.MESSAGE_LENGTH); 37 | 38 | static { 39 | Random random = new Random(System.nanoTime()); 40 | byte[] bytes = new byte[Configurations.MESSAGE_LENGTH]; 41 | random.nextBytes(bytes); 42 | PAYLOAD.writeBytes(bytes); 43 | } 44 | 45 | /** 46 | * Main runner. 47 | * 48 | * @param args program arguments. 49 | */ 50 | public static void main(String[] args) { 51 | System.out.println( 52 | "message size: " 53 | + Configurations.MESSAGE_LENGTH 54 | + ", number of messages: " 55 | + Configurations.NUMBER_OF_MESSAGES 56 | + ", address: " 57 | + Configurations.MDC_ADDRESS 58 | + ", port: " 59 | + Configurations.MDC_PORT); 60 | 61 | LoopResources loopResources = LoopResources.create("reactor-netty"); 62 | 63 | Connection connection = 64 | TcpClient.create(ConnectionProvider.newConnection()) 65 | .runOn(loopResources) 66 | .host(Configurations.MDC_ADDRESS) 67 | .port(Configurations.MDC_PORT) 68 | .option(ChannelOption.TCP_NODELAY, true) 69 | .option(ChannelOption.SO_KEEPALIVE, true) 70 | .option(ChannelOption.SO_REUSEADDR, true) 71 | .doOnConnected(System.out::println) 72 | .bootstrap( 73 | b -> 74 | BootstrapHandlers.updateConfiguration( 75 | b, 76 | "channel", 77 | (connectionObserver, channel) -> { 78 | setupChannel(channel); 79 | })) 80 | .connectNow(); 81 | 82 | ContinueBarrier barrier = new ContinueBarrier("Execute again?"); 83 | do { 84 | System.out.println("Pinging " + Configurations.NUMBER_OF_MESSAGES + " messages"); 85 | roundTripMessages(connection, Configurations.NUMBER_OF_MESSAGES); 86 | System.out.println("Histogram of RTT latencies in microseconds."); 87 | } while (barrier.await()); 88 | 89 | connection.dispose(); 90 | 91 | connection.onDispose(loopResources).onDispose().block(); 92 | } 93 | 94 | private static void roundTripMessages(Connection connection, long count) { 95 | HISTOGRAM.reset(); 96 | 97 | Disposable disp = latencyReporter.start(); 98 | 99 | connection 100 | .outbound() 101 | .options(SendOptions::flushOnEach) 102 | .sendObject(Flux.range(0, Configurations.REQUESTED)) 103 | .then() 104 | .subscribe(); 105 | 106 | connection 107 | .outbound() 108 | .options(SendOptions::flushOnEach) 109 | .sendObject( 110 | connection 111 | .inbound() 112 | .receive() 113 | .retain() 114 | .take(count) 115 | .doOnNext( 116 | buffer -> { 117 | long start = buffer.readLong(); 118 | buffer.readerIndex(Configurations.MESSAGE_LENGTH); 119 | long diff = System.nanoTime() - start; 120 | HISTOGRAM.recordValue(diff); 121 | buffer.release(); 122 | }) 123 | .map(buffer -> 1)) 124 | .then( 125 | Mono.defer( 126 | () -> Mono.delay(Duration.ofMillis(100)).doOnSubscribe(s -> disp.dispose()).then())) 127 | .then() 128 | .block(); 129 | } 130 | 131 | private static void setupChannel(Channel channel) { 132 | final int maxFrameLength = 1024 * 1024; 133 | final int lengthFieldLength = 2; 134 | 135 | ChannelPipeline pipeline = channel.pipeline(); 136 | pipeline.addLast(new LengthFieldPrepender(lengthFieldLength)); 137 | pipeline.addLast( 138 | new LengthFieldBasedFrameDecoder( 139 | maxFrameLength, 0, lengthFieldLength, 0, lengthFieldLength)); 140 | pipeline.addLast( 141 | new MessageToByteEncoder() { 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 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 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> 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> 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> 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> handler() { 49 | return handler; 50 | } 51 | 52 | public AeronOptions handler( 53 | Function> 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 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 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 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 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 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> 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> 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 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 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 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 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 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 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 source) { 12 | super(source); 13 | } 14 | 15 | @Override 16 | public void subscribe(CoreSubscriber 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> handler) { 62 | return Mono.fromRunnable(() -> start0(handler)).thenReturn(this); 63 | } 64 | 65 | private void start0(Function> 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> 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 | --------------------------------------------------------------------------------