From f99a06fd6559818ade5e86623ab0053debaf60a1 Mon Sep 17 00:00:00 2001 From: robertroeser Date: Mon, 10 Aug 2015 11:58:30 -0700 Subject: [PATCH 001/105] Update .gitignore --- .gitignore | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..3be3ba898 --- /dev/null +++ b/.gitignore @@ -0,0 +1,68 @@ +# Compiled source # +################### +*.com +*.class +*.dll +*.exe +*.o +*.so + +# Packages # +############ +# it's better to unpack these files and commit the raw source +# git has its own built in compression methods +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip + +# Logs and databases # +###################### +*.log + +# OS generated files # +###################### +.DS_Store* +ehthumbs.db +Icon? +Thumbs.db + +# Editor Files # +################ +*~ +*.swp + +# Gradle Files # +################ +.gradle +.ivy2 +.ivy2.cache +.m2 + +# Build output directies +/target +*/target +/build +*/build + +# IntelliJ specific files/directories +out +.idea +*.ipr +*.iws +*.iml +atlassian-ide-plugin.xml + +# Eclipse specific files/directories +.classpath +.project +.settings +.metadata + +# NetBeans specific files/directories +.nbattrs +/bin From 52f9610cf5bdf041925f86a719e1d8fe48a2ff28 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Mon, 10 Aug 2015 12:04:11 -0700 Subject: [PATCH 002/105] adding travis and gradle wrapper --- .travis.yml | 26 +++++ build.gradle | 32 ++++++ buildViaTravis.sh | 16 +++ gradle.properties | 1 + gradlew | 164 ++++++++++++++++++++++++++++++ gradlew.bat | 90 ++++++++++++++++ settings.gradle | 1 + wrapper/gradle-wrapper.properties | 6 ++ 8 files changed, 336 insertions(+) create mode 100644 .travis.yml create mode 100644 build.gradle create mode 100755 buildViaTravis.sh create mode 100644 gradle.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 wrapper/gradle-wrapper.properties diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..0039232f7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,26 @@ +language: java +jdk: +- oraclejdk8 + +# force upgrade Java8 as per https://github.com/travis-ci/travis-ci/issues/4042 (fixes compilation issue) +addons: + apt: + packages: + - oracle-java8-installer + +sudo: false +# as per http://blog.travis-ci.com/2014-12-17-faster-builds-with-container-based-infrastructure/ + +# script for build and release via Travis to Bintray +script: gradle/buildViaTravis.sh + +# cache between builds +cache: + directories: + - $HOME/.m2 + - $HOME/.gradle + +env: + global: + - secure: cXHzd2WHqmdmJEyEKlELt8Rp9qCvhTRXTEHpQz0sKt55KorI8vO33sSOBs8uBqknWgGgOzHsB7cw0dJRxCmW+BRy90ELtdg/dVLzU8D8BrI6/DHzd/Bhyt9wx2eVdLmDV7lQ113AqJ7lphbH+U8ceTBlbNYDPKcIjFhsPO0WcPxQYed45na8XRK0UcAOpVmmNlTE6fHy5acQblNO84SN6uevCFqWAZJY7rc6xGrzFzca+ul5kR8xIzdE5jKs2Iw0MDeWi8cshkhj9c0FDtfsNIB1F+NafDtEdqjt6kMqYAUUiTAM2QdNoffzgmWEbVOj3uvthlm+S11XaU3Cn2uC7CiZTn2ebuoqCuV5Ge6KQI0ysEQVUfLhIF7iJG6dJvoyYy8ta8LEcjcsYAdF34BVddoUJkp+eJuhlto2aTZsDdXpmnwRM1PPDRoyrLjRcKiWYPR2tO2RG9sb0nRAGEpHTDd5ju2Ta4zpvgpWGUiKprs5R+YY7TEg16VSTYMmCJj5C9ap2lYIH4EoxsQpuxYig9AV1sOUJujLSa4TXqlcOmSM0IkHJ/i0VE8TZg4nV4XowyH6nKZ63InF4pUDcG13BpJQyTFKbK2D0lFn8MzpWvIV2oOUxNkOaOBg9cGhAnv9Sfw/Iv1UVaUgCNQd2M0R0rwfJoPCg2mmWVxsvh3cW4M= + - secure: UKZHoS/uw6SuAI9n0lCHWEc74H9+STpdvMmLd+nANjWkMFfo0bOUbm1SsV6PU6d2r8C5k4dEsW90J4diunR856R8vO+DpJPwUNJDuLm2Kiv7zhLJrXqpRTw3E3ijdFA84xocTN1CxZakW+ZP2wnb83jI3p99rgotc0i1wz9n1onaZrhZK5c3Rod2cdRig0wkeKK9NhwupXbXkpPtRNFRCOPgKvjPiEeW5YRZ/YxOs+OL9Sy6764b46EiWP/DFPGOTkJwz2mxLRT8sBx6rjeyf6v41NQPW1rlNwIDKcpnQl1n49k5SgARZvhFlakRdLyzljj1L0/VLk7xNDEQx3LYxl2mSl7AQlA8RYkxqirMRnIHHXrA7hhPuCYxp2nlpciwuh69vAOfliL3JeAsEgj0PKiQp7HQyBPQOvfmiGH2oIo+dkXvQwmLZTDnj9vNzZIS+rADbZoLzKftZKAUIWCze5zQ6mCkwKiuVYYWl2aPoy2XxRkA51t6sEHA0/iYrqaOX76WHGH0JhoAGWEIBNNP/rRnO38Rm96pm6SHrzLa1VxVFT6dRGljFTxvCsgsCfx/rRL+a1E0j89nLAmOGkDpyhUaKWqVQJWk3H1AeQ3cWGXfvUhDyaSTxcKs6AuQ2E5TtNgkbx0Xjq8NDjuiP57WDFYMXGvIqkgSzKG3A0DSMHI= diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..88b4b4236 --- /dev/null +++ b/build.gradle @@ -0,0 +1,32 @@ +buildscript { + repositories { + jcenter() + } + + dependencies { classpath 'io.reactivesocket:gradle-nebula-plugin-reactivesocket:1.0.0' } +} + +description = 'ReactiveSocket: stream oriented messaging passing with Reactive Stream semantics.' + +apply plugin: 'reactivesocket-project' +apply plugin: 'java' + +dependencies { + compile 'io.reactivex:rxjava:1.0.13' + compile 'io.reactivex:rxjava-reactive-streams:1.0.1' + compile 'org.reactivestreams:reactive-streams:1.0.0.final' + compile 'uk.co.real-logic:Agrona:0.4.2' + testCompile 'junit:junit-dep:4.10' + testCompile 'org.mockito:mockito-core:1.8.5' +} + + +// support for snapshot/final releases via versioned branch names like 1.x +nebulaRelease { + addReleaseBranchPattern(/\d+\.\d+\.\d+/) + addReleaseBranchPattern('HEAD') +} + +if (project.hasProperty('release.useLastTag')) { + tasks.prepare.enabled = false +} diff --git a/buildViaTravis.sh b/buildViaTravis.sh new file mode 100755 index 000000000..d98e5eb60 --- /dev/null +++ b/buildViaTravis.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# This script will build the project. + +if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then + echo -e "Build Pull Request #$TRAVIS_PULL_REQUEST => Branch [$TRAVIS_BRANCH]" + ./gradlew -Prelease.useLastTag=true build +elif [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" == "" ]; then + echo -e 'Build Branch with Snapshot => Branch ['$TRAVIS_BRANCH']' + ./gradlew -Prelease.travisci=true -PbintrayUser="${bintrayUser}" -PbintrayKey="${bintrayKey}" -PsonatypeUsername="${sonatypeUsername}" -PsonatypePassword="${sonatypePassword}" build snapshot --stacktrace +elif [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" != "" ]; then + echo -e 'Build Branch for Release => Branch ['$TRAVIS_BRANCH'] Tag ['$TRAVIS_TAG']' + ./gradlew -Prelease.travisci=true -Prelease.useLastTag=true -PbintrayUser="${bintrayUser}" -PbintrayKey="${bintrayKey}" -PsonatypeUsername="${sonatypeUsername}" -PsonatypePassword="${sonatypePassword}" final --stacktrace +else + echo -e 'WARN: Should not be here => Branch ['$TRAVIS_BRANCH'] Tag ['$TRAVIS_TAG'] Pull Request ['$TRAVIS_PULL_REQUEST']' + ./gradlew -Prelease.useLastTag=true build +fi diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000..ef6032984 --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +release.scope=patch diff --git a/gradlew b/gradlew new file mode 100755 index 000000000..91a7e269e --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..8a0b282aa --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..5aea80f18 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name='reactivesocket' diff --git a/wrapper/gradle-wrapper.properties b/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..aa1a4a0ed --- /dev/null +++ b/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Jul 23 15:18:42 PDT 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip From f9ecc6cc5c7f9bbe875d76c93574d859b6eb0839 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Mon, 10 Aug 2015 12:05:25 -0700 Subject: [PATCH 003/105] adding travis and gradle wrapper --- gradle/buildViaTravis.sh | 16 ++++++++++++++++ gradle/wrapper/gradle-wrapper.properties | 6 ++++++ 2 files changed, 22 insertions(+) create mode 100755 gradle/buildViaTravis.sh create mode 100644 gradle/wrapper/gradle-wrapper.properties diff --git a/gradle/buildViaTravis.sh b/gradle/buildViaTravis.sh new file mode 100755 index 000000000..d98e5eb60 --- /dev/null +++ b/gradle/buildViaTravis.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# This script will build the project. + +if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then + echo -e "Build Pull Request #$TRAVIS_PULL_REQUEST => Branch [$TRAVIS_BRANCH]" + ./gradlew -Prelease.useLastTag=true build +elif [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" == "" ]; then + echo -e 'Build Branch with Snapshot => Branch ['$TRAVIS_BRANCH']' + ./gradlew -Prelease.travisci=true -PbintrayUser="${bintrayUser}" -PbintrayKey="${bintrayKey}" -PsonatypeUsername="${sonatypeUsername}" -PsonatypePassword="${sonatypePassword}" build snapshot --stacktrace +elif [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" != "" ]; then + echo -e 'Build Branch for Release => Branch ['$TRAVIS_BRANCH'] Tag ['$TRAVIS_TAG']' + ./gradlew -Prelease.travisci=true -Prelease.useLastTag=true -PbintrayUser="${bintrayUser}" -PbintrayKey="${bintrayKey}" -PsonatypeUsername="${sonatypeUsername}" -PsonatypePassword="${sonatypePassword}" final --stacktrace +else + echo -e 'WARN: Should not be here => Branch ['$TRAVIS_BRANCH'] Tag ['$TRAVIS_TAG'] Pull Request ['$TRAVIS_PULL_REQUEST']' + ./gradlew -Prelease.useLastTag=true build +fi diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..aa1a4a0ed --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Jul 23 15:18:42 PDT 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip From ef8c4bd14a3d73604608ae17b6915c70e9e9e672 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Thu, 13 Aug 2015 16:46:27 -0700 Subject: [PATCH 004/105] initial commit --- .../aeron/AeronServerDuplexConnection.java | 7 ++++ .../aeron/ReactiveSocketAeronServer.java | 40 +++++++++++++++++++ .../aeron/ReactivesocketAeronClient.java | 7 ++++ 3 files changed, 54 insertions(+) create mode 100644 src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java create mode 100644 src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java create mode 100644 src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java diff --git a/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java new file mode 100644 index 000000000..1293553b3 --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java @@ -0,0 +1,7 @@ +package io.reactivesocket.aeron; + +/** + * Created by rroeser on 8/13/15. + */ +public class AeronServerDuplexConnection { +} diff --git a/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java new file mode 100644 index 000000000..99b108bc6 --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java @@ -0,0 +1,40 @@ +package io.reactivesocket.aeron; + +import io.reactivesocket.ReactiveSocketServerProtocol; +import io.reactivesocket.RequestHandler; +import uk.co.real_logic.aeron.Aeron; +import uk.co.real_logic.aeron.Image; + +/** + * Created by rroeser on 8/13/15. + */ +public class ReactiveSocketAeronServer { + private final ReactiveSocketServerProtocol rsServerProtocol; + + private final Aeron aeron; + + private final int SERVER_STREAM_ID = 1; + + private final int port; + + public ReactiveSocketAeronServer(int port, RequestHandler requestHandler) { + this.port = port; + + rsServerProtocol = ReactiveSocketServerProtocol.create(requestHandler); + + final Aeron.Context ctx = new Aeron.Context(); + ctx.newImageHandler(this::newImageHandler); + + aeron = Aeron.connect(ctx); + + aeron.addSubscription("udp://localhost:" + port, SERVER_STREAM_ID); + } + + void newImageHandler(Image image, String channel, int streamId, int sessionId, long joiningPosition, String sourceIdentity) { + if (SERVER_STREAM_ID == streamId) { + + } else { + System.out.println(""); + } + } +} diff --git a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java new file mode 100644 index 000000000..aa6e0f06c --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java @@ -0,0 +1,7 @@ +package io.reactivesocket.aeron; + +/** + * Created by rroeser on 8/13/15. + */ +public class ReactivesocketAeronClient { +} From d8f4bf917ea8a75a0570c011abd3be83486ccc38 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Thu, 13 Aug 2015 16:47:57 -0700 Subject: [PATCH 005/105] initial commit first commit --- build.gradle | 20 ++- settings.gradle | 2 +- .../aeron/AeronServerDuplexConnection.java | 76 +++++++++- .../aeron/ReactiveSocketAeronServer.java | 92 ++++++++++-- .../aeron/ReactivesocketAeronClient.java | 140 ++++++++++++++++++ wrapper/gradle-wrapper.properties | 6 - 6 files changed, 308 insertions(+), 28 deletions(-) delete mode 100644 wrapper/gradle-wrapper.properties diff --git a/build.gradle b/build.gradle index 88b4b4236..70eccbb8e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,9 +1,9 @@ buildscript { - repositories { - jcenter() - } + repositories { + jcenter() + } - dependencies { classpath 'io.reactivesocket:gradle-nebula-plugin-reactivesocket:1.0.0' } + dependencies { classpath 'io.reactivesocket:gradle-nebula-plugin-reactivesocket:1.0.0' } } description = 'ReactiveSocket: stream oriented messaging passing with Reactive Stream semantics.' @@ -11,11 +11,15 @@ description = 'ReactiveSocket: stream oriented messaging passing with Reactive S apply plugin: 'reactivesocket-project' apply plugin: 'java' +repositories { + maven { url 'https://oss.jfrog.org/libs-snapshot' } +} + dependencies { - compile 'io.reactivex:rxjava:1.0.13' - compile 'io.reactivex:rxjava-reactive-streams:1.0.1' - compile 'org.reactivestreams:reactive-streams:1.0.0.final' + compile 'io.reactivex:rxjava:1.0.13' + compile 'io.reactivesocket:reactivesocket:0.0.1-SNAPSHOT' compile 'uk.co.real-logic:Agrona:0.4.2' + compile 'uk.co.real-logic:aeron-all:0.1.2' testCompile 'junit:junit-dep:4.10' testCompile 'org.mockito:mockito-core:1.8.5' } @@ -29,4 +33,4 @@ nebulaRelease { if (project.hasProperty('release.useLastTag')) { tasks.prepare.enabled = false -} +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 5aea80f18..a5d203c41 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name='reactivesocket' +rootProject.name='reactivesocket-aeron-rxjava' diff --git a/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java index 1293553b3..9a1f939b7 100644 --- a/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java @@ -1,7 +1,75 @@ package io.reactivesocket.aeron; -/** - * Created by rroeser on 8/13/15. - */ -public class AeronServerDuplexConnection { +import io.reactivesocket.DuplexConnection; +import io.reactivesocket.Frame; +import org.reactivestreams.Publisher; +import rx.Observable; +import rx.RxReactiveStreams; +import rx.subjects.PublishSubject; +import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.aeron.logbuffer.BufferClaim; +import uk.co.real_logic.agrona.MutableDirectBuffer; + +import java.nio.ByteBuffer; + +public class AeronServerDuplexConnection implements DuplexConnection { + private static final byte[] EMTPY = new byte[0]; + + private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); + + private Publication publication; + private PublishSubject subject; + + private int aeronStreamId; + private int aeronSessionId; + + public AeronServerDuplexConnection( + Publication publication, + int aeronStreamId, + int aeronSessionId) { + this.publication = publication; + this.subject = PublishSubject.create(); + this.aeronStreamId = aeronStreamId; + this.aeronSessionId = aeronSessionId; + } + + PublishSubject getSubject() { + return subject; + } + + @Override + public Publisher getInput() { + return RxReactiveStreams.toPublisher(subject); + } + + public Publisher write(Publisher o) { + Observable req = RxReactiveStreams + .toObservable(o) + .map(frame -> { + final ByteBuffer byteBuffer = frame.getByteBuffer(); + + for (;;) { + final BufferClaim bufferClaim = bufferClaims.get(); + final long offer = publication.tryClaim(byteBuffer.capacity(), bufferClaim); + if (offer >= 0) { + try { + final MutableDirectBuffer buffer = bufferClaim.buffer(); + final int offset = bufferClaim.offset(); + buffer.putBytes(offset, byteBuffer, 0, byteBuffer.capacity()); + } finally { + bufferClaim.commit(); + } + + break; + } else if (Publication.NOT_CONNECTED == offer) { + throw new RuntimeException("not connected"); + } + + } + + return null; + }); + + return RxReactiveStreams.toPublisher(req); + } } diff --git a/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java index 99b108bc6..767ed8d9a 100644 --- a/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java @@ -1,40 +1,114 @@ package io.reactivesocket.aeron; +import io.reactivesocket.Frame; import io.reactivesocket.ReactiveSocketServerProtocol; import io.reactivesocket.RequestHandler; +import rx.Scheduler; +import rx.schedulers.Schedulers; +import rx.subjects.PublishSubject; import uk.co.real_logic.aeron.Aeron; +import uk.co.real_logic.aeron.FragmentAssembler; import uk.co.real_logic.aeron.Image; +import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.aeron.Subscription; +import uk.co.real_logic.aeron.logbuffer.Header; +import uk.co.real_logic.agrona.DirectBuffer; +import uk.co.real_logic.agrona.collections.Int2ObjectHashMap; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.ByteBuffer; + +public class ReactiveSocketAeronServer implements Closeable { -/** - * Created by rroeser on 8/13/15. - */ -public class ReactiveSocketAeronServer { private final ReactiveSocketServerProtocol rsServerProtocol; private final Aeron aeron; private final int SERVER_STREAM_ID = 1; + private final int CLIENT_STREAM_ID = 2; + private final int port; - public ReactiveSocketAeronServer(int port, RequestHandler requestHandler) { - this.port = port; + private final Int2ObjectHashMap connections; - rsServerProtocol = ReactiveSocketServerProtocol.create(requestHandler); + private final Scheduler.Worker worker; + + private final Subscription subscription; + + private volatile boolean running = true; + + private ReactiveSocketAeronServer(int port, RequestHandler requestHandler) { + this.port = port; + this.connections = new Int2ObjectHashMap<>(); final Aeron.Context ctx = new Aeron.Context(); ctx.newImageHandler(this::newImageHandler); aeron = Aeron.connect(ctx); - aeron.addSubscription("udp://localhost:" + port, SERVER_STREAM_ID); + subscription = aeron.addSubscription("udp://localhost:" + port, SERVER_STREAM_ID); + + final FragmentAssembler fragmentAssembler = new FragmentAssembler(this::fragmentHandler); + + worker = Schedulers.computation().createWorker(); + + poll(fragmentAssembler); + + rsServerProtocol = ReactiveSocketServerProtocol.create(requestHandler); + } + + public static ReactiveSocketAeronServer create(int port, RequestHandler requestHandler) { + return new ReactiveSocketAeronServer(port, requestHandler); + } + + public static ReactiveSocketAeronServer create(RequestHandler requestHandler) { + return new ReactiveSocketAeronServer(39790, requestHandler); + } + + void poll(FragmentAssembler fragmentAssembler) { + if (running) { + worker.schedule(() -> { + subscription.poll(fragmentAssembler, Integer.MAX_VALUE); + poll(fragmentAssembler); + }); + } + } + + void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) { + final int sessionId = header.sessionId(); + AeronServerDuplexConnection connection = connections.get(sessionId); + + if (connection != null) { + final PublishSubject subject = connection.getSubject(); + ByteBuffer bytes = ByteBuffer.allocate(buffer.capacity()); + buffer.getBytes(0, bytes, buffer.capacity()); + final Frame frame = Frame.from(bytes); + subject.onNext(frame); + } else { + System.out.println("No connection found for session id " + sessionId); + } } void newImageHandler(Image image, String channel, int streamId, int sessionId, long joiningPosition, String sourceIdentity) { if (SERVER_STREAM_ID == streamId) { + final AeronServerDuplexConnection connection = connections.computeIfAbsent(sessionId, (_s) -> { + final String responseChannel = "udp://" + sourceIdentity.substring(0, sourceIdentity.indexOf(':')) + ":" + port; + Publication publication = aeron.addPublication(responseChannel, CLIENT_STREAM_ID); + return new AeronServerDuplexConnection(publication, streamId, sessionId); + }); + rsServerProtocol.acceptConnection(connection); } else { - System.out.println(""); + System.out.println("Unsupported stream id " + streamId); } } + + @Override + public void close() throws IOException { + running = false; + worker.unsubscribe(); + aeron.close(); + } } diff --git a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java index aa6e0f06c..766b15efc 100644 --- a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java @@ -1,7 +1,147 @@ package io.reactivesocket.aeron; +import io.reactivesocket.DuplexConnection; +import io.reactivesocket.Frame; +import io.reactivesocket.ReactiveSocketClientProtocol; +import org.reactivestreams.Publisher; +import rx.Observable; +import rx.RxReactiveStreams; +import rx.Scheduler; +import rx.schedulers.Schedulers; +import rx.subjects.PublishSubject; +import uk.co.real_logic.aeron.Aeron; +import uk.co.real_logic.aeron.FragmentAssembler; +import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.aeron.Subscription; +import uk.co.real_logic.aeron.logbuffer.Header; +import uk.co.real_logic.agrona.DirectBuffer; +import uk.co.real_logic.agrona.collections.Int2ObjectHashMap; +import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; + +import java.nio.ByteBuffer; + /** * Created by rroeser on 8/13/15. */ public class ReactivesocketAeronClient { + private static final byte[] EMTPY = new byte[0]; + + private static final ThreadLocal buffers = ThreadLocal.withInitial(() -> new UnsafeBuffer(EMTPY)); + + private static final Int2ObjectHashMap subscriptions = new Int2ObjectHashMap<>(); + + private static final Int2ObjectHashMap> subjects = new Int2ObjectHashMap<>(); + + private static final int SERVER_STREAM_ID = 1; + + private static final int CLIENT_STREAM_ID = 2; + + private final ReactiveSocketClientProtocol rsClientProtocol; + + private final Aeron aeron; + + private volatile boolean running = true; + + private final int port; + + private ReactivesocketAeronClient(String host, int port) { + this.port = port; + + final Aeron.Context ctx = new Aeron.Context(); + aeron = Aeron.connect(ctx); + + final String channel = "udp://" + host + ":" + port; + + final Publication publication = aeron.addPublication(channel, SERVER_STREAM_ID); + + final int sessionId = publication.sessionId(); + + subjects.computeIfAbsent(sessionId, (_p) -> PublishSubject.create()); + + subscriptions.computeIfAbsent(port, (_p) -> { + Subscription subscription = aeron.addSubscription(channel, CLIENT_STREAM_ID); + + final FragmentAssembler fragmentAssembler = new FragmentAssembler(this::fragmentHandler); + + poll(fragmentAssembler, subscription, Schedulers.computation().createWorker()); + + return subscription; + }); + + this.rsClientProtocol = + ReactiveSocketClientProtocol.create(new DuplexConnection() { + + public Publisher getInput() { + PublishSubject publishSubject = subjects.get(port); + return RxReactiveStreams.toPublisher(publishSubject); + } + + @Override + public Publisher write(Publisher o) { + Observable req = RxReactiveStreams + .toObservable(o) + .map(frame -> { + final UnsafeBuffer buffer = buffers.get(); + ByteBuffer byteBuffer = frame.getByteBuffer(); + buffer.wrap(byteBuffer); + + for (;;) { + final long offer = publication.offer(buffer); + + if (offer >= 0) { + break; + } else if (Publication.NOT_CONNECTED == offer) { + throw new RuntimeException("not connected"); + } + } + + return null; + }); + + return RxReactiveStreams.toPublisher(req); + } + }); + } + + public static ReactivesocketAeronClient create(String host, int port) { + return new ReactivesocketAeronClient(host, port); + } + + public static ReactivesocketAeronClient create(String host) { + return new ReactivesocketAeronClient(host, 39790); + } + + void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) { + final PublishSubject subject = subjects.get(header.sessionId()); + ByteBuffer bytes = ByteBuffer.allocate(buffer.capacity()); + buffer.getBytes(0, bytes, buffer.capacity()); + final Frame frame = Frame.from(bytes); + subject.onNext(frame); + } + + void poll(FragmentAssembler fragmentAssembler, Subscription subscription, Scheduler.Worker worker) { + if (running) { + worker.schedule(() -> { + subscription.poll(fragmentAssembler, Integer.MAX_VALUE); + poll(fragmentAssembler, subscription, worker); + }); + } + } + + public Publisher requestResponse(String payload) { + return rsClientProtocol.requestResponse(payload); + } + + public Publisher requestStream(String payload) { + return rsClientProtocol.requestStream(payload); + } + + public Publisher fireAndForget(String payload) { + return rsClientProtocol.fireAndForget(payload); + } + + public Publisher requestSubscription(String payload) { + return rsClientProtocol.requestSubscription(payload); + } + } diff --git a/wrapper/gradle-wrapper.properties b/wrapper/gradle-wrapper.properties deleted file mode 100644 index aa1a4a0ed..000000000 --- a/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Thu Jul 23 15:18:42 PDT 2015 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip From 713f009057d5186394d2a835e0ea8953e3f16af4 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Fri, 14 Aug 2015 14:36:04 -0700 Subject: [PATCH 006/105] added code to wait until a connection is established --- .../aeron/AeronServerDuplexConnection.java | 54 +++++++++--- .../io/reactivesocket/aeron/Constants.java | 12 +++ .../io/reactivesocket/aeron/MessageType.java | 43 ++++++++++ .../aeron/ReactiveSocketAeronServer.java | 46 ++++++---- .../aeron/ReactivesocketAeronClient.java | 85 ++++++++++++++++--- .../aeron/ReactiveSocketAeronTest.java | 55 ++++++++++++ 6 files changed, 251 insertions(+), 44 deletions(-) create mode 100644 src/main/java/io/reactivesocket/aeron/Constants.java create mode 100644 src/main/java/io/reactivesocket/aeron/MessageType.java create mode 100644 src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java diff --git a/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java index 9a1f939b7..0e2678d24 100644 --- a/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java @@ -8,29 +8,23 @@ import rx.subjects.PublishSubject; import uk.co.real_logic.aeron.Publication; import uk.co.real_logic.aeron.logbuffer.BufferClaim; +import uk.co.real_logic.agrona.BitUtil; import uk.co.real_logic.agrona.MutableDirectBuffer; import java.nio.ByteBuffer; +import java.util.concurrent.TimeUnit; -public class AeronServerDuplexConnection implements DuplexConnection { - private static final byte[] EMTPY = new byte[0]; +public class AeronServerDuplexConnection implements DuplexConnection, AutoCloseable { private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); private Publication publication; private PublishSubject subject; - private int aeronStreamId; - private int aeronSessionId; - public AeronServerDuplexConnection( - Publication publication, - int aeronStreamId, - int aeronSessionId) { + Publication publication) { this.publication = publication; this.subject = PublishSubject.create(); - this.aeronStreamId = aeronStreamId; - this.aeronSessionId = aeronSessionId; } PublishSubject getSubject() { @@ -47,15 +41,16 @@ public Publisher write(Publisher o) { .toObservable(o) .map(frame -> { final ByteBuffer byteBuffer = frame.getByteBuffer(); - + final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; for (;;) { final BufferClaim bufferClaim = bufferClaims.get(); - final long offer = publication.tryClaim(byteBuffer.capacity(), bufferClaim); + final long offer = publication.tryClaim(length, bufferClaim); if (offer >= 0) { try { final MutableDirectBuffer buffer = bufferClaim.buffer(); final int offset = bufferClaim.offset(); - buffer.putBytes(offset, byteBuffer, 0, byteBuffer.capacity()); + buffer.putInt(offset, MessageType.FRAME.getEncodedType()); + buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, 0, byteBuffer.capacity()); } finally { bufferClaim.commit(); } @@ -72,4 +67,37 @@ public Publisher write(Publisher o) { return RxReactiveStreams.toPublisher(req); } + + void establishConnection() { + final long start = System.nanoTime(); + final int sessionId = publication.sessionId(); + final BufferClaim bufferClaim = bufferClaims.get(); + + for (;;) { + final long current = System.nanoTime(); + if (current - start > TimeUnit.SECONDS.toNanos(30)) { + throw new RuntimeException("Timed out waiting to establish connection for session id => " + sessionId); + } + + final long offer = publication.tryClaim(BitUtil.SIZE_OF_INT, bufferClaim); + if (offer >= 0) { + try { + final MutableDirectBuffer buffer = bufferClaim.buffer(); + final int offeset = bufferClaim.offset(); + buffer.putInt(offeset, MessageType.ESTABLISH_CONNECTION_RESPONSE.getEncodedType()); + } finally { + bufferClaim.commit(); + } + + break; + } + + } + } + + @Override + public void close() throws Exception { + subject.onCompleted(); + publication.close(); + } } diff --git a/src/main/java/io/reactivesocket/aeron/Constants.java b/src/main/java/io/reactivesocket/aeron/Constants.java new file mode 100644 index 000000000..6c20248c1 --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/Constants.java @@ -0,0 +1,12 @@ +package io.reactivesocket.aeron; + +public final class Constants { + + private Constants() {} + + public static final int SERVER_STREAM_ID = 1; + + public static final int CLIENT_STREAM_ID = 2; + + public static final byte[] EMTPY = new byte[0]; +} diff --git a/src/main/java/io/reactivesocket/aeron/MessageType.java b/src/main/java/io/reactivesocket/aeron/MessageType.java new file mode 100644 index 000000000..d091dea07 --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/MessageType.java @@ -0,0 +1,43 @@ +package io.reactivesocket.aeron; + +/** + * Type of message being sent. + */ +enum MessageType { + ESTABLISH_CONNECTION_REQUEST(0x01), + ESTABLISH_CONNECTION_RESPONSE(0x02), + FRAME(0x03); + + private static MessageType[] typesById; + + /** + * Index types by id for indexed lookup. + */ + static { + int max = 0; + + for (MessageType t : values()) { + max = Math.max(t.id, max); + } + + typesById = new MessageType[max + 1]; + + for (MessageType t : values()) { + typesById[t.id] = t; + } + } + + private final int id; + + MessageType(int id) { + this.id = id; + } + + public int getEncodedType() { + return id; + } + + public static MessageType from(int id) { + return typesById[id]; + } +} diff --git a/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java index 767ed8d9a..80a46ef4a 100644 --- a/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java @@ -12,23 +12,22 @@ import uk.co.real_logic.aeron.Publication; import uk.co.real_logic.aeron.Subscription; import uk.co.real_logic.aeron.logbuffer.Header; +import uk.co.real_logic.agrona.BitUtil; import uk.co.real_logic.agrona.DirectBuffer; import uk.co.real_logic.agrona.collections.Int2ObjectHashMap; -import java.io.Closeable; import java.io.IOException; import java.nio.ByteBuffer; -public class ReactiveSocketAeronServer implements Closeable { +import static io.reactivesocket.aeron.Constants.CLIENT_STREAM_ID; +import static io.reactivesocket.aeron.Constants.SERVER_STREAM_ID; + +public class ReactiveSocketAeronServer implements AutoCloseable { private final ReactiveSocketServerProtocol rsServerProtocol; private final Aeron aeron; - private final int SERVER_STREAM_ID = 1; - - private final int CLIENT_STREAM_ID = 2; - private final int port; private final Int2ObjectHashMap connections; @@ -78,26 +77,36 @@ void poll(FragmentAssembler fragmentAssembler) { void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) { final int sessionId = header.sessionId(); - AeronServerDuplexConnection connection = connections.get(sessionId); - - if (connection != null) { - final PublishSubject subject = connection.getSubject(); - ByteBuffer bytes = ByteBuffer.allocate(buffer.capacity()); - buffer.getBytes(0, bytes, buffer.capacity()); - final Frame frame = Frame.from(bytes); - subject.onNext(frame); - } else { - System.out.println("No connection found for session id " + sessionId); + + int messageTypeInt = buffer.getInt(0); + MessageType type = MessageType.from(messageTypeInt); + + if (MessageType.FRAME == type) { + + AeronServerDuplexConnection connection = connections.get(sessionId); + + if (connection != null) { + final PublishSubject subject = connection.getSubject(); + ByteBuffer bytes = ByteBuffer.allocate(buffer.capacity()); + buffer.getBytes(BitUtil.SIZE_OF_INT, bytes, buffer.capacity()); + final Frame frame = Frame.from(bytes); + subject.onNext(frame); + } + } else if (MessageType.ESTABLISH_CONNECTION_REQUEST == type) { + AeronServerDuplexConnection connection = connections.get(sessionId); + connection.establishConnection(); } + } void newImageHandler(Image image, String channel, int streamId, int sessionId, long joiningPosition, String sourceIdentity) { + System.out.println(String.format("Handling new image for session id => %d and stream id => %d", streamId, sessionId)); if (SERVER_STREAM_ID == streamId) { - final AeronServerDuplexConnection connection = connections.computeIfAbsent(sessionId, (_s) -> { final String responseChannel = "udp://" + sourceIdentity.substring(0, sourceIdentity.indexOf(':')) + ":" + port; Publication publication = aeron.addPublication(responseChannel, CLIENT_STREAM_ID); - return new AeronServerDuplexConnection(publication, streamId, sessionId); + System.out.println(String.format("Creating new connection for responseChannel => %s, streamId => %d, and sessionId => %d", responseChannel, streamId, sessionId)); + return new AeronServerDuplexConnection(publication); }); rsServerProtocol.acceptConnection(connection); } else { @@ -105,6 +114,7 @@ void newImageHandler(Image image, String channel, int streamId, int sessionId, l } } + @Override public void close() throws IOException { running = false; diff --git a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java index 766b15efc..a3b82967e 100644 --- a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java @@ -14,27 +14,30 @@ import uk.co.real_logic.aeron.Publication; import uk.co.real_logic.aeron.Subscription; import uk.co.real_logic.aeron.logbuffer.Header; +import uk.co.real_logic.agrona.BitUtil; import uk.co.real_logic.agrona.DirectBuffer; import uk.co.real_logic.agrona.collections.Int2ObjectHashMap; import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; import java.nio.ByteBuffer; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static io.reactivesocket.aeron.Constants.CLIENT_STREAM_ID; +import static io.reactivesocket.aeron.Constants.EMTPY; +import static io.reactivesocket.aeron.Constants.SERVER_STREAM_ID; /** * Created by rroeser on 8/13/15. */ public class ReactivesocketAeronClient { - private static final byte[] EMTPY = new byte[0]; - private static final ThreadLocal buffers = ThreadLocal.withInitial(() -> new UnsafeBuffer(EMTPY)); private static final Int2ObjectHashMap subscriptions = new Int2ObjectHashMap<>(); private static final Int2ObjectHashMap> subjects = new Int2ObjectHashMap<>(); - private static final int SERVER_STREAM_ID = 1; - - private static final int CLIENT_STREAM_ID = 2; + private static final Int2ObjectHashMap establishConnectionLatches = new Int2ObjectHashMap<>(); private final ReactiveSocketClientProtocol rsClientProtocol; @@ -52,6 +55,8 @@ private ReactivesocketAeronClient(String host, int port) { final String channel = "udp://" + host + ":" + port; + System.out.println("Creating a publication to channel => " + channel); + final Publication publication = aeron.addPublication(channel, SERVER_STREAM_ID); final int sessionId = publication.sessionId(); @@ -68,11 +73,13 @@ private ReactivesocketAeronClient(String host, int port) { return subscription; }); + establishConnection(publication, sessionId); + this.rsClientProtocol = ReactiveSocketClientProtocol.create(new DuplexConnection() { public Publisher getInput() { - PublishSubject publishSubject = subjects.get(port); + PublishSubject publishSubject = subjects.get(sessionId); return RxReactiveStreams.toPublisher(publishSubject); } @@ -81,9 +88,14 @@ public Publisher write(Publisher o) { Observable req = RxReactiveStreams .toObservable(o) .map(frame -> { + final ByteBuffer frameBuffer = frame.getByteBuffer(); + final int frameBufferLength = frameBuffer.capacity(); final UnsafeBuffer buffer = buffers.get(); - ByteBuffer byteBuffer = frame.getByteBuffer(); - buffer.wrap(byteBuffer); + final byte[] bytes = new byte[frameBufferLength + BitUtil.SIZE_OF_INT]; + + buffer.wrap(bytes); + buffer.putInt(0, MessageType.FRAME.getEncodedType()); + buffer.putBytes(BitUtil.SIZE_OF_INT, frameBuffer, frameBufferLength); for (;;) { final long offer = publication.offer(buffer); @@ -112,11 +124,20 @@ public static ReactivesocketAeronClient create(String host) { } void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) { - final PublishSubject subject = subjects.get(header.sessionId()); - ByteBuffer bytes = ByteBuffer.allocate(buffer.capacity()); - buffer.getBytes(0, bytes, buffer.capacity()); - final Frame frame = Frame.from(bytes); - subject.onNext(frame); + int messageTypeInt = buffer.getInt(0); + MessageType messageType = MessageType.from(messageTypeInt); + if (messageType == MessageType.FRAME) { + final PublishSubject subject = subjects.get(header.sessionId()); + ByteBuffer bytes = ByteBuffer.allocate(buffer.capacity()); + buffer.getBytes(BitUtil.SIZE_OF_INT, bytes, buffer.capacity()); + final Frame frame = Frame.from(bytes); + subject.onNext(frame); + } else if (messageType == MessageType.ESTABLISH_CONNECTION_RESPONSE) { + CountDownLatch latch = establishConnectionLatches.get(header.sessionId()); + latch.countDown(); + } else { + System.out.println("Unknow message type => " + messageTypeInt); + } } void poll(FragmentAssembler fragmentAssembler, Subscription subscription, Scheduler.Worker worker) { @@ -128,6 +149,44 @@ void poll(FragmentAssembler fragmentAssembler, Subscription subscription, Schedu } } + /** + * Establishes a connection between the client and server. Waits for 30 seconds before throwing a exception. + */ + void establishConnection(final Publication publication, final int sessionId) { + try { + UnsafeBuffer buffer = buffers.get(); + buffer.wrap(new byte[BitUtil.SIZE_OF_INT]); + buffer.putInt(0, MessageType.ESTABLISH_CONNECTION_REQUEST.getEncodedType()); + + CountDownLatch latch = new CountDownLatch(1); + establishConnectionLatches.put(sessionId, latch); + + long offer = -1; + final long start = System.nanoTime(); + for (;;) { + final long current = System.nanoTime(); + if (current - start > TimeUnit.SECONDS.toNanos(30)) { + throw new RuntimeException("Timed out waiting to establish connection for session id => " + sessionId); + } + + if (offer < 0) { + offer = publication.offer(buffer); + } + + if (latch.getCount() > 0) { + break; + } + } + + System.out.println(String.format("Connection established for channel => %s, stream id => %d", + publication.channel(), + publication.sessionId())); + } finally { + establishConnectionLatches.remove(sessionId); + } + + } + public Publisher requestResponse(String payload) { return rsClientProtocol.requestResponse(payload); } diff --git a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java new file mode 100644 index 000000000..0a34dc2bd --- /dev/null +++ b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java @@ -0,0 +1,55 @@ +package io.reactivesocket.aeron; + +import io.reactivesocket.RequestHandler; +import org.junit.Test; +import org.reactivestreams.Publisher; +import rx.Observable; +import rx.RxReactiveStreams; +import uk.co.real_logic.aeron.driver.MediaDriver; + +/** + * Created by rroeser on 8/14/15. + */ +public class ReactiveSocketAeronTest { + @Test + public void test() throws Exception { + + + + final MediaDriver.Context context = new MediaDriver.Context(); + context.dirsDeleteOnStart(); + + final MediaDriver mediaDriver = MediaDriver.launch(context); + + ReactiveSocketAeronServer.create(new RequestHandler() { + @Override + public Publisher handleRequestResponse(String request) { + System.out.println("Server got => " + request); + Observable pong = Observable.just("pong"); + return RxReactiveStreams.toPublisher(pong); + } + + @Override + public Publisher handleRequestStream(String request) { + return null; + } + + @Override + public Publisher handleRequestSubscription(String request) { + return null; + } + + @Override + public Publisher handleFireAndForget(String request) { + return null; + } + }); + + ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost"); + //Publisher ping = client.requestResponse("ping"); + //RxReactiveStreams.toObservable(ping).doOnError(Throwable::printStackTrace).forEach(a -> System.out.println("pong from the server => " + a)); + + } + + +} From 659eaa15e2a2e8072eecf92a23a431c63484102e Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Fri, 14 Aug 2015 15:28:03 -0700 Subject: [PATCH 007/105] fixed code that establishes a connection: --- .../aeron/AeronServerDuplexConnection.java | 7 +++-- .../aeron/ReactiveSocketAeronServer.java | 26 ++++++++++++++----- .../aeron/ReactivesocketAeronClient.java | 10 ++++--- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java index 0e2678d24..78e50ac92 100644 --- a/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java @@ -68,23 +68,26 @@ public Publisher write(Publisher o) { return RxReactiveStreams.toPublisher(req); } - void establishConnection() { + void ackEstablishConnection(int ackSessionId) { final long start = System.nanoTime(); final int sessionId = publication.sessionId(); final BufferClaim bufferClaim = bufferClaims.get(); + System.out.print("Acking establish connection for session id => " + ackSessionId); + for (;;) { final long current = System.nanoTime(); if (current - start > TimeUnit.SECONDS.toNanos(30)) { throw new RuntimeException("Timed out waiting to establish connection for session id => " + sessionId); } - final long offer = publication.tryClaim(BitUtil.SIZE_OF_INT, bufferClaim); + final long offer = publication.tryClaim(2 * BitUtil.SIZE_OF_INT, bufferClaim); if (offer >= 0) { try { final MutableDirectBuffer buffer = bufferClaim.buffer(); final int offeset = bufferClaim.offset(); buffer.putInt(offeset, MessageType.ESTABLISH_CONNECTION_RESPONSE.getEncodedType()); + buffer.putInt(offeset + BitUtil.SIZE_OF_INT, ackSessionId); } finally { bufferClaim.commit(); } diff --git a/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java index 80a46ef4a..a163d57a5 100644 --- a/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java @@ -18,6 +18,7 @@ import java.io.IOException; import java.nio.ByteBuffer; +import java.util.concurrent.TimeUnit; import static io.reactivesocket.aeron.Constants.CLIENT_STREAM_ID; import static io.reactivesocket.aeron.Constants.SERVER_STREAM_ID; @@ -30,7 +31,7 @@ public class ReactiveSocketAeronServer implements AutoCloseable { private final int port; - private final Int2ObjectHashMap connections; + private volatile Int2ObjectHashMap connections; private final Scheduler.Worker worker; @@ -78,7 +79,7 @@ void poll(FragmentAssembler fragmentAssembler) { void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) { final int sessionId = header.sessionId(); - int messageTypeInt = buffer.getInt(0); + int messageTypeInt = buffer.getInt(offset); MessageType type = MessageType.from(messageTypeInt); if (MessageType.FRAME == type) { @@ -88,26 +89,39 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) if (connection != null) { final PublishSubject subject = connection.getSubject(); ByteBuffer bytes = ByteBuffer.allocate(buffer.capacity()); - buffer.getBytes(BitUtil.SIZE_OF_INT, bytes, buffer.capacity()); + buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, buffer.capacity()); final Frame frame = Frame.from(bytes); subject.onNext(frame); } } else if (MessageType.ESTABLISH_CONNECTION_REQUEST == type) { - AeronServerDuplexConnection connection = connections.get(sessionId); - connection.establishConnection(); + final long start = System.nanoTime(); + AeronServerDuplexConnection connection = null; + System.out.println("Looking a connection to ack establish connection for session id => " + sessionId); + while (connection == null) { + final long current = System.nanoTime(); + + if (current - start > TimeUnit.SECONDS.toNanos(30)) { + throw new RuntimeException("unable to find connection to ack establish connection for session id => " + sessionId); + } + + connection = connections.get(sessionId); + } + System.out.println("Found a connection to ack establish connection for session id => " + sessionId); + connection.ackEstablishConnection(sessionId); } } void newImageHandler(Image image, String channel, int streamId, int sessionId, long joiningPosition, String sourceIdentity) { - System.out.println(String.format("Handling new image for session id => %d and stream id => %d", streamId, sessionId)); if (SERVER_STREAM_ID == streamId) { + System.out.println(String.format("Handling new image for session id => %d and stream id => %d", streamId, sessionId)); final AeronServerDuplexConnection connection = connections.computeIfAbsent(sessionId, (_s) -> { final String responseChannel = "udp://" + sourceIdentity.substring(0, sourceIdentity.indexOf(':')) + ":" + port; Publication publication = aeron.addPublication(responseChannel, CLIENT_STREAM_ID); System.out.println(String.format("Creating new connection for responseChannel => %s, streamId => %d, and sessionId => %d", responseChannel, streamId, sessionId)); return new AeronServerDuplexConnection(publication); }); + System.out.println("Accepting ReactiveSocket connection"); rsServerProtocol.acceptConnection(connection); } else { System.out.println("Unsupported stream id " + streamId); diff --git a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java index a3b82967e..b42e19b71 100644 --- a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java @@ -124,7 +124,7 @@ public static ReactivesocketAeronClient create(String host) { } void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) { - int messageTypeInt = buffer.getInt(0); + int messageTypeInt = buffer.getInt(offset); MessageType messageType = MessageType.from(messageTypeInt); if (messageType == MessageType.FRAME) { final PublishSubject subject = subjects.get(header.sessionId()); @@ -133,7 +133,9 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) final Frame frame = Frame.from(bytes); subject.onNext(frame); } else if (messageType == MessageType.ESTABLISH_CONNECTION_RESPONSE) { - CountDownLatch latch = establishConnectionLatches.get(header.sessionId()); + int ackSessionId = buffer.getInt(offset + BitUtil.SIZE_OF_INT); + System.out.println(String.format("Received establish connection ack for session id => %d", ackSessionId)); + CountDownLatch latch = establishConnectionLatches.get(ackSessionId); latch.countDown(); } else { System.out.println("Unknow message type => " + messageTypeInt); @@ -150,7 +152,7 @@ void poll(FragmentAssembler fragmentAssembler, Subscription subscription, Schedu } /** - * Establishes a connection between the client and server. Waits for 30 seconds before throwing a exception. + * Establishes a connection between the client and server. Waits for 39 seconds before throwing a exception. */ void establishConnection(final Publication publication, final int sessionId) { try { @@ -173,7 +175,7 @@ void establishConnection(final Publication publication, final int sessionId) { offer = publication.offer(buffer); } - if (latch.getCount() > 0) { + if (latch.getCount() == 0) { break; } } From e9869f274891bd26e0e9532db64401d8a60553e5 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Fri, 14 Aug 2015 16:57:15 -0700 Subject: [PATCH 008/105] receives ping sends pong --- .../aeron/AeronServerDuplexConnection.java | 8 +- .../aeron/ReactiveSocketAeronServer.java | 39 ++++-- .../aeron/ReactivesocketAeronClient.java | 116 +++++++++--------- .../aeron/ReactiveSocketAeronTest.java | 31 ++++- 4 files changed, 125 insertions(+), 69 deletions(-) diff --git a/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java index 78e50ac92..d289d8d05 100644 --- a/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java @@ -39,7 +39,7 @@ public Publisher getInput() { public Publisher write(Publisher o) { Observable req = RxReactiveStreams .toObservable(o) - .map(frame -> { + .flatMap(frame -> { final ByteBuffer byteBuffer = frame.getByteBuffer(); final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; for (;;) { @@ -57,12 +57,12 @@ public Publisher write(Publisher o) { break; } else if (Publication.NOT_CONNECTED == offer) { - throw new RuntimeException("not connected"); + return Observable.error(new RuntimeException("not connected")); } } - return null; + return Observable.empty(); }); return RxReactiveStreams.toPublisher(req); @@ -73,7 +73,7 @@ void ackEstablishConnection(int ackSessionId) { final int sessionId = publication.sessionId(); final BufferClaim bufferClaim = bufferClaims.get(); - System.out.print("Acking establish connection for session id => " + ackSessionId); + System.out.println("Acking establish connection for session id => " + ackSessionId); for (;;) { final long current = System.nanoTime(); diff --git a/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java index a163d57a5..9ba3e44a2 100644 --- a/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java @@ -1,7 +1,7 @@ package io.reactivesocket.aeron; import io.reactivesocket.Frame; -import io.reactivesocket.ReactiveSocketServerProtocol; +import io.reactivesocket.ReactiveSocket; import io.reactivesocket.RequestHandler; import rx.Scheduler; import rx.schedulers.Schedulers; @@ -25,8 +25,6 @@ public class ReactiveSocketAeronServer implements AutoCloseable { - private final ReactiveSocketServerProtocol rsServerProtocol; - private final Aeron aeron; private final int port; @@ -39,9 +37,34 @@ public class ReactiveSocketAeronServer implements AutoCloseable { private volatile boolean running = true; + private final RequestHandler requestHandler; + + private static final org.reactivestreams.Subscriber PROTOCOL_SUBSCRIBER = new org.reactivestreams.Subscriber() { + @Override + public void onSubscribe(org.reactivestreams.Subscription s) { + s.request(Long.MAX_VALUE); + } + + @Override + public void onNext(Void t) { + + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + } + + @Override + public void onComplete() { + + } + }; + private ReactiveSocketAeronServer(int port, RequestHandler requestHandler) { this.port = port; this.connections = new Int2ObjectHashMap<>(); + this.requestHandler = requestHandler; final Aeron.Context ctx = new Aeron.Context(); ctx.newImageHandler(this::newImageHandler); @@ -56,7 +79,6 @@ private ReactiveSocketAeronServer(int port, RequestHandler requestHandler) { poll(fragmentAssembler); - rsServerProtocol = ReactiveSocketServerProtocol.create(requestHandler); } public static ReactiveSocketAeronServer create(int port, RequestHandler requestHandler) { @@ -88,8 +110,8 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) if (connection != null) { final PublishSubject subject = connection.getSubject(); - ByteBuffer bytes = ByteBuffer.allocate(buffer.capacity()); - buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, buffer.capacity()); + ByteBuffer bytes = ByteBuffer.allocate(length); + buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); final Frame frame = Frame.from(bytes); subject.onNext(frame); } @@ -122,17 +144,18 @@ void newImageHandler(Image image, String channel, int streamId, int sessionId, l return new AeronServerDuplexConnection(publication); }); System.out.println("Accepting ReactiveSocket connection"); - rsServerProtocol.acceptConnection(connection); + ReactiveSocket socket = ReactiveSocket.accept(connection, requestHandler); + socket.responderPublisher().subscribe(PROTOCOL_SUBSCRIBER); } else { System.out.println("Unsupported stream id " + streamId); } } - @Override public void close() throws IOException { running = false; worker.unsubscribe(); aeron.close(); } + } diff --git a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java index b42e19b71..9757182c2 100644 --- a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java @@ -2,7 +2,7 @@ import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; -import io.reactivesocket.ReactiveSocketClientProtocol; +import io.reactivesocket.ReactiveSocket; import org.reactivestreams.Publisher; import rx.Observable; import rx.RxReactiveStreams; @@ -39,10 +39,12 @@ public class ReactivesocketAeronClient { private static final Int2ObjectHashMap establishConnectionLatches = new Int2ObjectHashMap<>(); - private final ReactiveSocketClientProtocol rsClientProtocol; + private ReactiveSocket rsClientProtocol; private final Aeron aeron; + private final Publication publication; + private volatile boolean running = true; private final int port; @@ -57,12 +59,8 @@ private ReactivesocketAeronClient(String host, int port) { System.out.println("Creating a publication to channel => " + channel); - final Publication publication = aeron.addPublication(channel, SERVER_STREAM_ID); - + publication = aeron.addPublication(channel, SERVER_STREAM_ID); final int sessionId = publication.sessionId(); - - subjects.computeIfAbsent(sessionId, (_p) -> PublishSubject.create()); - subscriptions.computeIfAbsent(port, (_p) -> { Subscription subscription = aeron.addSubscription(channel, CLIENT_STREAM_ID); @@ -75,44 +73,6 @@ private ReactivesocketAeronClient(String host, int port) { establishConnection(publication, sessionId); - this.rsClientProtocol = - ReactiveSocketClientProtocol.create(new DuplexConnection() { - - public Publisher getInput() { - PublishSubject publishSubject = subjects.get(sessionId); - return RxReactiveStreams.toPublisher(publishSubject); - } - - @Override - public Publisher write(Publisher o) { - Observable req = RxReactiveStreams - .toObservable(o) - .map(frame -> { - final ByteBuffer frameBuffer = frame.getByteBuffer(); - final int frameBufferLength = frameBuffer.capacity(); - final UnsafeBuffer buffer = buffers.get(); - final byte[] bytes = new byte[frameBufferLength + BitUtil.SIZE_OF_INT]; - - buffer.wrap(bytes); - buffer.putInt(0, MessageType.FRAME.getEncodedType()); - buffer.putBytes(BitUtil.SIZE_OF_INT, frameBuffer, frameBufferLength); - - for (;;) { - final long offer = publication.offer(buffer); - - if (offer >= 0) { - break; - } else if (Publication.NOT_CONNECTED == offer) { - throw new RuntimeException("not connected"); - } - } - - return null; - }); - - return RxReactiveStreams.toPublisher(req); - } - }); } public static ReactivesocketAeronClient create(String host, int port) { @@ -128,15 +88,58 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) MessageType messageType = MessageType.from(messageTypeInt); if (messageType == MessageType.FRAME) { final PublishSubject subject = subjects.get(header.sessionId()); - ByteBuffer bytes = ByteBuffer.allocate(buffer.capacity()); - buffer.getBytes(BitUtil.SIZE_OF_INT, bytes, buffer.capacity()); + ByteBuffer bytes = ByteBuffer.allocate(length); + buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); final Frame frame = Frame.from(bytes); subject.onNext(frame); } else if (messageType == MessageType.ESTABLISH_CONNECTION_RESPONSE) { int ackSessionId = buffer.getInt(offset + BitUtil.SIZE_OF_INT); System.out.println(String.format("Received establish connection ack for session id => %d", ackSessionId)); + + subjects.computeIfAbsent(header.sessionId(), (_p) -> PublishSubject.create()); + + this.rsClientProtocol = + ReactiveSocket.connect(new DuplexConnection() { + + public Publisher getInput() { + PublishSubject publishSubject = subjects.get(header.sessionId()); + return RxReactiveStreams.toPublisher(publishSubject); + } + + @Override + public Publisher write(Publisher o) { + Observable req = RxReactiveStreams + .toObservable(o) + .flatMap(frame -> { + final ByteBuffer frameBuffer = frame.getByteBuffer(); + final int frameBufferLength = frameBuffer.capacity(); + final UnsafeBuffer buffer = buffers.get(); + final byte[] bytes = new byte[frameBufferLength + BitUtil.SIZE_OF_INT]; + + buffer.wrap(bytes); + buffer.putInt(0, MessageType.FRAME.getEncodedType()); + buffer.putBytes(BitUtil.SIZE_OF_INT, frameBuffer, frameBufferLength); + + for (; ; ) { + final long offer = publication.offer(buffer); + + if (offer >= 0) { + break; + } else if (Publication.NOT_CONNECTED == offer) { + Observable.error(new RuntimeException("not connected")); + } + } + + return Observable.empty(); + }); + + return RxReactiveStreams.toPublisher(req); + } + }); + CountDownLatch latch = establishConnectionLatches.get(ackSessionId); latch.countDown(); + } else { System.out.println("Unknow message type => " + messageTypeInt); } @@ -189,20 +192,23 @@ void establishConnection(final Publication publication, final int sessionId) { } - public Publisher requestResponse(String payload) { - return rsClientProtocol.requestResponse(payload); + public Publisher requestResponse(String data, String metadata) { + return rsClientProtocol.requestResponse(data, metadata); } - public Publisher requestStream(String payload) { - return rsClientProtocol.requestStream(payload); + public Publisher fireAndForget(String data, String metadata) { + return rsClientProtocol.fireAndForget(data, metadata); } - public Publisher fireAndForget(String payload) { - return rsClientProtocol.fireAndForget(payload); + public Publisher requestStream(String data, String metadata) { + return rsClientProtocol.requestStream(data, metadata); } - public Publisher requestSubscription(String payload) { - return rsClientProtocol.requestSubscription(payload); + public Publisher requestSubscription(String data, String metadata) { + return rsClientProtocol.requestSubscription(data, metadata); } + public Publisher responderPublisher() { + return rsClientProtocol.responderPublisher(); + } } diff --git a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java index 0a34dc2bd..bd8f9f290 100644 --- a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java @@ -3,10 +3,14 @@ import io.reactivesocket.RequestHandler; import org.junit.Test; import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; import rx.Observable; import rx.RxReactiveStreams; import uk.co.real_logic.aeron.driver.MediaDriver; +import java.util.concurrent.CountDownLatch; + /** * Created by rroeser on 8/14/15. */ @@ -45,10 +49,33 @@ public Publisher handleFireAndForget(String request) { } }); + CountDownLatch latch = new CountDownLatch(1); + ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost"); - //Publisher ping = client.requestResponse("ping"); - //RxReactiveStreams.toObservable(ping).doOnError(Throwable::printStackTrace).forEach(a -> System.out.println("pong from the server => " + a)); + client.requestResponse("ping", "ping metadata").subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); + System.out.println("here we go"); + } + + @Override + public void onNext(String s) { + System.out.println(s); + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + } + + @Override + public void onComplete() { + latch.countDown(); + } + }); + latch.await(); } From cc9031493985693c4a3776c85609fc1e53722a14 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Tue, 18 Aug 2015 12:47:58 -0700 Subject: [PATCH 009/105] added code to establish a connection. Publish operator will now use offer to send a message if the message is larger than the MTU size. Created a AeronClientDuplex connection --- .../aeron/AeronClientDuplexConnection.java | 44 ++++++ .../aeron/AeronServerDuplexConnection.java | 54 ++------ .../io/reactivesocket/aeron/Constants.java | 2 +- .../reactivesocket/aeron/OperatorPublish.java | 100 ++++++++++++++ .../aeron/ReactiveSocketAeronServer.java | 16 ++- .../aeron/ReactivesocketAeronClient.java | 74 +++------- .../aeron/ReactiveSocketAeronPerf.java | 105 ++++++++++++++ .../jmh/InputWithIncrementingInteger.java | 129 ++++++++++++++++++ .../aeron/jmh/LatchedObserver.java | 48 +++++++ .../aeron/OperatorPublishTest.java | 99 ++++++++++++++ .../aeron/ReactiveSocketAeronTest.java | 59 ++++---- 11 files changed, 601 insertions(+), 129 deletions(-) create mode 100644 src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java create mode 100644 src/main/java/io/reactivesocket/aeron/OperatorPublish.java create mode 100644 src/perf/java/io/reactivesocket/aeron/ReactiveSocketAeronPerf.java create mode 100644 src/perf/java/io/reactivesocket/aeron/jmh/InputWithIncrementingInteger.java create mode 100644 src/perf/java/io/reactivesocket/aeron/jmh/LatchedObserver.java create mode 100644 src/test/java/io/reactivesocket/aeron/OperatorPublishTest.java diff --git a/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java new file mode 100644 index 000000000..a2c171ea6 --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java @@ -0,0 +1,44 @@ +package io.reactivesocket.aeron; + +import io.reactivesocket.DuplexConnection; +import io.reactivesocket.Frame; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import rx.Observable; +import rx.RxReactiveStreams; +import uk.co.real_logic.aeron.Publication; + +public class AeronClientDuplexConnection implements DuplexConnection, AutoCloseable { + private Publication publication; + private Subscriber subscriber; + private Publisher publisher; + + public AeronClientDuplexConnection(Publication publication) { + this.publication = publication; + this.publisher = (Subscriber s) -> subscriber = s; + } + + public Subscriber getSubscriber() { + return subscriber; + } + + public Publisher getInput() { + return publisher; + } + + @Override + public Publisher write(Publisher o) { + final Observable frameObservable = RxReactiveStreams.toObservable(o); + final Observable voidObservable = frameObservable + .lift(new OperatorPublish(publication)); + + return RxReactiveStreams.toPublisher(voidObservable); + } + + @Override + public void close() throws Exception { + subscriber.onComplete(); + publication.close(); + } +} + diff --git a/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java index d289d8d05..45b18c2fd 100644 --- a/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java @@ -3,15 +3,14 @@ import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; import rx.Observable; import rx.RxReactiveStreams; -import rx.subjects.PublishSubject; import uk.co.real_logic.aeron.Publication; import uk.co.real_logic.aeron.logbuffer.BufferClaim; import uk.co.real_logic.agrona.BitUtil; import uk.co.real_logic.agrona.MutableDirectBuffer; -import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; public class AeronServerDuplexConnection implements DuplexConnection, AutoCloseable { @@ -19,53 +18,30 @@ public class AeronServerDuplexConnection implements DuplexConnection, AutoClosea private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); private Publication publication; - private PublishSubject subject; + private Subscriber subscriber; + private Publisher publisher; public AeronServerDuplexConnection( Publication publication) { this.publication = publication; - this.subject = PublishSubject.create(); + this.publisher = (Subscriber s) -> subscriber = s; } - PublishSubject getSubject() { - return subject; + public Subscriber getSubscriber() { + return subscriber; } @Override public Publisher getInput() { - return RxReactiveStreams.toPublisher(subject); + return publisher; } public Publisher write(Publisher o) { - Observable req = RxReactiveStreams - .toObservable(o) - .flatMap(frame -> { - final ByteBuffer byteBuffer = frame.getByteBuffer(); - final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; - for (;;) { - final BufferClaim bufferClaim = bufferClaims.get(); - final long offer = publication.tryClaim(length, bufferClaim); - if (offer >= 0) { - try { - final MutableDirectBuffer buffer = bufferClaim.buffer(); - final int offset = bufferClaim.offset(); - buffer.putInt(offset, MessageType.FRAME.getEncodedType()); - buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, 0, byteBuffer.capacity()); - } finally { - bufferClaim.commit(); - } - - break; - } else if (Publication.NOT_CONNECTED == offer) { - return Observable.error(new RuntimeException("not connected")); - } + final Observable frameObservable = RxReactiveStreams.toObservable(o); + final Observable voidObservable = frameObservable + .lift(new OperatorPublish(publication)); - } - - return Observable.empty(); - }); - - return RxReactiveStreams.toPublisher(req); + return RxReactiveStreams.toPublisher(voidObservable); } void ackEstablishConnection(int ackSessionId) { @@ -85,9 +61,9 @@ void ackEstablishConnection(int ackSessionId) { if (offer >= 0) { try { final MutableDirectBuffer buffer = bufferClaim.buffer(); - final int offeset = bufferClaim.offset(); - buffer.putInt(offeset, MessageType.ESTABLISH_CONNECTION_RESPONSE.getEncodedType()); - buffer.putInt(offeset + BitUtil.SIZE_OF_INT, ackSessionId); + final int offset = bufferClaim.offset(); + buffer.putInt(offset, MessageType.ESTABLISH_CONNECTION_RESPONSE.getEncodedType()); + buffer.putInt(offset + BitUtil.SIZE_OF_INT, ackSessionId); } finally { bufferClaim.commit(); } @@ -100,7 +76,7 @@ void ackEstablishConnection(int ackSessionId) { @Override public void close() throws Exception { - subject.onCompleted(); + subscriber.onComplete(); publication.close(); } } diff --git a/src/main/java/io/reactivesocket/aeron/Constants.java b/src/main/java/io/reactivesocket/aeron/Constants.java index 6c20248c1..a49b4a6ac 100644 --- a/src/main/java/io/reactivesocket/aeron/Constants.java +++ b/src/main/java/io/reactivesocket/aeron/Constants.java @@ -1,6 +1,6 @@ package io.reactivesocket.aeron; -public final class Constants { +final class Constants { private Constants() {} diff --git a/src/main/java/io/reactivesocket/aeron/OperatorPublish.java b/src/main/java/io/reactivesocket/aeron/OperatorPublish.java new file mode 100644 index 000000000..5c32e47d6 --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/OperatorPublish.java @@ -0,0 +1,100 @@ +package io.reactivesocket.aeron; + +import io.reactivesocket.Frame; +import rx.Observable; +import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.aeron.logbuffer.BufferClaim; +import uk.co.real_logic.agrona.BitUtil; +import uk.co.real_logic.agrona.MutableDirectBuffer; +import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; + +import java.nio.ByteBuffer; + +class OperatorPublish implements Observable.Operator { + + private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); + + private static final ThreadLocal unsafeBuffers = ThreadLocal.withInitial(() -> new UnsafeBuffer(Constants.EMTPY)); + + private Publication publication; + + public OperatorPublish(Publication publication) { + this.publication = publication; + } + + @Override + public rx.Subscriber call(rx.Subscriber child) { + return new rx.Subscriber(child) { + @Override + public void onStart() { + request(1); + } + + @Override + public void onCompleted() { + child.onCompleted(); + } + + @Override + public void onError(Throwable e) { + child.onError(e); + } + + @Override + public void onNext(Frame frame) { + + final ByteBuffer byteBuffer = frame.getByteBuffer(); + final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; + + // If the length is less the MTU size send the message using tryClaim which does not fragment the message + // If the message is larger the the MTU size send it using offer. + if (length < publication.maxMessageLength()) { + tryClaim(byteBuffer, length); + } else { + offer(byteBuffer, length); + } + } + + void offer(ByteBuffer byteBuffer, int length) { + final byte[] bytes = new byte[length]; + final UnsafeBuffer unsafeBuffer = unsafeBuffers.get(); + unsafeBuffer.wrap(bytes); + unsafeBuffer.putInt(0, MessageType.FRAME.getEncodedType()); + unsafeBuffer.putBytes(BitUtil.SIZE_OF_INT, byteBuffer, byteBuffer.capacity()); + for (;;) { + final long offer = publication.offer(unsafeBuffer); + if (offer >= 0) { + break; + } else if (Publication.NOT_CONNECTED == offer) { + child.onError(new RuntimeException("not connected")); + break; + } + } + + } + + void tryClaim(ByteBuffer byteBuffer, int length) { + final BufferClaim bufferClaim = bufferClaims.get(); + for (;;) { + final long offer = publication.tryClaim(length, bufferClaim); + if (offer >= 0) { + try { + final MutableDirectBuffer buffer = bufferClaim.buffer(); + final int offset = bufferClaim.offset(); + buffer.putInt(offset, MessageType.FRAME.getEncodedType()); + buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, 0, byteBuffer.capacity()); + } finally { + bufferClaim.commit(); + } + + break; + } else if (Publication.NOT_CONNECTED == offer) { + child.onError(new RuntimeException("not connected")); + break; + } + } + request(1); + } + }; + } +} diff --git a/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java index 9ba3e44a2..680c6b5b8 100644 --- a/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java @@ -3,9 +3,9 @@ import io.reactivesocket.Frame; import io.reactivesocket.ReactiveSocket; import io.reactivesocket.RequestHandler; +import org.reactivestreams.Subscriber; import rx.Scheduler; import rx.schedulers.Schedulers; -import rx.subjects.PublishSubject; import uk.co.real_logic.aeron.Aeron; import uk.co.real_logic.aeron.FragmentAssembler; import uk.co.real_logic.aeron.Image; @@ -16,7 +16,6 @@ import uk.co.real_logic.agrona.DirectBuffer; import uk.co.real_logic.agrona.collections.Int2ObjectHashMap; -import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; @@ -105,15 +104,13 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) MessageType type = MessageType.from(messageTypeInt); if (MessageType.FRAME == type) { - AeronServerDuplexConnection connection = connections.get(sessionId); - if (connection != null) { - final PublishSubject subject = connection.getSubject(); + final Subscriber subscriber = connection.getSubscriber(); ByteBuffer bytes = ByteBuffer.allocate(length); buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); final Frame frame = Frame.from(bytes); - subject.onNext(frame); + subscriber.onNext(frame); } } else if (MessageType.ESTABLISH_CONNECTION_REQUEST == type) { final long start = System.nanoTime(); @@ -145,6 +142,7 @@ void newImageHandler(Image image, String channel, int streamId, int sessionId, l }); System.out.println("Accepting ReactiveSocket connection"); ReactiveSocket socket = ReactiveSocket.accept(connection, requestHandler); + socket.responderPublisher().subscribe(PROTOCOL_SUBSCRIBER); } else { System.out.println("Unsupported stream id " + streamId); @@ -152,10 +150,14 @@ void newImageHandler(Image image, String channel, int streamId, int sessionId, l } @Override - public void close() throws IOException { + public void close() throws Exception { running = false; worker.unsubscribe(); aeron.close(); + + for (AeronServerDuplexConnection connection : connections.values()) { + connection.close(); + } } } diff --git a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java index 9757182c2..2ee6e5617 100644 --- a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java @@ -1,14 +1,11 @@ package io.reactivesocket.aeron; -import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; import io.reactivesocket.ReactiveSocket; import org.reactivestreams.Publisher; -import rx.Observable; -import rx.RxReactiveStreams; +import org.reactivestreams.Subscriber; import rx.Scheduler; import rx.schedulers.Schedulers; -import rx.subjects.PublishSubject; import uk.co.real_logic.aeron.Aeron; import uk.co.real_logic.aeron.FragmentAssembler; import uk.co.real_logic.aeron.Publication; @@ -30,12 +27,10 @@ /** * Created by rroeser on 8/13/15. */ -public class ReactivesocketAeronClient { - private static final ThreadLocal buffers = ThreadLocal.withInitial(() -> new UnsafeBuffer(EMTPY)); - +public class ReactivesocketAeronClient implements AutoCloseable { private static final Int2ObjectHashMap subscriptions = new Int2ObjectHashMap<>(); - private static final Int2ObjectHashMap> subjects = new Int2ObjectHashMap<>(); + private static final Int2ObjectHashMap connections = new Int2ObjectHashMap<>(); private static final Int2ObjectHashMap establishConnectionLatches = new Int2ObjectHashMap<>(); @@ -56,7 +51,6 @@ private ReactivesocketAeronClient(String host, int port) { aeron = Aeron.connect(ctx); final String channel = "udp://" + host + ":" + port; - System.out.println("Creating a publication to channel => " + channel); publication = aeron.addPublication(channel, SERVER_STREAM_ID); @@ -87,55 +81,20 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) int messageTypeInt = buffer.getInt(offset); MessageType messageType = MessageType.from(messageTypeInt); if (messageType == MessageType.FRAME) { - final PublishSubject subject = subjects.get(header.sessionId()); - ByteBuffer bytes = ByteBuffer.allocate(length); + final AeronClientDuplexConnection connection = connections.get(header.sessionId()); + final Subscriber subscriber = connection.getSubscriber(); + final ByteBuffer bytes = ByteBuffer.allocate(length); buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); final Frame frame = Frame.from(bytes); - subject.onNext(frame); + subscriber.onNext(frame); } else if (messageType == MessageType.ESTABLISH_CONNECTION_RESPONSE) { int ackSessionId = buffer.getInt(offset + BitUtil.SIZE_OF_INT); System.out.println(String.format("Received establish connection ack for session id => %d", ackSessionId)); - subjects.computeIfAbsent(header.sessionId(), (_p) -> PublishSubject.create()); + AeronClientDuplexConnection connection = connections.computeIfAbsent(header.sessionId(), (_p) -> new AeronClientDuplexConnection(publication)); this.rsClientProtocol = - ReactiveSocket.connect(new DuplexConnection() { - - public Publisher getInput() { - PublishSubject publishSubject = subjects.get(header.sessionId()); - return RxReactiveStreams.toPublisher(publishSubject); - } - - @Override - public Publisher write(Publisher o) { - Observable req = RxReactiveStreams - .toObservable(o) - .flatMap(frame -> { - final ByteBuffer frameBuffer = frame.getByteBuffer(); - final int frameBufferLength = frameBuffer.capacity(); - final UnsafeBuffer buffer = buffers.get(); - final byte[] bytes = new byte[frameBufferLength + BitUtil.SIZE_OF_INT]; - - buffer.wrap(bytes); - buffer.putInt(0, MessageType.FRAME.getEncodedType()); - buffer.putBytes(BitUtil.SIZE_OF_INT, frameBuffer, frameBufferLength); - - for (; ; ) { - final long offer = publication.offer(buffer); - - if (offer >= 0) { - break; - } else if (Publication.NOT_CONNECTED == offer) { - Observable.error(new RuntimeException("not connected")); - } - } - - return Observable.empty(); - }); - - return RxReactiveStreams.toPublisher(req); - } - }); + ReactiveSocket.connect(connection); CountDownLatch latch = establishConnectionLatches.get(ackSessionId); latch.countDown(); @@ -155,11 +114,11 @@ void poll(FragmentAssembler fragmentAssembler, Subscription subscription, Schedu } /** - * Establishes a connection between the client and server. Waits for 39 seconds before throwing a exception. + * Establishes a connection between the client and server. Waits for 30 seconds before throwing a exception. */ void establishConnection(final Publication publication, final int sessionId) { try { - UnsafeBuffer buffer = buffers.get(); + final UnsafeBuffer buffer = new UnsafeBuffer(EMTPY); buffer.wrap(new byte[BitUtil.SIZE_OF_INT]); buffer.putInt(0, MessageType.ESTABLISH_CONNECTION_REQUEST.getEncodedType()); @@ -211,4 +170,15 @@ public Publisher requestSubscription(String data, String metadata) { public Publisher responderPublisher() { return rsClientProtocol.responderPublisher(); } + + @Override + public void close() throws Exception { + for (Subscription subscription : subscriptions.values()) { + subscription.close(); + } + + for (AeronClientDuplexConnection connection : connections.values()) { + connection.close(); + } + } } diff --git a/src/perf/java/io/reactivesocket/aeron/ReactiveSocketAeronPerf.java b/src/perf/java/io/reactivesocket/aeron/ReactiveSocketAeronPerf.java new file mode 100644 index 000000000..ce3071e6f --- /dev/null +++ b/src/perf/java/io/reactivesocket/aeron/ReactiveSocketAeronPerf.java @@ -0,0 +1,105 @@ +package io.reactivesocket.aeron; + +import io.reactivesocket.RequestHandler; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.infra.Blackhole; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import rx.Observable; +import rx.RxReactiveStreams; + +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.SECONDS) +public class ReactiveSocketAeronPerf { + //static final MediaDriver mediaDriver = MediaDriver.launchEmbedded(); + + static final ReactiveSocketAeronServer server = ReactiveSocketAeronServer.create(new RequestHandler() { + @Override + public Publisher handleRequestResponse(String request) { + return RxReactiveStreams.toPublisher(Observable.just(request)); + } + + @Override + public Publisher handleRequestStream(String request) { + return null; + } + + @Override + public Publisher handleRequestSubscription(String request) { + return null; + } + + @Override + public Publisher handleFireAndForget(String request) { + return null; + } + }); + + static final ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost"); + + @State(Scope.Benchmark) + public static class Input { + private String message; + private String metaData; + private Blackhole bh; + + @Setup + public void setup(Blackhole bh) { + this.bh = bh; + this.message = "ping"; + this.metaData = "metadata test"; + } + + public Blackhole getBh() { + return bh; + } + + public String getMetaData() { + return metaData; + } + + public String getMessage() { + return message; + } + + public Subscriber newSubscriber() { + return new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(8); + } + + @Override + public void onNext(String s) { + bh.consume(s); + } + + @Override + public void onError(Throwable t) { + + } + + @Override + public void onComplete() { + + } + }; + } + } + + @Benchmark + public void pingPongTest(Input input) { + client + .requestResponse(input.getMessage(), input.getMetaData()) + .subscribe(input.newSubscriber()); + } +} diff --git a/src/perf/java/io/reactivesocket/aeron/jmh/InputWithIncrementingInteger.java b/src/perf/java/io/reactivesocket/aeron/jmh/InputWithIncrementingInteger.java new file mode 100644 index 000000000..a6b8da055 --- /dev/null +++ b/src/perf/java/io/reactivesocket/aeron/jmh/InputWithIncrementingInteger.java @@ -0,0 +1,129 @@ +package io.reactivesocket.aeron.jmh; +/** + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; +import rx.Observable; +import rx.Observable.OnSubscribe; +import rx.Observer; +import rx.Subscriber; + +import java.util.Iterator; + +/** + * Exposes an Observable and Observer that increments n Integers and consumes them in a Blackhole. + */ +public abstract class InputWithIncrementingInteger { + public Iterable iterable; + public Observable observable; + public Observable firehose; + public Blackhole bh; + public Observer observer; + + public abstract int getSize(); + + @Setup + public void setup(final Blackhole bh) { + this.bh = bh; + observable = Observable.range(0, getSize()); + + firehose = Observable.create(new OnSubscribe() { + + @Override + public void call(Subscriber s) { + for (int i = 0; i < getSize(); i++) { + s.onNext(i); + } + s.onCompleted(); + } + + }); + + iterable = new Iterable() { + + @Override + public Iterator iterator() { + return new Iterator() { + + int i = 0; + + @Override + public boolean hasNext() { + return i < getSize(); + } + + @Override + public Integer next() { + return i++; + } + + @Override + public void remove() { + + } + + }; + } + + }; + observer = new Observer() { + + @Override + public void onCompleted() { + + } + + @Override + public void onError(Throwable e) { + + } + + @Override + public void onNext(Integer t) { + bh.consume(t); + } + + }; + + } + + public LatchedObserver newLatchedObserver() { + return new LatchedObserver(bh); + } + + public Subscriber newSubscriber() { + return new Subscriber() { + + @Override + public void onCompleted() { + + } + + @Override + public void onError(Throwable e) { + + } + + @Override + public void onNext(Integer t) { + bh.consume(t); + } + + }; + } + +} \ No newline at end of file diff --git a/src/perf/java/io/reactivesocket/aeron/jmh/LatchedObserver.java b/src/perf/java/io/reactivesocket/aeron/jmh/LatchedObserver.java new file mode 100644 index 000000000..18b262cc0 --- /dev/null +++ b/src/perf/java/io/reactivesocket/aeron/jmh/LatchedObserver.java @@ -0,0 +1,48 @@ +package io.reactivesocket.aeron.jmh; + +/** + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import org.openjdk.jmh.infra.Blackhole; +import rx.Observer; + +import java.util.concurrent.CountDownLatch; + +public class LatchedObserver implements Observer { + + public CountDownLatch latch = new CountDownLatch(1); + private final Blackhole bh; + + public LatchedObserver(Blackhole bh) { + this.bh = bh; + } + + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + latch.countDown(); + } + + @Override + public void onNext(T t) { + bh.consume(t); + } + +} \ No newline at end of file diff --git a/src/test/java/io/reactivesocket/aeron/OperatorPublishTest.java b/src/test/java/io/reactivesocket/aeron/OperatorPublishTest.java new file mode 100644 index 000000000..fa57da6db --- /dev/null +++ b/src/test/java/io/reactivesocket/aeron/OperatorPublishTest.java @@ -0,0 +1,99 @@ +package io.reactivesocket.aeron; + +import io.reactivesocket.Frame; +import org.junit.Test; +import rx.Subscriber; +import uk.co.real_logic.aeron.Publication; + +import java.nio.ByteBuffer; + +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class OperatorPublishTest { + @Test + public void testShouldCallTryClaimWhenSmallerThanMTU() throws Exception { + String message = "I'm a message longer than 1"; + ByteBuffer buffer = ByteBuffer.wrap(message.getBytes()); + + Frame frame = mock(Frame.class); + when(frame.getByteBuffer()).thenReturn(buffer); + + Publication publication = mock(Publication.class); + when(publication.maxMessageLength()).thenReturn(1000); + + OperatorPublish publish = new OperatorPublish(publication); + + try { + Subscriber subscriber = new Subscriber() { + @Override + public void onCompleted() { + + } + + @Override + public void onError(Throwable e) { + + } + + @Override + public void onNext(Object o) { + + } + }; + + Subscriber call = publish.call(subscriber); + call.onNext(frame); + + } catch (Throwable t) { + } + + verify(publication, times(1)).tryClaim(anyInt(), anyObject()); + + } + + @Test + public void testShouldCallOfferWhenLargerThenMTU() throws Exception { + String message = "I'm a message longer than 1"; + ByteBuffer buffer = ByteBuffer.wrap(message.getBytes()); + + Frame frame = mock(Frame.class); + when(frame.getByteBuffer()).thenReturn(buffer); + + Publication publication = mock(Publication.class); + when(publication.maxMessageLength()).thenReturn(1); + + OperatorPublish publish = new OperatorPublish(publication); + + try { + Subscriber subscriber = new Subscriber() { + @Override + public void onCompleted() { + + } + + @Override + public void onError(Throwable e) { + + } + + @Override + public void onNext(Object o) { + + } + }; + + Subscriber call = publish.call(subscriber); + call.onNext(frame); + + } catch (Throwable t) { + } + + verify(publication, times(1)).offer(anyObject()); + + } +} \ No newline at end of file diff --git a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java index bd8f9f290..793a8c5c0 100644 --- a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java @@ -1,10 +1,9 @@ package io.reactivesocket.aeron; import io.reactivesocket.RequestHandler; +import org.junit.BeforeClass; import org.junit.Test; import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; import rx.Observable; import rx.RxReactiveStreams; import uk.co.real_logic.aeron.driver.MediaDriver; @@ -15,16 +14,16 @@ * Created by rroeser on 8/14/15. */ public class ReactiveSocketAeronTest { - @Test - public void test() throws Exception { - - - + @BeforeClass + public static void init() { final MediaDriver.Context context = new MediaDriver.Context(); context.dirsDeleteOnStart(); final MediaDriver mediaDriver = MediaDriver.launch(context); + } + @Test(timeout = 5000) + public void testRequestReponse() throws Exception { ReactiveSocketAeronServer.create(new RequestHandler() { @Override public Publisher handleRequestResponse(String request) { @@ -49,34 +48,34 @@ public Publisher handleFireAndForget(String request) { } }); - CountDownLatch latch = new CountDownLatch(1); + CountDownLatch latch = new CountDownLatch(10_000); - ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost"); - client.requestResponse("ping", "ping metadata").subscribe(new Subscriber() { - @Override - public void onSubscribe(Subscription s) { - s.request(Long.MAX_VALUE); - System.out.println("here we go"); - } - @Override - public void onNext(String s) { - System.out.println(s); - } - - @Override - public void onError(Throwable t) { - t.printStackTrace(); - } + ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost"); - @Override - public void onComplete() { - latch.countDown(); - } - }); + Observable + .range(1, 10_000) + .flatMap(i -> + RxReactiveStreams.toObservable(client.requestResponse("ping =>" + i, "ping metadata")) + ) + .subscribe(new rx.Subscriber() { + @Override + public void onCompleted() { + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + } + + @Override + public void onNext(String s) { + System.out.println(s + " countdown => " + latch.getCount()); + latch.countDown(); + } + }); latch.await(); } - } From 9d86b1c8656618f3a68552995f3af2a917107e19 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Tue, 18 Aug 2015 12:53:30 -0700 Subject: [PATCH 010/105] adding gradle wrapper jar --- gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 51018 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 gradle/wrapper/gradle-wrapper.jar diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..c97a8bdb9088d370da7e88784a7a093b971aa23a GIT binary patch literal 51018 zcmagFbChSz(k5C}UABH@+qP}H%eL+6vTawFZQHiHY}>BeGv~~A=l$l)y}5Som4C!u znHei0^2sM+D@gwUg$4qGgao=#br%Kt+d%%u>u-bl+hs*n1ZgGZ#OQwjDf~mwOBOy} z@UMW{-;Vmf3(5-0Ns5UotI)}c-OEl+$Vk)D&B002QcX|JG$=7FGVdJTP124^PRUMD zOVR*CpM@Bw929C&wxW|39~2sn_BUajVxD5&Io>(~|Fy9gm>3ur-vVWO-L648qRuK~#rxo+Dno zN$;BHeJBFq{$312A@64P)Cr$5QiJxUsyQ{(bEyq5gJ$No=5CfVip&aH46>kLmk4Td zXj+eR5gq9fKfj77AR$KvvG!=REopfPZmgAl3g31WCOgP`{y1k$L|*R_{GeGPSRpYC zaQx8d0XP?0T%Z4@oRQ7OkHnCA~wEL?pXA2Xjzaw`KK^JFp z6I*8sBLinU$A2lINZG~?SrE||jUsepZm&$gDtT?$Q{^ziZcZNyYIraxjckc51i=&r zo5QJ#*ef#0uSn0jAe_G!-y{pH98{9=mhWP6nt5ijp}~va*Y^`XFKUEro+7PQfuS~~ zUl!$jRl1 za6yh{VIy&i z+Ka0B?$#wFemv78?abqT08h7K{b5vSw#P?s4h;pzW4!p^^LJ@j!@FmJ1Um}Wd%JKojYOknfl_H3>Hesd! z{3~Odlw$N@58>CeT$W*<+}bdulAir8=ut_T<2CvCq4*)>eOH?}`yuvtM_7miv0p<8Y!>RnQy{-T4ME}|DB>$Il{mZIE zqx=547Hr7(jkqWbR~4$g$Lq*L&|x zd?2(FuMl#r|KL zj#k!^#}Y*S5{uVaepITYXll090@eDXd8xWEI8h$10!aWRZyXF&P1j-k)A~cbi^S4$ zeuVEqoRxP#iF!1!W2|k;t=s8na`Kv=-xoxqzdS&3a?Cw{hcZVpj1p2`S4{gQ98s*6 zV7DzG4yX&!Q&CLGT((~tN*Xp%>+R`HkV`7vyEmJ!=2_IOShtftYWPrLw~}xNM_0e zRS^b3Z9b2B$*=9$yt@&Hre9*Y2b?}h{6a?>O6c9WLc6{B!fxqFK>pr7o8xk89_9Yu)N<3ozvWjp3h zPmt{pchc%36=FVB%|NpiUe62UAds^kig7jKwKz<(`KIWJ`xzEtkpLLNu;@?R6!$~j zXa67|Oy>|zNJO2JV4nX0gRZZq6-P0qPt6enL86NPi;{-~x1R;CDN$b2_C-sE> z>NCJISRlR>ygMi`HI7TT{{{SK+Db5y2rQ9Wm@90oB3o0btqU?)v@dh#63Dz%^=BeNIf>g+{Sk?83{-0)wv!}B@1O^23_7@#6|7SB5 zbvLqhak6kV5woy15i~L~adMJ1ur)9<`FGq;R+F|zF~Rw^$sn_6w;>cDRImmLZd3@M zKwAh%Sv54*%!4Ze1GJ2>>9lV~XUa_7z zej{;5F-?hJrJPdeh%5*!PgVnWQGH=%j@T;E$Y@Os)AiCQNY)r{bgauNGIn8Qv!#6P zv>aNaH-0b_#8&2J(Xp8@UKIK*&6t#BiBu}@0ExVoZ;O+GiQ-6mRb_7FNn?bSo^MhX zf-6nYPRG;CG8y^yvg5&Ow2+odsO6eDg%OLCXlp7)ve=dY4rdku7*Kc&*!MSx3X>_j z-(_TmF$kMY0-0L;Mj(I!-ko8sA6AO01SG9jl(Zo_vfODRxZJd*D_9smUMGwEQgH0;Q$Y$lY~VT5i>Qt!6uU!hDOcLMy4XB<(dr_ zui*M9iaRE}emTsJTIB#9ekn})-h^6*TVq^GOHZ{XV^sYK3d5+&I`x^TQ4I7T3cAs> z-bmk0l6{j-B7+4f(bS!~VH54a3SaGnTP)qw_+Dk-PQraznR>me*DFdaL+|5y!rx4n zF;0Ux5s)}`4i7{r<;EdP*2da%)`ror>1xK+ZNyhuqSnkzBF_%xU6(?>Be8BKouSX4 zF9O4%qwlxzQL*u6IjNMvLB;PG4)6neISC4A0M?rEvL`6f2YCz2e7MNa8ToiylcdSV zxsXFVuG||t<8Z3>q%M^L6#>So=FbPQx%F0O>7%77nVlL4ikNlYEO6`zJubx-V*ScKH>)+DEz=cD8S{oa)F z3MqfFWx8}9@B<$B4-N5`ALEF_t`|VtB3nF=L?mR{$8i|0;zEY!?DjSXHourmHmtBp z2w830pyiD=Rg-ialH9m0b*tA~ZNl!&UaGHTK7<@%!!vVw>aW*9FDP&eJr zUVf(nk1_wa?!BT+n(1X{fa8z#r&I|F&@NWsglp>v-=I{5KAA6{!^zsMG%(8Vi0;}Uq%5%*FC1{M#2_B=gh7R^1%b#k{Z{FB&!gWF30~q9QoMDiVjgakbIw4lS5aDU- zlDRYMa?01gXk6DZs+~j-lOpCU3gdt*E8Qm z^Jp1+5A5V0dynkoKKK;QDRo2uY4i@vd1?rNU=-GiO&FG%R?8{*@j$~_Vmj^~_QHlo zUKPdELl}cL+=n5K?MK5f*19F-JXQ)Y)vi9TpSIYE$nRn4PFw~Z5IR(L(CF6%qBQHqs zQTpQ;6E8Otf>uoqB8A)*e}hn_0B7~7R*q5g?X=TNdyAU0l)%>>ydhZhp0?}ylVZcCOF!V0L@fg!Dkse%^B+#Zc`jR)#(CjQd56Zgr1GThOH-VvVuxy z>9-cOCGK^^HIf;i(uZHS5?Ky!FSC#)i>L9^V74i@!#R>VU)4s54lu6J2iIkOdBu)R z9(pNuesI`#9s+%@K)}Gi#rnDU8yX8$g+fU=pA3P@zv=sfh2zi=1tOcd16vUAEe-aq z52e(IDMrba=0STqG?6*<=@uh>8Swmhpya(D-T?fq9N7h7{p(N9*DTCO_&4^fwO(8 zgkgh$){ug>;esT1#xSgpm;{<2j#`ijhk|&}f@(tqKk*KbEb(T5D9H%if(V!p>S;mS zsKMhs;z~;YWCJTn2`s0HeZ&0IR26-2Ee`);Y|Os^hT%U0nE!r(l`ydVOBMVZy+o^> zJE5qee%oXk54cVgC`d^KLxNbmh5Z6pLsQL46(Nu)&;+#0+9d`Xvs<$@0sy%$VxRr6 zF$3y+oPh%vz0;#^-xQB-?7ycX*GxUHx{h6DUbCHMF1EivUeSMjzWf}Ziz;;&7Df?c z$r>z;U}t?Hy-xxM7~L_@xuH;zsb;C&ri7?PfjWp)LrG3cIm!jblo3o@xnnQPUkJf$ z^$nqE_je?8lGAgO7hPL1Fc3>B4bTLskUGE zA&d*iD8Uy|_S0C*n2u}17lrZZOgPFp6EeA(Z1>QfBi7^qY0hD5vB4u;;#3qlnz}SM z(WgeE`<)CTzxi4U*F9*qk{~T=)mmmI*FUYMgEHJ~hNdE&9nLhZretik2j=K3RYB*F z#1#Z8MckH$(6*8ytik?G^b_Lbq38)j#~IC{Kkor`6i&B=m;+Kn=BApI5sQ_(WDEU2 zU9UDT!jd$0K6507^*PFf)HH0HQpeIKh)$KZjJxynrGo<%)j2|}q}LzY4xLRFjaAGl z^NK3#MuSX{ERkj%0l#dj5Nm)ana42c?3%Dl9NS4Er0>fE=#FyGT=L%5etXuQaf+YX z>-5X~4AHVbF>-%2to~DyQVS!el(ci^DJK0Wt&H0tc*0(_;WR&5;*lCb&Bdg@U~LhU z8W3aFnDAhJS{uLMp!&A8kynE1Tn*}5tlws;rUJ=r*}$d7z}!$j=8C`_a~X8J&<|~9 zZIn`fBjqyS6m=K|58)xHjSHro2s}l-nx+@BYv@wt@2{vt6l()xQ3 z1vfX~r+3JV3r;UORrjKUvSWAu3RU;qEp7M0Ew8VFgY-!3i=?3QB|^IY;!_Vu7qT=w zdc(#k4jsBi)>?Jk4{@>@{q=~_J635pKPIE&B4*4O(amlNp9bfZx^amStA`1C7uL@_ zt5gl^bcrq^)_gdk(w!_>?x#*~8Ql-u8TUZ%Qc3R2`GtIzYVD?zT%JmBI(j)*@i1*Pf}@*w_7afP#$~ui{%Tt>mc8f#=1#cuZvZorz8lltv*K-MQAdw zc^9kZN^GW(L1{p;!m|c9lBVwnAbpBGa8OV2%m>G9H#v4SQ zk|$69J9+JVoei}vo{kMLxBQHlncHaN5%d(kMbykE78)R^H~NgRj=IqY)dmxPcn$L! zc|v3Ou1|nUk$(>V6a>;ul!L7zY>C8umET2P_#{=8t>PVJ&pf^T{;W9T!-5j;b7BVa z={~0=%<-2#P#_Xa6XHIFFK$J_MjR$P3k}<#lX^yq!2_9}c|`QI0ElK~-5+QZm9L!M zlJPg&I&5qX3vLWax5`gJa9sFvLA)9)%1!WXp^2kk8g6Wgk<&ikFPxL{;^mqA>IOIG z?L{thfTmxFln;=Tud#>QW8cCvix+wU2y^uBY41HRD|Slx)g;4c%zI{c80p5xI=S_) z!+k^iGh|LXo<{&6fPie_fq=;VbI4RMa5fioax$?o{I5WntoCYzt&a4yybSP2<~HJKCZo6X zk=@8mHbyu0$n%X)p96Ua{@{%;rw4gN2Q++?Mx>_0sJ-^O21Q$l36+9VaoKvH=#+!A zcwfA9;-@h2z&*3_K;nJshh|pH*^MG! zu_hVW%ozAW*LF0!cbN7LX8ijy&*q$4Gcm7CiX>}U=NY%2sudJF$<_mFhYkRk%haMv zDM51{=UW`wNQ2R z$7BM2q!FjS{6kOvRmP^=2< z{s>jh04u=BU2{koV_$U3_UDlNO+*A3&7IUrJ_ZHmP^WFhOWDZ>EWeppJ(VwE1WeIDk1C^o$U+*ZhK)p=Yp5)i0yrZ&X)q=Sg~7i zfM0*EYUREvz^_ja(C99na!SXokLp-lfe!j;m2VGR1G85j5a^PYmsi!!{gX+@=;!v6 z*?XQU)lp*cAz7-#MxjA<(ng_tHea2Nff&Wcz_!Z9NJvDFwAfW-$I%*i*&bY{q$6l{ zEPBJB=}Id51qEK|ODO6I$d{xoH1jm6WLM!XiS!Xnu}h?Wf?cX1SjpC|ENQ!n8!aos za$_rStUYa6J8F$;&W$-PlDes`;5B#q4scJQPTR7IJz=BU>PnVaN z+hqvjDU+`->|b)5R7{H6W2&gl{_O0R2-X*FS`Vu( zP_|oU|DF4{vlkb}pAg*l?IV=5)=#?wW!gSHcb1?R^LjKEq9wyyrU5k_A9QyOp*H^TU_II9b%1ppYE`gRO)b}_CB`j?Wz2(YU7Mob#sm%1nRN&Y8;^p z0E!yVa{N7vV_0W`!RrQJsq&g2U|2`AAHx3rDpPk9Rs z&Z{f%G~pz*po0uHuWaAa@`}?f3+YT))R57|UR02=MPAGRk?CMI#O%Z#L%_u!0q^Pn zvg$>qC$c98b2tYIBR{aI1AyS*NeQ3)hkI?EYhyS!pTqa zcE7of2o-oFZ&($W{T&cg13S(9w>x;q$={J}o^NI;5|{aw7qrAiz+^jzZllm;x6$7CyjO*{3G~2#=dBje@|%p25mFt_gx+<6n9tLPpO}=EI!QXsa}1-! z%srCY@SZ=&KO400H0_7(5sQXZdCuoDa|!+FGuI4DqF+z&E|HG1+E)cbdCz%qe;-ds zsPp2P8tbbVh!rvwn+_)ud|9flHFq`};LR+EV}#~;Xe)a74ECr5Z*%y6r+UaSF(pYN z3eMmT&e!}l;7vSvRGSid7TM-Crgpz>Pa@s%-eWnV0JUCbw5v!W5` zkwp-57L2-!|BaXT9rwi#c&j0(Dq#;)k^QEf`j)u=@$+3Te(xLP zK{&mwoPlx@m=3BI0_6g3-t8ns%b zRjOGX!h(GT)B?Rmt(8r}OEf@78+`|n&pn!j8qiHC!P}{}>mqoz-vq3SzXJyh57Bpi zq_j2KB0yb@U?2F98MJO{fd#OIo~K}!UQimY+8~qd=*JbrD#6d&mHX2wx?2Tp2Q#nu z2YdI@Q6J*rC|huAsN>MdEza(c?sbE>U=#Y<1p4vu`Wg?P$GP54|6;b!Kj&8X2k`*8 zcn1QmemM?LRrVa0p{8NG(PVSf-~(PUpv#oV!V2oW7ESsTxI3B>k#&a5uo%rmliyr( z0e2wxV-OoHDp!Q!NzV~*-2F8xa!6NfSc!>)?A zPz$_;2I_lyqUJ@dDdt^v&cj+s6v@I`e%4TZ9fk<3oMyY}xsTYqX?s&?n)n9ZRT@*V za*8RosiA!0In%%e;?U4m;_JDL>~$I{OGH4IiA>>v*G2?ma>oHm`zE8WJ&+caVZNc> z7GJQzYD8brg3Or!5ilMj+;AXpv))SU<3!l6-$YE0m}+V%S>@%#6N*M+-3CJX0=e-< zlEHRnEKSFO3y1Zc8E(iVyOsZlg4M-Q-XikrDADUk;(Ny65CBaoZ?PTQj{UOq%U}2?3 zqE3TMOK5!B7*i7HiL`Z;THehb$C7B@qR!MdB*=!2fclgyLxV}t&g$=Z%gQJ>=L;ZQ zXwO1S=VLM`Uy8_LAAJ`htp2X=W*E^VkD1xOG48lhaUzM}S`w2n6?vxjaDsg_B8LYXsFNW9aEhAc@N&VcRsfwTLYl+u&t2b#jN8@}fiGo;{>G4A$Tsj{)&%h= z(0Ss~uA_X}T13 z?vja|;h^r;c*Z}&RkUZ0&r%q%KJ-D=!}=DCl!LwwOb7up^QgnhT!!u&Xpm#Ho7%OR!Qc0 z>vR_WSiSt_DCFSCesM8zH^?H?fxecN7-<>VY1Q;Jv6+%_9q%ePyDtny!$@vly1b&c#ox66>nD&>4PpA;SOWr)(SfQC>s=p8OP5JxzsysBp%AC z_p+JBMsBv^&cIXbkl4Fr90?Qm(1_|YK!cXUfMh-dUGZA*u0suuQeh6xv~Y4v*#X64 ztHEjnJ~{Rt>bIlPq1R7kccDW`JY|mZ`P9PEMLOQxA{@L08}nq!%-wYDO<4JKHFb?c z${e*C1yolp1&lh&th6G+vg zr=XzRxYx^-fQ zFwRl8UXAd?uDUtR2$hPa&%Vl^aM)0y>j=P4Hr(n+AN# zMUcADA7iPe$j)O^w8jelB#w?;8I8`@Rh*tf0>gyLRrf16=`dIo2T7mgeV>`lu#f*x zr2Rfk+f|&iIZH#i4#reAzF``M!y;<|w{=H#*T2m8TtE@&^Q{tQLCIq&taw`bx5Xds zqDhG-lLX!{%efQeFHAv)&DO)WSPqFc=zvE{C}sm72oSj9v*CQtYFiq|9#?{s{82(P-b_zMOn~H-t4c$ z+E1WK8k60Bs~dooiGjclGq>WKo{Y73#Ucv9Jd|Q$P5kc0wGb)Rj`BRvFd;;#Mu`37 z74e|UWBIt5T%ubs?eQ8U(Pc%qoqV6e!`8Oa>>~R^Rb=PDfOeBoaF}Sj_=`v4Ie`Z2 zgZQjU_)~@Wv2&p>PWco*Z{Ig^nT0t0=)Ck?4zLS$F5PK&RL~1Z(JRs@m#e58p+k^w zBuKfIiCyorn_%lA1MJFVotZ_;V!}F6iL#5sEU@%Qog=6YqZJO z8=v>7<@oOMwr4v9r0$8ph?0u{4~JmT-2x_4wDfT`ZI56|H5?+ejTH{RC}2a*%djql z8<7gMC{8E23P+1kRx!g7NilMNH=G1Nno$V&KCEjUSqv%M-xnGx@NKZkQ+ITl ztGm21{6xE>RT3@aJb>}gLN9g9Dc3DIo1u?4Ls=7}Dj^$Pl(8yZiZ{!Z=BJ)<%_!m= z-6??Q62JZt?3m^Kiw~%+g76dK)ZnBE+z-EhRQwosfQdIC+a0?|_DNKUL7HV_qh4TZ zw>h;m

>{cK%Glr0N+^9_g+*qFXo%Qsn_x!QK~{D@R|W(|+L*zMQO}NgP`+hb4Z7 z)L3A@tfL}tv?v!^nhMXY$=b2epb5wA*ud*bv%RE#&V1M1BUvUTMiA4#_2gmT;;05gs z=?!#xWzMu+f*<TeXq9${c7>q${D)^J)?$?UfM%gFoim{jeQ=-!4F! z@@N}m_?$led7Ma9T$2hL&3pk7fqwod>>FSiqKW)+sFW7>SvF;r&$g;sY)JnA<**!!=~wz8kmi zhD7EIk#)A@q@?#2q^ckn{2Hir6QMjeB(kIToB5%^{+4<59rY9ED924qp8Lg@`uwTo zq#7CV9wR(NFIYwy{x9l~9XV9=PL}vk!E=uX&nbAGT=0U>qJlWKC_S*a^T>VV z$(+JUhL07O@V(c7dp!fnEliZffwtJ9x*MS9l57(3rINgVr71+!zp``)_>H@H6KA&F zzO0^`pP7kk-Bi5}ZBH9#R?;&3PV&k#lk+T*r++RX7Z%L`Nr(dgEsQWP6~kd6K+Zq{ zt9sdj1|`pKJRIm0>XGRIshdE;&K0(Hw>L%c$*JZbx*pH4Lf3w%CB8$~S0JB#wY5)x zY;Bb&Kr*iC(ALIHI$$Lmo9Nv^Bu0qt(BStDw-ilOd*uMpIXbn~e>jgs1@4U%gI-2E zSq0f#IJATJFgbG{?GYdcy&$JwfJT2J7aq4tt?^Y#+oDbcHbaH(bOpo0g+Lv|LvxPf zgFMj|@B@}>i)&i;HHHO=3ivfe)k^|8x;TJY1vF&yYXH}N$${gPrP8=x2<;6*KQ~lA z_P%xq>>>A;C=+IB&Q&qGIbJhw%w3}ZJ(tC^;c^W6X_2#L7i2BWh1zc&k+)}o5b;w$ z?{+3F60gPxGh4O>{z#TU;qQKFJy_V5ybO!@>@9gk539@)@N#+W?l+%b2FQZmOzK*j zlRdwlOei`eton#x>mLcSfI-4cMXmGniu9aBtn=svfyX|uPgYLZ2@E;+KHHcM`%f^e${zzxB*so(CtD!$)=^V>ho-hCoM&bzYeZvuZtyJ^HNzU z*Sq&XRow!m5kq^VdQ$l|l4EpxlvN{hT?-a+0VCHDKe+USX{m+C>G!rVv-$ICN^~^L za}%gVp}M+!K$%~MzrjBZ7zx`rF0CNL|L{9LeKW?BU4+N@-9|Qs^w+vESBGyV>YNYP@B)-|62L{pMV& zjRV>5mJr<1)483dC%cS3UuW#-fjj;2O;dH*80l*5RVe}?6EJkL!l#&FABrrFB<*ij|#7j zT4Lx4$VwpI${D}^EP(L*z6k+x7?xoKj%J+Fr*4~MYgk^i$tL+qOILY>Tbh6ACP2N^ zp`|9kVXks!u_>d>7R}W>`{HZJcavS1UTgfR#!75kkup^&3A{z42YrHIGxW6hgSEUu zj0>xUc1l9N9eFBh+JY<7`KDAYgQ#(l0ga%8@OTQ=piSFXLm;q1M}<}>mjx~pca6C{ zV`^B2bw~okULIpIrn+WWO69{oM1h+Kc#55D0%rPBPPGe6YfJeD8-IckVi^{|q zIfRy~XEtUbA{ywTMPuB)>9kZm=dntj+Ah&XU;sfir8`6msIzt)7qC>BSRed6vMa!R zHStEoKPCz!5J4~v`3c|+EiH)VOzwGtCAv{A`T(*&%4;@LM=`qlYxaH-r7Gfr(mg^L zSoV1Decek13Q8QBZ{S$eIGANNc%{iEW)B8TZ;u*m=7#mwaUd3LAXY2{55+^epB=h; z$W7(q9;kwIV43NnQXjL!KSDAk|CqxDrp zD?^$s#p$^G<7+g^U$qf=iFNEF*aH&Ut1mTWip<<7(;Hd~ z_PU#BDya{58YXkfZa=t%Yamzh7TRyVAcU5tN+R_AHS0(>svS%#&SYLxqxZ)}xh&T* z9Q65qb9+~?M0ssb^rMRGTE_qCj$l&?EKG!K&(WU2HlWa6k|rVQ%D)5G&iQH}+f<<} zV8&c34i&&#w=8->LfT-YWhy7oAdY2)%n_|V_5QJP^1Y1kbuX|(8$Ep97&%Wfdx6Up zaua~&a#ApN49Pt!U$BRz4^*=t6N^GvzZd#V#vOh%^*i2Zu_~)S5z#L+?I!AIcWMBg z-#mYF?IzcUvF%GK8StiQIG6=IH&|n_Le7u+#5Jo0NJmfS>`=u3LRb( zzGMUnCC@&)@w%U(+7Nd0VQN+wAE*m{(gR%zm`pF2@jzgk(-ZqPbNOFse{y|=0bdyo^VXo6TVtKH(=Mv-6!n!hVF z3^YpNCIwQb;JL#%ZwZ&IK_IAGz^J=J`q^Jflxs!iV4Jimlp=GW?Ya@w13x<1YYaR~Q zDj>Z+JKyz8x9K%2`|9Inc3P-Ce-pah@w~Tjiu(8P%n>a#LAb}{JW3t$hr9j34y!Bt$`ou)wB(Z{fhFhqi1j=!5?>J$xW8NXrM z?UoMd2tK$(>J&b58=vktI9C9@PI={J>SPai9{cdc2Yb~qud~ooVc$s=AE$Wg0WELM zPtRCIedri;(NVMs1$(J;EOkM-7Veg)2gzD!(>bZseI35=6j+L&TiW66`KBo1`3*yp z^ccW7UbfUiMwMB!1*|qMaOS+VR*RGzr-XIRdT~Iz%ufxUxDs^0~XTgNGLImRm ze>0t8iv>Pf{=9JU3{r~lK*FgE0wOqIF7u$>2$flHqF`vOksk;6C1O8h4a8cFQ!|wx zJb-uaiTd1gH$5vItO71nyZ9RH6i_Iwe!R%3DT!^D0UDfSn_OGC*%_Z@C-4}c!RC`` zql&Xc+#;Ln%FVe%x&u_ddpb*3a#Nu-D^4~|=Cxdt*2^PUx`men!)eU6lL-s`+MmC{C)CeH2c)`bX9p3FPM1hw~ia7 zd&l|J0frh4LvkLr#$=Mg_r_t_n9v^dQnPtt-)*J(_d;bG^0 z?s3nE>^&2`AiNYr@Q`nQ%iQHfnM|8%~Rmm+@ z=f_V;{0@#(a}9DS+M|D=?(i`KAlvd%NO~S@ZoUuxF=vE#AGi&S0 zPtj3QYE`cai2UJM_C0MM$={mJ2KH!IuBTGk&4%8h#)leI{`<@^dC#Q83QlE70g|X1 zjnKrGrm6#*^wO31nn?`iE7)r<9^f507*3xtCpod9>>077C_}|tCM`;r07p$n2|hRLwVNLsB54$bnfe*ddEeNdRpgA+5+C)lLZUKK_GBiNok$`(G&qo*lwZh{J?>0-q83OgH`V^d| zjW%w^K+gzWk+~9_zCDq5q(NjG@!2>DJV7Ju=yu%q^|fnZC;tDh2}eXEwJCb&1qkPm_nz8;a0fj}$aH;E zt6?3rV|}L}ME1-nLL+kHL}+Qfg@LP^@U9W}q~S{Y-cS+uFH`K8;!wIF-j2-~t&LA8 zm8`LbZHWZW`36`}R}9*}EgJD{sYhsz%+G&6>6W%eovt27u3OGTXcpftjm}3tQb04g50EPsSrXbT}>Q|`_CKdmxb zZIY0yl+`KFq-P8l+i0^k%NLycIZIke%%TcGMkMz{&~A-hsJ*;DNQY=Y^K5p(sE~ZfOl2 z_BABj)#8lYVg%{*$cui`9qySxAmpNOO5oHIE;ySL=_ea*DnktdfTF0Vj?k2wc^gI9 zqa2%-qx{&1U!<09>X;wnx4?{MaYHDYOm9l{^Y-?zIpCQ_9uLz}_gR7~+ktT#bW-hW za`zwEKD}Lm_G7(o;yYSHzK01fkCaRilV%QY>R7AMK$mK!HCy99&nds~VyxSU9def} z11iEf8mAk+|BOk&pwvAZBn?U3<+>BIczOgSRRIy$o5_XA`Ei~-585Bj7A#w|7JTjv zK%=9C-3KRMWR>%C0D~Gv*0(TcM6(~!zoW2B3|;4J#+PG&@EY&mwBPEz*%;;CCu=Uy zn*F8DP}q`k7o9H!E&qr*z9RdPz!F{u1;@3#t#oI)+5yPOE?(6;XC11GQ>NXjQaIeR z;X}`q#>(W4hOR<>Q|ANwClHP4Pr4!c3q*te0#T~}3`GC+=i0yF=>NEz|5rZS9c2XY z1u>!P(FIP7Z7o;}VA%OVBO!!rEo5j7VI5>+U3(svQe8Bp7S|ZlxF?ZVtnOLjws2&g z!Dg}0L1JUVZYwlXD0}_hef`jV-S~YWRZl}ZvVxGdG}-yO{ka7j%l|q{4AdO)NaebN z2GF|k=Ij)Jr&qZl0vtNF;n1tyAk*uf4OKZlF#+gDs8KtWM4L8hhAR&4DpWhcYgFws zp>rEPxBy-5T_PPi@NOyzJ8}TGT{!3~wHq<|tN3#}r8+D-HT#+fvW*f$z*hcF71gp- zQipv{K#Rw%E8zSV9&kO_aZuwnvCHe|UV~oJ=`IkAjxf&Yzg4pL`SL3Q)>L(JH;@Xi zzcT=V(p_Vy$X#T}!h1C`c9a?aanA^vkIv6m$d-?a9Y0YZ*7H^p>Vc9TPyNQCY=YMD zB?_H=?AomBB`acP9|pSnWGMAum%ic!x|=GrrtF2Q`}bbvONzkfRs7YK!uD=pfe&%$ zGwkI#HxG+#CLlA56$b5)E-hsQ_w*S?a zf}R~-iJ>?PQj;rmQd46Ll)L97Bmy0bD9W%t7oPMTADi6>{#%rwf_zJ(Xp&Gh}jJmAh_?*qEhnp1_UYf4964yk6@L`z_b z2;oH1zX)D*5mpyB8goO3E0LwmLNq!_8%ZId*y2%4Qj4*GTp8T2k~WDMClcIi(p_6# zq_BQ~4bGoxz;qA8|G?j72trK0n1+}y%S;t$Hj!Hlb||YZ-#HjYI#a%I2WQMmNwk(F zzCdp+VmJWiTuC0{oFI79Xt=1N-t?5)AZQ^)vMh6RgRwhJYOUc~WOa1xXN5S?&^Q!J z6Ajz_Uiw4YS*?SJ1r3oMt?T?a*KN$5>k>))hj6$QLaUn&7AQ4C$ z>fTl_Y~8}5k9b_vp2*a8Uyk-%3%Bp zH%=b`2DOOmUJr{8d_H0W^tVAFdb=xFd+sqI=Y5GieRhBkxq<6j2!B;N!A<%K9U8LQ zHIs%Fo}93B&!E$j3!If3*bNHW=aHe}@obFcs?#f#@vw$gQqs-bgBvsdXu81;4P7NP z@_F)tlq-sN^pE5|#S?|{^Q;SvQU&BEJLs?KUHq4l5I;%!FRh3t{Ebmh7h0VxYqU~L z=L5-))HWZ!WVPRrLSdv46bnJ(N!0m5C=oYQ`AR6|rTJac3EC`=Lhez8C~7RW2kew> zv#5$y;YCU+Gf0-D;U@WF`ev~?5@~7#hO@{H!vX|23(94fSgiJgGT`-l;2J$Y#1l+; z5KSx67nQ~I_um$WV?-4>Sv<0lnpx_!Ur0tYWfvt~zw)wOGZW>u02$(yh}5S`g3S7f zp!FpF+EU6v&O}0VF1Ep{D3AGqD86-4J4%MeFv0`|>LCJ_!;s>!BGD2A85}=+Ly6R^ z_CTTS;B!Do^5%KK?FOLb`; zANqUd0l<7o)H_o&4##twRFjn)scla?4Fa%-mTNyFCKb>+Ej9mEfRxNT5F2DmH}X_l zcZ%mLpOPq>`bcilsERe`I(9VW*05D~wd|znOs}F9!&cM^vmUgITd=Fur-J$G94nr} zC}`ZbYepX36(=;eoABN@p)M%>VPducxE2~%w#~*b=X9JZ1IYH=9S~g07k^pRw7@OM zzKMwFzM`!^Ou9uz(-vLX_uSFacQe~pl?+8o|7DKd+AitJH`yW#SML;>gl*?`$NNvo ztG#R=w~iXFCPS!a8>YbOTm7yK@&*q?3saEXuhumzK33x^$kSb715wrwXJ+qP}nNyoNr+wR!v*tYF-PW^kWea_O^&%UUq>Y^?RW6n9o zH^1?Y>t5$M?yG~b6~j2+5zN9}3!#p@{x;s~5%XHq;F0W_&C+tXbB1+>ULPdh~sg|!Y2NLApamJqM-$@(*kGBMs-r(Dt|$(QJowROIU+BV^ZHe zo*tL2-R|^&d(6ZW9>Dp?PX06A;!%~6HgX2J4d$fI969X4BJkC-vN_> z?z|q16?48Lz}&16Tf7qH*`yDv=>WOr0LGS&jtlX-=)FFfzB1AQW&MSu9fp0!aYIT- zsuRb-dBlJ_TMzzf*UCDfrD8A};(i&4XnS}*k%IV7HG_M<&;#Y4^b#JSCIgh^-%IbO zd4*vDyKUL2T@q`L;7x=;iRR2~9lg4D`;8RmZ48JhH=XfM86$7uY}+9koEG`z4N{k2 zQqX7g#T>oKWkV_}{j z5l*><-BN0{&^Q9w{#ZTR+}TX$8KLzVw()@3KYDQRdNkU*&Fd>({nWG`)3*XgH?emB zPrc~IU(EN==K9SWIU$=ka@cc;>Adw5y#3_-jynn$*~F&Msm&1Whn!h(yCa%fc7w;_ zEZcfR0!zYohSBCvZCpXz5KIbUm}G?uW7ESbH8C;V5y+fc0dm+=+QXhei))Oz)R^ROnUJ28qtU}m~uzyV)pCbslXo>lpt;wd5OZ%dx2G-o- zBA#R5oEDx48Mf)xxw6*1;?wLZLXG&Cm6NmI>U(SS74Zk!=6Z^RU$AH-VLCP+z`kiE(3)!YWV|JuNUb7;m9JQ7oAtE@m9zB zREE`6@?=!sb5kp)ReS?ofSXTpt;{D)Odydm(^u{c8KnjlolgCJ4rPR94~~S4_&CU6 zPwH9S3%nc$Y!>tb$HFScr190?6hF1aig!JUDaE=mM0#M(5O@!P(Wy#U7)~+&Ytv0y)T2~m z({p1XD@mq|b{@9m0=aq8e7vRjG)Rk5QMzcQWm9!LMsnGM6?+?;tq`RpQ(-{z%+x#8 z`K>w)p1#Z-k-psd&r*eYuqP_)Mo_)x5LcHpgc7^F16C#&noPq*Nk8lUx?%@nVoQYD zO-01=4VOuIA6LyD%*^sGEUMjJRYYr>N2ba3jt-EWkYFP&>h*3F{B`BaH~hJfc4b6c z*udx)d)XntP2Ujt%R=akvUd!4Jn}~DOSoXuHg_>OQdOb!G6x!^?=U@YR7G2u6qT<@5}B*Zm^7QcwWF#av=2X0sFBq1Xk4cbfDoIRcve#;R72=+ zmlSzU@3N%0wcZj^;vEn0rcO(H&3~6k&keO@)C`Z>=y`~2q(!M%M4gKr9kzLKoQ?t! znHEzZCnHNeAp{BWD`K0|geg8@%%Bv%is(~NL*`&AVj`?DubTN^ zUFqxG9_Jf&B=v9BlKwc92#}wYPAzPYu~^!Mo1)~cIUb7sdZ#@yuTU!F3Gdo%vA7*` zqfe0K93)zpJ(dc?J4==ypLl)xUS>D?^Z;(`>4|j$G9~nNUYAqFK3fJ$dZYM@qsbA5 zsL2wBH2;m!T~K8lBWV`Zoj#!j^jSem(u)7&S*g-=IP&%zR$nfJ^GE_JVaf#nG=c=H zCwAI5Ym!v7+X?s8N1b*!4Q!F5QnpaU_zvYm7{8D>yn(1(D_!Uf3N1mPenNE=U1+$V zqu~q3d&rG4b3{ta!A1P%1M*|~jRoyNSI}gs%#g5rR8hhRQKQJ=`!#zERm!5?kRkk; zPTmhMC+Xq(qD19I;LIedL#h<0D1GAC6v&U5IhifW7uF!XL()CE;0th-Er*RouRpoi zN;KE5=}B!Zs1uvzrS_DWZ8VmpK_M^OLiwvj#Sc-39im!n!Y7CHV<$>)AF#QZ7A@nQN{n6?n)`fp)4)Hcc`dXut z*9pNC5G#Gtc>wawNZ6JZIm%a|t_v2`{Rppqu=?3g8b=$Ow-RYngvvov@jHXEBlDrZ z)#wLz5I6h}@yeY(YlHRU#u6&k&Lgs~1b}4<}kTo65 zciD^S2d_Hz!gE1(Fd&FH6RVLzn;8%bu*LC4~(D@l2a;E;?5_G`Fm2HF<%pr4S2;M8{PVaP1D-*xa{l0Y#RdP=1S3|(d$vjNr%=mOY zD3HC_u#Uzm=Q3=cMC1~wYIM~iz4~rcP|Muh8L^gKAh2PjUk&2Yu7yYWS^U11!u3OZ zz1`9$@R|~~9QMFhuDFX0>OOn8mH0hoeIK{8)X+jg!=g;Z2kRwM{tFTusb6#nX)J$s z{9wPpDhdhBrgr+N*%Hnvxj1NtK&H5cG@#hS*sY>@oEFJfYbE~mi?lOG(dGW=DuR1N zixrbxuQA_t?3^SEVU?}zn+P`qf<8|#jw#4X+GD+dyPIF^#jvJ?@LD(@duKy3GL%Z= z_#F#d)qU>nEuRvG2W;5~&mh4EQ_$zA zb2v1b&~$~#1(OAM8bX0^Y7%pPqoPcIWPuOqY2>){={4;PW zSk*!c8{kA06VLb!@J%q(1JbxXkqc5=BQe=5tSICmP$d*`lUA4Sr@#%xm?owN#hIPO z7W2*$na;%$%WXCtE9JD>LJ7Q|JMf?MpU^hjKOiLZdFb}qovt~bY3_TDbK1Y&W;Wr! zMeX8qy%7>&3>M&|IKxd~-Equv+k$e}sa&U2{sBA0RYlvaf{*5@K zf}1;VT>J)&)l;@F)kYRde-6-wumZl=H?i~2Yb{dl974IrS z?5x~n2HaS@)drqf0OG+pm2cjDsJqZmCY5i|-Uw7)uee5vJsDKVIh2}!-dn|a_+FGz zYS8{+@Wg6zCHeSPLtU5m(u1{_fV1h-By(fp>BDQd*>Q;+wY(yftMp3Bd3p-F9ol$- zNJ&MWi}%$+`Pl0H>B6c+1Y9$cb2*gzO6EuzBXVmSKmsHo`RYkzik}rP)+}*XhAw z2NjIxghxT0+qi3rXeth~8bVb6k)1L%s7@pw*eg-9K^HBBQ?ZYNlM|EII_k?RxXvm(C04IQQAMp=72NyB z=;k%n=zeGr=9>oFsrH6F7!ia!fQZ>I^ddWQ00p4wJax^9OQ;4_31| zo4>yBxiD=+hUj6sf?HKS=6Y*ytUv(55Ny`41nMv58@QTA7WQ47=XR8KQB83%qeqqs zWB=`mU3jWL<~uqcW4#(##GrRPAwIy$AFI83_LIGO4njjRAlJKg;JUrEs)Jz>>)=%C zDh?yP+YZaUSgNp9QtIA#qI4=6j1`qsYc|*uhB^gnS7C>Z1x_s@)^A-8IQC-jZQbD@ zSBzi3ei@7t%h$#;L!+zPQEjo`0Pz;>f|wJy%QTgDcwPr`;3&;%#A^LW(rQ@UNw=qg ztQ~(+0sEfG(pciMmFh09x5jtmmTk3L-NStTtxfsjW24HTz6yA zQ8K`Tef~6Mq<$^9yEml03H~E zDZ1J=B#4dC7Gb`EM6JnE6&JLi5>uF^^WuQVv53i>>d;TqTMVqnghH!Mr@ooUC-lJX zw`&C10MBodjrQ`(?-d$8dx%@wZyv=Mf(V@+oSsql16_@vHwra;OLe|DjH?`+v140f zW?>y9F%I4d)QMj1I{e4IR7pSk)*)bEHbtS;`Z9Y0OF3MHw!)tnR&<4L80h8SPioLd zC@(h|M&@7I{A|u11}OhXgTvg6`0BvAMRXR|P@{DO-tY)7=b0v{n7bjAk3_!l;s=L( zJ~+{F!mN?fJz9=B;-*)&IdUJXStMYL5hi0r7yP#+#8@jgKT!hA1Fe2no9T3VCD+X-0UvKMUc3^TJwpHzBpvHJN ztaaiI-)Y2ydT}eV7siXTtm!IBwc_>p>$&J^7wTz9=s9ml2=xJOTjVeXCP+$_s| za`r57kXdR>1VF0rj27<^q|qaesYM=Q58r_@xt4#TGhwIb6j{Z}c}}Nbl%&2sQPK{@ zJQR^;4mo3X3PEJCgxg21I)&)w3)U#gmjbuks9tW47U`-+=>2N zXWA2QiFcd?{QFLTCSUy-bm$G`p94&*!ydVJv#LN{-T@8hOC^pOjX2%mTa9VK2bZWN zG01BMNS&Kq*f^GSu^8uxBZNF#1oP!G>_M@-uYa*KZ42_C$^*_@S-|5jit69`AZh>J z2TA*1*i?G}Wq{89|AIx8rl;g6rzBLwC1fThCuqhdXZ97Jre>6GCg_zIm=qXT>X;Y- z$VXLsS6BrGCI*&WDvI$LNf|oI78!a;=`o2#ndz|uDyk{!u}PWcL*RdC&fEY%Z(*k# zdS?J11Q(DNVgQ~ET`a7PX&p_BOf2l3|KU^c#7@}`62JsqcS$xwAPc&}YkO8IUlyy> zCKHlPQ1OG3ob9u}&%YhnaWgmQ3~=Wxo0q30Av1b`Olz7znB6>2Q z8^KsxH*a!l;p5EjK9i4c(a%-^{>Y2Piq-rU&6qmn#1i<(HzjR%A!0W&0x_|FbHL|w& zClu~qZ;u;&NZ|%>pp*cFK*oQ6yMWZW!qx=9gK`C6V*lHwo2;w@ zV8WvCfIvr5w}jbLZ_mOX7CNu2=-ibhNd}=jZna+&+vL1oGl!g%zPM1_*a1`B~6|6W0S53|Az? zedtkJ!zAqJt`tUd^V&XSG35L<(V%upWWv%7Qi9!k{VYebU*#RLY;5MrKY!sS*odqD z%(>mdO{Z}QPuyU&;p*8lWm&=4W&6jmpreB6O55aP^H=Wm37K%RYNa+Q+a?|{%t-ri zx{GbP&VMacUU;z5c%ueLVkg^(gi=%atP-7A8lr5fZQtGnWKrDAqrdB8Q8$r|0I7>aTx?2- zXV4T*S1aTcyo;(5cfLZ$$D)Vjphyy%2P--Rt!zQuUe7~O8w+?qR?YhomnmZ$%TiI= zWpCB>cFM^G6)3s;hbC1{$3t?kkso7>@MR41mAsH2SOswpHS&9g4ZEToH-R7hzlW+IeR!i?BMtl&dyF0fL%T8yQ#-i=pZFm*b(!1uPv8c$R~ssaDyFDQbT=5I_cBx}9TX_B1)o33V)#=i$0G<~ zp3w#bTk!d96A2qkRVZ==EZTb1)|W0zz1NEpcN>}qretif72)BCub>0xa6ODUVhAgE z?^<+VD>N^1M8xE%NLBXjT3zO>m;J;P8V*xGQ0X(Yl_RlhWNateX+s!VE17-~(_<#2 zm?{y$Hgn2U&^G%vaNiTMwOM*V zD|#{&)s>7v7PH@KVfrmcotl6-(1Ui*) za|+Yq&3}kc%|cx^kOYj7laLFO=#tlh(39-$;#185iF9#(g#14TdUJqp;E0A`MJ2XiUlX`u)?OoH^U zpZ2&Q!R1^@*EVf(cyyQ8Z1!W{$Vron5XR6M@ciw-A%{uR9HUx<_}C9I?D+Siapv1l z^3b?>_!VMi{`EL+T)NR*+)P1%X>$0n?Y}8HY?iV|Q zCF+W3%rJ|wf?x`pbC8-Nz9Lf_fZn02_KVChKL}Gj{X_Qf*TK_OCBqaCSOI~6gNNsT zvjYAW`bq%##?}n5KI{zuM8f|DlV>Z>$RPtn9McXH1DpK2LjC3d&3t)dN&<1ou%d9t zdPHQH@U4=|5*Q8Fv8$Aq+TO9u?_RgS;bg;&eo41euGNB8mK@Gona@2Q*Xwp$40s5*i;$!IPEiIHI%g(+oeetY@&qEs72V+FO~j4Zh9_jW>_Ag% z4ZYn8(W>Fl9kO~OyxB6DK`Yab2VGi|(T$d4=h42EED7UDyP+`O?tLhp1vQT*J77&L zwHg1a!?ho>7!l!vo|mXgi1;D23=J`|^|@80ZPld4(_kr3{-Xfv_IX0hOPDj6uy`2* zA?5u2bts`DXyfY}X2_Gt0HT0Wc*7%o#T5VA5k6^aNAOCC;&9LUgXI!*xh?DFqOAn3 zB*9N(V7dAlqrhDcy%>{St^#ul0gdTpRz(r_aX z)gV-(O5*Xk<{H}AryU}X-bcELN;_N zAt_Upft6KZSEhkGA@MPVc@x|jYPeiK=8lmY?zS-v+6K?am>C6M5UB8ghahr+U{fDR zF7EXo{8oR(`Ux@Yofse~l>)^3e@L?aVYdhD>@GF{>OO$TZ1P`Q{ol6u zxi{v*<`zvG>a+f=S;VDxKq;`gBB+Sjre`zB$rMkbDz>rDuroS%PGv;X&NIVv!f)@8oD)-j3->$z+ ztds*KDFhh_2It+!sv%zZPW_q?9ye-f6NdKqgf>0op9IC#$$oUsneUvxk`~at_>71l ztz>gKgj0PiXRMri%P8icFw$X$sbn(SJi+wn?!277MQP8ifKvK4Vp%8!DhW?g;{KFxPwa$ip#Z`?j_+qu;|W~Q!pkfbpQ)uSZt?mTp@b#ZjHOXMUl;Ee?P5iyr{%3#3U7$1Tedenc? z?@mZNw@UB*>RH9dJ0c9`a{2w6q;3>tltEaeI3YGL2~hf4(wwxGLLS zXwvy})B`5nJ~t8@e0*{Z$8At4y7j@e66T9l)Ju_XCS{8_JMY zm>%)o4#1Tw&l#6b=VIE5w6m(etZ^$vo!w%OCbe`mx9uz4F!!X%oS!7qQyYLuMWcJy zs=6**T(q2Mr>meQa2GFq1Td*2=$lS$bQ=yuct;T+W1;zXw!h#{HL^N3rFIjDZMvH7 z=lUklbh$iM$9?o64Jq_N0Jo*5>=;=`=Xd6s9Oh6@d1`&IZSnc!pi7gX4eKq>Q;=M0pQ_bMi(D?2 z8*{jeP1=i#R|_gr%JZ1pf}ak&`_H&sRU@Mf+dC3$*OXv#`%sAp?pXBPS!Fd+rSqsMLP;*Pg;76 zU+H(Bk^bcE#9jpQ?FzNL^m6PM|3khh&A3L_M|=q#*7{P)dYFqS7~n_ zDF^Y_t!@Pfb(6ZZTUN%?G@IorkJM5!<~JyJ$KB#PU0f{aF^kjEhZf)%`aeyF(O{80 zy~;f?4(O*FT`b^CF@|gyQBCo0As_j!Mr38Dyve8=b%;?259WsqX3=Qz-ZUmdWk)v~ zLg1NOW;Q-TzKRZ`d$+HF3&#E!cPoyxUKQRU@H|3Z% z2s5uo0$Wq0&&Y~>o%;Ih<4rq^fIbUQIpy$OtDPG)lzLGdwG-SR@#aP`+1FI5IF0Rx zYNl_${HM3H(gO+5wD22;nd@u2v&81g*&&jB6I|!ywj71siwG$Cm4cs(`}*JKtap7Z zuJT^5Jp-ibIvWx~QDYl}%+R@BNiOk(DGmHf%=b|w3k<=Fe{%+fn8~vR-lgtvVcE^d{gM2`gAs<)geKN$~ z5wLa(|6YGsj5pX4${kisPkjEguY`$B8r#Fi6&(>;X8|{EZ)|enQH4bpofFJr52rSn zcQ-U_Q6-Bnb{7jtXO}gUfdmtu*Idfnx-yD~ASZw%m-tv(uaPB?>Wb=9~`zfP4>rEYy5)WE?D?a{xiX{MRvX>T)=9mF}*aK$X=PIw}`4*BE5?NrnOdY z*qNmYPG_HfuZEWNd4YKePAlA@T#_Si;sY!NG&We#@nra>f&nE0U9^9CT+nPZpxBB~ zo1X7w!fN&x-oHrXbKv`3P{6~W)roaWs=CGN{ZLyYml+6kM;va!$%ZoB)w_Zd_iVJ0 zQg3qHzkeZKE7JVA;_Qmt@XK;^O=~DXdDcg^9TG_R!~@%9)(!azq#7EDzYpwn{*CdX zu>r0CSs_LXQ7_X#0%zD%U*688tiuN4U@DM?f7^6*d&;CyzJsvXdXuLvZveF?5KK86 z2&H5ns%8Kat-o;oJ*0G(f%lA5gR^B9qb4{icK#We@DKI{hKUcPZ7Ds~fV$jv?awcZ zA0Z=xcmGIx`zvq5u|lh#0_1G#fGVB+e_x~gYju*WVk0*%kK${){9Azw!%pc2Oc(~+ z&cvSp1~JPJ4V@vK@YV=(isn>v&UG+%7k)QVuUFjg!4=EM`;+E`GzRO^QViHUEY> zu2D*)H~QIQ*UiH1^WcI)c#~4N`R!6!)1oFWi#PNp5ELlz;c8FPHJC6V`J2B1H)t+D zmBPtK_Gm&=&14p#1JEt>53xJ)4m!fiY1gu)A?Tu)9xq#A?m+Lgh^0tSbViEVY|y^g z4jhJ1h4u%C*w4hO9Y~3dd(IrgDDTn}!+nzQbQ`YC#v5n+F~Kfzxf$&Ovy>cWiqd#B z9C-g~eG6#>&ii7Cgdms~jNn15CMbhxb?4~@pgfT@l75_3< z%7&y>^d_Q2qG(CJ28B0)a`mATy?@s14h$E`cL6KJ7LZ5t{cl!?imlcEkVSW^Oeg~C zcpjcH5(6fF2!z|M>u(1RS^_ZpP+SbZ1uC-`68jH^ z`jP@HCWReXBdlN?SNC)9hH!*F5Zxv^I>~@x&Op|eHccW^Cp^;)42K+|vv%(aijSdE z(zRSANo~>9q_t}IM9+5aVF<6VV8)WoKEP%)HrO1ka;(=?Z_XM8Mm9~y{U)9 zp}^*=C^n5gFOc3LiWm5>)PFWn|E81f(KGrU*Hq6F)O3N@zxIN6fXfvZN0b>L&HM+E z=^ZrKN0j|NfcFWt^hCN6N&lPKH$PH3<3Ezxl&{nZ(qRQ=4s>l?Qo64vG_d`Mm8bGUUDDqay!AreR$B zo5nj1qC(oFF06@|<10Ac5f&1uugDFq)^;Qysi>Zb)6;#|#x5c_jZ9-p!R7n5&Z)Tg z9iLBWs$2ziAesLdLlh+2Nc^2EQf2r$Du^+cHd6L(043ZA%~JitXnK!D#Uq>>$;{zVc(c_Q^HV#@K_8b@>Q-Jj5157mdo@J&knfEJdwK_Rm@@;9M1UolnV z)ds1O#PI2y#hgt$w_iMW_l(hjo6D(Y)AJYRFFC>p}hq-dnfvigd3R~}Kt*dCOyn5henB033;FwO9iRD6!>A<8gJMN+7)+a6=vZrBx5ZPW(jRr%T%KorX*4$K-5$v$JK(;w*i}V{2 zV4Y1wQat?@#VFRe2quJ$mlltJ*$kCIfhi}eoPx&UrP+ntTis^P)vswHSQqwHLm2MO*x=a$`Acv@G>nlFvCWHC^81yr+h)LYy8`vzMfY z%Rp10-;}-eW{`40uWV{XzHPuNqrtf2CryBy*!`+~`@{O~np@ z^;{F$Zh@w*F$&m^dMQzjpMl*_Phn!B=2TEudpS|>Pa zsl90Su@kv&M^dSTdOPmMq_}A-PFC%?>P@P1dEuw$bb7|{KzsuaLxqPfFDv+CEC%EmaamJC;HpTGP8Id(&s$jV*eVDyM+Wp& z^`!+}p8o!7h~ZRKAtgeC!XbonimNV6F+fPnD}tU_N~!9^a=pLr4BURBNC+^|FKn|a z?2$p3+J3&z&9XlP*&45Ll7XUobJg%2CPngyyCbyp75hP?O>dGTIL5WR1)7v9VXG{Xb*;qglC8y+%|Y09zIDQ22;atF>q1n_vbBqYb}1?3Et5As^|-) zV>S}+s3B85B>a%Om{GG!I1+|7B!1^Q{gXTWC{xWo?IjFKi~+Q5P8Nss&L|1BMrF!@ zuU3sRJ1^BzmGdfSiVNtn-YU*1k|ODSBTt3=yz83^Iq^bwNMcT+l{OdzXCi8{`^7P; z+ad^emV?ft@|1T~!*V*7YX0eSeU9Tm*K{d_QxulLeEe$Ipk-bQf1VI`4z&K&4X^~q z=EB&UsVrRQO9m%rpu)^pu5SMtvvb3#&VH5dvlE3zJ)O}2&GLa$QRBqQ3+~?PRgZ~v z#uOQQq+375DbyI=4BI0@cXUg4%@|Y5!nYg((mvJUl|i~I&(%;Jnpt00!#GSlgPdB# zBaJ^g!<^dW9!K`^zXa*Q#%nzwhwLL=kF|lbM!@KsmHKpDLo1m3bY)vkiqoieFNj_> zU0nOAUD=(Y9A$e#aXf;=-(z8gGocnb*K41Pk9=2J@ikx4ZO)Dz?++645026|rqIT4 zZxW>5rLpe#$I?fUiv*`_=hLxziQnCaeel3a=~p>?G!sujYFKb}+HdQ&9G%HGyo~3Z z3OrCHAj!MhTJ9{73{An-DVA!=WNUhn=w#4yUsn{jhP1HF^2grxy_4ry)jIyr z|8q{1Wvx9^3Q&7X{@t1SpYJjMKaygyqRwB=RGvnSMpqe`ri?weC`wUETNFJKB6EI} z0H}EG7dDBI%TZHRQ*zR;!e2#l!MraZZ-o(VY(R+67Or^H*`3EZ6FhEzK0ZHTb`bQu zjq6SahDt&cLTy4W%9ZD`7>z5uY`|L)pFxFsD3bv_3_k?7?`4J4hfFsP6*8?XuJ?Vt z#B&XJS5Yh+iNZ{^!|^0x9&J68t2~oQ{X%^-644Cokq_A|So1#E_CRnz1*a`6hB{ZG zo(}ETzCBP$p7a*SRyb55iMpv9_!hExW_&r&u^Gf%#i;xzR3=*UmfvltxJin#XCG$; z(kTrvpDuXU{7r=cMOUZek~@M9_SFR|6=OV6%z#3MsGZcapY9?x*vO1Xji&=F$e7Xe z$*=EK;%DG$lCjU%Pk5ALQP7tch_)s+nxeKaIZ8SM&Y^-SbQ&iU8ehSasG-$gLy^S& z;@r`y^(iUUr5~`C@Z%;Y)&|p$@#HiJzGT7%PyZtI6SIWKQvs6UHiZ9pr2m(0Z2S*` z|KIg}wZA)3*TVn_*KmV~VHJmd5KSS6jOKSlO~AY=#vPKP;T4-Xpy(m_e8NIRMvKH6 ziPR#g1Y6nXWIEf-hw{q~-wto9+&>|{=c#`pIl0BcQZ|HrA_L_dGbV%lc7cGa^_~>CI1u8$#%` zPY1bhQ0ZOwj9%Pv=*!(T3RlTlF8at-yd{O8CLFvqt8&Bl8x4I#%)6*)_?E+G1`yZj zA-?M=-)iF2T4D62a^0GEi=23?aBs=qmIT;(EjC|BJ`TQBovSA!$(5u;vl8n$RXh5w zsnb%Nq%}*T4peiN1TBN4_W#_2i1`Q0}S^~#8x(p(Xg7orgnzU)=z3hO&q;B)18SEI+ zr#}XvVvIr%!7XV}f=m7ZTO}9949l)BvU+ugkWQ;SW9Z>$~f?|_?=#!1vJ5VmKUR{$~0GNkAJSFV7{XY`1P1}9tjt} z1ld3q1V-W;V^+T9sK}K;U0GdOPN~FMGIJ<2j*BQIsWw-X(w0in9u|~YBXsq`;jY)) z>i6JEi8gP^c`;vN4NQubP79lK*QAWIxExD#YO|q}whK0QTBnvwvNmqC4UXxXfUWkT zujg@N;V@$y57AaFj;8=k=`n#kX@7({5Os{^%ZP6G+_bO&9^>8Guq)}w3O9au4zNb zk=Pp}U)h#)(5$carkLEFQXEhruIU{}T)@;|mk$_hD&Ly@K#tmd^cT_*r}^&VU<%Ab zPw>(jnhf5fPrQ7#V5q<59AL4ze50Ycd;_5`goIZx&dX**&=>E{i5ypQqh?{xd$CKXERp;-261TV!FpV86fy^- z_$gx+kyt6^xpsHr2zHf>;-2$PVwOX+M7Fb`-GiL?bZFyX(bizk>T4JRE8!oY_wD!= z;N((1g2u;>($5t_#?mg$oo#I+?cAPdl(CDh-u3v|5gw)^J|*oR+@!P_uj#8@FOYNW)S3+j;h%RtD2iX2zlP<8A4t_ z#j{GxZf`FuFZpnaAZD5^DnrFR?mTQ_C^3^4FAN{4o%aLGsck3m4NWcnj3Ik-d7;zi zach$~(PRd%Bf|V(Z8+O6xR$j|5~=#6hWE%dtPNbfoUnmF(CoA+7Bbv_54;##hjl~T z4CB6SWlm-9KoR6Cl;YNDf4_E4O8q1Zy5IO3p0WMvbYr{jiw*n;IS_HxN=(>nb8z?9 z0oyt?A>#Bcly)BX3<>=h$$ZX0Nf5VhhkN?q5QKs++RUr;A*mC;a?{PymQPK&TVan7 zJfIzprEaIteX_QIE(>8u9>VF`mU4&4t8PV^zdJr}+6@e|5MusW{T6yUsrHPR<;OXy!?f>E6 z{TCZ!c7l{#KR-(F3>c`eH=q1K!9_197P8Aeu|gDe0SU&l(2o>Gn@|`K$S+AXGP)0- zKNR~Y6^tvw`!rXlS)AJst|fNY^tIof>b3aMusy7g>XXGNLoab$Ay$zQp?pNe)vVlt zq?J0d>Nb!Ff8l}a)Yxh)vrlVEaZUa*k`%sQnLNqCK#0*)^k^wfJ9k>Y4c=6}`}t!{ zJeQjGC66-DKiIa(N-2cc$k9bw{qe=j+x=UR}VpX6fn6C^rrP4!BI^PTCr zE|t`C+afO1c5?}=y2{oKmTEU{RN;mN10JK_s1}WN*j`M(x<$2hB4Fa9SUsdA$=! z;EWuRiSQFk;EZ$;YRIxzH}dkwe=!CzS*x{30whIHfG{HZ@BisP{DptF0SXy7nE-~a znAkd5I0I%_|7V9#vC`jcs?6RrJBINsFB(G;DfFGiZ-xZXl7@mLmV)!oK;T)Xt2VBw zfFWgmJ42}%FZ$k3Vwl%7*wWD8$U!iZ$F+9bKrA=hARux~;-{oPdAgHe zm7Uux^K5s@fBJ+NOL0&Z9;#O#BAG{$h;fg`v@Sl?u*yjKyRle-E^FL8d+V0=hIgI ztfuLg4PstU;e(h-)wO^%N$kT2`q440+wMg*9c%(^W<3=@btqme#CfHWr(K%=t^rIF z17F^lj}?ufehl^g{-+!Vbm}n7^f(ue4q43Xt;nF)kYBvDM@vR)(b%Jv%P8FC#17tpr-<)G-!pXcFeA=SB#;7m*~bgu z8Q^MM3bG}BMAE?Gho7n4&>z?vTgaRd$n9~x9^v@I9^~gjN??%2Lg12T)-KW`{8)R9 z8l0AXzg-QWtVQGwCXpi^U}k8?Y*0N;N348mQGlR6bV%fubz^XVZbh8vF34~z$go2! z#E+|9@+D9x{Q^s9Oh%`l{uzm+u`$$LCye&v;1)+b+)(%+ z9>Z1WgE&d>9fQ4}mt#yFlWu$N4$^jgu|5?KOTeSXN!qb5C zR$grX>X~#jv!z2I0s_|{iZmwmM#L?R9B&eopr3cf6H8DNRTAG!pAIodvLqc%OfH zA0JG`=Khcsc6ym2xXk-Xoyk4CM}PfLDf|U?di_u@%gdEN4@Z`rk;XbbFOj-bnD|rR zsX?yf6Uz$g`yJ-A& z?YuD@n0vqnuL>`6uwjC%+U>zNx%ha)*DNw(B@k{{#k+@ZGqW$w*;HvJ$LUz9C(1)O zJBk()2TF9*R0m3wFs6{(RIih0oQoZtuiU9KadJP0&CZ&FjxDsS=b3V9Z-JxH&ziEV zoGT7R1e?_NNx8C#Rn;rzJ(wDUQZ2kwWYHl8G91A#SLm&x_Xyv})jPu;RUJ{OOanqp@n|Pnit75z)o%y4wo0JrA+$AZ~^V z)DmT@y~7{az}*wo%^Igu@6fX8jhK72M37zzHIC6*fH$ZTZLnII5fkf~u$uQ=5Sioa zkfe>&+{gy%&5fAwVpva^o9HTMi)NhB2(wkVeVsPm>#eS=)5U$OxpaEfxKr z&b|Vws-}Bix~033?oLs<5$Oi$Zlt@ryBq25?vm~nkZwdu;(z_rpT6kl_pbk0>#TEO z-FcpwIp@yI*?Z5kQR{?}v4j(Q-$mMVo(fb}m{5J(qtB*!Q@&&z+EW5pBk34{OdT<9 zD9p5`N?a?0h`>IZRFTLp)~GVy-1DP>tNBb-TU?-CXWrurCy^ z1b5d=SrF}08-g0@rzIWLX>8A2X2uE)sJL0V3LCj(BR99;;&Y;6lvzv{v>a^)dBA}^ zQ$)n3gdonWoCXMl%z?_Ngo+|pO1j|m*;qB0@cg-25UGOnZEa{xU94|7#-g600fq8X zBhBy37?MFT0uk)_|x@|g!G^JC2(e9BI)eR3) z@~BhT44M+*^;0V9go>Q1THXUUJz##OJ~w3`7;Sak(rF%C8R^)F6(?U&ngg0qhX?1T^ zn^aVL$TlF%s?~=3?Mh@`=%ycoz@orN zA0zTF4b@?~;ICSofVkv$z)viLzwFkWxzh5+M>KDEiW6f9j=xowqfLn2NMhR>yZPa* z)sUBW;udU`9d^BF;9LCIQAN0Wte0^UlO3OS?21Tm<3ITiJiolxKiRkyh@-4It)HjK z_wAJ;qNfaky8*kK_>kZ2A@vE-JAI9e*=;5x{Y2W^**Y#C9>Lq0WWAXQi789j)1a%W zqwh+^`^y!FDI>pQ0lsGpIc*#m_)9uW_8Hs&7B}9V@az`@r z2Q!RsY{ZTU`tADK*7}y6Lop^{9FNPOM&Uf8%?ufM@u5qoCd(BIqOSHpo}M%7TMcP! zpw$-a$(BKy;3-jqk_q4x?p?>L(OwfB~g~P(Dnr3-s%%Z%|LcmasLGWY1KU ztDLA=!Xq3{&FmzhrMzWgGo7tVh#b+^*8NP%w&1aLPjx^^G>RF;_?%c;<#|FvSIm|bi#%%CU%|T^V zpU+G;5K@0Z)93SkZX&WYjKldU6>^w2ZYuNL*6my|E7CRS6>sCtYw|mGdQPgoL*cl{ zc}PR-b5p8uz=lhWbI#2~ZO>pyKO>R{yCT&@gOBaHEfh1SqCA~+K%cd<41wp|TJFNu zg=e4Qe<9KRfJ(pw>gVzK{cKdAc_KH0Y3q!LBCt%TRTb&0_gcNBpTNscWCxOPr%HDq zr8f=k4hcd{tlskDzmfBwC`Y_97ECUo#-1PAKNJjBW1r2x3LX4F>W&Va zSFIFixnwK~h@uB-q|an|rZx?CiyXDGNr!KXb(ld)bT$cE5H&s&jm^ohoOe^V@sf7G z(4D8o*E*hz5`!+D?-mbCMQ!->4#|y%kj@E%W}i%R67lRZYDU~MAJyCWWO^KlCOgs= zZ8?n(`4uhf+(@oV7$@6vI(74c_bNbn~#pe*CoF6dsW zxT#_WRQEb&cu(DTmqp>V{aw!yTrq8G#~gzwl@#1Ugf7P3n zb=+#53+|VFc;G)Lcl0UPesWEYZ`yn(H#Q}eojjOEB8892_<4e&g*mS^Jt%3AvOd0- zHRf`hUCpYQ&;mcyfV1C1l&<^yZPM$FBG_xEohk2Ihkix&3V%}E951(|C1UY>W}Gc& zdbK6~eB2zU4A^U)=QaL$(^vfWcu7yaxCs(6A@zM-z{H()&W+n7l|h3N`HFn25!@W< zWKG1&chsmU<%3_zR%@j5L)Q|fzm#O#gp9?_B6=O~eS@%hDGP=pe@ztU@vfK7*#Fj* znF=0=Nx|Vd6*t!QJguuqNH!*kzoD!$y|CTTZY>2biK^Sic?Z@ge;Sm}6OZ!~0jeJ` z#alw?WrD6xST55YSs4k%H@qrON>0Sjla#X!s-|$`oRT)1{qA^QxU6q>Rl`<()C<~w zkDrIhKR_bTXgz=H*M&b?dXNc*iX?@HDkc05cr}z6Z;d;YRhC9wYa8r3x+sn9Gv)qr1p#GjZt=r&B;Q@G>QFc6!b=K1d})Z(4D#>{jPUGNX#!mJ3F~TN zqG?j;*TPVmp>btSxY{kUP2O*!G3By-7j zL?<{rW89&sydsQ?lHlH~W=vA$&h8qG8AWtxV0-Ad`PZa%AFH^9rSXmmZ{E^UTgl0A zk0uZDpX2$t)H9!0M5y*ff}67n9sA*Pp2BIiN}Yf^Fn@5u^B#6p&kunksiZVMZrmM(({ z`zz`j+S&2T`p}h4^>e&czLy^qcZ6>_?-Evl<58M%d3sLbJW$oa-o!<~X&(#knw`v) ze>pribAnjA29kuB;|`o(C8};KQYiK;7DhO7D$v4j_AEZUB1x|>G?{Mpbs{|#akLHFoHIH9Ni*4c8zg~AzM75WYtgB4{1JgUt5sZbFF5n zqR+&9r6seGY#ZQ(%Kj;tO|D;SmWUfZV$=VmnB@s=6|tEho(Pe1Sa)gmuHut$cZe~U zCmGjLa$XOVC@*R6vA%&n=?Af2 zvk@lUp}Nt`uF1UT_c%bj2?ePL)lTMIHjs#?rNt+s1m%*-#MO^SKk6 zu!I^C6Y+FXFb*=et)_A^Tw%pRp6w+J^MgZHZ)dhC@-{)(wW{$pp+j^+qRwih_)SiZ zxjlzF?87x|!@Q48>8FAukiB2zz%3dJGU)DH>9gKD*bA{47k+pKTXE&SYfl@@-5}Wb zWqq~jD21&#^&$NJLuG=01=GRjzVqC7D#Hkp?7nvAuW^))xFs}3ndB4_*o6iR$j_<^ z3BiMWj6e3foZsiz-9ReSU~mLATB;*=jPN&ME7qD<$7idDT|iemj~#)9k7l&UTw6gL ze%iBTSuml0fqCdwoi6B1vlTq%_VnMn$VVp)Os?eKcf>x!}~BfS_Toklq>msUBpS8Nv3 z+gk6QyzUJGN1eo~OxPt8Rb(h440VJfMU#t>r)K=P=TDh?5Tp%iFDoARp|%kH*ZDh)vwPssBU&f zI_XM4sJ;p&bl=u{6WnJ3+;^GNxaC-@^wzORFWML{1Sc4il#q0NpJHD-x~*&;8wj0u zv7eq%-LYrYdU8{vYYEb4gGE7~6;noyS=U-U8WyA#o&Q~ZC;e31qO=iX1{SMxgmbqT zZB{VN9+Y?O znniHqU3+8+!@?#LUGV#7{>?7xL)rLAK_q^g0yj*@HQQ~UoKjZiv}753-=eaeDfLSF zHRyaM=sK{fP1icTTIOYAcT~j8YkYNH{)v+0n!#mfdjBC)YrW*EY}c+Q7yCWU{{1Tg zhuEgY3qpKPO0n~v_k6Vv4KhapX3a#cFTl+!j zDpSQ3&JdcISxck6z&kyP3=c^k59Kaj2El9%&`a30WUNOqPmUp_$JDpwP~#9J1KY8E zMF|TVIctP9nzIQ%)((ax&YYEy>z?7sVc7O4oKkpP{~!wR%%=c62mk7s|Ms|la~OON z#E?CF^)&!_623O3Mtv}zJjqLbfkuyFoh+ai3;7ukGA#H5+@VsOseSTt&rbFg=zXjJ z!FV^sNoI(B9u?mr=#|a(3YX0h_x8y9Ur_~&X7 z!oleo6;4p+St_QfPgrVpf%vgsCc`$=aNkF&KDAGt(cf7#w@K7$H}S9Cj;hjbva27Y zS3TgKO{OqoAU4+Er`Me|RV+TPv-5d~J7(vzJKI~DUvH(^AWwv}KlI>TeGSK@NA9xO z*jdK$91`?U#UyRDI_ZT4duJh6t^6kynj==-V74WAA5Hd9GN8mfNXqNB$jLTuXWw`!)@^ zm*<0yO*Bj39S!$DFWzA& zF|<946vxy00F&p&xA;Ox%+EWwOz@$Q}hyQpg)PepQB`XcB z#G1|skM|fnJRO*f>kWUeVTK@mLe%88qQBY9p{F^EEZMj>qTHay+&x|iDSEjILQTt zuLUpX_OWv5A>(RxBv>b0eGuuMC9;OS&&e~nK=Nk8nb$^$3(Xp&j~cgq+oCW~wB<^o zNh(U9gA#KVr)})Af&_h$rq7@@ilI-T!5FKp9Ev`u@;tWARF+~cIZs+%QdPD@FIz_X zoQSQ-bO(Z1ZWy#nNt9cGbLrheQYKS~|FcBbO`gDb)E1P{g}l6Yk++?>PQo>)CBl~GI!eC$qPRAeECqLvhO%EB|J*i34aDN8EU9+MRU?vf$fWr%!gYDfz48_LvmNPR*#7~Ld8L`7p>DoJ8YnUlXvhA`JkE?Hsb>cN6 zLOF%gV=Ica?Xj!;vVr|+K7X;FqNDTzu7AEEa#az=j%XcCdT=k^(I%HG`*3sJUA}Fysl_oui8nUQBz1B z+foXLJI(STPG*4Qer6gF1=t0@qcxo>dMP{Cx|fxQK7G?m%U_O;YIILLW%wnu+}S?8 zM}MsFlwb0~fqd6?l#B!8hy-1K=b?w=BpkBWg1hE;jQSP#vgFlPy7wI)2s~-QXRc0y zy!PXgY>!X%PauKm;9k9)+%s^L;yl(W)vFd;(Ac9Ba&7CV+DNr%PhLiRhg=DFHKPr8 zHLJ~xij6UMKWp*?xF#CZHmG$QktiIe!*)^y*^>!+JUns*Qa*$JP`}wB0`2mi%<)n- zJ=nSp4wjVwS1Um4ZURx)FTI@4n+cX~t${)U(sK=52+fsubrd6b-1Zz&N5k}G5Akp_ zuO^5m6yXjs9@~k6$?h?Fg~7*;{ft(g7_T` zkTG9N)ObVMH?WuFVDYI})`wTt=N6wp{6FI;H;J99n{0cEqlsFl_5zEVe|YBKmYhv- z7Mp>%{U(;$I(fo{`2uMJ4mADqd$VZefhQ@^Bw{Tite zo!X4r449h<0CO|-?*rFwago1ncnFnM{g1jwZ5_=aQLY*aFFAT9&0r9E^Xno|2129m z9B^+7elBIduahzjqC4~`Y3o%+pjd=}E5Lt0dO(|tBI=Tu%JBu@4_)P8YI|^b0D6N+ zMWOrpwKO1Li4XpUK0RyGFDO}*Iw!&-t(S(H)JoOu_+33!+vUf~ob}cq2xuhd_oh~g zym^&F7YQq8J_U+C`!rNL1<+mE8bfQ)`i*Y!UR49s{AEsIwq;C37tL=8VGqHW(p}TwXCBscLn0{JQo?XHD|PG#UFg zKg&0bzfn+^F=OV|QkA1Mvb2!JdQG1|ml7|4_Xw6+}LtR zmOkcXcvM()G2FntxRkYjL#DRzI5t>8;j*>yZ9@buMpOHqm+l0;j}{n6zOf>X|DpH* z)=lKJC2kco>8qmBWKy;S<hc)jk-7V!$50Mr@9Gqi>t&$^1zsPmgfNNguTZrL|CF% z&c+-i@f0#bF?Kw84v@JHA^kIiS(=Dn4zkO}C|3!|p9yh1s|KXmE!Cdd#_y_$D8jU+ zL2V;+^H|X#uC3f7jCvWYiNM7NkLilvd6}Zk537Wn3Vaoqp9F}n)s9OPUwfX_EcLD zq#Vy$U*lt+4JQfi1Gq?R{^hks}tXQ{WNsGwu1vd_Tka=UE5x6 zlK}V?6aqGs{wv9eZ$ATGdo63dZ}9`aNKTZ<1J)J(Sz5ZZ{1D9|kZ z`OruV#BfPd%pLA^hbriGOBviIZmOPpg zkJDiciB}sZX9g`>)X&sc_3cx0(0Ba=uOCae^|G${Kn>p(y;Pl?*lF0B`hm8qtH9Es z-leoN!?f|OF)IlOSBd^Kij>22UjOTX604?8Uy?h5cl~yljNuwLar>stTSTv^zAWW8 zw;rpeiIkYj@on<6Wp=o{zE98gYP2EC%PfI-P}Ft{KQWx z!{^NhojB@Cfyiot4LNl8L$Tbu^QM>YQqxSH&~giAy*3Q@ zGpyY1r$QtWV>OdF_QpJ*A`)`LAD7~#c^7v^4Ies8(4)`5R{^{E&hJ1l$&OjC}x+%ga%2jHa_71;%%;I#qls(TYOcDBKu=Ag5SEF1MaGX$H&u9_oMh@3+> zr4(mW(fnx$P?ccoErMvfR~G+Vv66v`z+wVI^bEclBYq$Fe+lP$6pqmGNH$8u3@ub9Ng*PQ0t^kcQn%Y^D#}*59CObKfB(cird~1=G@1`RF4eK>xJ7Gg z`*I(Ycg;*A*6fScWCf~HnJ6Ed>4v6X$R{k{Lcz5Vtm zn8^ZiZ@8IPT3S^ZionIz&WU*QXh8B7@gTq;Mj!>6#jwuRNh(cF>(0)b7P@D?%zWq^ zsrHU5xKK5?$$iLvnKfx+uLDV1-eHxec{J1j*tgy=n&y0Q|M}R0?n#c7X0P)5)GU1s zAq*QFOiUy+J?alCPsHm-P6HFDZHgi1;4m2#IA*}hN33Lf!C%BO1W1OZgdo(ipW)PD z-W(o(cCz);qBc;8mFtnQFH$K~o0e6sQjiT@PBAyElP@Y&#^j^h^E0PoD3)^S(iCRj zT4q(#H%{gqOlrdF!ql2Go)SxXD@-FlfHat>SJPOc(wM*&8kI4GpfP@w99NsC`nG9| zoW+%q9NRBbdA?Ak%mg&~6EWuDUcf6Xa(teqfJsL*Ki*05xk9~d{P{*BOP3M)k73J7 z)rQe!sRK(OpCMS}5c957?^YH5b@lkv?^sE`;3WydAG&!hIh2@#zv+(iXg= zYzw$K2cB^>@hWeOUEeARgF#27TvPl6r zI7jUgFb$|FT>)NN%|llHgpNd*JzGuCwWnZx@z6nYS^*BC?>^wQYA0NV*v>i_CWMF5 z4mcdWBLPFFCslGCLU_s9UMX)h#oVRgDl?Ome$O|lmy?J#K)FU_J|alkyN^5x+u?ot zJ|53dFL{Ftj8cq9J8`XWJCZe=ea!L_Y%PATE0(PYq6NX4pZ28!%^`heHk!VaTgR@3 zWBz_c;D#3lt1~5Eiw31KO?-_LHzyl^{JsqQrQ=}ebopoFy|^0@lu>?gN?p`p{rb6A zlfyBJ&Wn-|uv=i>ry3XsmJw;5{CoFvlCgEKofb@N%aXt~4fs$~WUBI_s6VMYtZ-=* zDAYW)pQl>C`vk5xw=w(S4*sgGTQa4ais(E8dXg&y%sZ=jvL8PE-NpH(Q@B1Gz{#!= z*yHX?hbDE<@xV0f>X5!LsU51$kFUz-q233fi=D^PsT1SUg(+P_g54!tKX-n^20kX- zQVU;zAyv+OZr0=Gd5n33Jlf%U1p)#y5pYg6QF=Wk1$oF%+iQdsfT-cv7~Y}1Q+>`g zf^s*|$pm^ye{PMRnKc)Q6RAPC>bQ{QjF+;bAGik>QYjx&!9qO8$!O}m63pR$uNSWr z-0BA&zlobgNbU@$-it8HlQB)gC27y?)(Xsw{iO7_VFpXV?LYrk5S}HvQ`UG?r zgZLhkf$hM!cnIx=&bG6fm5W-B|5RCooxv`LOB%KGULVP+ng&mbMi0dvB94axtpOok z7fiiiJ^kl&(HAa~cWhC&`2QS3qMvXjehs6(1&q-gzmL(fjyAS>X8*>fkQy~4*2RP3 zH#%2jR#Q7;JT17y!iOx-?Ta3PhAu<*dekOgIU>9qYb$Hrl$^;aY%C?-u<6m3 zgNiFq!(GWKISgb4eQt}<7XXvXcp?a#BbXHq@%e-F5OCl~)_NsYA|-OH z%@;bJG5dHm~aqYBZ6C zf0+V^=15swb$7c5wtUhjgtf@ZI5(1Iyn$&)Jw?L|nW&g{o;B+u*J8y-&($$TQf$Y) zi>DFtrSyjU=7qD^@*9F%avtFo5 z!Z(D8@iKr@R!HzHr05y$=u>>g z-I`UUPiA;8gcQ}@y?}M(iD8Kgul>QFoL@6cc=jNvV))ohpG)>VxhJ(@|FGz~cwBEz zN#e&dl=O3!bmp`Sqo zXn*|uvHFi&>Ax&C_GKntBiz4Ih0FmG1MB9}*2|!&216`&ppd7PAy*O{*Uaq3B8Q3w zKSgzUvI=5g`#RC;b~_||ouBwIzLPC_HvI!H6}{F_5=?u;9fxoD=D9VFTi+^BJ@+q7E^o#gJY=^p=!hi z{Y8!>QNu-%Ijt3hCPftTvS_;585mZTc#163&3*LK`=CmB} zl|$KYFxa2^ub=C~(}askIugT5HpVgNbxBwx6_!GYFjg-#yP{G^b~?=`t3^8!>Oju&EpQ$+ZJJv`W>XIz&EvkSFxSm@3J+#!I}EB(U_u+$aR zp4ye?&Gmiy$uY0e)5tu+e!KDdDhR8@#lwy!9O!6~y;VilL!A2cZI1&Em3f%zDm~CS zs-e^j*KtoSC^ccWhK>g8?gsK5a^QNXy726CG<6AC!`qxUJ@23Dly3Xa^Fh^HKB3JB z|B%oFR`QMmoo{Z?xXqWr14e+19Ax->eUErRK5Oe^Jc;TS0(o==b9*)&4&Veoun%8?&L8u6{P)tJ8q<+4tgKFV&uSfc4ie%s&8r zWG?z4cK*0V;i~6hXn@vR0$Tp{_WwAlJ%IuL{TtxpRUKe`=itcu*47XpKWn7(Q(O5l zVh zOHD2hP=FV)Fw>K;urPUR`DbXHb@jy_0LTsCDgEjY{&7@$!a)8J8X&uC`ql=piA>1I zRF79jN6*GaQp;S+;7789$92~!^K5(rs5=6{p7+nX6F&VTfugmRC15Wiz|^5{WbhWC zU=2`n|8w1?U2%;z0bPO#s5||SiUY3Ip6>zD0)Ob1$M;hNkgvh?zg2w1dOrvi5E^0* zsOHygOFxck&j-{$qW>e8+MjEDwY(8{0>Dcp0GRoI00OSno=UiX!~|^Gv(+*+{WIEf zyF_3&p#8CcvE-j!>j&^3{Gt7P78bT&lT}!L%~lUElm1Lm@%XNF2oenP1G=>a?OURO zA9bzvj0LDO{{i~@T@!TB(|P-Un&XdA!#ZP5hybX*0948!kpF2u9>6a12h?xR@ptU6 zmHu-_JWiqd1G?HX05InN0rz{i{#QVps|8HsZ9{0xMq-;M~4qp9{ zsf%=y;7LV)yI6=TqrYwzr!Sriv51@6HzorX#O!Ziw{U=qJ z*59c9i(30*_{T!yKjDdW{s#a3PW(-F{4v#I#nPWtLMH!$>RYG%ukLy*`uLN?(EMMJ z{9OX_af=>niam3e8g1OY+2!6~G|HfAP zxFwGxG=36AX8lI=+vEDTpW9=v_fO>b+~1IYf0({O|JeurxDk)NjX$Y^-u;d0f6S$i zooqj0_X_?7`+dOpi_7hC10I{=e=^aQ{>Jop$^T2={f`DcHV^-#0V@BE<`>xS4f=NZ z%h>nWPWTgFsq$ao|97k5 Date: Tue, 18 Aug 2015 13:12:34 -0700 Subject: [PATCH 011/105] ignoring test that starts MediaDriver --- .../java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java index 793a8c5c0..f17e10036 100644 --- a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java @@ -2,6 +2,7 @@ import io.reactivesocket.RequestHandler; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; import org.reactivestreams.Publisher; import rx.Observable; @@ -13,6 +14,7 @@ /** * Created by rroeser on 8/14/15. */ +@Ignore public class ReactiveSocketAeronTest { @BeforeClass public static void init() { From ebed5596273b87544cdd015c7a35bb3a88644cb5 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Tue, 18 Aug 2015 15:08:28 -0700 Subject: [PATCH 012/105] added readme --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 000000000..51e4a997a --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# ReactiveSocket over Aeron + +This is an implementation of Aeron. + +## Master Build Status + + + +## Bugs and Feedback + +For bugs, questions and discussions please use the [Github Issues](https://github.com/ReactiveSocket/reactivesocket-aeron-rxjava/issues). + + +## LICENSE + +Copyright 2015 Netflix, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file From a2a2fb5ff5baabaa01336506de4553ce5897d1e3 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Tue, 18 Aug 2015 15:09:34 -0700 Subject: [PATCH 013/105] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 51e4a997a..25f194c86 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ReactiveSocket over Aeron -This is an implementation of Aeron. +This is an implementation over Aeron. ## Master Build Status From b2fb3d65aec9e0b306518198f0a6da9b30536352 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Mon, 24 Aug 2015 10:49:01 -0700 Subject: [PATCH 014/105] updated to work with ReactiveSocket changes --- build.gradle | 1 + .../aeron/AeronClientDuplexConnection.java | 2 +- .../aeron/AeronServerDuplexConnection.java | 2 +- .../aeron/ReactiveSocketAeronServer.java | 26 +++++- .../aeron/ReactivesocketAeronClient.java | 54 +++++++++---- .../aeron/ReactiveSocketAeronPerf.java | 21 ++--- .../aeron/ReactiveSocketAeronTest.java | 29 ++++--- .../io/reactivesocket/aeron/TestUtil.java | 80 +++++++++++++++++++ 8 files changed, 169 insertions(+), 46 deletions(-) create mode 100644 src/test/java/io/reactivesocket/aeron/TestUtil.java diff --git a/build.gradle b/build.gradle index 70eccbb8e..d2b536469 100644 --- a/build.gradle +++ b/build.gradle @@ -17,6 +17,7 @@ repositories { dependencies { compile 'io.reactivex:rxjava:1.0.13' + compile 'io.reactivex:rxjava-reactive-streams:1.0.1' compile 'io.reactivesocket:reactivesocket:0.0.1-SNAPSHOT' compile 'uk.co.real-logic:Agrona:0.4.2' compile 'uk.co.real-logic:aeron-all:0.1.2' diff --git a/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java index a2c171ea6..8e87e79ac 100644 --- a/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java @@ -27,7 +27,7 @@ public Publisher getInput() { } @Override - public Publisher write(Publisher o) { + public Publisher addOutput(Publisher o) { final Observable frameObservable = RxReactiveStreams.toObservable(o); final Observable voidObservable = frameObservable .lift(new OperatorPublish(publication)); diff --git a/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java index 45b18c2fd..2c36ff4c2 100644 --- a/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java @@ -36,7 +36,7 @@ public Publisher getInput() { return publisher; } - public Publisher write(Publisher o) { + public Publisher addOutput(Publisher o) { final Observable frameObservable = RxReactiveStreams.toObservable(o); final Observable voidObservable = frameObservable .lift(new OperatorPublish(publication)); diff --git a/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java index 680c6b5b8..9167d62db 100644 --- a/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java @@ -3,6 +3,7 @@ import io.reactivesocket.Frame; import io.reactivesocket.ReactiveSocket; import io.reactivesocket.RequestHandler; +import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import rx.Scheduler; import rx.schedulers.Schedulers; @@ -141,9 +142,30 @@ void newImageHandler(Image image, String channel, int streamId, int sessionId, l return new AeronServerDuplexConnection(publication); }); System.out.println("Accepting ReactiveSocket connection"); - ReactiveSocket socket = ReactiveSocket.accept(connection, requestHandler); + ReactiveSocket socket = ReactiveSocket.createResponderAndRequestor(requestHandler); + Publisher connect = socket.connect(connection); + connect.subscribe(new Subscriber() { + @Override + public void onSubscribe(org.reactivestreams.Subscription s) { + s.request(Long.MAX_VALUE); + } + + @Override + public void onNext(Void aVoid) { + + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + } + + @Override + public void onComplete() { + + } + }); - socket.responderPublisher().subscribe(PROTOCOL_SUBSCRIBER); } else { System.out.println("Unsupported stream id " + streamId); } diff --git a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java index 2ee6e5617..13fd198bf 100644 --- a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java @@ -1,6 +1,7 @@ package io.reactivesocket.aeron; import io.reactivesocket.Frame; +import io.reactivesocket.Payload; import io.reactivesocket.ReactiveSocket; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; @@ -34,7 +35,7 @@ public class ReactivesocketAeronClient implements AutoCloseable { private static final Int2ObjectHashMap establishConnectionLatches = new Int2ObjectHashMap<>(); - private ReactiveSocket rsClientProtocol; + private final ReactiveSocket rsClientProtocol; private final Aeron aeron; @@ -46,6 +47,8 @@ public class ReactivesocketAeronClient implements AutoCloseable { private ReactivesocketAeronClient(String host, int port) { this.port = port; + this.rsClientProtocol = + ReactiveSocket.createRequestor(); final Aeron.Context ctx = new Aeron.Context(); aeron = Aeron.connect(ctx); @@ -91,11 +94,34 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) int ackSessionId = buffer.getInt(offset + BitUtil.SIZE_OF_INT); System.out.println(String.format("Received establish connection ack for session id => %d", ackSessionId)); - AeronClientDuplexConnection connection = connections.computeIfAbsent(header.sessionId(), (_p) -> new AeronClientDuplexConnection(publication)); + final AeronClientDuplexConnection connection = connections.computeIfAbsent(header.sessionId(), (_p) -> new AeronClientDuplexConnection(publication)); - this.rsClientProtocol = - ReactiveSocket.connect(connection); + Publisher connect = this + .rsClientProtocol + .connect(connection); + connect.subscribe(new Subscriber() { + @Override + public void onSubscribe(org.reactivestreams.Subscription s) { + s.request(Long.MAX_VALUE); + } + + @Override + public void onNext(Void aVoid) { + + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + } + + @Override + public void onComplete() { + } + }); + + System.out.println("ReactiveSocket connected to Aeron session => " + ackSessionId); CountDownLatch latch = establishConnectionLatches.get(ackSessionId); latch.countDown(); @@ -151,24 +177,20 @@ void establishConnection(final Publication publication, final int sessionId) { } - public Publisher requestResponse(String data, String metadata) { - return rsClientProtocol.requestResponse(data, metadata); - } - - public Publisher fireAndForget(String data, String metadata) { - return rsClientProtocol.fireAndForget(data, metadata); + public Publisher requestResponse(Payload payload) { + return rsClientProtocol.requestResponse(payload); } - public Publisher requestStream(String data, String metadata) { - return rsClientProtocol.requestStream(data, metadata); + public Publisher fireAndForget(Payload payload) { + return rsClientProtocol.fireAndForget(payload); } - public Publisher requestSubscription(String data, String metadata) { - return rsClientProtocol.requestSubscription(data, metadata); + public Publisher requestStream(Payload payload) { + return rsClientProtocol.requestStream(payload); } - public Publisher responderPublisher() { - return rsClientProtocol.responderPublisher(); + public Publisher requestSubscription(Payload payload) { + return rsClientProtocol.requestSubscription(payload); } @Override diff --git a/src/perf/java/io/reactivesocket/aeron/ReactiveSocketAeronPerf.java b/src/perf/java/io/reactivesocket/aeron/ReactiveSocketAeronPerf.java index ce3071e6f..c750292fa 100644 --- a/src/perf/java/io/reactivesocket/aeron/ReactiveSocketAeronPerf.java +++ b/src/perf/java/io/reactivesocket/aeron/ReactiveSocketAeronPerf.java @@ -1,19 +1,8 @@ package io.reactivesocket.aeron; -import io.reactivesocket.RequestHandler; -import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.Setup; -import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.infra.Blackhole; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; -import rx.Observable; -import rx.RxReactiveStreams; import java.util.concurrent.TimeUnit; @@ -21,7 +10,7 @@ @OutputTimeUnit(TimeUnit.SECONDS) public class ReactiveSocketAeronPerf { //static final MediaDriver mediaDriver = MediaDriver.launchEmbedded(); - +/* static final ReactiveSocketAeronServer server = ReactiveSocketAeronServer.create(new RequestHandler() { @Override public Publisher handleRequestResponse(String request) { @@ -98,8 +87,8 @@ public void onComplete() { @Benchmark public void pingPongTest(Input input) { - client - .requestResponse(input.getMessage(), input.getMetaData()) - .subscribe(input.newSubscriber()); - } + //client + // .requestResponse(input.getMessage(), input.getMetaData()) + // .subscribe(input.newSubscriber()); + }*/ } diff --git a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java index f17e10036..4b78c5eb5 100644 --- a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java @@ -1,5 +1,7 @@ package io.reactivesocket.aeron; +import io.reactivesocket.Frame; +import io.reactivesocket.Payload; import io.reactivesocket.RequestHandler; import org.junit.BeforeClass; import org.junit.Ignore; @@ -9,6 +11,7 @@ import rx.RxReactiveStreams; import uk.co.real_logic.aeron.driver.MediaDriver; +import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; /** @@ -24,28 +27,31 @@ public static void init() { final MediaDriver mediaDriver = MediaDriver.launch(context); } - @Test(timeout = 5000) + @Test(timeout = 70000) public void testRequestReponse() throws Exception { ReactiveSocketAeronServer.create(new RequestHandler() { + Frame frame = Frame.from(ByteBuffer.allocate(1)); + @Override - public Publisher handleRequestResponse(String request) { + public Publisher handleRequestResponse(Payload payload) { + String request = TestUtil.byteToString(payload.getData()); System.out.println("Server got => " + request); - Observable pong = Observable.just("pong"); + Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong", null)); return RxReactiveStreams.toPublisher(pong); } @Override - public Publisher handleRequestStream(String request) { + public Publisher handleRequestStream(Payload payload) { return null; } @Override - public Publisher handleRequestSubscription(String request) { + public Publisher handleRequestSubscription(Payload payload) { return null; } @Override - public Publisher handleFireAndForget(String request) { + public Publisher handleFireAndForget(Payload payload) { return null; } }); @@ -57,10 +63,13 @@ public Publisher handleFireAndForget(String request) { Observable .range(1, 10_000) - .flatMap(i -> - RxReactiveStreams.toObservable(client.requestResponse("ping =>" + i, "ping metadata")) + .flatMap(i -> { + System.out.println("pinging => " + i); + Payload payload = TestUtil.utf8EncodedPayload("ping =>" + i, null); + return RxReactiveStreams.toObservable(client.requestResponse(payload)); + } ) - .subscribe(new rx.Subscriber() { + .subscribe(new rx.Subscriber() { @Override public void onCompleted() { } @@ -71,7 +80,7 @@ public void onError(Throwable e) { } @Override - public void onNext(String s) { + public void onNext(Payload s) { System.out.println(s + " countdown => " + latch.getCount()); latch.countDown(); } diff --git a/src/test/java/io/reactivesocket/aeron/TestUtil.java b/src/test/java/io/reactivesocket/aeron/TestUtil.java new file mode 100644 index 000000000..29ce236c2 --- /dev/null +++ b/src/test/java/io/reactivesocket/aeron/TestUtil.java @@ -0,0 +1,80 @@ +package io.reactivesocket.aeron; + +import io.reactivesocket.Frame; +import io.reactivesocket.FrameType; +import io.reactivesocket.Payload; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; + +public class TestUtil +{ + public static Frame utf8EncodedFrame(final long streamId, final FrameType type, final String data) + { + return Frame.from(streamId, type, byteBufferFromUtf8String(data)); + } + + public static Payload utf8EncodedPayload(final String data, final String metadata) + { + return new PayloadImpl(data, metadata); + } + + public static String byteToString(final ByteBuffer byteBuffer) + { + final byte[] bytes = new byte[byteBuffer.capacity()]; + byteBuffer.get(bytes); + return new String(bytes, Charset.forName("UTF-8")); + } + + public static ByteBuffer byteBufferFromUtf8String(final String data) + { + final byte[] bytes = data.getBytes(Charset.forName("UTF-8")); + return ByteBuffer.wrap(bytes); + } + + private static class PayloadImpl implements Payload // some JDK shoutout + { + private ByteBuffer data; + private ByteBuffer metadata; + + public PayloadImpl(final String data, final String metadata) + { + if (null == data) + { + this.data = ByteBuffer.allocate(0); + } + else + { + this.data = byteBufferFromUtf8String(data); + } + + if (null == metadata) + { + this.metadata = ByteBuffer.allocate(0); + } + else + { + this.metadata = byteBufferFromUtf8String(metadata); + } + } + + public boolean equals(Object obj) + { + final Payload rhs = (Payload)obj; + + return (TestUtil.byteToString(data).equals(TestUtil.byteToString(rhs.getData()))) && + (TestUtil.byteToString(metadata).equals(TestUtil.byteToString(rhs.getMetadata()))); + } + + public ByteBuffer getData() + { + return data; + } + + public ByteBuffer getMetadata() + { + return metadata; + } + } + +} \ No newline at end of file From 25a3fdc7c137f4ac345c0062360d06541b54f26b Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Mon, 24 Aug 2015 14:26:49 -0700 Subject: [PATCH 015/105] updated operator to look for the mtulength when sending a large message --- .../reactivesocket/aeron/OperatorPublish.java | 9 +- .../aeron/ReactiveSocketAeronPerf.java | 94 ------------------- .../aeron/OperatorPublishTest.java | 1 - .../aeron/ReactiveSocketAeronTest.java | 75 +++++++++++++++ 4 files changed, 83 insertions(+), 96 deletions(-) delete mode 100644 src/perf/java/io/reactivesocket/aeron/ReactiveSocketAeronPerf.java diff --git a/src/main/java/io/reactivesocket/aeron/OperatorPublish.java b/src/main/java/io/reactivesocket/aeron/OperatorPublish.java index 5c32e47d6..ff6a95340 100644 --- a/src/main/java/io/reactivesocket/aeron/OperatorPublish.java +++ b/src/main/java/io/reactivesocket/aeron/OperatorPublish.java @@ -18,8 +18,15 @@ class OperatorPublish implements Observable.Operator { private Publication publication; + private final int mtuLength; + public OperatorPublish(Publication publication) { this.publication = publication; + + String mtuLength = System.getProperty("aeron.mtu.length", "4096"); + + this.mtuLength = Integer.parseInt(mtuLength); + } @Override @@ -48,7 +55,7 @@ public void onNext(Frame frame) { // If the length is less the MTU size send the message using tryClaim which does not fragment the message // If the message is larger the the MTU size send it using offer. - if (length < publication.maxMessageLength()) { + if (length < mtuLength) { tryClaim(byteBuffer, length); } else { offer(byteBuffer, length); diff --git a/src/perf/java/io/reactivesocket/aeron/ReactiveSocketAeronPerf.java b/src/perf/java/io/reactivesocket/aeron/ReactiveSocketAeronPerf.java deleted file mode 100644 index c750292fa..000000000 --- a/src/perf/java/io/reactivesocket/aeron/ReactiveSocketAeronPerf.java +++ /dev/null @@ -1,94 +0,0 @@ -package io.reactivesocket.aeron; - -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.OutputTimeUnit; - -import java.util.concurrent.TimeUnit; - -@BenchmarkMode(Mode.Throughput) -@OutputTimeUnit(TimeUnit.SECONDS) -public class ReactiveSocketAeronPerf { - //static final MediaDriver mediaDriver = MediaDriver.launchEmbedded(); -/* - static final ReactiveSocketAeronServer server = ReactiveSocketAeronServer.create(new RequestHandler() { - @Override - public Publisher handleRequestResponse(String request) { - return RxReactiveStreams.toPublisher(Observable.just(request)); - } - - @Override - public Publisher handleRequestStream(String request) { - return null; - } - - @Override - public Publisher handleRequestSubscription(String request) { - return null; - } - - @Override - public Publisher handleFireAndForget(String request) { - return null; - } - }); - - static final ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost"); - - @State(Scope.Benchmark) - public static class Input { - private String message; - private String metaData; - private Blackhole bh; - - @Setup - public void setup(Blackhole bh) { - this.bh = bh; - this.message = "ping"; - this.metaData = "metadata test"; - } - - public Blackhole getBh() { - return bh; - } - - public String getMetaData() { - return metaData; - } - - public String getMessage() { - return message; - } - - public Subscriber newSubscriber() { - return new Subscriber() { - @Override - public void onSubscribe(Subscription s) { - s.request(8); - } - - @Override - public void onNext(String s) { - bh.consume(s); - } - - @Override - public void onError(Throwable t) { - - } - - @Override - public void onComplete() { - - } - }; - } - } - - @Benchmark - public void pingPongTest(Input input) { - //client - // .requestResponse(input.getMessage(), input.getMetaData()) - // .subscribe(input.newSubscriber()); - }*/ -} diff --git a/src/test/java/io/reactivesocket/aeron/OperatorPublishTest.java b/src/test/java/io/reactivesocket/aeron/OperatorPublishTest.java index fa57da6db..34707ec2b 100644 --- a/src/test/java/io/reactivesocket/aeron/OperatorPublishTest.java +++ b/src/test/java/io/reactivesocket/aeron/OperatorPublishTest.java @@ -78,7 +78,6 @@ public void onCompleted() { @Override public void onError(Throwable e) { - } @Override diff --git a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java index 4b78c5eb5..3e2f1134f 100644 --- a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java @@ -12,6 +12,7 @@ import uk.co.real_logic.aeron.driver.MediaDriver; import java.nio.ByteBuffer; +import java.util.Random; import java.util.concurrent.CountDownLatch; /** @@ -89,4 +90,78 @@ public void onNext(Payload s) { latch.await(); } + @Test(timeout = 60000) + public void sendLargeMessage() throws Exception { + + Random random = new Random(); + byte[] b = new byte[1_000_000]; + random.nextBytes(b); + + ReactiveSocketAeronServer.create(new RequestHandler() { + @Override + public Publisher handleRequestResponse(Payload payload) { + System.out.println("Server got => " + b.length); + Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong", null)); + return RxReactiveStreams.toPublisher(pong); + } + + @Override + public Publisher handleRequestStream(Payload payload) { + return null; + } + + @Override + public Publisher handleRequestSubscription(Payload payload) { + return null; + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return null; + } + }); + + CountDownLatch latch = new CountDownLatch(2); + + + ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost"); + + Observable + .range(1, 2) + .flatMap(i -> { + System.out.println("pinging => " + i); + Payload payload = new Payload() { + @Override + public ByteBuffer getData() { + return ByteBuffer.wrap(b); + } + + @Override + public ByteBuffer getMetadata() { + return null; + } + }; + return RxReactiveStreams.toObservable(client.requestResponse(payload)); + } + ) + .subscribe(new rx.Subscriber() { + @Override + public void onCompleted() { + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + } + + @Override + public void onNext(Payload s) { + System.out.println(s + " countdown => " + latch.getCount()); + latch.countDown(); + } + }); + + latch.await(); + } + } From 23cd6a3ebea7ae761721e8953b38050155b10ed1 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Wed, 26 Aug 2015 10:16:57 -0700 Subject: [PATCH 016/105] trying with out operator... --- .../aeron/AeronClientDuplexConnection.java | 1 + .../aeron/AeronServerDuplexConnection.java | 5 + .../aeron/PublishSubscription.java | 120 ++++++++++++++++++ .../aeron/ReactivesocketAeronClient.java | 2 +- .../aeron/OperatorPublishTest.java | 5 +- .../aeron/ReactiveSocketAeronTest.java | 14 +- 6 files changed, 141 insertions(+), 6 deletions(-) create mode 100644 src/main/java/io/reactivesocket/aeron/PublishSubscription.java diff --git a/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java index 8e87e79ac..f06f0bfcc 100644 --- a/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java @@ -28,6 +28,7 @@ public Publisher getInput() { @Override public Publisher addOutput(Publisher o) { + final Observable frameObservable = RxReactiveStreams.toObservable(o); final Observable voidObservable = frameObservable .lift(new OperatorPublish(publication)); diff --git a/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java index 2c36ff4c2..c91e25015 100644 --- a/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java @@ -36,7 +36,12 @@ public Publisher getInput() { return publisher; } + @Override public Publisher addOutput(Publisher o) { + /*Publisher p = (Subscriber s) -> + s.onSubscribe(new PublishSubscription(s, o, publication)); + + return p;*/ final Observable frameObservable = RxReactiveStreams.toObservable(o); final Observable voidObservable = frameObservable .lift(new OperatorPublish(publication)); diff --git a/src/main/java/io/reactivesocket/aeron/PublishSubscription.java b/src/main/java/io/reactivesocket/aeron/PublishSubscription.java new file mode 100644 index 000000000..dcbea9335 --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/PublishSubscription.java @@ -0,0 +1,120 @@ +package io.reactivesocket.aeron; + +import io.reactivesocket.Frame; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.aeron.logbuffer.BufferClaim; +import uk.co.real_logic.agrona.BitUtil; +import uk.co.real_logic.agrona.MutableDirectBuffer; +import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; + +import java.nio.ByteBuffer; + +public class PublishSubscription implements Subscription { + private final Subscriber subscriber; + + private final Publisher source; + + private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); + + private static final ThreadLocal unsafeBuffers = ThreadLocal.withInitial(() -> new UnsafeBuffer(Constants.EMTPY)); + + private Publication publication; + + private final int mtuLength; + + + public PublishSubscription(Subscriber subscriber, Publisher source, Publication publication) { + this.source = source; + this.subscriber = subscriber; + this.publication = publication; + + String mtuLength = System.getProperty("aeron.mtu.length", "4096"); + + this.mtuLength = Integer.parseInt(mtuLength); + + request(1); + } + + @Override + public void cancel() { + + } + + @Override + public void request(long n) { + source.subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(1); + } + + @Override + public void onNext(Frame frame) { + final ByteBuffer byteBuffer = frame.getByteBuffer(); + final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; + + // If the length is less the MTU size send the message using tryClaim which does not fragment the message + // If the message is larger the the MTU size send it using offer. + if (length < mtuLength) { + tryClaim(byteBuffer, length); + } else { + offer(byteBuffer, length); + } + + } + + @Override + public void onError(Throwable t) { + subscriber.onError(t); + } + + @Override + public void onComplete() { + subscriber.onComplete(); + } + }); + } + + void offer(ByteBuffer byteBuffer, int length) { + final byte[] bytes = new byte[length]; + final UnsafeBuffer unsafeBuffer = unsafeBuffers.get(); + unsafeBuffer.wrap(bytes); + unsafeBuffer.putInt(0, MessageType.FRAME.getEncodedType()); + unsafeBuffer.putBytes(BitUtil.SIZE_OF_INT, byteBuffer, byteBuffer.capacity()); + for (;;) { + final long offer = publication.offer(unsafeBuffer); + if (offer >= 0) { + break; + } else if (Publication.NOT_CONNECTED == offer) { + subscriber.onError(new RuntimeException("not connected")); + break; + } + } + + } + + void tryClaim(ByteBuffer byteBuffer, int length) { + final BufferClaim bufferClaim = bufferClaims.get(); + for (;;) { + final long offer = publication.tryClaim(length, bufferClaim); + if (offer >= 0) { + try { + final MutableDirectBuffer buffer = bufferClaim.buffer(); + final int offset = bufferClaim.offset(); + buffer.putInt(offset, MessageType.FRAME.getEncodedType()); + buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, 0, byteBuffer.capacity()); + } finally { + bufferClaim.commit(); + } + + break; + } else if (Publication.NOT_CONNECTED == offer) { + subscriber.onError(new RuntimeException("not connected")); + break; + } + } + } +} diff --git a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java index 13fd198bf..08be7d728 100644 --- a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java @@ -126,7 +126,7 @@ public void onComplete() { latch.countDown(); } else { - System.out.println("Unknow message type => " + messageTypeInt); + System.out.println("Unknown message type => " + messageTypeInt); } } diff --git a/src/test/java/io/reactivesocket/aeron/OperatorPublishTest.java b/src/test/java/io/reactivesocket/aeron/OperatorPublishTest.java index 34707ec2b..d4ddfc61c 100644 --- a/src/test/java/io/reactivesocket/aeron/OperatorPublishTest.java +++ b/src/test/java/io/reactivesocket/aeron/OperatorPublishTest.java @@ -1,7 +1,6 @@ package io.reactivesocket.aeron; import io.reactivesocket.Frame; -import org.junit.Test; import rx.Subscriber; import uk.co.real_logic.aeron.Publication; @@ -15,7 +14,7 @@ import static org.mockito.Mockito.when; public class OperatorPublishTest { - @Test + //@Test public void testShouldCallTryClaimWhenSmallerThanMTU() throws Exception { String message = "I'm a message longer than 1"; ByteBuffer buffer = ByteBuffer.wrap(message.getBytes()); @@ -56,7 +55,7 @@ public void onNext(Object o) { } - @Test + //@Test public void testShouldCallOfferWhenLargerThenMTU() throws Exception { String message = "I'm a message longer than 1"; ByteBuffer buffer = ByteBuffer.wrap(message.getBytes()); diff --git a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java index 3e2f1134f..99c4e04d1 100644 --- a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java @@ -41,13 +41,18 @@ public Publisher handleRequestResponse(Payload payload) { return RxReactiveStreams.toPublisher(pong); } + @Override + public Publisher handleChannel(Payload initialPayload, Publisher payloads) { + return null; + } + @Override public Publisher handleRequestStream(Payload payload) { return null; } @Override - public Publisher handleRequestSubscription(Payload payload) { + public Publisher handleSubscription(Payload payload) { return null; } @@ -105,13 +110,18 @@ public Publisher handleRequestResponse(Payload payload) { return RxReactiveStreams.toPublisher(pong); } + @Override + public Publisher handleChannel(Payload initialPayload, Publisher payloads) { + return null; + } + @Override public Publisher handleRequestStream(Payload payload) { return null; } @Override - public Publisher handleRequestSubscription(Payload payload) { + public Publisher handleSubscription(Payload payload) { return null; } From f58cb787c241f0fce3de1f29b749e82512c44a20 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Mon, 31 Aug 2015 09:48:39 -0700 Subject: [PATCH 017/105] updating for lease --- .../aeron/AeronClientDuplexConnection.java | 14 +- .../aeron/AeronServerDuplexConnection.java | 17 +- .../aeron/CompletableSubscription.java | 107 ++++++++++ .../reactivesocket/aeron/MpscScheduler.java | 193 ++++++++++++++++++ .../aeron/PublishSubscription.java | 3 +- .../aeron/ReactiveSocketAeronServer.java | 65 +++--- .../aeron/ReactivesocketAeronClient.java | 69 +++---- .../aeron/MpscSchedulerPerf.java | 58 ++++++ .../aeron/MpscSchedulerTest.java | 60 ++++++ 9 files changed, 500 insertions(+), 86 deletions(-) create mode 100644 src/main/java/io/reactivesocket/aeron/CompletableSubscription.java create mode 100644 src/main/java/io/reactivesocket/aeron/MpscScheduler.java create mode 100644 src/perf/java/io/reactivesocket/aeron/MpscSchedulerPerf.java create mode 100644 src/test/java/io/reactivesocket/aeron/MpscSchedulerTest.java diff --git a/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java index f06f0bfcc..8a58e5ae6 100644 --- a/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java @@ -1,11 +1,10 @@ package io.reactivesocket.aeron; +import io.reactivesocket.Completable; import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; -import rx.Observable; -import rx.RxReactiveStreams; import uk.co.real_logic.aeron.Publication; public class AeronClientDuplexConnection implements DuplexConnection, AutoCloseable { @@ -27,17 +26,12 @@ public Publisher getInput() { } @Override - public Publisher addOutput(Publisher o) { - - final Observable frameObservable = RxReactiveStreams.toObservable(o); - final Observable voidObservable = frameObservable - .lift(new OperatorPublish(publication)); - - return RxReactiveStreams.toPublisher(voidObservable); + public void addOutput(Publisher o, Completable callback) { + o.subscribe(new CompletableSubscription(publication, callback)); } @Override - public void close() throws Exception { + public void close() { subscriber.onComplete(); publication.close(); } diff --git a/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java index c91e25015..86c3987fb 100644 --- a/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java @@ -1,11 +1,10 @@ package io.reactivesocket.aeron; +import io.reactivesocket.Completable; import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; -import rx.Observable; -import rx.RxReactiveStreams; import uk.co.real_logic.aeron.Publication; import uk.co.real_logic.aeron.logbuffer.BufferClaim; import uk.co.real_logic.agrona.BitUtil; @@ -37,16 +36,8 @@ public Publisher getInput() { } @Override - public Publisher addOutput(Publisher o) { - /*Publisher p = (Subscriber s) -> - s.onSubscribe(new PublishSubscription(s, o, publication)); - - return p;*/ - final Observable frameObservable = RxReactiveStreams.toObservable(o); - final Observable voidObservable = frameObservable - .lift(new OperatorPublish(publication)); - - return RxReactiveStreams.toPublisher(voidObservable); + public void addOutput(Publisher o, Completable callback) { + o.subscribe(new CompletableSubscription(publication, callback)); } void ackEstablishConnection(int ackSessionId) { @@ -80,7 +71,7 @@ void ackEstablishConnection(int ackSessionId) { } @Override - public void close() throws Exception { + public void close() { subscriber.onComplete(); publication.close(); } diff --git a/src/main/java/io/reactivesocket/aeron/CompletableSubscription.java b/src/main/java/io/reactivesocket/aeron/CompletableSubscription.java new file mode 100644 index 000000000..8e0e72eae --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/CompletableSubscription.java @@ -0,0 +1,107 @@ +package io.reactivesocket.aeron; + +import io.reactivesocket.Completable; +import io.reactivesocket.Frame; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.aeron.logbuffer.BufferClaim; +import uk.co.real_logic.agrona.BitUtil; +import uk.co.real_logic.agrona.MutableDirectBuffer; +import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; + +import java.nio.ByteBuffer; + +/** + * Created by rroeser on 8/27/15. + */ +public class CompletableSubscription implements Subscriber { + + private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); + + private static final ThreadLocal unsafeBuffers = ThreadLocal.withInitial(() -> new UnsafeBuffer(Constants.EMTPY)); + + private final Publication publication; + + private final Completable completable; + + private final int mtuLength; + + public CompletableSubscription(Publication publication, Completable completable) { + this.publication = publication; + this.completable = completable; + + String mtuLength = System.getProperty("aeron.mtu.length", "4096"); + + this.mtuLength = Integer.parseInt(mtuLength); + } + + @Override + public void onSubscribe(Subscription s) { + s.request(1); + } + + @Override + public void onNext(Frame frame) { + final ByteBuffer byteBuffer = frame.getByteBuffer(); + final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; + + // If the length is less the MTU size send the message using tryClaim which does not fragment the message + // If the message is larger the the MTU size send it using offer. + if (length < mtuLength) { + tryClaim(byteBuffer, length); + } else { + offer(byteBuffer, length); + } + } + + @Override + public void onError(Throwable t) { + completable.error(t); + } + + @Override + public void onComplete() { + completable.success(); + } + + void offer(ByteBuffer byteBuffer, int length) { + final byte[] bytes = new byte[length]; + final UnsafeBuffer unsafeBuffer = unsafeBuffers.get(); + unsafeBuffer.wrap(bytes); + unsafeBuffer.putInt(0, MessageType.FRAME.getEncodedType()); + unsafeBuffer.putBytes(BitUtil.SIZE_OF_INT, byteBuffer, byteBuffer.capacity()); + for (;;) { + final long offer = publication.offer(unsafeBuffer); + if (offer >= 0) { + break; + } else if (Publication.NOT_CONNECTED == offer) { + completable.error(new RuntimeException("not connected")); + break; + } + } + + } + + void tryClaim(ByteBuffer byteBuffer, int length) { + final BufferClaim bufferClaim = bufferClaims.get(); + for (;;) { + final long offer = publication.tryClaim(length, bufferClaim); + if (offer >= 0) { + try { + final MutableDirectBuffer buffer = bufferClaim.buffer(); + final int offset = bufferClaim.offset(); + buffer.putInt(offset, MessageType.FRAME.getEncodedType()); + buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, 0, byteBuffer.capacity()); + } finally { + bufferClaim.commit(); + } + + break; + } else if (Publication.NOT_CONNECTED == offer) { + completable.error(new RuntimeException("not connected")); + break; + } + } + } +} diff --git a/src/main/java/io/reactivesocket/aeron/MpscScheduler.java b/src/main/java/io/reactivesocket/aeron/MpscScheduler.java new file mode 100644 index 000000000..da96de467 --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/MpscScheduler.java @@ -0,0 +1,193 @@ +package io.reactivesocket.aeron; + +import rx.Scheduler; +import rx.Subscription; +import rx.exceptions.Exceptions; +import rx.functions.Action0; +import rx.internal.util.RxThreadFactory; +import rx.internal.util.unsafe.MpmcArrayQueue; +import rx.internal.util.unsafe.MpscLinkedQueue; +import rx.plugins.RxJavaPlugins; +import uk.co.real_logic.agrona.concurrent.IdleStrategy; +import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; + +import java.io.Closeable; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Created by rroeser on 8/30/15. + */ +public class MpscScheduler extends Scheduler implements Closeable { + private final MpscLinkedQueue[] actionQueues; + + private AtomicLong count; + + private volatile boolean running = true; + private long p1, p2, p3, p4, p5, p6, p7; + + private final int mask; + private long p11, p12, p13, p14, p15, p16, p17; + + private ActionWrapperRecycler recycler; + + public MpscScheduler(int consumers, IdleStrategy idleStrategy) { + actionQueues = new MpscLinkedQueue[consumers]; + recycler = new ActionWrapperRecycler(); + + mask = consumers - 1; + + RxThreadFactory factory = new RxThreadFactory("MpscScheduler-"); + + for (int i = 0; i < consumers; i++) { + final MpscLinkedQueue actionQueue = new MpscLinkedQueue<>(); + actionQueues[i] = actionQueue; + + final Thread thread = factory.newThread(() -> { + while (running) { + ActionWrapper wrapper = null; + try { + while ((wrapper = actionQueue.poll()) == null) { + idleStrategy.idle(0); + } + wrapper.call(); + } catch (Throwable t) { + Exceptions.throwIfFatal(t); + RxJavaPlugins.getInstance().getErrorHandler().handleError(t); + } + finally { + if (wrapper != null) { + recycler.release(wrapper); + } + } + } + }); + + thread.start(); + } + + count = new AtomicLong(0); + + } + + public MpscScheduler() { + this(Runtime.getRuntime().availableProcessors(), new NoOpIdleStrategy()); + } + + @Override + public Worker createWorker() { + return new MpscWorker((int) count.getAndIncrement() & mask); + } + + @Override + public void close() throws IOException { + running = false; + } + + class MpscWorker extends Worker { + + private int queue; + + private volatile boolean unsubscribe; + + private MpscLinkedQueue actionQueue; + + public MpscWorker(int queue) { + this.queue = queue; + this.unsubscribe = false; + this.actionQueue = actionQueues[queue]; + } + + @Override + public Subscription schedule(Action0 action) { + return schedule(action, 0, TimeUnit.MILLISECONDS); + } + + @Override + public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) { + ActionWrapper wrapper = recycler.lease(); + wrapper.set(queue, action, delayTime, unit); + + actionQueue.offer(wrapper); + return wrapper; + } + + @Override + public void unsubscribe() { + this.unsubscribe = true; + } + + @Override + public boolean isUnsubscribed() { + return unsubscribe; + } + } + + private class ActionWrapperRecycler { + MpmcArrayQueue wrappers = new MpmcArrayQueue<>(128); + + public ActionWrapperRecycler() { + for (int i = 0; i < 128; i++) { + wrappers.offer(new ActionWrapper()); + } + } + + public ActionWrapper lease() { + ActionWrapper wrapper; + while((wrapper = wrappers.poll()) == null) {} + return wrapper; + } + + public void release(ActionWrapper wrapper) { + wrappers.offer(wrapper); + } + } + + private class ActionWrapper implements Subscription, Action0 { + private Action0 wrapped; + private long delayTime; + private TimeUnit timeUnit; + private volatile boolean unsubscribe; + private int queue; + private long start; + + // padding + private long p1, p2, p3, p4, p5, p6, p7, p8, p9; + private long p10, p11, p12, p13, p14, p15, p16, p17, p18, p19; + private long p20, p21, p22, p23, p24, p25, p26, p27, p28, p29; + private long p30, p31, p32, p33, p34, p35, p36, p37, p38, p39; + private long p40, p41, p42; + + public void set(int queue, Action0 wrapped, long delayTime, TimeUnit timeUnit) { + this.queue = queue; + this.wrapped = wrapped; + this.delayTime = delayTime; + this.timeUnit = timeUnit; + this.unsubscribe = false; + this.start = System.nanoTime(); + } + + @Override + public void call() { + if (!unsubscribe) { + if ((System.nanoTime() - start) >= timeUnit.toNanos(delayTime)) { + wrapped.call(); + } else { + actionQueues[queue].offer(this); + } + } + } + + @Override + public void unsubscribe() { + this.unsubscribe = true; + } + + @Override + public boolean isUnsubscribed() { + return unsubscribe; + } + + } +} diff --git a/src/main/java/io/reactivesocket/aeron/PublishSubscription.java b/src/main/java/io/reactivesocket/aeron/PublishSubscription.java index dcbea9335..660e1dc7c 100644 --- a/src/main/java/io/reactivesocket/aeron/PublishSubscription.java +++ b/src/main/java/io/reactivesocket/aeron/PublishSubscription.java @@ -35,7 +35,6 @@ public PublishSubscription(Subscriber subscriber, Publisher this.mtuLength = Integer.parseInt(mtuLength); - request(1); } @Override @@ -48,7 +47,7 @@ public void request(long n) { source.subscribe(new Subscriber() { @Override public void onSubscribe(Subscription s) { - s.request(1); + s.request(n); } @Override diff --git a/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java index 9167d62db..e2f5cf2bf 100644 --- a/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java @@ -1,12 +1,13 @@ package io.reactivesocket.aeron; +import io.reactivesocket.ConnectionSetupHandler; +import io.reactivesocket.ConnectionSetupPayload; import io.reactivesocket.Frame; import io.reactivesocket.ReactiveSocket; import io.reactivesocket.RequestHandler; -import org.reactivestreams.Publisher; +import io.reactivesocket.exceptions.SetupException; import org.reactivestreams.Subscriber; import rx.Scheduler; -import rx.schedulers.Schedulers; import uk.co.real_logic.aeron.Aeron; import uk.co.real_logic.aeron.FragmentAssembler; import uk.co.real_logic.aeron.Image; @@ -16,6 +17,8 @@ import uk.co.real_logic.agrona.BitUtil; import uk.co.real_logic.agrona.DirectBuffer; import uk.co.real_logic.agrona.collections.Int2ObjectHashMap; +import uk.co.real_logic.agrona.collections.Long2ObjectHashMap; +import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; @@ -39,6 +42,8 @@ public class ReactiveSocketAeronServer implements AutoCloseable { private final RequestHandler requestHandler; + private Long2ObjectHashMap sockets; + private static final org.reactivestreams.Subscriber PROTOCOL_SUBSCRIBER = new org.reactivestreams.Subscriber() { @Override public void onSubscribe(org.reactivestreams.Subscription s) { @@ -61,10 +66,13 @@ public void onComplete() { } }; + private static final MpscScheduler MPSC_SCHEDULER = new MpscScheduler(1, new NoOpIdleStrategy()); + private ReactiveSocketAeronServer(int port, RequestHandler requestHandler) { this.port = port; this.connections = new Int2ObjectHashMap<>(); this.requestHandler = requestHandler; + this.sockets = new Long2ObjectHashMap<>(); final Aeron.Context ctx = new Aeron.Context(); ctx.newImageHandler(this::newImageHandler); @@ -75,7 +83,7 @@ private ReactiveSocketAeronServer(int port, RequestHandler requestHandler) { final FragmentAssembler fragmentAssembler = new FragmentAssembler(this::fragmentHandler); - worker = Schedulers.computation().createWorker(); + worker = MPSC_SCHEDULER.createWorker(); poll(fragmentAssembler); @@ -89,13 +97,35 @@ public static ReactiveSocketAeronServer create(RequestHandler requestHandler) { return new ReactiveSocketAeronServer(39790, requestHandler); } + + volatile long start = System.currentTimeMillis(); + void poll(FragmentAssembler fragmentAssembler) { - if (running) { + /* if (running) { worker.schedule(() -> { subscription.poll(fragmentAssembler, Integer.MAX_VALUE); poll(fragmentAssembler); }); - } + }*/ + + + worker.schedule(() -> { + while (running) { + try { + subscription.poll(fragmentAssembler, Integer.MAX_VALUE); + + /*if ((System.currentTimeMillis() - start) > TimeUnit.SECONDS.toMillis(1)) { + + sockets.values().forEach(socket -> { + socket.sendLease(1_000, 1); + System.out.println("Sending lease for socket: " + socket.toString()); + + }); + start = System.currentTimeMillis(); + } */ + } catch (Throwable t) {} + } + }); } void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) { @@ -142,29 +172,17 @@ void newImageHandler(Image image, String channel, int streamId, int sessionId, l return new AeronServerDuplexConnection(publication); }); System.out.println("Accepting ReactiveSocket connection"); - ReactiveSocket socket = ReactiveSocket.createResponderAndRequestor(requestHandler); - Publisher connect = socket.connect(connection); - connect.subscribe(new Subscriber() { + ReactiveSocket socket = ReactiveSocket.fromServerConnection(connection, new ConnectionSetupHandler() { @Override - public void onSubscribe(org.reactivestreams.Subscription s) { - s.request(Long.MAX_VALUE); + public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + return requestHandler; } + }); - @Override - public void onNext(Void aVoid) { - } + sockets.put(sessionId, socket); - @Override - public void onError(Throwable t) { - t.printStackTrace(); - } - - @Override - public void onComplete() { - - } - }); + socket.start(); } else { System.out.println("Unsupported stream id " + streamId); @@ -180,6 +198,7 @@ public void close() throws Exception { for (AeronServerDuplexConnection connection : connections.values()) { connection.close(); } + } } diff --git a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java index 08be7d728..a708aec6e 100644 --- a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java @@ -1,12 +1,12 @@ package io.reactivesocket.aeron; +import io.reactivesocket.ConnectionSetupPayload; import io.reactivesocket.Frame; import io.reactivesocket.Payload; import io.reactivesocket.ReactiveSocket; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import rx.Scheduler; -import rx.schedulers.Schedulers; import uk.co.real_logic.aeron.Aeron; import uk.co.real_logic.aeron.FragmentAssembler; import uk.co.real_logic.aeron.Publication; @@ -15,6 +15,7 @@ import uk.co.real_logic.agrona.BitUtil; import uk.co.real_logic.agrona.DirectBuffer; import uk.co.real_logic.agrona.collections.Int2ObjectHashMap; +import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; import java.nio.ByteBuffer; @@ -35,7 +36,7 @@ public class ReactivesocketAeronClient implements AutoCloseable { private static final Int2ObjectHashMap establishConnectionLatches = new Int2ObjectHashMap<>(); - private final ReactiveSocket rsClientProtocol; + private ReactiveSocket reactiveSocket; private final Aeron aeron; @@ -45,10 +46,10 @@ public class ReactivesocketAeronClient implements AutoCloseable { private final int port; + private static final MpscScheduler MPSC_SCHEDULER = new MpscScheduler(1, new NoOpIdleStrategy()); + private ReactivesocketAeronClient(String host, int port) { this.port = port; - this.rsClientProtocol = - ReactiveSocket.createRequestor(); final Aeron.Context ctx = new Aeron.Context(); aeron = Aeron.connect(ctx); @@ -63,7 +64,7 @@ private ReactivesocketAeronClient(String host, int port) { final FragmentAssembler fragmentAssembler = new FragmentAssembler(this::fragmentHandler); - poll(fragmentAssembler, subscription, Schedulers.computation().createWorker()); + poll(fragmentAssembler, subscription, MPSC_SCHEDULER.createWorker()); return subscription; }); @@ -96,30 +97,12 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) final AeronClientDuplexConnection connection = connections.computeIfAbsent(header.sessionId(), (_p) -> new AeronClientDuplexConnection(publication)); - Publisher connect = this - .rsClientProtocol - .connect(connection); - - connect.subscribe(new Subscriber() { - @Override - public void onSubscribe(org.reactivestreams.Subscription s) { - s.request(Long.MAX_VALUE); - } - - @Override - public void onNext(Void aVoid) { - - } - - @Override - public void onError(Throwable t) { - t.printStackTrace(); - } + reactiveSocket = ReactiveSocket.fromClientConnection( + connection, + ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS), + err -> err.printStackTrace()); - @Override - public void onComplete() { - } - }); + reactiveSocket.start(); System.out.println("ReactiveSocket connected to Aeron session => " + ackSessionId); CountDownLatch latch = establishConnectionLatches.get(ackSessionId); @@ -131,12 +114,15 @@ public void onComplete() { } void poll(FragmentAssembler fragmentAssembler, Subscription subscription, Scheduler.Worker worker) { - if (running) { - worker.schedule(() -> { - subscription.poll(fragmentAssembler, Integer.MAX_VALUE); - poll(fragmentAssembler, subscription, worker); - }); - } + worker.schedule(() -> { + while (running) { + try { + subscription.poll(fragmentAssembler, Integer.MAX_VALUE); + } catch (Throwable t) {} + } + }); + + } /** @@ -178,19 +164,26 @@ void establishConnection(final Publication publication, final int sessionId) { } public Publisher requestResponse(Payload payload) { - return rsClientProtocol.requestResponse(payload); + /* + or (int i = 0; i < 100; i++) { + double availability = reactiveSocket.availability(); + + System.out.println("AVAILABLE => " + availability); + LockSupport.parkNanos(100_000_000); + }*/ + return reactiveSocket.requestResponse(payload); } public Publisher fireAndForget(Payload payload) { - return rsClientProtocol.fireAndForget(payload); + return reactiveSocket.fireAndForget(payload); } public Publisher requestStream(Payload payload) { - return rsClientProtocol.requestStream(payload); + return reactiveSocket.requestStream(payload); } public Publisher requestSubscription(Payload payload) { - return rsClientProtocol.requestSubscription(payload); + return reactiveSocket.requestSubscription(payload); } @Override diff --git a/src/perf/java/io/reactivesocket/aeron/MpscSchedulerPerf.java b/src/perf/java/io/reactivesocket/aeron/MpscSchedulerPerf.java new file mode 100644 index 000000000..1cb75cec6 --- /dev/null +++ b/src/perf/java/io/reactivesocket/aeron/MpscSchedulerPerf.java @@ -0,0 +1,58 @@ +package io.reactivesocket.aeron; + +import io.reactivesocket.aeron.jmh.InputWithIncrementingInteger; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import rx.Observable; +import rx.schedulers.Schedulers; +import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; + +import java.util.concurrent.TimeUnit; + +/** + * Created by rroeser on 8/30/15. + */ +@BenchmarkMode(Mode.All) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +public class MpscSchedulerPerf { + @State(Scope.Thread) + public static class Input extends InputWithIncrementingInteger { + + @Param({ "100", "1000", "1000000" }) + public int size; + + @Override + public int getSize() { + return size; + } + } + + private static final MpscScheduler mpsc = new MpscScheduler(3, new NoOpIdleStrategy()); + + @Benchmark + public void observableConsumption(Input input) throws InterruptedException { + input.firehose.subscribeOn(mpsc).subscribe(input.observer); + } + + + @Benchmark + public void observableConsumption2(Input input) throws InterruptedException { + Observable + .just(1) + .flatMap(i -> { + return input.firehose.subscribeOn(mpsc); + }) + .subscribe(input.observer); + } + + //@Benchmark + public void observableConsumption3(Input input) throws InterruptedException { + input.firehose.subscribeOn(Schedulers.computation()).subscribe(input.observer); + } + +} diff --git a/src/test/java/io/reactivesocket/aeron/MpscSchedulerTest.java b/src/test/java/io/reactivesocket/aeron/MpscSchedulerTest.java new file mode 100644 index 000000000..adc7b09a1 --- /dev/null +++ b/src/test/java/io/reactivesocket/aeron/MpscSchedulerTest.java @@ -0,0 +1,60 @@ +package io.reactivesocket.aeron; + +import org.junit.Test; +import rx.Observable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +public class MpscSchedulerTest { + @Test + public void test() { + Observable.range(1, 10, new MpscScheduler()).doOnNext(i -> { + System.out.println("Thread " + Thread.currentThread() + " - the number is => " + i); + }).toBlocking().last(); + } + + @Test + public void test2() throws Exception { + MpscScheduler mpscScheduler = new MpscScheduler(); + + Observable.range(1, 245) + .flatMap(j -> { + return Observable.just(j).subscribeOn(mpscScheduler).doOnNext(i -> { + System.out.println("Thread " + Thread.currentThread() + " - the number is => " + i); + }); + }).toBlocking().last(); + mpscScheduler.close(); + } + + + @Test + public void test3() throws Exception { + MpscScheduler mpscScheduler = new MpscScheduler(); + + Observable.just("hello").repeat(50, mpscScheduler).doOnNext(i -> { + System.out.println("Thread " + Thread.currentThread() + " - the message is => " + i); + }).toBlocking().last(); + mpscScheduler.close(); + } + + @Test + public void test4()throws Exception { + MpscScheduler mpscScheduler = new MpscScheduler(); + + AtomicLong start = new AtomicLong(0); + + Observable + .just("hello") + .doOnNext(a -> start.set(System.currentTimeMillis())) + .delay(1, TimeUnit.SECONDS, mpscScheduler) + .doOnNext(a -> System.out.println("Delay == " + (System.currentTimeMillis() - start.get()))) + .doOnNext(i -> { + System.out.println("Thread " + Thread.currentThread() + " - the message is => " + i); + }) + .toBlocking().last(); + mpscScheduler.close(); + } + + +} \ No newline at end of file From 378abe958f92924b47a62b10ef3b054cfad43fbf Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Mon, 7 Sep 2015 00:13:43 -0700 Subject: [PATCH 018/105] switched from println to logger --- build.gradle | 1 + .../aeron/AeronClientDuplexConnection.java | 27 ++- .../aeron/AeronServerDuplexConnection.java | 8 +- .../aeron/CompletableSubscription.java | 22 +- .../io/reactivesocket/aeron/Loggable.java | 29 +++ .../reactivesocket/aeron/MpscScheduler.java | 193 ------------------ .../aeron/ReactiveSocketAeronServer.java | 88 +++----- .../aeron/ReactivesocketAeronClient.java | 83 ++++---- .../aeron/MpscSchedulerPerf.java | 58 ------ .../aeron/MpscSchedulerTest.java | 60 ------ .../aeron/ReactiveSocketAeronTest.java | 10 +- 11 files changed, 154 insertions(+), 425 deletions(-) create mode 100644 src/main/java/io/reactivesocket/aeron/Loggable.java delete mode 100644 src/main/java/io/reactivesocket/aeron/MpscScheduler.java delete mode 100644 src/perf/java/io/reactivesocket/aeron/MpscSchedulerPerf.java delete mode 100644 src/test/java/io/reactivesocket/aeron/MpscSchedulerTest.java diff --git a/build.gradle b/build.gradle index d2b536469..b528b15f9 100644 --- a/build.gradle +++ b/build.gradle @@ -21,6 +21,7 @@ dependencies { compile 'io.reactivesocket:reactivesocket:0.0.1-SNAPSHOT' compile 'uk.co.real-logic:Agrona:0.4.2' compile 'uk.co.real-logic:aeron-all:0.1.2' + compile 'org.slf4j:slf4j-api:1.7.12' testCompile 'junit:junit-dep:4.10' testCompile 'org.mockito:mockito-core:1.8.5' } diff --git a/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java index 8a58e5ae6..8db90f1e4 100644 --- a/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java @@ -5,16 +5,36 @@ import io.reactivesocket.Frame; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import rx.functions.Action0; import uk.co.real_logic.aeron.Publication; public class AeronClientDuplexConnection implements DuplexConnection, AutoCloseable { private Publication publication; private Subscriber subscriber; private Publisher publisher; + private Action0 handleClose; + + public AeronClientDuplexConnection(Publication publication, Action0 handleClose) { - public AeronClientDuplexConnection(Publication publication) { this.publication = publication; - this.publisher = (Subscriber s) -> subscriber = s; + this.publisher = (Subscriber s) -> { + subscriber = s; + s.onSubscribe(new Subscription() { + @Override + public void request(long n) { + + } + + @Override + public void cancel() { + + } + }); + }; + + this.handleClose = handleClose; + } public Subscriber getSubscriber() { @@ -27,13 +47,14 @@ public Publisher getInput() { @Override public void addOutput(Publisher o, Completable callback) { - o.subscribe(new CompletableSubscription(publication, callback)); + o.subscribe(new CompletableSubscription(publication, callback, this)); } @Override public void close() { subscriber.onComplete(); publication.close(); + handleClose.call(); } } diff --git a/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java index 86c3987fb..b6699b2ce 100644 --- a/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java @@ -12,7 +12,7 @@ import java.util.concurrent.TimeUnit; -public class AeronServerDuplexConnection implements DuplexConnection, AutoCloseable { +public class AeronServerDuplexConnection implements DuplexConnection, AutoCloseable, Loggable { private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); @@ -37,7 +37,7 @@ public Publisher getInput() { @Override public void addOutput(Publisher o, Completable callback) { - o.subscribe(new CompletableSubscription(publication, callback)); + o.subscribe(new CompletableSubscription(publication, callback, this)); } void ackEstablishConnection(int ackSessionId) { @@ -45,11 +45,11 @@ void ackEstablishConnection(int ackSessionId) { final int sessionId = publication.sessionId(); final BufferClaim bufferClaim = bufferClaims.get(); - System.out.println("Acking establish connection for session id => " + ackSessionId); + debug("Acking establish connection for session id => {}", ackSessionId); for (;;) { final long current = System.nanoTime(); - if (current - start > TimeUnit.SECONDS.toNanos(30)) { + if ((current - start) > TimeUnit.SECONDS.toNanos(30)) { throw new RuntimeException("Timed out waiting to establish connection for session id => " + sessionId); } diff --git a/src/main/java/io/reactivesocket/aeron/CompletableSubscription.java b/src/main/java/io/reactivesocket/aeron/CompletableSubscription.java index 8e0e72eae..4711fa524 100644 --- a/src/main/java/io/reactivesocket/aeron/CompletableSubscription.java +++ b/src/main/java/io/reactivesocket/aeron/CompletableSubscription.java @@ -7,6 +7,7 @@ import uk.co.real_logic.aeron.Publication; import uk.co.real_logic.aeron.logbuffer.BufferClaim; import uk.co.real_logic.agrona.BitUtil; +import uk.co.real_logic.agrona.LangUtil; import uk.co.real_logic.agrona.MutableDirectBuffer; import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; @@ -25,11 +26,14 @@ public class CompletableSubscription implements Subscriber { private final Completable completable; + private final AutoCloseable closeable; + private final int mtuLength; - public CompletableSubscription(Publication publication, Completable completable) { + public CompletableSubscription(Publication publication, Completable completable, AutoCloseable closeable) { this.publication = publication; this.completable = completable; + this.closeable = closeable; String mtuLength = System.getProperty("aeron.mtu.length", "4096"); @@ -71,21 +75,22 @@ void offer(ByteBuffer byteBuffer, int length) { unsafeBuffer.wrap(bytes); unsafeBuffer.putInt(0, MessageType.FRAME.getEncodedType()); unsafeBuffer.putBytes(BitUtil.SIZE_OF_INT, byteBuffer, byteBuffer.capacity()); - for (;;) { + do { final long offer = publication.offer(unsafeBuffer); if (offer >= 0) { break; } else if (Publication.NOT_CONNECTED == offer) { + closeQuietly(closeable); completable.error(new RuntimeException("not connected")); break; } - } + } while(true); } void tryClaim(ByteBuffer byteBuffer, int length) { final BufferClaim bufferClaim = bufferClaims.get(); - for (;;) { + do { final long offer = publication.tryClaim(length, bufferClaim); if (offer >= 0) { try { @@ -99,9 +104,18 @@ void tryClaim(ByteBuffer byteBuffer, int length) { break; } else if (Publication.NOT_CONNECTED == offer) { + closeQuietly(closeable); completable.error(new RuntimeException("not connected")); break; } + } while(true); + } + + void closeQuietly(AutoCloseable closeable) { + try { + closeable.close(); + } catch (Exception e) { + LangUtil.rethrowUnchecked(e); } } } diff --git a/src/main/java/io/reactivesocket/aeron/Loggable.java b/src/main/java/io/reactivesocket/aeron/Loggable.java new file mode 100644 index 000000000..d2fabf416 --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/Loggable.java @@ -0,0 +1,29 @@ +package io.reactivesocket.aeron; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * No more needed to type Logger LOGGER = LoggerFactory.getLogger.... + */ +interface Loggable { + default void info(String message, Object... args) { + logger().debug(message, args); + } + + default void error(String message, Throwable t, Object... args) { + logger().debug(message, t, args); + } + + default void debug(String message, Object... args) { + logger().debug(message, args); + } + + static final ConcurrentHashMap loggers = new ConcurrentHashMap<>(); + + default Logger logger() { + return loggers.computeIfAbsent(getClass(), LoggerFactory::getLogger); + } +} diff --git a/src/main/java/io/reactivesocket/aeron/MpscScheduler.java b/src/main/java/io/reactivesocket/aeron/MpscScheduler.java deleted file mode 100644 index da96de467..000000000 --- a/src/main/java/io/reactivesocket/aeron/MpscScheduler.java +++ /dev/null @@ -1,193 +0,0 @@ -package io.reactivesocket.aeron; - -import rx.Scheduler; -import rx.Subscription; -import rx.exceptions.Exceptions; -import rx.functions.Action0; -import rx.internal.util.RxThreadFactory; -import rx.internal.util.unsafe.MpmcArrayQueue; -import rx.internal.util.unsafe.MpscLinkedQueue; -import rx.plugins.RxJavaPlugins; -import uk.co.real_logic.agrona.concurrent.IdleStrategy; -import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; - -import java.io.Closeable; -import java.io.IOException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; - -/** - * Created by rroeser on 8/30/15. - */ -public class MpscScheduler extends Scheduler implements Closeable { - private final MpscLinkedQueue[] actionQueues; - - private AtomicLong count; - - private volatile boolean running = true; - private long p1, p2, p3, p4, p5, p6, p7; - - private final int mask; - private long p11, p12, p13, p14, p15, p16, p17; - - private ActionWrapperRecycler recycler; - - public MpscScheduler(int consumers, IdleStrategy idleStrategy) { - actionQueues = new MpscLinkedQueue[consumers]; - recycler = new ActionWrapperRecycler(); - - mask = consumers - 1; - - RxThreadFactory factory = new RxThreadFactory("MpscScheduler-"); - - for (int i = 0; i < consumers; i++) { - final MpscLinkedQueue actionQueue = new MpscLinkedQueue<>(); - actionQueues[i] = actionQueue; - - final Thread thread = factory.newThread(() -> { - while (running) { - ActionWrapper wrapper = null; - try { - while ((wrapper = actionQueue.poll()) == null) { - idleStrategy.idle(0); - } - wrapper.call(); - } catch (Throwable t) { - Exceptions.throwIfFatal(t); - RxJavaPlugins.getInstance().getErrorHandler().handleError(t); - } - finally { - if (wrapper != null) { - recycler.release(wrapper); - } - } - } - }); - - thread.start(); - } - - count = new AtomicLong(0); - - } - - public MpscScheduler() { - this(Runtime.getRuntime().availableProcessors(), new NoOpIdleStrategy()); - } - - @Override - public Worker createWorker() { - return new MpscWorker((int) count.getAndIncrement() & mask); - } - - @Override - public void close() throws IOException { - running = false; - } - - class MpscWorker extends Worker { - - private int queue; - - private volatile boolean unsubscribe; - - private MpscLinkedQueue actionQueue; - - public MpscWorker(int queue) { - this.queue = queue; - this.unsubscribe = false; - this.actionQueue = actionQueues[queue]; - } - - @Override - public Subscription schedule(Action0 action) { - return schedule(action, 0, TimeUnit.MILLISECONDS); - } - - @Override - public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) { - ActionWrapper wrapper = recycler.lease(); - wrapper.set(queue, action, delayTime, unit); - - actionQueue.offer(wrapper); - return wrapper; - } - - @Override - public void unsubscribe() { - this.unsubscribe = true; - } - - @Override - public boolean isUnsubscribed() { - return unsubscribe; - } - } - - private class ActionWrapperRecycler { - MpmcArrayQueue wrappers = new MpmcArrayQueue<>(128); - - public ActionWrapperRecycler() { - for (int i = 0; i < 128; i++) { - wrappers.offer(new ActionWrapper()); - } - } - - public ActionWrapper lease() { - ActionWrapper wrapper; - while((wrapper = wrappers.poll()) == null) {} - return wrapper; - } - - public void release(ActionWrapper wrapper) { - wrappers.offer(wrapper); - } - } - - private class ActionWrapper implements Subscription, Action0 { - private Action0 wrapped; - private long delayTime; - private TimeUnit timeUnit; - private volatile boolean unsubscribe; - private int queue; - private long start; - - // padding - private long p1, p2, p3, p4, p5, p6, p7, p8, p9; - private long p10, p11, p12, p13, p14, p15, p16, p17, p18, p19; - private long p20, p21, p22, p23, p24, p25, p26, p27, p28, p29; - private long p30, p31, p32, p33, p34, p35, p36, p37, p38, p39; - private long p40, p41, p42; - - public void set(int queue, Action0 wrapped, long delayTime, TimeUnit timeUnit) { - this.queue = queue; - this.wrapped = wrapped; - this.delayTime = delayTime; - this.timeUnit = timeUnit; - this.unsubscribe = false; - this.start = System.nanoTime(); - } - - @Override - public void call() { - if (!unsubscribe) { - if ((System.nanoTime() - start) >= timeUnit.toNanos(delayTime)) { - wrapped.call(); - } else { - actionQueues[queue].offer(this); - } - } - } - - @Override - public void unsubscribe() { - this.unsubscribe = true; - } - - @Override - public boolean isUnsubscribed() { - return unsubscribe; - } - - } -} diff --git a/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java index e2f5cf2bf..3454c356d 100644 --- a/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java @@ -8,6 +8,7 @@ import io.reactivesocket.exceptions.SetupException; import org.reactivestreams.Subscriber; import rx.Scheduler; +import rx.schedulers.Schedulers; import uk.co.real_logic.aeron.Aeron; import uk.co.real_logic.aeron.FragmentAssembler; import uk.co.real_logic.aeron.Image; @@ -18,7 +19,6 @@ import uk.co.real_logic.agrona.DirectBuffer; import uk.co.real_logic.agrona.collections.Int2ObjectHashMap; import uk.co.real_logic.agrona.collections.Long2ObjectHashMap; -import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; @@ -26,7 +26,7 @@ import static io.reactivesocket.aeron.Constants.CLIENT_STREAM_ID; import static io.reactivesocket.aeron.Constants.SERVER_STREAM_ID; -public class ReactiveSocketAeronServer implements AutoCloseable { +public class ReactiveSocketAeronServer implements AutoCloseable, Loggable { private final Aeron aeron; @@ -44,31 +44,7 @@ public class ReactiveSocketAeronServer implements AutoCloseable { private Long2ObjectHashMap sockets; - private static final org.reactivestreams.Subscriber PROTOCOL_SUBSCRIBER = new org.reactivestreams.Subscriber() { - @Override - public void onSubscribe(org.reactivestreams.Subscription s) { - s.request(Long.MAX_VALUE); - } - - @Override - public void onNext(Void t) { - - } - - @Override - public void onError(Throwable t) { - t.printStackTrace(); - } - - @Override - public void onComplete() { - - } - }; - - private static final MpscScheduler MPSC_SCHEDULER = new MpscScheduler(1, new NoOpIdleStrategy()); - - private ReactiveSocketAeronServer(int port, RequestHandler requestHandler) { + private ReactiveSocketAeronServer(String host, int port, RequestHandler requestHandler) { this.port = port; this.connections = new Int2ObjectHashMap<>(); this.requestHandler = requestHandler; @@ -76,56 +52,47 @@ private ReactiveSocketAeronServer(int port, RequestHandler requestHandler) { final Aeron.Context ctx = new Aeron.Context(); ctx.newImageHandler(this::newImageHandler); + ctx.errorHandler(t -> { + t.printStackTrace(); + }); aeron = Aeron.connect(ctx); - subscription = aeron.addSubscription("udp://localhost:" + port, SERVER_STREAM_ID); + subscription = aeron.addSubscription("udp://" + host + ":" + port, SERVER_STREAM_ID); final FragmentAssembler fragmentAssembler = new FragmentAssembler(this::fragmentHandler); - worker = MPSC_SCHEDULER.createWorker(); - + worker = Schedulers.computation().createWorker(); poll(fragmentAssembler); } + public static ReactiveSocketAeronServer create(String host, int port, RequestHandler requestHandler) { + return new ReactiveSocketAeronServer(host, port, requestHandler); + } + public static ReactiveSocketAeronServer create(int port, RequestHandler requestHandler) { - return new ReactiveSocketAeronServer(port, requestHandler); + return create("127.0.0.1", port, requestHandler); } public static ReactiveSocketAeronServer create(RequestHandler requestHandler) { - return new ReactiveSocketAeronServer(39790, requestHandler); + return create(39790, requestHandler); } volatile long start = System.currentTimeMillis(); void poll(FragmentAssembler fragmentAssembler) { - /* if (running) { + if (running) { worker.schedule(() -> { - subscription.poll(fragmentAssembler, Integer.MAX_VALUE); - poll(fragmentAssembler); - }); - }*/ - - - worker.schedule(() -> { - while (running) { try { subscription.poll(fragmentAssembler, Integer.MAX_VALUE); - - /*if ((System.currentTimeMillis() - start) > TimeUnit.SECONDS.toMillis(1)) { - - sockets.values().forEach(socket -> { - socket.sendLease(1_000, 1); - System.out.println("Sending lease for socket: " + socket.toString()); - - }); - start = System.currentTimeMillis(); - } */ - } catch (Throwable t) {} - } - }); + poll(fragmentAssembler); + } catch (Throwable t) { + t.printStackTrace(); + } + }); + } } void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) { @@ -146,7 +113,7 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) } else if (MessageType.ESTABLISH_CONNECTION_REQUEST == type) { final long start = System.nanoTime(); AeronServerDuplexConnection connection = null; - System.out.println("Looking a connection to ack establish connection for session id => " + sessionId); + debug("Looking a connection to ack establish connection for session id => {}", sessionId); while (connection == null) { final long current = System.nanoTime(); @@ -156,7 +123,7 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) connection = connections.get(sessionId); } - System.out.println("Found a connection to ack establish connection for session id => " + sessionId); + debug("Found a connection to ack establish connection for session id => {}", sessionId); connection.ackEstablishConnection(sessionId); } @@ -164,14 +131,14 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) void newImageHandler(Image image, String channel, int streamId, int sessionId, long joiningPosition, String sourceIdentity) { if (SERVER_STREAM_ID == streamId) { - System.out.println(String.format("Handling new image for session id => %d and stream id => %d", streamId, sessionId)); + debug("Handling new image for session id => {} and stream id => {}", streamId, sessionId); final AeronServerDuplexConnection connection = connections.computeIfAbsent(sessionId, (_s) -> { final String responseChannel = "udp://" + sourceIdentity.substring(0, sourceIdentity.indexOf(':')) + ":" + port; Publication publication = aeron.addPublication(responseChannel, CLIENT_STREAM_ID); - System.out.println(String.format("Creating new connection for responseChannel => %s, streamId => %d, and sessionId => %d", responseChannel, streamId, sessionId)); + debug("Creating new connection for responseChannel => {}, streamId => {}, and sessionId => {}", responseChannel, streamId, sessionId); return new AeronServerDuplexConnection(publication); }); - System.out.println("Accepting ReactiveSocket connection"); + debug("Accepting ReactiveSocket connection"); ReactiveSocket socket = ReactiveSocket.fromServerConnection(connection, new ConnectionSetupHandler() { @Override public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { @@ -179,13 +146,12 @@ public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupExc } }); - sockets.put(sessionId, socket); socket.start(); } else { - System.out.println("Unsupported stream id " + streamId); + debug("Unsupported stream id {}", streamId); } } diff --git a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java index a708aec6e..5a088f0fe 100644 --- a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java @@ -7,6 +7,7 @@ import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import rx.Scheduler; +import rx.schedulers.Schedulers; import uk.co.real_logic.aeron.Aeron; import uk.co.real_logic.aeron.FragmentAssembler; import uk.co.real_logic.aeron.Publication; @@ -15,7 +16,6 @@ import uk.co.real_logic.agrona.BitUtil; import uk.co.real_logic.agrona.DirectBuffer; import uk.co.real_logic.agrona.collections.Int2ObjectHashMap; -import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; import java.nio.ByteBuffer; @@ -29,7 +29,7 @@ /** * Created by rroeser on 8/13/15. */ -public class ReactivesocketAeronClient implements AutoCloseable { +public class ReactivesocketAeronClient implements Loggable { private static final Int2ObjectHashMap subscriptions = new Int2ObjectHashMap<>(); private static final Int2ObjectHashMap connections = new Int2ObjectHashMap<>(); @@ -46,25 +46,47 @@ public class ReactivesocketAeronClient implements AutoCloseable { private final int port; - private static final MpscScheduler MPSC_SCHEDULER = new MpscScheduler(1, new NoOpIdleStrategy()); + static { + Runtime + .getRuntime() + .addShutdownHook(new Thread(() -> { + for (Subscription subscription : subscriptions.values()) { + subscription.close(); + } - private ReactivesocketAeronClient(String host, int port) { + for (AeronClientDuplexConnection connection : connections.values()) { + connection.close(); + } + })); + } + + private ReactivesocketAeronClient(String host, String server, int port) { this.port = port; final Aeron.Context ctx = new Aeron.Context(); + ctx.errorHandler(t -> { + t.printStackTrace(); + }); + aeron = Aeron.connect(ctx); final String channel = "udp://" + host + ":" + port; - System.out.println("Creating a publication to channel => " + channel); + final String subscriptionChannel = "udp://" + server + ":" + port; + System.out.println("Creating a publication to channel => " + channel); publication = aeron.addPublication(channel, SERVER_STREAM_ID); final int sessionId = publication.sessionId(); + + System.out.println("Created a publication for sessionId => " + sessionId); + subscriptions.computeIfAbsent(port, (_p) -> { - Subscription subscription = aeron.addSubscription(channel, CLIENT_STREAM_ID); + System.out.println("Creating a subscription to channel => " + subscriptionChannel); + Subscription subscription = aeron.addSubscription(subscriptionChannel, CLIENT_STREAM_ID); + System.out.println("Subscription created to channel => " + subscriptionChannel); final FragmentAssembler fragmentAssembler = new FragmentAssembler(this::fragmentHandler); - poll(fragmentAssembler, subscription, MPSC_SCHEDULER.createWorker()); + poll(fragmentAssembler, subscription, Schedulers.computation().createWorker()); return subscription; }); @@ -73,12 +95,12 @@ private ReactivesocketAeronClient(String host, int port) { } - public static ReactivesocketAeronClient create(String host, int port) { - return new ReactivesocketAeronClient(host, port); + public static ReactivesocketAeronClient create(String host, String server, int port) { + return new ReactivesocketAeronClient(host, server, port); } - public static ReactivesocketAeronClient create(String host) { - return new ReactivesocketAeronClient(host, 39790); + public static ReactivesocketAeronClient create(String host, String server) { + return new ReactivesocketAeronClient(host, server, 39790); } void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) { @@ -94,8 +116,11 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) } else if (messageType == MessageType.ESTABLISH_CONNECTION_RESPONSE) { int ackSessionId = buffer.getInt(offset + BitUtil.SIZE_OF_INT); System.out.println(String.format("Received establish connection ack for session id => %d", ackSessionId)); - - final AeronClientDuplexConnection connection = connections.computeIfAbsent(header.sessionId(), (_p) -> new AeronClientDuplexConnection(publication)); + final int headerSessionId = header.sessionId(); + final AeronClientDuplexConnection connection = + connections + .computeIfAbsent(headerSessionId, (_p) -> + new AeronClientDuplexConnection(publication, () -> connections.remove(headerSessionId))); reactiveSocket = ReactiveSocket.fromClientConnection( connection, @@ -115,14 +140,15 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) void poll(FragmentAssembler fragmentAssembler, Subscription subscription, Scheduler.Worker worker) { worker.schedule(() -> { - while (running) { + if (running) { try { subscription.poll(fragmentAssembler, Integer.MAX_VALUE); - } catch (Throwable t) {} + poll(fragmentAssembler, subscription, worker); + } catch (Throwable t) { + t.printStackTrace(); + } } }); - - } /** @@ -141,7 +167,7 @@ void establishConnection(final Publication publication, final int sessionId) { final long start = System.nanoTime(); for (;;) { final long current = System.nanoTime(); - if (current - start > TimeUnit.SECONDS.toNanos(30)) { + if ((current - start) > TimeUnit.SECONDS.toNanos(30)) { throw new RuntimeException("Timed out waiting to establish connection for session id => " + sessionId); } @@ -154,9 +180,9 @@ void establishConnection(final Publication publication, final int sessionId) { } } - System.out.println(String.format("Connection established for channel => %s, stream id => %d", + debug("Connection established for channel => {}, stream id => {}", publication.channel(), - publication.sessionId())); + publication.sessionId()); } finally { establishConnectionLatches.remove(sessionId); } @@ -164,13 +190,6 @@ void establishConnection(final Publication publication, final int sessionId) { } public Publisher requestResponse(Payload payload) { - /* - or (int i = 0; i < 100; i++) { - double availability = reactiveSocket.availability(); - - System.out.println("AVAILABLE => " + availability); - LockSupport.parkNanos(100_000_000); - }*/ return reactiveSocket.requestResponse(payload); } @@ -186,14 +205,4 @@ public Publisher requestSubscription(Payload payload) { return reactiveSocket.requestSubscription(payload); } - @Override - public void close() throws Exception { - for (Subscription subscription : subscriptions.values()) { - subscription.close(); - } - - for (AeronClientDuplexConnection connection : connections.values()) { - connection.close(); - } - } } diff --git a/src/perf/java/io/reactivesocket/aeron/MpscSchedulerPerf.java b/src/perf/java/io/reactivesocket/aeron/MpscSchedulerPerf.java deleted file mode 100644 index 1cb75cec6..000000000 --- a/src/perf/java/io/reactivesocket/aeron/MpscSchedulerPerf.java +++ /dev/null @@ -1,58 +0,0 @@ -package io.reactivesocket.aeron; - -import io.reactivesocket.aeron.jmh.InputWithIncrementingInteger; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Param; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.State; -import rx.Observable; -import rx.schedulers.Schedulers; -import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; - -import java.util.concurrent.TimeUnit; - -/** - * Created by rroeser on 8/30/15. - */ -@BenchmarkMode(Mode.All) -@OutputTimeUnit(TimeUnit.MICROSECONDS) -public class MpscSchedulerPerf { - @State(Scope.Thread) - public static class Input extends InputWithIncrementingInteger { - - @Param({ "100", "1000", "1000000" }) - public int size; - - @Override - public int getSize() { - return size; - } - } - - private static final MpscScheduler mpsc = new MpscScheduler(3, new NoOpIdleStrategy()); - - @Benchmark - public void observableConsumption(Input input) throws InterruptedException { - input.firehose.subscribeOn(mpsc).subscribe(input.observer); - } - - - @Benchmark - public void observableConsumption2(Input input) throws InterruptedException { - Observable - .just(1) - .flatMap(i -> { - return input.firehose.subscribeOn(mpsc); - }) - .subscribe(input.observer); - } - - //@Benchmark - public void observableConsumption3(Input input) throws InterruptedException { - input.firehose.subscribeOn(Schedulers.computation()).subscribe(input.observer); - } - -} diff --git a/src/test/java/io/reactivesocket/aeron/MpscSchedulerTest.java b/src/test/java/io/reactivesocket/aeron/MpscSchedulerTest.java deleted file mode 100644 index adc7b09a1..000000000 --- a/src/test/java/io/reactivesocket/aeron/MpscSchedulerTest.java +++ /dev/null @@ -1,60 +0,0 @@ -package io.reactivesocket.aeron; - -import org.junit.Test; -import rx.Observable; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; - -public class MpscSchedulerTest { - @Test - public void test() { - Observable.range(1, 10, new MpscScheduler()).doOnNext(i -> { - System.out.println("Thread " + Thread.currentThread() + " - the number is => " + i); - }).toBlocking().last(); - } - - @Test - public void test2() throws Exception { - MpscScheduler mpscScheduler = new MpscScheduler(); - - Observable.range(1, 245) - .flatMap(j -> { - return Observable.just(j).subscribeOn(mpscScheduler).doOnNext(i -> { - System.out.println("Thread " + Thread.currentThread() + " - the number is => " + i); - }); - }).toBlocking().last(); - mpscScheduler.close(); - } - - - @Test - public void test3() throws Exception { - MpscScheduler mpscScheduler = new MpscScheduler(); - - Observable.just("hello").repeat(50, mpscScheduler).doOnNext(i -> { - System.out.println("Thread " + Thread.currentThread() + " - the message is => " + i); - }).toBlocking().last(); - mpscScheduler.close(); - } - - @Test - public void test4()throws Exception { - MpscScheduler mpscScheduler = new MpscScheduler(); - - AtomicLong start = new AtomicLong(0); - - Observable - .just("hello") - .doOnNext(a -> start.set(System.currentTimeMillis())) - .delay(1, TimeUnit.SECONDS, mpscScheduler) - .doOnNext(a -> System.out.println("Delay == " + (System.currentTimeMillis() - start.get()))) - .doOnNext(i -> { - System.out.println("Thread " + Thread.currentThread() + " - the message is => " + i); - }) - .toBlocking().last(); - mpscScheduler.close(); - } - - -} \ No newline at end of file diff --git a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java index 99c4e04d1..9418354d9 100644 --- a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java @@ -36,7 +36,7 @@ public void testRequestReponse() throws Exception { @Override public Publisher handleRequestResponse(Payload payload) { String request = TestUtil.byteToString(payload.getData()); - System.out.println("Server got => " + request); + //System.out.println("Server got => " + request); Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong", null)); return RxReactiveStreams.toPublisher(pong); } @@ -65,12 +65,12 @@ public Publisher handleFireAndForget(Payload payload) { CountDownLatch latch = new CountDownLatch(10_000); - ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost"); + ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost", "localhost"); Observable .range(1, 10_000) .flatMap(i -> { - System.out.println("pinging => " + i); + //System.out.println("pinging => " + i); Payload payload = TestUtil.utf8EncodedPayload("ping =>" + i, null); return RxReactiveStreams.toObservable(client.requestResponse(payload)); } @@ -87,7 +87,7 @@ public void onError(Throwable e) { @Override public void onNext(Payload s) { - System.out.println(s + " countdown => " + latch.getCount()); + //System.out.println(s + " countdown => " + latch.getCount()); latch.countDown(); } }); @@ -134,7 +134,7 @@ public Publisher handleFireAndForget(Payload payload) { CountDownLatch latch = new CountDownLatch(2); - ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost"); + ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost", "localhost"); Observable .range(1, 2) From 3d6a8e1bae3685dc3d2292b7fd6dd68044b6f47c Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Thu, 10 Sep 2015 12:04:52 -0700 Subject: [PATCH 019/105] updating to reactive socket 1.0.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b528b15f9..f492f7407 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ buildscript { jcenter() } - dependencies { classpath 'io.reactivesocket:gradle-nebula-plugin-reactivesocket:1.0.0' } + dependencies { classpath 'io.reactivesocket:gradle-nebula-plugin-reactivesocket:1.0.1' } } description = 'ReactiveSocket: stream oriented messaging passing with Reactive Stream semantics.' From 39b6ad52d0a52e88518358b1194caf1c7da5249b Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Thu, 10 Sep 2015 12:14:01 -0700 Subject: [PATCH 020/105] updated to match reactive socket java changes --- .../aeron/ReactiveSocketAeronTest.java | 10 ++++++ .../io/reactivesocket/aeron/TestUtil.java | 32 +++++++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java index 9418354d9..1b513bc75 100644 --- a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java @@ -60,6 +60,11 @@ public Publisher handleSubscription(Payload payload) { public Publisher handleFireAndForget(Payload payload) { return null; } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } }); CountDownLatch latch = new CountDownLatch(10_000); @@ -129,6 +134,11 @@ public Publisher handleSubscription(Payload payload) { public Publisher handleFireAndForget(Payload payload) { return null; } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } }); CountDownLatch latch = new CountDownLatch(2); diff --git a/src/test/java/io/reactivesocket/aeron/TestUtil.java b/src/test/java/io/reactivesocket/aeron/TestUtil.java index 29ce236c2..1df48fb11 100644 --- a/src/test/java/io/reactivesocket/aeron/TestUtil.java +++ b/src/test/java/io/reactivesocket/aeron/TestUtil.java @@ -3,15 +3,37 @@ import io.reactivesocket.Frame; import io.reactivesocket.FrameType; import io.reactivesocket.Payload; +import uk.co.real_logic.agrona.MutableDirectBuffer; import java.nio.ByteBuffer; import java.nio.charset.Charset; public class TestUtil { - public static Frame utf8EncodedFrame(final long streamId, final FrameType type, final String data) + public static Frame utf8EncodedRequestFrame(final int streamId, final FrameType type, final String data, final int initialRequestN) { - return Frame.from(streamId, type, byteBufferFromUtf8String(data)); + return Frame.Request.from(streamId, type, new Payload() + { + public ByteBuffer getData() + { + return byteBufferFromUtf8String(data); + } + + public ByteBuffer getMetadata() + { + return Frame.NULL_BYTEBUFFER; + } + }, initialRequestN); + } + + public static Frame utf8EncodedResponseFrame(final int streamId, final FrameType type, final String data) + { + return Frame.Response.from(streamId, type, utf8EncodedPayload(data, null)); + } + + public static Frame utf8EncodedErrorFrame(final int streamId, final String data) + { + return Frame.Error.from(streamId, new Exception(data)); } public static Payload utf8EncodedPayload(final String data, final String metadata) @@ -32,6 +54,11 @@ public static ByteBuffer byteBufferFromUtf8String(final String data) return ByteBuffer.wrap(bytes); } + public static void copyFrame(final MutableDirectBuffer dst, final int offset, final Frame frame) + { + dst.putBytes(offset, frame.getByteBuffer(), frame.offset(), frame.length()); + } + private static class PayloadImpl implements Payload // some JDK shoutout { private ByteBuffer data; @@ -60,6 +87,7 @@ public PayloadImpl(final String data, final String metadata) public boolean equals(Object obj) { + System.out.println("equals: " + obj); final Payload rhs = (Payload)obj; return (TestUtil.byteToString(data).equals(TestUtil.byteToString(rhs.getData()))) && From 427ded52cb53cc88623150bdbaa91202af567995 Mon Sep 17 00:00:00 2001 From: Ben Christensen Date: Thu, 10 Sep 2015 12:21:05 -0700 Subject: [PATCH 021/105] Bintray credentials --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0039232f7..50633ccb0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,5 +22,5 @@ cache: env: global: - - secure: cXHzd2WHqmdmJEyEKlELt8Rp9qCvhTRXTEHpQz0sKt55KorI8vO33sSOBs8uBqknWgGgOzHsB7cw0dJRxCmW+BRy90ELtdg/dVLzU8D8BrI6/DHzd/Bhyt9wx2eVdLmDV7lQ113AqJ7lphbH+U8ceTBlbNYDPKcIjFhsPO0WcPxQYed45na8XRK0UcAOpVmmNlTE6fHy5acQblNO84SN6uevCFqWAZJY7rc6xGrzFzca+ul5kR8xIzdE5jKs2Iw0MDeWi8cshkhj9c0FDtfsNIB1F+NafDtEdqjt6kMqYAUUiTAM2QdNoffzgmWEbVOj3uvthlm+S11XaU3Cn2uC7CiZTn2ebuoqCuV5Ge6KQI0ysEQVUfLhIF7iJG6dJvoyYy8ta8LEcjcsYAdF34BVddoUJkp+eJuhlto2aTZsDdXpmnwRM1PPDRoyrLjRcKiWYPR2tO2RG9sb0nRAGEpHTDd5ju2Ta4zpvgpWGUiKprs5R+YY7TEg16VSTYMmCJj5C9ap2lYIH4EoxsQpuxYig9AV1sOUJujLSa4TXqlcOmSM0IkHJ/i0VE8TZg4nV4XowyH6nKZ63InF4pUDcG13BpJQyTFKbK2D0lFn8MzpWvIV2oOUxNkOaOBg9cGhAnv9Sfw/Iv1UVaUgCNQd2M0R0rwfJoPCg2mmWVxsvh3cW4M= - - secure: UKZHoS/uw6SuAI9n0lCHWEc74H9+STpdvMmLd+nANjWkMFfo0bOUbm1SsV6PU6d2r8C5k4dEsW90J4diunR856R8vO+DpJPwUNJDuLm2Kiv7zhLJrXqpRTw3E3ijdFA84xocTN1CxZakW+ZP2wnb83jI3p99rgotc0i1wz9n1onaZrhZK5c3Rod2cdRig0wkeKK9NhwupXbXkpPtRNFRCOPgKvjPiEeW5YRZ/YxOs+OL9Sy6764b46EiWP/DFPGOTkJwz2mxLRT8sBx6rjeyf6v41NQPW1rlNwIDKcpnQl1n49k5SgARZvhFlakRdLyzljj1L0/VLk7xNDEQx3LYxl2mSl7AQlA8RYkxqirMRnIHHXrA7hhPuCYxp2nlpciwuh69vAOfliL3JeAsEgj0PKiQp7HQyBPQOvfmiGH2oIo+dkXvQwmLZTDnj9vNzZIS+rADbZoLzKftZKAUIWCze5zQ6mCkwKiuVYYWl2aPoy2XxRkA51t6sEHA0/iYrqaOX76WHGH0JhoAGWEIBNNP/rRnO38Rm96pm6SHrzLa1VxVFT6dRGljFTxvCsgsCfx/rRL+a1E0j89nLAmOGkDpyhUaKWqVQJWk3H1AeQ3cWGXfvUhDyaSTxcKs6AuQ2E5TtNgkbx0Xjq8NDjuiP57WDFYMXGvIqkgSzKG3A0DSMHI= + - secure: U4mRou9dXE0QDYx7Wm4lhZ2yNe+/XoWN0wdkZsc+cdDFicPQU4o/tHWlZ2zdBaP4lPKyc/JBIaCS6wdIEIj5oaZQ7NCKpgNpz4YguoLmPrApO7qoD3Aa48NbylKlyAl5Lc7dJ11Ru1BouwBR17EM9HVOFfLu+IN16wppHd4LBbFu6PRUs5m4JAkx3G4fZRPtikuxHt/vJDAZbE1g/wNQJ3chiq6FWTFkgLtCABkUulD/pv7iDP9xKrVMxQcKY+CQOJSoJbteIJMmxv6GSpIbNA91+YWdPpXh0l8BYzzz1RbmqqrNyB6qdHWjET7Bq6UMjPNTND+Qedps6Rqnv1EZbvybeUqExSRoJ9EwOq5T44T2afIL6+zjE/5chSWBKuylHQR/ZOfer97UGtDkcjIB5dmsmpfb9Ibyf8wdpAbMt50xP+NsBYGtAiiEtnmdYsdFbCPH6n9/SGPRJMJtqZJCB1bYuScKB2fXn+Gfqhhl2+OfHy5q8G6Ir9Lkbh95Ke9bFUgjKnFyL4lnXDUs80guZSQ2eZi21c4o3UYQfdIunzj3VSgLGmRacV6OEtE45hSGcfZ5nLXgojsZp6fb3Td9dfIbUUnAFeGhCBY0gPCCO5hBdLAGVJbLkwXm2TSW4ILZBO9U0ykbNB8wFzGpdbkATYtOrGvbtd0njG3TAnCBR+k= + - secure: F+xT9kf1wSMwt4dCbN7qf7SicJSlYYW2Sl/WlrhA3STXUV3md+i/THYTi2cBZdoF63reZDMGa84szGw7X+y6V9Pr5zUrbEfAAi8FtvGG0wgw89Eksd8XcCOM3/rDKkavbVAGChL5Q8PwugA8U0Tarzbc+RSFpYgLPh74qsk3OECSavMOXRd9UPGqA52VAJyU1qkwc2PD641R4oDvUQdla/xKhKYUm4LCojZRtEEeZQLDykxXrVITGtA7LO8LzHvqJdt1A4GsykzfbKTAAGKi9BbFMt/F5ZCa5fIGewdjpjxbdDsCMdvASmhlpP2BiH6wuV9CyyK4EGeAQczboR5AamhYoIcxA4VgOrGWkdHCWMBDVz10JDLfAthsOVA57aSHFO255jJAq4S0xpXqPpshDJJYZbH+33PQKvCzwpw03KhNEReykmuOk03/1NpXqMwr+Zd/5wo/243ToTLvbtBvZBqLM6aRBUntxnencOv7pWljRzfhPiZrNG1nwTxQuGJwgY8+xYZYXNlyRObMdmiDMI2xDkaXr168Nx/wVci+0lnj4YD9Xlyqa1+MxC9W7GC8l3x2PbvVZ/XiOgWimDZMz9fKc/QnVXy/Mv9dM+kjin02DKswk4tlxhZ8mheBhAH7ejHCVtdAwZxNRXK+uf/MnDIjpMJ81Cbjfh/+KJ8PV1U= From bcc6f16053675c28bb6ae45216542e8a8f28b1e1 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Tue, 15 Sep 2015 10:11:51 -0700 Subject: [PATCH 022/105] switched to concurrent maps --- .../aeron/AeronClientDuplexConnection.java | 3 +- .../aeron/CompletableSubscription.java | 3 +- .../aeron/ReactiveSocketAeronServer.java | 82 ++++++---- .../aeron/ReactivesocketAeronClient.java | 24 ++- .../aeron/ReactiveSocketAeronTest.java | 143 ++++++++++-------- 5 files changed, 149 insertions(+), 106 deletions(-) diff --git a/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java index 8db90f1e4..dad9fec59 100644 --- a/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java @@ -52,9 +52,8 @@ public void addOutput(Publisher o, Completable callback) { @Override public void close() { - subscriber.onComplete(); - publication.close(); handleClose.call(); + this.publication.close(); } } diff --git a/src/main/java/io/reactivesocket/aeron/CompletableSubscription.java b/src/main/java/io/reactivesocket/aeron/CompletableSubscription.java index 4711fa524..bf6dbd924 100644 --- a/src/main/java/io/reactivesocket/aeron/CompletableSubscription.java +++ b/src/main/java/io/reactivesocket/aeron/CompletableSubscription.java @@ -7,7 +7,6 @@ import uk.co.real_logic.aeron.Publication; import uk.co.real_logic.aeron.logbuffer.BufferClaim; import uk.co.real_logic.agrona.BitUtil; -import uk.co.real_logic.agrona.LangUtil; import uk.co.real_logic.agrona.MutableDirectBuffer; import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; @@ -115,7 +114,7 @@ void closeQuietly(AutoCloseable closeable) { try { closeable.close(); } catch (Exception e) { - LangUtil.rethrowUnchecked(e); + // ignore } } } diff --git a/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java index 3454c356d..066ad65e9 100644 --- a/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java @@ -1,11 +1,9 @@ package io.reactivesocket.aeron; import io.reactivesocket.ConnectionSetupHandler; -import io.reactivesocket.ConnectionSetupPayload; import io.reactivesocket.Frame; +import io.reactivesocket.LeaseGovernor; import io.reactivesocket.ReactiveSocket; -import io.reactivesocket.RequestHandler; -import io.reactivesocket.exceptions.SetupException; import org.reactivestreams.Subscriber; import rx.Scheduler; import rx.schedulers.Schedulers; @@ -17,22 +15,21 @@ import uk.co.real_logic.aeron.logbuffer.Header; import uk.co.real_logic.agrona.BitUtil; import uk.co.real_logic.agrona.DirectBuffer; -import uk.co.real_logic.agrona.collections.Int2ObjectHashMap; -import uk.co.real_logic.agrona.collections.Long2ObjectHashMap; import java.nio.ByteBuffer; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static io.reactivesocket.aeron.Constants.CLIENT_STREAM_ID; import static io.reactivesocket.aeron.Constants.SERVER_STREAM_ID; public class ReactiveSocketAeronServer implements AutoCloseable, Loggable { - private final Aeron aeron; private final int port; - private volatile Int2ObjectHashMap connections; + private final ConcurrentHashMap connections; private final Scheduler.Worker worker; @@ -40,25 +37,31 @@ public class ReactiveSocketAeronServer implements AutoCloseable, Loggable { private volatile boolean running = true; - private final RequestHandler requestHandler; + private final ConnectionSetupHandler connectionSetupHandler; + + private final LeaseGovernor leaseGovernor; - private Long2ObjectHashMap sockets; + private final ConcurrentHashMap sockets; - private ReactiveSocketAeronServer(String host, int port, RequestHandler requestHandler) { + private final CountDownLatch shutdownLatch; + + private ReactiveSocketAeronServer(String host, int port, ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { this.port = port; - this.connections = new Int2ObjectHashMap<>(); - this.requestHandler = requestHandler; - this.sockets = new Long2ObjectHashMap<>(); + this.connections = new ConcurrentHashMap<>(); + this.connectionSetupHandler = connectionSetupHandler; + this.leaseGovernor = leaseGovernor; + this.sockets = new ConcurrentHashMap<>(); + this.shutdownLatch = new CountDownLatch(1); final Aeron.Context ctx = new Aeron.Context(); ctx.newImageHandler(this::newImageHandler); - ctx.errorHandler(t -> { - t.printStackTrace(); - }); + ctx.errorHandler(t -> error(t.getMessage(), t)); aeron = Aeron.connect(ctx); - subscription = aeron.addSubscription("udp://" + host + ":" + port, SERVER_STREAM_ID); + final String serverChannel = "udp://" + host + ":" + port; + info("Start new ReactiveSocketAeronServer on channel {}", serverChannel); + subscription = aeron.addSubscription(serverChannel, SERVER_STREAM_ID); final FragmentAssembler fragmentAssembler = new FragmentAssembler(this::fragmentHandler); @@ -67,20 +70,29 @@ private ReactiveSocketAeronServer(String host, int port, RequestHandler requestH } - public static ReactiveSocketAeronServer create(String host, int port, RequestHandler requestHandler) { - return new ReactiveSocketAeronServer(host, port, requestHandler); + public static ReactiveSocketAeronServer create(String host, int port, ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { + return new ReactiveSocketAeronServer(host, port, connectionSetupHandler, leaseGovernor); + } + + public static ReactiveSocketAeronServer create(int port, ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { + return create("127.0.0.1", port, connectionSetupHandler, leaseGovernor); } - public static ReactiveSocketAeronServer create(int port, RequestHandler requestHandler) { - return create("127.0.0.1", port, requestHandler); + public static ReactiveSocketAeronServer create(ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { + return create(39790, connectionSetupHandler, leaseGovernor); } - public static ReactiveSocketAeronServer create(RequestHandler requestHandler) { - return create(39790, requestHandler); + public static ReactiveSocketAeronServer create(String host, int port, ConnectionSetupHandler connectionSetupHandler) { + return new ReactiveSocketAeronServer(host, port, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); } + public static ReactiveSocketAeronServer create(int port, ConnectionSetupHandler connectionSetupHandler) { + return create("127.0.0.1", port, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); + } - volatile long start = System.currentTimeMillis(); + public static ReactiveSocketAeronServer create(ConnectionSetupHandler connectionSetupHandler) { + return create(39790, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); + } void poll(FragmentAssembler fragmentAssembler) { if (running) { @@ -92,7 +104,9 @@ void poll(FragmentAssembler fragmentAssembler) { t.printStackTrace(); } }); - } + } else { + shutdownLatch.countDown(); + } } void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) { @@ -139,17 +153,17 @@ void newImageHandler(Image image, String channel, int streamId, int sessionId, l return new AeronServerDuplexConnection(publication); }); debug("Accepting ReactiveSocket connection"); - ReactiveSocket socket = ReactiveSocket.fromServerConnection(connection, new ConnectionSetupHandler() { - @Override - public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { - return requestHandler; - } - }); + ReactiveSocket socket = ReactiveSocket.fromServerConnection( + connection, + connectionSetupHandler, + leaseGovernor, + error -> error(error.getMessage(), error)); sockets.put(sessionId, socket); socket.start(); + } else { debug("Unsupported stream id {}", streamId); } @@ -158,6 +172,9 @@ public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupExc @Override public void close() throws Exception { running = false; + + shutdownLatch.await(30, TimeUnit.SECONDS); + worker.unsubscribe(); aeron.close(); @@ -165,6 +182,9 @@ public void close() throws Exception { connection.close(); } + for (ReactiveSocket reactiveSocket : sockets.values()) { + reactiveSocket.close(); + } } } diff --git a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java index 5a088f0fe..f725793c1 100644 --- a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java @@ -15,10 +15,10 @@ import uk.co.real_logic.aeron.logbuffer.Header; import uk.co.real_logic.agrona.BitUtil; import uk.co.real_logic.agrona.DirectBuffer; -import uk.co.real_logic.agrona.collections.Int2ObjectHashMap; import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; import java.nio.ByteBuffer; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -30,11 +30,11 @@ * Created by rroeser on 8/13/15. */ public class ReactivesocketAeronClient implements Loggable { - private static final Int2ObjectHashMap subscriptions = new Int2ObjectHashMap<>(); + private static final ConcurrentHashMap subscriptions = new ConcurrentHashMap<>(); - private static final Int2ObjectHashMap connections = new Int2ObjectHashMap<>(); + private static final ConcurrentHashMap connections = new ConcurrentHashMap<>(); - private static final Int2ObjectHashMap establishConnectionLatches = new Int2ObjectHashMap<>(); + private static final ConcurrentHashMap establishConnectionLatches = new ConcurrentHashMap<>(); private ReactiveSocket reactiveSocket; @@ -42,7 +42,9 @@ public class ReactivesocketAeronClient implements Loggable { private final Publication publication; - private volatile boolean running = true; + private volatile static boolean running = true; + + private static final CountDownLatch shutdownLatch = new CountDownLatch(1); private final int port; @@ -50,6 +52,14 @@ public class ReactivesocketAeronClient implements Loggable { Runtime .getRuntime() .addShutdownHook(new Thread(() -> { + running = false; + + try { + shutdownLatch.await(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + for (Subscription subscription : subscriptions.values()) { subscription.close(); } @@ -145,8 +155,10 @@ void poll(FragmentAssembler fragmentAssembler, Subscription subscription, Schedu subscription.poll(fragmentAssembler, Integer.MAX_VALUE); poll(fragmentAssembler, subscription, worker); } catch (Throwable t) { - t.printStackTrace(); + error(t.getMessage(), t); } + } else { + shutdownLatch.countDown(); } }); } diff --git a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java index 1b513bc75..ab686f3be 100644 --- a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java @@ -1,8 +1,11 @@ package io.reactivesocket.aeron; +import io.reactivesocket.ConnectionSetupHandler; +import io.reactivesocket.ConnectionSetupPayload; import io.reactivesocket.Frame; import io.reactivesocket.Payload; import io.reactivesocket.RequestHandler; +import io.reactivesocket.exceptions.SetupException; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; @@ -23,47 +26,52 @@ public class ReactiveSocketAeronTest { @BeforeClass public static void init() { final MediaDriver.Context context = new MediaDriver.Context(); - context.dirsDeleteOnStart(); + context.dirsDeleteOnStart(true); final MediaDriver mediaDriver = MediaDriver.launch(context); } - @Test(timeout = 70000) + @Test(timeout = 60000) public void testRequestReponse() throws Exception { - ReactiveSocketAeronServer.create(new RequestHandler() { - Frame frame = Frame.from(ByteBuffer.allocate(1)); - - @Override - public Publisher handleRequestResponse(Payload payload) { - String request = TestUtil.byteToString(payload.getData()); - //System.out.println("Server got => " + request); - Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong", null)); - return RxReactiveStreams.toPublisher(pong); - } - - @Override - public Publisher handleChannel(Payload initialPayload, Publisher payloads) { - return null; - } - - @Override - public Publisher handleRequestStream(Payload payload) { - return null; - } - - @Override - public Publisher handleSubscription(Payload payload) { - return null; - } - - @Override - public Publisher handleFireAndForget(Payload payload) { - return null; - } - + ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { @Override - public Publisher handleMetadataPush(Payload payload) { - return null; + public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + return new RequestHandler() { + Frame frame = Frame.from(ByteBuffer.allocate(1)); + + @Override + public Publisher handleRequestResponse(Payload payload) { + String request = TestUtil.byteToString(payload.getData()); + //System.out.println("Server got => " + request); + Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong", null)); + return RxReactiveStreams.toPublisher(pong); + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher payloads) { + return null; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + return null; + } + + @Override + public Publisher handleSubscription(Payload payload) { + return null; + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return null; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + }; } }); @@ -107,37 +115,42 @@ public void sendLargeMessage() throws Exception { byte[] b = new byte[1_000_000]; random.nextBytes(b); - ReactiveSocketAeronServer.create(new RequestHandler() { - @Override - public Publisher handleRequestResponse(Payload payload) { - System.out.println("Server got => " + b.length); - Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong", null)); - return RxReactiveStreams.toPublisher(pong); - } - - @Override - public Publisher handleChannel(Payload initialPayload, Publisher payloads) { - return null; - } - - @Override - public Publisher handleRequestStream(Payload payload) { - return null; - } - - @Override - public Publisher handleSubscription(Payload payload) { - return null; - } - - @Override - public Publisher handleFireAndForget(Payload payload) { - return null; - } - + ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { @Override - public Publisher handleMetadataPush(Payload payload) { - return null; + public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + return new RequestHandler() { + @Override + public Publisher handleRequestResponse(Payload payload) { + System.out.println("Server got => " + b.length); + Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong", null)); + return RxReactiveStreams.toPublisher(pong); + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher payloads) { + return null; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + return null; + } + + @Override + public Publisher handleSubscription(Payload payload) { + return null; + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return null; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + }; } }); From dc955aeaf28ff319e6fcf7f41c1065d97b2303b4 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Tue, 15 Sep 2015 22:43:09 -0700 Subject: [PATCH 023/105] commit, saving for later --- .../aeron/AeronClientDuplexConnection.java | 80 ++++--- .../aeron/AeronServerDuplexConnection.java | 25 +- .../aeron/CompletableSubscription.java | 1 + .../io/reactivesocket/aeron/FrameHolder.java | 22 ++ .../aeron/ReactiveSocketAeronServer.java | 34 ++- .../aeron/ReactivesocketAeronClient.java | 218 +++++++++++++++--- .../exceptions/SetupException.java | 23 ++ .../aeron/ReactiveSocketAeronTest.java | 91 ++++++++ 8 files changed, 421 insertions(+), 73 deletions(-) create mode 100644 src/main/java/io/reactivesocket/aeron/FrameHolder.java create mode 100644 src/main/java/io/reactivesocket/exceptions/SetupException.java diff --git a/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java index dad9fec59..f5c9b93a0 100644 --- a/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java @@ -3,57 +3,83 @@ import io.reactivesocket.Completable; import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; +import io.reactivesocket.FrameType; +import io.reactivesocket.observable.Observable; +import io.reactivesocket.observable.Observer; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; -import rx.functions.Action0; import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue; + +import java.util.concurrent.atomic.AtomicBoolean; public class AeronClientDuplexConnection implements DuplexConnection, AutoCloseable { - private Publication publication; - private Subscriber subscriber; - private Publisher publisher; - private Action0 handleClose; + private Publication publication; + private Observer observer; + private Observable observable; + private ManyToOneConcurrentArrayQueue framesSendQueue; + private AtomicBoolean initialized; - public AeronClientDuplexConnection(Publication publication, Action0 handleClose) { + public AeronClientDuplexConnection(Publication publication, ManyToOneConcurrentArrayQueue framesSendQueue, AtomicBoolean initialized) { + System.out.println("publication => " + publication.toString()); this.publication = publication; - this.publisher = (Subscriber s) -> { - subscriber = s; - s.onSubscribe(new Subscription() { - @Override - public void request(long n) { - - } - - @Override - public void cancel() { - - } - }); + this.framesSendQueue = framesSendQueue; + this.observable = new Observable() { + @Override + public void subscribe(Observer o) { + observer = o; + } }; - this.handleClose = handleClose; + this.initialized = initialized; } - public Subscriber getSubscriber() { - return subscriber; + public Observer getSubscriber() { + return observer; } - public Publisher getInput() { - return publisher; + public Observable getInput() { + return observable; } @Override public void addOutput(Publisher o, Completable callback) { - o.subscribe(new CompletableSubscription(publication, callback, this)); + o.subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(1); + } + + @Override + public void onNext(Frame frame) { + System.out.println("STARTED => " + initialized.get()); + if (frame.getType() != FrameType.SETUP) { + System.out.println("dropping frame that isn't setup => " + frame.toString()); + } else { + System.out.println("#### #### #### FOUND THE SETUP FRAME => " + frame.toString()); + } + + //while (!framesSendQueue.offer(new FrameHolder(frame, publication))) {} + } + + @Override + public void onError(Throwable t) { + callback.error(t); + } + + @Override + public void onComplete() { + callback.success(); + } + }); } @Override public void close() { - handleClose.call(); - this.publication.close(); + observer.onComplete(); } } diff --git a/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java index b6699b2ce..3cd883d52 100644 --- a/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java @@ -3,8 +3,9 @@ import io.reactivesocket.Completable; import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; +import io.reactivesocket.observable.Observable; +import io.reactivesocket.observable.Observer; import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; import uk.co.real_logic.aeron.Publication; import uk.co.real_logic.aeron.logbuffer.BufferClaim; import uk.co.real_logic.agrona.BitUtil; @@ -17,22 +18,26 @@ public class AeronServerDuplexConnection implements DuplexConnection, AutoClosea private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); private Publication publication; - private Subscriber subscriber; - private Publisher publisher; + private Observer observer; + private Observable observable; public AeronServerDuplexConnection( Publication publication) { this.publication = publication; - this.publisher = (Subscriber s) -> subscriber = s; + this.observable = new Observable() { + @Override + public void subscribe(Observer o) { + observer = o; + } + }; } - public Subscriber getSubscriber() { - return subscriber; + public Observer getSubscriber() { + return observer; } - @Override - public Publisher getInput() { - return publisher; + public Observable getInput() { + return observable; } @Override @@ -72,7 +77,7 @@ void ackEstablishConnection(int ackSessionId) { @Override public void close() { - subscriber.onComplete(); + observer.onComplete(); publication.close(); } } diff --git a/src/main/java/io/reactivesocket/aeron/CompletableSubscription.java b/src/main/java/io/reactivesocket/aeron/CompletableSubscription.java index bf6dbd924..c39d4bb91 100644 --- a/src/main/java/io/reactivesocket/aeron/CompletableSubscription.java +++ b/src/main/java/io/reactivesocket/aeron/CompletableSubscription.java @@ -46,6 +46,7 @@ public void onSubscribe(Subscription s) { @Override public void onNext(Frame frame) { + System.out.println("SERVER => " + frame.toString()); final ByteBuffer byteBuffer = frame.getByteBuffer(); final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; diff --git a/src/main/java/io/reactivesocket/aeron/FrameHolder.java b/src/main/java/io/reactivesocket/aeron/FrameHolder.java new file mode 100644 index 000000000..251a0700f --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/FrameHolder.java @@ -0,0 +1,22 @@ +package io.reactivesocket.aeron; + +import io.reactivesocket.Frame; +import uk.co.real_logic.aeron.Publication; + +public class FrameHolder { + private Publication publication; + private Frame frame; + + public FrameHolder(Frame frame, Publication publication) { + this.frame = frame; + this.publication = publication; + } + + public Publication getPublication() { + return publication; + } + + public Frame getFrame() { + return frame; + } +} diff --git a/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java index 066ad65e9..8d7c9c268 100644 --- a/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java @@ -1,10 +1,11 @@ package io.reactivesocket.aeron; +import io.reactivesocket.Completable; import io.reactivesocket.ConnectionSetupHandler; import io.reactivesocket.Frame; import io.reactivesocket.LeaseGovernor; import io.reactivesocket.ReactiveSocket; -import org.reactivestreams.Subscriber; +import io.reactivesocket.observable.Observer; import rx.Scheduler; import rx.schedulers.Schedulers; import uk.co.real_logic.aeron.Aeron; @@ -118,7 +119,7 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) if (MessageType.FRAME == type) { AeronServerDuplexConnection connection = connections.get(sessionId); if (connection != null) { - final Subscriber subscriber = connection.getSubscriber(); + Observer subscriber = connection.getSubscriber(); ByteBuffer bytes = ByteBuffer.allocate(length); buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); final Frame frame = Frame.from(bytes); @@ -161,9 +162,36 @@ void newImageHandler(Image image, String channel, int streamId, int sessionId, l sockets.put(sessionId, socket); - socket.start(); + CountDownLatch latch = new CountDownLatch(1); + socket.start(new Completable() { + @Override + public void success() { + latch.countDown(); + System.out.println("SERVER --- SUCCESS !!!!!!!"); + } + + @Override + public void error(Throwable e) { + latch.countDown(); + System.out.println("SERVER --- NO !!!!!!!"); + } + }); + + try { + latch.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println("SERVER --- SUCCESS !!!!!!!"); +/* + + try { + waitForStart.await(30, TimeUnit.SECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + }*/ } else { debug("Unsupported stream id {}", streamId); } diff --git a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java index f725793c1..40ea46080 100644 --- a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java @@ -1,26 +1,33 @@ package io.reactivesocket.aeron; +import io.reactivesocket.Completable; import io.reactivesocket.ConnectionSetupPayload; import io.reactivesocket.Frame; import io.reactivesocket.Payload; import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.observable.Observer; import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; import rx.Scheduler; import rx.schedulers.Schedulers; import uk.co.real_logic.aeron.Aeron; import uk.co.real_logic.aeron.FragmentAssembler; import uk.co.real_logic.aeron.Publication; import uk.co.real_logic.aeron.Subscription; +import uk.co.real_logic.aeron.logbuffer.BufferClaim; import uk.co.real_logic.aeron.logbuffer.Header; import uk.co.real_logic.agrona.BitUtil; import uk.co.real_logic.agrona.DirectBuffer; +import uk.co.real_logic.agrona.MutableDirectBuffer; +import uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue; import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collection; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import static io.reactivesocket.aeron.Constants.CLIENT_STREAM_ID; import static io.reactivesocket.aeron.Constants.EMTPY; @@ -29,25 +36,33 @@ /** * Created by rroeser on 8/13/15. */ -public class ReactivesocketAeronClient implements Loggable { - private static final ConcurrentHashMap subscriptions = new ConcurrentHashMap<>(); +public class ReactivesocketAeronClient implements Loggable, AutoCloseable { + static final ConcurrentHashMap subscriptions = new ConcurrentHashMap<>(); - private static final ConcurrentHashMap connections = new ConcurrentHashMap<>(); + static final ConcurrentHashMap connections = new ConcurrentHashMap<>(); - private static final ConcurrentHashMap establishConnectionLatches = new ConcurrentHashMap<>(); + static final ConcurrentHashMap establishConnectionLatches = new ConcurrentHashMap<>(); + + static final ConcurrentHashMap publications = new ConcurrentHashMap<>(); private ReactiveSocket reactiveSocket; private final Aeron aeron; - private final Publication publication; - private volatile static boolean running = true; + volatile int sessionId; + + volatile int serverSessionId; + private static final CountDownLatch shutdownLatch = new CountDownLatch(1); private final int port; + private static final ManyToOneConcurrentArrayQueue framesSendQueue; + + private static int mtuLength; + static { Runtime .getRuntime() @@ -68,8 +83,14 @@ public class ReactivesocketAeronClient implements Loggable { connection.close(); } })); + + int queueSize = Integer.getInteger("framesSendQueueSize", 16); + framesSendQueue = new ManyToOneConcurrentArrayQueue<>(queueSize); + mtuLength = Integer.getInteger("aeron.mtu.length", 4096); } + private static volatile boolean pollingStarted = false; + private ReactivesocketAeronClient(String host, String server, int port) { this.port = port; @@ -84,22 +105,32 @@ private ReactivesocketAeronClient(String host, String server, int port) { final String subscriptionChannel = "udp://" + server + ":" + port; System.out.println("Creating a publication to channel => " + channel); - publication = aeron.addPublication(channel, SERVER_STREAM_ID); - final int sessionId = publication.sessionId(); + Publication publication = aeron.addPublication(channel, SERVER_STREAM_ID); + publications.putIfAbsent(publication.sessionId(), publication); + System.out.println("Creating publication => " + publication.toString()); + sessionId = publication.sessionId(); System.out.println("Created a publication for sessionId => " + sessionId); + Subscription subscription = subscriptions.get(port); - subscriptions.computeIfAbsent(port, (_p) -> { - System.out.println("Creating a subscription to channel => " + subscriptionChannel); - Subscription subscription = aeron.addSubscription(subscriptionChannel, CLIENT_STREAM_ID); - System.out.println("Subscription created to channel => " + subscriptionChannel); + if (subscription == null) { + synchronized (subscriptions) { + System.out.println("Creating a subscription to channel => " + subscriptionChannel); + subscription = aeron.addSubscription(subscriptionChannel, CLIENT_STREAM_ID); + subscriptions.putIfAbsent(port, subscription); + System.out.println("Subscription created to channel => " + subscriptionChannel); - final FragmentAssembler fragmentAssembler = new FragmentAssembler(this::fragmentHandler); + if (!pollingStarted) { + System.out.println("Polling hasn't started yet - starting polling"); - poll(fragmentAssembler, subscription, Schedulers.computation().createWorker()); + final FragmentAssembler fragmentAssembler = new FragmentAssembler(this::fragmentHandler); - return subscription; - }); + poll(fragmentAssembler, Schedulers.newThread().createWorker()); + + pollingStarted = true; + } + } + } establishConnection(publication, sessionId); @@ -110,7 +141,7 @@ public static ReactivesocketAeronClient create(String host, String server, int p } public static ReactivesocketAeronClient create(String host, String server) { - return new ReactivesocketAeronClient(host, server, 39790); + return create(host, server, 39790); } void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) { @@ -118,51 +149,147 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) MessageType messageType = MessageType.from(messageTypeInt); if (messageType == MessageType.FRAME) { final AeronClientDuplexConnection connection = connections.get(header.sessionId()); - final Subscriber subscriber = connection.getSubscriber(); + Observer subscriber = connection.getSubscriber(); final ByteBuffer bytes = ByteBuffer.allocate(length); buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); final Frame frame = Frame.from(bytes); subscriber.onNext(frame); } else if (messageType == MessageType.ESTABLISH_CONNECTION_RESPONSE) { - int ackSessionId = buffer.getInt(offset + BitUtil.SIZE_OF_INT); + final int ackSessionId = buffer.getInt(offset + BitUtil.SIZE_OF_INT); + Publication publication = publications.get(ackSessionId); System.out.println(String.format("Received establish connection ack for session id => %d", ackSessionId)); - final int headerSessionId = header.sessionId(); + System.out.println("ESTABLISH_CONNECTION_RESPONSE publication => " + publication.toString()); + serverSessionId = header.sessionId(); final AeronClientDuplexConnection connection = connections - .computeIfAbsent(headerSessionId, (_p) -> - new AeronClientDuplexConnection(publication, () -> connections.remove(headerSessionId))); + .computeIfAbsent(serverSessionId, (_p) -> + new AeronClientDuplexConnection(publication, framesSendQueue, initialized)); reactiveSocket = ReactiveSocket.fromClientConnection( connection, ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS), err -> err.printStackTrace()); - reactiveSocket.start(); + reactiveSocket.start(new Completable() { + @Override + public void success() { + initialized.set(true); + System.out.println("SUCCESSSSSS!!!!!!"); + } + + @Override + public void error(Throwable e) { + initialized.set(true); + System.out.println("NOOOOOO!!!!!!"); + } + }); + + + info("ReactiveSocket connected to Aeron session => " + ackSessionId); + CountDownLatch latch = establishConnectionLatches.remove(ackSessionId); - System.out.println("ReactiveSocket connected to Aeron session => " + ackSessionId); - CountDownLatch latch = establishConnectionLatches.get(ackSessionId); latch.countDown(); + System.out.println("HERE #*#*#*#*#**#*#*#*#*#*"); } else { - System.out.println("Unknown message type => " + messageTypeInt); + debug("Unknown message type => " + messageTypeInt); } } - void poll(FragmentAssembler fragmentAssembler, Subscription subscription, Scheduler.Worker worker) { + AtomicBoolean initialized = new AtomicBoolean(false); + + void poll(FragmentAssembler fragmentAssembler, Scheduler.Worker worker) { worker.schedule(() -> { - if (running) { + while (running) { try { - subscription.poll(fragmentAssembler, Integer.MAX_VALUE); - poll(fragmentAssembler, subscription, worker); + + // Pool subscriptions + Collection subscriptions = ReactivesocketAeronClient.subscriptions.values(); + if (subscriptions != null) { + subscriptions.forEach(subscription -> + subscription.poll(fragmentAssembler, Integer.MAX_VALUE) + ); + } + + ArrayList frames = new ArrayList<>(128); + + // Drain send queue + framesSendQueue + .drainTo(frames, 128); + + frames.forEach(fh -> { + Frame frame = fh.getFrame(); + System.out.println("QUEUE FRAME => " + frame.toString()); + + final ByteBuffer byteBuffer = frame.getByteBuffer(); + final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; + + // If the length is less the MTU size send the message using tryClaim which does not fragment the message + // If the message is larger the the MTU size send it using offer. + if (length < mtuLength) { + tryClaim(fh.getPublication(), byteBuffer, length); + } else { + offer(fh.getPublication(), byteBuffer, length); + } + + frame.release(); + }); + + poll(fragmentAssembler, worker); } catch (Throwable t) { + t.printStackTrace(); error(t.getMessage(), t); } - } else { - shutdownLatch.countDown(); } + + shutdownLatch.countDown(); + }); } + private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); + + private static final ThreadLocal unsafeBuffers = ThreadLocal.withInitial(() -> new UnsafeBuffer(Constants.EMTPY)); + + void offer(Publication publication, ByteBuffer byteBuffer, int length) { + final byte[] bytes = new byte[length]; + final UnsafeBuffer unsafeBuffer = unsafeBuffers.get(); + unsafeBuffer.wrap(bytes); + unsafeBuffer.putInt(0, MessageType.FRAME.getEncodedType()); + unsafeBuffer.putBytes(BitUtil.SIZE_OF_INT, byteBuffer, byteBuffer.capacity()); + do { + final long offer = publication.offer(unsafeBuffer); + if (offer >= 0) { + break; + } else if (Publication.NOT_CONNECTED == offer) { + throw new RuntimeException("not connected"); + } + } while(true); + + } + + void tryClaim(Publication publication, ByteBuffer byteBuffer, int length) { + final BufferClaim bufferClaim = bufferClaims.get(); + do { + final long offer = publication.tryClaim(length, bufferClaim); + if (offer >= 0) { + try { + final MutableDirectBuffer buffer = bufferClaim.buffer(); + final int offset = bufferClaim.offset(); + buffer.putInt(offset, MessageType.FRAME.getEncodedType()); + buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, 0, byteBuffer.capacity()); + } finally { + bufferClaim.commit(); + } + + break; + } else if (Publication.NOT_CONNECTED == offer) { + throw new RuntimeException("not connected"); + } + } while(true); + } + + /** * Establishes a connection between the client and server. Waits for 30 seconds before throwing a exception. */ @@ -217,4 +344,29 @@ public Publisher requestSubscription(Payload payload) { return reactiveSocket.requestSubscription(payload); } + @Override + public void close() throws Exception { + // First clean up the different maps + // Remove the AeronDuplexConnection from the connections map + AeronClientDuplexConnection connection = connections.remove(serverSessionId); + + // This should already be removed but remove it just in case to be safe + establishConnectionLatches.remove(sessionId); + + // Close the different connections + closeQuietly(connection); + closeQuietly(reactiveSocket); + System.out.println("closing publication => " + publications.get(sessionId).toString()); + Publication publication = publications.remove(sessionId); + closeQuietly(publication); + + } + + private void closeQuietly(AutoCloseable closeable) { + try { + closeable.close(); + } catch (Throwable t) { + debug(t.getMessage(), t); + } + } } diff --git a/src/main/java/io/reactivesocket/exceptions/SetupException.java b/src/main/java/io/reactivesocket/exceptions/SetupException.java new file mode 100644 index 000000000..bad9572c2 --- /dev/null +++ b/src/main/java/io/reactivesocket/exceptions/SetupException.java @@ -0,0 +1,23 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.exceptions; + +public class SetupException extends Throwable { + public SetupException(String message) { + super(message); + } + +} diff --git a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java index ab686f3be..582fbe734 100644 --- a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java @@ -6,6 +6,7 @@ import io.reactivesocket.Payload; import io.reactivesocket.RequestHandler; import io.reactivesocket.exceptions.SetupException; +import junit.framework.Assert; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; @@ -197,4 +198,94 @@ public void onNext(Payload s) { latch.await(); } + @Test + public void testReconnect() throws Exception { + ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { + @Override + public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + return new RequestHandler() { + Frame frame = Frame.from(ByteBuffer.allocate(1)); + + @Override + public Publisher handleRequestResponse(Payload payload) { + String request = TestUtil.byteToString(payload.getData()); + //System.out.println("Server got => " + request); + Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong", null)); + return RxReactiveStreams.toPublisher(pong); + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher payloads) { + return null; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + return null; + } + + @Override + public Publisher handleSubscription(Payload payload) { + return null; + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return null; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + }; + } + }); + + CountDownLatch latch = new CountDownLatch(1); + + ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost", "localhost"); + + Observable + .range(1, 1) + .flatMap(i -> { + //System.out.println("pinging => " + i); + Payload payload = TestUtil.utf8EncodedPayload("ping =>" + i, null); + return RxReactiveStreams.toObservable(client.requestResponse(payload)); + } + ) + .subscribe(new rx.Subscriber() { + @Override + public void onCompleted() { + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + } + + @Override + public void onNext(Payload s) { + //System.out.println(s + " countdown => " + latch.getCount()); + latch.countDown(); + } + }); + + latch.await(); + + int serverSessionId = client.serverSessionId; + int sessionId = client.sessionId; + + Assert.assertNotNull(client.connections.get(serverSessionId)); + + client.close(); + + Assert.assertNull(client.connections.get(serverSessionId)); + Assert.assertNull(client.establishConnectionLatches.get(sessionId)); + + ReactivesocketAeronClient newConnection = ReactivesocketAeronClient.create("localhost", "localhost"); + + } + + } From c7c4b86925144edaa1bab35bdc87cbde9a131239 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Wed, 16 Sep 2015 13:08:17 -0700 Subject: [PATCH 024/105] switched to use manytoonequeue instead of directly handling to aeron. added basic perf test --- build.gradle | 1 + src/main/java/io/reactivesocket/Frame.java | 551 ++++++++++++++++++ .../aeron/AeronClientDuplexConnection.java | 23 +- .../aeron/AeronServerDuplexConnection.java | 2 + .../aeron/CompletableSubscription.java | 1 - .../io/reactivesocket/aeron/Constants.java | 2 + .../reactivesocket/aeron/OperatorPublish.java | 107 ---- .../aeron/ReactiveSocketAeronServer.java | 23 +- .../aeron/ReactivesocketAeronClient.java | 202 +++---- .../aeron/jmh/RequestResponsePerf.java | 144 +++++ .../aeron/OperatorPublishTest.java | 97 --- .../aeron/ReactiveSocketAeronTest.java | 7 +- 12 files changed, 820 insertions(+), 340 deletions(-) create mode 100644 src/main/java/io/reactivesocket/Frame.java delete mode 100644 src/main/java/io/reactivesocket/aeron/OperatorPublish.java create mode 100644 src/perf/java/io/reactivesocket/aeron/jmh/RequestResponsePerf.java delete mode 100644 src/test/java/io/reactivesocket/aeron/OperatorPublishTest.java diff --git a/build.gradle b/build.gradle index f492f7407..dd8f6a5e2 100644 --- a/build.gradle +++ b/build.gradle @@ -21,6 +21,7 @@ dependencies { compile 'io.reactivesocket:reactivesocket:0.0.1-SNAPSHOT' compile 'uk.co.real-logic:Agrona:0.4.2' compile 'uk.co.real-logic:aeron-all:0.1.2' + compile 'com.goldmansachs:gs-collections:6.2.0' compile 'org.slf4j:slf4j-api:1.7.12' testCompile 'junit:junit-dep:4.10' testCompile 'org.mockito:mockito-core:1.8.5' diff --git a/src/main/java/io/reactivesocket/Frame.java b/src/main/java/io/reactivesocket/Frame.java new file mode 100644 index 000000000..0f32719e9 --- /dev/null +++ b/src/main/java/io/reactivesocket/Frame.java @@ -0,0 +1,551 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket; + +import io.reactivesocket.internal.*; +import uk.co.real_logic.agrona.DirectBuffer; +import uk.co.real_logic.agrona.MutableDirectBuffer; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; + +import static java.lang.System.getProperty; + +/** + * Represents a Frame sent over a {@link DuplexConnection}. + *

+ * This provides encoding, decoding and field accessors. + */ +public class Frame implements Payload +{ + public static final ByteBuffer NULL_BYTEBUFFER = FrameHeaderFlyweight.NULL_BYTEBUFFER; + + /* + * ThreadLocal handling in the pool itself. We don't have a per thread pool at this level. + */ + + private static final String FRAME_POOLER_CLASS_NAME = + getProperty("io.reactivesocket.FramePool", "io.reactivesocket.internal.RobertsFramePool"); + private static final FramePool POOL; + + static + { + FramePool tmpPool; + + try + { + tmpPool = (FramePool)Class.forName(FRAME_POOLER_CLASS_NAME).newInstance(); + } + catch (final Exception ex) + { + tmpPool = new UnpooledFrame(); + } + + POOL = tmpPool; + } + + // not final so we can reuse this object + private MutableDirectBuffer directBuffer; + private int offset = 0; + private int length = 0; + + private Frame(final MutableDirectBuffer directBuffer) + { + this.directBuffer = directBuffer; + } + + /** + * Return underlying {@link ByteBuffer} for frame + * + * @return underlying {@link ByteBuffer} for frame + */ + public ByteBuffer getByteBuffer() { + return directBuffer.byteBuffer(); + } + + /** + * Return {@link ByteBuffer} that is a {@link ByteBuffer#slice()} for the frame data + * + * If no data is present, the ByteBuffer will have 0 capacity. + * + * @return ByteBuffer containing the data + */ + public ByteBuffer getData() + { + return FrameHeaderFlyweight.sliceFrameData(directBuffer, offset, 0); + } + + /** + * Return {@link ByteBuffer} that is a {@link ByteBuffer#slice()} for the frame metadata + * + * If no metadata is present, the ByteBuffer will have 0 capacity. + * + * @return ByteBuffer containing the data + */ + public ByteBuffer getMetadata() + { + return FrameHeaderFlyweight.sliceFrameMetadata(directBuffer, offset, 0); + } + + /** + * Return frame stream identifier + * + * @return frame stream identifier + */ + public int getStreamId() { + return FrameHeaderFlyweight.streamId(directBuffer, offset); + } + + /** + * Return frame {@link FrameType} + * + * @return frame type + */ + public FrameType getType() { + return FrameHeaderFlyweight.frameType(directBuffer, offset); + } + + /** + * Return the offset in the buffer of the frame + * + * @return offset of frame within the buffer + */ + public int offset() + { + return offset; + } + + /** + * Return the encoded length of a frame or the frame length + * + * @return frame length + */ + public int length() + { + return length; + } + + /** + * Mutates this Frame to contain the given ByteBuffer + * + * @param byteBuffer to wrap + */ + public void wrap(final ByteBuffer byteBuffer, final int offset) + { + wrap(POOL.acquireMutableDirectBuffer(byteBuffer), offset); + } + + /** + * Mutates this Frame to contain the given MutableDirectBuffer + * + * @param directBuffer to wrap + */ + public void wrap(final MutableDirectBuffer directBuffer, final int offset) + { + this.directBuffer = directBuffer; + this.offset = offset; + } + + /** + * Acquire a free Frame backed by given ByteBuffer + * + * @param byteBuffer to wrap + * @return new {@link Frame} + */ + public static Frame from(final ByteBuffer byteBuffer) { + return POOL.acquireFrame(byteBuffer); + } + + /** + * Acquire a free Frame and back with the given {@link DirectBuffer} starting at offset for length bytes + * + * @param directBuffer to use as backing buffer + * @param offset of start of frame + * @param length of frame in bytes + * @return frame + */ + public static Frame from(final DirectBuffer directBuffer, final int offset, final int length) + { + final Frame frame = POOL.acquireFrame((MutableDirectBuffer)directBuffer); + frame.offset = offset; + frame.length = length; + + return frame; + } + + /** + * Construct a new Frame from the given {@link MutableDirectBuffer} + * + * NOTE: always allocates. Used for pooling. + * + * @param directBuffer to wrap + * @return new {@link Frame} + */ + public static Frame allocate(final MutableDirectBuffer directBuffer) + { + return new Frame(directBuffer); + } + + /** + * Release frame for re-use. + */ + public void release() + { + POOL.release(this.directBuffer); + POOL.release(this); + } + + /** + * Mutates this Frame to contain the given parameters. + * + * NOTE: acquires a new backing buffer and releases current backing buffer + * + * @param streamId to include in frame + * @param type to include in frame + * @param data to include in frame + */ + public void wrap(final int streamId, final FrameType type, final ByteBuffer data) + { + POOL.release(this.directBuffer); + + this.directBuffer = + POOL.acquireMutableDirectBuffer(FrameHeaderFlyweight.computeFrameHeaderLength(type, 0, data.capacity())); + + this.length = FrameHeaderFlyweight.encode(this.directBuffer, offset, streamId, type, NULL_BYTEBUFFER, data); + } + + /* TODO: + * + * fromRequest(type, id, payload) + * fromKeepalive(ByteBuffer data) + * + */ + + // SETUP specific getters + public static class Setup + { + public static Frame from( + int flags, + int keepaliveInterval, + int maxLifetime, + String metadataMimeType, + String dataMimeType, + Payload payload) + { + final ByteBuffer metadata = payload.getMetadata(); + final ByteBuffer data = payload.getData(); + + final Frame frame = + POOL.acquireFrame(SetupFrameFlyweight.computeFrameLength(metadataMimeType, dataMimeType, metadata.capacity(), data.capacity())); + + frame.length = SetupFrameFlyweight.encode( + frame.directBuffer, frame.offset, flags, keepaliveInterval, maxLifetime, metadataMimeType, dataMimeType, metadata, data); + return frame; + } + + public static int getFlags(final Frame frame) + { + ensureFrameType(FrameType.SETUP, frame); + final int flags = FrameHeaderFlyweight.flags(frame.directBuffer, frame.offset); + + return flags & (SetupFrameFlyweight.FLAGS_WILL_HONOR_LEASE | SetupFrameFlyweight.FLAGS_STRICT_INTERPRETATION); + } + + public static int version(final Frame frame) + { + ensureFrameType(FrameType.SETUP, frame); + return SetupFrameFlyweight.version(frame.directBuffer, frame.offset); + } + + public static int keepaliveInterval(final Frame frame) + { + ensureFrameType(FrameType.SETUP, frame); + return SetupFrameFlyweight.keepaliveInterval(frame.directBuffer, frame.offset); + } + + public static int maxLifetime(final Frame frame) + { + ensureFrameType(FrameType.SETUP, frame); + return SetupFrameFlyweight.maxLifetime(frame.directBuffer, frame.offset); + } + + public static String metadataMimeType(final Frame frame) + { + ensureFrameType(FrameType.SETUP, frame); + return SetupFrameFlyweight.metadataMimeType(frame.directBuffer, frame.offset); + } + + public static String dataMimeType(final Frame frame) + { + ensureFrameType(FrameType.SETUP, frame); + return SetupFrameFlyweight.dataMimeType(frame.directBuffer, frame.offset); + } + } + + public static class Error + { + public static Frame from( + int streamId, + final Throwable throwable, + ByteBuffer metadata, + ByteBuffer data + ) { + final int code = ErrorFrameFlyweight.errorCodeFromException(throwable); + final Frame frame = POOL.acquireFrame( + ErrorFrameFlyweight.computeFrameLength(data.capacity(), metadata.capacity())); + + frame.length = ErrorFrameFlyweight.encode( + frame.directBuffer, frame.offset, streamId, code, metadata, data); + return frame; + } + + public static Frame from( + int streamId, + final Throwable throwable, + ByteBuffer metadata + ) { + String data = (throwable.getMessage() == null ? "" : throwable.getMessage()); + byte[] bytes = data.getBytes(Charset.forName("UTF-8")); + final ByteBuffer dataBuffer = ByteBuffer.wrap(bytes); + + return from(streamId, throwable, metadata, dataBuffer); + } + + public static Frame from( + int streamId, + final Throwable throwable + ) { + return from(streamId, throwable, NULL_BYTEBUFFER); + } + + public static int errorCode(final Frame frame) + { + ensureFrameType(FrameType.ERROR, frame); + return ErrorFrameFlyweight.errorCode(frame.directBuffer, frame.offset); + } + } + + public static class Lease + { + public static Frame from(int ttl, int numberOfRequests, ByteBuffer metadata) + { + final Frame frame = POOL.acquireFrame(LeaseFrameFlyweight.computeFrameLength(metadata.capacity())); + + frame.length = LeaseFrameFlyweight.encode(frame.directBuffer, frame.offset, ttl, numberOfRequests, metadata); + return frame; + } + + public static int ttl(final Frame frame) + { + ensureFrameType(FrameType.LEASE, frame); + return LeaseFrameFlyweight.ttl(frame.directBuffer, frame.offset); + } + + public static int numberOfRequests(final Frame frame) + { + ensureFrameType(FrameType.LEASE, frame); + return LeaseFrameFlyweight.numRequests(frame.directBuffer, frame.offset); + } + } + + public static class RequestN + { + public static Frame from(int streamId, int requestN) + { + final Frame frame = POOL.acquireFrame(RequestNFrameFlyweight.computeFrameLength()); + + frame.length = RequestNFrameFlyweight.encode(frame.directBuffer, frame.offset, streamId, requestN); + return frame; + } + + public static long requestN(final Frame frame) + { + ensureFrameType(FrameType.REQUEST_N, frame); + return RequestNFrameFlyweight.requestN(frame.directBuffer, frame.offset); + } + } + + public static class Request + { + public static Frame from(int streamId, FrameType type, Payload payload, int initialRequestN) + { + final ByteBuffer d = payload.getData() != null ? payload.getData() : NULL_BYTEBUFFER; + final ByteBuffer md = payload.getMetadata() != null ? payload.getMetadata() : NULL_BYTEBUFFER; + + final Frame frame = POOL.acquireFrame(RequestFrameFlyweight.computeFrameLength(type, md.capacity(), d.capacity())); + + if (type.hasInitialRequestN()) + { + frame.length = RequestFrameFlyweight.encode(frame.directBuffer, frame.offset, streamId, type, initialRequestN, md, d); + } + else + { + frame.length = RequestFrameFlyweight.encode(frame.directBuffer, frame.offset, streamId, type, md, d); + } + + return frame; + } + + public static Frame from(int streamId, FrameType type, int flags) + { + final Frame frame = POOL.acquireFrame(RequestFrameFlyweight.computeFrameLength(type, 0, 0)); + + frame.length = RequestFrameFlyweight.encode(frame.directBuffer, frame.offset, streamId, type, flags); + return frame; + } + + public static long initialRequestN(final Frame frame) + { + final FrameType type = frame.getType(); + long result; + + if (!type.isRequestType()) + { + throw new AssertionError("expected request type, but saw " + type.name()); + } + + switch (frame.getType()) + { + case REQUEST_RESPONSE: + result = 1; + break; + case FIRE_AND_FORGET: + result = 0; + break; + default: + result = RequestFrameFlyweight.initialRequestN(frame.directBuffer, frame.offset); + break; + } + + return result; + } + + public static boolean isRequestChannelComplete(final Frame frame) + { + ensureFrameType(FrameType.REQUEST_CHANNEL, frame); + final int flags = FrameHeaderFlyweight.flags(frame.directBuffer, frame.offset); + + return (flags & RequestFrameFlyweight.FLAGS_REQUEST_CHANNEL_C) == RequestFrameFlyweight.FLAGS_REQUEST_CHANNEL_C; + } + } + + public static class Response + { + public static Frame from(int streamId, FrameType type, Payload payload) + { + final ByteBuffer data = payload.getData() != null ? payload.getData() : NULL_BYTEBUFFER; + final ByteBuffer metadata = payload.getMetadata() != null ? payload.getMetadata() : NULL_BYTEBUFFER; + + final Frame frame = + POOL.acquireFrame(FrameHeaderFlyweight.computeFrameHeaderLength(type, metadata.capacity(), data.capacity())); + + frame.length = FrameHeaderFlyweight.encode(frame.directBuffer, frame.offset, streamId, type, metadata, data); + return frame; + } + + public static Frame from(int streamId, FrameType type) + { + final Frame frame = + POOL.acquireFrame(FrameHeaderFlyweight.computeFrameHeaderLength(type, 0, 0)); + + frame.length = FrameHeaderFlyweight.encode( + frame.directBuffer, frame.offset, streamId, type, Frame.NULL_BYTEBUFFER, Frame.NULL_BYTEBUFFER); + return frame; + } + } + + public static class Cancel + { + public static Frame from(int streamId) + { + final Frame frame = + POOL.acquireFrame(FrameHeaderFlyweight.computeFrameHeaderLength(FrameType.CANCEL, 0, 0)); + + frame.length = FrameHeaderFlyweight.encode( + frame.directBuffer, frame.offset, streamId, FrameType.CANCEL, Frame.NULL_BYTEBUFFER, Frame.NULL_BYTEBUFFER); + return frame; + } + } + + public static class Keepalive + { + public static Frame from(ByteBuffer data, boolean respond) + { + final Frame frame = + POOL.acquireFrame(FrameHeaderFlyweight.computeFrameHeaderLength(FrameType.KEEPALIVE, 0, data.capacity())); + + final int flags = (respond ? FrameHeaderFlyweight.FLAGS_KEEPALIVE_R : 0); + + frame.length = FrameHeaderFlyweight.encode( + frame.directBuffer, frame.offset, flags, FrameType.KEEPALIVE, Frame.NULL_BYTEBUFFER, data); + + return frame; + } + + public static boolean hasRespondFlag(final Frame frame) + { + ensureFrameType(FrameType.KEEPALIVE, frame); + final int flags = FrameHeaderFlyweight.flags(frame.directBuffer, frame.offset); + + return (flags & FrameHeaderFlyweight.FLAGS_KEEPALIVE_R) == FrameHeaderFlyweight.FLAGS_KEEPALIVE_R; + } + } + + public static void ensureFrameType(final FrameType frameType, final Frame frame) + { + final FrameType typeInFrame = frame.getType(); + + if (typeInFrame != frameType) + { + throw new AssertionError("expected " + frameType + ", but saw" + typeInFrame); + } + } + + @Override + public String toString() { + FrameType type = FrameType.UNDEFINED; + StringBuilder payload = new StringBuilder(); + long streamId = -1; + + try + { + type = FrameHeaderFlyweight.frameType(directBuffer, 0); + ByteBuffer byteBuffer; + byte[] bytes; + + byteBuffer = FrameHeaderFlyweight.sliceFrameMetadata(directBuffer, 0, 0); + if (0 < byteBuffer.capacity()) + { + bytes = new byte[byteBuffer.capacity()]; + byteBuffer.get(bytes); + payload.append(String.format("metadata: \"%s\" ", new String(bytes, Charset.forName("UTF-8")))); + } + + byteBuffer = FrameHeaderFlyweight.sliceFrameData(directBuffer, 0, 0); + if (0 < byteBuffer.capacity()) + { + bytes = new byte[byteBuffer.capacity()]; + byteBuffer.get(bytes); + payload.append(String.format("data: \"%s\"", new String(bytes, Charset.forName("UTF-8")))); + } + + streamId = FrameHeaderFlyweight.streamId(directBuffer, 0); + } catch (Exception e) { + e.printStackTrace(); + } + return "Frame[" + offset + "] => Stream ID: " + streamId + " Type: " + type + " Payload Data: " + payload; + } +} diff --git a/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java index f5c9b93a0..1ebeff887 100644 --- a/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java @@ -3,7 +3,7 @@ import io.reactivesocket.Completable; import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; -import io.reactivesocket.FrameType; +import io.reactivesocket.internal.EmptyDisposable; import io.reactivesocket.observable.Observable; import io.reactivesocket.observable.Observer; import org.reactivestreams.Publisher; @@ -21,7 +21,7 @@ public class AeronClientDuplexConnection implements DuplexConnection, AutoClosea private ManyToOneConcurrentArrayQueue framesSendQueue; private AtomicBoolean initialized; - public AeronClientDuplexConnection(Publication publication, ManyToOneConcurrentArrayQueue framesSendQueue, AtomicBoolean initialized) { + public AeronClientDuplexConnection(Publication publication, ManyToOneConcurrentArrayQueue framesSendQueue) { System.out.println("publication => " + publication.toString()); this.publication = publication; @@ -30,11 +30,10 @@ public AeronClientDuplexConnection(Publication publication, ManyToOneConcurrentA @Override public void subscribe(Observer o) { observer = o; + observer.onSubscribe(new EmptyDisposable()); } }; - this.initialized = initialized; - } public Observer getSubscriber() { @@ -48,30 +47,29 @@ public Observable getInput() { @Override public void addOutput(Publisher o, Completable callback) { o.subscribe(new Subscriber() { + volatile boolean running = true; + @Override public void onSubscribe(Subscription s) { - s.request(1); + s.request(128); } @Override public void onNext(Frame frame) { - System.out.println("STARTED => " + initialized.get()); - if (frame.getType() != FrameType.SETUP) { - System.out.println("dropping frame that isn't setup => " + frame.toString()); - } else { - System.out.println("#### #### #### FOUND THE SETUP FRAME => " + frame.toString()); - } + while (running && !framesSendQueue.offer(new FrameHolder(frame, publication))) { - //while (!framesSendQueue.offer(new FrameHolder(frame, publication))) {} + } } @Override public void onError(Throwable t) { + running = false; callback.error(t); } @Override public void onComplete() { + running = false; callback.success(); } }); @@ -79,7 +77,6 @@ public void onComplete() { @Override public void close() { - observer.onComplete(); } } diff --git a/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java index 3cd883d52..579adac18 100644 --- a/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java @@ -3,6 +3,7 @@ import io.reactivesocket.Completable; import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; +import io.reactivesocket.internal.EmptyDisposable; import io.reactivesocket.observable.Observable; import io.reactivesocket.observable.Observer; import org.reactivestreams.Publisher; @@ -28,6 +29,7 @@ public AeronServerDuplexConnection( @Override public void subscribe(Observer o) { observer = o; + o.onSubscribe(new EmptyDisposable()); } }; } diff --git a/src/main/java/io/reactivesocket/aeron/CompletableSubscription.java b/src/main/java/io/reactivesocket/aeron/CompletableSubscription.java index c39d4bb91..bf6dbd924 100644 --- a/src/main/java/io/reactivesocket/aeron/CompletableSubscription.java +++ b/src/main/java/io/reactivesocket/aeron/CompletableSubscription.java @@ -46,7 +46,6 @@ public void onSubscribe(Subscription s) { @Override public void onNext(Frame frame) { - System.out.println("SERVER => " + frame.toString()); final ByteBuffer byteBuffer = frame.getByteBuffer(); final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; diff --git a/src/main/java/io/reactivesocket/aeron/Constants.java b/src/main/java/io/reactivesocket/aeron/Constants.java index a49b4a6ac..96d8845ca 100644 --- a/src/main/java/io/reactivesocket/aeron/Constants.java +++ b/src/main/java/io/reactivesocket/aeron/Constants.java @@ -9,4 +9,6 @@ private Constants() {} public static final int CLIENT_STREAM_ID = 2; public static final byte[] EMTPY = new byte[0]; + + public static final int QUEUE_SIZE = Integer.getInteger("framesSendQueueSize", 128); } diff --git a/src/main/java/io/reactivesocket/aeron/OperatorPublish.java b/src/main/java/io/reactivesocket/aeron/OperatorPublish.java deleted file mode 100644 index ff6a95340..000000000 --- a/src/main/java/io/reactivesocket/aeron/OperatorPublish.java +++ /dev/null @@ -1,107 +0,0 @@ -package io.reactivesocket.aeron; - -import io.reactivesocket.Frame; -import rx.Observable; -import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.aeron.logbuffer.BufferClaim; -import uk.co.real_logic.agrona.BitUtil; -import uk.co.real_logic.agrona.MutableDirectBuffer; -import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; - -import java.nio.ByteBuffer; - -class OperatorPublish implements Observable.Operator { - - private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); - - private static final ThreadLocal unsafeBuffers = ThreadLocal.withInitial(() -> new UnsafeBuffer(Constants.EMTPY)); - - private Publication publication; - - private final int mtuLength; - - public OperatorPublish(Publication publication) { - this.publication = publication; - - String mtuLength = System.getProperty("aeron.mtu.length", "4096"); - - this.mtuLength = Integer.parseInt(mtuLength); - - } - - @Override - public rx.Subscriber call(rx.Subscriber child) { - return new rx.Subscriber(child) { - @Override - public void onStart() { - request(1); - } - - @Override - public void onCompleted() { - child.onCompleted(); - } - - @Override - public void onError(Throwable e) { - child.onError(e); - } - - @Override - public void onNext(Frame frame) { - - final ByteBuffer byteBuffer = frame.getByteBuffer(); - final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; - - // If the length is less the MTU size send the message using tryClaim which does not fragment the message - // If the message is larger the the MTU size send it using offer. - if (length < mtuLength) { - tryClaim(byteBuffer, length); - } else { - offer(byteBuffer, length); - } - } - - void offer(ByteBuffer byteBuffer, int length) { - final byte[] bytes = new byte[length]; - final UnsafeBuffer unsafeBuffer = unsafeBuffers.get(); - unsafeBuffer.wrap(bytes); - unsafeBuffer.putInt(0, MessageType.FRAME.getEncodedType()); - unsafeBuffer.putBytes(BitUtil.SIZE_OF_INT, byteBuffer, byteBuffer.capacity()); - for (;;) { - final long offer = publication.offer(unsafeBuffer); - if (offer >= 0) { - break; - } else if (Publication.NOT_CONNECTED == offer) { - child.onError(new RuntimeException("not connected")); - break; - } - } - - } - - void tryClaim(ByteBuffer byteBuffer, int length) { - final BufferClaim bufferClaim = bufferClaims.get(); - for (;;) { - final long offer = publication.tryClaim(length, bufferClaim); - if (offer >= 0) { - try { - final MutableDirectBuffer buffer = bufferClaim.buffer(); - final int offset = bufferClaim.offset(); - buffer.putInt(offset, MessageType.FRAME.getEncodedType()); - buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, 0, byteBuffer.capacity()); - } finally { - bufferClaim.commit(); - } - - break; - } else if (Publication.NOT_CONNECTED == offer) { - child.onError(new RuntimeException("not connected")); - break; - } - } - request(1); - } - }; - } -} diff --git a/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java index 8d7c9c268..539867ec2 100644 --- a/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java @@ -162,36 +162,17 @@ void newImageHandler(Image image, String channel, int streamId, int sessionId, l sockets.put(sessionId, socket); - CountDownLatch latch = new CountDownLatch(1); - - socket.start(new Completable() { @Override public void success() { - latch.countDown(); - System.out.println("SERVER --- SUCCESS !!!!!!!"); + } @Override public void error(Throwable e) { - latch.countDown(); - System.out.println("SERVER --- NO !!!!!!!"); + } }); - - try { - latch.await(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - System.out.println("SERVER --- SUCCESS !!!!!!!"); -/* - - try { - waitForStart.await(30, TimeUnit.SECONDS); - } catch (InterruptedException e) { - e.printStackTrace(); - }*/ } else { debug("Unsupported stream id {}", streamId); } diff --git a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java index 40ea46080..a92982eab 100644 --- a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java @@ -1,5 +1,6 @@ package io.reactivesocket.aeron; +import com.gs.collections.impl.map.mutable.ConcurrentHashMap; import io.reactivesocket.Completable; import io.reactivesocket.ConnectionSetupPayload; import io.reactivesocket.Frame; @@ -22,22 +23,20 @@ import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Collection; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; import static io.reactivesocket.aeron.Constants.CLIENT_STREAM_ID; import static io.reactivesocket.aeron.Constants.EMTPY; +import static io.reactivesocket.aeron.Constants.QUEUE_SIZE; import static io.reactivesocket.aeron.Constants.SERVER_STREAM_ID; /** * Created by rroeser on 8/13/15. */ public class ReactivesocketAeronClient implements Loggable, AutoCloseable { - static final ConcurrentHashMap subscriptions = new ConcurrentHashMap<>(); + static volatile Subscription[] subscriptions = new Subscription[0]; static final ConcurrentHashMap connections = new ConcurrentHashMap<>(); @@ -75,7 +74,7 @@ public class ReactivesocketAeronClient implements Loggable, AutoCloseable { e.printStackTrace(); } - for (Subscription subscription : subscriptions.values()) { + for (Subscription subscription : subscriptions) { subscription.close(); } @@ -84,8 +83,7 @@ public class ReactivesocketAeronClient implements Loggable, AutoCloseable { } })); - int queueSize = Integer.getInteger("framesSendQueueSize", 16); - framesSendQueue = new ManyToOneConcurrentArrayQueue<>(queueSize); + framesSendQueue = new ManyToOneConcurrentArrayQueue<>(QUEUE_SIZE); mtuLength = Integer.getInteger("aeron.mtu.length", 4096); } @@ -111,25 +109,39 @@ private ReactivesocketAeronClient(String host, String server, int port) { sessionId = publication.sessionId(); System.out.println("Created a publication for sessionId => " + sessionId); - Subscription subscription = subscriptions.get(port); + synchronized (subscriptions) { + final Subscription[] old = subscriptions; + boolean found = false; + int i = 0; + while (i < old.length) { + String c = old[i].channel(); + if (c.equals(subscriptionChannel)) { + found = true; + break; + } + i++; + } - if (subscription == null) { - synchronized (subscriptions) { + if (!found) { System.out.println("Creating a subscription to channel => " + subscriptionChannel); - subscription = aeron.addSubscription(subscriptionChannel, CLIENT_STREAM_ID); - subscriptions.putIfAbsent(port, subscription); + Subscription subscription = aeron.addSubscription(subscriptionChannel, CLIENT_STREAM_ID); + final Subscription[] newList = new Subscription[old.length + 1]; + System.arraycopy(old, 0, newList, 0, old.length); + newList[old.length] = subscription; + subscriptions = newList; System.out.println("Subscription created to channel => " + subscriptionChannel); + } - if (!pollingStarted) { - System.out.println("Polling hasn't started yet - starting polling"); + } - final FragmentAssembler fragmentAssembler = new FragmentAssembler(this::fragmentHandler); + if (!pollingStarted) { + System.out.println("Polling hasn't started yet - starting polling"); - poll(fragmentAssembler, Schedulers.newThread().createWorker()); + final FragmentAssembler fragmentAssembler = new FragmentAssembler(this::fragmentHandler); - pollingStarted = true; - } - } + poll(fragmentAssembler, Schedulers.newThread().createWorker()); + + pollingStarted = true; } establishConnection(publication, sessionId); @@ -145,100 +157,94 @@ public static ReactivesocketAeronClient create(String host, String server) { } void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) { - int messageTypeInt = buffer.getInt(offset); - MessageType messageType = MessageType.from(messageTypeInt); - if (messageType == MessageType.FRAME) { - final AeronClientDuplexConnection connection = connections.get(header.sessionId()); - Observer subscriber = connection.getSubscriber(); - final ByteBuffer bytes = ByteBuffer.allocate(length); - buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); - final Frame frame = Frame.from(bytes); - subscriber.onNext(frame); - } else if (messageType == MessageType.ESTABLISH_CONNECTION_RESPONSE) { - final int ackSessionId = buffer.getInt(offset + BitUtil.SIZE_OF_INT); - Publication publication = publications.get(ackSessionId); - System.out.println(String.format("Received establish connection ack for session id => %d", ackSessionId)); - System.out.println("ESTABLISH_CONNECTION_RESPONSE publication => " + publication.toString()); - serverSessionId = header.sessionId(); - final AeronClientDuplexConnection connection = - connections - .computeIfAbsent(serverSessionId, (_p) -> - new AeronClientDuplexConnection(publication, framesSendQueue, initialized)); - - reactiveSocket = ReactiveSocket.fromClientConnection( - connection, - ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS), - err -> err.printStackTrace()); - - reactiveSocket.start(new Completable() { - @Override - public void success() { - initialized.set(true); - System.out.println("SUCCESSSSSS!!!!!!"); - } + try { + int messageTypeInt = buffer.getInt(offset); + MessageType messageType = MessageType.from(messageTypeInt); + if (messageType == MessageType.FRAME) { + final AeronClientDuplexConnection connection = connections.get(header.sessionId()); + Observer subscriber = connection.getSubscriber(); + final ByteBuffer bytes = ByteBuffer.allocate(length); + buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); + final Frame frame = Frame.from(bytes); + subscriber.onNext(frame); + } else if (messageType == MessageType.ESTABLISH_CONNECTION_RESPONSE) { + final int ackSessionId = buffer.getInt(offset + BitUtil.SIZE_OF_INT); + Publication publication = publications.get(ackSessionId); + System.out.println(String.format("Received establish connection ack for session id => %d", ackSessionId)); + serverSessionId = header.sessionId(); + final AeronClientDuplexConnection connection = + connections + .computeIfAbsent(serverSessionId, (_p) -> + new AeronClientDuplexConnection(publication, framesSendQueue)); + + reactiveSocket = ReactiveSocket.fromClientConnection( + connection, + ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS), + err -> err.printStackTrace()); + + reactiveSocket.start(new Completable() { + @Override + public void success() { - @Override - public void error(Throwable e) { - initialized.set(true); - System.out.println("NOOOOOO!!!!!!"); - } - }); + } + @Override + public void error(Throwable e) { - info("ReactiveSocket connected to Aeron session => " + ackSessionId); - CountDownLatch latch = establishConnectionLatches.remove(ackSessionId); + } + }); - latch.countDown(); - System.out.println("HERE #*#*#*#*#**#*#*#*#*#*"); + info("ReactiveSocket connected to Aeron session => " + ackSessionId); + CountDownLatch latch = establishConnectionLatches.remove(ackSessionId); - } else { - debug("Unknown message type => " + messageTypeInt); + latch.countDown(); + } else { + debug("Unknown message type => " + messageTypeInt); + } + } catch (Throwable t) { + System.out.println("ERROR fragmentHandler"); + t.printStackTrace(); + error("error handling framement", t); } } - AtomicBoolean initialized = new AtomicBoolean(false); - void poll(FragmentAssembler fragmentAssembler, Scheduler.Worker worker) { worker.schedule(() -> { while (running) { try { - - // Pool subscriptions - Collection subscriptions = ReactivesocketAeronClient.subscriptions.values(); - if (subscriptions != null) { - subscriptions.forEach(subscription -> - subscription.poll(fragmentAssembler, Integer.MAX_VALUE) - ); - } - - ArrayList frames = new ArrayList<>(128); - - // Drain send queue framesSendQueue - .drainTo(frames, 128); + .drain(new Consumer() { + @Override + public void accept(FrameHolder fh) { + Frame frame = fh.getFrame(); + final ByteBuffer byteBuffer = frame.getByteBuffer(); + final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; + + // If the length is less the MTU size send the message using tryClaim which does not fragment the message + // If the message is larger the the MTU size send it using offer. + if (length < mtuLength) { + tryClaim(fh.getPublication(), byteBuffer, length); + } else { + offer(fh.getPublication(), byteBuffer, length); + } + + frame.release(); + } + }); - frames.forEach(fh -> { - Frame frame = fh.getFrame(); - System.out.println("QUEUE FRAME => " + frame.toString()); - - final ByteBuffer byteBuffer = frame.getByteBuffer(); - final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; - - // If the length is less the MTU size send the message using tryClaim which does not fragment the message - // If the message is larger the the MTU size send it using offer. - if (length < mtuLength) { - tryClaim(fh.getPublication(), byteBuffer, length); - } else { - offer(fh.getPublication(), byteBuffer, length); - } - - frame.release(); - }); + } catch (Throwable t) { + error("error draining send frame queue", t); + } - poll(fragmentAssembler, worker); + try { + final Subscription[] s = subscriptions; + int i = 0; + while (i < s.length) { + s[i].poll(fragmentAssembler, Integer.MAX_VALUE); + i++; + } } catch (Throwable t) { - t.printStackTrace(); - error(t.getMessage(), t); + error("error polling aeron subscription", t); } } diff --git a/src/perf/java/io/reactivesocket/aeron/jmh/RequestResponsePerf.java b/src/perf/java/io/reactivesocket/aeron/jmh/RequestResponsePerf.java new file mode 100644 index 000000000..8755886d7 --- /dev/null +++ b/src/perf/java/io/reactivesocket/aeron/jmh/RequestResponsePerf.java @@ -0,0 +1,144 @@ +package io.reactivesocket.aeron.jmh; + +import io.reactivesocket.ConnectionSetupHandler; +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.Payload; +import io.reactivesocket.RequestHandler; +import io.reactivesocket.aeron.ReactiveSocketAeronServer; +import io.reactivesocket.aeron.ReactivesocketAeronClient; +import io.reactivesocket.exceptions.SetupException; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.infra.Blackhole; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import uk.co.real_logic.aeron.driver.MediaDriver; +import uk.co.real_logic.aeron.driver.ThreadingMode; +import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; + +import java.nio.ByteBuffer; +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +public class RequestResponsePerf { + static final MediaDriver mediaDriver; + + static { + MediaDriver.Context ctx = new MediaDriver.Context(); + ctx.dirsDeleteOnStart(true); + ctx.threadingMode(ThreadingMode.DEDICATED); + ctx.conductorIdleStrategy(new NoOpIdleStrategy()); + ctx.receiverIdleStrategy(new NoOpIdleStrategy()); + ctx.conductorIdleStrategy(new NoOpIdleStrategy()); + mediaDriver = MediaDriver.launch(ctx); + } + + @State(Scope.Thread) + public static class Input { + ReactiveSocketAeronServer server; + ReactivesocketAeronClient client; + Blackhole bh; + Payload payload; + + @Param({ "100", "10000", "100000", "1000000" }) + // @Param({ "1000" }) + public int size; + + @Setup + public void init(Blackhole bh) { + this.bh = bh; + + payload = new Payload() { + @Override + public ByteBuffer getData() { + return ByteBuffer.wrap("1".getBytes()); + } + + @Override + public ByteBuffer getMetadata() { + return ByteBuffer.allocate(0); + } + }; + + server = ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { + @Override + public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + return new RequestHandler() { + @Override + public Publisher handleRequestResponse(Payload p) { + return new Publisher() { + @Override + public void subscribe(Subscriber s) { + s.onNext(payload); + s.onComplete(); + } + }; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + return null; + } + + @Override + public Publisher handleSubscription(Payload payload) { + return null; + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return null; + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher payloads) { + return null; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + }; + } + }); + + client = ReactivesocketAeronClient.create("localhost", "localhost"); + } + } + + @Benchmark + public void requestReponsePerf(Input input) { + for (int i = 0; i < input.size; i++) { + input.client.requestResponse(input.payload).subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + + } + + @Override + public void onNext(Payload payload) { + input.bh.consume(payload); + } + + @Override + public void onError(Throwable t) { + + } + + @Override + public void onComplete() { + + } + }); + } + } +} diff --git a/src/test/java/io/reactivesocket/aeron/OperatorPublishTest.java b/src/test/java/io/reactivesocket/aeron/OperatorPublishTest.java deleted file mode 100644 index d4ddfc61c..000000000 --- a/src/test/java/io/reactivesocket/aeron/OperatorPublishTest.java +++ /dev/null @@ -1,97 +0,0 @@ -package io.reactivesocket.aeron; - -import io.reactivesocket.Frame; -import rx.Subscriber; -import uk.co.real_logic.aeron.Publication; - -import java.nio.ByteBuffer; - -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyObject; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class OperatorPublishTest { - //@Test - public void testShouldCallTryClaimWhenSmallerThanMTU() throws Exception { - String message = "I'm a message longer than 1"; - ByteBuffer buffer = ByteBuffer.wrap(message.getBytes()); - - Frame frame = mock(Frame.class); - when(frame.getByteBuffer()).thenReturn(buffer); - - Publication publication = mock(Publication.class); - when(publication.maxMessageLength()).thenReturn(1000); - - OperatorPublish publish = new OperatorPublish(publication); - - try { - Subscriber subscriber = new Subscriber() { - @Override - public void onCompleted() { - - } - - @Override - public void onError(Throwable e) { - - } - - @Override - public void onNext(Object o) { - - } - }; - - Subscriber call = publish.call(subscriber); - call.onNext(frame); - - } catch (Throwable t) { - } - - verify(publication, times(1)).tryClaim(anyInt(), anyObject()); - - } - - //@Test - public void testShouldCallOfferWhenLargerThenMTU() throws Exception { - String message = "I'm a message longer than 1"; - ByteBuffer buffer = ByteBuffer.wrap(message.getBytes()); - - Frame frame = mock(Frame.class); - when(frame.getByteBuffer()).thenReturn(buffer); - - Publication publication = mock(Publication.class); - when(publication.maxMessageLength()).thenReturn(1); - - OperatorPublish publish = new OperatorPublish(publication); - - try { - Subscriber subscriber = new Subscriber() { - @Override - public void onCompleted() { - - } - - @Override - public void onError(Throwable e) { - } - - @Override - public void onNext(Object o) { - - } - }; - - Subscriber call = publish.call(subscriber); - call.onNext(frame); - - } catch (Throwable t) { - } - - verify(publication, times(1)).offer(anyObject()); - - } -} \ No newline at end of file diff --git a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java index 582fbe734..aa865d6e0 100644 --- a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java @@ -76,13 +76,13 @@ public Publisher handleMetadataPush(Payload payload) { } }); - CountDownLatch latch = new CountDownLatch(10_000); + CountDownLatch latch = new CountDownLatch(130); ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost", "localhost"); Observable - .range(1, 10_000) + .range(1, 130) .flatMap(i -> { //System.out.println("pinging => " + i); Payload payload = TestUtil.utf8EncodedPayload("ping =>" + i, null); @@ -96,6 +96,7 @@ public void onCompleted() { @Override public void onError(Throwable e) { + System.out.println("counted to => " + latch.getCount()); e.printStackTrace(); } @@ -172,7 +173,7 @@ public ByteBuffer getData() { @Override public ByteBuffer getMetadata() { - return null; + return ByteBuffer.allocate(0); } }; return RxReactiveStreams.toObservable(client.requestResponse(payload)); From 667c8054e7bb368d1ce46310b5befd43d90ace8c Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Wed, 16 Sep 2015 16:36:49 -0700 Subject: [PATCH 025/105] making it work with two clients/servers at the same time --- src/main/java/io/reactivesocket/Frame.java | 2 +- .../io/reactivesocket/aeron/FrameHolder.java | 22 --- .../aeron/PublishSubscription.java | 119 --------------- .../AeronClientDuplexConnection.java | 23 ++- .../aeron/client/FrameHolder.java | 52 +++++++ .../ReactivesocketAeronClient.java | 106 +++++++++---- .../aeron/{ => internal}/Constants.java | 4 +- .../aeron/{ => internal}/Loggable.java | 4 +- .../aeron/{ => internal}/MessageType.java | 4 +- .../AeronServerDuplexConnection.java | 4 +- .../{ => server}/CompletableSubscription.java | 4 +- .../ReactiveSocketAeronServer.java | 8 +- .../aeron/jmh/RequestResponsePerf.java | 144 ------------------ .../aeron/ReactiveSocketAeronTest.java | 125 ++++++++++++--- .../client/ReactivesocketAeronClientTest.java | 122 +++++++++++++++ 15 files changed, 375 insertions(+), 368 deletions(-) delete mode 100644 src/main/java/io/reactivesocket/aeron/FrameHolder.java delete mode 100644 src/main/java/io/reactivesocket/aeron/PublishSubscription.java rename src/main/java/io/reactivesocket/aeron/{ => client}/AeronClientDuplexConnection.java (80%) create mode 100644 src/main/java/io/reactivesocket/aeron/client/FrameHolder.java rename src/main/java/io/reactivesocket/aeron/{ => client}/ReactivesocketAeronClient.java (80%) rename src/main/java/io/reactivesocket/aeron/{ => internal}/Constants.java (79%) rename src/main/java/io/reactivesocket/aeron/{ => internal}/Loggable.java (91%) rename src/main/java/io/reactivesocket/aeron/{ => internal}/MessageType.java (91%) rename src/main/java/io/reactivesocket/aeron/{ => server}/AeronServerDuplexConnection.java (95%) rename src/main/java/io/reactivesocket/aeron/{ => server}/CompletableSubscription.java (96%) rename src/main/java/io/reactivesocket/aeron/{ => server}/ReactiveSocketAeronServer.java (96%) delete mode 100644 src/perf/java/io/reactivesocket/aeron/jmh/RequestResponsePerf.java create mode 100644 src/test/java/io/reactivesocket/aeron/client/ReactivesocketAeronClientTest.java diff --git a/src/main/java/io/reactivesocket/Frame.java b/src/main/java/io/reactivesocket/Frame.java index 0f32719e9..4691f5752 100644 --- a/src/main/java/io/reactivesocket/Frame.java +++ b/src/main/java/io/reactivesocket/Frame.java @@ -38,7 +38,7 @@ public class Frame implements Payload */ private static final String FRAME_POOLER_CLASS_NAME = - getProperty("io.reactivesocket.FramePool", "io.reactivesocket.internal.RobertsFramePool"); + getProperty("io.reactivesocket.FramePool", "io.reactivesocket.internal.UnpooledFrame"); private static final FramePool POOL; static diff --git a/src/main/java/io/reactivesocket/aeron/FrameHolder.java b/src/main/java/io/reactivesocket/aeron/FrameHolder.java deleted file mode 100644 index 251a0700f..000000000 --- a/src/main/java/io/reactivesocket/aeron/FrameHolder.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.reactivesocket.aeron; - -import io.reactivesocket.Frame; -import uk.co.real_logic.aeron.Publication; - -public class FrameHolder { - private Publication publication; - private Frame frame; - - public FrameHolder(Frame frame, Publication publication) { - this.frame = frame; - this.publication = publication; - } - - public Publication getPublication() { - return publication; - } - - public Frame getFrame() { - return frame; - } -} diff --git a/src/main/java/io/reactivesocket/aeron/PublishSubscription.java b/src/main/java/io/reactivesocket/aeron/PublishSubscription.java deleted file mode 100644 index 660e1dc7c..000000000 --- a/src/main/java/io/reactivesocket/aeron/PublishSubscription.java +++ /dev/null @@ -1,119 +0,0 @@ -package io.reactivesocket.aeron; - -import io.reactivesocket.Frame; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; -import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.aeron.logbuffer.BufferClaim; -import uk.co.real_logic.agrona.BitUtil; -import uk.co.real_logic.agrona.MutableDirectBuffer; -import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; - -import java.nio.ByteBuffer; - -public class PublishSubscription implements Subscription { - private final Subscriber subscriber; - - private final Publisher source; - - private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); - - private static final ThreadLocal unsafeBuffers = ThreadLocal.withInitial(() -> new UnsafeBuffer(Constants.EMTPY)); - - private Publication publication; - - private final int mtuLength; - - - public PublishSubscription(Subscriber subscriber, Publisher source, Publication publication) { - this.source = source; - this.subscriber = subscriber; - this.publication = publication; - - String mtuLength = System.getProperty("aeron.mtu.length", "4096"); - - this.mtuLength = Integer.parseInt(mtuLength); - - } - - @Override - public void cancel() { - - } - - @Override - public void request(long n) { - source.subscribe(new Subscriber() { - @Override - public void onSubscribe(Subscription s) { - s.request(n); - } - - @Override - public void onNext(Frame frame) { - final ByteBuffer byteBuffer = frame.getByteBuffer(); - final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; - - // If the length is less the MTU size send the message using tryClaim which does not fragment the message - // If the message is larger the the MTU size send it using offer. - if (length < mtuLength) { - tryClaim(byteBuffer, length); - } else { - offer(byteBuffer, length); - } - - } - - @Override - public void onError(Throwable t) { - subscriber.onError(t); - } - - @Override - public void onComplete() { - subscriber.onComplete(); - } - }); - } - - void offer(ByteBuffer byteBuffer, int length) { - final byte[] bytes = new byte[length]; - final UnsafeBuffer unsafeBuffer = unsafeBuffers.get(); - unsafeBuffer.wrap(bytes); - unsafeBuffer.putInt(0, MessageType.FRAME.getEncodedType()); - unsafeBuffer.putBytes(BitUtil.SIZE_OF_INT, byteBuffer, byteBuffer.capacity()); - for (;;) { - final long offer = publication.offer(unsafeBuffer); - if (offer >= 0) { - break; - } else if (Publication.NOT_CONNECTED == offer) { - subscriber.onError(new RuntimeException("not connected")); - break; - } - } - - } - - void tryClaim(ByteBuffer byteBuffer, int length) { - final BufferClaim bufferClaim = bufferClaims.get(); - for (;;) { - final long offer = publication.tryClaim(length, bufferClaim); - if (offer >= 0) { - try { - final MutableDirectBuffer buffer = bufferClaim.buffer(); - final int offset = bufferClaim.offset(); - buffer.putInt(offset, MessageType.FRAME.getEncodedType()); - buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, 0, byteBuffer.capacity()); - } finally { - bufferClaim.commit(); - } - - break; - } else if (Publication.NOT_CONNECTED == offer) { - subscriber.onError(new RuntimeException("not connected")); - break; - } - } - } -} diff --git a/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java similarity index 80% rename from src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java rename to src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java index 1ebeff887..9d2ccb6fa 100644 --- a/src/main/java/io/reactivesocket/aeron/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java @@ -1,4 +1,4 @@ -package io.reactivesocket.aeron; +package io.reactivesocket.aeron.client; import io.reactivesocket.Completable; import io.reactivesocket.DuplexConnection; @@ -12,26 +12,20 @@ import uk.co.real_logic.aeron.Publication; import uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue; -import java.util.concurrent.atomic.AtomicBoolean; - public class AeronClientDuplexConnection implements DuplexConnection, AutoCloseable { private Publication publication; private Observer observer; private Observable observable; private ManyToOneConcurrentArrayQueue framesSendQueue; - private AtomicBoolean initialized; public AeronClientDuplexConnection(Publication publication, ManyToOneConcurrentArrayQueue framesSendQueue) { System.out.println("publication => " + publication.toString()); this.publication = publication; this.framesSendQueue = framesSendQueue; - this.observable = new Observable() { - @Override - public void subscribe(Observer o) { - observer = o; - observer.onSubscribe(new EmptyDisposable()); - } + this.observable = (Observer o) -> { + observer = o; + observer.onSubscribe(new EmptyDisposable()); }; } @@ -48,17 +42,20 @@ public Observable getInput() { public void addOutput(Publisher o, Completable callback) { o.subscribe(new Subscriber() { volatile boolean running = true; - + Subscription s; @Override public void onSubscribe(Subscription s) { - s.request(128); + this.s = s; + s.request(1); } @Override public void onNext(Frame frame) { - while (running && !framesSendQueue.offer(new FrameHolder(frame, publication))) { + final FrameHolder frameHolder = FrameHolder.get(frame, publication); + while (running && !framesSendQueue.offer(frameHolder)) { } + s.request(1); } @Override diff --git a/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java b/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java new file mode 100644 index 000000000..c5f0f7711 --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java @@ -0,0 +1,52 @@ +package io.reactivesocket.aeron.client; + +import io.reactivesocket.Frame; +import io.reactivesocket.aeron.internal.Constants; +import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue; + +/** + * Holds a frame and the publication that it's supposed to be sent on. + * Pools instances on an {@link uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue} + */ +class FrameHolder { + private static ManyToOneConcurrentArrayQueue FRAME_HOLDER_QUEUE + = new ManyToOneConcurrentArrayQueue<>(Constants.QUEUE_SIZE); + + static { + for (int i = 0; i < Constants.QUEUE_SIZE; i++) { + FRAME_HOLDER_QUEUE.offer(new FrameHolder()); + } + } + + private Publication publication; + private Frame frame; + + private FrameHolder() {} + + public static FrameHolder get(Frame frame, Publication publication) { + FrameHolder frameHolder = FRAME_HOLDER_QUEUE.poll(); + + if (frameHolder == null) { + frameHolder = new FrameHolder(); + } + + frameHolder.frame = frame; + frameHolder.publication = publication; + + return frameHolder; + } + + public Publication getPublication() { + return publication; + } + + public Frame getFrame() { + return frame; + } + + public void release() { + frame.release(); + FRAME_HOLDER_QUEUE.offer(this); + } +} diff --git a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/client/ReactivesocketAeronClient.java similarity index 80% rename from src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java rename to src/main/java/io/reactivesocket/aeron/client/ReactivesocketAeronClient.java index a92982eab..258761a0f 100644 --- a/src/main/java/io/reactivesocket/aeron/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/client/ReactivesocketAeronClient.java @@ -1,4 +1,4 @@ -package io.reactivesocket.aeron; +package io.reactivesocket.aeron.client; import com.gs.collections.impl.map.mutable.ConcurrentHashMap; import io.reactivesocket.Completable; @@ -6,6 +6,9 @@ import io.reactivesocket.Frame; import io.reactivesocket.Payload; import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.aeron.internal.Constants; +import io.reactivesocket.aeron.internal.Loggable; +import io.reactivesocket.aeron.internal.MessageType; import io.reactivesocket.observable.Observer; import org.reactivestreams.Publisher; import rx.Scheduler; @@ -25,12 +28,11 @@ import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; -import static io.reactivesocket.aeron.Constants.CLIENT_STREAM_ID; -import static io.reactivesocket.aeron.Constants.EMTPY; -import static io.reactivesocket.aeron.Constants.QUEUE_SIZE; -import static io.reactivesocket.aeron.Constants.SERVER_STREAM_ID; +import static io.reactivesocket.aeron.internal.Constants.CLIENT_STREAM_ID; +import static io.reactivesocket.aeron.internal.Constants.EMTPY; +import static io.reactivesocket.aeron.internal.Constants.QUEUE_SIZE; +import static io.reactivesocket.aeron.internal.Constants.SERVER_STREAM_ID; /** * Created by rroeser on 8/13/15. @@ -44,7 +46,7 @@ public class ReactivesocketAeronClient implements Loggable, AutoCloseable { static final ConcurrentHashMap publications = new ConcurrentHashMap<>(); - private ReactiveSocket reactiveSocket; + static final ConcurrentHashMap reactiveSockets = new ConcurrentHashMap<>(); private final Aeron aeron; @@ -170,14 +172,14 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) } else if (messageType == MessageType.ESTABLISH_CONNECTION_RESPONSE) { final int ackSessionId = buffer.getInt(offset + BitUtil.SIZE_OF_INT); Publication publication = publications.get(ackSessionId); - System.out.println(String.format("Received establish connection ack for session id => %d", ackSessionId)); serverSessionId = header.sessionId(); + System.out.println(String.format("Received establish connection ack for session id => %d, and server session id => %d", ackSessionId, serverSessionId)); final AeronClientDuplexConnection connection = connections .computeIfAbsent(serverSessionId, (_p) -> new AeronClientDuplexConnection(publication, framesSendQueue)); - reactiveSocket = ReactiveSocket.fromClientConnection( + ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection( connection, ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS), err -> err.printStackTrace()); @@ -194,6 +196,8 @@ public void error(Throwable e) { } }); + reactiveSockets.putIfAbsent(ackSessionId, reactiveSocket); + info("ReactiveSocket connected to Aeron session => " + ackSessionId); CountDownLatch latch = establishConnectionLatches.remove(ackSessionId); @@ -211,30 +215,26 @@ public void error(Throwable e) { void poll(FragmentAssembler fragmentAssembler, Scheduler.Worker worker) { worker.schedule(() -> { while (running) { - try { - framesSendQueue - .drain(new Consumer() { - @Override - public void accept(FrameHolder fh) { - Frame frame = fh.getFrame(); - final ByteBuffer byteBuffer = frame.getByteBuffer(); - final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; - - // If the length is less the MTU size send the message using tryClaim which does not fragment the message - // If the message is larger the the MTU size send it using offer. - if (length < mtuLength) { - tryClaim(fh.getPublication(), byteBuffer, length); - } else { - offer(fh.getPublication(), byteBuffer, length); - } - - frame.release(); + framesSendQueue + .drain((FrameHolder fh) -> { + try { + Frame frame = fh.getFrame(); + final ByteBuffer byteBuffer = frame.getByteBuffer(); + final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; + + // If the length is less the MTU size send the message using tryClaim which does not fragment the message + // If the message is larger the the MTU size send it using offer. + if (length < mtuLength) { + tryClaim(fh.getPublication(), byteBuffer, length); + } else { + offer(fh.getPublication(), byteBuffer, length); } - }); - - } catch (Throwable t) { - error("error draining send frame queue", t); - } + } catch (Throwable t) { + error("error draining send frame queue", t); + } finally { + fh.release(); + } + }); try { final Subscription[] s = subscriptions; @@ -335,21 +335,61 @@ void establishConnection(final Publication publication, final int sessionId) { } public Publisher requestResponse(Payload payload) { + ReactiveSocket reactiveSocket = reactiveSockets.get(sessionId); return reactiveSocket.requestResponse(payload); } public Publisher fireAndForget(Payload payload) { + ReactiveSocket reactiveSocket = reactiveSockets.get(sessionId); return reactiveSocket.fireAndForget(payload); } public Publisher requestStream(Payload payload) { + ReactiveSocket reactiveSocket = reactiveSockets.get(sessionId); return reactiveSocket.requestStream(payload); } public Publisher requestSubscription(Payload payload) { + ReactiveSocket reactiveSocket = reactiveSockets.get(sessionId); return reactiveSocket.requestSubscription(payload); } + public static boolean isRunning() { + return running; + } + + public static void setRunning(boolean running) { + ReactivesocketAeronClient.running = running; + } + + public int getSessionId() { + return sessionId; + } + + public void setSessionId(int sessionId) { + this.sessionId = sessionId; + } + + public int getPort() { + return port; + } + + public int getServerSessionId() { + return serverSessionId; + } + + public void setServerSessionId(int serverSessionId) { + this.serverSessionId = serverSessionId; + } + + public static boolean isPollingStarted() { + return pollingStarted; + } + + public static void setPollingStarted(boolean pollingStarted) { + ReactivesocketAeronClient.pollingStarted = pollingStarted; + } + @Override public void close() throws Exception { // First clean up the different maps @@ -361,7 +401,7 @@ public void close() throws Exception { // Close the different connections closeQuietly(connection); - closeQuietly(reactiveSocket); + closeQuietly(reactiveSockets.get(sessionId)); System.out.println("closing publication => " + publications.get(sessionId).toString()); Publication publication = publications.remove(sessionId); closeQuietly(publication); diff --git a/src/main/java/io/reactivesocket/aeron/Constants.java b/src/main/java/io/reactivesocket/aeron/internal/Constants.java similarity index 79% rename from src/main/java/io/reactivesocket/aeron/Constants.java rename to src/main/java/io/reactivesocket/aeron/internal/Constants.java index 96d8845ca..ccaf6d76a 100644 --- a/src/main/java/io/reactivesocket/aeron/Constants.java +++ b/src/main/java/io/reactivesocket/aeron/internal/Constants.java @@ -1,6 +1,6 @@ -package io.reactivesocket.aeron; +package io.reactivesocket.aeron.internal; -final class Constants { +public final class Constants { private Constants() {} diff --git a/src/main/java/io/reactivesocket/aeron/Loggable.java b/src/main/java/io/reactivesocket/aeron/internal/Loggable.java similarity index 91% rename from src/main/java/io/reactivesocket/aeron/Loggable.java rename to src/main/java/io/reactivesocket/aeron/internal/Loggable.java index d2fabf416..27f7994d1 100644 --- a/src/main/java/io/reactivesocket/aeron/Loggable.java +++ b/src/main/java/io/reactivesocket/aeron/internal/Loggable.java @@ -1,4 +1,4 @@ -package io.reactivesocket.aeron; +package io.reactivesocket.aeron.internal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -8,7 +8,7 @@ /** * No more needed to type Logger LOGGER = LoggerFactory.getLogger.... */ -interface Loggable { +public interface Loggable { default void info(String message, Object... args) { logger().debug(message, args); } diff --git a/src/main/java/io/reactivesocket/aeron/MessageType.java b/src/main/java/io/reactivesocket/aeron/internal/MessageType.java similarity index 91% rename from src/main/java/io/reactivesocket/aeron/MessageType.java rename to src/main/java/io/reactivesocket/aeron/internal/MessageType.java index d091dea07..53b691c0f 100644 --- a/src/main/java/io/reactivesocket/aeron/MessageType.java +++ b/src/main/java/io/reactivesocket/aeron/internal/MessageType.java @@ -1,9 +1,9 @@ -package io.reactivesocket.aeron; +package io.reactivesocket.aeron.internal; /** * Type of message being sent. */ -enum MessageType { +public enum MessageType { ESTABLISH_CONNECTION_REQUEST(0x01), ESTABLISH_CONNECTION_RESPONSE(0x02), FRAME(0x03); diff --git a/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java similarity index 95% rename from src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java rename to src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java index 579adac18..cd0adb21f 100644 --- a/src/main/java/io/reactivesocket/aeron/AeronServerDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java @@ -1,8 +1,10 @@ -package io.reactivesocket.aeron; +package io.reactivesocket.aeron.server; import io.reactivesocket.Completable; import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; +import io.reactivesocket.aeron.internal.Loggable; +import io.reactivesocket.aeron.internal.MessageType; import io.reactivesocket.internal.EmptyDisposable; import io.reactivesocket.observable.Observable; import io.reactivesocket.observable.Observer; diff --git a/src/main/java/io/reactivesocket/aeron/CompletableSubscription.java b/src/main/java/io/reactivesocket/aeron/server/CompletableSubscription.java similarity index 96% rename from src/main/java/io/reactivesocket/aeron/CompletableSubscription.java rename to src/main/java/io/reactivesocket/aeron/server/CompletableSubscription.java index bf6dbd924..47644d066 100644 --- a/src/main/java/io/reactivesocket/aeron/CompletableSubscription.java +++ b/src/main/java/io/reactivesocket/aeron/server/CompletableSubscription.java @@ -1,7 +1,9 @@ -package io.reactivesocket.aeron; +package io.reactivesocket.aeron.server; import io.reactivesocket.Completable; import io.reactivesocket.Frame; +import io.reactivesocket.aeron.internal.Constants; +import io.reactivesocket.aeron.internal.MessageType; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import uk.co.real_logic.aeron.Publication; diff --git a/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java similarity index 96% rename from src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java rename to src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java index 539867ec2..443f71c19 100644 --- a/src/main/java/io/reactivesocket/aeron/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java @@ -1,10 +1,12 @@ -package io.reactivesocket.aeron; +package io.reactivesocket.aeron.server; import io.reactivesocket.Completable; import io.reactivesocket.ConnectionSetupHandler; import io.reactivesocket.Frame; import io.reactivesocket.LeaseGovernor; import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.aeron.internal.Loggable; +import io.reactivesocket.aeron.internal.MessageType; import io.reactivesocket.observable.Observer; import rx.Scheduler; import rx.schedulers.Schedulers; @@ -22,8 +24,8 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import static io.reactivesocket.aeron.Constants.CLIENT_STREAM_ID; -import static io.reactivesocket.aeron.Constants.SERVER_STREAM_ID; +import static io.reactivesocket.aeron.internal.Constants.CLIENT_STREAM_ID; +import static io.reactivesocket.aeron.internal.Constants.SERVER_STREAM_ID; public class ReactiveSocketAeronServer implements AutoCloseable, Loggable { private final Aeron aeron; diff --git a/src/perf/java/io/reactivesocket/aeron/jmh/RequestResponsePerf.java b/src/perf/java/io/reactivesocket/aeron/jmh/RequestResponsePerf.java deleted file mode 100644 index 8755886d7..000000000 --- a/src/perf/java/io/reactivesocket/aeron/jmh/RequestResponsePerf.java +++ /dev/null @@ -1,144 +0,0 @@ -package io.reactivesocket.aeron.jmh; - -import io.reactivesocket.ConnectionSetupHandler; -import io.reactivesocket.ConnectionSetupPayload; -import io.reactivesocket.Payload; -import io.reactivesocket.RequestHandler; -import io.reactivesocket.aeron.ReactiveSocketAeronServer; -import io.reactivesocket.aeron.ReactivesocketAeronClient; -import io.reactivesocket.exceptions.SetupException; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Param; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.Setup; -import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.infra.Blackhole; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; -import uk.co.real_logic.aeron.driver.MediaDriver; -import uk.co.real_logic.aeron.driver.ThreadingMode; -import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; - -import java.nio.ByteBuffer; -import java.util.concurrent.TimeUnit; - -@BenchmarkMode(Mode.Throughput) -@OutputTimeUnit(TimeUnit.MICROSECONDS) -public class RequestResponsePerf { - static final MediaDriver mediaDriver; - - static { - MediaDriver.Context ctx = new MediaDriver.Context(); - ctx.dirsDeleteOnStart(true); - ctx.threadingMode(ThreadingMode.DEDICATED); - ctx.conductorIdleStrategy(new NoOpIdleStrategy()); - ctx.receiverIdleStrategy(new NoOpIdleStrategy()); - ctx.conductorIdleStrategy(new NoOpIdleStrategy()); - mediaDriver = MediaDriver.launch(ctx); - } - - @State(Scope.Thread) - public static class Input { - ReactiveSocketAeronServer server; - ReactivesocketAeronClient client; - Blackhole bh; - Payload payload; - - @Param({ "100", "10000", "100000", "1000000" }) - // @Param({ "1000" }) - public int size; - - @Setup - public void init(Blackhole bh) { - this.bh = bh; - - payload = new Payload() { - @Override - public ByteBuffer getData() { - return ByteBuffer.wrap("1".getBytes()); - } - - @Override - public ByteBuffer getMetadata() { - return ByteBuffer.allocate(0); - } - }; - - server = ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { - @Override - public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { - return new RequestHandler() { - @Override - public Publisher handleRequestResponse(Payload p) { - return new Publisher() { - @Override - public void subscribe(Subscriber s) { - s.onNext(payload); - s.onComplete(); - } - }; - } - - @Override - public Publisher handleRequestStream(Payload payload) { - return null; - } - - @Override - public Publisher handleSubscription(Payload payload) { - return null; - } - - @Override - public Publisher handleFireAndForget(Payload payload) { - return null; - } - - @Override - public Publisher handleChannel(Payload initialPayload, Publisher payloads) { - return null; - } - - @Override - public Publisher handleMetadataPush(Payload payload) { - return null; - } - }; - } - }); - - client = ReactivesocketAeronClient.create("localhost", "localhost"); - } - } - - @Benchmark - public void requestReponsePerf(Input input) { - for (int i = 0; i < input.size; i++) { - input.client.requestResponse(input.payload).subscribe(new Subscriber() { - @Override - public void onSubscribe(Subscription s) { - - } - - @Override - public void onNext(Payload payload) { - input.bh.consume(payload); - } - - @Override - public void onError(Throwable t) { - - } - - @Override - public void onComplete() { - - } - }); - } - } -} diff --git a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java index aa865d6e0..964c7c958 100644 --- a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java @@ -5,8 +5,9 @@ import io.reactivesocket.Frame; import io.reactivesocket.Payload; import io.reactivesocket.RequestHandler; +import io.reactivesocket.aeron.client.ReactivesocketAeronClient; +import io.reactivesocket.aeron.server.ReactiveSocketAeronServer; import io.reactivesocket.exceptions.SetupException; -import junit.framework.Assert; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; @@ -86,7 +87,7 @@ public Publisher handleMetadataPush(Payload payload) { .flatMap(i -> { //System.out.println("pinging => " + i); Payload payload = TestUtil.utf8EncodedPayload("ping =>" + i, null); - return RxReactiveStreams.toObservable(client.requestResponse(payload)); + return RxReactiveStreams.toObservable(client.requestResponse(payload)); } ) .subscribe(new rx.Subscriber() { @@ -158,7 +159,6 @@ public Publisher handleMetadataPush(Payload payload) { CountDownLatch latch = new CountDownLatch(2); - ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost", "localhost"); Observable @@ -199,19 +199,60 @@ public void onNext(Payload s) { latch.await(); } + @Test - public void testReconnect() throws Exception { + public void createTwoServersAndTwoClients()throws Exception { + Random random = new Random(); + byte[] b = new byte[1_000_000]; + random.nextBytes(b); + ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { @Override public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { return new RequestHandler() { - Frame frame = Frame.from(ByteBuffer.allocate(1)); + @Override + public Publisher handleRequestResponse(Payload payload) { + System.out.println("Server got => " + b.length); + Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong server 1", null)); + return RxReactiveStreams.toPublisher(pong); + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher payloads) { + return null; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + return null; + } + + @Override + public Publisher handleSubscription(Payload payload) { + return null; + } + @Override + public Publisher handleFireAndForget(Payload payload) { + return null; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + }; + } + }); + + ReactiveSocketAeronServer.create(12345, new ConnectionSetupHandler() { + @Override + public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + return new RequestHandler() { @Override public Publisher handleRequestResponse(Payload payload) { - String request = TestUtil.byteToString(payload.getData()); - //System.out.println("Server got => " + request); - Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong", null)); + System.out.println("Server got => " + b.length); + Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong server 2", null)); return RxReactiveStreams.toPublisher(pong); } @@ -243,15 +284,29 @@ public Publisher handleMetadataPush(Payload payload) { } }); - CountDownLatch latch = new CountDownLatch(1); + CountDownLatch latch = new CountDownLatch(2 * 130); ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost", "localhost"); + ReactivesocketAeronClient client2 = ReactivesocketAeronClient.create("localhost", "localhost", 12345); + Observable - .range(1, 1) + .range(1, 130) .flatMap(i -> { - //System.out.println("pinging => " + i); - Payload payload = TestUtil.utf8EncodedPayload("ping =>" + i, null); + System.out.println("pinging server 1 => " + i); + Payload payload = new Payload() { + @Override + public ByteBuffer getData() { + return ByteBuffer.wrap(b); + } + + @Override + public ByteBuffer getMetadata() { + return ByteBuffer.allocate(0); + } + }; + + latch.countDown(); return RxReactiveStreams.toObservable(client.requestResponse(payload)); } ) @@ -267,26 +322,46 @@ public void onError(Throwable e) { @Override public void onNext(Payload s) { - //System.out.println(s + " countdown => " + latch.getCount()); - latch.countDown(); + System.out.println(s + " countdown server 1 => " + latch.getCount()); } }); - latch.await(); - - int serverSessionId = client.serverSessionId; - int sessionId = client.sessionId; - - Assert.assertNotNull(client.connections.get(serverSessionId)); + Observable + .range(1, 130) + .flatMap(i -> { + System.out.println("pinging server 2 => " + i); + Payload payload = new Payload() { + @Override + public ByteBuffer getData() { + return ByteBuffer.wrap(b); + } - client.close(); + @Override + public ByteBuffer getMetadata() { + return ByteBuffer.allocate(0); + } + }; + latch.countDown(); + return RxReactiveStreams.toObservable(client2.requestResponse(payload)); + } + ) + .subscribe(new rx.Subscriber() { + @Override + public void onCompleted() { + } - Assert.assertNull(client.connections.get(serverSessionId)); - Assert.assertNull(client.establishConnectionLatches.get(sessionId)); + @Override + public void onError(Throwable e) { + e.printStackTrace(); + } - ReactivesocketAeronClient newConnection = ReactivesocketAeronClient.create("localhost", "localhost"); + @Override + public void onNext(Payload s) { + System.out.println(s + " countdown server 2 => " + latch.getCount()); + } + }); + latch.await(); } - } diff --git a/src/test/java/io/reactivesocket/aeron/client/ReactivesocketAeronClientTest.java b/src/test/java/io/reactivesocket/aeron/client/ReactivesocketAeronClientTest.java new file mode 100644 index 000000000..dc6cee952 --- /dev/null +++ b/src/test/java/io/reactivesocket/aeron/client/ReactivesocketAeronClientTest.java @@ -0,0 +1,122 @@ +package io.reactivesocket.aeron.client; + +import io.reactivesocket.ConnectionSetupHandler; +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.Frame; +import io.reactivesocket.Payload; +import io.reactivesocket.RequestHandler; +import io.reactivesocket.aeron.TestUtil; +import io.reactivesocket.aeron.server.ReactiveSocketAeronServer; +import io.reactivesocket.exceptions.SetupException; +import junit.framework.Assert; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.reactivestreams.Publisher; +import rx.Observable; +import rx.RxReactiveStreams; +import uk.co.real_logic.aeron.driver.MediaDriver; + +import java.nio.ByteBuffer; +import java.util.concurrent.CountDownLatch; + +@Ignore +public class ReactivesocketAeronClientTest { + + @BeforeClass + public static void init() { + final MediaDriver.Context context = new MediaDriver.Context(); + context.dirsDeleteOnStart(true); + + final MediaDriver mediaDriver = MediaDriver.launch(context); + } + + @Test + public void testReconnect() throws Exception { + ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { + @Override + public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + return new RequestHandler() { + Frame frame = Frame.from(ByteBuffer.allocate(1)); + + @Override + public Publisher handleRequestResponse(Payload payload) { + String request = TestUtil.byteToString(payload.getData()); + //System.out.println("Server got => " + request); + Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong", null)); + return RxReactiveStreams.toPublisher(pong); + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher payloads) { + return null; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + return null; + } + + @Override + public Publisher handleSubscription(Payload payload) { + return null; + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return null; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + }; + } + }); + + CountDownLatch latch = new CountDownLatch(1); + + ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost", "localhost"); + + Observable + .range(1, 1) + .flatMap(i -> { + //System.out.println("pinging => " + i); + Payload payload = TestUtil.utf8EncodedPayload("ping =>" + i, null); + return RxReactiveStreams.toObservable(client.requestResponse(payload)); + } + ) + .subscribe(new rx.Subscriber() { + @Override + public void onCompleted() { + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + } + + @Override + public void onNext(Payload s) { + //System.out.println(s + " countdown => " + latch.getCount()); + latch.countDown(); + } + }); + + latch.await(); + + int serverSessionId = client.serverSessionId; + int sessionId = client.sessionId; + + Assert.assertNotNull(client.connections.get(serverSessionId)); + + client.close(); + + Assert.assertNull(client.connections.get(serverSessionId)); + Assert.assertNull(client.establishConnectionLatches.get(sessionId)); + + ReactivesocketAeronClient newConnection = ReactivesocketAeronClient.create("localhost", "localhost"); + + } +} \ No newline at end of file From ddb092aa42201b938b9d06859b70de04a2ac8a98 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Wed, 16 Sep 2015 17:22:36 -0700 Subject: [PATCH 026/105] breaking out client into single thread version, and another that schedules it's work on the RXJava computation event loops --- .../multi/AeronClientDuplexConnection.java | 86 ++++++ .../aeron/client/multi/FrameHolder.java | 52 ++++ .../multi/ReactivesocketAeronClient.java | 7 + .../AeronClientDuplexConnection.java | 17 +- .../client/{ => single}/FrameHolder.java | 2 +- .../ReactivesocketAeronClient.java | 2 +- .../aeron/internal/Constants.java | 3 + .../AbstractConcurrentArrayQueue.java | 278 ++++++++++++++++++ .../ManyToManyConcurrentArrayQueue.java | 173 +++++++++++ .../aeron/internal/concurrent/Pipe.java | 75 +++++ .../aeron/internal/concurrent/QueuedPipe.java | 28 ++ .../aeron/ReactiveSocketAeronTest.java | 2 +- .../ReactivesocketAeronClientTest.java | 2 +- 13 files changed, 715 insertions(+), 12 deletions(-) create mode 100644 src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java create mode 100644 src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java create mode 100644 src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java rename src/main/java/io/reactivesocket/aeron/client/{ => single}/AeronClientDuplexConnection.java (82%) rename src/main/java/io/reactivesocket/aeron/client/{ => single}/FrameHolder.java (96%) rename src/main/java/io/reactivesocket/aeron/client/{ => single}/ReactivesocketAeronClient.java (99%) create mode 100644 src/main/java/io/reactivesocket/aeron/internal/concurrent/AbstractConcurrentArrayQueue.java create mode 100644 src/main/java/io/reactivesocket/aeron/internal/concurrent/ManyToManyConcurrentArrayQueue.java create mode 100644 src/main/java/io/reactivesocket/aeron/internal/concurrent/Pipe.java create mode 100644 src/main/java/io/reactivesocket/aeron/internal/concurrent/QueuedPipe.java rename src/test/java/io/reactivesocket/aeron/client/{ => single}/ReactivesocketAeronClientTest.java (98%) diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java new file mode 100644 index 000000000..24416d720 --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java @@ -0,0 +1,86 @@ +package io.reactivesocket.aeron.client.multi; + + +import io.reactivesocket.Completable; +import io.reactivesocket.DuplexConnection; +import io.reactivesocket.Frame; +import io.reactivesocket.aeron.internal.Constants; +import io.reactivesocket.aeron.internal.concurrent.ManyToManyConcurrentArrayQueue; +import io.reactivesocket.internal.EmptyDisposable; +import io.reactivesocket.observable.Observable; +import io.reactivesocket.observable.Observer; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import rx.Scheduler; +import rx.schedulers.Schedulers; +import uk.co.real_logic.aeron.Publication; + +public class AeronClientDuplexConnection implements DuplexConnection, AutoCloseable { + private Publication publication; + private Observer observer; + private Observable observable; + private ManyToManyConcurrentArrayQueue framesSendQueue; + private Scheduler.Worker worker; + + public AeronClientDuplexConnection(Publication publication, ManyToManyConcurrentArrayQueue framesSendQueue) { + this.publication = publication; + this.framesSendQueue = framesSendQueue; + this.observable = (Observer o) -> { + observer = o; + observer.onSubscribe(new EmptyDisposable()); + }; + this.worker = Schedulers.computation().createWorker(); + + } + + public Observer getSubscriber() { + return observer; + } + + public Observable getInput() { + return observable; + } + + @Override + public void addOutput(Publisher o, Completable callback) { + o.subscribe(new Subscriber() { + volatile boolean running = true; + Subscription s; + @Override + public void onSubscribe(Subscription s) { + this.s = s; + s.request(framesSendQueue.remainingCapacity() + 1); + } + + @Override + public void onNext(final Frame frame) { + final FrameHolder frameHolder = FrameHolder.get(frame, publication); + int limit = Constants.MULTI_THREADED_SPIN_LIMIT; + while (running && !framesSendQueue.offer(frameHolder)) { + if (--limit < 0) { + worker.schedule(() -> onNext(frame)); + } + } + s.request(framesSendQueue.remainingCapacity() + 1); + } + + @Override + public void onError(Throwable t) { + running = false; + callback.error(t); + } + + @Override + public void onComplete() { + running = false; + callback.success(); + } + }); + } + + @Override + public void close() { + worker.unsubscribe(); + } +} diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java b/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java new file mode 100644 index 000000000..8c17bb8b8 --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java @@ -0,0 +1,52 @@ +package io.reactivesocket.aeron.client.multi; + +import io.reactivesocket.Frame; +import io.reactivesocket.aeron.internal.Constants; +import io.reactivesocket.aeron.internal.concurrent.ManyToManyConcurrentArrayQueue; +import uk.co.real_logic.aeron.Publication; + +/** + * Holds a frame and the publication that it's supposed to be sent on. + * Pools instances on an {@link uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue} + */ +class FrameHolder { + private static ManyToManyConcurrentArrayQueue FRAME_HOLDER_QUEUE + = new ManyToManyConcurrentArrayQueue<>(Constants.QUEUE_SIZE); + + static { + for (int i = 0; i < Constants.QUEUE_SIZE; i++) { + FRAME_HOLDER_QUEUE.offer(new FrameHolder()); + } + } + + private Publication publication; + private Frame frame; + + private FrameHolder() {} + + public static FrameHolder get(Frame frame, Publication publication) { + FrameHolder frameHolder = FRAME_HOLDER_QUEUE.poll(); + + if (frameHolder == null) { + frameHolder = new FrameHolder(); + } + + frameHolder.frame = frame; + frameHolder.publication = publication; + + return frameHolder; + } + + public Publication getPublication() { + return publication; + } + + public Frame getFrame() { + return frame; + } + + public void release() { + frame.release(); + FRAME_HOLDER_QUEUE.offer(this); + } +} diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java new file mode 100644 index 000000000..ae71f6b66 --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java @@ -0,0 +1,7 @@ +package io.reactivesocket.aeron.client.multi; + +/** + * Created by rroeser on 9/16/15. + */ +public class ReactivesocketAeronClient { +} diff --git a/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/single/AeronClientDuplexConnection.java similarity index 82% rename from src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java rename to src/main/java/io/reactivesocket/aeron/client/single/AeronClientDuplexConnection.java index 9d2ccb6fa..a5d117110 100644 --- a/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/single/AeronClientDuplexConnection.java @@ -1,4 +1,4 @@ -package io.reactivesocket.aeron.client; +package io.reactivesocket.aeron.client.single; import io.reactivesocket.Completable; import io.reactivesocket.DuplexConnection; @@ -10,23 +10,25 @@ import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.agrona.concurrent.IdleStrategy; import uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue; +import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; public class AeronClientDuplexConnection implements DuplexConnection, AutoCloseable { private Publication publication; private Observer observer; private Observable observable; private ManyToOneConcurrentArrayQueue framesSendQueue; + private IdleStrategy idleStrategy; public AeronClientDuplexConnection(Publication publication, ManyToOneConcurrentArrayQueue framesSendQueue) { - - System.out.println("publication => " + publication.toString()); this.publication = publication; this.framesSendQueue = framesSendQueue; this.observable = (Observer o) -> { observer = o; observer.onSubscribe(new EmptyDisposable()); }; + this.idleStrategy = new NoOpIdleStrategy(); } @@ -46,16 +48,15 @@ public void addOutput(Publisher o, Completable callback) { @Override public void onSubscribe(Subscription s) { this.s = s; - s.request(1); + s.request(framesSendQueue.remainingCapacity() + 1); + //s.request(Long.MAX_VALUE); } @Override public void onNext(Frame frame) { final FrameHolder frameHolder = FrameHolder.get(frame, publication); - while (running && !framesSendQueue.offer(frameHolder)) { - - } - s.request(1); + while (running && !framesSendQueue.offer(frameHolder)) idleStrategy.idle(-1); + s.request(framesSendQueue.remainingCapacity() + 1); } @Override diff --git a/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java b/src/main/java/io/reactivesocket/aeron/client/single/FrameHolder.java similarity index 96% rename from src/main/java/io/reactivesocket/aeron/client/FrameHolder.java rename to src/main/java/io/reactivesocket/aeron/client/single/FrameHolder.java index c5f0f7711..0a9a18029 100644 --- a/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java +++ b/src/main/java/io/reactivesocket/aeron/client/single/FrameHolder.java @@ -1,4 +1,4 @@ -package io.reactivesocket.aeron.client; +package io.reactivesocket.aeron.client.single; import io.reactivesocket.Frame; import io.reactivesocket.aeron.internal.Constants; diff --git a/src/main/java/io/reactivesocket/aeron/client/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/client/single/ReactivesocketAeronClient.java similarity index 99% rename from src/main/java/io/reactivesocket/aeron/client/ReactivesocketAeronClient.java rename to src/main/java/io/reactivesocket/aeron/client/single/ReactivesocketAeronClient.java index 258761a0f..723d9f71f 100644 --- a/src/main/java/io/reactivesocket/aeron/client/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/client/single/ReactivesocketAeronClient.java @@ -1,4 +1,4 @@ -package io.reactivesocket.aeron.client; +package io.reactivesocket.aeron.client.single; import com.gs.collections.impl.map.mutable.ConcurrentHashMap; import io.reactivesocket.Completable; diff --git a/src/main/java/io/reactivesocket/aeron/internal/Constants.java b/src/main/java/io/reactivesocket/aeron/internal/Constants.java index ccaf6d76a..854da8947 100644 --- a/src/main/java/io/reactivesocket/aeron/internal/Constants.java +++ b/src/main/java/io/reactivesocket/aeron/internal/Constants.java @@ -11,4 +11,7 @@ private Constants() {} public static final byte[] EMTPY = new byte[0]; public static final int QUEUE_SIZE = Integer.getInteger("framesSendQueueSize", 128); + + public static final int MULTI_THREADED_SPIN_LIMIT = Integer.getInteger("multiSpinLimit", 100); + } diff --git a/src/main/java/io/reactivesocket/aeron/internal/concurrent/AbstractConcurrentArrayQueue.java b/src/main/java/io/reactivesocket/aeron/internal/concurrent/AbstractConcurrentArrayQueue.java new file mode 100644 index 000000000..c8e489afe --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/internal/concurrent/AbstractConcurrentArrayQueue.java @@ -0,0 +1,278 @@ +package io.reactivesocket.aeron.internal.concurrent; + +/* + * Copyright 2015 Real Logic Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.Collection; + + import uk.co.real_logic.agrona.BitUtil; + + import java.util.*; + + import static uk.co.real_logic.agrona.UnsafeAccess.UNSAFE; + +/** + * Pad out a cacheline to the left of a tail to prevent false sharing. + */ +class AbstractConcurrentArrayQueuePadding1 +{ + @SuppressWarnings("unused") + protected long p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15; +} + +/** + * Value for the tail that is expected to be padded. + */ +class AbstractConcurrentArrayQueueTail extends AbstractConcurrentArrayQueuePadding1 +{ + protected volatile long tail; +} + +/** + * Pad out a cacheline between the tail and the head to prevent false sharing. + */ +class AbstractConcurrentArrayQueuePadding2 extends AbstractConcurrentArrayQueueTail +{ + @SuppressWarnings("unused") + protected long p16, p17, p18, p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30; +} + +/** + * Value for the head that is expected to be padded. + */ +class AbstractConcurrentArrayQueueHead extends AbstractConcurrentArrayQueuePadding2 +{ + protected volatile long head; +} + +/** + * Pad out a cacheline between the tail and the head to prevent false sharing. + */ +class AbstractConcurrentArrayQueuePadding3 extends AbstractConcurrentArrayQueueHead +{ + @SuppressWarnings("unused") + protected long p31, p32, p33, p34, p35, p36, p37, p38, p39, p40, p41, p42, p43, p44, p45; +} + +/** + * Left over immutable queue fields. + */ +public abstract class AbstractConcurrentArrayQueue + extends AbstractConcurrentArrayQueuePadding3 + implements QueuedPipe +{ + protected static final long TAIL_OFFSET; + protected static final long HEAD_OFFSET; + protected static final int BUFFER_ARRAY_BASE; + protected static final int SHIFT_FOR_SCALE; + + static + { + try + { + BUFFER_ARRAY_BASE = UNSAFE.arrayBaseOffset(Object[].class); + SHIFT_FOR_SCALE = BitUtil.calculateShiftForScale(UNSAFE.arrayIndexScale(Object[].class)); + TAIL_OFFSET = UNSAFE.objectFieldOffset(AbstractConcurrentArrayQueueTail.class.getDeclaredField("tail")); + HEAD_OFFSET = UNSAFE.objectFieldOffset(AbstractConcurrentArrayQueueHead.class.getDeclaredField("head")); + } + catch (final Exception ex) + { + throw new RuntimeException(ex); + } + } + + protected final long mask; + protected final int capacity; + protected final E[] buffer; + + @SuppressWarnings("unchecked") + public AbstractConcurrentArrayQueue(final int requestedCapacity) + { + capacity = BitUtil.findNextPositivePowerOfTwo(requestedCapacity); + mask = capacity - 1; + buffer = (E[])new Object[capacity]; + } + + public long addedCount() + { + return tail; + } + + public long removedCount() + { + return head; + } + + public int capacity() + { + return capacity; + } + + public int remainingCapacity() + { + return capacity() - size(); + } + + @SuppressWarnings("unchecked") + public E peek() + { + return (E)UNSAFE.getObjectVolatile(buffer, sequenceToBufferOffset(head, mask)); + } + + public boolean add(final E e) + { + if (offer(e)) + { + return true; + } + + throw new IllegalStateException("Queue is full"); + } + + public E remove() + { + final E e = poll(); + if (null == e) + { + throw new NoSuchElementException("Queue is empty"); + } + + return e; + } + + public E element() + { + final E e = peek(); + if (null == e) + { + throw new NoSuchElementException("Queue is empty"); + } + + return e; + } + + public boolean isEmpty() + { + return tail == head; + } + + public boolean contains(final Object o) + { + if (null == o) + { + return false; + } + + final Object[] buffer = this.buffer; + + for (long i = head, limit = tail; i < limit; i++) + { + final Object e = UNSAFE.getObjectVolatile(buffer, sequenceToBufferOffset(i, mask)); + if (o.equals(e)) + { + return true; + } + } + + return false; + } + + public Iterator iterator() + { + throw new UnsupportedOperationException(); + } + + public Object[] toArray() + { + throw new UnsupportedOperationException(); + } + + public T[] toArray(final T[] a) + { + throw new UnsupportedOperationException(); + } + + public boolean remove(final Object o) + { + throw new UnsupportedOperationException(); + } + + public boolean containsAll(final Collection c) + { + for (final Object o : c) + { + if (!contains(o)) + { + return false; + } + } + + return true; + } + + public boolean addAll(final Collection c) + { + for (final E e : c) + { + add(e); + } + + return true; + } + + public boolean removeAll(final Collection c) + { + throw new UnsupportedOperationException(); + } + + public boolean retainAll(final Collection c) + { + throw new UnsupportedOperationException(); + } + + public void clear() + { + Object value; + do + { + value = poll(); + } + while (null != value); + } + + public int size() + { + long currentHeadBefore; + long currentTail; + long currentHeadAfter = head; + + do + { + currentHeadBefore = currentHeadAfter; + currentTail = tail; + currentHeadAfter = head; + + } + while (currentHeadAfter != currentHeadBefore); + + return (int)(currentTail - currentHeadAfter); + } + + public static long sequenceToBufferOffset(final long sequence, final long mask) + { + return BUFFER_ARRAY_BASE + ((sequence & mask) << SHIFT_FOR_SCALE); + } +} \ No newline at end of file diff --git a/src/main/java/io/reactivesocket/aeron/internal/concurrent/ManyToManyConcurrentArrayQueue.java b/src/main/java/io/reactivesocket/aeron/internal/concurrent/ManyToManyConcurrentArrayQueue.java new file mode 100644 index 000000000..96e3c0c62 --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/internal/concurrent/ManyToManyConcurrentArrayQueue.java @@ -0,0 +1,173 @@ +package io.reactivesocket.aeron.internal.concurrent; + +/* + * Copyright 2015 Real Logic Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import java.util.Collection; +import java.util.function.Consumer; + +import static uk.co.real_logic.agrona.UnsafeAccess.UNSAFE; + +/** + * Many producer to many consumer concurrent queue that is array backed. + * + * + * This is a Java port of the + * MPMC queue by Dmitry Vyukov. + * + * Note: This queue breaks the contract for peek and poll in that it can return null when the queue has no node available + * but is not empty. This is a conflated design issue in the Queue implementation. If you wish to check for empty then call + * {@link ManyToManyConcurrentArrayQueue#isEmpty()}. + * + * @param type of the elements stored in the {@link java.util.Queue}. + */ +public class ManyToManyConcurrentArrayQueue extends AbstractConcurrentArrayQueue +{ + private static final int SEQUENCES_ARRAY_BASE; + + static + { + try + { + SEQUENCES_ARRAY_BASE = UNSAFE.arrayBaseOffset(long[].class); + } + catch (final Exception ex) + { + throw new RuntimeException(ex); + } + } + + private final long[] sequences; + + public ManyToManyConcurrentArrayQueue(final int requestedCapacity) + { + super(requestedCapacity); + + final long[] sequences = new long[capacity]; + + for (int i = 0, size = capacity; i < size; i++) + { + final long sequenceOffset = sequenceArrayOffset(i, mask); + UNSAFE.putOrderedLong(sequences, sequenceOffset, i); + } + + this.sequences = sequences; + } + + public boolean offer(final E e) + { + if (null == e) + { + throw new NullPointerException("element cannot be null"); + } + + final long mask = this.mask; + final long[] sequences = this.sequences; + + do + { + final long currentTail = tail; + final long sequenceOffset = sequenceArrayOffset(currentTail, mask); + final long sequence = UNSAFE.getLongVolatile(sequences, sequenceOffset); + + if (sequence < currentTail) + { + return false; + } + + if (UNSAFE.compareAndSwapLong(this, TAIL_OFFSET, currentTail, currentTail + 1L)) + { + UNSAFE.putObject(buffer, sequenceToBufferOffset(currentTail, mask), e); + UNSAFE.putOrderedLong(sequences, sequenceOffset, currentTail + 1L); + + return true; + } + } + while (true); + } + + @SuppressWarnings("unchecked") + public E poll() + { + final long[] sequences = this.sequences; + final long mask = this.mask; + + do + { + final long currentHead = head; + final long sequenceOffset = sequenceArrayOffset(currentHead, mask); + final long sequence = UNSAFE.getLongVolatile(sequences, sequenceOffset); + final long attemptedHead = currentHead + 1L; + + if (sequence < attemptedHead) + { + return null; + } + + if (UNSAFE.compareAndSwapLong(this, HEAD_OFFSET, currentHead, attemptedHead)) + { + final long elementOffset = sequenceToBufferOffset(currentHead, mask); + + final Object e = UNSAFE.getObject(buffer, elementOffset); + UNSAFE.putObject(buffer, elementOffset, null); + UNSAFE.putOrderedLong(sequences, sequenceOffset, attemptedHead + mask); + + return (E)e; + } + } + while (true); + } + + public int drain(final Consumer elementHandler) + { + final int size = size(); + int count = 0; + + E e; + while (count < size && null != (e = poll())) + { + elementHandler.accept(e); + ++count; + } + + return count; + } + + public int drainTo(final Collection target, final int limit) + { + int count = 0; + + while (count < limit) + { + final E e = poll(); + if (null == e) + { + break; + } + + target.add(e); + ++count; + } + + return count; + } + + private static long sequenceArrayOffset(final long sequence, final long mask) + { + return SEQUENCES_ARRAY_BASE + ((sequence & mask) << 3); + } +} \ No newline at end of file diff --git a/src/main/java/io/reactivesocket/aeron/internal/concurrent/Pipe.java b/src/main/java/io/reactivesocket/aeron/internal/concurrent/Pipe.java new file mode 100644 index 000000000..f76f3231a --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/internal/concurrent/Pipe.java @@ -0,0 +1,75 @@ +package io.reactivesocket.aeron.internal.concurrent; + +/* + * Copyright 2015 Real Logic Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.Collection; +import java.util.function.Consumer; + +/** + * A container for items processed in sequence + */ +public interface Pipe +{ + /** + * The number of items added to this container since creation. + * + * @return the number of items added. + */ + long addedCount(); + + /** + * The number of items removed from this container since creation. + * + * @return the number of items removed. + */ + long removedCount(); + + /** + * The maximum capacity of this container to hold items. + * + * @return the capacity of the container. + */ + int capacity(); + + /** + * Get the remaining capacity for elements in the container given the current size. + * + * @return remaining capacity of the container + */ + int remainingCapacity(); + + /** + * Invoke a {@link Consumer} callback on each elements to drain the collection of elements until it is empty. + * + * If possible, implementations should use smart batching to best handle burst traffic. + * + * @param elementHandler to callback for processing elements + * @return the number of elements drained + */ + int drain(Consumer elementHandler); + + /** + * Drain available elements into the provided {@link java.util.Collection} up to a provided maximum limit of elements. + * + * If possible, implementations should use smart batching to best handle burst traffic. + * + * @param target in to which elements are drained. + * @param limit of the maximum number of elements to drain. + * @return the number of elements actually drained. + */ + int drainTo(Collection target, int limit); +} \ No newline at end of file diff --git a/src/main/java/io/reactivesocket/aeron/internal/concurrent/QueuedPipe.java b/src/main/java/io/reactivesocket/aeron/internal/concurrent/QueuedPipe.java new file mode 100644 index 000000000..5a9ed0875 --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/internal/concurrent/QueuedPipe.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015 Real Logic Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.internal.concurrent; + + +import java.util.Queue; + +/** + * Composed interface for concurrent queues and sequenced containers. + * + * @param type of the elements stored in the {@link java.util.Queue}. + */ +public interface QueuedPipe extends Queue, Pipe +{ +} \ No newline at end of file diff --git a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java index 964c7c958..3831706d1 100644 --- a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java @@ -5,7 +5,7 @@ import io.reactivesocket.Frame; import io.reactivesocket.Payload; import io.reactivesocket.RequestHandler; -import io.reactivesocket.aeron.client.ReactivesocketAeronClient; +import io.reactivesocket.aeron.client.single.ReactivesocketAeronClient; import io.reactivesocket.aeron.server.ReactiveSocketAeronServer; import io.reactivesocket.exceptions.SetupException; import org.junit.BeforeClass; diff --git a/src/test/java/io/reactivesocket/aeron/client/ReactivesocketAeronClientTest.java b/src/test/java/io/reactivesocket/aeron/client/single/ReactivesocketAeronClientTest.java similarity index 98% rename from src/test/java/io/reactivesocket/aeron/client/ReactivesocketAeronClientTest.java rename to src/test/java/io/reactivesocket/aeron/client/single/ReactivesocketAeronClientTest.java index dc6cee952..9b95d00f0 100644 --- a/src/test/java/io/reactivesocket/aeron/client/ReactivesocketAeronClientTest.java +++ b/src/test/java/io/reactivesocket/aeron/client/single/ReactivesocketAeronClientTest.java @@ -1,4 +1,4 @@ -package io.reactivesocket.aeron.client; +package io.reactivesocket.aeron.client.single; import io.reactivesocket.ConnectionSetupHandler; import io.reactivesocket.ConnectionSetupPayload; From 64c9c007b6aa63d5a402df571fd46a852a53d105 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Thu, 17 Sep 2015 13:06:43 -0700 Subject: [PATCH 027/105] threading working needs to be cleaned up --- .../multi/AeronClientDuplexConnection.java | 20 +- .../multi/ReactivesocketAeronClient.java | 489 +++++++++++++++++- .../server/ReactiveSocketAeronServer.java | 1 + .../client/multi/ReactiveSocketAeronTest.java | 368 +++++++++++++ 4 files changed, 873 insertions(+), 5 deletions(-) create mode 100644 src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java index 24416d720..6c38d304d 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java @@ -16,6 +16,8 @@ import rx.schedulers.Schedulers; import uk.co.real_logic.aeron.Publication; +import java.util.concurrent.atomic.AtomicLong; + public class AeronClientDuplexConnection implements DuplexConnection, AutoCloseable { private Publication publication; private Observer observer; @@ -23,9 +25,9 @@ public class AeronClientDuplexConnection implements DuplexConnection, AutoClosea private ManyToManyConcurrentArrayQueue framesSendQueue; private Scheduler.Worker worker; - public AeronClientDuplexConnection(Publication publication, ManyToManyConcurrentArrayQueue framesSendQueue) { + public AeronClientDuplexConnection(Publication publication) { this.publication = publication; - this.framesSendQueue = framesSendQueue; + this.framesSendQueue = new ManyToManyConcurrentArrayQueue<>(128); this.observable = (Observer o) -> { observer = o; observer.onSubscribe(new EmptyDisposable()); @@ -38,10 +40,13 @@ public Observer getSubscriber() { return observer; } + @Override public Observable getInput() { return observable; } + static final AtomicLong count = new AtomicLong(); + @Override public void addOutput(Publisher o, Completable callback) { o.subscribe(new Subscriber() { @@ -57,12 +62,15 @@ public void onSubscribe(Subscription s) { public void onNext(final Frame frame) { final FrameHolder frameHolder = FrameHolder.get(frame, publication); int limit = Constants.MULTI_THREADED_SPIN_LIMIT; - while (running && !framesSendQueue.offer(frameHolder)) { + if (running && !framesSendQueue.offer(frameHolder)) { if (--limit < 0) { worker.schedule(() -> onNext(frame)); } } - s.request(framesSendQueue.remainingCapacity() + 1); + + final int r = framesSendQueue.remainingCapacity() + 1; + s.request(r); + } @Override @@ -79,6 +87,10 @@ public void onComplete() { }); } + public ManyToManyConcurrentArrayQueue getFramesSendQueue() { + return framesSendQueue; + } + @Override public void close() { worker.unsubscribe(); diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java index ae71f6b66..3520216f9 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java @@ -1,7 +1,494 @@ package io.reactivesocket.aeron.client.multi; +import io.reactivesocket.Completable; +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.Frame; +import io.reactivesocket.Payload; +import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.aeron.internal.Constants; +import io.reactivesocket.aeron.internal.Loggable; +import io.reactivesocket.aeron.internal.MessageType; +import io.reactivesocket.aeron.internal.concurrent.ManyToManyConcurrentArrayQueue; +import io.reactivesocket.observable.Observer; +import org.reactivestreams.Publisher; +import rx.Scheduler; +import rx.schedulers.Schedulers; +import uk.co.real_logic.aeron.Aeron; +import uk.co.real_logic.aeron.FragmentAssembler; +import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.aeron.Subscription; +import uk.co.real_logic.aeron.logbuffer.BufferClaim; +import uk.co.real_logic.aeron.logbuffer.Header; +import uk.co.real_logic.agrona.BitUtil; +import uk.co.real_logic.agrona.DirectBuffer; +import uk.co.real_logic.agrona.LangUtil; +import uk.co.real_logic.agrona.MutableDirectBuffer; +import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.LockSupport; + +import static io.reactivesocket.aeron.internal.Constants.CLIENT_STREAM_ID; +import static io.reactivesocket.aeron.internal.Constants.EMTPY; +import static io.reactivesocket.aeron.internal.Constants.SERVER_STREAM_ID; + + /** * Created by rroeser on 9/16/15. */ -public class ReactivesocketAeronClient { +public class ReactivesocketAeronClient implements Loggable, AutoCloseable { + static final ArrayList SUBSCRIPTION_GROUPS = new ArrayList<>(); + + private class SubscriptionGroup { + String channel; + Subscription[] subscriptions; + } + + static final ConcurrentHashMap connections = new ConcurrentHashMap<>(); + + static final ConcurrentHashMap establishConnectionLatches = new ConcurrentHashMap<>(); + + static final ConcurrentHashMap publications = new ConcurrentHashMap<>(); + + static final ConcurrentHashMap reactiveSockets = new ConcurrentHashMap<>(); + + private final Aeron aeron; + + private volatile static boolean running = true; + + volatile int sessionId; + + volatile int serverSessionId; + + private static final CountDownLatch shutdownLatch = new CountDownLatch(1); + + private final int port; + + private static int mtuLength; + + private static final int NUM_PROCESSORS = Runtime.getRuntime().availableProcessors(); + + private static Scheduler.Worker[] workers; + + static { + Runtime + .getRuntime() + .addShutdownHook(new Thread(() -> { + running = false; + + try { + shutdownLatch.await(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + for (SubscriptionGroup subscriptionGroup : SUBSCRIPTION_GROUPS) { + for (Subscription subscription : subscriptionGroup.subscriptions) { + subscription.close(); + } + } + + for (AeronClientDuplexConnection connection : connections.values()) { + connection.close(); + } + })); + + mtuLength = Integer.getInteger("aeron.mtu.length", 4096); + workers = new Scheduler.Worker[NUM_PROCESSORS]; + + for (int i = 0; i < NUM_PROCESSORS; i++) { + workers[i] = Schedulers.computation().createWorker(); + } + } + + private static volatile boolean pollingStarted = false; + + private ReactivesocketAeronClient(String host, String server, int port) { + this.port = port; + + final Aeron.Context ctx = new Aeron.Context(); + ctx.errorHandler(t -> { + t.printStackTrace(); + }); + + aeron = Aeron.connect(ctx); + + final String channel = "udp://" + host + ":" + port; + final String subscriptionChannel = "udp://" + server + ":" + port; + + System.out.println("Creating a publication to channel => " + channel); + Publication publication = aeron.addPublication(channel, SERVER_STREAM_ID); + publications.putIfAbsent(publication.sessionId(), publication); + System.out.println("Creating publication => " + publication.toString()); + sessionId = publication.sessionId(); + + System.out.println("Created a publication for sessionId => " + sessionId); + synchronized (SUBSCRIPTION_GROUPS) { + boolean found = SUBSCRIPTION_GROUPS + .stream() + .anyMatch(sg -> subscriptionChannel.equals(sg.channel)); + if (!found) { + SubscriptionGroup subscriptionGroup = new SubscriptionGroup(); + subscriptionGroup.subscriptions = new Subscription[NUM_PROCESSORS]; + for (int i = 0; i < NUM_PROCESSORS; i++) { + System.out.println("Creating a subscription to channel => " + subscriptionChannel + ", and processing => " + i); + subscriptionGroup.subscriptions[i] = aeron.addSubscription(subscriptionChannel, CLIENT_STREAM_ID); + System.out.println("Subscription created to channel => " + subscriptionChannel + ", and processing => " + i); + } + SUBSCRIPTION_GROUPS.add(subscriptionGroup); + } + } + + if (!pollingStarted) { + System.out.println("Polling hasn't started yet - starting " + + Runtime.getRuntime().availableProcessors() + + " pollers"); + CyclicBarrier startBarrier = new CyclicBarrier(NUM_PROCESSORS + 1); + for (int i = 0; i < NUM_PROCESSORS; i++) { + System.out.println("Starting " + + i + + " poller"); + poll(i, Schedulers.computation().createWorker(), startBarrier); + } + + try { + startBarrier.await(30, TimeUnit.SECONDS); + } catch (Exception e) { + LangUtil.rethrowUnchecked(e); + } + + pollingStarted = true; + } + + establishConnection(publication, sessionId); + + } + + public static ReactivesocketAeronClient create(String host, String server, int port) { + return new ReactivesocketAeronClient(host, server, port); + } + + public static ReactivesocketAeronClient create(String host, String server) { + return create(host, server, 39790); + } + + static final AtomicLong atomicLong = new AtomicLong(); + + void fragmentHandler(int magicNumber, DirectBuffer buffer, int offset, int length, Header header) { + + //LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); + final int currentMagic = Math.abs((int) header.position() % NUM_PROCESSORS); + if (currentMagic != magicNumber) { + // System.out.println("NO LUV Thread => " + Thread.currentThread() + ", currentMagic => " + currentMagic + ", magicNumber => " + magicNumber + ", postion => " + header.position()); + return; + } + + //System.out.println("WOOOOHOOOOOO Thread => " + Thread.currentThread() + ", currentMagic => " + currentMagic + ", magicNumber => " + magicNumber + ", postion => " + header.position()); + + try { + int messageTypeInt = buffer.getInt(offset); + MessageType messageType = MessageType.from(messageTypeInt); + if (messageType == MessageType.FRAME) { + final AeronClientDuplexConnection connection = connections.get(header.sessionId()); + Observer subscriber = connection.getSubscriber(); + final ByteBuffer bytes = ByteBuffer.allocate(length); + buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); + final Frame frame = Frame.from(bytes); + System.out.println("$$$$ CLIENT GOT => " + frame.toString() + " - " + atomicLong.getAndIncrement()); + subscriber.onNext(frame); + } else if (messageType == MessageType.ESTABLISH_CONNECTION_RESPONSE) { + final int ackSessionId = buffer.getInt(offset + BitUtil.SIZE_OF_INT); + + CountDownLatch latch = establishConnectionLatches.remove(ackSessionId); + + if (latch == null) { + System.out.println(Thread.currentThread() + " => null"); + return; + } + + Publication publication = publications.get(ackSessionId); + serverSessionId = header.sessionId(); + System.out.println(String.format("Received establish connection ack for session id => %d, and server session id => %d", ackSessionId, serverSessionId)); + final AeronClientDuplexConnection connection = + connections + .computeIfAbsent(serverSessionId, (_p) -> + new AeronClientDuplexConnection(publication)); + + ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection( + connection, + ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS), + err -> err.printStackTrace()); + + reactiveSocket.start(new Completable() { + @Override + public void success() { + + } + + @Override + public void error(Throwable e) { + + } + }); + + reactiveSockets.putIfAbsent(ackSessionId, reactiveSocket); + + latch.countDown(); + + System.out.println("ReactiveSocket connected to Aeron session => " + ackSessionId); + } else { + debug("Unknown message type => " + messageTypeInt); + } + } catch (Throwable t) { + System.out.println("ERROR fragmentHandler"); + t.printStackTrace(); + error("error handling framement", t); + } + } + + void poll(final int magicNumber, final Scheduler.Worker worker, CyclicBarrier startBarrier) { + worker.schedule(() -> { + if (startBarrier != null && !pollingStarted) { + try { + System.out.println("Waiting... " + magicNumber); + startBarrier.await(30, TimeUnit.SECONDS); + System.out.println("We have waited... " + magicNumber); + } catch (Exception e) { + LangUtil.rethrowUnchecked(e); + } + } + + if (running) { + try { + final Collection values = connections.values(); + + if (values != null) { + values.forEach(connection -> { + ManyToManyConcurrentArrayQueue framesSendQueue = connection.getFramesSendQueue(); + framesSendQueue + .drain((FrameHolder fh) -> { + try { + Frame frame = fh.getFrame(); + final ByteBuffer byteBuffer = frame.getByteBuffer(); + final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; + + // If the length is less the MTU size send the message using tryClaim which does not fragment the message + // If the message is larger the the MTU size send it using offer. + if (length < mtuLength) { + tryClaim(fh.getPublication(), byteBuffer, length); + } else { + offer(fh.getPublication(), byteBuffer, length); + } + } catch (Throwable t) { + error("error draining send frame queue", t); + } finally { + fh.release(); + } + }); + }); + } + + try { + final FragmentAssembler fragmentAssembler = new FragmentAssembler( + (DirectBuffer buffer, int offset, int length, Header header) -> + fragmentHandler(magicNumber, buffer, offset, length, header)); + + + //System.out.println("processing subscriptions => " + magicNumber); + //LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); + SUBSCRIPTION_GROUPS + .forEach(subscriptionGroup -> { + //System.out.println("processing subscriptions in foreach => " + magicNumber); + //LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); + Subscription subscription = subscriptionGroup.subscriptions[magicNumber]; + subscription.poll(fragmentAssembler, Integer.MAX_VALUE); + }); + } catch (Throwable t) { + t.printStackTrace(); + error("error polling aeron subscription", t); + } + } catch (Throwable t) { + t.printStackTrace(); + } + finally { + poll(magicNumber, worker, null); + } + + } else { + shutdownLatch.countDown(); + } + + }); + } + + private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); + + private static final ThreadLocal unsafeBuffers = ThreadLocal.withInitial(() -> new UnsafeBuffer(Constants.EMTPY)); + + void offer(Publication publication, ByteBuffer byteBuffer, int length) { + final byte[] bytes = new byte[length]; + final UnsafeBuffer unsafeBuffer = unsafeBuffers.get(); + unsafeBuffer.wrap(bytes); + unsafeBuffer.putInt(0, MessageType.FRAME.getEncodedType()); + unsafeBuffer.putBytes(BitUtil.SIZE_OF_INT, byteBuffer, byteBuffer.capacity()); + do { + final long offer = publication.offer(unsafeBuffer); + if (offer >= 0) { + break; + } else if (Publication.NOT_CONNECTED == offer) { + throw new RuntimeException("not connected"); + } + } while(true); + + } + + void tryClaim(Publication publication, ByteBuffer byteBuffer, int length) { + final BufferClaim bufferClaim = bufferClaims.get(); + do { + final long offer = publication.tryClaim(length, bufferClaim); + if (offer >= 0) { + try { + final MutableDirectBuffer buffer = bufferClaim.buffer(); + final int offset = bufferClaim.offset(); + buffer.putInt(offset, MessageType.FRAME.getEncodedType()); + buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, 0, byteBuffer.capacity()); + } finally { + bufferClaim.commit(); + } + + break; + } else if (Publication.NOT_CONNECTED == offer) { + throw new RuntimeException("not connected"); + } + } while(true); + } + + + /** + * Establishes a connection between the client and server. Waits for 30 seconds before throwing a exception. + */ + void establishConnection(final Publication publication, final int sessionId) { + + try { + final UnsafeBuffer buffer = new UnsafeBuffer(EMTPY); + buffer.wrap(new byte[BitUtil.SIZE_OF_INT]); + buffer.putInt(0, MessageType.ESTABLISH_CONNECTION_REQUEST.getEncodedType()); + + CountDownLatch latch = new CountDownLatch(1); + establishConnectionLatches.put(sessionId, latch); + + long offer = -1; + final long start = System.nanoTime(); + for (;;) { + final long current = System.nanoTime(); + if ((current - start) > TimeUnit.SECONDS.toNanos(30)) { + throw new RuntimeException("Timed out waiting to establish connection for session id => " + sessionId); + } + + System.out.println(Thread.currentThread() + " - Sending establishConnection message"); + publication.offer(buffer); + LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); + + if (latch.getCount() == 0) { + break; + } + } + + debug("Connection established for channel => {}, stream id => {}", + publication.channel(), + publication.sessionId()); + } finally { + establishConnectionLatches.remove(sessionId); + } + + } + + public Publisher requestResponse(Payload payload) { + ReactiveSocket reactiveSocket = reactiveSockets.get(sessionId); + return reactiveSocket.requestResponse(payload); + } + + public Publisher fireAndForget(Payload payload) { + ReactiveSocket reactiveSocket = reactiveSockets.get(sessionId); + return reactiveSocket.fireAndForget(payload); + } + + public Publisher requestStream(Payload payload) { + ReactiveSocket reactiveSocket = reactiveSockets.get(sessionId); + return reactiveSocket.requestStream(payload); + } + + public Publisher requestSubscription(Payload payload) { + ReactiveSocket reactiveSocket = reactiveSockets.get(sessionId); + return reactiveSocket.requestSubscription(payload); + } + + public static boolean isRunning() { + return running; + } + + public static void setRunning(boolean running) { + ReactivesocketAeronClient.running = running; + } + + public int getSessionId() { + return sessionId; + } + + public void setSessionId(int sessionId) { + this.sessionId = sessionId; + } + + public int getPort() { + return port; + } + + public int getServerSessionId() { + return serverSessionId; + } + + public void setServerSessionId(int serverSessionId) { + this.serverSessionId = serverSessionId; + } + + public static boolean isPollingStarted() { + return pollingStarted; + } + + public static void setPollingStarted(boolean pollingStarted) { + ReactivesocketAeronClient.pollingStarted = pollingStarted; + } + + @Override + public void close() throws Exception { + // First clean up the different maps + // Remove the AeronDuplexConnection from the connections map + AeronClientDuplexConnection connection = connections.remove(serverSessionId); + + // This should already be removed but remove it just in case to be safe + establishConnectionLatches.remove(sessionId); + + // Close the different connections + closeQuietly(connection); + closeQuietly(reactiveSockets.get(sessionId)); + System.out.println("closing publication => " + publications.get(sessionId).toString()); + Publication publication = publications.remove(sessionId); + closeQuietly(publication); + + } + + private void closeQuietly(AutoCloseable closeable) { + try { + closeable.close(); + } catch (Throwable t) { + debug(t.getMessage(), t); + } + } } diff --git a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java index 443f71c19..75357ca83 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java @@ -125,6 +125,7 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) ByteBuffer bytes = ByteBuffer.allocate(length); buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); final Frame frame = Frame.from(bytes); + System.out.println("### Server Sending => " + frame); subscriber.onNext(frame); } } else if (MessageType.ESTABLISH_CONNECTION_REQUEST == type) { diff --git a/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java new file mode 100644 index 000000000..46bfac272 --- /dev/null +++ b/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java @@ -0,0 +1,368 @@ +package io.reactivesocket.aeron.client.multi; + +import io.reactivesocket.ConnectionSetupHandler; +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.Frame; +import io.reactivesocket.Payload; +import io.reactivesocket.RequestHandler; +import io.reactivesocket.aeron.TestUtil; +import io.reactivesocket.aeron.server.ReactiveSocketAeronServer; +import io.reactivesocket.exceptions.SetupException; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.reactivestreams.Publisher; +import rx.Observable; +import rx.RxReactiveStreams; +import uk.co.real_logic.aeron.driver.MediaDriver; + +import java.nio.ByteBuffer; +import java.util.Random; +import java.util.concurrent.CountDownLatch; + +/** + * Created by rroeser on 8/14/15. + */ +@Ignore +public class ReactiveSocketAeronTest { + @BeforeClass + public static void init() { + final MediaDriver.Context context = new MediaDriver.Context(); + context.dirsDeleteOnStart(true); + + final MediaDriver mediaDriver = MediaDriver.launch(context); + } + + @Test(timeout = 6000000) + public void testRequestReponse() throws Exception { + ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { + @Override + public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + return new RequestHandler() { + Frame frame = Frame.from(ByteBuffer.allocate(1)); + + @Override + public Publisher handleRequestResponse(Payload payload) { + String request = TestUtil.byteToString(payload.getData()); + System.out.println(Thread.currentThread() + " Server got => " + request); + Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong", null)); + return RxReactiveStreams.toPublisher(pong); + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher payloads) { + return null; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + return null; + } + + @Override + public Publisher handleSubscription(Payload payload) { + return null; + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return null; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + }; + } + }); + + CountDownLatch latch = new CountDownLatch(130); + + + ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost", "localhost"); + + Observable + .range(1, 130) + .flatMap(i -> { + //System.out.println("pinging => " + i); + Payload payload = TestUtil.utf8EncodedPayload("ping =>" + i, null); + return RxReactiveStreams.toObservable(client.requestResponse(payload)); + } + ) + .subscribe(new rx.Subscriber() { + @Override + public void onCompleted() { + System.out.println("I HAVE COMPLETED $$$$$$$$$$$$$$$$$$$$$$$$$$$$"); + } + + @Override + public void onError(Throwable e) { + System.out.println(Thread.currentThread() + " counted to => " + latch.getCount()); + e.printStackTrace(); + } + + @Override + public void onNext(Payload s) { + System.out.println(Thread.currentThread() + " countdown => " + latch.getCount()); + latch.countDown(); + } + }); + + latch.await(); + } + + @Test(timeout = 60000) + public void sendLargeMessage() throws Exception { + + Random random = new Random(); + byte[] b = new byte[1_000_000]; + random.nextBytes(b); + + ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { + @Override + public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + return new RequestHandler() { + @Override + public Publisher handleRequestResponse(Payload payload) { + System.out.println("Server got => " + b.length); + Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong", null)); + return RxReactiveStreams.toPublisher(pong); + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher payloads) { + return null; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + return null; + } + + @Override + public Publisher handleSubscription(Payload payload) { + return null; + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return null; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + }; + } + }); + + CountDownLatch latch = new CountDownLatch(2); + + ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost", "localhost"); + + Observable + .range(1, 2) + .flatMap(i -> { + System.out.println("pinging => " + i); + Payload payload = new Payload() { + @Override + public ByteBuffer getData() { + return ByteBuffer.wrap(b); + } + + @Override + public ByteBuffer getMetadata() { + return ByteBuffer.allocate(0); + } + }; + return RxReactiveStreams.toObservable(client.requestResponse(payload)); + } + ) + .subscribe(new rx.Subscriber() { + @Override + public void onCompleted() { + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + } + + @Override + public void onNext(Payload s) { + System.out.println(s + "countdown => " + latch.getCount()); + latch.countDown(); + } + }); + + latch.await(); + } + + + @Test + public void createTwoServersAndTwoClients()throws Exception { + Random random = new Random(); + byte[] b = new byte[1_000_000]; + random.nextBytes(b); + + ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { + @Override + public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + return new RequestHandler() { + @Override + public Publisher handleRequestResponse(Payload payload) { + System.out.println("Server got => " + b.length); + Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong server 1", null)); + return RxReactiveStreams.toPublisher(pong); + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher payloads) { + return null; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + return null; + } + + @Override + public Publisher handleSubscription(Payload payload) { + return null; + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return null; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + }; + } + }); + + ReactiveSocketAeronServer.create(12345, new ConnectionSetupHandler() { + @Override + public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + return new RequestHandler() { + @Override + public Publisher handleRequestResponse(Payload payload) { + System.out.println("Server got => " + b.length); + Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong server 2", null)); + return RxReactiveStreams.toPublisher(pong); + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher payloads) { + return null; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + return null; + } + + @Override + public Publisher handleSubscription(Payload payload) { + return null; + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return null; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + }; + } + }); + + CountDownLatch latch = new CountDownLatch(2 * 130); + + ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost", "localhost"); + + ReactivesocketAeronClient client2 = ReactivesocketAeronClient.create("localhost", "localhost", 12345); + + Observable + .range(1, 130) + .flatMap(i -> { + System.out.println("pinging server 1 => " + i); + Payload payload = new Payload() { + @Override + public ByteBuffer getData() { + return ByteBuffer.wrap(b); + } + + @Override + public ByteBuffer getMetadata() { + return ByteBuffer.allocate(0); + } + }; + + latch.countDown(); + return RxReactiveStreams.toObservable(client.requestResponse(payload)); + } + ) + .subscribe(new rx.Subscriber() { + @Override + public void onCompleted() { + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + } + + @Override + public void onNext(Payload s) { + System.out.println(s + " countdown server 1 => " + latch.getCount()); + } + }); + + Observable + .range(1, 130) + .flatMap(i -> { + System.out.println("pinging server 2 => " + i); + Payload payload = new Payload() { + @Override + public ByteBuffer getData() { + return ByteBuffer.wrap(b); + } + + @Override + public ByteBuffer getMetadata() { + return ByteBuffer.allocate(0); + } + }; + latch.countDown(); + return RxReactiveStreams.toObservable(client2.requestResponse(payload)); + } + ) + .subscribe(new rx.Subscriber() { + @Override + public void onCompleted() { + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + } + + @Override + public void onNext(Payload s) { + System.out.println(s + " countdown server 2 => " + latch.getCount()); + } + }); + + latch.await(); + } + +} From f57386707237be90cf8e162ac2b005f26da169b1 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Thu, 17 Sep 2015 16:49:15 -0700 Subject: [PATCH 028/105] uses count from server to move processing between threads --- .../multi/AeronClientDuplexConnection.java | 3 +- .../multi/ReactivesocketAeronClient.java | 45 ++++++++++++------- .../single/ReactivesocketAeronClient.java | 2 +- .../server/AeronServerDuplexConnection.java | 4 +- .../aeron/server/CompletableSubscription.java | 13 +++++- .../server/ReactiveSocketAeronServer.java | 2 +- .../client/multi/ReactiveSocketAeronTest.java | 2 +- 7 files changed, 47 insertions(+), 24 deletions(-) diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java index 6c38d304d..11ef15f8e 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java @@ -55,7 +55,8 @@ public void addOutput(Publisher o, Completable callback) { @Override public void onSubscribe(Subscription s) { this.s = s; - s.request(framesSendQueue.remainingCapacity() + 1); + s.request(Long.MAX_VALUE); + //s.request(framesSendQueue.remainingCapacity() + 1); } @Override diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java index 3520216f9..3570dd5c0 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java @@ -183,18 +183,25 @@ public static ReactivesocketAeronClient create(String host, String server) { void fragmentHandler(int magicNumber, DirectBuffer buffer, int offset, int length, Header header) { - //LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); - final int currentMagic = Math.abs((int) header.position() % NUM_PROCESSORS); - if (currentMagic != magicNumber) { - // System.out.println("NO LUV Thread => " + Thread.currentThread() + ", currentMagic => " + currentMagic + ", magicNumber => " + magicNumber + ", postion => " + header.position()); - return; - } - - //System.out.println("WOOOOHOOOOOO Thread => " + Thread.currentThread() + ", currentMagic => " + currentMagic + ", magicNumber => " + magicNumber + ", postion => " + header.position()); - try { - int messageTypeInt = buffer.getInt(offset); + short messageCount = buffer.getShort(offset); + short messageTypeInt = buffer.getShort(offset + BitUtil.SIZE_OF_SHORT); + final int currentMagic = Math.abs(messageCount % NUM_PROCESSORS); MessageType messageType = MessageType.from(messageTypeInt); + + StringBuilder sb = new StringBuilder(); + + sb.append(Thread.currentThread() + " messageTypeInt => " + messageTypeInt).append('\n'); + sb.append(Thread.currentThread() + " messageCount => " + messageCount).append('\n'); + sb.append(Thread.currentThread() + " message type => " + messageType).append('\n'); + + System.out.println(sb.toString()); + + LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); + if (currentMagic != magicNumber) { + return; + } + if (messageType == MessageType.FRAME) { final AeronClientDuplexConnection connection = connections.get(header.sessionId()); Observer subscriber = connection.getSubscriber(); @@ -301,12 +308,12 @@ void poll(final int magicNumber, final Scheduler.Worker worker, CyclicBarrier st fragmentHandler(magicNumber, buffer, offset, length, header)); - //System.out.println("processing subscriptions => " + magicNumber); - //LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); + System.out.println("processing subscriptions => " + magicNumber); + LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); SUBSCRIPTION_GROUPS .forEach(subscriptionGroup -> { - //System.out.println("processing subscriptions in foreach => " + magicNumber); - //LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); + System.out.println("processing subscriptions in foreach => " + magicNumber); + LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); Subscription subscription = subscriptionGroup.subscriptions[magicNumber]; subscription.poll(fragmentAssembler, Integer.MAX_VALUE); }); @@ -336,7 +343,9 @@ void offer(Publication publication, ByteBuffer byteBuffer, int length) { final byte[] bytes = new byte[length]; final UnsafeBuffer unsafeBuffer = unsafeBuffers.get(); unsafeBuffer.wrap(bytes); - unsafeBuffer.putInt(0, MessageType.FRAME.getEncodedType()); + + unsafeBuffer.putShort(0, (short) 0); + unsafeBuffer.putShort(BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); unsafeBuffer.putBytes(BitUtil.SIZE_OF_INT, byteBuffer, byteBuffer.capacity()); do { final long offer = publication.offer(unsafeBuffer); @@ -357,7 +366,8 @@ void tryClaim(Publication publication, ByteBuffer byteBuffer, int length) { try { final MutableDirectBuffer buffer = bufferClaim.buffer(); final int offset = bufferClaim.offset(); - buffer.putInt(offset, MessageType.FRAME.getEncodedType()); + buffer.putShort(offset, (short) 0); + buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, 0, byteBuffer.capacity()); } finally { bufferClaim.commit(); @@ -379,7 +389,8 @@ void establishConnection(final Publication publication, final int sessionId) { try { final UnsafeBuffer buffer = new UnsafeBuffer(EMTPY); buffer.wrap(new byte[BitUtil.SIZE_OF_INT]); - buffer.putInt(0, MessageType.ESTABLISH_CONNECTION_REQUEST.getEncodedType()); + buffer.putShort(0, (short) 0); + buffer.putShort(BitUtil.SIZE_OF_SHORT, (short) MessageType.ESTABLISH_CONNECTION_REQUEST.getEncodedType()); CountDownLatch latch = new CountDownLatch(1); establishConnectionLatches.put(sessionId, latch); diff --git a/src/main/java/io/reactivesocket/aeron/client/single/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/client/single/ReactivesocketAeronClient.java index 723d9f71f..d03be3ae3 100644 --- a/src/main/java/io/reactivesocket/aeron/client/single/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/client/single/ReactivesocketAeronClient.java @@ -160,7 +160,7 @@ public static ReactivesocketAeronClient create(String host, String server) { void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) { try { - int messageTypeInt = buffer.getInt(offset); + short messageTypeInt = buffer.getShort(offset + BitUtil.SIZE_OF_SHORT); MessageType messageType = MessageType.from(messageTypeInt); if (messageType == MessageType.FRAME) { final AeronClientDuplexConnection connection = connections.get(header.sessionId()); diff --git a/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java index cd0adb21f..be44fb570 100644 --- a/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java @@ -67,7 +67,9 @@ void ackEstablishConnection(int ackSessionId) { try { final MutableDirectBuffer buffer = bufferClaim.buffer(); final int offset = bufferClaim.offset(); - buffer.putInt(offset, MessageType.ESTABLISH_CONNECTION_RESPONSE.getEncodedType()); + + buffer.putShort(offset, (short) 0); + buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.ESTABLISH_CONNECTION_RESPONSE.getEncodedType()); buffer.putInt(offset + BitUtil.SIZE_OF_INT, ackSessionId); } finally { bufferClaim.commit(); diff --git a/src/main/java/io/reactivesocket/aeron/server/CompletableSubscription.java b/src/main/java/io/reactivesocket/aeron/server/CompletableSubscription.java index 47644d066..02b8e4081 100644 --- a/src/main/java/io/reactivesocket/aeron/server/CompletableSubscription.java +++ b/src/main/java/io/reactivesocket/aeron/server/CompletableSubscription.java @@ -31,6 +31,12 @@ public class CompletableSubscription implements Subscriber { private final int mtuLength; + private volatile static short count = 1; + + public short getAndIncrement() { + return count++; + } + public CompletableSubscription(Publication publication, Completable completable, AutoCloseable closeable) { this.publication = publication; this.completable = completable; @@ -74,7 +80,8 @@ void offer(ByteBuffer byteBuffer, int length) { final byte[] bytes = new byte[length]; final UnsafeBuffer unsafeBuffer = unsafeBuffers.get(); unsafeBuffer.wrap(bytes); - unsafeBuffer.putInt(0, MessageType.FRAME.getEncodedType()); + unsafeBuffer.putShort(0, getAndIncrement()); + unsafeBuffer.putShort(BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); unsafeBuffer.putBytes(BitUtil.SIZE_OF_INT, byteBuffer, byteBuffer.capacity()); do { final long offer = publication.offer(unsafeBuffer); @@ -97,7 +104,9 @@ void tryClaim(ByteBuffer byteBuffer, int length) { try { final MutableDirectBuffer buffer = bufferClaim.buffer(); final int offset = bufferClaim.offset(); - buffer.putInt(offset, MessageType.FRAME.getEncodedType()); + System.out.println("server count => " + count); + buffer.putShort(offset, getAndIncrement()); + buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, 0, byteBuffer.capacity()); } finally { bufferClaim.commit(); diff --git a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java index 75357ca83..7c1686a9d 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java @@ -115,7 +115,7 @@ void poll(FragmentAssembler fragmentAssembler) { void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) { final int sessionId = header.sessionId(); - int messageTypeInt = buffer.getInt(offset); + short messageTypeInt = buffer.getShort(offset + BitUtil.SIZE_OF_SHORT); MessageType type = MessageType.from(messageTypeInt); if (MessageType.FRAME == type) { diff --git a/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java index 46bfac272..c579d8cda 100644 --- a/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java @@ -85,7 +85,7 @@ public Publisher handleMetadataPush(Payload payload) { Observable .range(1, 130) .flatMap(i -> { - //System.out.println("pinging => " + i); + System.out.println("pinging => " + i); Payload payload = TestUtil.utf8EncodedPayload("ping =>" + i, null); return RxReactiveStreams.toObservable(client.requestResponse(payload)); } From 259a22f5e627a66b2ac9bf17ebc67eab68954f4c Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Fri, 18 Sep 2015 11:14:23 -0700 Subject: [PATCH 029/105] using scheduling periodically --- .../multi/AeronClientDuplexConnection.java | 3 +- .../multi/ReactivesocketAeronClient.java | 39 +++++++++---------- .../aeron/internal/Constants.java | 5 +++ .../aeron/server/CompletableSubscription.java | 17 ++++---- .../server/ReactiveSocketAeronServer.java | 20 +++++----- 5 files changed, 43 insertions(+), 41 deletions(-) diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java index 11ef15f8e..a12e67859 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java @@ -55,8 +55,7 @@ public void addOutput(Publisher o, Completable callback) { @Override public void onSubscribe(Subscription s) { this.s = s; - s.request(Long.MAX_VALUE); - //s.request(framesSendQueue.remainingCapacity() + 1); + s.request(framesSendQueue.remainingCapacity()); } @Override diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java index 3570dd5c0..88e16fd5a 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java @@ -73,7 +73,7 @@ private class SubscriptionGroup { private static int mtuLength; - private static final int NUM_PROCESSORS = Runtime.getRuntime().availableProcessors(); + private static final int NUM_PROCESSORS = Runtime.getRuntime().availableProcessors() / 2; private static Scheduler.Worker[] workers; @@ -187,28 +187,28 @@ void fragmentHandler(int magicNumber, DirectBuffer buffer, int offset, int lengt short messageCount = buffer.getShort(offset); short messageTypeInt = buffer.getShort(offset + BitUtil.SIZE_OF_SHORT); final int currentMagic = Math.abs(messageCount % NUM_PROCESSORS); - MessageType messageType = MessageType.from(messageTypeInt); - +/* StringBuilder sb = new StringBuilder(); sb.append(Thread.currentThread() + " messageTypeInt => " + messageTypeInt).append('\n'); sb.append(Thread.currentThread() + " messageCount => " + messageCount).append('\n'); sb.append(Thread.currentThread() + " message type => " + messageType).append('\n'); - System.out.println(sb.toString()); + System.out.println(sb.toString());*/ - LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); + //LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); if (currentMagic != magicNumber) { return; } + final MessageType messageType = MessageType.from(messageTypeInt); if (messageType == MessageType.FRAME) { final AeronClientDuplexConnection connection = connections.get(header.sessionId()); Observer subscriber = connection.getSubscriber(); final ByteBuffer bytes = ByteBuffer.allocate(length); buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); final Frame frame = Frame.from(bytes); - System.out.println("$$$$ CLIENT GOT => " + frame.toString() + " - " + atomicLong.getAndIncrement()); + //System.out.println("$$$$ CLIENT GOT => " + frame.toString() + " - " + atomicLong.getAndIncrement()); subscriber.onNext(frame); } else if (messageType == MessageType.ESTABLISH_CONNECTION_RESPONSE) { final int ackSessionId = buffer.getInt(offset + BitUtil.SIZE_OF_INT); @@ -261,7 +261,7 @@ public void error(Throwable e) { } void poll(final int magicNumber, final Scheduler.Worker worker, CyclicBarrier startBarrier) { - worker.schedule(() -> { + worker.schedulePeriodically(() -> { if (startBarrier != null && !pollingStarted) { try { System.out.println("Waiting... " + magicNumber); @@ -302,21 +302,21 @@ void poll(final int magicNumber, final Scheduler.Worker worker, CyclicBarrier st }); } + try { final FragmentAssembler fragmentAssembler = new FragmentAssembler( (DirectBuffer buffer, int offset, int length, Header header) -> fragmentHandler(magicNumber, buffer, offset, length, header)); - - System.out.println("processing subscriptions => " + magicNumber); - LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); - SUBSCRIPTION_GROUPS - .forEach(subscriptionGroup -> { - System.out.println("processing subscriptions in foreach => " + magicNumber); - LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); - Subscription subscription = subscriptionGroup.subscriptions[magicNumber]; - subscription.poll(fragmentAssembler, Integer.MAX_VALUE); - }); + //System.out.println("processing subscriptions => " + magicNumber); + //LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); + SUBSCRIPTION_GROUPS + .forEach(subscriptionGroup -> { + //System.out.println("processing subscriptions in foreach => " + magicNumber); + //LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); + Subscription subscription = subscriptionGroup.subscriptions[magicNumber]; + subscription.poll(fragmentAssembler, Integer.MAX_VALUE); + }); } catch (Throwable t) { t.printStackTrace(); error("error polling aeron subscription", t); @@ -324,15 +324,12 @@ void poll(final int magicNumber, final Scheduler.Worker worker, CyclicBarrier st } catch (Throwable t) { t.printStackTrace(); } - finally { - poll(magicNumber, worker, null); - } } else { shutdownLatch.countDown(); } - }); + }, 0, 1, TimeUnit.NANOSECONDS); } private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); diff --git a/src/main/java/io/reactivesocket/aeron/internal/Constants.java b/src/main/java/io/reactivesocket/aeron/internal/Constants.java index 854da8947..45d5005a9 100644 --- a/src/main/java/io/reactivesocket/aeron/internal/Constants.java +++ b/src/main/java/io/reactivesocket/aeron/internal/Constants.java @@ -1,5 +1,8 @@ package io.reactivesocket.aeron.internal; +import uk.co.real_logic.agrona.concurrent.IdleStrategy; +import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; + public final class Constants { private Constants() {} @@ -14,4 +17,6 @@ private Constants() {} public static final int MULTI_THREADED_SPIN_LIMIT = Integer.getInteger("multiSpinLimit", 100); + public static final IdleStrategy SERVER_IDLE_STRATEGY = new NoOpIdleStrategy(); + } diff --git a/src/main/java/io/reactivesocket/aeron/server/CompletableSubscription.java b/src/main/java/io/reactivesocket/aeron/server/CompletableSubscription.java index 02b8e4081..224892f11 100644 --- a/src/main/java/io/reactivesocket/aeron/server/CompletableSubscription.java +++ b/src/main/java/io/reactivesocket/aeron/server/CompletableSubscription.java @@ -13,6 +13,7 @@ import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicLong; /** * Created by rroeser on 8/27/15. @@ -31,11 +32,7 @@ public class CompletableSubscription implements Subscriber { private final int mtuLength; - private volatile static short count = 1; - - public short getAndIncrement() { - return count++; - } + private volatile static AtomicLong count = new AtomicLong(); public CompletableSubscription(Publication publication, Completable completable, AutoCloseable closeable) { this.publication = publication; @@ -76,11 +73,15 @@ public void onComplete() { completable.success(); } + private short getCount() { + return (short) count.incrementAndGet(); + } + void offer(ByteBuffer byteBuffer, int length) { final byte[] bytes = new byte[length]; final UnsafeBuffer unsafeBuffer = unsafeBuffers.get(); unsafeBuffer.wrap(bytes); - unsafeBuffer.putShort(0, getAndIncrement()); + unsafeBuffer.putShort(0, getCount()); unsafeBuffer.putShort(BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); unsafeBuffer.putBytes(BitUtil.SIZE_OF_INT, byteBuffer, byteBuffer.capacity()); do { @@ -104,8 +105,8 @@ void tryClaim(ByteBuffer byteBuffer, int length) { try { final MutableDirectBuffer buffer = bufferClaim.buffer(); final int offset = bufferClaim.offset(); - System.out.println("server count => " + count); - buffer.putShort(offset, getAndIncrement()); + //System.out.println("server count => " + count); + buffer.putShort(offset, getCount()); buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, 0, byteBuffer.capacity()); } finally { diff --git a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java index 7c1686a9d..9c6bdf275 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java @@ -25,6 +25,7 @@ import java.util.concurrent.TimeUnit; import static io.reactivesocket.aeron.internal.Constants.CLIENT_STREAM_ID; +import static io.reactivesocket.aeron.internal.Constants.SERVER_IDLE_STRATEGY; import static io.reactivesocket.aeron.internal.Constants.SERVER_STREAM_ID; public class ReactiveSocketAeronServer implements AutoCloseable, Loggable { @@ -68,7 +69,7 @@ private ReactiveSocketAeronServer(String host, int port, ConnectionSetupHandler final FragmentAssembler fragmentAssembler = new FragmentAssembler(this::fragmentHandler); - worker = Schedulers.computation().createWorker(); + worker = Schedulers.newThread().createWorker(); poll(fragmentAssembler); } @@ -98,18 +99,17 @@ public static ReactiveSocketAeronServer create(ConnectionSetupHandler connection } void poll(FragmentAssembler fragmentAssembler) { - if (running) { - worker.schedule(() -> { + worker.schedule(() -> { + while (running) { try { - subscription.poll(fragmentAssembler, Integer.MAX_VALUE); - poll(fragmentAssembler); + int poll = subscription.poll(fragmentAssembler, Integer.MAX_VALUE); + SERVER_IDLE_STRATEGY.idle(poll); } catch (Throwable t) { t.printStackTrace(); } - }); - } else { - shutdownLatch.countDown(); - } + } + shutdownLatch.countDown(); + }); } void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) { @@ -125,7 +125,7 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) ByteBuffer bytes = ByteBuffer.allocate(length); buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); final Frame frame = Frame.from(bytes); - System.out.println("### Server Sending => " + frame); + //System.out.println("### Server Sending => " + frame); subscriber.onNext(frame); } } else if (MessageType.ESTABLISH_CONNECTION_REQUEST == type) { From a75c3e411b9de5023a74fa3c31fd005297a5789c Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Fri, 18 Sep 2015 20:39:09 -0700 Subject: [PATCH 030/105] moving tryClaim,offer methods to use utils class --- src/main/java/io/reactivesocket/Frame.java | 21 +++-- .../multi/AeronClientDuplexConnection.java | 66 +++++-------- .../aeron/client/multi/FrameHolder.java | 9 +- .../multi/ReactivesocketAeronClient.java | 8 +- .../aeron/internal/AeronUtil.java | 92 +++++++++++++++++++ .../aeron/internal/Constants.java | 3 + .../aeron/server/CompletableSubscription.java | 62 ++----------- 7 files changed, 152 insertions(+), 109 deletions(-) create mode 100644 src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java diff --git a/src/main/java/io/reactivesocket/Frame.java b/src/main/java/io/reactivesocket/Frame.java index 4691f5752..bd66bf19d 100644 --- a/src/main/java/io/reactivesocket/Frame.java +++ b/src/main/java/io/reactivesocket/Frame.java @@ -38,7 +38,7 @@ public class Frame implements Payload */ private static final String FRAME_POOLER_CLASS_NAME = - getProperty("io.reactivesocket.FramePool", "io.reactivesocket.internal.UnpooledFrame"); + getProperty("io.reactivesocket.FramePool", "io.reactivesocket.internal.ThreadLocalFramePool"); private static final FramePool POOL; static @@ -382,19 +382,22 @@ public static class Request { public static Frame from(int streamId, FrameType type, Payload payload, int initialRequestN) { + final ByteBuffer d = payload.getData() != null ? payload.getData() : NULL_BYTEBUFFER; final ByteBuffer md = payload.getMetadata() != null ? payload.getMetadata() : NULL_BYTEBUFFER; final Frame frame = POOL.acquireFrame(RequestFrameFlyweight.computeFrameLength(type, md.capacity(), d.capacity())); +try { - if (type.hasInitialRequestN()) - { - frame.length = RequestFrameFlyweight.encode(frame.directBuffer, frame.offset, streamId, type, initialRequestN, md, d); - } - else - { - frame.length = RequestFrameFlyweight.encode(frame.directBuffer, frame.offset, streamId, type, md, d); - } + if (type.hasInitialRequestN()) { + frame.length = RequestFrameFlyweight.encode(frame.directBuffer, frame.offset, streamId, type, initialRequestN, md, d); + } else { + frame.length = RequestFrameFlyweight.encode(frame.directBuffer, frame.offset, streamId, type, md, d); + } +} catch (Throwable t) { + System.out.println("!@#!@#!@#!@#!@#!@#!@#!@# stream id => " + streamId + "frame.offset =>" + frame.offset + " type => " + type); + throw new RuntimeException(t); +} return frame; } diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java index a12e67859..5c246407a 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java @@ -10,14 +10,12 @@ import io.reactivesocket.observable.Observable; import io.reactivesocket.observable.Observer; import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; +import rx.RxReactiveStreams; import rx.Scheduler; +import rx.exceptions.MissingBackpressureException; import rx.schedulers.Schedulers; import uk.co.real_logic.aeron.Publication; -import java.util.concurrent.atomic.AtomicLong; - public class AeronClientDuplexConnection implements DuplexConnection, AutoCloseable { private Publication publication; private Observer observer; @@ -27,7 +25,7 @@ public class AeronClientDuplexConnection implements DuplexConnection, AutoClosea public AeronClientDuplexConnection(Publication publication) { this.publication = publication; - this.framesSendQueue = new ManyToManyConcurrentArrayQueue<>(128); + this.framesSendQueue = new ManyToManyConcurrentArrayQueue<>(Constants.CONCURRENCY); this.observable = (Observer o) -> { observer = o; observer.onSubscribe(new EmptyDisposable()); @@ -45,46 +43,28 @@ public Observable getInput() { return observable; } - static final AtomicLong count = new AtomicLong(); - @Override public void addOutput(Publisher o, Completable callback) { - o.subscribe(new Subscriber() { - volatile boolean running = true; - Subscription s; - @Override - public void onSubscribe(Subscription s) { - this.s = s; - s.request(framesSendQueue.remainingCapacity()); - } - - @Override - public void onNext(final Frame frame) { - final FrameHolder frameHolder = FrameHolder.get(frame, publication); - int limit = Constants.MULTI_THREADED_SPIN_LIMIT; - if (running && !framesSendQueue.offer(frameHolder)) { - if (--limit < 0) { - worker.schedule(() -> onNext(frame)); - } - } - - final int r = framesSendQueue.remainingCapacity() + 1; - s.request(r); - - } - - @Override - public void onError(Throwable t) { - running = false; - callback.error(t); - } - - @Override - public void onComplete() { - running = false; - callback.success(); - } - }); + rx.Observable frameObservable = RxReactiveStreams.toObservable(o); + frameObservable + .flatMap(frame -> { + return rx.Observable.create(subscriber -> { + final FrameHolder frameHolder = FrameHolder.get(frame, publication, subscriber); + subscriber.onNext(frameHolder); + }) + .doOnNext(fh -> { + boolean offer = false; + int i = 0; + do { + offer = framesSendQueue.offer(fh); + if (!offer && ++i > 100) { + rx.Observable.error(new MissingBackpressureException()); + } + } while (!offer); + }); + }, Constants.CONCURRENCY) + .subscribe(ignore -> { + }, callback::error, callback::success); } public ManyToManyConcurrentArrayQueue getFramesSendQueue() { diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java b/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java index 8c17bb8b8..88c949cfe 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java @@ -3,6 +3,7 @@ import io.reactivesocket.Frame; import io.reactivesocket.aeron.internal.Constants; import io.reactivesocket.aeron.internal.concurrent.ManyToManyConcurrentArrayQueue; +import rx.Subscriber; import uk.co.real_logic.aeron.Publication; /** @@ -21,10 +22,11 @@ class FrameHolder { private Publication publication; private Frame frame; + private Subscriber s; private FrameHolder() {} - public static FrameHolder get(Frame frame, Publication publication) { + public static FrameHolder get(Frame frame, Publication publication, Subscriber s) { FrameHolder frameHolder = FRAME_HOLDER_QUEUE.poll(); if (frameHolder == null) { @@ -33,6 +35,7 @@ public static FrameHolder get(Frame frame, Publication publication) { frameHolder.frame = frame; frameHolder.publication = publication; + frameHolder.s = s; return frameHolder; } @@ -46,6 +49,10 @@ public Frame getFrame() { } public void release() { + if (s != null) { + s.onCompleted(); + } + frame.release(); FRAME_HOLDER_QUEUE.offer(this); } diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java index 88e16fd5a..db02f6fcc 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java @@ -36,6 +36,7 @@ import java.util.concurrent.locks.LockSupport; import static io.reactivesocket.aeron.internal.Constants.CLIENT_STREAM_ID; +import static io.reactivesocket.aeron.internal.Constants.CONCURRENCY; import static io.reactivesocket.aeron.internal.Constants.EMTPY; import static io.reactivesocket.aeron.internal.Constants.SERVER_STREAM_ID; @@ -73,7 +74,7 @@ private class SubscriptionGroup { private static int mtuLength; - private static final int NUM_PROCESSORS = Runtime.getRuntime().availableProcessors() / 2; + private static final int NUM_PROCESSORS = CONCURRENCY; private static Scheduler.Worker[] workers; @@ -293,10 +294,11 @@ void poll(final int magicNumber, final Scheduler.Worker worker, CyclicBarrier st } else { offer(fh.getPublication(), byteBuffer, length); } + + fh.release(); } catch (Throwable t) { - error("error draining send frame queue", t); - } finally { fh.release(); + error("error draining send frame queue", t); } }); }); diff --git a/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java b/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java new file mode 100644 index 000000000..c1ae7fc65 --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java @@ -0,0 +1,92 @@ +package io.reactivesocket.aeron.internal; + +import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.aeron.logbuffer.BufferClaim; +import uk.co.real_logic.agrona.MutableDirectBuffer; +import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; + +/** + * Utils for dealing with Aeron + */ +public class AeronUtil { + + private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); + + private static final ThreadLocal unsafeBuffers = ThreadLocal.withInitial(() -> new UnsafeBuffer(Constants.EMTPY)); + + /** + * Sends a message using offer. This method will spin-lock if Aeron signals back pressure. + * + * This method of sending data does not need to know how long the message is. + * + * @param publication publication to send the message on + * + * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} + * that is send over Aeron + */ + public static void offer(Publication publication, BufferFiller fillBuffer) { + final UnsafeBuffer buffer = unsafeBuffers.get(); + fillBuffer.fill(0, buffer); + do { + final long offer = publication.offer(buffer); + if (offer >= 0) { + break; + } else if (Publication.NOT_CONNECTED == offer) { + throw new RuntimeException("not connected"); + } + } while(true); + } + + /** + * Sends a message using tryClaim. This method will spin-lock if Aeron signals back pressure. The message + * being sent needs to be equal or smaller than Aeron's MTU size or an exception will be thrown. + * + * In order to use this method of sending data you need to know the length of data. + * + * @param publication publication to send the message on + * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} + * that is send over Aeron + * @param length the length of data + */ + public static void tryClaim(Publication publication, BufferFiller fillBuffer, int length) { + final BufferClaim bufferClaim = bufferClaims.get(); + do { + final long offer = publication.tryClaim(length, bufferClaim); + if (offer >= 0) { + try { + final MutableDirectBuffer buffer = bufferClaim.buffer(); + final int offset = bufferClaim.offset(); + fillBuffer.fill(offset, buffer); + } finally { + bufferClaim.commit(); + } + } else if (Publication.NOT_CONNECTED == offer) { + throw new RuntimeException("not connected"); + } + } while (true); + } + + /** + * Attempts to send the data using tryClaim. If the message data length is large then the Aeron MTU + * size it will use offer instead. + * + * @param publication publication to send the message on + * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} + * that is send over Aeron + * @param length the length of data + */ + public static void tryClaimOrOffer(Publication publication, BufferFiller fillBuffer, int length) { + if (length < Constants.AERON_MTU_SIZE) { + tryClaim(publication, fillBuffer, length); + } else { + offer(publication, fillBuffer); + } + } + + /** + * Implement this to fill a DirectBuffer passed in by either the offer or tryClaim methods. + */ + public interface BufferFiller { + void fill(int offset, MutableDirectBuffer buffer); + } +} diff --git a/src/main/java/io/reactivesocket/aeron/internal/Constants.java b/src/main/java/io/reactivesocket/aeron/internal/Constants.java index 45d5005a9..798b50272 100644 --- a/src/main/java/io/reactivesocket/aeron/internal/Constants.java +++ b/src/main/java/io/reactivesocket/aeron/internal/Constants.java @@ -19,4 +19,7 @@ private Constants() {} public static final IdleStrategy SERVER_IDLE_STRATEGY = new NoOpIdleStrategy(); + public static final int CONCURRENCY = Runtime.getRuntime().availableProcessors() / 2; + + public static final int AERON_MTU_SIZE = Integer.getInteger("aeron.mtu.length", 4096); } diff --git a/src/main/java/io/reactivesocket/aeron/server/CompletableSubscription.java b/src/main/java/io/reactivesocket/aeron/server/CompletableSubscription.java index 224892f11..0643d8661 100644 --- a/src/main/java/io/reactivesocket/aeron/server/CompletableSubscription.java +++ b/src/main/java/io/reactivesocket/aeron/server/CompletableSubscription.java @@ -2,6 +2,7 @@ import io.reactivesocket.Completable; import io.reactivesocket.Frame; +import io.reactivesocket.aeron.internal.AeronUtil; import io.reactivesocket.aeron.internal.Constants; import io.reactivesocket.aeron.internal.MessageType; import org.reactivestreams.Subscriber; @@ -9,7 +10,6 @@ import uk.co.real_logic.aeron.Publication; import uk.co.real_logic.aeron.logbuffer.BufferClaim; import uk.co.real_logic.agrona.BitUtil; -import uk.co.real_logic.agrona.MutableDirectBuffer; import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; import java.nio.ByteBuffer; @@ -54,13 +54,14 @@ public void onNext(Frame frame) { final ByteBuffer byteBuffer = frame.getByteBuffer(); final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; - // If the length is less the MTU size send the message using tryClaim which does not fragment the message - // If the message is larger the the MTU size send it using offer. - if (length < mtuLength) { - tryClaim(byteBuffer, length); - } else { - offer(byteBuffer, length); - } + AeronUtil.tryClaimOrOffer(publication, (offset, buffer) -> { + final byte[] bytes = new byte[length]; + buffer.wrap(bytes); + buffer.putShort(offset, getCount()); + buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); + buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, byteBuffer.capacity()); + }, length); + } @Override @@ -77,51 +78,6 @@ private short getCount() { return (short) count.incrementAndGet(); } - void offer(ByteBuffer byteBuffer, int length) { - final byte[] bytes = new byte[length]; - final UnsafeBuffer unsafeBuffer = unsafeBuffers.get(); - unsafeBuffer.wrap(bytes); - unsafeBuffer.putShort(0, getCount()); - unsafeBuffer.putShort(BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); - unsafeBuffer.putBytes(BitUtil.SIZE_OF_INT, byteBuffer, byteBuffer.capacity()); - do { - final long offer = publication.offer(unsafeBuffer); - if (offer >= 0) { - break; - } else if (Publication.NOT_CONNECTED == offer) { - closeQuietly(closeable); - completable.error(new RuntimeException("not connected")); - break; - } - } while(true); - - } - - void tryClaim(ByteBuffer byteBuffer, int length) { - final BufferClaim bufferClaim = bufferClaims.get(); - do { - final long offer = publication.tryClaim(length, bufferClaim); - if (offer >= 0) { - try { - final MutableDirectBuffer buffer = bufferClaim.buffer(); - final int offset = bufferClaim.offset(); - //System.out.println("server count => " + count); - buffer.putShort(offset, getCount()); - buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); - buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, 0, byteBuffer.capacity()); - } finally { - bufferClaim.commit(); - } - - break; - } else if (Publication.NOT_CONNECTED == offer) { - closeQuietly(closeable); - completable.error(new RuntimeException("not connected")); - break; - } - } while(true); - } - void closeQuietly(AutoCloseable closeable) { try { closeable.close(); From 0a520715338f451a06e646ad94846fcf8e3e1755 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Mon, 21 Sep 2015 17:42:55 -0700 Subject: [PATCH 031/105] supported getInput being called more than once --- src/main/java/io/reactivesocket/Frame.java | 554 ------------------ .../aeron/AeronDuplexConnectionSubject.java | 78 +++ .../multi/AeronClientDuplexConnection.java | 31 +- .../multi/ReactivesocketAeronClient.java | 18 +- .../aeron/internal/AeronUtil.java | 12 +- .../server/AeronServerDuplexConnection.java | 28 +- .../server/ReactiveSocketAeronServer.java | 18 +- 7 files changed, 119 insertions(+), 620 deletions(-) delete mode 100644 src/main/java/io/reactivesocket/Frame.java create mode 100644 src/main/java/io/reactivesocket/aeron/AeronDuplexConnectionSubject.java diff --git a/src/main/java/io/reactivesocket/Frame.java b/src/main/java/io/reactivesocket/Frame.java deleted file mode 100644 index bd66bf19d..000000000 --- a/src/main/java/io/reactivesocket/Frame.java +++ /dev/null @@ -1,554 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket; - -import io.reactivesocket.internal.*; -import uk.co.real_logic.agrona.DirectBuffer; -import uk.co.real_logic.agrona.MutableDirectBuffer; - -import java.nio.ByteBuffer; -import java.nio.charset.Charset; - -import static java.lang.System.getProperty; - -/** - * Represents a Frame sent over a {@link DuplexConnection}. - *

- * This provides encoding, decoding and field accessors. - */ -public class Frame implements Payload -{ - public static final ByteBuffer NULL_BYTEBUFFER = FrameHeaderFlyweight.NULL_BYTEBUFFER; - - /* - * ThreadLocal handling in the pool itself. We don't have a per thread pool at this level. - */ - - private static final String FRAME_POOLER_CLASS_NAME = - getProperty("io.reactivesocket.FramePool", "io.reactivesocket.internal.ThreadLocalFramePool"); - private static final FramePool POOL; - - static - { - FramePool tmpPool; - - try - { - tmpPool = (FramePool)Class.forName(FRAME_POOLER_CLASS_NAME).newInstance(); - } - catch (final Exception ex) - { - tmpPool = new UnpooledFrame(); - } - - POOL = tmpPool; - } - - // not final so we can reuse this object - private MutableDirectBuffer directBuffer; - private int offset = 0; - private int length = 0; - - private Frame(final MutableDirectBuffer directBuffer) - { - this.directBuffer = directBuffer; - } - - /** - * Return underlying {@link ByteBuffer} for frame - * - * @return underlying {@link ByteBuffer} for frame - */ - public ByteBuffer getByteBuffer() { - return directBuffer.byteBuffer(); - } - - /** - * Return {@link ByteBuffer} that is a {@link ByteBuffer#slice()} for the frame data - * - * If no data is present, the ByteBuffer will have 0 capacity. - * - * @return ByteBuffer containing the data - */ - public ByteBuffer getData() - { - return FrameHeaderFlyweight.sliceFrameData(directBuffer, offset, 0); - } - - /** - * Return {@link ByteBuffer} that is a {@link ByteBuffer#slice()} for the frame metadata - * - * If no metadata is present, the ByteBuffer will have 0 capacity. - * - * @return ByteBuffer containing the data - */ - public ByteBuffer getMetadata() - { - return FrameHeaderFlyweight.sliceFrameMetadata(directBuffer, offset, 0); - } - - /** - * Return frame stream identifier - * - * @return frame stream identifier - */ - public int getStreamId() { - return FrameHeaderFlyweight.streamId(directBuffer, offset); - } - - /** - * Return frame {@link FrameType} - * - * @return frame type - */ - public FrameType getType() { - return FrameHeaderFlyweight.frameType(directBuffer, offset); - } - - /** - * Return the offset in the buffer of the frame - * - * @return offset of frame within the buffer - */ - public int offset() - { - return offset; - } - - /** - * Return the encoded length of a frame or the frame length - * - * @return frame length - */ - public int length() - { - return length; - } - - /** - * Mutates this Frame to contain the given ByteBuffer - * - * @param byteBuffer to wrap - */ - public void wrap(final ByteBuffer byteBuffer, final int offset) - { - wrap(POOL.acquireMutableDirectBuffer(byteBuffer), offset); - } - - /** - * Mutates this Frame to contain the given MutableDirectBuffer - * - * @param directBuffer to wrap - */ - public void wrap(final MutableDirectBuffer directBuffer, final int offset) - { - this.directBuffer = directBuffer; - this.offset = offset; - } - - /** - * Acquire a free Frame backed by given ByteBuffer - * - * @param byteBuffer to wrap - * @return new {@link Frame} - */ - public static Frame from(final ByteBuffer byteBuffer) { - return POOL.acquireFrame(byteBuffer); - } - - /** - * Acquire a free Frame and back with the given {@link DirectBuffer} starting at offset for length bytes - * - * @param directBuffer to use as backing buffer - * @param offset of start of frame - * @param length of frame in bytes - * @return frame - */ - public static Frame from(final DirectBuffer directBuffer, final int offset, final int length) - { - final Frame frame = POOL.acquireFrame((MutableDirectBuffer)directBuffer); - frame.offset = offset; - frame.length = length; - - return frame; - } - - /** - * Construct a new Frame from the given {@link MutableDirectBuffer} - * - * NOTE: always allocates. Used for pooling. - * - * @param directBuffer to wrap - * @return new {@link Frame} - */ - public static Frame allocate(final MutableDirectBuffer directBuffer) - { - return new Frame(directBuffer); - } - - /** - * Release frame for re-use. - */ - public void release() - { - POOL.release(this.directBuffer); - POOL.release(this); - } - - /** - * Mutates this Frame to contain the given parameters. - * - * NOTE: acquires a new backing buffer and releases current backing buffer - * - * @param streamId to include in frame - * @param type to include in frame - * @param data to include in frame - */ - public void wrap(final int streamId, final FrameType type, final ByteBuffer data) - { - POOL.release(this.directBuffer); - - this.directBuffer = - POOL.acquireMutableDirectBuffer(FrameHeaderFlyweight.computeFrameHeaderLength(type, 0, data.capacity())); - - this.length = FrameHeaderFlyweight.encode(this.directBuffer, offset, streamId, type, NULL_BYTEBUFFER, data); - } - - /* TODO: - * - * fromRequest(type, id, payload) - * fromKeepalive(ByteBuffer data) - * - */ - - // SETUP specific getters - public static class Setup - { - public static Frame from( - int flags, - int keepaliveInterval, - int maxLifetime, - String metadataMimeType, - String dataMimeType, - Payload payload) - { - final ByteBuffer metadata = payload.getMetadata(); - final ByteBuffer data = payload.getData(); - - final Frame frame = - POOL.acquireFrame(SetupFrameFlyweight.computeFrameLength(metadataMimeType, dataMimeType, metadata.capacity(), data.capacity())); - - frame.length = SetupFrameFlyweight.encode( - frame.directBuffer, frame.offset, flags, keepaliveInterval, maxLifetime, metadataMimeType, dataMimeType, metadata, data); - return frame; - } - - public static int getFlags(final Frame frame) - { - ensureFrameType(FrameType.SETUP, frame); - final int flags = FrameHeaderFlyweight.flags(frame.directBuffer, frame.offset); - - return flags & (SetupFrameFlyweight.FLAGS_WILL_HONOR_LEASE | SetupFrameFlyweight.FLAGS_STRICT_INTERPRETATION); - } - - public static int version(final Frame frame) - { - ensureFrameType(FrameType.SETUP, frame); - return SetupFrameFlyweight.version(frame.directBuffer, frame.offset); - } - - public static int keepaliveInterval(final Frame frame) - { - ensureFrameType(FrameType.SETUP, frame); - return SetupFrameFlyweight.keepaliveInterval(frame.directBuffer, frame.offset); - } - - public static int maxLifetime(final Frame frame) - { - ensureFrameType(FrameType.SETUP, frame); - return SetupFrameFlyweight.maxLifetime(frame.directBuffer, frame.offset); - } - - public static String metadataMimeType(final Frame frame) - { - ensureFrameType(FrameType.SETUP, frame); - return SetupFrameFlyweight.metadataMimeType(frame.directBuffer, frame.offset); - } - - public static String dataMimeType(final Frame frame) - { - ensureFrameType(FrameType.SETUP, frame); - return SetupFrameFlyweight.dataMimeType(frame.directBuffer, frame.offset); - } - } - - public static class Error - { - public static Frame from( - int streamId, - final Throwable throwable, - ByteBuffer metadata, - ByteBuffer data - ) { - final int code = ErrorFrameFlyweight.errorCodeFromException(throwable); - final Frame frame = POOL.acquireFrame( - ErrorFrameFlyweight.computeFrameLength(data.capacity(), metadata.capacity())); - - frame.length = ErrorFrameFlyweight.encode( - frame.directBuffer, frame.offset, streamId, code, metadata, data); - return frame; - } - - public static Frame from( - int streamId, - final Throwable throwable, - ByteBuffer metadata - ) { - String data = (throwable.getMessage() == null ? "" : throwable.getMessage()); - byte[] bytes = data.getBytes(Charset.forName("UTF-8")); - final ByteBuffer dataBuffer = ByteBuffer.wrap(bytes); - - return from(streamId, throwable, metadata, dataBuffer); - } - - public static Frame from( - int streamId, - final Throwable throwable - ) { - return from(streamId, throwable, NULL_BYTEBUFFER); - } - - public static int errorCode(final Frame frame) - { - ensureFrameType(FrameType.ERROR, frame); - return ErrorFrameFlyweight.errorCode(frame.directBuffer, frame.offset); - } - } - - public static class Lease - { - public static Frame from(int ttl, int numberOfRequests, ByteBuffer metadata) - { - final Frame frame = POOL.acquireFrame(LeaseFrameFlyweight.computeFrameLength(metadata.capacity())); - - frame.length = LeaseFrameFlyweight.encode(frame.directBuffer, frame.offset, ttl, numberOfRequests, metadata); - return frame; - } - - public static int ttl(final Frame frame) - { - ensureFrameType(FrameType.LEASE, frame); - return LeaseFrameFlyweight.ttl(frame.directBuffer, frame.offset); - } - - public static int numberOfRequests(final Frame frame) - { - ensureFrameType(FrameType.LEASE, frame); - return LeaseFrameFlyweight.numRequests(frame.directBuffer, frame.offset); - } - } - - public static class RequestN - { - public static Frame from(int streamId, int requestN) - { - final Frame frame = POOL.acquireFrame(RequestNFrameFlyweight.computeFrameLength()); - - frame.length = RequestNFrameFlyweight.encode(frame.directBuffer, frame.offset, streamId, requestN); - return frame; - } - - public static long requestN(final Frame frame) - { - ensureFrameType(FrameType.REQUEST_N, frame); - return RequestNFrameFlyweight.requestN(frame.directBuffer, frame.offset); - } - } - - public static class Request - { - public static Frame from(int streamId, FrameType type, Payload payload, int initialRequestN) - { - - final ByteBuffer d = payload.getData() != null ? payload.getData() : NULL_BYTEBUFFER; - final ByteBuffer md = payload.getMetadata() != null ? payload.getMetadata() : NULL_BYTEBUFFER; - - final Frame frame = POOL.acquireFrame(RequestFrameFlyweight.computeFrameLength(type, md.capacity(), d.capacity())); -try { - - if (type.hasInitialRequestN()) { - frame.length = RequestFrameFlyweight.encode(frame.directBuffer, frame.offset, streamId, type, initialRequestN, md, d); - } else { - frame.length = RequestFrameFlyweight.encode(frame.directBuffer, frame.offset, streamId, type, md, d); - } -} catch (Throwable t) { - System.out.println("!@#!@#!@#!@#!@#!@#!@#!@# stream id => " + streamId + "frame.offset =>" + frame.offset + " type => " + type); - throw new RuntimeException(t); -} - - return frame; - } - - public static Frame from(int streamId, FrameType type, int flags) - { - final Frame frame = POOL.acquireFrame(RequestFrameFlyweight.computeFrameLength(type, 0, 0)); - - frame.length = RequestFrameFlyweight.encode(frame.directBuffer, frame.offset, streamId, type, flags); - return frame; - } - - public static long initialRequestN(final Frame frame) - { - final FrameType type = frame.getType(); - long result; - - if (!type.isRequestType()) - { - throw new AssertionError("expected request type, but saw " + type.name()); - } - - switch (frame.getType()) - { - case REQUEST_RESPONSE: - result = 1; - break; - case FIRE_AND_FORGET: - result = 0; - break; - default: - result = RequestFrameFlyweight.initialRequestN(frame.directBuffer, frame.offset); - break; - } - - return result; - } - - public static boolean isRequestChannelComplete(final Frame frame) - { - ensureFrameType(FrameType.REQUEST_CHANNEL, frame); - final int flags = FrameHeaderFlyweight.flags(frame.directBuffer, frame.offset); - - return (flags & RequestFrameFlyweight.FLAGS_REQUEST_CHANNEL_C) == RequestFrameFlyweight.FLAGS_REQUEST_CHANNEL_C; - } - } - - public static class Response - { - public static Frame from(int streamId, FrameType type, Payload payload) - { - final ByteBuffer data = payload.getData() != null ? payload.getData() : NULL_BYTEBUFFER; - final ByteBuffer metadata = payload.getMetadata() != null ? payload.getMetadata() : NULL_BYTEBUFFER; - - final Frame frame = - POOL.acquireFrame(FrameHeaderFlyweight.computeFrameHeaderLength(type, metadata.capacity(), data.capacity())); - - frame.length = FrameHeaderFlyweight.encode(frame.directBuffer, frame.offset, streamId, type, metadata, data); - return frame; - } - - public static Frame from(int streamId, FrameType type) - { - final Frame frame = - POOL.acquireFrame(FrameHeaderFlyweight.computeFrameHeaderLength(type, 0, 0)); - - frame.length = FrameHeaderFlyweight.encode( - frame.directBuffer, frame.offset, streamId, type, Frame.NULL_BYTEBUFFER, Frame.NULL_BYTEBUFFER); - return frame; - } - } - - public static class Cancel - { - public static Frame from(int streamId) - { - final Frame frame = - POOL.acquireFrame(FrameHeaderFlyweight.computeFrameHeaderLength(FrameType.CANCEL, 0, 0)); - - frame.length = FrameHeaderFlyweight.encode( - frame.directBuffer, frame.offset, streamId, FrameType.CANCEL, Frame.NULL_BYTEBUFFER, Frame.NULL_BYTEBUFFER); - return frame; - } - } - - public static class Keepalive - { - public static Frame from(ByteBuffer data, boolean respond) - { - final Frame frame = - POOL.acquireFrame(FrameHeaderFlyweight.computeFrameHeaderLength(FrameType.KEEPALIVE, 0, data.capacity())); - - final int flags = (respond ? FrameHeaderFlyweight.FLAGS_KEEPALIVE_R : 0); - - frame.length = FrameHeaderFlyweight.encode( - frame.directBuffer, frame.offset, flags, FrameType.KEEPALIVE, Frame.NULL_BYTEBUFFER, data); - - return frame; - } - - public static boolean hasRespondFlag(final Frame frame) - { - ensureFrameType(FrameType.KEEPALIVE, frame); - final int flags = FrameHeaderFlyweight.flags(frame.directBuffer, frame.offset); - - return (flags & FrameHeaderFlyweight.FLAGS_KEEPALIVE_R) == FrameHeaderFlyweight.FLAGS_KEEPALIVE_R; - } - } - - public static void ensureFrameType(final FrameType frameType, final Frame frame) - { - final FrameType typeInFrame = frame.getType(); - - if (typeInFrame != frameType) - { - throw new AssertionError("expected " + frameType + ", but saw" + typeInFrame); - } - } - - @Override - public String toString() { - FrameType type = FrameType.UNDEFINED; - StringBuilder payload = new StringBuilder(); - long streamId = -1; - - try - { - type = FrameHeaderFlyweight.frameType(directBuffer, 0); - ByteBuffer byteBuffer; - byte[] bytes; - - byteBuffer = FrameHeaderFlyweight.sliceFrameMetadata(directBuffer, 0, 0); - if (0 < byteBuffer.capacity()) - { - bytes = new byte[byteBuffer.capacity()]; - byteBuffer.get(bytes); - payload.append(String.format("metadata: \"%s\" ", new String(bytes, Charset.forName("UTF-8")))); - } - - byteBuffer = FrameHeaderFlyweight.sliceFrameData(directBuffer, 0, 0); - if (0 < byteBuffer.capacity()) - { - bytes = new byte[byteBuffer.capacity()]; - byteBuffer.get(bytes); - payload.append(String.format("data: \"%s\"", new String(bytes, Charset.forName("UTF-8")))); - } - - streamId = FrameHeaderFlyweight.streamId(directBuffer, 0); - } catch (Exception e) { - e.printStackTrace(); - } - return "Frame[" + offset + "] => Stream ID: " + streamId + " Type: " + type + " Payload Data: " + payload; - } -} diff --git a/src/main/java/io/reactivesocket/aeron/AeronDuplexConnectionSubject.java b/src/main/java/io/reactivesocket/aeron/AeronDuplexConnectionSubject.java new file mode 100644 index 000000000..65b29d2c0 --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/AeronDuplexConnectionSubject.java @@ -0,0 +1,78 @@ +package io.reactivesocket.aeron; + +import io.reactivesocket.Frame; +import io.reactivesocket.observable.Disposable; +import io.reactivesocket.observable.Observable; +import io.reactivesocket.observable.Observer; + +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Predicate; + +/** +* Class used to manage connections input to a duplex connection in Aeron. +*/ +public class AeronDuplexConnectionSubject implements Observable, Observer { + private static final AtomicLong count = new AtomicLong(); + + private final long id; + + private final ArrayList subjects; + + private Observer internal; + + public AeronDuplexConnectionSubject(ArrayList subjects) { + this.id = count.incrementAndGet(); + this.subjects = subjects; + } + + @Override + public void subscribe(Observer o) { + internal = o; + } + + @Override + public void onNext(Frame frame) { + internal.onNext(frame); + } + + @Override + public void onError(Throwable e) { + internal.onError(e); + } + + @Override + public void onComplete() { + internal.onComplete(); + } + + @Override + public void onSubscribe(Disposable d) { + internal.onSubscribe(()-> + subjects + .removeIf(new Predicate() { + @Override + public boolean test(AeronDuplexConnectionSubject subject) { + return id == subject.id; + } + }) + ); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + AeronDuplexConnectionSubject that = (AeronDuplexConnectionSubject) o; + + if (id != that.id) return false; + + return true; + } + + @Override + public int hashCode() { + return (int) (id ^ (id >>> 32)); + } +} diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java index 5c246407a..b897f26c6 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java @@ -4,43 +4,39 @@ import io.reactivesocket.Completable; import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; +import io.reactivesocket.aeron.AeronDuplexConnectionSubject; import io.reactivesocket.aeron.internal.Constants; import io.reactivesocket.aeron.internal.concurrent.ManyToManyConcurrentArrayQueue; -import io.reactivesocket.internal.EmptyDisposable; import io.reactivesocket.observable.Observable; import io.reactivesocket.observable.Observer; import org.reactivestreams.Publisher; import rx.RxReactiveStreams; -import rx.Scheduler; import rx.exceptions.MissingBackpressureException; -import rx.schedulers.Schedulers; import uk.co.real_logic.aeron.Publication; +import java.util.ArrayList; +import java.util.List; + public class AeronClientDuplexConnection implements DuplexConnection, AutoCloseable { - private Publication publication; - private Observer observer; - private Observable observable; - private ManyToManyConcurrentArrayQueue framesSendQueue; - private Scheduler.Worker worker; + private final Publication publication; + private final ManyToManyConcurrentArrayQueue framesSendQueue; + private final ArrayList subjects; public AeronClientDuplexConnection(Publication publication) { this.publication = publication; this.framesSendQueue = new ManyToManyConcurrentArrayQueue<>(Constants.CONCURRENCY); - this.observable = (Observer o) -> { - observer = o; - observer.onSubscribe(new EmptyDisposable()); - }; - this.worker = Schedulers.computation().createWorker(); - + this.subjects = new ArrayList<>(); } - public Observer getSubscriber() { - return observer; + public List> getSubscriber() { + return subjects; } @Override public Observable getInput() { - return observable; + AeronDuplexConnectionSubject subject = new AeronDuplexConnectionSubject(subjects); + subjects.add(subject); + return subject; } @Override @@ -73,6 +69,5 @@ public ManyToManyConcurrentArrayQueue getFramesSendQueue() { @Override public void close() { - worker.unsubscribe(); } } diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java index db02f6fcc..b05e68b57 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java @@ -1,6 +1,5 @@ package io.reactivesocket.aeron.client.multi; -import io.reactivesocket.Completable; import io.reactivesocket.ConnectionSetupPayload; import io.reactivesocket.Frame; import io.reactivesocket.Payload; @@ -28,6 +27,7 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; @@ -205,12 +205,12 @@ void fragmentHandler(int magicNumber, DirectBuffer buffer, int offset, int lengt final MessageType messageType = MessageType.from(messageTypeInt); if (messageType == MessageType.FRAME) { final AeronClientDuplexConnection connection = connections.get(header.sessionId()); - Observer subscriber = connection.getSubscriber(); + List> subscribers = connection.getSubscriber(); final ByteBuffer bytes = ByteBuffer.allocate(length); buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); final Frame frame = Frame.from(bytes); //System.out.println("$$$$ CLIENT GOT => " + frame.toString() + " - " + atomicLong.getAndIncrement()); - subscriber.onNext(frame); + subscribers.forEach(s -> s.onNext(frame)); } else if (messageType == MessageType.ESTABLISH_CONNECTION_RESPONSE) { final int ackSessionId = buffer.getInt(offset + BitUtil.SIZE_OF_INT); @@ -234,17 +234,7 @@ void fragmentHandler(int magicNumber, DirectBuffer buffer, int offset, int lengt ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS), err -> err.printStackTrace()); - reactiveSocket.start(new Completable() { - @Override - public void success() { - - } - - @Override - public void error(Throwable e) { - - } - }); + reactiveSocket.startAndWait(); reactiveSockets.putIfAbsent(ackSessionId, reactiveSocket); diff --git a/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java b/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java index c1ae7fc65..2fc2457b0 100644 --- a/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java +++ b/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java @@ -76,10 +76,14 @@ public static void tryClaim(Publication publication, BufferFiller fillBuffer, in * @param length the length of data */ public static void tryClaimOrOffer(Publication publication, BufferFiller fillBuffer, int length) { - if (length < Constants.AERON_MTU_SIZE) { - tryClaim(publication, fillBuffer, length); - } else { - offer(publication, fillBuffer); + try { + if (length < Constants.AERON_MTU_SIZE) { + tryClaim(publication, fillBuffer, length); + } else { + offer(publication, fillBuffer); + } + } catch (Throwable t) { + t.printStackTrace(); } } diff --git a/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java index be44fb570..4513a7b33 100644 --- a/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java @@ -3,9 +3,9 @@ import io.reactivesocket.Completable; import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; +import io.reactivesocket.aeron.AeronDuplexConnectionSubject; import io.reactivesocket.aeron.internal.Loggable; import io.reactivesocket.aeron.internal.MessageType; -import io.reactivesocket.internal.EmptyDisposable; import io.reactivesocket.observable.Observable; import io.reactivesocket.observable.Observer; import org.reactivestreams.Publisher; @@ -14,34 +14,31 @@ import uk.co.real_logic.agrona.BitUtil; import uk.co.real_logic.agrona.MutableDirectBuffer; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.TimeUnit; public class AeronServerDuplexConnection implements DuplexConnection, AutoCloseable, Loggable { private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); - - private Publication publication; - private Observer observer; - private Observable observable; + private final Publication publication; + private final ArrayList subjects; public AeronServerDuplexConnection( Publication publication) { this.publication = publication; - this.observable = new Observable() { - @Override - public void subscribe(Observer o) { - observer = o; - o.onSubscribe(new EmptyDisposable()); - } - }; + this.subjects = new ArrayList<>(); } - public Observer getSubscriber() { - return observer; + public List> getSubscriber() { + return subjects; } + @Override public Observable getInput() { - return observable; + AeronDuplexConnectionSubject subject = new AeronDuplexConnectionSubject(subjects); + subjects.add(subject); + return subject; } @Override @@ -83,7 +80,6 @@ void ackEstablishConnection(int ackSessionId) { @Override public void close() { - observer.onComplete(); publication.close(); } } diff --git a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java index 9c6bdf275..9c7c48415 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java @@ -1,6 +1,5 @@ package io.reactivesocket.aeron.server; -import io.reactivesocket.Completable; import io.reactivesocket.ConnectionSetupHandler; import io.reactivesocket.Frame; import io.reactivesocket.LeaseGovernor; @@ -20,6 +19,7 @@ import uk.co.real_logic.agrona.DirectBuffer; import java.nio.ByteBuffer; +import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -121,12 +121,12 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) if (MessageType.FRAME == type) { AeronServerDuplexConnection connection = connections.get(sessionId); if (connection != null) { - Observer subscriber = connection.getSubscriber(); + List> subscribers = connection.getSubscriber(); ByteBuffer bytes = ByteBuffer.allocate(length); buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); final Frame frame = Frame.from(bytes); //System.out.println("### Server Sending => " + frame); - subscriber.onNext(frame); + subscribers.forEach(s -> s.onNext(frame)); } } else if (MessageType.ESTABLISH_CONNECTION_REQUEST == type) { final long start = System.nanoTime(); @@ -165,17 +165,7 @@ void newImageHandler(Image image, String channel, int streamId, int sessionId, l sockets.put(sessionId, socket); - socket.start(new Completable() { - @Override - public void success() { - - } - - @Override - public void error(Throwable e) { - - } - }); + socket.startAndWait(); } else { debug("Unsupported stream id {}", streamId); } From 553eb23050776217b27714e9cb641929aa57f1e2 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Mon, 21 Sep 2015 23:51:08 -0700 Subject: [PATCH 032/105] working again with bi-directional connection, need to clean up messaging --- .../aeron/AeronDuplexConnectionSubject.java | 37 ++++++++++---- .../multi/ReactivesocketAeronClient.java | 3 +- .../aeron/internal/AeronUtil.java | 9 ++-- .../server/AeronServerDuplexConnection.java | 1 + .../aeron/server/CompletableSubscription.java | 48 ++++++------------- .../server/ReactiveSocketAeronServer.java | 2 +- .../client/multi/ReactiveSocketAeronTest.java | 4 +- 7 files changed, 56 insertions(+), 48 deletions(-) diff --git a/src/main/java/io/reactivesocket/aeron/AeronDuplexConnectionSubject.java b/src/main/java/io/reactivesocket/aeron/AeronDuplexConnectionSubject.java index 65b29d2c0..dcd9ea8e1 100644 --- a/src/main/java/io/reactivesocket/aeron/AeronDuplexConnectionSubject.java +++ b/src/main/java/io/reactivesocket/aeron/AeronDuplexConnectionSubject.java @@ -24,15 +24,41 @@ public class AeronDuplexConnectionSubject implements Observable, Observer public AeronDuplexConnectionSubject(ArrayList subjects) { this.id = count.incrementAndGet(); this.subjects = subjects; + + /* + + public AeronServerDuplexConnection( + Publication publication) { + this.publication = publication; + this.observable = new Observable() { + @Override + public void subscribe(Observer o) { + observer = o; + o.onSubscribe(new EmptyDisposable()); + } + }; + } + + */ } @Override public void subscribe(Observer o) { internal = o; + internal.onSubscribe(() -> + subjects + .removeIf(new Predicate() { + @Override + public boolean test(AeronDuplexConnectionSubject subject) { + return id == subject.id; + } + }) + ); } @Override public void onNext(Frame frame) { + System.out.println("^^^^^^^^^^^^^^^^^^^^ on next" + frame); internal.onNext(frame); } @@ -43,20 +69,13 @@ public void onError(Throwable e) { @Override public void onComplete() { + System.out.println("^^@#$@#^^^^^^^^^#$#$^^^^^^#@^^^ on completed"); internal.onComplete(); } @Override public void onSubscribe(Disposable d) { - internal.onSubscribe(()-> - subjects - .removeIf(new Predicate() { - @Override - public boolean test(AeronDuplexConnectionSubject subject) { - return id == subject.id; - } - }) - ); + } @Override diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java index b05e68b57..0756b915f 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java @@ -209,7 +209,7 @@ void fragmentHandler(int magicNumber, DirectBuffer buffer, int offset, int lengt final ByteBuffer bytes = ByteBuffer.allocate(length); buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); final Frame frame = Frame.from(bytes); - //System.out.println("$$$$ CLIENT GOT => " + frame.toString() + " - " + atomicLong.getAndIncrement()); + System.out.println("$$$$ CLIENT GOT => " + frame.toString() + " - " + atomicLong.getAndIncrement()); subscribers.forEach(s -> s.onNext(frame)); } else if (messageType == MessageType.ESTABLISH_CONNECTION_RESPONSE) { final int ackSessionId = buffer.getInt(offset + BitUtil.SIZE_OF_INT); @@ -277,6 +277,7 @@ void poll(final int magicNumber, final Scheduler.Worker worker, CyclicBarrier st final ByteBuffer byteBuffer = frame.getByteBuffer(); final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; + System.out.println("--------- Client sending => " + frame.toString()); // If the length is less the MTU size send the message using tryClaim which does not fragment the message // If the message is larger the the MTU size send it using offer. if (length < mtuLength) { diff --git a/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java b/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java index 2fc2457b0..753f0080c 100644 --- a/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java +++ b/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java @@ -17,15 +17,17 @@ public class AeronUtil { /** * Sends a message using offer. This method will spin-lock if Aeron signals back pressure. * - * This method of sending data does not need to know how long the message is. + * This method of sending data does need to know how long the message is. * * @param publication publication to send the message on * * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} * that is send over Aeron */ - public static void offer(Publication publication, BufferFiller fillBuffer) { + public static void offer(Publication publication, BufferFiller fillBuffer, int length) { final UnsafeBuffer buffer = unsafeBuffers.get(); + byte[] bytes = new byte[length]; + buffer.wrap(bytes); fillBuffer.fill(0, buffer); do { final long offer = publication.offer(buffer); @@ -57,6 +59,7 @@ public static void tryClaim(Publication publication, BufferFiller fillBuffer, in final MutableDirectBuffer buffer = bufferClaim.buffer(); final int offset = bufferClaim.offset(); fillBuffer.fill(offset, buffer); + break; } finally { bufferClaim.commit(); } @@ -80,7 +83,7 @@ public static void tryClaimOrOffer(Publication publication, BufferFiller fillBuf if (length < Constants.AERON_MTU_SIZE) { tryClaim(publication, fillBuffer, length); } else { - offer(publication, fillBuffer); + offer(publication, fillBuffer, length); } } catch (Throwable t) { t.printStackTrace(); diff --git a/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java index 4513a7b33..0e014011d 100644 --- a/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java @@ -36,6 +36,7 @@ public List> getSubscriber() { @Override public Observable getInput() { + System.out.println("---- FOR THE SERVER GETTING THE INPUT--"); AeronDuplexConnectionSubject subject = new AeronDuplexConnectionSubject(subjects); subjects.add(subject); return subject; diff --git a/src/main/java/io/reactivesocket/aeron/server/CompletableSubscription.java b/src/main/java/io/reactivesocket/aeron/server/CompletableSubscription.java index 0643d8661..dae9c915c 100644 --- a/src/main/java/io/reactivesocket/aeron/server/CompletableSubscription.java +++ b/src/main/java/io/reactivesocket/aeron/server/CompletableSubscription.java @@ -3,14 +3,11 @@ import io.reactivesocket.Completable; import io.reactivesocket.Frame; import io.reactivesocket.aeron.internal.AeronUtil; -import io.reactivesocket.aeron.internal.Constants; import io.reactivesocket.aeron.internal.MessageType; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.aeron.logbuffer.BufferClaim; import uk.co.real_logic.agrona.BitUtil; -import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicLong; @@ -20,48 +17,39 @@ */ public class CompletableSubscription implements Subscriber { - private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); - - private static final ThreadLocal unsafeBuffers = ThreadLocal.withInitial(() -> new UnsafeBuffer(Constants.EMTPY)); - private final Publication publication; private final Completable completable; - - private final AutoCloseable closeable; - - private final int mtuLength; - private volatile static AtomicLong count = new AtomicLong(); + private Subscription subscription; public CompletableSubscription(Publication publication, Completable completable, AutoCloseable closeable) { this.publication = publication; this.completable = completable; - this.closeable = closeable; - - String mtuLength = System.getProperty("aeron.mtu.length", "4096"); - - this.mtuLength = Integer.parseInt(mtuLength); } @Override public void onSubscribe(Subscription s) { s.request(1); + this.subscription = s; } @Override public void onNext(Frame frame) { final ByteBuffer byteBuffer = frame.getByteBuffer(); - final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; + final int length = frame.length() + BitUtil.SIZE_OF_INT; - AeronUtil.tryClaimOrOffer(publication, (offset, buffer) -> { - final byte[] bytes = new byte[length]; - buffer.wrap(bytes); - buffer.putShort(offset, getCount()); - buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); - buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, byteBuffer.capacity()); - }, length); - + try { + AeronUtil.tryClaimOrOffer(publication, (offset, buffer) -> { + buffer.putShort(offset, getCount()); + buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); + buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, frame.offset(), frame.length()); + }, length); + } catch (Throwable t) { + t.printStackTrace(); + } + + System.out.println("### Server Sending => " + frame); } @Override @@ -71,6 +59,7 @@ public void onError(Throwable t) { @Override public void onComplete() { + System.out.println("&&&&&&&&&&&&&&&&& CompletableSubscription.onComplete"); completable.success(); } @@ -78,11 +67,4 @@ private short getCount() { return (short) count.incrementAndGet(); } - void closeQuietly(AutoCloseable closeable) { - try { - closeable.close(); - } catch (Exception e) { - // ignore - } - } } diff --git a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java index 9c7c48415..6808848f6 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java @@ -125,7 +125,7 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) ByteBuffer bytes = ByteBuffer.allocate(length); buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); final Frame frame = Frame.from(bytes); - //System.out.println("### Server Sending => " + frame); + System.out.println("&&&&&&& fragmentHandler -> " + frame.toString()); subscribers.forEach(s -> s.onNext(frame)); } } else if (MessageType.ESTABLISH_CONNECTION_REQUEST == type) { diff --git a/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java index c579d8cda..649f242af 100644 --- a/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java @@ -19,6 +19,7 @@ import java.nio.ByteBuffer; import java.util.Random; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicLong; /** * Created by rroeser on 8/14/15. @@ -35,6 +36,7 @@ public static void init() { @Test(timeout = 6000000) public void testRequestReponse() throws Exception { + AtomicLong server = new AtomicLong(); ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { @Override public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { @@ -45,7 +47,7 @@ public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupExc public Publisher handleRequestResponse(Payload payload) { String request = TestUtil.byteToString(payload.getData()); System.out.println(Thread.currentThread() + " Server got => " + request); - Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong", null)); + Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong => " + server.incrementAndGet(), null)); return RxReactiveStreams.toPublisher(pong); } From e34b4aab6a0c3ce72651ec93cc1fabb7eae4087b Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Tue, 22 Sep 2015 15:04:14 -0700 Subject: [PATCH 033/105] updated AeronUtil to recycle MutableByteBuffers on the offer method --- .../aeron/AeronDuplexConnectionSubject.java | 16 ------- .../AbstractClientDuplexConnection.java | 43 +++++++++++++++++++ .../multi/AeronClientDuplexConnection.java | 35 +++------------ .../single/AeronClientDuplexConnection.java | 2 +- .../aeron/internal/AeronUtil.java | 40 +++++++++++++++-- .../server/AeronServerDuplexConnection.java | 4 +- ...scription.java => ServerSubscription.java} | 26 +++++------ 7 files changed, 103 insertions(+), 63 deletions(-) create mode 100644 src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java rename src/main/java/io/reactivesocket/aeron/server/{CompletableSubscription.java => ServerSubscription.java} (69%) diff --git a/src/main/java/io/reactivesocket/aeron/AeronDuplexConnectionSubject.java b/src/main/java/io/reactivesocket/aeron/AeronDuplexConnectionSubject.java index dcd9ea8e1..a72caa42c 100644 --- a/src/main/java/io/reactivesocket/aeron/AeronDuplexConnectionSubject.java +++ b/src/main/java/io/reactivesocket/aeron/AeronDuplexConnectionSubject.java @@ -24,22 +24,6 @@ public class AeronDuplexConnectionSubject implements Observable, Observer public AeronDuplexConnectionSubject(ArrayList subjects) { this.id = count.incrementAndGet(); this.subjects = subjects; - - /* - - public AeronServerDuplexConnection( - Publication publication) { - this.publication = publication; - this.observable = new Observable() { - @Override - public void subscribe(Observer o) { - observer = o; - o.onSubscribe(new EmptyDisposable()); - } - }; - } - - */ } @Override diff --git a/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java new file mode 100644 index 000000000..c85ab7edb --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java @@ -0,0 +1,43 @@ +package io.reactivesocket.aeron.client; + +import io.reactivesocket.DuplexConnection; +import io.reactivesocket.Frame; +import io.reactivesocket.aeron.AeronDuplexConnectionSubject; +import io.reactivesocket.aeron.internal.concurrent.AbstractConcurrentArrayQueue; +import io.reactivesocket.observable.Observable; +import io.reactivesocket.observable.Observer; +import uk.co.real_logic.aeron.Publication; + +import java.util.ArrayList; +import java.util.List; + +public abstract class AbstractClientDuplexConnection, F> implements DuplexConnection { + protected final ArrayList subjects; + + protected final Publication publication; + + protected final T framesSendQueue; + + public AbstractClientDuplexConnection(Publication publication) { + this.publication = publication; + this.subjects = new ArrayList<>(); + this.framesSendQueue = createQueue(); + } + + @Override + public final Observable getInput() { + AeronDuplexConnectionSubject subject = new AeronDuplexConnectionSubject(subjects); + subjects.add(subject); + return subject; + } + + public final List> getSubscriber() { + return subjects; + } + + public final T getFramesSendQueue() { + return framesSendQueue; + } + + protected abstract T createQueue(); +} diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java index b897f26c6..28a28c1d9 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java @@ -2,41 +2,23 @@ import io.reactivesocket.Completable; -import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; -import io.reactivesocket.aeron.AeronDuplexConnectionSubject; +import io.reactivesocket.aeron.client.AbstractClientDuplexConnection; import io.reactivesocket.aeron.internal.Constants; import io.reactivesocket.aeron.internal.concurrent.ManyToManyConcurrentArrayQueue; -import io.reactivesocket.observable.Observable; -import io.reactivesocket.observable.Observer; import org.reactivestreams.Publisher; import rx.RxReactiveStreams; import rx.exceptions.MissingBackpressureException; import uk.co.real_logic.aeron.Publication; -import java.util.ArrayList; -import java.util.List; - -public class AeronClientDuplexConnection implements DuplexConnection, AutoCloseable { - private final Publication publication; - private final ManyToManyConcurrentArrayQueue framesSendQueue; - private final ArrayList subjects; - +public class AeronClientDuplexConnection extends AbstractClientDuplexConnection, FrameHolder> { public AeronClientDuplexConnection(Publication publication) { - this.publication = publication; - this.framesSendQueue = new ManyToManyConcurrentArrayQueue<>(Constants.CONCURRENCY); - this.subjects = new ArrayList<>(); - } - - public List> getSubscriber() { - return subjects; + super(publication); } @Override - public Observable getInput() { - AeronDuplexConnectionSubject subject = new AeronDuplexConnectionSubject(subjects); - subjects.add(subject); - return subject; + protected ManyToManyConcurrentArrayQueue createQueue() { + return new ManyToManyConcurrentArrayQueue<>(Constants.CONCURRENCY); } @Override @@ -53,7 +35,7 @@ public void addOutput(Publisher o, Completable callback) { int i = 0; do { offer = framesSendQueue.offer(fh); - if (!offer && ++i > 100) { + if (!offer && ++i > Constants.MULTI_THREADED_SPIN_LIMIT) { rx.Observable.error(new MissingBackpressureException()); } } while (!offer); @@ -63,11 +45,8 @@ public void addOutput(Publisher o, Completable callback) { }, callback::error, callback::success); } - public ManyToManyConcurrentArrayQueue getFramesSendQueue() { - return framesSendQueue; - } - @Override public void close() { } + } diff --git a/src/main/java/io/reactivesocket/aeron/client/single/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/single/AeronClientDuplexConnection.java index a5d117110..1853c08f1 100644 --- a/src/main/java/io/reactivesocket/aeron/client/single/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/single/AeronClientDuplexConnection.java @@ -14,7 +14,7 @@ import uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue; import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; -public class AeronClientDuplexConnection implements DuplexConnection, AutoCloseable { +public class AeronClientDuplexConnection implements DuplexConnection { private Publication publication; private Observer observer; private Observable observable; diff --git a/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java b/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java index 753f0080c..87aa92d9e 100644 --- a/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java +++ b/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java @@ -3,6 +3,7 @@ import uk.co.real_logic.aeron.Publication; import uk.co.real_logic.aeron.logbuffer.BufferClaim; import uk.co.real_logic.agrona.MutableDirectBuffer; +import uk.co.real_logic.agrona.concurrent.OneToOneConcurrentArrayQueue; import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; /** @@ -12,7 +13,8 @@ public class AeronUtil { private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); - private static final ThreadLocal unsafeBuffers = ThreadLocal.withInitial(() -> new UnsafeBuffer(Constants.EMTPY)); + private static final ThreadLocal> unsafeBuffers + = ThreadLocal.withInitial(() -> new OneToOneConcurrentArrayQueue<>(16)); /** * Sends a message using offer. This method will spin-lock if Aeron signals back pressure. @@ -25,9 +27,7 @@ public class AeronUtil { * that is send over Aeron */ public static void offer(Publication publication, BufferFiller fillBuffer, int length) { - final UnsafeBuffer buffer = unsafeBuffers.get(); - byte[] bytes = new byte[length]; - buffer.wrap(bytes); + final MutableDirectBuffer buffer = getDirectBuffer(length); fillBuffer.fill(0, buffer); do { final long offer = publication.offer(buffer); @@ -37,6 +37,38 @@ public static void offer(Publication publication, BufferFiller fillBuffer, int l throw new RuntimeException("not connected"); } } while(true); + + recycleDirectBuffer(buffer); + } + + /** + * Try to get a MutableDirectBuffer from a thread-safe pool for a given length. If the buffer found + * is bigger then the buffer in the pool creates a new buffer. If no buffer is found creates a new buffer + * + * @param length the requested length + * @return either a new MutableDirectBuffer or a recycled one that has the capacity to hold the data from the old one + */ + public static MutableDirectBuffer getDirectBuffer(int length) { + OneToOneConcurrentArrayQueue queue = unsafeBuffers.get(); + MutableDirectBuffer buffer = queue.poll(); + + if (buffer != null && buffer.capacity() < length) { + return buffer; + } else { + byte[] bytes = new byte[length]; + buffer = new UnsafeBuffer(bytes); + return buffer; + } + } + + /** + * Sends a DirectBuffer back to the thread pools to be recycled. + * + * @param directBuffer the DirectBuffer to recycle + */ + public static void recycleDirectBuffer(MutableDirectBuffer directBuffer) { + OneToOneConcurrentArrayQueue queue = unsafeBuffers.get(); + queue.offer(directBuffer); } /** diff --git a/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java index 0e014011d..d362c1946 100644 --- a/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java @@ -18,7 +18,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; -public class AeronServerDuplexConnection implements DuplexConnection, AutoCloseable, Loggable { +public class AeronServerDuplexConnection implements DuplexConnection, Loggable { private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); private final Publication publication; @@ -44,7 +44,7 @@ public Observable getInput() { @Override public void addOutput(Publisher o, Completable callback) { - o.subscribe(new CompletableSubscription(publication, callback, this)); + o.subscribe(new ServerSubscription(publication, callback)); } void ackEstablishConnection(int ackSessionId) { diff --git a/src/main/java/io/reactivesocket/aeron/server/CompletableSubscription.java b/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java similarity index 69% rename from src/main/java/io/reactivesocket/aeron/server/CompletableSubscription.java rename to src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java index dae9c915c..8617d5932 100644 --- a/src/main/java/io/reactivesocket/aeron/server/CompletableSubscription.java +++ b/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java @@ -10,28 +10,32 @@ import uk.co.real_logic.agrona.BitUtil; import java.nio.ByteBuffer; -import java.util.concurrent.atomic.AtomicLong; /** - * Created by rroeser on 8/27/15. + * Subscription used by the AeronServerDuplexConnection to handle incoming frames and send them + * on a publication. + * + * @see io.reactivesocket.aeron.server.AeronServerDuplexConnection */ -public class CompletableSubscription implements Subscriber { +class ServerSubscription implements Subscriber { + + /** + * Count is used to by the client to round-robin request between threads. + */ + private short count; private final Publication publication; private final Completable completable; - private volatile static AtomicLong count = new AtomicLong(); - private Subscription subscription; - public CompletableSubscription(Publication publication, Completable completable, AutoCloseable closeable) { + public ServerSubscription(Publication publication, Completable completable) { this.publication = publication; this.completable = completable; } @Override public void onSubscribe(Subscription s) { - s.request(1); - this.subscription = s; + s.request(Long.MAX_VALUE); } @Override @@ -46,10 +50,9 @@ public void onNext(Frame frame) { buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, frame.offset(), frame.length()); }, length); } catch (Throwable t) { - t.printStackTrace(); + onError(t); } - System.out.println("### Server Sending => " + frame); } @Override @@ -59,12 +62,11 @@ public void onError(Throwable t) { @Override public void onComplete() { - System.out.println("&&&&&&&&&&&&&&&&& CompletableSubscription.onComplete"); completable.success(); } private short getCount() { - return (short) count.incrementAndGet(); + return count++; } } From fee59a8ad7f35607d1fedb0444e5710c226d61fd Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Tue, 22 Sep 2015 15:40:30 -0700 Subject: [PATCH 034/105] added the option timeouts to sending data via Aeron --- .../multi/ReactivesocketAeronClient.java | 70 ++------------- .../aeron/internal/AeronUtil.java | 90 ++++++++++++------- .../server/AeronServerDuplexConnection.java | 37 ++------ 3 files changed, 72 insertions(+), 125 deletions(-) diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java index 0756b915f..dccfa6b30 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java @@ -4,7 +4,7 @@ import io.reactivesocket.Frame; import io.reactivesocket.Payload; import io.reactivesocket.ReactiveSocket; -import io.reactivesocket.aeron.internal.Constants; +import io.reactivesocket.aeron.internal.AeronUtil; import io.reactivesocket.aeron.internal.Loggable; import io.reactivesocket.aeron.internal.MessageType; import io.reactivesocket.aeron.internal.concurrent.ManyToManyConcurrentArrayQueue; @@ -16,12 +16,10 @@ import uk.co.real_logic.aeron.FragmentAssembler; import uk.co.real_logic.aeron.Publication; import uk.co.real_logic.aeron.Subscription; -import uk.co.real_logic.aeron.logbuffer.BufferClaim; import uk.co.real_logic.aeron.logbuffer.Header; import uk.co.real_logic.agrona.BitUtil; import uk.co.real_logic.agrona.DirectBuffer; import uk.co.real_logic.agrona.LangUtil; -import uk.co.real_logic.agrona.MutableDirectBuffer; import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; import java.nio.ByteBuffer; @@ -276,16 +274,11 @@ void poll(final int magicNumber, final Scheduler.Worker worker, CyclicBarrier st Frame frame = fh.getFrame(); final ByteBuffer byteBuffer = frame.getByteBuffer(); final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; - - System.out.println("--------- Client sending => " + frame.toString()); - // If the length is less the MTU size send the message using tryClaim which does not fragment the message - // If the message is larger the the MTU size send it using offer. - if (length < mtuLength) { - tryClaim(fh.getPublication(), byteBuffer, length); - } else { - offer(fh.getPublication(), byteBuffer, length); - } - + AeronUtil.tryClaimOrOffer(fh.getPublication(), (offset, buffer) -> { + buffer.putShort(offset, (short) 0); + buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); + buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, 0, byteBuffer.capacity()); + }, length); fh.release(); } catch (Throwable t) { fh.release(); @@ -295,7 +288,6 @@ void poll(final int magicNumber, final Scheduler.Worker worker, CyclicBarrier st }); } - try { final FragmentAssembler fragmentAssembler = new FragmentAssembler( (DirectBuffer buffer, int offset, int length, Header header) -> @@ -325,52 +317,6 @@ void poll(final int magicNumber, final Scheduler.Worker worker, CyclicBarrier st }, 0, 1, TimeUnit.NANOSECONDS); } - private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); - - private static final ThreadLocal unsafeBuffers = ThreadLocal.withInitial(() -> new UnsafeBuffer(Constants.EMTPY)); - - void offer(Publication publication, ByteBuffer byteBuffer, int length) { - final byte[] bytes = new byte[length]; - final UnsafeBuffer unsafeBuffer = unsafeBuffers.get(); - unsafeBuffer.wrap(bytes); - - unsafeBuffer.putShort(0, (short) 0); - unsafeBuffer.putShort(BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); - unsafeBuffer.putBytes(BitUtil.SIZE_OF_INT, byteBuffer, byteBuffer.capacity()); - do { - final long offer = publication.offer(unsafeBuffer); - if (offer >= 0) { - break; - } else if (Publication.NOT_CONNECTED == offer) { - throw new RuntimeException("not connected"); - } - } while(true); - - } - - void tryClaim(Publication publication, ByteBuffer byteBuffer, int length) { - final BufferClaim bufferClaim = bufferClaims.get(); - do { - final long offer = publication.tryClaim(length, bufferClaim); - if (offer >= 0) { - try { - final MutableDirectBuffer buffer = bufferClaim.buffer(); - final int offset = bufferClaim.offset(); - buffer.putShort(offset, (short) 0); - buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); - buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, 0, byteBuffer.capacity()); - } finally { - bufferClaim.commit(); - } - - break; - } else if (Publication.NOT_CONNECTED == offer) { - throw new RuntimeException("not connected"); - } - } while(true); - } - - /** * Establishes a connection between the client and server. Waits for 30 seconds before throwing a exception. */ @@ -394,7 +340,9 @@ void establishConnection(final Publication publication, final int sessionId) { } System.out.println(Thread.currentThread() + " - Sending establishConnection message"); - publication.offer(buffer); + if (offer < 0) { + offer = publication.offer(buffer); + } LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); if (latch.getCount() == 0) { diff --git a/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java b/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java index 87aa92d9e..19e2340ca 100644 --- a/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java +++ b/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java @@ -6,6 +6,8 @@ import uk.co.real_logic.agrona.concurrent.OneToOneConcurrentArrayQueue; import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; +import java.util.concurrent.TimeUnit; + /** * Utils for dealing with Aeron */ @@ -26,10 +28,17 @@ public class AeronUtil { * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} * that is send over Aeron */ - public static void offer(Publication publication, BufferFiller fillBuffer, int length) { + public static void offer(Publication publication, BufferFiller fillBuffer, int length, int timeout, TimeUnit timeUnit) { final MutableDirectBuffer buffer = getDirectBuffer(length); fillBuffer.fill(0, buffer); + final long start = System.nanoTime(); do { + if (timeout > 0) { + final long current = System.nanoTime(); + if ((current - start) > timeUnit.toNanos(timeout)) { + throw new RuntimeException("Timed out publishing data"); + } + } final long offer = publication.offer(buffer); if (offer >= 0) { break; @@ -41,36 +50,6 @@ public static void offer(Publication publication, BufferFiller fillBuffer, int l recycleDirectBuffer(buffer); } - /** - * Try to get a MutableDirectBuffer from a thread-safe pool for a given length. If the buffer found - * is bigger then the buffer in the pool creates a new buffer. If no buffer is found creates a new buffer - * - * @param length the requested length - * @return either a new MutableDirectBuffer or a recycled one that has the capacity to hold the data from the old one - */ - public static MutableDirectBuffer getDirectBuffer(int length) { - OneToOneConcurrentArrayQueue queue = unsafeBuffers.get(); - MutableDirectBuffer buffer = queue.poll(); - - if (buffer != null && buffer.capacity() < length) { - return buffer; - } else { - byte[] bytes = new byte[length]; - buffer = new UnsafeBuffer(bytes); - return buffer; - } - } - - /** - * Sends a DirectBuffer back to the thread pools to be recycled. - * - * @param directBuffer the DirectBuffer to recycle - */ - public static void recycleDirectBuffer(MutableDirectBuffer directBuffer) { - OneToOneConcurrentArrayQueue queue = unsafeBuffers.get(); - queue.offer(directBuffer); - } - /** * Sends a message using tryClaim. This method will spin-lock if Aeron signals back pressure. The message * being sent needs to be equal or smaller than Aeron's MTU size or an exception will be thrown. @@ -82,9 +61,17 @@ public static void recycleDirectBuffer(MutableDirectBuffer directBuffer) { * that is send over Aeron * @param length the length of data */ - public static void tryClaim(Publication publication, BufferFiller fillBuffer, int length) { + public static void tryClaim(Publication publication, BufferFiller fillBuffer, int length, int timeout, TimeUnit timeUnit) { final BufferClaim bufferClaim = bufferClaims.get(); + final long start = System.nanoTime(); do { + if (timeout > 0) { + final long current = System.nanoTime(); + if ((current - start) > timeUnit.toNanos(timeout)) { + throw new RuntimeException("Timed out publishing data"); + } + } + final long offer = publication.tryClaim(length, bufferClaim); if (offer >= 0) { try { @@ -111,17 +98,52 @@ public static void tryClaim(Publication publication, BufferFiller fillBuffer, in * @param length the length of data */ public static void tryClaimOrOffer(Publication publication, BufferFiller fillBuffer, int length) { + tryClaimOrOffer(publication, fillBuffer, length, -1, null); + } + + public static void tryClaimOrOffer(Publication publication, BufferFiller fillBuffer, int length, int timeout, TimeUnit timeUnit) { try { if (length < Constants.AERON_MTU_SIZE) { - tryClaim(publication, fillBuffer, length); + tryClaim(publication, fillBuffer, length, timeout, timeUnit); } else { - offer(publication, fillBuffer, length); + offer(publication, fillBuffer, length, timeout, timeUnit); } } catch (Throwable t) { t.printStackTrace(); } } + + /** + * Try to get a MutableDirectBuffer from a thread-safe pool for a given length. If the buffer found + * is bigger then the buffer in the pool creates a new buffer. If no buffer is found creates a new buffer + * + * @param length the requested length + * @return either a new MutableDirectBuffer or a recycled one that has the capacity to hold the data from the old one + */ + public static MutableDirectBuffer getDirectBuffer(int length) { + OneToOneConcurrentArrayQueue queue = unsafeBuffers.get(); + MutableDirectBuffer buffer = queue.poll(); + + if (buffer != null && buffer.capacity() < length) { + return buffer; + } else { + byte[] bytes = new byte[length]; + buffer = new UnsafeBuffer(bytes); + return buffer; + } + } + + /** + * Sends a DirectBuffer back to the thread pools to be recycled. + * + * @param directBuffer the DirectBuffer to recycle + */ + public static void recycleDirectBuffer(MutableDirectBuffer directBuffer) { + OneToOneConcurrentArrayQueue queue = unsafeBuffers.get(); + queue.offer(directBuffer); + } + /** * Implement this to fill a DirectBuffer passed in by either the offer or tryClaim methods. */ diff --git a/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java index d362c1946..a21b21ea2 100644 --- a/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java @@ -4,6 +4,7 @@ import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; import io.reactivesocket.aeron.AeronDuplexConnectionSubject; +import io.reactivesocket.aeron.internal.AeronUtil; import io.reactivesocket.aeron.internal.Loggable; import io.reactivesocket.aeron.internal.MessageType; import io.reactivesocket.observable.Observable; @@ -12,7 +13,6 @@ import uk.co.real_logic.aeron.Publication; import uk.co.real_logic.aeron.logbuffer.BufferClaim; import uk.co.real_logic.agrona.BitUtil; -import uk.co.real_logic.agrona.MutableDirectBuffer; import java.util.ArrayList; import java.util.List; @@ -48,35 +48,12 @@ public void addOutput(Publisher o, Completable callback) { } void ackEstablishConnection(int ackSessionId) { - final long start = System.nanoTime(); - final int sessionId = publication.sessionId(); - final BufferClaim bufferClaim = bufferClaims.get(); - - debug("Acking establish connection for session id => {}", ackSessionId); - - for (;;) { - final long current = System.nanoTime(); - if ((current - start) > TimeUnit.SECONDS.toNanos(30)) { - throw new RuntimeException("Timed out waiting to establish connection for session id => " + sessionId); - } - - final long offer = publication.tryClaim(2 * BitUtil.SIZE_OF_INT, bufferClaim); - if (offer >= 0) { - try { - final MutableDirectBuffer buffer = bufferClaim.buffer(); - final int offset = bufferClaim.offset(); - - buffer.putShort(offset, (short) 0); - buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.ESTABLISH_CONNECTION_RESPONSE.getEncodedType()); - buffer.putInt(offset + BitUtil.SIZE_OF_INT, ackSessionId); - } finally { - bufferClaim.commit(); - } - - break; - } - - } + debug("Acking establish connection for session id => {}", ackSessionId); + AeronUtil.tryClaimOrOffer(publication, (offset, buffer) -> { + buffer.putShort(offset, (short) 0); + buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.ESTABLISH_CONNECTION_RESPONSE.getEncodedType()); + buffer.putInt(offset + BitUtil.SIZE_OF_INT, ackSessionId); + }, 2 * BitUtil.SIZE_OF_INT, 30, TimeUnit.SECONDS); } @Override From e623f652bf7ef9d111337a2bcd5508ece54ac1d0 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Tue, 22 Sep 2015 15:55:37 -0700 Subject: [PATCH 035/105] more cleanup --- .../aeron/AeronDuplexConnectionSubject.java | 17 +++------- .../AbstractClientDuplexConnection.java | 6 ++-- .../server/AeronServerDuplexConnection.java | 4 --- .../server/ReactiveSocketAeronServer.java | 34 ++++++++++--------- 4 files changed, 25 insertions(+), 36 deletions(-) diff --git a/src/main/java/io/reactivesocket/aeron/AeronDuplexConnectionSubject.java b/src/main/java/io/reactivesocket/aeron/AeronDuplexConnectionSubject.java index a72caa42c..4149e3642 100644 --- a/src/main/java/io/reactivesocket/aeron/AeronDuplexConnectionSubject.java +++ b/src/main/java/io/reactivesocket/aeron/AeronDuplexConnectionSubject.java @@ -5,9 +5,8 @@ import io.reactivesocket.observable.Observable; import io.reactivesocket.observable.Observer; -import java.util.ArrayList; +import java.util.List; import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Predicate; /** * Class used to manage connections input to a duplex connection in Aeron. @@ -17,11 +16,11 @@ public class AeronDuplexConnectionSubject implements Observable, Observer private final long id; - private final ArrayList subjects; + private final List subjects; private Observer internal; - public AeronDuplexConnectionSubject(ArrayList subjects) { + public AeronDuplexConnectionSubject(List subjects) { this.id = count.incrementAndGet(); this.subjects = subjects; } @@ -31,18 +30,11 @@ public void subscribe(Observer o) { internal = o; internal.onSubscribe(() -> subjects - .removeIf(new Predicate() { - @Override - public boolean test(AeronDuplexConnectionSubject subject) { - return id == subject.id; - } - }) - ); + .removeIf(s -> s.id == id)); } @Override public void onNext(Frame frame) { - System.out.println("^^^^^^^^^^^^^^^^^^^^ on next" + frame); internal.onNext(frame); } @@ -53,7 +45,6 @@ public void onError(Throwable e) { @Override public void onComplete() { - System.out.println("^^@#$@#^^^^^^^^^#$#$^^^^^^#@^^^ on completed"); internal.onComplete(); } diff --git a/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java index c85ab7edb..f8907760c 100644 --- a/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java @@ -8,11 +8,11 @@ import io.reactivesocket.observable.Observer; import uk.co.real_logic.aeron.Publication; -import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; public abstract class AbstractClientDuplexConnection, F> implements DuplexConnection { - protected final ArrayList subjects; + protected final CopyOnWriteArrayList subjects; protected final Publication publication; @@ -20,7 +20,7 @@ public abstract class AbstractClientDuplexConnection(); + this.subjects = new CopyOnWriteArrayList<>(); this.framesSendQueue = createQueue(); } diff --git a/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java index a21b21ea2..47ba8a4d5 100644 --- a/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java @@ -11,7 +11,6 @@ import io.reactivesocket.observable.Observer; import org.reactivestreams.Publisher; import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.aeron.logbuffer.BufferClaim; import uk.co.real_logic.agrona.BitUtil; import java.util.ArrayList; @@ -19,8 +18,6 @@ import java.util.concurrent.TimeUnit; public class AeronServerDuplexConnection implements DuplexConnection, Loggable { - - private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); private final Publication publication; private final ArrayList subjects; @@ -36,7 +33,6 @@ public List> getSubscriber() { @Override public Observable getInput() { - System.out.println("---- FOR THE SERVER GETTING THE INPUT--"); AeronDuplexConnectionSubject subject = new AeronDuplexConnectionSubject(subjects); subjects.add(subject); return subject; diff --git a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java index 6808848f6..c8fb931f2 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java @@ -7,8 +7,6 @@ import io.reactivesocket.aeron.internal.Loggable; import io.reactivesocket.aeron.internal.MessageType; import io.reactivesocket.observable.Observer; -import rx.Scheduler; -import rx.schedulers.Schedulers; import uk.co.real_logic.aeron.Aeron; import uk.co.real_logic.aeron.FragmentAssembler; import uk.co.real_logic.aeron.Image; @@ -29,24 +27,22 @@ import static io.reactivesocket.aeron.internal.Constants.SERVER_STREAM_ID; public class ReactiveSocketAeronServer implements AutoCloseable, Loggable { - private final Aeron aeron; + private static volatile Aeron aeron; + + private volatile boolean running = true; private final int port; private final ConcurrentHashMap connections; - private final Scheduler.Worker worker; + private final ConcurrentHashMap sockets; private final Subscription subscription; - private volatile boolean running = true; - private final ConnectionSetupHandler connectionSetupHandler; private final LeaseGovernor leaseGovernor; - private final ConcurrentHashMap sockets; - private final CountDownLatch shutdownLatch; private ReactiveSocketAeronServer(String host, int port, ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { @@ -57,11 +53,17 @@ private ReactiveSocketAeronServer(String host, int port, ConnectionSetupHandler this.sockets = new ConcurrentHashMap<>(); this.shutdownLatch = new CountDownLatch(1); - final Aeron.Context ctx = new Aeron.Context(); - ctx.newImageHandler(this::newImageHandler); - ctx.errorHandler(t -> error(t.getMessage(), t)); + if (aeron == null) { + synchronized (shutdownLatch) { + if (aeron == null) { + final Aeron.Context ctx = new Aeron.Context(); + ctx.newImageHandler(this::newImageHandler); + ctx.errorHandler(t -> error(t.getMessage(), t)); - aeron = Aeron.connect(ctx); + aeron = Aeron.connect(ctx); + } + } + } final String serverChannel = "udp://" + host + ":" + port; info("Start new ReactiveSocketAeronServer on channel {}", serverChannel); @@ -69,7 +71,6 @@ private ReactiveSocketAeronServer(String host, int port, ConnectionSetupHandler final FragmentAssembler fragmentAssembler = new FragmentAssembler(this::fragmentHandler); - worker = Schedulers.newThread().createWorker(); poll(fragmentAssembler); } @@ -99,7 +100,7 @@ public static ReactiveSocketAeronServer create(ConnectionSetupHandler connection } void poll(FragmentAssembler fragmentAssembler) { - worker.schedule(() -> { + Thread dutyThread = new Thread(() -> { while (running) { try { int poll = subscription.poll(fragmentAssembler, Integer.MAX_VALUE); @@ -110,6 +111,9 @@ void poll(FragmentAssembler fragmentAssembler) { } shutdownLatch.countDown(); }); + dutyThread.setName("reactive-socket-aeron-server"); + dutyThread.setDaemon(true); + dutyThread.start(); } void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) { @@ -125,7 +129,6 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) ByteBuffer bytes = ByteBuffer.allocate(length); buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); final Frame frame = Frame.from(bytes); - System.out.println("&&&&&&& fragmentHandler -> " + frame.toString()); subscribers.forEach(s -> s.onNext(frame)); } } else if (MessageType.ESTABLISH_CONNECTION_REQUEST == type) { @@ -177,7 +180,6 @@ public void close() throws Exception { shutdownLatch.await(30, TimeUnit.SECONDS); - worker.unsubscribe(); aeron.close(); for (AeronServerDuplexConnection connection : connections.values()) { From f2687b629f490ab7547aa5f1498bdc2be743e764 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Tue, 22 Sep 2015 16:00:45 -0700 Subject: [PATCH 036/105] using Frame.wrap on the server side --- .../aeron/server/ReactiveSocketAeronServer.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java index c8fb931f2..d4f4f5afa 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java @@ -16,7 +16,6 @@ import uk.co.real_logic.agrona.BitUtil; import uk.co.real_logic.agrona.DirectBuffer; -import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; @@ -126,9 +125,7 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) AeronServerDuplexConnection connection = connections.get(sessionId); if (connection != null) { List> subscribers = connection.getSubscriber(); - ByteBuffer bytes = ByteBuffer.allocate(length); - buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); - final Frame frame = Frame.from(bytes); + final Frame frame = Frame.from(buffer, BitUtil.SIZE_OF_INT + offset, length); subscribers.forEach(s -> s.onNext(frame)); } } else if (MessageType.ESTABLISH_CONNECTION_REQUEST == type) { From 0588ed77bbab4456f99dbd93427248b97c032033 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Tue, 22 Sep 2015 23:16:06 -0700 Subject: [PATCH 037/105] switched to rx packages --- .../aeron/AeronDuplexConnectionSubject.java | 6 +- .../AbstractClientDuplexConnection.java | 4 +- .../multi/AeronClientDuplexConnection.java | 2 +- .../aeron/client/multi/FrameHolder.java | 8 +- .../multi/ReactivesocketAeronClient.java | 122 ++++++++---------- .../single/AeronClientDuplexConnection.java | 8 +- .../aeron/client/single/FrameHolder.java | 6 - .../single/ReactivesocketAeronClient.java | 4 +- .../aeron/internal/Constants.java | 2 +- .../aeron/internal/Int2ObjectPair.java | 26 ++++ .../server/AeronServerDuplexConnection.java | 6 +- .../server/ReactiveSocketAeronServer.java | 39 ++++-- .../aeron/server/ServerSubscription.java | 2 +- 13 files changed, 126 insertions(+), 109 deletions(-) create mode 100644 src/main/java/io/reactivesocket/aeron/internal/Int2ObjectPair.java diff --git a/src/main/java/io/reactivesocket/aeron/AeronDuplexConnectionSubject.java b/src/main/java/io/reactivesocket/aeron/AeronDuplexConnectionSubject.java index 4149e3642..1772b6ee5 100644 --- a/src/main/java/io/reactivesocket/aeron/AeronDuplexConnectionSubject.java +++ b/src/main/java/io/reactivesocket/aeron/AeronDuplexConnectionSubject.java @@ -1,9 +1,9 @@ package io.reactivesocket.aeron; import io.reactivesocket.Frame; -import io.reactivesocket.observable.Disposable; -import io.reactivesocket.observable.Observable; -import io.reactivesocket.observable.Observer; +import io.reactivesocket.rx.Disposable; +import io.reactivesocket.rx.Observable; +import io.reactivesocket.rx.Observer; import java.util.List; import java.util.concurrent.atomic.AtomicLong; diff --git a/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java index f8907760c..6d521947c 100644 --- a/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java @@ -4,8 +4,8 @@ import io.reactivesocket.Frame; import io.reactivesocket.aeron.AeronDuplexConnectionSubject; import io.reactivesocket.aeron.internal.concurrent.AbstractConcurrentArrayQueue; -import io.reactivesocket.observable.Observable; -import io.reactivesocket.observable.Observer; +import io.reactivesocket.rx.Observable; +import io.reactivesocket.rx.Observer; import uk.co.real_logic.aeron.Publication; import java.util.List; diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java index 28a28c1d9..57530e431 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java @@ -1,11 +1,11 @@ package io.reactivesocket.aeron.client.multi; -import io.reactivesocket.Completable; import io.reactivesocket.Frame; import io.reactivesocket.aeron.client.AbstractClientDuplexConnection; import io.reactivesocket.aeron.internal.Constants; import io.reactivesocket.aeron.internal.concurrent.ManyToManyConcurrentArrayQueue; +import io.reactivesocket.rx.Completable; import org.reactivestreams.Publisher; import rx.RxReactiveStreams; import rx.exceptions.MissingBackpressureException; diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java b/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java index 88c949cfe..53223b29c 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java @@ -8,18 +8,12 @@ /** * Holds a frame and the publication that it's supposed to be sent on. - * Pools instances on an {@link uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue} + * Pools instances on an {@link ManyToManyConcurrentArrayQueue} */ class FrameHolder { private static ManyToManyConcurrentArrayQueue FRAME_HOLDER_QUEUE = new ManyToManyConcurrentArrayQueue<>(Constants.QUEUE_SIZE); - static { - for (int i = 0; i < Constants.QUEUE_SIZE; i++) { - FRAME_HOLDER_QUEUE.offer(new FrameHolder()); - } - } - private Publication publication; private Frame frame; private Subscriber s; diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java index dccfa6b30..4a04253a3 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java @@ -8,7 +8,7 @@ import io.reactivesocket.aeron.internal.Loggable; import io.reactivesocket.aeron.internal.MessageType; import io.reactivesocket.aeron.internal.concurrent.ManyToManyConcurrentArrayQueue; -import io.reactivesocket.observable.Observer; +import io.reactivesocket.rx.Observer; import org.reactivestreams.Publisher; import rx.Scheduler; import rx.schedulers.Schedulers; @@ -23,14 +23,13 @@ import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; import java.nio.ByteBuffer; -import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.LockSupport; import static io.reactivesocket.aeron.internal.Constants.CLIENT_STREAM_ID; @@ -43,7 +42,7 @@ * Created by rroeser on 9/16/15. */ public class ReactivesocketAeronClient implements Loggable, AutoCloseable { - static final ArrayList SUBSCRIPTION_GROUPS = new ArrayList<>(); + static final CopyOnWriteArrayList SUBSCRIPTION_GROUPS = new CopyOnWriteArrayList<>(); private class SubscriptionGroup { String channel; @@ -58,7 +57,7 @@ private class SubscriptionGroup { static final ConcurrentHashMap reactiveSockets = new ConcurrentHashMap<>(); - private final Aeron aeron; + private volatile static Aeron aeron; private volatile static boolean running = true; @@ -70,10 +69,6 @@ private class SubscriptionGroup { private final int port; - private static int mtuLength; - - private static final int NUM_PROCESSORS = CONCURRENCY; - private static Scheduler.Worker[] workers; static { @@ -98,13 +93,6 @@ private class SubscriptionGroup { connection.close(); } })); - - mtuLength = Integer.getInteger("aeron.mtu.length", 4096); - workers = new Scheduler.Worker[NUM_PROCESSORS]; - - for (int i = 0; i < NUM_PROCESSORS; i++) { - workers[i] = Schedulers.computation().createWorker(); - } } private static volatile boolean pollingStarted = false; @@ -112,58 +100,71 @@ private class SubscriptionGroup { private ReactivesocketAeronClient(String host, String server, int port) { this.port = port; - final Aeron.Context ctx = new Aeron.Context(); - ctx.errorHandler(t -> { - t.printStackTrace(); - }); + synchronized (SUBSCRIPTION_GROUPS) { + if (aeron == null) { - aeron = Aeron.connect(ctx); + final Aeron.Context ctx = new Aeron.Context(); + ctx.errorHandler(t -> { + t.printStackTrace(); + }); + + aeron = Aeron.connect(ctx); + } + + workers = new Scheduler.Worker[CONCURRENCY]; + + for (int i = 0; i < CONCURRENCY; i++) { + workers[i] = Schedulers.computation().createWorker(); + } + } final String channel = "udp://" + host + ":" + port; final String subscriptionChannel = "udp://" + server + ":" + port; - System.out.println("Creating a publication to channel => " + channel); + debug("Creating a publication to channel => " + channel); Publication publication = aeron.addPublication(channel, SERVER_STREAM_ID); publications.putIfAbsent(publication.sessionId(), publication); - System.out.println("Creating publication => " + publication.toString()); + debug("Creating publication => " + publication.toString()); sessionId = publication.sessionId(); - System.out.println("Created a publication for sessionId => " + sessionId); + debug("Created a publication for sessionId => " + sessionId); synchronized (SUBSCRIPTION_GROUPS) { boolean found = SUBSCRIPTION_GROUPS .stream() .anyMatch(sg -> subscriptionChannel.equals(sg.channel)); if (!found) { SubscriptionGroup subscriptionGroup = new SubscriptionGroup(); - subscriptionGroup.subscriptions = new Subscription[NUM_PROCESSORS]; - for (int i = 0; i < NUM_PROCESSORS; i++) { - System.out.println("Creating a subscription to channel => " + subscriptionChannel + ", and processing => " + i); + subscriptionGroup.subscriptions = new Subscription[CONCURRENCY]; + for (int i = 0; i < CONCURRENCY; i++) { + debug("Creating a subscription to channel => " + subscriptionChannel + ", and processing => " + i); subscriptionGroup.subscriptions[i] = aeron.addSubscription(subscriptionChannel, CLIENT_STREAM_ID); - System.out.println("Subscription created to channel => " + subscriptionChannel + ", and processing => " + i); + debug("Subscription created to channel => " + subscriptionChannel + ", and processing => " + i); } SUBSCRIPTION_GROUPS.add(subscriptionGroup); } } - if (!pollingStarted) { - System.out.println("Polling hasn't started yet - starting " - + Runtime.getRuntime().availableProcessors() - + " pollers"); - CyclicBarrier startBarrier = new CyclicBarrier(NUM_PROCESSORS + 1); - for (int i = 0; i < NUM_PROCESSORS; i++) { - System.out.println("Starting " - + i - + " poller"); - poll(i, Schedulers.computation().createWorker(), startBarrier); - } + synchronized (SUBSCRIPTION_GROUPS) { + if (!pollingStarted) { + debug("Polling hasn't started yet - starting " + + Runtime.getRuntime().availableProcessors() + + " pollers"); + CyclicBarrier startBarrier = new CyclicBarrier(CONCURRENCY + 1); + for (int i = 0; i < CONCURRENCY; i++) { + debug("Starting " + + i + + " poller"); + poll(i, Schedulers.computation().createWorker(), startBarrier); + } - try { - startBarrier.await(30, TimeUnit.SECONDS); - } catch (Exception e) { - LangUtil.rethrowUnchecked(e); - } + try { + startBarrier.await(30, TimeUnit.SECONDS); + } catch (Exception e) { + LangUtil.rethrowUnchecked(e); + } - pollingStarted = true; + pollingStarted = true; + } } establishConnection(publication, sessionId); @@ -178,25 +179,14 @@ public static ReactivesocketAeronClient create(String host, String server) { return create(host, server, 39790); } - static final AtomicLong atomicLong = new AtomicLong(); - - void fragmentHandler(int magicNumber, DirectBuffer buffer, int offset, int length, Header header) { + void fragmentHandler(int threadId, DirectBuffer buffer, int offset, int length, Header header) { try { short messageCount = buffer.getShort(offset); short messageTypeInt = buffer.getShort(offset + BitUtil.SIZE_OF_SHORT); - final int currentMagic = Math.abs(messageCount % NUM_PROCESSORS); -/* - StringBuilder sb = new StringBuilder(); - - sb.append(Thread.currentThread() + " messageTypeInt => " + messageTypeInt).append('\n'); - sb.append(Thread.currentThread() + " messageCount => " + messageCount).append('\n'); - sb.append(Thread.currentThread() + " message type => " + messageType).append('\n'); - - System.out.println(sb.toString());*/ + final int currentThreadId = Math.abs(messageCount % CONCURRENCY); - //LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); - if (currentMagic != magicNumber) { + if (currentThreadId != threadId) { return; } @@ -204,10 +194,10 @@ void fragmentHandler(int magicNumber, DirectBuffer buffer, int offset, int lengt if (messageType == MessageType.FRAME) { final AeronClientDuplexConnection connection = connections.get(header.sessionId()); List> subscribers = connection.getSubscriber(); + //TODO think about how to recycle these, hard because could be handed to another thread I think? final ByteBuffer bytes = ByteBuffer.allocate(length); buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); final Frame frame = Frame.from(bytes); - System.out.println("$$$$ CLIENT GOT => " + frame.toString() + " - " + atomicLong.getAndIncrement()); subscribers.forEach(s -> s.onNext(frame)); } else if (messageType == MessageType.ESTABLISH_CONNECTION_RESPONSE) { final int ackSessionId = buffer.getInt(offset + BitUtil.SIZE_OF_INT); @@ -215,13 +205,12 @@ void fragmentHandler(int magicNumber, DirectBuffer buffer, int offset, int lengt CountDownLatch latch = establishConnectionLatches.remove(ackSessionId); if (latch == null) { - System.out.println(Thread.currentThread() + " => null"); return; } Publication publication = publications.get(ackSessionId); serverSessionId = header.sessionId(); - System.out.println(String.format("Received establish connection ack for session id => %d, and server session id => %d", ackSessionId, serverSessionId)); + debug(String.format("Received establish connection ack for session id => %d, and server session id => %d", ackSessionId, serverSessionId)); final AeronClientDuplexConnection connection = connections .computeIfAbsent(serverSessionId, (_p) -> @@ -249,13 +238,11 @@ void fragmentHandler(int magicNumber, DirectBuffer buffer, int offset, int lengt } } - void poll(final int magicNumber, final Scheduler.Worker worker, CyclicBarrier startBarrier) { + void poll(final int threadId, final Scheduler.Worker worker, CyclicBarrier startBarrier) { worker.schedulePeriodically(() -> { if (startBarrier != null && !pollingStarted) { try { - System.out.println("Waiting... " + magicNumber); startBarrier.await(30, TimeUnit.SECONDS); - System.out.println("We have waited... " + magicNumber); } catch (Exception e) { LangUtil.rethrowUnchecked(e); } @@ -291,7 +278,7 @@ void poll(final int magicNumber, final Scheduler.Worker worker, CyclicBarrier st try { final FragmentAssembler fragmentAssembler = new FragmentAssembler( (DirectBuffer buffer, int offset, int length, Header header) -> - fragmentHandler(magicNumber, buffer, offset, length, header)); + fragmentHandler(threadId, buffer, offset, length, header)); //System.out.println("processing subscriptions => " + magicNumber); //LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); @@ -299,7 +286,7 @@ void poll(final int magicNumber, final Scheduler.Worker worker, CyclicBarrier st .forEach(subscriptionGroup -> { //System.out.println("processing subscriptions in foreach => " + magicNumber); //LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); - Subscription subscription = subscriptionGroup.subscriptions[magicNumber]; + Subscription subscription = subscriptionGroup.subscriptions[threadId]; subscription.poll(fragmentAssembler, Integer.MAX_VALUE); }); } catch (Throwable t) { @@ -339,7 +326,6 @@ void establishConnection(final Publication publication, final int sessionId) { throw new RuntimeException("Timed out waiting to establish connection for session id => " + sessionId); } - System.out.println(Thread.currentThread() + " - Sending establishConnection message"); if (offer < 0) { offer = publication.offer(buffer); } diff --git a/src/main/java/io/reactivesocket/aeron/client/single/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/single/AeronClientDuplexConnection.java index 1853c08f1..2737c3950 100644 --- a/src/main/java/io/reactivesocket/aeron/client/single/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/single/AeronClientDuplexConnection.java @@ -1,11 +1,11 @@ package io.reactivesocket.aeron.client.single; -import io.reactivesocket.Completable; import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; -import io.reactivesocket.internal.EmptyDisposable; -import io.reactivesocket.observable.Observable; -import io.reactivesocket.observable.Observer; +import io.reactivesocket.internal.rx.EmptyDisposable; +import io.reactivesocket.rx.Completable; +import io.reactivesocket.rx.Observable; +import io.reactivesocket.rx.Observer; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; diff --git a/src/main/java/io/reactivesocket/aeron/client/single/FrameHolder.java b/src/main/java/io/reactivesocket/aeron/client/single/FrameHolder.java index 0a9a18029..5a4e3cc81 100644 --- a/src/main/java/io/reactivesocket/aeron/client/single/FrameHolder.java +++ b/src/main/java/io/reactivesocket/aeron/client/single/FrameHolder.java @@ -13,12 +13,6 @@ class FrameHolder { private static ManyToOneConcurrentArrayQueue FRAME_HOLDER_QUEUE = new ManyToOneConcurrentArrayQueue<>(Constants.QUEUE_SIZE); - static { - for (int i = 0; i < Constants.QUEUE_SIZE; i++) { - FRAME_HOLDER_QUEUE.offer(new FrameHolder()); - } - } - private Publication publication; private Frame frame; diff --git a/src/main/java/io/reactivesocket/aeron/client/single/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/client/single/ReactivesocketAeronClient.java index d03be3ae3..ab3cdf74a 100644 --- a/src/main/java/io/reactivesocket/aeron/client/single/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/client/single/ReactivesocketAeronClient.java @@ -1,7 +1,7 @@ package io.reactivesocket.aeron.client.single; import com.gs.collections.impl.map.mutable.ConcurrentHashMap; -import io.reactivesocket.Completable; +import io.reactivesocket.rx.Completable; import io.reactivesocket.ConnectionSetupPayload; import io.reactivesocket.Frame; import io.reactivesocket.Payload; @@ -9,7 +9,7 @@ import io.reactivesocket.aeron.internal.Constants; import io.reactivesocket.aeron.internal.Loggable; import io.reactivesocket.aeron.internal.MessageType; -import io.reactivesocket.observable.Observer; +import io.reactivesocket.rx.Observer; import org.reactivestreams.Publisher; import rx.Scheduler; import rx.schedulers.Schedulers; diff --git a/src/main/java/io/reactivesocket/aeron/internal/Constants.java b/src/main/java/io/reactivesocket/aeron/internal/Constants.java index 798b50272..f4b92b448 100644 --- a/src/main/java/io/reactivesocket/aeron/internal/Constants.java +++ b/src/main/java/io/reactivesocket/aeron/internal/Constants.java @@ -19,7 +19,7 @@ private Constants() {} public static final IdleStrategy SERVER_IDLE_STRATEGY = new NoOpIdleStrategy(); - public static final int CONCURRENCY = Runtime.getRuntime().availableProcessors() / 2; + public static final int CONCURRENCY = Integer.getInteger("clientConcurrency", Runtime.getRuntime().availableProcessors() / 2); public static final int AERON_MTU_SIZE = Integer.getInteger("aeron.mtu.length", 4096); } diff --git a/src/main/java/io/reactivesocket/aeron/internal/Int2ObjectPair.java b/src/main/java/io/reactivesocket/aeron/internal/Int2ObjectPair.java new file mode 100644 index 000000000..a1697e03b --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/internal/Int2ObjectPair.java @@ -0,0 +1,26 @@ +package io.reactivesocket.aeron.internal; + +/** + * Creates a pair, with one value an int and the other an Object. + */ +public class Int2ObjectPair { + private int i; + private T t; + + private Int2ObjectPair(int i, T t) { + this.i = i; + this.t = t; + } + + public static Int2ObjectPair pairOf(int i, T t) { + return new Int2ObjectPair(i, t); + } + + public int getInt() { + return i; + } + + public T getObject() { + return t; + } +} diff --git a/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java index 47ba8a4d5..2ae098096 100644 --- a/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java @@ -1,14 +1,14 @@ package io.reactivesocket.aeron.server; -import io.reactivesocket.Completable; import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; import io.reactivesocket.aeron.AeronDuplexConnectionSubject; import io.reactivesocket.aeron.internal.AeronUtil; import io.reactivesocket.aeron.internal.Loggable; import io.reactivesocket.aeron.internal.MessageType; -import io.reactivesocket.observable.Observable; -import io.reactivesocket.observable.Observer; +import io.reactivesocket.rx.Observable; +import io.reactivesocket.rx.Completable; +import io.reactivesocket.rx.Observer; import org.reactivestreams.Publisher; import uk.co.real_logic.aeron.Publication; import uk.co.real_logic.agrona.BitUtil; diff --git a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java index d4f4f5afa..2ea18fb4c 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java @@ -6,7 +6,7 @@ import io.reactivesocket.ReactiveSocket; import io.reactivesocket.aeron.internal.Loggable; import io.reactivesocket.aeron.internal.MessageType; -import io.reactivesocket.observable.Observer; +import io.reactivesocket.rx.Observer; import uk.co.real_logic.aeron.Aeron; import uk.co.real_logic.aeron.FragmentAssembler; import uk.co.real_logic.aeron.Image; @@ -18,6 +18,7 @@ import java.util.List; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -28,7 +29,11 @@ public class ReactiveSocketAeronServer implements AutoCloseable, Loggable { private static volatile Aeron aeron; - private volatile boolean running = true; + private static volatile boolean running = true; + + private static volatile boolean pollingStarted = false; + + private static final CopyOnWriteArrayList servers = new CopyOnWriteArrayList<>(); private final int port; @@ -44,6 +49,8 @@ public class ReactiveSocketAeronServer implements AutoCloseable, Loggable { private final CountDownLatch shutdownLatch; + private final FragmentAssembler fragmentAssembler; + private ReactiveSocketAeronServer(String host, int port, ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { this.port = port; this.connections = new ConcurrentHashMap<>(); @@ -68,10 +75,11 @@ private ReactiveSocketAeronServer(String host, int port, ConnectionSetupHandler info("Start new ReactiveSocketAeronServer on channel {}", serverChannel); subscription = aeron.addSubscription(serverChannel, SERVER_STREAM_ID); - final FragmentAssembler fragmentAssembler = new FragmentAssembler(this::fragmentHandler); + fragmentAssembler = new FragmentAssembler(this::fragmentHandler); - poll(fragmentAssembler); + servers.add(this); + poll(); } public static ReactiveSocketAeronServer create(String host, int port, ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { @@ -98,21 +106,30 @@ public static ReactiveSocketAeronServer create(ConnectionSetupHandler connection return create(39790, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); } - void poll(FragmentAssembler fragmentAssembler) { + synchronized void poll() { + if (pollingStarted) { + return; + } + Thread dutyThread = new Thread(() -> { while (running) { - try { - int poll = subscription.poll(fragmentAssembler, Integer.MAX_VALUE); - SERVER_IDLE_STRATEGY.idle(poll); - } catch (Throwable t) { - t.printStackTrace(); - } + servers + .forEach(server -> { + try { + int poll = server.subscription.poll(server.fragmentAssembler, Integer.MAX_VALUE); + SERVER_IDLE_STRATEGY.idle(poll); + } catch (Throwable t) { + t.printStackTrace(); + } + }); + } shutdownLatch.countDown(); }); dutyThread.setName("reactive-socket-aeron-server"); dutyThread.setDaemon(true); dutyThread.start(); + pollingStarted = true; } void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) { diff --git a/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java b/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java index 8617d5932..e9da1534c 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java +++ b/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java @@ -1,6 +1,6 @@ package io.reactivesocket.aeron.server; -import io.reactivesocket.Completable; +import io.reactivesocket.rx.Completable; import io.reactivesocket.Frame; import io.reactivesocket.aeron.internal.AeronUtil; import io.reactivesocket.aeron.internal.MessageType; From bde4cb15a8bf80cc83a7556f9efed04982d7db20 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Wed, 23 Sep 2015 00:00:22 -0700 Subject: [PATCH 038/105] added slf4j-simple logging for tests --- build.gradle | 2 +- .../multi/ReactivesocketAeronClient.java | 2 +- .../single/AeronClientDuplexConnection.java | 80 ---- .../aeron/client/single/FrameHolder.java | 46 -- .../single/ReactivesocketAeronClient.java | 418 ------------------ .../aeron/internal/NotConnectedException.java | 10 + .../aeron/ReactiveSocketAeronTest.java | 2 +- .../single/ReactivesocketAeronClientTest.java | 122 ----- src/test/resources/simplelogger.properties | 34 ++ 9 files changed, 47 insertions(+), 669 deletions(-) delete mode 100644 src/main/java/io/reactivesocket/aeron/client/single/AeronClientDuplexConnection.java delete mode 100644 src/main/java/io/reactivesocket/aeron/client/single/FrameHolder.java delete mode 100644 src/main/java/io/reactivesocket/aeron/client/single/ReactivesocketAeronClient.java create mode 100644 src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java delete mode 100644 src/test/java/io/reactivesocket/aeron/client/single/ReactivesocketAeronClientTest.java create mode 100644 src/test/resources/simplelogger.properties diff --git a/build.gradle b/build.gradle index dd8f6a5e2..e040d725f 100644 --- a/build.gradle +++ b/build.gradle @@ -21,10 +21,10 @@ dependencies { compile 'io.reactivesocket:reactivesocket:0.0.1-SNAPSHOT' compile 'uk.co.real-logic:Agrona:0.4.2' compile 'uk.co.real-logic:aeron-all:0.1.2' - compile 'com.goldmansachs:gs-collections:6.2.0' compile 'org.slf4j:slf4j-api:1.7.12' testCompile 'junit:junit-dep:4.10' testCompile 'org.mockito:mockito-core:1.8.5' + testCompile 'org.slf4j:slf4j-simple:1.7.12' } diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java index 4a04253a3..886f6dfa2 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java @@ -147,7 +147,7 @@ private ReactivesocketAeronClient(String host, String server, int port) { synchronized (SUBSCRIPTION_GROUPS) { if (!pollingStarted) { debug("Polling hasn't started yet - starting " - + Runtime.getRuntime().availableProcessors() + + CONCURRENCY + " pollers"); CyclicBarrier startBarrier = new CyclicBarrier(CONCURRENCY + 1); for (int i = 0; i < CONCURRENCY; i++) { diff --git a/src/main/java/io/reactivesocket/aeron/client/single/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/single/AeronClientDuplexConnection.java deleted file mode 100644 index 2737c3950..000000000 --- a/src/main/java/io/reactivesocket/aeron/client/single/AeronClientDuplexConnection.java +++ /dev/null @@ -1,80 +0,0 @@ -package io.reactivesocket.aeron.client.single; - -import io.reactivesocket.DuplexConnection; -import io.reactivesocket.Frame; -import io.reactivesocket.internal.rx.EmptyDisposable; -import io.reactivesocket.rx.Completable; -import io.reactivesocket.rx.Observable; -import io.reactivesocket.rx.Observer; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; -import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.agrona.concurrent.IdleStrategy; -import uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue; -import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; - -public class AeronClientDuplexConnection implements DuplexConnection { - private Publication publication; - private Observer observer; - private Observable observable; - private ManyToOneConcurrentArrayQueue framesSendQueue; - private IdleStrategy idleStrategy; - - public AeronClientDuplexConnection(Publication publication, ManyToOneConcurrentArrayQueue framesSendQueue) { - this.publication = publication; - this.framesSendQueue = framesSendQueue; - this.observable = (Observer o) -> { - observer = o; - observer.onSubscribe(new EmptyDisposable()); - }; - this.idleStrategy = new NoOpIdleStrategy(); - - } - - public Observer getSubscriber() { - return observer; - } - - public Observable getInput() { - return observable; - } - - @Override - public void addOutput(Publisher o, Completable callback) { - o.subscribe(new Subscriber() { - volatile boolean running = true; - Subscription s; - @Override - public void onSubscribe(Subscription s) { - this.s = s; - s.request(framesSendQueue.remainingCapacity() + 1); - //s.request(Long.MAX_VALUE); - } - - @Override - public void onNext(Frame frame) { - final FrameHolder frameHolder = FrameHolder.get(frame, publication); - while (running && !framesSendQueue.offer(frameHolder)) idleStrategy.idle(-1); - s.request(framesSendQueue.remainingCapacity() + 1); - } - - @Override - public void onError(Throwable t) { - running = false; - callback.error(t); - } - - @Override - public void onComplete() { - running = false; - callback.success(); - } - }); - } - - @Override - public void close() { - } -} - diff --git a/src/main/java/io/reactivesocket/aeron/client/single/FrameHolder.java b/src/main/java/io/reactivesocket/aeron/client/single/FrameHolder.java deleted file mode 100644 index 5a4e3cc81..000000000 --- a/src/main/java/io/reactivesocket/aeron/client/single/FrameHolder.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.reactivesocket.aeron.client.single; - -import io.reactivesocket.Frame; -import io.reactivesocket.aeron.internal.Constants; -import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue; - -/** - * Holds a frame and the publication that it's supposed to be sent on. - * Pools instances on an {@link uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue} - */ -class FrameHolder { - private static ManyToOneConcurrentArrayQueue FRAME_HOLDER_QUEUE - = new ManyToOneConcurrentArrayQueue<>(Constants.QUEUE_SIZE); - - private Publication publication; - private Frame frame; - - private FrameHolder() {} - - public static FrameHolder get(Frame frame, Publication publication) { - FrameHolder frameHolder = FRAME_HOLDER_QUEUE.poll(); - - if (frameHolder == null) { - frameHolder = new FrameHolder(); - } - - frameHolder.frame = frame; - frameHolder.publication = publication; - - return frameHolder; - } - - public Publication getPublication() { - return publication; - } - - public Frame getFrame() { - return frame; - } - - public void release() { - frame.release(); - FRAME_HOLDER_QUEUE.offer(this); - } -} diff --git a/src/main/java/io/reactivesocket/aeron/client/single/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/client/single/ReactivesocketAeronClient.java deleted file mode 100644 index ab3cdf74a..000000000 --- a/src/main/java/io/reactivesocket/aeron/client/single/ReactivesocketAeronClient.java +++ /dev/null @@ -1,418 +0,0 @@ -package io.reactivesocket.aeron.client.single; - -import com.gs.collections.impl.map.mutable.ConcurrentHashMap; -import io.reactivesocket.rx.Completable; -import io.reactivesocket.ConnectionSetupPayload; -import io.reactivesocket.Frame; -import io.reactivesocket.Payload; -import io.reactivesocket.ReactiveSocket; -import io.reactivesocket.aeron.internal.Constants; -import io.reactivesocket.aeron.internal.Loggable; -import io.reactivesocket.aeron.internal.MessageType; -import io.reactivesocket.rx.Observer; -import org.reactivestreams.Publisher; -import rx.Scheduler; -import rx.schedulers.Schedulers; -import uk.co.real_logic.aeron.Aeron; -import uk.co.real_logic.aeron.FragmentAssembler; -import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.aeron.Subscription; -import uk.co.real_logic.aeron.logbuffer.BufferClaim; -import uk.co.real_logic.aeron.logbuffer.Header; -import uk.co.real_logic.agrona.BitUtil; -import uk.co.real_logic.agrona.DirectBuffer; -import uk.co.real_logic.agrona.MutableDirectBuffer; -import uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue; -import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; - -import java.nio.ByteBuffer; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import static io.reactivesocket.aeron.internal.Constants.CLIENT_STREAM_ID; -import static io.reactivesocket.aeron.internal.Constants.EMTPY; -import static io.reactivesocket.aeron.internal.Constants.QUEUE_SIZE; -import static io.reactivesocket.aeron.internal.Constants.SERVER_STREAM_ID; - -/** - * Created by rroeser on 8/13/15. - */ -public class ReactivesocketAeronClient implements Loggable, AutoCloseable { - static volatile Subscription[] subscriptions = new Subscription[0]; - - static final ConcurrentHashMap connections = new ConcurrentHashMap<>(); - - static final ConcurrentHashMap establishConnectionLatches = new ConcurrentHashMap<>(); - - static final ConcurrentHashMap publications = new ConcurrentHashMap<>(); - - static final ConcurrentHashMap reactiveSockets = new ConcurrentHashMap<>(); - - private final Aeron aeron; - - private volatile static boolean running = true; - - volatile int sessionId; - - volatile int serverSessionId; - - private static final CountDownLatch shutdownLatch = new CountDownLatch(1); - - private final int port; - - private static final ManyToOneConcurrentArrayQueue framesSendQueue; - - private static int mtuLength; - - static { - Runtime - .getRuntime() - .addShutdownHook(new Thread(() -> { - running = false; - - try { - shutdownLatch.await(5, TimeUnit.SECONDS); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - for (Subscription subscription : subscriptions) { - subscription.close(); - } - - for (AeronClientDuplexConnection connection : connections.values()) { - connection.close(); - } - })); - - framesSendQueue = new ManyToOneConcurrentArrayQueue<>(QUEUE_SIZE); - mtuLength = Integer.getInteger("aeron.mtu.length", 4096); - } - - private static volatile boolean pollingStarted = false; - - private ReactivesocketAeronClient(String host, String server, int port) { - this.port = port; - - final Aeron.Context ctx = new Aeron.Context(); - ctx.errorHandler(t -> { - t.printStackTrace(); - }); - - aeron = Aeron.connect(ctx); - - final String channel = "udp://" + host + ":" + port; - final String subscriptionChannel = "udp://" + server + ":" + port; - - System.out.println("Creating a publication to channel => " + channel); - Publication publication = aeron.addPublication(channel, SERVER_STREAM_ID); - publications.putIfAbsent(publication.sessionId(), publication); - System.out.println("Creating publication => " + publication.toString()); - sessionId = publication.sessionId(); - - System.out.println("Created a publication for sessionId => " + sessionId); - synchronized (subscriptions) { - final Subscription[] old = subscriptions; - boolean found = false; - int i = 0; - while (i < old.length) { - String c = old[i].channel(); - if (c.equals(subscriptionChannel)) { - found = true; - break; - } - i++; - } - - if (!found) { - System.out.println("Creating a subscription to channel => " + subscriptionChannel); - Subscription subscription = aeron.addSubscription(subscriptionChannel, CLIENT_STREAM_ID); - final Subscription[] newList = new Subscription[old.length + 1]; - System.arraycopy(old, 0, newList, 0, old.length); - newList[old.length] = subscription; - subscriptions = newList; - System.out.println("Subscription created to channel => " + subscriptionChannel); - } - - } - - if (!pollingStarted) { - System.out.println("Polling hasn't started yet - starting polling"); - - final FragmentAssembler fragmentAssembler = new FragmentAssembler(this::fragmentHandler); - - poll(fragmentAssembler, Schedulers.newThread().createWorker()); - - pollingStarted = true; - } - - establishConnection(publication, sessionId); - - } - - public static ReactivesocketAeronClient create(String host, String server, int port) { - return new ReactivesocketAeronClient(host, server, port); - } - - public static ReactivesocketAeronClient create(String host, String server) { - return create(host, server, 39790); - } - - void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) { - try { - short messageTypeInt = buffer.getShort(offset + BitUtil.SIZE_OF_SHORT); - MessageType messageType = MessageType.from(messageTypeInt); - if (messageType == MessageType.FRAME) { - final AeronClientDuplexConnection connection = connections.get(header.sessionId()); - Observer subscriber = connection.getSubscriber(); - final ByteBuffer bytes = ByteBuffer.allocate(length); - buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); - final Frame frame = Frame.from(bytes); - subscriber.onNext(frame); - } else if (messageType == MessageType.ESTABLISH_CONNECTION_RESPONSE) { - final int ackSessionId = buffer.getInt(offset + BitUtil.SIZE_OF_INT); - Publication publication = publications.get(ackSessionId); - serverSessionId = header.sessionId(); - System.out.println(String.format("Received establish connection ack for session id => %d, and server session id => %d", ackSessionId, serverSessionId)); - final AeronClientDuplexConnection connection = - connections - .computeIfAbsent(serverSessionId, (_p) -> - new AeronClientDuplexConnection(publication, framesSendQueue)); - - ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection( - connection, - ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS), - err -> err.printStackTrace()); - - reactiveSocket.start(new Completable() { - @Override - public void success() { - - } - - @Override - public void error(Throwable e) { - - } - }); - - reactiveSockets.putIfAbsent(ackSessionId, reactiveSocket); - - info("ReactiveSocket connected to Aeron session => " + ackSessionId); - CountDownLatch latch = establishConnectionLatches.remove(ackSessionId); - - latch.countDown(); - } else { - debug("Unknown message type => " + messageTypeInt); - } - } catch (Throwable t) { - System.out.println("ERROR fragmentHandler"); - t.printStackTrace(); - error("error handling framement", t); - } - } - - void poll(FragmentAssembler fragmentAssembler, Scheduler.Worker worker) { - worker.schedule(() -> { - while (running) { - framesSendQueue - .drain((FrameHolder fh) -> { - try { - Frame frame = fh.getFrame(); - final ByteBuffer byteBuffer = frame.getByteBuffer(); - final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; - - // If the length is less the MTU size send the message using tryClaim which does not fragment the message - // If the message is larger the the MTU size send it using offer. - if (length < mtuLength) { - tryClaim(fh.getPublication(), byteBuffer, length); - } else { - offer(fh.getPublication(), byteBuffer, length); - } - } catch (Throwable t) { - error("error draining send frame queue", t); - } finally { - fh.release(); - } - }); - - try { - final Subscription[] s = subscriptions; - int i = 0; - while (i < s.length) { - s[i].poll(fragmentAssembler, Integer.MAX_VALUE); - i++; - } - } catch (Throwable t) { - error("error polling aeron subscription", t); - } - } - - shutdownLatch.countDown(); - - }); - } - - private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); - - private static final ThreadLocal unsafeBuffers = ThreadLocal.withInitial(() -> new UnsafeBuffer(Constants.EMTPY)); - - void offer(Publication publication, ByteBuffer byteBuffer, int length) { - final byte[] bytes = new byte[length]; - final UnsafeBuffer unsafeBuffer = unsafeBuffers.get(); - unsafeBuffer.wrap(bytes); - unsafeBuffer.putInt(0, MessageType.FRAME.getEncodedType()); - unsafeBuffer.putBytes(BitUtil.SIZE_OF_INT, byteBuffer, byteBuffer.capacity()); - do { - final long offer = publication.offer(unsafeBuffer); - if (offer >= 0) { - break; - } else if (Publication.NOT_CONNECTED == offer) { - throw new RuntimeException("not connected"); - } - } while(true); - - } - - void tryClaim(Publication publication, ByteBuffer byteBuffer, int length) { - final BufferClaim bufferClaim = bufferClaims.get(); - do { - final long offer = publication.tryClaim(length, bufferClaim); - if (offer >= 0) { - try { - final MutableDirectBuffer buffer = bufferClaim.buffer(); - final int offset = bufferClaim.offset(); - buffer.putInt(offset, MessageType.FRAME.getEncodedType()); - buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, 0, byteBuffer.capacity()); - } finally { - bufferClaim.commit(); - } - - break; - } else if (Publication.NOT_CONNECTED == offer) { - throw new RuntimeException("not connected"); - } - } while(true); - } - - - /** - * Establishes a connection between the client and server. Waits for 30 seconds before throwing a exception. - */ - void establishConnection(final Publication publication, final int sessionId) { - try { - final UnsafeBuffer buffer = new UnsafeBuffer(EMTPY); - buffer.wrap(new byte[BitUtil.SIZE_OF_INT]); - buffer.putInt(0, MessageType.ESTABLISH_CONNECTION_REQUEST.getEncodedType()); - - CountDownLatch latch = new CountDownLatch(1); - establishConnectionLatches.put(sessionId, latch); - - long offer = -1; - final long start = System.nanoTime(); - for (;;) { - final long current = System.nanoTime(); - if ((current - start) > TimeUnit.SECONDS.toNanos(30)) { - throw new RuntimeException("Timed out waiting to establish connection for session id => " + sessionId); - } - - if (offer < 0) { - offer = publication.offer(buffer); - } - - if (latch.getCount() == 0) { - break; - } - } - - debug("Connection established for channel => {}, stream id => {}", - publication.channel(), - publication.sessionId()); - } finally { - establishConnectionLatches.remove(sessionId); - } - - } - - public Publisher requestResponse(Payload payload) { - ReactiveSocket reactiveSocket = reactiveSockets.get(sessionId); - return reactiveSocket.requestResponse(payload); - } - - public Publisher fireAndForget(Payload payload) { - ReactiveSocket reactiveSocket = reactiveSockets.get(sessionId); - return reactiveSocket.fireAndForget(payload); - } - - public Publisher requestStream(Payload payload) { - ReactiveSocket reactiveSocket = reactiveSockets.get(sessionId); - return reactiveSocket.requestStream(payload); - } - - public Publisher requestSubscription(Payload payload) { - ReactiveSocket reactiveSocket = reactiveSockets.get(sessionId); - return reactiveSocket.requestSubscription(payload); - } - - public static boolean isRunning() { - return running; - } - - public static void setRunning(boolean running) { - ReactivesocketAeronClient.running = running; - } - - public int getSessionId() { - return sessionId; - } - - public void setSessionId(int sessionId) { - this.sessionId = sessionId; - } - - public int getPort() { - return port; - } - - public int getServerSessionId() { - return serverSessionId; - } - - public void setServerSessionId(int serverSessionId) { - this.serverSessionId = serverSessionId; - } - - public static boolean isPollingStarted() { - return pollingStarted; - } - - public static void setPollingStarted(boolean pollingStarted) { - ReactivesocketAeronClient.pollingStarted = pollingStarted; - } - - @Override - public void close() throws Exception { - // First clean up the different maps - // Remove the AeronDuplexConnection from the connections map - AeronClientDuplexConnection connection = connections.remove(serverSessionId); - - // This should already be removed but remove it just in case to be safe - establishConnectionLatches.remove(sessionId); - - // Close the different connections - closeQuietly(connection); - closeQuietly(reactiveSockets.get(sessionId)); - System.out.println("closing publication => " + publications.get(sessionId).toString()); - Publication publication = publications.remove(sessionId); - closeQuietly(publication); - - } - - private void closeQuietly(AutoCloseable closeable) { - try { - closeable.close(); - } catch (Throwable t) { - debug(t.getMessage(), t); - } - } -} diff --git a/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java b/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java new file mode 100644 index 000000000..38f4d2c4f --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java @@ -0,0 +1,10 @@ +package io.reactivesocket.aeron.internal; + +public class NotConnectedException extends RuntimeException { + + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } + +} diff --git a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java index 3831706d1..2db9cabcb 100644 --- a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java @@ -5,7 +5,7 @@ import io.reactivesocket.Frame; import io.reactivesocket.Payload; import io.reactivesocket.RequestHandler; -import io.reactivesocket.aeron.client.single.ReactivesocketAeronClient; +import io.reactivesocket.aeron.client.multi.ReactivesocketAeronClient; import io.reactivesocket.aeron.server.ReactiveSocketAeronServer; import io.reactivesocket.exceptions.SetupException; import org.junit.BeforeClass; diff --git a/src/test/java/io/reactivesocket/aeron/client/single/ReactivesocketAeronClientTest.java b/src/test/java/io/reactivesocket/aeron/client/single/ReactivesocketAeronClientTest.java deleted file mode 100644 index 9b95d00f0..000000000 --- a/src/test/java/io/reactivesocket/aeron/client/single/ReactivesocketAeronClientTest.java +++ /dev/null @@ -1,122 +0,0 @@ -package io.reactivesocket.aeron.client.single; - -import io.reactivesocket.ConnectionSetupHandler; -import io.reactivesocket.ConnectionSetupPayload; -import io.reactivesocket.Frame; -import io.reactivesocket.Payload; -import io.reactivesocket.RequestHandler; -import io.reactivesocket.aeron.TestUtil; -import io.reactivesocket.aeron.server.ReactiveSocketAeronServer; -import io.reactivesocket.exceptions.SetupException; -import junit.framework.Assert; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; -import org.reactivestreams.Publisher; -import rx.Observable; -import rx.RxReactiveStreams; -import uk.co.real_logic.aeron.driver.MediaDriver; - -import java.nio.ByteBuffer; -import java.util.concurrent.CountDownLatch; - -@Ignore -public class ReactivesocketAeronClientTest { - - @BeforeClass - public static void init() { - final MediaDriver.Context context = new MediaDriver.Context(); - context.dirsDeleteOnStart(true); - - final MediaDriver mediaDriver = MediaDriver.launch(context); - } - - @Test - public void testReconnect() throws Exception { - ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { - @Override - public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { - return new RequestHandler() { - Frame frame = Frame.from(ByteBuffer.allocate(1)); - - @Override - public Publisher handleRequestResponse(Payload payload) { - String request = TestUtil.byteToString(payload.getData()); - //System.out.println("Server got => " + request); - Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong", null)); - return RxReactiveStreams.toPublisher(pong); - } - - @Override - public Publisher handleChannel(Payload initialPayload, Publisher payloads) { - return null; - } - - @Override - public Publisher handleRequestStream(Payload payload) { - return null; - } - - @Override - public Publisher handleSubscription(Payload payload) { - return null; - } - - @Override - public Publisher handleFireAndForget(Payload payload) { - return null; - } - - @Override - public Publisher handleMetadataPush(Payload payload) { - return null; - } - }; - } - }); - - CountDownLatch latch = new CountDownLatch(1); - - ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost", "localhost"); - - Observable - .range(1, 1) - .flatMap(i -> { - //System.out.println("pinging => " + i); - Payload payload = TestUtil.utf8EncodedPayload("ping =>" + i, null); - return RxReactiveStreams.toObservable(client.requestResponse(payload)); - } - ) - .subscribe(new rx.Subscriber() { - @Override - public void onCompleted() { - } - - @Override - public void onError(Throwable e) { - e.printStackTrace(); - } - - @Override - public void onNext(Payload s) { - //System.out.println(s + " countdown => " + latch.getCount()); - latch.countDown(); - } - }); - - latch.await(); - - int serverSessionId = client.serverSessionId; - int sessionId = client.sessionId; - - Assert.assertNotNull(client.connections.get(serverSessionId)); - - client.close(); - - Assert.assertNull(client.connections.get(serverSessionId)); - Assert.assertNull(client.establishConnectionLatches.get(sessionId)); - - ReactivesocketAeronClient newConnection = ReactivesocketAeronClient.create("localhost", "localhost"); - - } -} \ No newline at end of file diff --git a/src/test/resources/simplelogger.properties b/src/test/resources/simplelogger.properties new file mode 100644 index 000000000..bffb759cc --- /dev/null +++ b/src/test/resources/simplelogger.properties @@ -0,0 +1,34 @@ +# SLF4J's SimpleLogger configuration file +# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. + +# Default logging detail level for all instances of SimpleLogger. +# Must be one of ("trace", "debug", "info", "warn", or "error"). +# If not specified, defaults to "info". +org.slf4j.simpleLogger.defaultLogLevel=debug + +# Logging detail level for a SimpleLogger instance named "xxxxx". +# Must be one of ("trace", "debug", "info", "warn", or "error"). +# If not specified, the default logging detail level is used. +#org.slf4j.simpleLogger.log.xxxxx= + +# Set to true if you want the current date and time to be included in output messages. +# Default is false, and will output the number of milliseconds elapsed since startup. +org.slf4j.simpleLogger.showDateTime=true + +# The date and time format to be used in the output messages. +# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat. +# If the format is not specified or is invalid, the default format is used. +# The default format is yyyy-MM-dd HH:mm:ss:SSS Z. +org.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss + +# Set to true if you want to output the current thread name. +# Defaults to true. +org.slf4j.simpleLogger.showThreadName=true + +# Set to true if you want the Logger instance name to be included in output messages. +# Defaults to true. +org.slf4j.simpleLogger.showLogName=true + +# Set to true if you want the last component of the name to be included in output messages. +# Defaults to false. +org.slf4j.simpleLogger.showShortLogName=true \ No newline at end of file From 5d19892074eac910023289e2543f42540919c91e Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Wed, 23 Sep 2015 01:11:13 -0700 Subject: [PATCH 039/105] removed gradle file entry --- .../aeron/AeronDuplexConnectionSubject.java | 72 ---------------- .../AbstractClientDuplexConnection.java | 21 +++-- .../multi/ReactivesocketAeronClient.java | 43 +--------- .../server/AeronServerDuplexConnection.java | 29 +++++-- .../server/ReactiveSocketAeronServer.java | 82 ++++++++++--------- .../aeron/server/ServerSubscription.java | 2 +- .../client/multi/ReactiveSocketAeronTest.java | 11 +-- 7 files changed, 91 insertions(+), 169 deletions(-) delete mode 100644 src/main/java/io/reactivesocket/aeron/AeronDuplexConnectionSubject.java diff --git a/src/main/java/io/reactivesocket/aeron/AeronDuplexConnectionSubject.java b/src/main/java/io/reactivesocket/aeron/AeronDuplexConnectionSubject.java deleted file mode 100644 index 1772b6ee5..000000000 --- a/src/main/java/io/reactivesocket/aeron/AeronDuplexConnectionSubject.java +++ /dev/null @@ -1,72 +0,0 @@ -package io.reactivesocket.aeron; - -import io.reactivesocket.Frame; -import io.reactivesocket.rx.Disposable; -import io.reactivesocket.rx.Observable; -import io.reactivesocket.rx.Observer; - -import java.util.List; -import java.util.concurrent.atomic.AtomicLong; - -/** -* Class used to manage connections input to a duplex connection in Aeron. -*/ -public class AeronDuplexConnectionSubject implements Observable, Observer { - private static final AtomicLong count = new AtomicLong(); - - private final long id; - - private final List subjects; - - private Observer internal; - - public AeronDuplexConnectionSubject(List subjects) { - this.id = count.incrementAndGet(); - this.subjects = subjects; - } - - @Override - public void subscribe(Observer o) { - internal = o; - internal.onSubscribe(() -> - subjects - .removeIf(s -> s.id == id)); - } - - @Override - public void onNext(Frame frame) { - internal.onNext(frame); - } - - @Override - public void onError(Throwable e) { - internal.onError(e); - } - - @Override - public void onComplete() { - internal.onComplete(); - } - - @Override - public void onSubscribe(Disposable d) { - - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - AeronDuplexConnectionSubject that = (AeronDuplexConnectionSubject) o; - - if (id != that.id) return false; - - return true; - } - - @Override - public int hashCode() { - return (int) (id ^ (id >>> 32)); - } -} diff --git a/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java index 6d521947c..bb6b866db 100644 --- a/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java @@ -2,17 +2,20 @@ import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; -import io.reactivesocket.aeron.AeronDuplexConnectionSubject; import io.reactivesocket.aeron.internal.concurrent.AbstractConcurrentArrayQueue; +import io.reactivesocket.rx.Disposable; import io.reactivesocket.rx.Observable; import io.reactivesocket.rx.Observer; import uk.co.real_logic.aeron.Publication; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicLong; public abstract class AbstractClientDuplexConnection, F> implements DuplexConnection { - protected final CopyOnWriteArrayList subjects; + protected final static AtomicLong count = new AtomicLong(); + + protected final CopyOnWriteArrayList> subjects; protected final Publication publication; @@ -26,9 +29,17 @@ public AbstractClientDuplexConnection(Publication publication) { @Override public final Observable getInput() { - AeronDuplexConnectionSubject subject = new AeronDuplexConnectionSubject(subjects); - subjects.add(subject); - return subject; + return new Observable() { + public void subscribe(Observer o) { + o.onSubscribe(new Disposable() { + @Override + public void dispose() { + subjects.removeIf(s -> s == o); + } + }); + subjects.add(o); + } + }; } public final List> getSubscriber() { diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java index 886f6dfa2..62b5e4d5e 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java @@ -61,7 +61,7 @@ private class SubscriptionGroup { private volatile static boolean running = true; - volatile int sessionId; + final int sessionId; volatile int serverSessionId; @@ -198,6 +198,7 @@ void fragmentHandler(int threadId, DirectBuffer buffer, int offset, int length, final ByteBuffer bytes = ByteBuffer.allocate(length); buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); final Frame frame = Frame.from(bytes); + debug("client processing frame {}", frame); subscribers.forEach(s -> s.onNext(frame)); } else if (messageType == MessageType.ESTABLISH_CONNECTION_RESPONSE) { final int ackSessionId = buffer.getInt(offset + BitUtil.SIZE_OF_INT); @@ -227,7 +228,7 @@ void fragmentHandler(int threadId, DirectBuffer buffer, int offset, int length, latch.countDown(); - System.out.println("ReactiveSocket connected to Aeron session => " + ackSessionId); + debug("ReactiveSocket connected to Aeron session => " + ackSessionId); } else { debug("Unknown message type => " + messageTypeInt); } @@ -365,42 +366,6 @@ public Publisher requestSubscription(Payload payload) { return reactiveSocket.requestSubscription(payload); } - public static boolean isRunning() { - return running; - } - - public static void setRunning(boolean running) { - ReactivesocketAeronClient.running = running; - } - - public int getSessionId() { - return sessionId; - } - - public void setSessionId(int sessionId) { - this.sessionId = sessionId; - } - - public int getPort() { - return port; - } - - public int getServerSessionId() { - return serverSessionId; - } - - public void setServerSessionId(int serverSessionId) { - this.serverSessionId = serverSessionId; - } - - public static boolean isPollingStarted() { - return pollingStarted; - } - - public static void setPollingStarted(boolean pollingStarted) { - ReactivesocketAeronClient.pollingStarted = pollingStarted; - } - @Override public void close() throws Exception { // First clean up the different maps @@ -423,7 +388,7 @@ private void closeQuietly(AutoCloseable closeable) { try { closeable.close(); } catch (Throwable t) { - debug(t.getMessage(), t); + error(t.getMessage(), t); } } } diff --git a/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java index 2ae098096..b9f85a3b6 100644 --- a/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java @@ -2,29 +2,32 @@ import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; -import io.reactivesocket.aeron.AeronDuplexConnectionSubject; import io.reactivesocket.aeron.internal.AeronUtil; import io.reactivesocket.aeron.internal.Loggable; import io.reactivesocket.aeron.internal.MessageType; -import io.reactivesocket.rx.Observable; import io.reactivesocket.rx.Completable; +import io.reactivesocket.rx.Disposable; +import io.reactivesocket.rx.Observable; import io.reactivesocket.rx.Observer; import org.reactivestreams.Publisher; import uk.co.real_logic.aeron.Publication; import uk.co.real_logic.agrona.BitUtil; -import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; public class AeronServerDuplexConnection implements DuplexConnection, Loggable { + protected final static AtomicLong count = new AtomicLong(); + private final Publication publication; - private final ArrayList subjects; + private final CopyOnWriteArrayList> subjects; public AeronServerDuplexConnection( Publication publication) { this.publication = publication; - this.subjects = new ArrayList<>(); + this.subjects = new CopyOnWriteArrayList<>(); } public List> getSubscriber() { @@ -32,10 +35,18 @@ public List> getSubscriber() { } @Override - public Observable getInput() { - AeronDuplexConnectionSubject subject = new AeronDuplexConnectionSubject(subjects); - subjects.add(subject); - return subject; + public final Observable getInput() { + return new Observable() { + public void subscribe(Observer o) { + o.onSubscribe(new Disposable() { + @Override + public void dispose() { + subjects.removeIf(s -> s == o); + } + }); + subjects.add(o); + } + }; } @Override diff --git a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java index 2ea18fb4c..f4a1c7f7e 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java @@ -37,9 +37,9 @@ public class ReactiveSocketAeronServer implements AutoCloseable, Loggable { private final int port; - private final ConcurrentHashMap connections; + private static final ConcurrentHashMap connections = new ConcurrentHashMap<>(); - private final ConcurrentHashMap sockets; + private static final ConcurrentHashMap sockets = new ConcurrentHashMap<>(); private final Subscription subscription; @@ -52,11 +52,10 @@ public class ReactiveSocketAeronServer implements AutoCloseable, Loggable { private final FragmentAssembler fragmentAssembler; private ReactiveSocketAeronServer(String host, int port, ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { + this.port = port; - this.connections = new ConcurrentHashMap<>(); this.connectionSetupHandler = connectionSetupHandler; this.leaseGovernor = leaseGovernor; - this.sockets = new ConcurrentHashMap<>(); this.shutdownLatch = new CountDownLatch(1); if (aeron == null) { @@ -113,15 +112,15 @@ synchronized void poll() { Thread dutyThread = new Thread(() -> { while (running) { - servers - .forEach(server -> { - try { - int poll = server.subscription.poll(server.fragmentAssembler, Integer.MAX_VALUE); - SERVER_IDLE_STRATEGY.idle(poll); - } catch (Throwable t) { - t.printStackTrace(); - } - }); + int poll = 0; + for (ReactiveSocketAeronServer server : servers) { + try { + poll += server.subscription.poll(server.fragmentAssembler, Integer.MAX_VALUE); + } catch (Throwable t) { + t.printStackTrace(); + } + } + SERVER_IDLE_STRATEGY.idle(poll); } shutdownLatch.countDown(); @@ -133,34 +132,40 @@ synchronized void poll() { } void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) { - final int sessionId = header.sessionId(); + final int sessionId = header.sessionId(); - short messageTypeInt = buffer.getShort(offset + BitUtil.SIZE_OF_SHORT); - MessageType type = MessageType.from(messageTypeInt); + short messageTypeInt = buffer.getShort(offset + BitUtil.SIZE_OF_SHORT); + MessageType type = MessageType.from(messageTypeInt); - if (MessageType.FRAME == type) { - AeronServerDuplexConnection connection = connections.get(sessionId); - if (connection != null) { - List> subscribers = connection.getSubscriber(); - final Frame frame = Frame.from(buffer, BitUtil.SIZE_OF_INT + offset, length); - subscribers.forEach(s -> s.onNext(frame)); - } - } else if (MessageType.ESTABLISH_CONNECTION_REQUEST == type) { - final long start = System.nanoTime(); - AeronServerDuplexConnection connection = null; - debug("Looking a connection to ack establish connection for session id => {}", sessionId); - while (connection == null) { - final long current = System.nanoTime(); - - if (current - start > TimeUnit.SECONDS.toNanos(30)) { - throw new RuntimeException("unable to find connection to ack establish connection for session id => " + sessionId); + if (MessageType.FRAME == type) { + AeronServerDuplexConnection connection = connections.get(sessionId); + if (connection != null) { + List> subscribers = connection.getSubscriber(); + final Frame frame = Frame.from(buffer, BitUtil.SIZE_OF_INT + offset, length); + subscribers.forEach(s -> { + try { + s.onNext(frame); + } catch (Throwable t) { + s.onError(t); + } + }); } - - connection = connections.get(sessionId); + } else if (MessageType.ESTABLISH_CONNECTION_REQUEST == type) { + final long start = System.nanoTime(); + AeronServerDuplexConnection connection = null; + debug("Looking a connection to ack establish connection for session id => {}", sessionId); + while (connection == null) { + final long current = System.nanoTime(); + + if (current - start > TimeUnit.SECONDS.toNanos(30)) { + throw new RuntimeException("unable to find connection to ack establish connection for session id => " + sessionId); + } + + connection = connections.get(sessionId); + } + debug("Found a connection to ack establish connection for session id => {}", sessionId); + connection.ackEstablishConnection(sessionId); } - debug("Found a connection to ack establish connection for session id => {}", sessionId); - connection.ackEstablishConnection(sessionId); - } } @@ -190,6 +195,7 @@ void newImageHandler(Image image, String channel, int streamId, int sessionId, l @Override public void close() throws Exception { + /* running = false; shutdownLatch.await(30, TimeUnit.SECONDS); @@ -202,7 +208,7 @@ public void close() throws Exception { for (ReactiveSocket reactiveSocket : sockets.values()) { reactiveSocket.close(); - } + } */ } } diff --git a/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java b/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java index e9da1534c..274d8abb6 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java +++ b/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java @@ -35,7 +35,7 @@ public ServerSubscription(Publication publication, Completable completable) { @Override public void onSubscribe(Subscription s) { - s.request(Long.MAX_VALUE); + s.request(1); } @Override diff --git a/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java index 649f242af..bea5f6c89 100644 --- a/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java @@ -206,7 +206,7 @@ public void onNext(Payload s) { @Test public void createTwoServersAndTwoClients()throws Exception { Random random = new Random(); - byte[] b = new byte[1_000_000]; + byte[] b = new byte[1]; random.nextBytes(b); ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { @@ -215,7 +215,7 @@ public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupExc return new RequestHandler() { @Override public Publisher handleRequestResponse(Payload payload) { - System.out.println("Server got => " + b.length); + System.out.println("pong 1 => " + payload.getData()); Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong server 1", null)); return RxReactiveStreams.toPublisher(pong); } @@ -254,7 +254,7 @@ public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupExc return new RequestHandler() { @Override public Publisher handleRequestResponse(Payload payload) { - System.out.println("Server got => " + b.length); + System.out.println("pong 2 => " + payload.getData()); Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong server 2", null)); return RxReactiveStreams.toPublisher(pong); } @@ -309,7 +309,6 @@ public ByteBuffer getMetadata() { } }; - latch.countDown(); return RxReactiveStreams.toObservable(client.requestResponse(payload)); } ) @@ -325,6 +324,7 @@ public void onError(Throwable e) { @Override public void onNext(Payload s) { + latch.countDown(); System.out.println(s + " countdown server 1 => " + latch.getCount()); } }); @@ -344,7 +344,7 @@ public ByteBuffer getMetadata() { return ByteBuffer.allocate(0); } }; - latch.countDown(); + return RxReactiveStreams.toObservable(client2.requestResponse(payload)); } ) @@ -360,6 +360,7 @@ public void onError(Throwable e) { @Override public void onNext(Payload s) { + latch.countDown(); System.out.println(s + " countdown server 2 => " + latch.getCount()); } }); From 6e85ff122659b5187c9b8d211e3298e698d60033 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Wed, 23 Sep 2015 11:35:21 -0700 Subject: [PATCH 040/105] moved managing Aeron server information into a singleton --- .../multi/ReactivesocketAeronClient.java | 2 +- .../aeron/internal/Int2ObjectPair.java | 26 ---- .../server/ReactiveSocketAeronServer.java | 121 ++++++------------ .../aeron/server/ServerAeronManager.java | 90 +++++++++++++ .../client/multi/ReactiveSocketAeronTest.java | 8 +- 5 files changed, 134 insertions(+), 113 deletions(-) delete mode 100644 src/main/java/io/reactivesocket/aeron/internal/Int2ObjectPair.java create mode 100644 src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java index 62b5e4d5e..b939fe3c0 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java @@ -198,7 +198,7 @@ void fragmentHandler(int threadId, DirectBuffer buffer, int offset, int length, final ByteBuffer bytes = ByteBuffer.allocate(length); buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); final Frame frame = Frame.from(bytes); - debug("client processing frame {}", frame); + //debug("client processing frame {}", frame); subscribers.forEach(s -> s.onNext(frame)); } else if (messageType == MessageType.ESTABLISH_CONNECTION_RESPONSE) { final int ackSessionId = buffer.getInt(offset + BitUtil.SIZE_OF_INT); diff --git a/src/main/java/io/reactivesocket/aeron/internal/Int2ObjectPair.java b/src/main/java/io/reactivesocket/aeron/internal/Int2ObjectPair.java deleted file mode 100644 index a1697e03b..000000000 --- a/src/main/java/io/reactivesocket/aeron/internal/Int2ObjectPair.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.reactivesocket.aeron.internal; - -/** - * Creates a pair, with one value an int and the other an Object. - */ -public class Int2ObjectPair { - private int i; - private T t; - - private Int2ObjectPair(int i, T t) { - this.i = i; - this.t = t; - } - - public static Int2ObjectPair pairOf(int i, T t) { - return new Int2ObjectPair(i, t); - } - - public int getInt() { - return i; - } - - public T getObject() { - return t; - } -} diff --git a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java index f4a1c7f7e..4494791a1 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java @@ -18,28 +18,17 @@ import java.util.List; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static io.reactivesocket.aeron.internal.Constants.CLIENT_STREAM_ID; -import static io.reactivesocket.aeron.internal.Constants.SERVER_IDLE_STRATEGY; import static io.reactivesocket.aeron.internal.Constants.SERVER_STREAM_ID; public class ReactiveSocketAeronServer implements AutoCloseable, Loggable { - private static volatile Aeron aeron; - - private static volatile boolean running = true; - - private static volatile boolean pollingStarted = false; - - private static final CopyOnWriteArrayList servers = new CopyOnWriteArrayList<>(); - private final int port; - private static final ConcurrentHashMap connections = new ConcurrentHashMap<>(); + private final ConcurrentHashMap connections = new ConcurrentHashMap<>(); - private static final ConcurrentHashMap sockets = new ConcurrentHashMap<>(); + private final ConcurrentHashMap sockets = new ConcurrentHashMap<>(); private final Subscription subscription; @@ -47,88 +36,25 @@ public class ReactiveSocketAeronServer implements AutoCloseable, Loggable { private final LeaseGovernor leaseGovernor; - private final CountDownLatch shutdownLatch; - - private final FragmentAssembler fragmentAssembler; + private static final ServerAeronManager manager = ServerAeronManager.getInstance(); private ReactiveSocketAeronServer(String host, int port, ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { - this.port = port; this.connectionSetupHandler = connectionSetupHandler; this.leaseGovernor = leaseGovernor; - this.shutdownLatch = new CountDownLatch(1); - if (aeron == null) { - synchronized (shutdownLatch) { - if (aeron == null) { - final Aeron.Context ctx = new Aeron.Context(); - ctx.newImageHandler(this::newImageHandler); - ctx.errorHandler(t -> error(t.getMessage(), t)); + manager.addNewImageHandler(this::newImageHandler); - aeron = Aeron.connect(ctx); - } - } - } + Aeron aeron = manager.getAeron(); final String serverChannel = "udp://" + host + ":" + port; - info("Start new ReactiveSocketAeronServer on channel {}", serverChannel); + info("Starting new ReactiveSocketAeronServer on channel {}", serverChannel); subscription = aeron.addSubscription(serverChannel, SERVER_STREAM_ID); - fragmentAssembler = new FragmentAssembler(this::fragmentHandler); - - servers.add(this); - - poll(); - } - - public static ReactiveSocketAeronServer create(String host, int port, ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { - return new ReactiveSocketAeronServer(host, port, connectionSetupHandler, leaseGovernor); - } - - public static ReactiveSocketAeronServer create(int port, ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { - return create("127.0.0.1", port, connectionSetupHandler, leaseGovernor); - } - - public static ReactiveSocketAeronServer create(ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { - return create(39790, connectionSetupHandler, leaseGovernor); - } - - public static ReactiveSocketAeronServer create(String host, int port, ConnectionSetupHandler connectionSetupHandler) { - return new ReactiveSocketAeronServer(host, port, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); - } - - public static ReactiveSocketAeronServer create(int port, ConnectionSetupHandler connectionSetupHandler) { - return create("127.0.0.1", port, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); - } - - public static ReactiveSocketAeronServer create(ConnectionSetupHandler connectionSetupHandler) { - return create(39790, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); - } + FragmentAssembler fragmentAssembler = new FragmentAssembler(this::fragmentHandler); + manager.addSubscription(subscription, fragmentAssembler); - synchronized void poll() { - if (pollingStarted) { - return; - } - Thread dutyThread = new Thread(() -> { - while (running) { - int poll = 0; - for (ReactiveSocketAeronServer server : servers) { - try { - poll += server.subscription.poll(server.fragmentAssembler, Integer.MAX_VALUE); - } catch (Throwable t) { - t.printStackTrace(); - } - } - SERVER_IDLE_STRATEGY.idle(poll); - - } - shutdownLatch.countDown(); - }); - dutyThread.setName("reactive-socket-aeron-server"); - dutyThread.setDaemon(true); - dutyThread.start(); - pollingStarted = true; } void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) { @@ -174,7 +100,7 @@ void newImageHandler(Image image, String channel, int streamId, int sessionId, l debug("Handling new image for session id => {} and stream id => {}", streamId, sessionId); final AeronServerDuplexConnection connection = connections.computeIfAbsent(sessionId, (_s) -> { final String responseChannel = "udp://" + sourceIdentity.substring(0, sourceIdentity.indexOf(':')) + ":" + port; - Publication publication = aeron.addPublication(responseChannel, CLIENT_STREAM_ID); + Publication publication = manager.getAeron().addPublication(responseChannel, CLIENT_STREAM_ID); debug("Creating new connection for responseChannel => {}, streamId => {}, and sessionId => {}", responseChannel, streamId, sessionId); return new AeronServerDuplexConnection(publication); }); @@ -195,6 +121,7 @@ void newImageHandler(Image image, String channel, int streamId, int sessionId, l @Override public void close() throws Exception { + manager.removeSubscription(subscription); /* running = false; @@ -211,4 +138,32 @@ public void close() throws Exception { } */ } + /* + * Factory Methods + */ + + public static ReactiveSocketAeronServer create(String host, int port, ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { + return new ReactiveSocketAeronServer(host, port, connectionSetupHandler, leaseGovernor); + } + + public static ReactiveSocketAeronServer create(int port, ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { + return create("127.0.0.1", port, connectionSetupHandler, leaseGovernor); + } + + public static ReactiveSocketAeronServer create(ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { + return create(39790, connectionSetupHandler, leaseGovernor); + } + + public static ReactiveSocketAeronServer create(String host, int port, ConnectionSetupHandler connectionSetupHandler) { + return new ReactiveSocketAeronServer(host, port, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); + } + + public static ReactiveSocketAeronServer create(int port, ConnectionSetupHandler connectionSetupHandler) { + return create("127.0.0.1", port, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); + } + + public static ReactiveSocketAeronServer create(ConnectionSetupHandler connectionSetupHandler) { + return create(39790, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); + } + } diff --git a/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java b/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java new file mode 100644 index 000000000..4c928bf4d --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java @@ -0,0 +1,90 @@ +package io.reactivesocket.aeron.server; + +import io.reactivesocket.aeron.internal.Loggable; +import uk.co.real_logic.aeron.Aeron; +import uk.co.real_logic.aeron.FragmentAssembler; +import uk.co.real_logic.aeron.Image; +import uk.co.real_logic.aeron.NewImageHandler; +import uk.co.real_logic.aeron.Subscription; + +import java.util.concurrent.CopyOnWriteArrayList; + +import static io.reactivesocket.aeron.internal.Constants.SERVER_IDLE_STRATEGY; + +/** + * Class that manages the Aeron instance and the server's polling thread. Lets you register more + * than one NewImageHandler to Aeron after the it's the Aeron instance has started + */ +public class ServerAeronManager implements Loggable { + private static final ServerAeronManager INSTANCE = new ServerAeronManager(); + + private final Aeron aeron; + + private CopyOnWriteArrayList imageHandlers = new CopyOnWriteArrayList<>(); + + private CopyOnWriteArrayList subscriptions = new CopyOnWriteArrayList<>(); + + private class SubscriptionHolder { + private Subscription subscription; + private FragmentAssembler fragmentAssembler; + + public SubscriptionHolder(Subscription subscription, FragmentAssembler fragmentAssembler) { + this.subscription = subscription; + this.fragmentAssembler = fragmentAssembler; + } + } + + public ServerAeronManager() { + final Aeron.Context ctx = new Aeron.Context(); + ctx.newImageHandler(this::newImageHandler); + ctx.errorHandler(t -> error("an exception occured", t)); + + aeron = Aeron.connect(ctx); + + poll(); + } + + public static ServerAeronManager getInstance() { + return INSTANCE; + } + + public void addNewImageHandler(NewImageHandler handler) { + imageHandlers.add(handler); + } + + public void addSubscription(Subscription subscription, FragmentAssembler fragmentAssembler) { + subscriptions.add(new SubscriptionHolder(subscription, fragmentAssembler)); + } + + public void removeSubscription(Subscription subscription) { + subscriptions.removeIf(s -> s.subscription == subscription); + } + + private void newImageHandler(Image image, String channel, int streamId, int sessionId, long joiningPosition, String sourceIdentity) { + imageHandlers + .forEach(handler -> handler.onNewImage(image, channel, streamId, sessionId, joiningPosition, sourceIdentity)); + } + + public Aeron getAeron() { + return aeron; + } + + void poll() { + Thread dutyThread = new Thread(() -> { + for (;;) { + int poll = 0; + for (SubscriptionHolder sh : subscriptions) { + try { + poll += sh.subscription.poll(sh.fragmentAssembler, Integer.MAX_VALUE); + } catch (Throwable t) { + t.printStackTrace(); + } + } + SERVER_IDLE_STRATEGY.idle(poll); + } + }); + dutyThread.setName("reactive-socket-aeron-server"); + dutyThread.setDaemon(true); + dutyThread.start(); + } +} diff --git a/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java index bea5f6c89..4afca0463 100644 --- a/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java @@ -287,14 +287,16 @@ public Publisher handleMetadataPush(Payload payload) { } }); - CountDownLatch latch = new CountDownLatch(2 * 130); + int count = 64; + + CountDownLatch latch = new CountDownLatch(2 * count); ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost", "localhost"); ReactivesocketAeronClient client2 = ReactivesocketAeronClient.create("localhost", "localhost", 12345); Observable - .range(1, 130) + .range(1, count) .flatMap(i -> { System.out.println("pinging server 1 => " + i); Payload payload = new Payload() { @@ -330,7 +332,7 @@ public void onNext(Payload s) { }); Observable - .range(1, 130) + .range(1, count) .flatMap(i -> { System.out.println("pinging server 2 => " + i); Payload payload = new Payload() { From ed4f2f7d86f89980142ca489149cc39031d9120c Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Wed, 23 Sep 2015 11:42:43 -0700 Subject: [PATCH 041/105] missed commit --- .../aeron/server/ServerAeronManager.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java b/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java index 4c928bf4d..3ffbb5f1e 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java +++ b/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java @@ -22,13 +22,13 @@ public class ServerAeronManager implements Loggable { private CopyOnWriteArrayList imageHandlers = new CopyOnWriteArrayList<>(); - private CopyOnWriteArrayList subscriptions = new CopyOnWriteArrayList<>(); + private CopyOnWriteArrayList subscriptions = new CopyOnWriteArrayList<>(); - private class SubscriptionHolder { + private class FragmentAssemblerHolder { private Subscription subscription; private FragmentAssembler fragmentAssembler; - public SubscriptionHolder(Subscription subscription, FragmentAssembler fragmentAssembler) { + public FragmentAssemblerHolder(Subscription subscription, FragmentAssembler fragmentAssembler) { this.subscription = subscription; this.fragmentAssembler = fragmentAssembler; } @@ -53,7 +53,7 @@ public void addNewImageHandler(NewImageHandler handler) { } public void addSubscription(Subscription subscription, FragmentAssembler fragmentAssembler) { - subscriptions.add(new SubscriptionHolder(subscription, fragmentAssembler)); + subscriptions.add(new FragmentAssemblerHolder(subscription, fragmentAssembler)); } public void removeSubscription(Subscription subscription) { @@ -73,7 +73,7 @@ void poll() { Thread dutyThread = new Thread(() -> { for (;;) { int poll = 0; - for (SubscriptionHolder sh : subscriptions) { + for (FragmentAssemblerHolder sh : subscriptions) { try { poll += sh.subscription.poll(sh.fragmentAssembler, Integer.MAX_VALUE); } catch (Throwable t) { From a0809e4255927e1738cd1692707032190849276c Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Wed, 23 Sep 2015 16:02:38 -0700 Subject: [PATCH 042/105] created a Poller class --- .../multi/ReactivesocketAeronClient.java | 56 +++---- .../client/multi/ReactiveSocketAeronTest.java | 137 ++++++++++++++++++ 2 files changed, 165 insertions(+), 28 deletions(-) diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java index b939fe3c0..6356f32d7 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java @@ -11,6 +11,7 @@ import io.reactivesocket.rx.Observer; import org.reactivestreams.Publisher; import rx.Scheduler; +import rx.functions.Action0; import rx.schedulers.Schedulers; import uk.co.real_logic.aeron.Aeron; import uk.co.real_logic.aeron.FragmentAssembler; @@ -142,19 +143,14 @@ private ReactivesocketAeronClient(String host, String server, int port) { } SUBSCRIPTION_GROUPS.add(subscriptionGroup); } - } - synchronized (SUBSCRIPTION_GROUPS) { if (!pollingStarted) { - debug("Polling hasn't started yet - starting " - + CONCURRENCY - + " pollers"); CyclicBarrier startBarrier = new CyclicBarrier(CONCURRENCY + 1); for (int i = 0; i < CONCURRENCY; i++) { - debug("Starting " - + i - + " poller"); - poll(i, Schedulers.computation().createWorker(), startBarrier); + Schedulers + .computation() + .createWorker() + .schedulePeriodically(new Poller(i, startBarrier), 0, 1, TimeUnit.NANOSECONDS); } try { @@ -239,9 +235,22 @@ void fragmentHandler(int threadId, DirectBuffer buffer, int offset, int length, } } - void poll(final int threadId, final Scheduler.Worker worker, CyclicBarrier startBarrier) { - worker.schedulePeriodically(() -> { - if (startBarrier != null && !pollingStarted) { + class Poller implements Action0 { + final int threadId; + CyclicBarrier startBarrier; + final FragmentAssembler fragmentAssembler; + + public Poller(int threadId, CyclicBarrier startBarrier) { + this.threadId = threadId; + this.startBarrier = startBarrier; + fragmentAssembler = new FragmentAssembler( + (DirectBuffer buffer, int offset, int length, Header header) -> + fragmentHandler(threadId, buffer, offset, length, header)); + debug("Starting new Poller for thread id => " + threadId); + } + + public void call() { + if (!pollingStarted) { try { startBarrier.await(30, TimeUnit.SECONDS); } catch (Exception e) { @@ -253,7 +262,7 @@ void poll(final int threadId, final Scheduler.Worker worker, CyclicBarrier start try { final Collection values = connections.values(); - if (values != null) { + if (!values.isEmpty()) { values.forEach(connection -> { ManyToManyConcurrentArrayQueue framesSendQueue = connection.getFramesSendQueue(); framesSendQueue @@ -277,19 +286,11 @@ void poll(final int threadId, final Scheduler.Worker worker, CyclicBarrier start } try { - final FragmentAssembler fragmentAssembler = new FragmentAssembler( - (DirectBuffer buffer, int offset, int length, Header header) -> - fragmentHandler(threadId, buffer, offset, length, header)); - - //System.out.println("processing subscriptions => " + magicNumber); - //LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); - SUBSCRIPTION_GROUPS - .forEach(subscriptionGroup -> { - //System.out.println("processing subscriptions in foreach => " + magicNumber); - //LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); - Subscription subscription = subscriptionGroup.subscriptions[threadId]; - subscription.poll(fragmentAssembler, Integer.MAX_VALUE); - }); + SUBSCRIPTION_GROUPS + .forEach(subscriptionGroup -> { + Subscription subscription = subscriptionGroup.subscriptions[threadId]; + subscription.poll(fragmentAssembler, Integer.MAX_VALUE); + }); } catch (Throwable t) { t.printStackTrace(); error("error polling aeron subscription", t); @@ -301,8 +302,7 @@ void poll(final int threadId, final Scheduler.Worker worker, CyclicBarrier start } else { shutdownLatch.countDown(); } - - }, 0, 1, TimeUnit.NANOSECONDS); + } } /** diff --git a/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java index 4afca0463..1fade0439 100644 --- a/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java @@ -12,6 +12,7 @@ import org.junit.Ignore; import org.junit.Test; import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; import rx.Observable; import rx.RxReactiveStreams; import uk.co.real_logic.aeron.driver.MediaDriver; @@ -370,4 +371,140 @@ public void onNext(Payload s) { latch.await(); } + @Test + public void testFireAndForget() throws Exception { + CountDownLatch latch = new CountDownLatch(130); + ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { + @Override + public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + return new RequestHandler() { + + @Override + public Publisher handleRequestResponse(Payload payload) { + return null; + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher payloads) { + return null; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + return null; + } + + @Override + public Publisher handleSubscription(Payload payload) { + return null; + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return new Publisher() { + @Override + public void subscribe(Subscriber s) { + latch.countDown(); + s.onComplete(); + } + }; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + }; + } + }); + + ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost", "localhost"); + + Observable + .range(1, 130) + .flatMap(i -> { + System.out.println("pinging => " + i); + Payload payload = TestUtil.utf8EncodedPayload("ping =>" + i, null); + return RxReactiveStreams.toObservable(client.fireAndForget(payload)); + } + ) + .subscribe(); + + latch.await(); + } + + @Test + public void testRequestStream() throws Exception { + CountDownLatch latch = new CountDownLatch(130); + ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { + @Override + public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + return new RequestHandler() { + + @Override + public Publisher handleRequestResponse(Payload payload) { + return null; + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher payloads) { + return null; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + return new Publisher() { + @Override + public void subscribe(Subscriber s) { + for (int i = 0; i < 1_000_000; i++) { + s.onNext(new Payload() { + @Override + public ByteBuffer getData() { + return ByteBuffer.allocate(0); + } + + @Override + public ByteBuffer getMetadata() { + return ByteBuffer.allocate(0); + } + }); + } + + } + }; + } + + @Override + public Publisher handleSubscription(Payload payload) { + return null; + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return null; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + }; + } + }); + + ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost", "localhost"); + + Observable + .range(1, 1) + .flatMap(i -> { + System.out.println("pinging => " + i); + Payload payload = TestUtil.utf8EncodedPayload("ping =>" + i, null); + return RxReactiveStreams.toObservable(client.requestStream(payload)); + } + ) + .doOnNext(i -> latch.countDown()) + .subscribe(); + + latch.await(); + } } From 435cbff8184bd5a51f9bcb635c770020aa57116d Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Thu, 24 Sep 2015 10:44:46 -0700 Subject: [PATCH 043/105] removed rxjava from client - just uses reactivestreams --- .../AbstractClientDuplexConnection.java | 13 ++- .../multi/AeronClientDuplexConnection.java | 50 +++++++----- .../aeron/client/multi/FrameHolder.java | 17 ++-- .../multi/ReactivesocketAeronClient.java | 81 ++++++++++--------- .../client/multi/ReactiveSocketAeronTest.java | 3 +- 5 files changed, 98 insertions(+), 66 deletions(-) diff --git a/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java index bb6b866db..954122e29 100644 --- a/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java @@ -2,18 +2,20 @@ import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; -import io.reactivesocket.aeron.internal.concurrent.AbstractConcurrentArrayQueue; import io.reactivesocket.rx.Disposable; import io.reactivesocket.rx.Observable; import io.reactivesocket.rx.Observer; import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.agrona.concurrent.AbstractConcurrentArrayQueue; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicInteger; public abstract class AbstractClientDuplexConnection, F> implements DuplexConnection { - protected final static AtomicLong count = new AtomicLong(); + protected final static AtomicInteger count = new AtomicInteger(); + + protected final int connectionId; protected final CopyOnWriteArrayList> subjects; @@ -25,6 +27,7 @@ public AbstractClientDuplexConnection(Publication publication) { this.publication = publication; this.subjects = new CopyOnWriteArrayList<>(); this.framesSendQueue = createQueue(); + this.connectionId = count.incrementAndGet(); } @Override @@ -51,4 +54,8 @@ public final T getFramesSendQueue() { } protected abstract T createQueue(); + + public int getConnectionId() { + return connectionId; + } } diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java index 57530e431..0a425ae4c 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java @@ -4,34 +4,40 @@ import io.reactivesocket.Frame; import io.reactivesocket.aeron.client.AbstractClientDuplexConnection; import io.reactivesocket.aeron.internal.Constants; -import io.reactivesocket.aeron.internal.concurrent.ManyToManyConcurrentArrayQueue; import io.reactivesocket.rx.Completable; import org.reactivestreams.Publisher; -import rx.RxReactiveStreams; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; import rx.exceptions.MissingBackpressureException; import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue; -public class AeronClientDuplexConnection extends AbstractClientDuplexConnection, FrameHolder> { +public class AeronClientDuplexConnection extends AbstractClientDuplexConnection, FrameHolder> { public AeronClientDuplexConnection(Publication publication) { super(publication); } @Override - protected ManyToManyConcurrentArrayQueue createQueue() { - return new ManyToManyConcurrentArrayQueue<>(Constants.CONCURRENCY); + protected ManyToOneConcurrentArrayQueue createQueue() { + return new ManyToOneConcurrentArrayQueue<>(Constants.CONCURRENCY); } @Override public void addOutput(Publisher o, Completable callback) { - rx.Observable frameObservable = RxReactiveStreams.toObservable(o); - frameObservable - .flatMap(frame -> { - return rx.Observable.create(subscriber -> { - final FrameHolder frameHolder = FrameHolder.get(frame, publication, subscriber); - subscriber.onNext(frameHolder); - }) - .doOnNext(fh -> { - boolean offer = false; + o + .subscribe(new Subscriber() { + private Subscription s; + + @Override + public void onSubscribe(Subscription s) { + this.s = s; + s.request(Constants.CONCURRENCY); + } + + @Override + public void onNext(Frame frame) { + final FrameHolder fh = FrameHolder.get(frame, publication, s); + boolean offer; int i = 0; do { offer = framesSendQueue.offer(fh); @@ -39,10 +45,18 @@ public void addOutput(Publisher o, Completable callback) { rx.Observable.error(new MissingBackpressureException()); } } while (!offer); - }); - }, Constants.CONCURRENCY) - .subscribe(ignore -> { - }, callback::error, callback::success); + } + + @Override + public void onError(Throwable t) { + callback.error(t); + } + + @Override + public void onComplete() { + callback.success(); + } + }); } @Override diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java b/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java index 53223b29c..cba64c0e0 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java @@ -3,25 +3,26 @@ import io.reactivesocket.Frame; import io.reactivesocket.aeron.internal.Constants; import io.reactivesocket.aeron.internal.concurrent.ManyToManyConcurrentArrayQueue; -import rx.Subscriber; +import org.reactivestreams.Subscription; import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.agrona.concurrent.OneToOneConcurrentArrayQueue; /** * Holds a frame and the publication that it's supposed to be sent on. * Pools instances on an {@link ManyToManyConcurrentArrayQueue} */ class FrameHolder { - private static ManyToManyConcurrentArrayQueue FRAME_HOLDER_QUEUE - = new ManyToManyConcurrentArrayQueue<>(Constants.QUEUE_SIZE); + private static final ThreadLocal> FRAME_HOLDER_QUEUE + = ThreadLocal.withInitial(() -> new OneToOneConcurrentArrayQueue<>(Constants.QUEUE_SIZE)); private Publication publication; private Frame frame; - private Subscriber s; + private Subscription s; private FrameHolder() {} - public static FrameHolder get(Frame frame, Publication publication, Subscriber s) { - FrameHolder frameHolder = FRAME_HOLDER_QUEUE.poll(); + public static FrameHolder get(Frame frame, Publication publication, Subscription s) { + FrameHolder frameHolder = FRAME_HOLDER_QUEUE.get().poll(); if (frameHolder == null) { frameHolder = new FrameHolder(); @@ -44,10 +45,10 @@ public Frame getFrame() { public void release() { if (s != null) { - s.onCompleted(); + s.request(1); } frame.release(); - FRAME_HOLDER_QUEUE.offer(this); + FRAME_HOLDER_QUEUE.get().offer(this); } } diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java index 6356f32d7..48c2c7a02 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java @@ -7,7 +7,6 @@ import io.reactivesocket.aeron.internal.AeronUtil; import io.reactivesocket.aeron.internal.Loggable; import io.reactivesocket.aeron.internal.MessageType; -import io.reactivesocket.aeron.internal.concurrent.ManyToManyConcurrentArrayQueue; import io.reactivesocket.rx.Observer; import org.reactivestreams.Publisher; import rx.Scheduler; @@ -21,15 +20,16 @@ import uk.co.real_logic.agrona.BitUtil; import uk.co.real_logic.agrona.DirectBuffer; import uk.co.real_logic.agrona.LangUtil; +import uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue; import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; import java.nio.ByteBuffer; import java.util.Collection; import java.util.List; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.CyclicBarrier; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; @@ -50,7 +50,7 @@ private class SubscriptionGroup { Subscription[] subscriptions; } - static final ConcurrentHashMap connections = new ConcurrentHashMap<>(); + static final ConcurrentSkipListMap connections = new ConcurrentSkipListMap<>(); static final ConcurrentHashMap establishConnectionLatches = new ConcurrentHashMap<>(); @@ -145,7 +145,7 @@ private ReactivesocketAeronClient(String host, String server, int port) { } if (!pollingStarted) { - CyclicBarrier startBarrier = new CyclicBarrier(CONCURRENCY + 1); + CountDownLatch startBarrier = new CountDownLatch(CONCURRENCY); for (int i = 0; i < CONCURRENCY; i++) { Schedulers .computation() @@ -189,13 +189,19 @@ void fragmentHandler(int threadId, DirectBuffer buffer, int offset, int length, final MessageType messageType = MessageType.from(messageTypeInt); if (messageType == MessageType.FRAME) { final AeronClientDuplexConnection connection = connections.get(header.sessionId()); - List> subscribers = connection.getSubscriber(); - //TODO think about how to recycle these, hard because could be handed to another thread I think? - final ByteBuffer bytes = ByteBuffer.allocate(length); - buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); - final Frame frame = Frame.from(bytes); - //debug("client processing frame {}", frame); - subscribers.forEach(s -> s.onNext(frame)); + final List> subscribers = connection.getSubscriber(); + if (!subscribers.isEmpty()) { + //TODO think about how to recycle these, hard because could be handed to another thread I think? + final ByteBuffer bytes = ByteBuffer.allocate(length); + buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); + final Frame frame = Frame.from(bytes); + int i = 0; + final int size = subscribers.size(); + do { + subscribers.get(i).onNext(frame); + i++; + } while (i < size); + } } else if (messageType == MessageType.ESTABLISH_CONNECTION_RESPONSE) { final int ackSessionId = buffer.getInt(offset + BitUtil.SIZE_OF_INT); @@ -237,12 +243,12 @@ void fragmentHandler(int threadId, DirectBuffer buffer, int offset, int length, class Poller implements Action0 { final int threadId; - CyclicBarrier startBarrier; + CountDownLatch startupLatch; final FragmentAssembler fragmentAssembler; - public Poller(int threadId, CyclicBarrier startBarrier) { + public Poller(int threadId, CountDownLatch startupLatch) { this.threadId = threadId; - this.startBarrier = startBarrier; + this.startupLatch = startupLatch; fragmentAssembler = new FragmentAssembler( (DirectBuffer buffer, int offset, int length, Header header) -> fragmentHandler(threadId, buffer, offset, length, header)); @@ -251,10 +257,10 @@ public Poller(int threadId, CyclicBarrier startBarrier) { public void call() { if (!pollingStarted) { - try { - startBarrier.await(30, TimeUnit.SECONDS); - } catch (Exception e) { - LangUtil.rethrowUnchecked(e); + startupLatch.countDown(); + + if (startupLatch.getCount() != workers.length) { + return; } } @@ -264,24 +270,27 @@ public void call() { if (!values.isEmpty()) { values.forEach(connection -> { - ManyToManyConcurrentArrayQueue framesSendQueue = connection.getFramesSendQueue(); - framesSendQueue - .drain((FrameHolder fh) -> { - try { - Frame frame = fh.getFrame(); - final ByteBuffer byteBuffer = frame.getByteBuffer(); - final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; - AeronUtil.tryClaimOrOffer(fh.getPublication(), (offset, buffer) -> { - buffer.putShort(offset, (short) 0); - buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); - buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, 0, byteBuffer.capacity()); - }, length); - fh.release(); - } catch (Throwable t) { - fh.release(); - error("error draining send frame queue", t); - } - }); + final int calculatedThreadId = Math.abs(connection.getConnectionId() % CONCURRENCY); + if (calculatedThreadId == threadId) { + ManyToOneConcurrentArrayQueue framesSendQueue = connection.getFramesSendQueue(); + framesSendQueue + .drain((FrameHolder fh) -> { + try { + Frame frame = fh.getFrame(); + final ByteBuffer byteBuffer = frame.getByteBuffer(); + final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; + AeronUtil.tryClaimOrOffer(fh.getPublication(), (offset, buffer) -> { + buffer.putShort(offset, (short) 0); + buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); + buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, frame.offset(), frame.length()); + }, length); + fh.release(); + } catch (Throwable t) { + fh.release(); + error("error draining send frame queue", t); + } + }); + } }); } diff --git a/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java index 1fade0439..09b808f69 100644 --- a/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java @@ -35,7 +35,7 @@ public static void init() { final MediaDriver mediaDriver = MediaDriver.launch(context); } - @Test(timeout = 6000000) + @Test(timeout = 60000) public void testRequestReponse() throws Exception { AtomicLong server = new AtomicLong(); ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { @@ -507,4 +507,5 @@ public Publisher handleMetadataPush(Payload payload) { latch.await(); } + } From 7103969105f41051e5e5190156250812c952a816 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Thu, 24 Sep 2015 11:06:45 -0700 Subject: [PATCH 044/105] refactoring to work with aeron 0.1.3 --- build.gradle | 4 ++-- .../aeron/client/multi/FrameHolder.java | 3 +-- .../aeron/server/ReactiveSocketAeronServer.java | 7 ++++--- .../aeron/server/ServerAeronManager.java | 14 +++++++------- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/build.gradle b/build.gradle index e040d725f..61f14b840 100644 --- a/build.gradle +++ b/build.gradle @@ -19,8 +19,8 @@ dependencies { compile 'io.reactivex:rxjava:1.0.13' compile 'io.reactivex:rxjava-reactive-streams:1.0.1' compile 'io.reactivesocket:reactivesocket:0.0.1-SNAPSHOT' - compile 'uk.co.real-logic:Agrona:0.4.2' - compile 'uk.co.real-logic:aeron-all:0.1.2' + compile 'uk.co.real-logic:Agrona:0.4.3' + compile 'uk.co.real-logic:aeron-all:0.1.3' compile 'org.slf4j:slf4j-api:1.7.12' testCompile 'junit:junit-dep:4.10' testCompile 'org.mockito:mockito-core:1.8.5' diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java b/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java index cba64c0e0..40d0ea305 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java @@ -2,14 +2,13 @@ import io.reactivesocket.Frame; import io.reactivesocket.aeron.internal.Constants; -import io.reactivesocket.aeron.internal.concurrent.ManyToManyConcurrentArrayQueue; import org.reactivestreams.Subscription; import uk.co.real_logic.aeron.Publication; import uk.co.real_logic.agrona.concurrent.OneToOneConcurrentArrayQueue; /** * Holds a frame and the publication that it's supposed to be sent on. - * Pools instances on an {@link ManyToManyConcurrentArrayQueue} + * Pools instances on an {@link uk.co.real_logic.agrona.concurrent.OneToOneConcurrentArrayQueue} */ class FrameHolder { private static final ThreadLocal> FRAME_HOLDER_QUEUE diff --git a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java index 4494791a1..30b51e05a 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java @@ -43,7 +43,7 @@ private ReactiveSocketAeronServer(String host, int port, ConnectionSetupHandler this.connectionSetupHandler = connectionSetupHandler; this.leaseGovernor = leaseGovernor; - manager.addNewImageHandler(this::newImageHandler); + manager.addAvailableImageHander(this::availableImageHandler); Aeron aeron = manager.getAeron(); @@ -95,7 +95,9 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) } - void newImageHandler(Image image, String channel, int streamId, int sessionId, long joiningPosition, String sourceIdentity) { + void availableImageHandler(Image image, Subscription subscription, long joiningPosition, String sourceIdentity) { + final int streamId = subscription.streamId(); + final int sessionId = image.sessionId(); if (SERVER_STREAM_ID == streamId) { debug("Handling new image for session id => {} and stream id => {}", streamId, sessionId); final AeronServerDuplexConnection connection = connections.computeIfAbsent(sessionId, (_s) -> { @@ -141,7 +143,6 @@ public void close() throws Exception { /* * Factory Methods */ - public static ReactiveSocketAeronServer create(String host, int port, ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { return new ReactiveSocketAeronServer(host, port, connectionSetupHandler, leaseGovernor); } diff --git a/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java b/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java index 3ffbb5f1e..53ac4d9f3 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java +++ b/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java @@ -2,9 +2,9 @@ import io.reactivesocket.aeron.internal.Loggable; import uk.co.real_logic.aeron.Aeron; +import uk.co.real_logic.aeron.AvailableImageHandler; import uk.co.real_logic.aeron.FragmentAssembler; import uk.co.real_logic.aeron.Image; -import uk.co.real_logic.aeron.NewImageHandler; import uk.co.real_logic.aeron.Subscription; import java.util.concurrent.CopyOnWriteArrayList; @@ -20,7 +20,7 @@ public class ServerAeronManager implements Loggable { private final Aeron aeron; - private CopyOnWriteArrayList imageHandlers = new CopyOnWriteArrayList<>(); + private CopyOnWriteArrayList imageHandlers = new CopyOnWriteArrayList<>(); private CopyOnWriteArrayList subscriptions = new CopyOnWriteArrayList<>(); @@ -36,8 +36,8 @@ public FragmentAssemblerHolder(Subscription subscription, FragmentAssembler frag public ServerAeronManager() { final Aeron.Context ctx = new Aeron.Context(); - ctx.newImageHandler(this::newImageHandler); - ctx.errorHandler(t -> error("an exception occured", t)); + ctx.availableImageHandler(this::availableImageHandler); + ctx.errorHandler(t -> error("an exception occurred", t)); aeron = Aeron.connect(ctx); @@ -48,7 +48,7 @@ public static ServerAeronManager getInstance() { return INSTANCE; } - public void addNewImageHandler(NewImageHandler handler) { + public void addAvailableImageHander(AvailableImageHandler handler) { imageHandlers.add(handler); } @@ -60,9 +60,9 @@ public void removeSubscription(Subscription subscription) { subscriptions.removeIf(s -> s.subscription == subscription); } - private void newImageHandler(Image image, String channel, int streamId, int sessionId, long joiningPosition, String sourceIdentity) { + private void availableImageHandler(Image image, Subscription subscription, long joiningPosition, String sourceIdentity) { imageHandlers - .forEach(handler -> handler.onNewImage(image, channel, streamId, sessionId, joiningPosition, sourceIdentity)); + .forEach(handler -> handler.onAvailableImage(image, subscription, joiningPosition, sourceIdentity)); } public Aeron getAeron() { From c44881d80344df94440a247023149b4e611b1c9f Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Fri, 25 Sep 2015 00:56:46 -0700 Subject: [PATCH 045/105] added code to deal with closing a connection and cleanup, as well as being able to reconnect --- .../client/multi/ClientAeronManager.java | 7 + .../aeron/ReactiveSocketAeronTest.java | 367 ------------------ 2 files changed, 7 insertions(+), 367 deletions(-) create mode 100644 src/main/java/io/reactivesocket/aeron/client/multi/ClientAeronManager.java delete mode 100644 src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/ClientAeronManager.java b/src/main/java/io/reactivesocket/aeron/client/multi/ClientAeronManager.java new file mode 100644 index 000000000..bd5d8c4c5 --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/client/multi/ClientAeronManager.java @@ -0,0 +1,7 @@ +package io.reactivesocket.aeron.client.multi; + +/** + * Created by rroeser on 9/24/15. + */ +public class ClientAeronManager { +} diff --git a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java deleted file mode 100644 index 2db9cabcb..000000000 --- a/src/test/java/io/reactivesocket/aeron/ReactiveSocketAeronTest.java +++ /dev/null @@ -1,367 +0,0 @@ -package io.reactivesocket.aeron; - -import io.reactivesocket.ConnectionSetupHandler; -import io.reactivesocket.ConnectionSetupPayload; -import io.reactivesocket.Frame; -import io.reactivesocket.Payload; -import io.reactivesocket.RequestHandler; -import io.reactivesocket.aeron.client.multi.ReactivesocketAeronClient; -import io.reactivesocket.aeron.server.ReactiveSocketAeronServer; -import io.reactivesocket.exceptions.SetupException; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; -import org.reactivestreams.Publisher; -import rx.Observable; -import rx.RxReactiveStreams; -import uk.co.real_logic.aeron.driver.MediaDriver; - -import java.nio.ByteBuffer; -import java.util.Random; -import java.util.concurrent.CountDownLatch; - -/** - * Created by rroeser on 8/14/15. - */ -@Ignore -public class ReactiveSocketAeronTest { - @BeforeClass - public static void init() { - final MediaDriver.Context context = new MediaDriver.Context(); - context.dirsDeleteOnStart(true); - - final MediaDriver mediaDriver = MediaDriver.launch(context); - } - - @Test(timeout = 60000) - public void testRequestReponse() throws Exception { - ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { - @Override - public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { - return new RequestHandler() { - Frame frame = Frame.from(ByteBuffer.allocate(1)); - - @Override - public Publisher handleRequestResponse(Payload payload) { - String request = TestUtil.byteToString(payload.getData()); - //System.out.println("Server got => " + request); - Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong", null)); - return RxReactiveStreams.toPublisher(pong); - } - - @Override - public Publisher handleChannel(Payload initialPayload, Publisher payloads) { - return null; - } - - @Override - public Publisher handleRequestStream(Payload payload) { - return null; - } - - @Override - public Publisher handleSubscription(Payload payload) { - return null; - } - - @Override - public Publisher handleFireAndForget(Payload payload) { - return null; - } - - @Override - public Publisher handleMetadataPush(Payload payload) { - return null; - } - }; - } - }); - - CountDownLatch latch = new CountDownLatch(130); - - - ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost", "localhost"); - - Observable - .range(1, 130) - .flatMap(i -> { - //System.out.println("pinging => " + i); - Payload payload = TestUtil.utf8EncodedPayload("ping =>" + i, null); - return RxReactiveStreams.toObservable(client.requestResponse(payload)); - } - ) - .subscribe(new rx.Subscriber() { - @Override - public void onCompleted() { - } - - @Override - public void onError(Throwable e) { - System.out.println("counted to => " + latch.getCount()); - e.printStackTrace(); - } - - @Override - public void onNext(Payload s) { - //System.out.println(s + " countdown => " + latch.getCount()); - latch.countDown(); - } - }); - - latch.await(); - } - - @Test(timeout = 60000) - public void sendLargeMessage() throws Exception { - - Random random = new Random(); - byte[] b = new byte[1_000_000]; - random.nextBytes(b); - - ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { - @Override - public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { - return new RequestHandler() { - @Override - public Publisher handleRequestResponse(Payload payload) { - System.out.println("Server got => " + b.length); - Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong", null)); - return RxReactiveStreams.toPublisher(pong); - } - - @Override - public Publisher handleChannel(Payload initialPayload, Publisher payloads) { - return null; - } - - @Override - public Publisher handleRequestStream(Payload payload) { - return null; - } - - @Override - public Publisher handleSubscription(Payload payload) { - return null; - } - - @Override - public Publisher handleFireAndForget(Payload payload) { - return null; - } - - @Override - public Publisher handleMetadataPush(Payload payload) { - return null; - } - }; - } - }); - - CountDownLatch latch = new CountDownLatch(2); - - ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost", "localhost"); - - Observable - .range(1, 2) - .flatMap(i -> { - System.out.println("pinging => " + i); - Payload payload = new Payload() { - @Override - public ByteBuffer getData() { - return ByteBuffer.wrap(b); - } - - @Override - public ByteBuffer getMetadata() { - return ByteBuffer.allocate(0); - } - }; - return RxReactiveStreams.toObservable(client.requestResponse(payload)); - } - ) - .subscribe(new rx.Subscriber() { - @Override - public void onCompleted() { - } - - @Override - public void onError(Throwable e) { - e.printStackTrace(); - } - - @Override - public void onNext(Payload s) { - System.out.println(s + " countdown => " + latch.getCount()); - latch.countDown(); - } - }); - - latch.await(); - } - - - @Test - public void createTwoServersAndTwoClients()throws Exception { - Random random = new Random(); - byte[] b = new byte[1_000_000]; - random.nextBytes(b); - - ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { - @Override - public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { - return new RequestHandler() { - @Override - public Publisher handleRequestResponse(Payload payload) { - System.out.println("Server got => " + b.length); - Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong server 1", null)); - return RxReactiveStreams.toPublisher(pong); - } - - @Override - public Publisher handleChannel(Payload initialPayload, Publisher payloads) { - return null; - } - - @Override - public Publisher handleRequestStream(Payload payload) { - return null; - } - - @Override - public Publisher handleSubscription(Payload payload) { - return null; - } - - @Override - public Publisher handleFireAndForget(Payload payload) { - return null; - } - - @Override - public Publisher handleMetadataPush(Payload payload) { - return null; - } - }; - } - }); - - ReactiveSocketAeronServer.create(12345, new ConnectionSetupHandler() { - @Override - public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { - return new RequestHandler() { - @Override - public Publisher handleRequestResponse(Payload payload) { - System.out.println("Server got => " + b.length); - Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong server 2", null)); - return RxReactiveStreams.toPublisher(pong); - } - - @Override - public Publisher handleChannel(Payload initialPayload, Publisher payloads) { - return null; - } - - @Override - public Publisher handleRequestStream(Payload payload) { - return null; - } - - @Override - public Publisher handleSubscription(Payload payload) { - return null; - } - - @Override - public Publisher handleFireAndForget(Payload payload) { - return null; - } - - @Override - public Publisher handleMetadataPush(Payload payload) { - return null; - } - }; - } - }); - - CountDownLatch latch = new CountDownLatch(2 * 130); - - ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost", "localhost"); - - ReactivesocketAeronClient client2 = ReactivesocketAeronClient.create("localhost", "localhost", 12345); - - Observable - .range(1, 130) - .flatMap(i -> { - System.out.println("pinging server 1 => " + i); - Payload payload = new Payload() { - @Override - public ByteBuffer getData() { - return ByteBuffer.wrap(b); - } - - @Override - public ByteBuffer getMetadata() { - return ByteBuffer.allocate(0); - } - }; - - latch.countDown(); - return RxReactiveStreams.toObservable(client.requestResponse(payload)); - } - ) - .subscribe(new rx.Subscriber() { - @Override - public void onCompleted() { - } - - @Override - public void onError(Throwable e) { - e.printStackTrace(); - } - - @Override - public void onNext(Payload s) { - System.out.println(s + " countdown server 1 => " + latch.getCount()); - } - }); - - Observable - .range(1, 130) - .flatMap(i -> { - System.out.println("pinging server 2 => " + i); - Payload payload = new Payload() { - @Override - public ByteBuffer getData() { - return ByteBuffer.wrap(b); - } - - @Override - public ByteBuffer getMetadata() { - return ByteBuffer.allocate(0); - } - }; - latch.countDown(); - return RxReactiveStreams.toObservable(client2.requestResponse(payload)); - } - ) - .subscribe(new rx.Subscriber() { - @Override - public void onCompleted() { - } - - @Override - public void onError(Throwable e) { - e.printStackTrace(); - } - - @Override - public void onNext(Payload s) { - System.out.println(s + " countdown server 2 => " + latch.getCount()); - } - }); - - latch.await(); - } - -} From bd3bd99d29a594a3579aa4f8bc773ac3698c5045 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Fri, 25 Sep 2015 11:08:20 -0700 Subject: [PATCH 046/105] added message sent from the client to clean up the server --- .../AbstractClientDuplexConnection.java | 3 - .../multi/AeronClientDuplexConnection.java | 2 +- .../client/multi/ClientAeronManager.java | 247 ++++++++++++- .../aeron/client/multi/FrameHolder.java | 9 +- .../multi/ReactivesocketAeronClient.java | 345 ++++++------------ .../aeron/internal/AeronUtil.java | 39 +- .../aeron/internal/Constants.java | 1 + .../aeron/internal/Loggable.java | 4 +- .../aeron/internal/MessageType.java | 3 +- .../server/AeronServerDuplexConnection.java | 20 +- .../server/ReactiveSocketAeronServer.java | 43 ++- .../aeron/server/ServerAeronManager.java | 19 +- .../client/multi/ReactiveSocketAeronTest.java | 116 +++++- 13 files changed, 554 insertions(+), 297 deletions(-) diff --git a/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java index 954122e29..9e2510d84 100644 --- a/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java @@ -19,12 +19,9 @@ public abstract class AbstractClientDuplexConnection> subjects; - protected final Publication publication; - protected final T framesSendQueue; public AbstractClientDuplexConnection(Publication publication) { - this.publication = publication; this.subjects = new CopyOnWriteArrayList<>(); this.framesSendQueue = createQueue(); this.connectionId = count.incrementAndGet(); diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java index 0a425ae4c..84a25b8ef 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java @@ -36,7 +36,7 @@ public void onSubscribe(Subscription s) { @Override public void onNext(Frame frame) { - final FrameHolder fh = FrameHolder.get(frame, publication, s); + final FrameHolder fh = FrameHolder.get(frame, s); boolean offer; int i = 0; do { diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/ClientAeronManager.java b/src/main/java/io/reactivesocket/aeron/client/multi/ClientAeronManager.java index bd5d8c4c5..116c2152e 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/ClientAeronManager.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/ClientAeronManager.java @@ -1,7 +1,250 @@ package io.reactivesocket.aeron.client.multi; +import io.reactivesocket.aeron.internal.Constants; +import io.reactivesocket.aeron.internal.Loggable; +import rx.Scheduler; +import rx.functions.Func1; +import rx.schedulers.Schedulers; +import uk.co.real_logic.aeron.Aeron; +import uk.co.real_logic.aeron.FragmentAssembler; +import uk.co.real_logic.aeron.Image; +import uk.co.real_logic.aeron.Subscription; +import uk.co.real_logic.aeron.logbuffer.FragmentHandler; + +import java.util.Optional; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.TimeUnit; + /** - * Created by rroeser on 9/24/15. + * Class for managing the Aeron on the client side. */ -public class ClientAeronManager { +public class ClientAeronManager implements Loggable { + private static final ClientAeronManager INSTANCE = new ClientAeronManager(); + + private final CopyOnWriteArrayList subscriptionGroups; + + private final Aeron aeron; + + private final Scheduler.Worker[] workers = new Scheduler.Worker[Constants.CONCURRENCY]; + + private ClientAeronManager() { + this.subscriptionGroups = new CopyOnWriteArrayList<>(); + + final Aeron.Context ctx = new Aeron.Context(); + ctx.errorHandler(t -> error("an exception occurred", t)); + + aeron = Aeron.connect(ctx); + + poll(); + } + + public static ClientAeronManager getInstance() { + return INSTANCE; + } + + /** + * Finds a SubscriptionGroup from the sessionId of one the servers in the group + * + * @param sessionId the session id whose SubscriptionGroup you want to find + * @return an Optional of SubscriptionGroup + */ + public Optional find(final int sessionId) { + return subscriptionGroups + .stream() + .filter(sg -> { + boolean found = false; + + for (Subscription subscription : sg.subscriptions) { + Image image = subscription.getImage(sessionId); + if (image != null) { + found = true; + break; + } + } + + return found; + }) + .findFirst(); + } + + public void removeClientAction(int id) { + subscriptionGroups + .forEach(sg -> + sg + .getClientActions() + .removeIf(c -> { + if (c.id == id) { + debug("removing client action for id => {}", id); + try { + c.close(); + } catch (Throwable e) { + debug("an exception occurred trying to close connection {}", e, id); + } + return true; + } else { + return false; + } + }) + ); + } + + + public boolean hasSubscriptionForChannel(String subscriptionChannel) { + return subscriptionGroups + .stream() + .anyMatch(sg -> sg.getChannel().equals(subscriptionChannel)); + } + + public Aeron getAeron() { + return aeron; + } + + /** + * Adds an Aeron subscription to be polled. This method will create a subscription for each of the polling threads. + * + * @param subscriptionChannel the channel to create subscriptions on + * @param streamId the stream id to create subscriptions on + * @param fragmentHandlerFactory factory that creates a fragment handler that is aware of the thread that is call it. + */ + public void addSubscription(String subscriptionChannel, int streamId, Func1 fragmentHandlerFactory) { + if (!hasSubscriptionForChannel(subscriptionChannel)) { + + + debug("Creating a subscriptions to channel => {}", subscriptionChannel); + Subscription[] subscriptions = new Subscription[Constants.CONCURRENCY]; + for (int i = 0; i < Constants.CONCURRENCY; i++) { + subscriptions[i] = aeron.addSubscription(subscriptionChannel, streamId); + debug("Subscription created with session id => {} and threadId => {}", subscriptions[i].streamId(), i); + } + SubscriptionGroup subscriptionGroup = new SubscriptionGroup(subscriptionChannel, subscriptions, fragmentHandlerFactory); + subscriptionGroups.add(subscriptionGroup); + debug("Subscriptions created to channel => {}", subscriptionChannel); + + } else { + debug("Subscription already exists for channel => {}", subscriptionChannel); + } + } + + /* + * Starts polling for the Aeron client. Will run register client actions and will automatically start polling + * subscriptions + */ + private void poll() { + info("ReactiveSocket Aeron Client concurreny is {}", Constants.CONCURRENCY); + for (int i = 0; i < Constants.CONCURRENCY; i++) { + final int threadId = i; + workers[threadId] = Schedulers.computation().createWorker(); + workers[threadId].schedulePeriodically(() -> { + try { + subscriptionGroups + .forEach(sg -> { + try { + Subscription subscription = sg.getSubscriptions()[threadId]; + subscription.poll(sg.getFragmentAssembler(threadId), Integer.MAX_VALUE); + + sg + .getClientActions() + .forEach(a -> a.call(threadId)); + } catch (Throwable t) { + error("error polling aeron subscription on thread with id " + threadId, t); + } + }); + + } catch (Throwable t) { + error("error in client polling loop on thread with id " + threadId, t); + } + }, 0, 1, TimeUnit.MICROSECONDS); + } + } + + /* + * Inner Classes + */ + + /** + * Creates a logic group of {@link uk.co.real_logic.aeron.Subscription}s to a particular channel. + */ + public static class SubscriptionGroup { + + private final static ThreadLocal threadLocalFragmentAssembler = new ThreadLocal<>(); + private final String channel; + private final Subscription[] subscriptions; + private final Func1 fragmentHandlerFactory; + + private final CopyOnWriteArraySet clientActions; + + public SubscriptionGroup(String channel, Subscription[] subscriptions, Func1 fragmentHandlerFactory) { + this.channel = channel; + this.subscriptions = subscriptions; + this.fragmentHandlerFactory = fragmentHandlerFactory; + this.clientActions = new CopyOnWriteArraySet<>(); + } + + public String getChannel() { + return channel; + } + + public Subscription[] getSubscriptions() { + return subscriptions; + } + + public FragmentAssembler getFragmentAssembler(int threadId) { + FragmentAssembler assembler = threadLocalFragmentAssembler.get(); + + if (assembler == null) { + assembler = new FragmentAssembler(fragmentHandlerFactory.call(threadId)); + threadLocalFragmentAssembler.set(assembler); + } + + return assembler; + } + + public CopyOnWriteArraySet getClientActions() { + return clientActions; + } + } + + public static abstract class ClientAction implements AutoCloseable { + protected int id; + + public ClientAction(int id) { + this.id = id; + } + + abstract void call(int threadId); + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ClientAction that = (ClientAction) o; + + if (id != that.id) return false; + + return true; + } + + @Override + public final int hashCode() { + return id; + } + } + + /** + * FragmentHandler that is aware of the thread that it is running on. This is useful if you only want a one thread + * to process a particular message. + */ + public static abstract class ThreadIdAwareFragmentHandler implements FragmentHandler { + private int threadId; + + public ThreadIdAwareFragmentHandler(int threadId) { + this.threadId = threadId; + } + + public final int getThreadId() { + return this.threadId; + } + } } diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java b/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java index 40d0ea305..48065eb65 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java @@ -3,7 +3,6 @@ import io.reactivesocket.Frame; import io.reactivesocket.aeron.internal.Constants; import org.reactivestreams.Subscription; -import uk.co.real_logic.aeron.Publication; import uk.co.real_logic.agrona.concurrent.OneToOneConcurrentArrayQueue; /** @@ -14,13 +13,12 @@ class FrameHolder { private static final ThreadLocal> FRAME_HOLDER_QUEUE = ThreadLocal.withInitial(() -> new OneToOneConcurrentArrayQueue<>(Constants.QUEUE_SIZE)); - private Publication publication; private Frame frame; private Subscription s; private FrameHolder() {} - public static FrameHolder get(Frame frame, Publication publication, Subscription s) { + public static FrameHolder get(Frame frame, Subscription s) { FrameHolder frameHolder = FRAME_HOLDER_QUEUE.get().poll(); if (frameHolder == null) { @@ -28,16 +26,11 @@ public static FrameHolder get(Frame frame, Publication publication, Subscription } frameHolder.frame = frame; - frameHolder.publication = publication; frameHolder.s = s; return frameHolder; } - public Publication getPublication() { - return publication; - } - public Frame getFrame() { return frame; } diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java index 48c2c7a02..8df5ed245 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java @@ -9,26 +9,17 @@ import io.reactivesocket.aeron.internal.MessageType; import io.reactivesocket.rx.Observer; import org.reactivestreams.Publisher; -import rx.Scheduler; -import rx.functions.Action0; -import rx.schedulers.Schedulers; -import uk.co.real_logic.aeron.Aeron; -import uk.co.real_logic.aeron.FragmentAssembler; import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.aeron.Subscription; import uk.co.real_logic.aeron.logbuffer.Header; import uk.co.real_logic.agrona.BitUtil; import uk.co.real_logic.agrona.DirectBuffer; -import uk.co.real_logic.agrona.LangUtil; import uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue; import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; import java.nio.ByteBuffer; -import java.util.Collection; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListMap; -import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; @@ -38,17 +29,10 @@ import static io.reactivesocket.aeron.internal.Constants.EMTPY; import static io.reactivesocket.aeron.internal.Constants.SERVER_STREAM_ID; - /** - * Created by rroeser on 9/16/15. + * Class that exposes ReactiveSocket over Aeron */ -public class ReactivesocketAeronClient implements Loggable, AutoCloseable { - static final CopyOnWriteArrayList SUBSCRIPTION_GROUPS = new CopyOnWriteArrayList<>(); - - private class SubscriptionGroup { - String channel; - Subscription[] subscriptions; - } +public class ReactiveSocketAeronClient implements Loggable, AutoCloseable { static final ConcurrentSkipListMap connections = new ConcurrentSkipListMap<>(); @@ -58,125 +42,40 @@ private class SubscriptionGroup { static final ConcurrentHashMap reactiveSockets = new ConcurrentHashMap<>(); - private volatile static Aeron aeron; - - private volatile static boolean running = true; + private static final ClientAeronManager manager = ClientAeronManager.getInstance(); final int sessionId; - volatile int serverSessionId; - - private static final CountDownLatch shutdownLatch = new CountDownLatch(1); - - private final int port; - - private static Scheduler.Worker[] workers; - - static { - Runtime - .getRuntime() - .addShutdownHook(new Thread(() -> { - running = false; - - try { - shutdownLatch.await(5, TimeUnit.SECONDS); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - for (SubscriptionGroup subscriptionGroup : SUBSCRIPTION_GROUPS) { - for (Subscription subscription : subscriptionGroup.subscriptions) { - subscription.close(); - } - } - - for (AeronClientDuplexConnection connection : connections.values()) { - connection.close(); - } - })); - } - - private static volatile boolean pollingStarted = false; - - private ReactivesocketAeronClient(String host, String server, int port) { - this.port = port; - - synchronized (SUBSCRIPTION_GROUPS) { - if (aeron == null) { - - final Aeron.Context ctx = new Aeron.Context(); - ctx.errorHandler(t -> { - t.printStackTrace(); - }); - - aeron = Aeron.connect(ctx); - } - - workers = new Scheduler.Worker[CONCURRENCY]; - - for (int i = 0; i < CONCURRENCY; i++) { - workers[i] = Schedulers.computation().createWorker(); - } - } - + /** + * Creates a new ReactivesocketAeronClient + * + * @param host the host name that client is listening on + * @param server the host of the server that client will send data too + * @param port the port to send and receive data on + */ + private ReactiveSocketAeronClient(String host, String server, int port) { final String channel = "udp://" + host + ":" + port; final String subscriptionChannel = "udp://" + server + ":" + port; - debug("Creating a publication to channel => " + channel); - Publication publication = aeron.addPublication(channel, SERVER_STREAM_ID); + debug("Creating a publication to channel => {}", channel); + Publication publication = manager.getAeron().addPublication(channel, SERVER_STREAM_ID); publications.putIfAbsent(publication.sessionId(), publication); - debug("Creating publication => " + publication.toString()); sessionId = publication.sessionId(); + debug("Created a publication with sessionId => {}", sessionId); - debug("Created a publication for sessionId => " + sessionId); - synchronized (SUBSCRIPTION_GROUPS) { - boolean found = SUBSCRIPTION_GROUPS - .stream() - .anyMatch(sg -> subscriptionChannel.equals(sg.channel)); - if (!found) { - SubscriptionGroup subscriptionGroup = new SubscriptionGroup(); - subscriptionGroup.subscriptions = new Subscription[CONCURRENCY]; - for (int i = 0; i < CONCURRENCY; i++) { - debug("Creating a subscription to channel => " + subscriptionChannel + ", and processing => " + i); - subscriptionGroup.subscriptions[i] = aeron.addSubscription(subscriptionChannel, CLIENT_STREAM_ID); - debug("Subscription created to channel => " + subscriptionChannel + ", and processing => " + i); - } - SUBSCRIPTION_GROUPS.add(subscriptionGroup); - } - - if (!pollingStarted) { - CountDownLatch startBarrier = new CountDownLatch(CONCURRENCY); - for (int i = 0; i < CONCURRENCY; i++) { - Schedulers - .computation() - .createWorker() - .schedulePeriodically(new Poller(i, startBarrier), 0, 1, TimeUnit.NANOSECONDS); - } - - try { - startBarrier.await(30, TimeUnit.SECONDS); - } catch (Exception e) { - LangUtil.rethrowUnchecked(e); + manager.addSubscription(subscriptionChannel, CLIENT_STREAM_ID, (Integer threadId) -> + new ClientAeronManager.ThreadIdAwareFragmentHandler(threadId) { + @Override + public void onFragment(DirectBuffer buffer, int offset, int length, Header header) { + fragmentHandler(getThreadId(), buffer, offset, length, header); + } } - - pollingStarted = true; - } - } + ); establishConnection(publication, sessionId); - - } - - public static ReactivesocketAeronClient create(String host, String server, int port) { - return new ReactivesocketAeronClient(host, server, port); - } - - public static ReactivesocketAeronClient create(String host, String server) { - return create(host, server, 39790); } void fragmentHandler(int threadId, DirectBuffer buffer, int offset, int length, Header header) { - try { short messageCount = buffer.getShort(offset); short messageTypeInt = buffer.getShort(offset + BitUtil.SIZE_OF_SHORT); @@ -189,18 +88,22 @@ void fragmentHandler(int threadId, DirectBuffer buffer, int offset, int length, final MessageType messageType = MessageType.from(messageTypeInt); if (messageType == MessageType.FRAME) { final AeronClientDuplexConnection connection = connections.get(header.sessionId()); - final List> subscribers = connection.getSubscriber(); - if (!subscribers.isEmpty()) { - //TODO think about how to recycle these, hard because could be handed to another thread I think? - final ByteBuffer bytes = ByteBuffer.allocate(length); - buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); - final Frame frame = Frame.from(bytes); - int i = 0; - final int size = subscribers.size(); - do { - subscribers.get(i).onNext(frame); - i++; - } while (i < size); + if (connection != null) { + final List> subscribers = connection.getSubscriber(); + if (!subscribers.isEmpty()) { + //TODO think about how to recycle these, hard because could be handed to another thread I think? + final ByteBuffer bytes = ByteBuffer.allocate(length); + buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); + final Frame frame = Frame.from(bytes); + int i = 0; + final int size = subscribers.size(); + do { + subscribers.get(i).onNext(frame); + i++; + } while (i < size); + } + } else { + debug("no connection found for Aeron Session Id {}", sessionId); } } else if (messageType == MessageType.ESTABLISH_CONNECTION_RESPONSE) { final int ackSessionId = buffer.getInt(offset + BitUtil.SIZE_OF_INT); @@ -212,8 +115,8 @@ void fragmentHandler(int threadId, DirectBuffer buffer, int offset, int length, } Publication publication = publications.get(ackSessionId); - serverSessionId = header.sessionId(); - debug(String.format("Received establish connection ack for session id => %d, and server session id => %d", ackSessionId, serverSessionId)); + final int serverSessionId = header.sessionId(); + debug("Received establish connection ack for session id => {}, and server session id => {}", ackSessionId, serverSessionId); final AeronClientDuplexConnection connection = connections .computeIfAbsent(serverSessionId, (_p) -> @@ -228,6 +131,17 @@ void fragmentHandler(int threadId, DirectBuffer buffer, int offset, int length, reactiveSockets.putIfAbsent(ackSessionId, reactiveSocket); + ReactiveSocketAeronClientAction clientAction + = new ReactiveSocketAeronClientAction(ackSessionId, reactiveSocket, connection); + + manager + .find(header.sessionId()) + .ifPresent(sg -> + sg + .getClientActions() + .add(clientAction) + ); + latch.countDown(); debug("ReactiveSocket connected to Aeron session => " + ackSessionId); @@ -235,82 +149,54 @@ void fragmentHandler(int threadId, DirectBuffer buffer, int offset, int length, debug("Unknown message type => " + messageTypeInt); } } catch (Throwable t) { - System.out.println("ERROR fragmentHandler"); - t.printStackTrace(); error("error handling framement", t); } } - class Poller implements Action0 { - final int threadId; - CountDownLatch startupLatch; - final FragmentAssembler fragmentAssembler; - - public Poller(int threadId, CountDownLatch startupLatch) { - this.threadId = threadId; - this.startupLatch = startupLatch; - fragmentAssembler = new FragmentAssembler( - (DirectBuffer buffer, int offset, int length, Header header) -> - fragmentHandler(threadId, buffer, offset, length, header)); - debug("Starting new Poller for thread id => " + threadId); - } + static class ReactiveSocketAeronClientAction extends ClientAeronManager.ClientAction implements Loggable { + private final ReactiveSocket reactiveSocket; + private final AeronClientDuplexConnection connection; - public void call() { - if (!pollingStarted) { - startupLatch.countDown(); + public ReactiveSocketAeronClientAction(int sessionId, ReactiveSocket reactiveSocket, AeronClientDuplexConnection connection) { + super(sessionId); + this.reactiveSocket = reactiveSocket; + this.connection = connection; + } - if (startupLatch.getCount() != workers.length) { - return; - } + @Override + void call(int threadId) { + final int calculatedThreadId = Math.abs(connection.getConnectionId() % CONCURRENCY); + if (threadId == calculatedThreadId) { + ManyToOneConcurrentArrayQueue framesSendQueue = connection.getFramesSendQueue(); + framesSendQueue + .drain((FrameHolder fh) -> { + try { + Frame frame = fh.getFrame(); + final ByteBuffer byteBuffer = frame.getByteBuffer(); + final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; + Publication publication = publications.get(id); + AeronUtil.tryClaimOrOffer(publication, (offset, buffer) -> { + buffer.putShort(offset, (short) 0); + buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); + buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, frame.offset(), frame.length()); + }, length); + } catch (Throwable t) { + error("error draining send frame queue", t); + } finally { + fh.release(); + } + }); } + } - if (running) { - try { - final Collection values = connections.values(); - - if (!values.isEmpty()) { - values.forEach(connection -> { - final int calculatedThreadId = Math.abs(connection.getConnectionId() % CONCURRENCY); - if (calculatedThreadId == threadId) { - ManyToOneConcurrentArrayQueue framesSendQueue = connection.getFramesSendQueue(); - framesSendQueue - .drain((FrameHolder fh) -> { - try { - Frame frame = fh.getFrame(); - final ByteBuffer byteBuffer = frame.getByteBuffer(); - final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; - AeronUtil.tryClaimOrOffer(fh.getPublication(), (offset, buffer) -> { - buffer.putShort(offset, (short) 0); - buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); - buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, frame.offset(), frame.length()); - }, length); - fh.release(); - } catch (Throwable t) { - fh.release(); - error("error draining send frame queue", t); - } - }); - } - }); - } - - try { - SUBSCRIPTION_GROUPS - .forEach(subscriptionGroup -> { - Subscription subscription = subscriptionGroup.subscriptions[threadId]; - subscription.poll(fragmentAssembler, Integer.MAX_VALUE); - }); - } catch (Throwable t) { - t.printStackTrace(); - error("error polling aeron subscription", t); - } - } catch (Throwable t) { - t.printStackTrace(); - } + @Override + public void close() throws Exception { + manager + .find(id) + .ifPresent(sg -> sg.getClientActions().remove(this)); - } else { - shutdownLatch.countDown(); - } + reactiveSocket.close(); + connection.close(); } } @@ -355,6 +241,29 @@ void establishConnection(final Publication publication, final int sessionId) { } + @Override + public void close() throws Exception { + manager.removeClientAction(sessionId); + + Publication publication = publications.remove(sessionId); + + if (publication != null) { + try { + + AeronUtil.tryClaimOrOffer(publication, (offset, buffer) -> { + buffer.putShort(offset, (short) 0); + buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.CONNECTION_DISCONNECT.getEncodedType()); + }, BitUtil.SIZE_OF_INT); + } catch (Throwable t) { + debug("error closing publication with session id => {}", publication.sessionId()); + } + publication.close(); + } + } + + /* + * ReactiveSocket methods + */ public Publisher requestResponse(Payload payload) { ReactiveSocket reactiveSocket = reactiveSockets.get(sessionId); return reactiveSocket.requestResponse(payload); @@ -375,29 +284,15 @@ public Publisher requestSubscription(Payload payload) { return reactiveSocket.requestSubscription(payload); } - @Override - public void close() throws Exception { - // First clean up the different maps - // Remove the AeronDuplexConnection from the connections map - AeronClientDuplexConnection connection = connections.remove(serverSessionId); - - // This should already be removed but remove it just in case to be safe - establishConnectionLatches.remove(sessionId); - - // Close the different connections - closeQuietly(connection); - closeQuietly(reactiveSockets.get(sessionId)); - System.out.println("closing publication => " + publications.get(sessionId).toString()); - Publication publication = publications.remove(sessionId); - closeQuietly(publication); - + /* + * Factory Methods + */ + public static ReactiveSocketAeronClient create(String host, String server, int port) { + return new ReactiveSocketAeronClient(host, server, port); } - private void closeQuietly(AutoCloseable closeable) { - try { - closeable.close(); - } catch (Throwable t) { - error(t.getMessage(), t); - } + public static ReactiveSocketAeronClient create(String host, String server) { + return create(host, server, 39790); } + } diff --git a/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java b/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java index 19e2340ca..ea28e5f29 100644 --- a/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java +++ b/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java @@ -20,13 +20,12 @@ public class AeronUtil { /** * Sends a message using offer. This method will spin-lock if Aeron signals back pressure. - * + *

* This method of sending data does need to know how long the message is. * * @param publication publication to send the message on - * - * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} - * that is send over Aeron + * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} + * that is send over Aeron */ public static void offer(Publication publication, BufferFiller fillBuffer, int length, int timeout, TimeUnit timeUnit) { final MutableDirectBuffer buffer = getDirectBuffer(length); @@ -45,7 +44,7 @@ public static void offer(Publication publication, BufferFiller fillBuffer, int l } else if (Publication.NOT_CONNECTED == offer) { throw new RuntimeException("not connected"); } - } while(true); + } while (true); recycleDirectBuffer(buffer); } @@ -53,13 +52,13 @@ public static void offer(Publication publication, BufferFiller fillBuffer, int l /** * Sends a message using tryClaim. This method will spin-lock if Aeron signals back pressure. The message * being sent needs to be equal or smaller than Aeron's MTU size or an exception will be thrown. - * + *

* In order to use this method of sending data you need to know the length of data. * * @param publication publication to send the message on - * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} - * that is send over Aeron - * @param length the length of data + * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} + * that is send over Aeron + * @param length the length of data */ public static void tryClaim(Publication publication, BufferFiller fillBuffer, int length, int timeout, TimeUnit timeUnit) { final BufferClaim bufferClaim = bufferClaims.get(); @@ -68,7 +67,7 @@ public static void tryClaim(Publication publication, BufferFiller fillBuffer, in if (timeout > 0) { final long current = System.nanoTime(); if ((current - start) > timeUnit.toNanos(timeout)) { - throw new RuntimeException("Timed out publishing data"); + throw new NotConnectedException(); } } @@ -83,7 +82,7 @@ public static void tryClaim(Publication publication, BufferFiller fillBuffer, in bufferClaim.commit(); } } else if (Publication.NOT_CONNECTED == offer) { - throw new RuntimeException("not connected"); + throw new NotConnectedException(); } } while (true); } @@ -93,23 +92,19 @@ public static void tryClaim(Publication publication, BufferFiller fillBuffer, in * size it will use offer instead. * * @param publication publication to send the message on - * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} - * that is send over Aeron - * @param length the length of data + * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} + * that is send over Aeron + * @param length the length of data */ public static void tryClaimOrOffer(Publication publication, BufferFiller fillBuffer, int length) { tryClaimOrOffer(publication, fillBuffer, length, -1, null); } public static void tryClaimOrOffer(Publication publication, BufferFiller fillBuffer, int length, int timeout, TimeUnit timeUnit) { - try { - if (length < Constants.AERON_MTU_SIZE) { - tryClaim(publication, fillBuffer, length, timeout, timeUnit); - } else { - offer(publication, fillBuffer, length, timeout, timeUnit); - } - } catch (Throwable t) { - t.printStackTrace(); + if (length < Constants.AERON_MTU_SIZE) { + tryClaim(publication, fillBuffer, length, timeout, timeUnit); + } else { + offer(publication, fillBuffer, length, timeout, timeUnit); } } diff --git a/src/main/java/io/reactivesocket/aeron/internal/Constants.java b/src/main/java/io/reactivesocket/aeron/internal/Constants.java index f4b92b448..cfe24efdf 100644 --- a/src/main/java/io/reactivesocket/aeron/internal/Constants.java +++ b/src/main/java/io/reactivesocket/aeron/internal/Constants.java @@ -22,4 +22,5 @@ private Constants() {} public static final int CONCURRENCY = Integer.getInteger("clientConcurrency", Runtime.getRuntime().availableProcessors() / 2); public static final int AERON_MTU_SIZE = Integer.getInteger("aeron.mtu.length", 4096); + } diff --git a/src/main/java/io/reactivesocket/aeron/internal/Loggable.java b/src/main/java/io/reactivesocket/aeron/internal/Loggable.java index 27f7994d1..3fc1c70a7 100644 --- a/src/main/java/io/reactivesocket/aeron/internal/Loggable.java +++ b/src/main/java/io/reactivesocket/aeron/internal/Loggable.java @@ -13,8 +13,8 @@ default void info(String message, Object... args) { logger().debug(message, args); } - default void error(String message, Throwable t, Object... args) { - logger().debug(message, t, args); + default void error(String message, Throwable t) { + logger().error(message, t); } default void debug(String message, Object... args) { diff --git a/src/main/java/io/reactivesocket/aeron/internal/MessageType.java b/src/main/java/io/reactivesocket/aeron/internal/MessageType.java index 53b691c0f..6e5ef8361 100644 --- a/src/main/java/io/reactivesocket/aeron/internal/MessageType.java +++ b/src/main/java/io/reactivesocket/aeron/internal/MessageType.java @@ -6,7 +6,8 @@ public enum MessageType { ESTABLISH_CONNECTION_REQUEST(0x01), ESTABLISH_CONNECTION_RESPONSE(0x02), - FRAME(0x03); + CONNECTION_DISCONNECT(0x3), + FRAME(0x04); private static MessageType[] typesById; diff --git a/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java index b9f85a3b6..06e6c8045 100644 --- a/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java @@ -5,6 +5,7 @@ import io.reactivesocket.aeron.internal.AeronUtil; import io.reactivesocket.aeron.internal.Loggable; import io.reactivesocket.aeron.internal.MessageType; +import io.reactivesocket.aeron.internal.NotConnectedException; import io.reactivesocket.rx.Completable; import io.reactivesocket.rx.Disposable; import io.reactivesocket.rx.Observable; @@ -56,11 +57,20 @@ public void addOutput(Publisher o, Completable callback) { void ackEstablishConnection(int ackSessionId) { debug("Acking establish connection for session id => {}", ackSessionId); - AeronUtil.tryClaimOrOffer(publication, (offset, buffer) -> { - buffer.putShort(offset, (short) 0); - buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.ESTABLISH_CONNECTION_RESPONSE.getEncodedType()); - buffer.putInt(offset + BitUtil.SIZE_OF_INT, ackSessionId); - }, 2 * BitUtil.SIZE_OF_INT, 30, TimeUnit.SECONDS); + for (int i = 0; i < 5; i++) { + try { + AeronUtil.tryClaimOrOffer(publication, (offset, buffer) -> { + buffer.putShort(offset, (short) 0); + buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.ESTABLISH_CONNECTION_RESPONSE.getEncodedType()); + buffer.putInt(offset + BitUtil.SIZE_OF_INT, ackSessionId); + }, 2 * BitUtil.SIZE_OF_INT, 30, TimeUnit.SECONDS); + break; + } catch (NotConnectedException ne) { + if (i >= 4) { + throw ne; + } + } + } } @Override diff --git a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java index 30b51e05a..89b214520 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import static io.reactivesocket.aeron.internal.Constants.CLIENT_STREAM_ID; import static io.reactivesocket.aeron.internal.Constants.SERVER_STREAM_ID; @@ -44,6 +45,7 @@ private ReactiveSocketAeronServer(String host, int port, ConnectionSetupHandler this.leaseGovernor = leaseGovernor; manager.addAvailableImageHander(this::availableImageHandler); + manager.addUnavailableImageHandler(this::unavailableImage); Aeron aeron = manager.getAeron(); @@ -91,6 +93,8 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) } debug("Found a connection to ack establish connection for session id => {}", sessionId); connection.ackEstablishConnection(sessionId); + } else if (MessageType.CONNECTION_DISCONNECT == type) { + closeReactiveSocket(sessionId); } } @@ -111,7 +115,12 @@ void availableImageHandler(Image image, Subscription subscription, long joiningP connection, connectionSetupHandler, leaseGovernor, - error -> error(error.getMessage(), error)); + new Consumer() { + @Override + public void accept(Throwable throwable) { + error(String.format("Error creating ReactiveSocket for Aeron session id => %d and stream id => %d", streamId, sessionId), throwable); + } + }); sockets.put(sessionId, socket); @@ -121,23 +130,29 @@ void availableImageHandler(Image image, Subscription subscription, long joiningP } } - @Override - public void close() throws Exception { - manager.removeSubscription(subscription); - /* - running = false; - - shutdownLatch.await(30, TimeUnit.SECONDS); + void unavailableImage(Image image, Subscription subscription, long position) { + closeReactiveSocket(image.sessionId()); + } - aeron.close(); + private void closeReactiveSocket(int sessionId) { + debug("closing connection for session id => " + sessionId); + ReactiveSocket socket = sockets.remove(sessionId); + connections.remove(sessionId); - for (AeronServerDuplexConnection connection : connections.values()) { - connection.close(); + try { + socket.close(); + } catch (Throwable t) { + error("error closing socket for session id => " + sessionId, t); } + } + + public boolean hasConnections() { + return !connections.isEmpty(); + } - for (ReactiveSocket reactiveSocket : sockets.values()) { - reactiveSocket.close(); - } */ + @Override + public void close() throws Exception { + manager.removeSubscription(subscription); } /* diff --git a/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java b/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java index 53ac4d9f3..f935f98e7 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java +++ b/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java @@ -6,6 +6,7 @@ import uk.co.real_logic.aeron.FragmentAssembler; import uk.co.real_logic.aeron.Image; import uk.co.real_logic.aeron.Subscription; +import uk.co.real_logic.aeron.UnavailableImageHandler; import java.util.concurrent.CopyOnWriteArrayList; @@ -20,7 +21,9 @@ public class ServerAeronManager implements Loggable { private final Aeron aeron; - private CopyOnWriteArrayList imageHandlers = new CopyOnWriteArrayList<>(); + private CopyOnWriteArrayList availableImageHandlers = new CopyOnWriteArrayList<>(); + + private CopyOnWriteArrayList unavailableImageHandlers = new CopyOnWriteArrayList<>(); private CopyOnWriteArrayList subscriptions = new CopyOnWriteArrayList<>(); @@ -37,6 +40,7 @@ public FragmentAssemblerHolder(Subscription subscription, FragmentAssembler frag public ServerAeronManager() { final Aeron.Context ctx = new Aeron.Context(); ctx.availableImageHandler(this::availableImageHandler); + ctx.unavailableImageHandler(this::unavailableImage); ctx.errorHandler(t -> error("an exception occurred", t)); aeron = Aeron.connect(ctx); @@ -49,7 +53,11 @@ public static ServerAeronManager getInstance() { } public void addAvailableImageHander(AvailableImageHandler handler) { - imageHandlers.add(handler); + availableImageHandlers.add(handler); + } + + public void addUnavailableImageHandler(UnavailableImageHandler handler) { + unavailableImageHandlers.add(handler); } public void addSubscription(Subscription subscription, FragmentAssembler fragmentAssembler) { @@ -61,10 +69,15 @@ public void removeSubscription(Subscription subscription) { } private void availableImageHandler(Image image, Subscription subscription, long joiningPosition, String sourceIdentity) { - imageHandlers + availableImageHandlers .forEach(handler -> handler.onAvailableImage(image, subscription, joiningPosition, sourceIdentity)); } + private void unavailableImage(Image image, Subscription subscription, long position) { + unavailableImageHandlers + .forEach(handler -> handler.onUnavailableImage(image, subscription, position)); + } + public Aeron getAeron() { return aeron; } diff --git a/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java index 09b808f69..3acd105e7 100644 --- a/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java @@ -20,7 +20,9 @@ import java.nio.ByteBuffer; import java.util.Random; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.LockSupport; /** * Created by rroeser on 8/14/15. @@ -35,7 +37,7 @@ public static void init() { final MediaDriver mediaDriver = MediaDriver.launch(context); } - @Test(timeout = 60000) + @Test(timeout = 10000) public void testRequestReponse() throws Exception { AtomicLong server = new AtomicLong(); ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { @@ -83,7 +85,7 @@ public Publisher handleMetadataPush(Payload payload) { CountDownLatch latch = new CountDownLatch(130); - ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost", "localhost"); + ReactiveSocketAeronClient client = ReactiveSocketAeronClient.create("localhost", "localhost"); Observable .range(1, 130) @@ -115,7 +117,7 @@ public void onNext(Payload s) { latch.await(); } - @Test(timeout = 60000) + @Test(timeout = 10000) public void sendLargeMessage() throws Exception { Random random = new Random(); @@ -163,7 +165,7 @@ public Publisher handleMetadataPush(Payload payload) { CountDownLatch latch = new CountDownLatch(2); - ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost", "localhost"); + ReactiveSocketAeronClient client = ReactiveSocketAeronClient.create("localhost", "localhost"); Observable .range(1, 2) @@ -204,7 +206,7 @@ public void onNext(Payload s) { } - @Test + @Test(timeout = 10000) public void createTwoServersAndTwoClients()throws Exception { Random random = new Random(); byte[] b = new byte[1]; @@ -292,9 +294,9 @@ public Publisher handleMetadataPush(Payload payload) { CountDownLatch latch = new CountDownLatch(2 * count); - ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost", "localhost"); + ReactiveSocketAeronClient client = ReactiveSocketAeronClient.create("localhost", "localhost"); - ReactivesocketAeronClient client2 = ReactivesocketAeronClient.create("localhost", "localhost", 12345); + ReactiveSocketAeronClient client2 = ReactiveSocketAeronClient.create("localhost", "localhost", 12345); Observable .range(1, count) @@ -371,7 +373,7 @@ public void onNext(Payload s) { latch.await(); } - @Test + @Test(timeout = 10000) public void testFireAndForget() throws Exception { CountDownLatch latch = new CountDownLatch(130); ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { @@ -418,7 +420,7 @@ public Publisher handleMetadataPush(Payload payload) { } }); - ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost", "localhost"); + ReactiveSocketAeronClient client = ReactiveSocketAeronClient.create("localhost", "localhost"); Observable .range(1, 130) @@ -433,7 +435,7 @@ public Publisher handleMetadataPush(Payload payload) { latch.await(); } - @Test + @Test(timeout = 10000) public void testRequestStream() throws Exception { CountDownLatch latch = new CountDownLatch(130); ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { @@ -492,7 +494,7 @@ public Publisher handleMetadataPush(Payload payload) { } }); - ReactivesocketAeronClient client = ReactivesocketAeronClient.create("localhost", "localhost"); + ReactiveSocketAeronClient client = ReactiveSocketAeronClient.create("localhost", "localhost"); Observable .range(1, 1) @@ -508,4 +510,96 @@ public Publisher handleMetadataPush(Payload payload) { latch.await(); } + @Test(timeout = 75000) + public void testReconnection() throws Exception { + System.out.println("--------------------------------------------------------------------------------"); + + ReactiveSocketAeronServer server = ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { + @Override + public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + return new RequestHandler() { + Frame frame = Frame.from(ByteBuffer.allocate(1)); + + @Override + public Publisher handleRequestResponse(Payload payload) { + String request = TestUtil.byteToString(payload.getData()); + System.out.println(Thread.currentThread() + " Server got => " + request); + Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong", null)); + return RxReactiveStreams.toPublisher(pong); + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher payloads) { + return null; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + return null; + } + + @Override + public Publisher handleSubscription(Payload payload) { + return null; + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return null; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + }; + } + }); + + System.out.println("--------------------------------------------------------------------------------"); + + for (int j = 0; j < 3; j++) { + CountDownLatch latch = new CountDownLatch(1); + + ReactiveSocketAeronClient client = ReactiveSocketAeronClient.create("localhost", "localhost"); + + Observable + .range(1, 1) + .flatMap(i -> { + Payload payload = TestUtil.utf8EncodedPayload("ping =>" + System.nanoTime(), null); + return RxReactiveStreams.toObservable(client.requestResponse(payload)); + } + ) + .subscribe(new rx.Subscriber() { + @Override + public void onCompleted() { + System.out.println("I HAVE COMPLETED $$$$$$$$$$$$$$$$$$$$$$$$$$$$"); + } + + @Override + public void onError(Throwable e) { + System.out.println(Thread.currentThread() + " counted to => " + latch.getCount()); + e.printStackTrace(); + } + + @Override + public void onNext(Payload s) { + System.out.println(Thread.currentThread() + " countdown => " + latch.getCount()); + latch.countDown(); + } + }); + + latch.await(); + + client.close(); + + while (server.hasConnections()) { + LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); + } + + System.out.println("--------------------------------------------------------------------------------"); + } + + } + } From cd59f3da5e21ca8ce40ec5994920c1b21d8fbd9b Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Fri, 25 Sep 2015 11:20:29 -0700 Subject: [PATCH 047/105] added license header --- .../multi/AeronClientDuplexConnection.java | 15 +++++++++++++++ .../aeron/client/multi/ClientAeronManager.java | 15 +++++++++++++++ .../aeron/client/multi/FrameHolder.java | 15 +++++++++++++++ .../client/multi/ReactivesocketAeronClient.java | 16 ++++++++++++++++ 4 files changed, 61 insertions(+) diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java index 84a25b8ef..8f7c6914a 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java @@ -1,3 +1,18 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.client.multi; diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/ClientAeronManager.java b/src/main/java/io/reactivesocket/aeron/client/multi/ClientAeronManager.java index 116c2152e..10c1eeb86 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/ClientAeronManager.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/ClientAeronManager.java @@ -1,3 +1,18 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.client.multi; import io.reactivesocket.aeron.internal.Constants; diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java b/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java index 48065eb65..943acd1fc 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java @@ -1,3 +1,18 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.client.multi; import io.reactivesocket.Frame; diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java index 8df5ed245..d82fbb7cd 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java @@ -1,3 +1,18 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.client.multi; import io.reactivesocket.ConnectionSetupPayload; @@ -31,6 +46,7 @@ /** * Class that exposes ReactiveSocket over Aeron + * */ public class ReactiveSocketAeronClient implements Loggable, AutoCloseable { From 4bccd0818d42f501fdfaad2457870cb748d58b54 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Fri, 25 Sep 2015 11:22:12 -0700 Subject: [PATCH 048/105] removed multi package --- .../client/AbstractClientDuplexConnection.java | 15 +++++++++++++++ .../{multi => }/AeronClientDuplexConnection.java | 3 +-- .../client/{multi => }/ClientAeronManager.java | 2 +- .../aeron/client/{multi => }/FrameHolder.java | 2 +- ...Client.java => ReactiveSocketAeronClient.java} | 2 +- .../reactivesocket/aeron/internal/AeronUtil.java | 15 +++++++++++++++ .../reactivesocket/aeron/internal/Constants.java | 15 +++++++++++++++ .../reactivesocket/aeron/internal/Loggable.java | 15 +++++++++++++++ .../aeron/internal/MessageType.java | 15 +++++++++++++++ .../aeron/internal/NotConnectedException.java | 15 +++++++++++++++ .../aeron/server/AeronServerDuplexConnection.java | 15 +++++++++++++++ .../aeron/server/ReactiveSocketAeronServer.java | 15 +++++++++++++++ .../aeron/server/ServerAeronManager.java | 15 +++++++++++++++ .../aeron/server/ServerSubscription.java | 15 +++++++++++++++ .../{multi => }/ReactiveSocketAeronTest.java | 3 ++- 15 files changed, 156 insertions(+), 6 deletions(-) rename src/main/java/io/reactivesocket/aeron/client/{multi => }/AeronClientDuplexConnection.java (95%) rename src/main/java/io/reactivesocket/aeron/client/{multi => }/ClientAeronManager.java (99%) rename src/main/java/io/reactivesocket/aeron/client/{multi => }/FrameHolder.java (97%) rename src/main/java/io/reactivesocket/aeron/client/{multi/ReactivesocketAeronClient.java => ReactiveSocketAeronClient.java} (99%) rename src/test/java/io/reactivesocket/aeron/client/{multi => }/ReactiveSocketAeronTest.java (99%) diff --git a/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java index 9e2510d84..49d6b8c1f 100644 --- a/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java @@ -1,3 +1,18 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.client; import io.reactivesocket.DuplexConnection; diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java similarity index 95% rename from src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java rename to src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java index 8f7c6914a..fef08a9d9 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.reactivesocket.aeron.client.multi; +package io.reactivesocket.aeron.client; import io.reactivesocket.Frame; -import io.reactivesocket.aeron.client.AbstractClientDuplexConnection; import io.reactivesocket.aeron.internal.Constants; import io.reactivesocket.rx.Completable; import org.reactivestreams.Publisher; diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/ClientAeronManager.java b/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java similarity index 99% rename from src/main/java/io/reactivesocket/aeron/client/multi/ClientAeronManager.java rename to src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java index 10c1eeb86..808ad23e1 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/ClientAeronManager.java +++ b/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.reactivesocket.aeron.client.multi; +package io.reactivesocket.aeron.client; import io.reactivesocket.aeron.internal.Constants; import io.reactivesocket.aeron.internal.Loggable; diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java b/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java similarity index 97% rename from src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java rename to src/main/java/io/reactivesocket/aeron/client/FrameHolder.java index 943acd1fc..1fe507c06 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/FrameHolder.java +++ b/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.reactivesocket.aeron.client.multi; +package io.reactivesocket.aeron.client; import io.reactivesocket.Frame; import io.reactivesocket.aeron.internal.Constants; diff --git a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/client/ReactiveSocketAeronClient.java similarity index 99% rename from src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java rename to src/main/java/io/reactivesocket/aeron/client/ReactiveSocketAeronClient.java index d82fbb7cd..a87979ae4 100644 --- a/src/main/java/io/reactivesocket/aeron/client/multi/ReactivesocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/client/ReactiveSocketAeronClient.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.reactivesocket.aeron.client.multi; +package io.reactivesocket.aeron.client; import io.reactivesocket.ConnectionSetupPayload; import io.reactivesocket.Frame; diff --git a/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java b/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java index ea28e5f29..30c4c8576 100644 --- a/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java +++ b/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java @@ -1,3 +1,18 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.internal; import uk.co.real_logic.aeron.Publication; diff --git a/src/main/java/io/reactivesocket/aeron/internal/Constants.java b/src/main/java/io/reactivesocket/aeron/internal/Constants.java index cfe24efdf..31994c963 100644 --- a/src/main/java/io/reactivesocket/aeron/internal/Constants.java +++ b/src/main/java/io/reactivesocket/aeron/internal/Constants.java @@ -1,3 +1,18 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.internal; import uk.co.real_logic.agrona.concurrent.IdleStrategy; diff --git a/src/main/java/io/reactivesocket/aeron/internal/Loggable.java b/src/main/java/io/reactivesocket/aeron/internal/Loggable.java index 3fc1c70a7..c59251e98 100644 --- a/src/main/java/io/reactivesocket/aeron/internal/Loggable.java +++ b/src/main/java/io/reactivesocket/aeron/internal/Loggable.java @@ -1,3 +1,18 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.internal; import org.slf4j.Logger; diff --git a/src/main/java/io/reactivesocket/aeron/internal/MessageType.java b/src/main/java/io/reactivesocket/aeron/internal/MessageType.java index 6e5ef8361..c294d232d 100644 --- a/src/main/java/io/reactivesocket/aeron/internal/MessageType.java +++ b/src/main/java/io/reactivesocket/aeron/internal/MessageType.java @@ -1,3 +1,18 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.internal; /** diff --git a/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java b/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java index 38f4d2c4f..ce87e7712 100644 --- a/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java +++ b/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java @@ -1,3 +1,18 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.internal; public class NotConnectedException extends RuntimeException { diff --git a/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java index 06e6c8045..8c6ad9db8 100644 --- a/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java @@ -1,3 +1,18 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.server; import io.reactivesocket.DuplexConnection; diff --git a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java index 89b214520..3ee23ebba 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java @@ -1,3 +1,18 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.server; import io.reactivesocket.ConnectionSetupHandler; diff --git a/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java b/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java index f935f98e7..f649e21c5 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java +++ b/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java @@ -1,3 +1,18 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.server; import io.reactivesocket.aeron.internal.Loggable; diff --git a/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java b/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java index 274d8abb6..5b3aa1f5f 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java +++ b/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java @@ -1,3 +1,18 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.server; import io.reactivesocket.rx.Completable; diff --git a/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java similarity index 99% rename from src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java rename to src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java index 3acd105e7..bcc67f7f9 100644 --- a/src/test/java/io/reactivesocket/aeron/client/multi/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java @@ -1,4 +1,4 @@ -package io.reactivesocket.aeron.client.multi; +package io.reactivesocket.aeron.client; import io.reactivesocket.ConnectionSetupHandler; import io.reactivesocket.ConnectionSetupPayload; @@ -6,6 +6,7 @@ import io.reactivesocket.Payload; import io.reactivesocket.RequestHandler; import io.reactivesocket.aeron.TestUtil; +import io.reactivesocket.aeron.client.ReactiveSocketAeronClient; import io.reactivesocket.aeron.server.ReactiveSocketAeronServer; import io.reactivesocket.exceptions.SetupException; import org.junit.BeforeClass; From 7d36f66190e7f58f65bbdb9f8feb6fe5fc5702b5 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Sat, 26 Sep 2015 01:22:09 -0700 Subject: [PATCH 049/105] added tracing log messages and fixed deadlock --- build.gradle | 1 + .../AbstractClientDuplexConnection.java | 10 +- .../client/AeronClientDuplexConnection.java | 46 ++++-- .../aeron/client/ClientAeronManager.java | 6 +- .../client/ReactiveSocketAeronClient.java | 47 ++++-- .../aeron/internal/Loggable.java | 13 +- .../server/AeronServerDuplexConnection.java | 61 +++++++- .../server/ReactiveSocketAeronServer.java | 7 +- .../aeron/server/ServerAeronManager.java | 10 +- .../aeron/server/ServerSubscription.java | 17 ++- .../aeron/client/MediaDriver.java | 32 +++++ .../io/reactivesocket/aeron/client/Ping.java | 112 +++++++++++++++ .../io/reactivesocket/aeron/client/Pong.java | 134 ++++++++++++++++++ .../aeron/client/ReactiveSocketAeronTest.java | 24 ++-- src/test/resources/simplelogger.properties | 1 + 15 files changed, 468 insertions(+), 53 deletions(-) create mode 100644 src/test/java/io/reactivesocket/aeron/client/MediaDriver.java create mode 100644 src/test/java/io/reactivesocket/aeron/client/Ping.java create mode 100644 src/test/java/io/reactivesocket/aeron/client/Pong.java diff --git a/build.gradle b/build.gradle index 61f14b840..807ef7d34 100644 --- a/build.gradle +++ b/build.gradle @@ -25,6 +25,7 @@ dependencies { testCompile 'junit:junit-dep:4.10' testCompile 'org.mockito:mockito-core:1.8.5' testCompile 'org.slf4j:slf4j-simple:1.7.12' + testCompile 'io.dropwizard.metrics:metrics-core:3.1.2' } diff --git a/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java index 49d6b8c1f..9278ebca7 100644 --- a/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java @@ -17,11 +17,11 @@ import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; +import io.reactivesocket.aeron.internal.concurrent.AbstractConcurrentArrayQueue; import io.reactivesocket.rx.Disposable; import io.reactivesocket.rx.Observable; import io.reactivesocket.rx.Observer; import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.agrona.concurrent.AbstractConcurrentArrayQueue; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -34,11 +34,9 @@ public abstract class AbstractClientDuplexConnection> subjects; - protected final T framesSendQueue; public AbstractClientDuplexConnection(Publication publication) { this.subjects = new CopyOnWriteArrayList<>(); - this.framesSendQueue = createQueue(); this.connectionId = count.incrementAndGet(); } @@ -61,11 +59,7 @@ public final List> getSubscriber() { return subjects; } - public final T getFramesSendQueue() { - return framesSendQueue; - } - - protected abstract T createQueue(); + public abstract T getFramesSendQueue(); public int getConnectionId() { return connectionId; diff --git a/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java index fef08a9d9..aec6db7e6 100644 --- a/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java @@ -18,23 +18,23 @@ import io.reactivesocket.Frame; import io.reactivesocket.aeron.internal.Constants; +import io.reactivesocket.aeron.internal.Loggable; +import io.reactivesocket.aeron.internal.concurrent.ManyToManyConcurrentArrayQueue; import io.reactivesocket.rx.Completable; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; -import rx.exceptions.MissingBackpressureException; import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue; -public class AeronClientDuplexConnection extends AbstractClientDuplexConnection, FrameHolder> { +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; + +public class AeronClientDuplexConnection extends AbstractClientDuplexConnection, FrameHolder> implements Loggable { public AeronClientDuplexConnection(Publication publication) { super(publication); } - @Override - protected ManyToOneConcurrentArrayQueue createQueue() { - return new ManyToOneConcurrentArrayQueue<>(Constants.CONCURRENCY); - } + protected static final ManyToManyConcurrentArrayQueue framesSendQueue = new ManyToManyConcurrentArrayQueue<>(65536); @Override public void addOutput(Publisher o, Completable callback) { @@ -44,30 +44,47 @@ public void addOutput(Publisher o, Completable callback) { @Override public void onSubscribe(Subscription s) { + if (isTraceEnabled()) { + trace("onSubscribe subscription => {} on connection id {} ", s.toString(), connectionId); + } + this.s = s; s.request(Constants.CONCURRENCY); + } @Override public void onNext(Frame frame) { + if (isTraceEnabled()) { + trace("onNext subscription => {} on connection id {} frame => {}", s.toString(), connectionId, frame.toString()); + } + final FrameHolder fh = FrameHolder.get(frame, s); boolean offer; - int i = 0; do { offer = framesSendQueue.offer(fh); - if (!offer && ++i > Constants.MULTI_THREADED_SPIN_LIMIT) { - rx.Observable.error(new MissingBackpressureException()); + if (!offer) { + System.out.println(Thread.currentThread() + " = BACKPRESSURE FOO = " + framesSendQueue.size()); + LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); + // onError(new MissingBackpressureException()); } } while (!offer); } @Override public void onError(Throwable t) { + if (isTraceEnabled()) { + trace("onError subscription => {} on connection id {} ", s.toString(), connectionId); + } + callback.error(t); } @Override public void onComplete() { + if (isTraceEnabled()) { + trace("onComplete subscription => {} on connection id {} ", s.toString(), connectionId); + } callback.success(); } }); @@ -77,4 +94,13 @@ public void onComplete() { public void close() { } + @Override + public String toString() { + return "AeronClientDuplexConnection => " + connectionId; + } + + @Override + public ManyToManyConcurrentArrayQueue getFramesSendQueue() { + return framesSendQueue; + } } diff --git a/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java b/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java index 808ad23e1..41b5f6273 100644 --- a/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java +++ b/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java @@ -160,7 +160,9 @@ private void poll() { sg .getClientActions() - .forEach(a -> a.call(threadId)); + .forEach(a -> { + a.call(threadId); + }); } catch (Throwable t) { error("error polling aeron subscription on thread with id " + threadId, t); } @@ -169,7 +171,7 @@ private void poll() { } catch (Throwable t) { error("error in client polling loop on thread with id " + threadId, t); } - }, 0, 1, TimeUnit.MICROSECONDS); + }, 0, 1, TimeUnit.NANOSECONDS); } } diff --git a/src/main/java/io/reactivesocket/aeron/client/ReactiveSocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/client/ReactiveSocketAeronClient.java index a87979ae4..a67152687 100644 --- a/src/main/java/io/reactivesocket/aeron/client/ReactiveSocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/client/ReactiveSocketAeronClient.java @@ -22,13 +22,13 @@ import io.reactivesocket.aeron.internal.AeronUtil; import io.reactivesocket.aeron.internal.Loggable; import io.reactivesocket.aeron.internal.MessageType; +import io.reactivesocket.aeron.internal.concurrent.ManyToManyConcurrentArrayQueue; import io.reactivesocket.rx.Observer; import org.reactivestreams.Publisher; import uk.co.real_logic.aeron.Publication; import uk.co.real_logic.aeron.logbuffer.Header; import uk.co.real_logic.agrona.BitUtil; import uk.co.real_logic.agrona.DirectBuffer; -import uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue; import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; import java.nio.ByteBuffer; @@ -58,10 +58,15 @@ public class ReactiveSocketAeronClient implements Loggable, AutoCloseable { static final ConcurrentHashMap reactiveSockets = new ConcurrentHashMap<>(); - private static final ClientAeronManager manager = ClientAeronManager.getInstance(); + static ClientAeronManager manager = ClientAeronManager.getInstance(); final int sessionId; + //For Test + ReactiveSocketAeronClient() { + sessionId = -1; + } + /** * Creates a new ReactivesocketAeronClient * @@ -181,28 +186,54 @@ public ReactiveSocketAeronClientAction(int sessionId, ReactiveSocket reactiveSoc @Override void call(int threadId) { - final int calculatedThreadId = Math.abs(connection.getConnectionId() % CONCURRENCY); - if (threadId == calculatedThreadId) { - ManyToOneConcurrentArrayQueue framesSendQueue = connection.getFramesSendQueue(); + final boolean traceEnabled = isTraceEnabled(); + + //final int calculatedThreadId = Math.abs(connection.getConnectionId() % CONCURRENCY); + + //if (traceEnabled) { + // trace("processing request for thread id => {}, caculcatedThreadId => {}", threadId, calculatedThreadId); + //LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); + //} + + //if (threadId == calculatedThreadId) { + ManyToManyConcurrentArrayQueue framesSendQueue = connection.getFramesSendQueue(); + + /* + if (traceEnabled && !framesSendQueue.isEmpty()) { + if (!framesSendQueue.isEmpty()) { + trace("Thread Id {} and connection Id {} draining queue", connection.getConnectionId()); + } else { + trace("Thread Id {} and connection Id {} found empty queue", connection.getConnectionId()); + } + } */ + framesSendQueue .drain((FrameHolder fh) -> { try { Frame frame = fh.getFrame(); + final ByteBuffer byteBuffer = frame.getByteBuffer(); final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; Publication publication = publications.get(id); AeronUtil.tryClaimOrOffer(publication, (offset, buffer) -> { + + if (traceEnabled) { + trace("Thread Id {} and connection Id {} sending Frame => {} on Aeron", threadId, connection.getConnectionId(), frame.toString()); + } + buffer.putShort(offset, (short) 0); buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, frame.offset(), frame.length()); }, length); + + fh.release(); + } catch (Throwable t) { - error("error draining send frame queue", t); - } finally { fh.release(); + error("error draining send frame queue", t); } }); - } + //} } @Override diff --git a/src/main/java/io/reactivesocket/aeron/internal/Loggable.java b/src/main/java/io/reactivesocket/aeron/internal/Loggable.java index c59251e98..16df2fd53 100644 --- a/src/main/java/io/reactivesocket/aeron/internal/Loggable.java +++ b/src/main/java/io/reactivesocket/aeron/internal/Loggable.java @@ -18,12 +18,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.concurrent.ConcurrentHashMap; - /** * No more needed to type Logger LOGGER = LoggerFactory.getLogger.... */ public interface Loggable { + default void info(String message, Object... args) { logger().debug(message, args); } @@ -36,9 +35,15 @@ default void debug(String message, Object... args) { logger().debug(message, args); } - static final ConcurrentHashMap loggers = new ConcurrentHashMap<>(); + default void trace(String message, Object... args) { + logger().trace(message, args); + } + + default boolean isTraceEnabled() { + return logger().isTraceEnabled(); + } default Logger logger() { - return loggers.computeIfAbsent(getClass(), LoggerFactory::getLogger); + return LoggerFactory.getLogger(getClass()); } } diff --git a/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java index 8c6ad9db8..0dd311955 100644 --- a/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java @@ -26,17 +26,16 @@ import io.reactivesocket.rx.Observable; import io.reactivesocket.rx.Observer; import org.reactivestreams.Publisher; +import rx.RxReactiveStreams; import uk.co.real_logic.aeron.Publication; import uk.co.real_logic.agrona.BitUtil; +import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; public class AeronServerDuplexConnection implements DuplexConnection, Loggable { - protected final static AtomicLong count = new AtomicLong(); - private final Publication publication; private final CopyOnWriteArrayList> subjects; @@ -52,27 +51,77 @@ public List> getSubscriber() { @Override public final Observable getInput() { + if (isTraceEnabled()) { + trace("-------getting input for publication session id {} ", publication.sessionId()); + } + return new Observable() { public void subscribe(Observer o) { o.onSubscribe(new Disposable() { @Override public void dispose() { + if (isTraceEnabled()) { + trace("removing Observer for publication with session id {} ", publication.sessionId()); + } + subjects.removeIf(s -> s == o); } }); + subjects.add(o); } }; } + private static volatile short count; + + + private short getCount() { + return count++; + } + @Override public void addOutput(Publisher o, Completable callback) { - o.subscribe(new ServerSubscription(publication, callback)); + RxReactiveStreams.toObservable(o).flatMap(frame -> + { + + if (isTraceEnabled()) { + trace("Server with publication session id {} sending frame => {}", publication.sessionId(), frame.toString()); + } + + final ByteBuffer byteBuffer = frame.getByteBuffer(); + final int length = frame.length() + BitUtil.SIZE_OF_INT; + + try { + AeronUtil.tryClaimOrOffer(publication, (offset, buffer) -> { + buffer.putShort(offset, getCount()); + buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); + buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, frame.offset(), frame.length()); + }, length); + } catch (Throwable t) { + return rx.Observable.error(t); + } + + if (isTraceEnabled()) { + trace("Server with publication session id {} sent frame with ReactiveSocket stream id => {}", publication.sessionId(), frame.getStreamId()); + } + + + return rx.Observable.empty(); + } + ) + .doOnCompleted(()-> System.out.println("-----ehere-----")). + subscribe(v -> { + }, callback::error, callback::success); + + + + //o.subscribe(new ServerSubscription(publication, callback)); } void ackEstablishConnection(int ackSessionId) { debug("Acking establish connection for session id => {}", ackSessionId); - for (int i = 0; i < 5; i++) { + for (int i = 0; i < 10; i++) { try { AeronUtil.tryClaimOrOffer(publication, (offset, buffer) -> { buffer.putShort(offset, (short) 0); @@ -81,7 +130,7 @@ void ackEstablishConnection(int ackSessionId) { }, 2 * BitUtil.SIZE_OF_INT, 30, TimeUnit.SECONDS); break; } catch (NotConnectedException ne) { - if (i >= 4) { + if (i >= 10) { throw ne; } } diff --git a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java index 3ee23ebba..b92c377e3 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java @@ -85,6 +85,11 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) if (connection != null) { List> subscribers = connection.getSubscriber(); final Frame frame = Frame.from(buffer, BitUtil.SIZE_OF_INT + offset, length); + + if (isTraceEnabled()) { + trace("---server received frame payload {} on session id {}", frame.getData(), sessionId); + } + subscribers.forEach(s -> { try { s.onNext(frame); @@ -96,7 +101,7 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) } else if (MessageType.ESTABLISH_CONNECTION_REQUEST == type) { final long start = System.nanoTime(); AeronServerDuplexConnection connection = null; - debug("Looking a connection to ack establish connection for session id => {}", sessionId); + debug("Looking for an AeronServerDuplexConnection connection to ack establish connection for session id => {}", sessionId); while (connection == null) { final long current = System.nanoTime(); diff --git a/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java b/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java index f649e21c5..6db9b6789 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java +++ b/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java @@ -40,7 +40,7 @@ public class ServerAeronManager implements Loggable { private CopyOnWriteArrayList unavailableImageHandlers = new CopyOnWriteArrayList<>(); - private CopyOnWriteArrayList subscriptions = new CopyOnWriteArrayList<>(); + private CopyOnWriteArrayList fragmentAssemblerHolders = new CopyOnWriteArrayList<>(); private class FragmentAssemblerHolder { private Subscription subscription; @@ -76,11 +76,13 @@ public void addUnavailableImageHandler(UnavailableImageHandler handler) { } public void addSubscription(Subscription subscription, FragmentAssembler fragmentAssembler) { - subscriptions.add(new FragmentAssemblerHolder(subscription, fragmentAssembler)); + debug("Adding subscription with session id {}", subscription.streamId()); + fragmentAssemblerHolders.add(new FragmentAssemblerHolder(subscription, fragmentAssembler)); } public void removeSubscription(Subscription subscription) { - subscriptions.removeIf(s -> s.subscription == subscription); + debug("Removing subscription with session id {}", subscription.streamId()); + fragmentAssemblerHolders.removeIf(s -> s.subscription == subscription); } private void availableImageHandler(Image image, Subscription subscription, long joiningPosition, String sourceIdentity) { @@ -101,7 +103,7 @@ void poll() { Thread dutyThread = new Thread(() -> { for (;;) { int poll = 0; - for (FragmentAssemblerHolder sh : subscriptions) { + for (FragmentAssemblerHolder sh : fragmentAssemblerHolders) { try { poll += sh.subscription.poll(sh.fragmentAssembler, Integer.MAX_VALUE); } catch (Throwable t) { diff --git a/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java b/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java index 5b3aa1f5f..2e7af781c 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java +++ b/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java @@ -15,6 +15,7 @@ */ package io.reactivesocket.aeron.server; +import io.reactivesocket.aeron.internal.Loggable; import io.reactivesocket.rx.Completable; import io.reactivesocket.Frame; import io.reactivesocket.aeron.internal.AeronUtil; @@ -32,7 +33,7 @@ * * @see io.reactivesocket.aeron.server.AeronServerDuplexConnection */ -class ServerSubscription implements Subscriber { +class ServerSubscription implements Subscriber, Loggable { /** * Count is used to by the client to round-robin request between threads. @@ -55,6 +56,11 @@ public void onSubscribe(Subscription s) { @Override public void onNext(Frame frame) { + + if (isTraceEnabled()) { + trace("Server with publication session id {} sending frame => {}", publication.sessionId(), frame.toString()); + } + final ByteBuffer byteBuffer = frame.getByteBuffer(); final int length = frame.length() + BitUtil.SIZE_OF_INT; @@ -63,11 +69,17 @@ public void onNext(Frame frame) { buffer.putShort(offset, getCount()); buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, frame.offset(), frame.length()); + System.out.println("WOOOHOOOO +> " + System.currentTimeMillis()); }, length); } catch (Throwable t) { onError(t); } + if (isTraceEnabled()) { + trace("Server with publication session id {} sent frame with ReactiveSocket stream id => {}", publication.sessionId(), frame.getStreamId()); + } + + } @Override @@ -77,6 +89,9 @@ public void onError(Throwable t) { @Override public void onComplete() { + if (isTraceEnabled()) { + trace("Server with publication session id {} completing", publication.sessionId()); + } completable.success(); } diff --git a/src/test/java/io/reactivesocket/aeron/client/MediaDriver.java b/src/test/java/io/reactivesocket/aeron/client/MediaDriver.java new file mode 100644 index 000000000..22c0741b4 --- /dev/null +++ b/src/test/java/io/reactivesocket/aeron/client/MediaDriver.java @@ -0,0 +1,32 @@ +package io.reactivesocket.aeron.client; + +import uk.co.real_logic.aeron.driver.ThreadingMode; +import uk.co.real_logic.agrona.concurrent.BackoffIdleStrategy; +import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; + +/** + * Created by rroeser on 8/16/15. + */ +public class MediaDriver { + public static void main(String... args) { + ThreadingMode threadingMode = ThreadingMode.SHARED; + + boolean dedicated = Boolean.getBoolean("dedicated"); + + if (true) { + threadingMode = ThreadingMode.DEDICATED; + } + + System.out.println("ThreadingMode => " + threadingMode); + + final uk.co.real_logic.aeron.driver.MediaDriver.Context ctx = new uk.co.real_logic.aeron.driver.MediaDriver.Context() + .threadingMode(threadingMode) + .dirsDeleteOnStart(true) + .conductorIdleStrategy(new BackoffIdleStrategy(1, 1, 1, 1)) + .receiverIdleStrategy(new NoOpIdleStrategy()) + .senderIdleStrategy(new NoOpIdleStrategy()); + + final uk.co.real_logic.aeron.driver.MediaDriver ignored = uk.co.real_logic.aeron.driver.MediaDriver.launch(ctx); + + } +} diff --git a/src/test/java/io/reactivesocket/aeron/client/Ping.java b/src/test/java/io/reactivesocket/aeron/client/Ping.java new file mode 100644 index 000000000..0cda6dc9b --- /dev/null +++ b/src/test/java/io/reactivesocket/aeron/client/Ping.java @@ -0,0 +1,112 @@ +package io.reactivesocket.aeron.client; + +import com.codahale.metrics.ConsoleReporter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; +import io.reactivesocket.Payload; +import rx.Observable; +import rx.RxReactiveStreams; +import rx.Subscriber; + +import java.nio.ByteBuffer; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Created by rroeser on 8/16/15. + */ +public class Ping { +/* + static { + RxJavaPlugins.getInstance().registerSchedulersHook(new RxJavaSchedulersHook() { + public Scheduler getComputationScheduler() { + return FastScheduler.getInstance(); + } + }); + } */ + + + public static void main(String... args) throws Exception { + String host = System.getProperty("host", "localhost"); + String server = System.getProperty("server", "localhost"); + Integer concurrency = Integer.getInteger("concurrency", 32); + + System.out.println("Setting host to => " + host); + + System.out.println("Setting ping is listening to => " + server); + + System.out.println("Setting concurrency to => " + concurrency); + + byte[] key = new byte[4]; + //byte[] key = new byte[BitUtil.SIZE_OF_INT]; + Random r = new Random(); + r.nextBytes(key); + + System.out.println("Sending data of size => " + key.length); + + final MetricRegistry metrics = new MetricRegistry(); + final Timer timer = metrics.timer("pingTimer"); + + final ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics) + .convertRatesTo(TimeUnit.SECONDS) + .convertDurationsTo(TimeUnit.MICROSECONDS) + .build(); + reporter.start(15, TimeUnit.SECONDS); + + ReactiveSocketAeronClient client = ReactiveSocketAeronClient.create(host, server); + + CountDownLatch latch = new CountDownLatch(Integer.MAX_VALUE); + + + Observable + .range(1, Integer.MAX_VALUE) + .flatMap(i -> { + long start = System.nanoTime(); + + Payload keyPayload = new Payload() { + ByteBuffer data = ByteBuffer.wrap(key); + ByteBuffer metadata = ByteBuffer.allocate(0); + + public ByteBuffer getData() { + return data; + } + + @Override + public ByteBuffer getMetadata() { + return metadata; + } + }; + + return RxReactiveStreams + .toObservable( + client + .requestResponse(keyPayload)) + .doOnNext(s -> { + long diff = System.nanoTime() - start; + timer.update(diff, TimeUnit.NANOSECONDS); + }); + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + } + + @Override + public void onNext(Payload payload) { + latch.countDown(); + } + }); + + latch.await(); + System.out.println("Sent => " + Integer.MAX_VALUE); + System.exit(0); + } + +} diff --git a/src/test/java/io/reactivesocket/aeron/client/Pong.java b/src/test/java/io/reactivesocket/aeron/client/Pong.java new file mode 100644 index 000000000..8d7eb7786 --- /dev/null +++ b/src/test/java/io/reactivesocket/aeron/client/Pong.java @@ -0,0 +1,134 @@ +package io.reactivesocket.aeron.client; + +import com.codahale.metrics.ConsoleReporter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; +import io.reactivesocket.ConnectionSetupHandler; +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.Payload; +import io.reactivesocket.RequestHandler; +import io.reactivesocket.aeron.server.ReactiveSocketAeronServer; +import io.reactivesocket.exceptions.SetupException; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import uk.co.real_logic.aeron.driver.ThreadingMode; +import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; + +import java.nio.ByteBuffer; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/** + * Created by rroeser on 8/16/15. + */ +public class Pong { + + public static void startDriver() { + ThreadingMode threadingMode = ThreadingMode.SHARED; + + boolean dedicated = Boolean.getBoolean("dedicated"); + + if (true) { + threadingMode = ThreadingMode.DEDICATED; + } + + System.out.println("ThreadingMode => " + threadingMode); + + final uk.co.real_logic.aeron.driver.MediaDriver.Context ctx = new uk.co.real_logic.aeron.driver.MediaDriver.Context() + .threadingMode(threadingMode) + .conductorIdleStrategy(new NoOpIdleStrategy()) + .receiverIdleStrategy(new NoOpIdleStrategy()) + .senderIdleStrategy(new NoOpIdleStrategy()); + + final uk.co.real_logic.aeron.driver.MediaDriver ignored = uk.co.real_logic.aeron.driver.MediaDriver.launchEmbedded(ctx); + } + + public static void main(String... args) { + // startDriver(); + + String host = System.getProperty("host", "localhost"); + + System.out.println("Setting host to => " + host); + + byte[] response = new byte[1024]; + //byte[] response = new byte[1024]; + Random r = new Random(); + r.nextBytes(response); + + System.out.println("Sending data of size => " + response.length); + + final MetricRegistry metrics = new MetricRegistry(); + final Timer timer = metrics.timer("pongTimer"); + + final ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics) + .convertRatesTo(TimeUnit.SECONDS) + .convertDurationsTo(TimeUnit.MILLISECONDS) + .build(); + reporter.start(15, TimeUnit.SECONDS); + + ReactiveSocketAeronServer server = ReactiveSocketAeronServer.create(host, 39790, new ConnectionSetupHandler() { + @Override + public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + return new RequestHandler() { + @Override + public Publisher handleRequestResponse(Payload payload) { + long time = System.nanoTime(); + + Publisher publisher = new Publisher() { + @Override + public void subscribe(Subscriber s) { + Payload responsePayload = new Payload() { + ByteBuffer data = ByteBuffer.wrap(response); + ByteBuffer metadata = ByteBuffer.allocate(0); + + public ByteBuffer getData() { + return data; + } + + @Override + public ByteBuffer getMetadata() { + return metadata; + } + }; + + s.onNext(responsePayload); + + long diff = System.nanoTime() - time; + timer.update(diff, TimeUnit.NANOSECONDS); + s.onComplete(); + } + }; + + return publisher; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + return null; + } + + @Override + public Publisher handleSubscription(Payload payload) { + return null; + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return null; + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher payloads) { + return null; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + }; + } + }); + } + +} diff --git a/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java index bcc67f7f9..2a5197798 100644 --- a/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java @@ -6,7 +6,6 @@ import io.reactivesocket.Payload; import io.reactivesocket.RequestHandler; import io.reactivesocket.aeron.TestUtil; -import io.reactivesocket.aeron.client.ReactiveSocketAeronClient; import io.reactivesocket.aeron.server.ReactiveSocketAeronServer; import io.reactivesocket.exceptions.SetupException; import org.junit.BeforeClass; @@ -38,7 +37,7 @@ public static void init() { final MediaDriver mediaDriver = MediaDriver.launch(context); } - @Test(timeout = 10000) + @Test(timeout = 100000) public void testRequestReponse() throws Exception { AtomicLong server = new AtomicLong(); ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { @@ -50,7 +49,7 @@ public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupExc @Override public Publisher handleRequestResponse(Payload payload) { String request = TestUtil.byteToString(payload.getData()); - System.out.println(Thread.currentThread() + " Server got => " + request); + //System.out.println(Thread.currentThread() + " Server got => " + request); Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong => " + server.incrementAndGet(), null)); return RxReactiveStreams.toPublisher(pong); } @@ -83,17 +82,24 @@ public Publisher handleMetadataPush(Payload payload) { } }); - CountDownLatch latch = new CountDownLatch(130); + CountDownLatch latch = new CountDownLatch(1300000); ReactiveSocketAeronClient client = ReactiveSocketAeronClient.create("localhost", "localhost"); Observable - .range(1, 130) + .range(1, 1300000) .flatMap(i -> { - System.out.println("pinging => " + i); + //System.out.println("pinging => " + i); Payload payload = TestUtil.utf8EncodedPayload("ping =>" + i, null); - return RxReactiveStreams.toObservable(client.requestResponse(payload)); + return RxReactiveStreams + .toObservable(client.requestResponse(payload)) + .doOnNext(f -> { + if (i % 1000 == 0) { + System.out.println("Got => " + i); + } + }) + .doOnNext(f -> latch.countDown()); } ) .subscribe(new rx.Subscriber() { @@ -110,8 +116,8 @@ public void onError(Throwable e) { @Override public void onNext(Payload s) { - System.out.println(Thread.currentThread() + " countdown => " + latch.getCount()); - latch.countDown(); + //System.out.println(Thread.currentThread() + " countdown => " + latch.getCount()); + //latch.countDown(); } }); diff --git a/src/test/resources/simplelogger.properties b/src/test/resources/simplelogger.properties index bffb759cc..5dd885ad4 100644 --- a/src/test/resources/simplelogger.properties +++ b/src/test/resources/simplelogger.properties @@ -5,6 +5,7 @@ # Must be one of ("trace", "debug", "info", "warn", or "error"). # If not specified, defaults to "info". org.slf4j.simpleLogger.defaultLogLevel=debug +#org.slf4j.simpleLogger.defaultLogLevel=trace # Logging detail level for a SimpleLogger instance named "xxxxx". # Must be one of ("trace", "debug", "info", "warn", or "error"). From d0ab9efb8d979a2267ba5167cb3c393cab0beb9b Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Sat, 26 Sep 2015 01:26:25 -0700 Subject: [PATCH 050/105] adding backpressure exception back in --- .../aeron/client/AeronClientDuplexConnection.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java index aec6db7e6..c3c580dea 100644 --- a/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java @@ -24,11 +24,9 @@ import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; +import rx.exceptions.MissingBackpressureException; import uk.co.real_logic.aeron.Publication; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.LockSupport; - public class AeronClientDuplexConnection extends AbstractClientDuplexConnection, FrameHolder> implements Loggable { public AeronClientDuplexConnection(Publication publication) { super(publication); @@ -64,9 +62,7 @@ public void onNext(Frame frame) { do { offer = framesSendQueue.offer(fh); if (!offer) { - System.out.println(Thread.currentThread() + " = BACKPRESSURE FOO = " + framesSendQueue.size()); - LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); - // onError(new MissingBackpressureException()); + onError(new MissingBackpressureException()); } } while (!offer); } From 191f794928a572e93b43db1b256287d832152909 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Sat, 26 Sep 2015 23:00:27 -0700 Subject: [PATCH 051/105] added tracining, performance improvments --- .../AbstractClientDuplexConnection.java | 16 ++- .../client/AeronClientDuplexConnection.java | 8 +- .../aeron/client/ClientAeronManager.java | 46 +++++--- .../client/ReactiveSocketAeronClient.java | 50 +++------ .../aeron/internal/Constants.java | 8 +- .../aeron/internal/Loggable.java | 6 +- .../aeron/client/MediaDriver.java | 20 +++- .../io/reactivesocket/aeron/client/Ping.java | 26 +++-- .../io/reactivesocket/aeron/client/Pong.java | 15 +++ .../aeron/client/ReactiveSocketAeronTest.java | 106 +++++++++++++++++- 10 files changed, 222 insertions(+), 79 deletions(-) diff --git a/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java index 9278ebca7..fd1a253ae 100644 --- a/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java @@ -17,14 +17,14 @@ import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; -import io.reactivesocket.aeron.internal.concurrent.AbstractConcurrentArrayQueue; import io.reactivesocket.rx.Disposable; import io.reactivesocket.rx.Observable; import io.reactivesocket.rx.Observer; import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.agrona.concurrent.AbstractConcurrentArrayQueue; +import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; public abstract class AbstractClientDuplexConnection, F> implements DuplexConnection { @@ -32,11 +32,11 @@ public abstract class AbstractClientDuplexConnection> subjects; + protected final ArrayList> subjects; public AbstractClientDuplexConnection(Publication publication) { - this.subjects = new CopyOnWriteArrayList<>(); + this.subjects = new ArrayList<>(); this.connectionId = count.incrementAndGet(); } @@ -47,10 +47,14 @@ public void subscribe(Observer o) { o.onSubscribe(new Disposable() { @Override public void dispose() { - subjects.removeIf(s -> s == o); + synchronized (count) { + subjects.removeIf(s -> s == o); + } } }); - subjects.add(o); + synchronized (count) { + subjects.add(o); + } } }; } diff --git a/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java index c3c580dea..ea6dca420 100644 --- a/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java @@ -19,20 +19,20 @@ import io.reactivesocket.Frame; import io.reactivesocket.aeron.internal.Constants; import io.reactivesocket.aeron.internal.Loggable; -import io.reactivesocket.aeron.internal.concurrent.ManyToManyConcurrentArrayQueue; import io.reactivesocket.rx.Completable; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import rx.exceptions.MissingBackpressureException; import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue; -public class AeronClientDuplexConnection extends AbstractClientDuplexConnection, FrameHolder> implements Loggable { +public class AeronClientDuplexConnection extends AbstractClientDuplexConnection, FrameHolder> implements Loggable { public AeronClientDuplexConnection(Publication publication) { super(publication); } - protected static final ManyToManyConcurrentArrayQueue framesSendQueue = new ManyToManyConcurrentArrayQueue<>(65536); + protected static final ManyToOneConcurrentArrayQueue framesSendQueue = new ManyToOneConcurrentArrayQueue<>(65536); @Override public void addOutput(Publisher o, Completable callback) { @@ -96,7 +96,7 @@ public String toString() { } @Override - public ManyToManyConcurrentArrayQueue getFramesSendQueue() { + public ManyToOneConcurrentArrayQueue getFramesSendQueue() { return framesSendQueue; } } diff --git a/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java b/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java index 41b5f6273..85b6cab5b 100644 --- a/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java +++ b/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java @@ -26,10 +26,11 @@ import uk.co.real_logic.aeron.Subscription; import uk.co.real_logic.aeron.logbuffer.FragmentHandler; +import java.util.List; import java.util.Optional; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; /** * Class for managing the Aeron on the client side. @@ -147,31 +148,44 @@ public void addSubscription(String subscriptionChannel, int streamId, Func1 { try { - subscriptionGroups - .forEach(sg -> { - try { + for (SubscriptionGroup sg : subscriptionGroups) { + try { + if (pollLock.tryLock()) { Subscription subscription = sg.getSubscriptions()[threadId]; subscription.poll(sg.getFragmentAssembler(threadId), Integer.MAX_VALUE); + } + + if (clientActionLock.tryLock()) { + final List clientActions = sg.getClientActions(); + + for (ClientAction a : clientActions) { + a.call(threadId); + } + } + } catch (Throwable t) { + error("error polling aeron subscription on thread with id " + threadId, t); + } finally { + if (pollLock.isHeldByCurrentThread()) { + pollLock.unlock(); + } - sg - .getClientActions() - .forEach(a -> { - a.call(threadId); - }); - } catch (Throwable t) { - error("error polling aeron subscription on thread with id " + threadId, t); + if (clientActionLock.isHeldByCurrentThread()) { + clientActionLock.unlock(); } - }); + } + } } catch (Throwable t) { error("error in client polling loop on thread with id " + threadId, t); } - }, 0, 1, TimeUnit.NANOSECONDS); + }, 0, 20, TimeUnit.MICROSECONDS); } } @@ -189,13 +203,13 @@ public static class SubscriptionGroup { private final Subscription[] subscriptions; private final Func1 fragmentHandlerFactory; - private final CopyOnWriteArraySet clientActions; + private final CopyOnWriteArrayList clientActions; public SubscriptionGroup(String channel, Subscription[] subscriptions, Func1 fragmentHandlerFactory) { this.channel = channel; this.subscriptions = subscriptions; this.fragmentHandlerFactory = fragmentHandlerFactory; - this.clientActions = new CopyOnWriteArraySet<>(); + this.clientActions = new CopyOnWriteArrayList<>(); } public String getChannel() { @@ -217,7 +231,7 @@ public FragmentAssembler getFragmentAssembler(int threadId) { return assembler; } - public CopyOnWriteArraySet getClientActions() { + public List getClientActions() { return clientActions; } } diff --git a/src/main/java/io/reactivesocket/aeron/client/ReactiveSocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/client/ReactiveSocketAeronClient.java index a67152687..7f10af2d4 100644 --- a/src/main/java/io/reactivesocket/aeron/client/ReactiveSocketAeronClient.java +++ b/src/main/java/io/reactivesocket/aeron/client/ReactiveSocketAeronClient.java @@ -22,13 +22,13 @@ import io.reactivesocket.aeron.internal.AeronUtil; import io.reactivesocket.aeron.internal.Loggable; import io.reactivesocket.aeron.internal.MessageType; -import io.reactivesocket.aeron.internal.concurrent.ManyToManyConcurrentArrayQueue; import io.reactivesocket.rx.Observer; import org.reactivestreams.Publisher; import uk.co.real_logic.aeron.Publication; import uk.co.real_logic.aeron.logbuffer.Header; import uk.co.real_logic.agrona.BitUtil; import uk.co.real_logic.agrona.DirectBuffer; +import uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue; import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; import java.nio.ByteBuffer; @@ -110,19 +110,21 @@ void fragmentHandler(int threadId, DirectBuffer buffer, int offset, int length, if (messageType == MessageType.FRAME) { final AeronClientDuplexConnection connection = connections.get(header.sessionId()); if (connection != null) { - final List> subscribers = connection.getSubscriber(); - if (!subscribers.isEmpty()) { - //TODO think about how to recycle these, hard because could be handed to another thread I think? - final ByteBuffer bytes = ByteBuffer.allocate(length); - buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); - final Frame frame = Frame.from(bytes); - int i = 0; - final int size = subscribers.size(); - do { - subscribers.get(i).onNext(frame); - i++; - } while (i < size); - } + final List> subscribers = connection.getSubscriber(); + if (!subscribers.isEmpty()) { + //TODO think about how to recycle these, hard because could be handed to another thread I think? + final ByteBuffer bytes = ByteBuffer.allocate(length); + buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); + final Frame frame = Frame.from(bytes); + int i = 0; + final int size = subscribers.size(); + do { + Observer frameObserver = subscribers.get(i); + frameObserver.onNext(frame); + + i++; + } while (i < size); + } } else { debug("no connection found for Aeron Session Id {}", sessionId); } @@ -187,25 +189,7 @@ public ReactiveSocketAeronClientAction(int sessionId, ReactiveSocket reactiveSoc @Override void call(int threadId) { final boolean traceEnabled = isTraceEnabled(); - - //final int calculatedThreadId = Math.abs(connection.getConnectionId() % CONCURRENCY); - - //if (traceEnabled) { - // trace("processing request for thread id => {}, caculcatedThreadId => {}", threadId, calculatedThreadId); - //LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); - //} - - //if (threadId == calculatedThreadId) { - ManyToManyConcurrentArrayQueue framesSendQueue = connection.getFramesSendQueue(); - - /* - if (traceEnabled && !framesSendQueue.isEmpty()) { - if (!framesSendQueue.isEmpty()) { - trace("Thread Id {} and connection Id {} draining queue", connection.getConnectionId()); - } else { - trace("Thread Id {} and connection Id {} found empty queue", connection.getConnectionId()); - } - } */ + ManyToOneConcurrentArrayQueue framesSendQueue = connection.getFramesSendQueue(); framesSendQueue .drain((FrameHolder fh) -> { diff --git a/src/main/java/io/reactivesocket/aeron/internal/Constants.java b/src/main/java/io/reactivesocket/aeron/internal/Constants.java index 31994c963..fa2e91057 100644 --- a/src/main/java/io/reactivesocket/aeron/internal/Constants.java +++ b/src/main/java/io/reactivesocket/aeron/internal/Constants.java @@ -28,14 +28,14 @@ private Constants() {} public static final byte[] EMTPY = new byte[0]; - public static final int QUEUE_SIZE = Integer.getInteger("framesSendQueueSize", 128); - - public static final int MULTI_THREADED_SPIN_LIMIT = Integer.getInteger("multiSpinLimit", 100); + public static final int QUEUE_SIZE = Integer.getInteger("reactivesocket.aeron.framesSendQueueSize", 128); public static final IdleStrategy SERVER_IDLE_STRATEGY = new NoOpIdleStrategy(); - public static final int CONCURRENCY = Integer.getInteger("clientConcurrency", Runtime.getRuntime().availableProcessors() / 2); + public static final int CONCURRENCY = Integer.getInteger("reactivesocket.aeron.clientConcurrency", 2); public static final int AERON_MTU_SIZE = Integer.getInteger("aeron.mtu.length", 4096); + public static final boolean TRACING_ENABLED = Boolean.getBoolean("reactivesocket.aeron.tracingEnabled"); + } diff --git a/src/main/java/io/reactivesocket/aeron/internal/Loggable.java b/src/main/java/io/reactivesocket/aeron/internal/Loggable.java index 16df2fd53..d2d540d3d 100644 --- a/src/main/java/io/reactivesocket/aeron/internal/Loggable.java +++ b/src/main/java/io/reactivesocket/aeron/internal/Loggable.java @@ -40,7 +40,11 @@ default void trace(String message, Object... args) { } default boolean isTraceEnabled() { - return logger().isTraceEnabled(); + if (Constants.TRACING_ENABLED) { + return logger().isTraceEnabled(); + } else { + return false; + } } default Logger logger() { diff --git a/src/test/java/io/reactivesocket/aeron/client/MediaDriver.java b/src/test/java/io/reactivesocket/aeron/client/MediaDriver.java index 22c0741b4..9a527635d 100644 --- a/src/test/java/io/reactivesocket/aeron/client/MediaDriver.java +++ b/src/test/java/io/reactivesocket/aeron/client/MediaDriver.java @@ -1,8 +1,22 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.client; import uk.co.real_logic.aeron.driver.ThreadingMode; import uk.co.real_logic.agrona.concurrent.BackoffIdleStrategy; -import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; /** * Created by rroeser on 8/16/15. @@ -23,8 +37,8 @@ public static void main(String... args) { .threadingMode(threadingMode) .dirsDeleteOnStart(true) .conductorIdleStrategy(new BackoffIdleStrategy(1, 1, 1, 1)) - .receiverIdleStrategy(new NoOpIdleStrategy()) - .senderIdleStrategy(new NoOpIdleStrategy()); + .receiverIdleStrategy(new BackoffIdleStrategy(1, 1, 1, 1)) + .senderIdleStrategy(new BackoffIdleStrategy(1, 1, 1, 1)); final uk.co.real_logic.aeron.driver.MediaDriver ignored = uk.co.real_logic.aeron.driver.MediaDriver.launch(ctx); diff --git a/src/test/java/io/reactivesocket/aeron/client/Ping.java b/src/test/java/io/reactivesocket/aeron/client/Ping.java index 0cda6dc9b..207c51e57 100644 --- a/src/test/java/io/reactivesocket/aeron/client/Ping.java +++ b/src/test/java/io/reactivesocket/aeron/client/Ping.java @@ -1,3 +1,18 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.client; import com.codahale.metrics.ConsoleReporter; @@ -17,26 +32,15 @@ * Created by rroeser on 8/16/15. */ public class Ping { -/* - static { - RxJavaPlugins.getInstance().registerSchedulersHook(new RxJavaSchedulersHook() { - public Scheduler getComputationScheduler() { - return FastScheduler.getInstance(); - } - }); - } */ - public static void main(String... args) throws Exception { String host = System.getProperty("host", "localhost"); String server = System.getProperty("server", "localhost"); - Integer concurrency = Integer.getInteger("concurrency", 32); System.out.println("Setting host to => " + host); System.out.println("Setting ping is listening to => " + server); - System.out.println("Setting concurrency to => " + concurrency); byte[] key = new byte[4]; //byte[] key = new byte[BitUtil.SIZE_OF_INT]; diff --git a/src/test/java/io/reactivesocket/aeron/client/Pong.java b/src/test/java/io/reactivesocket/aeron/client/Pong.java index 8d7eb7786..db041fae3 100644 --- a/src/test/java/io/reactivesocket/aeron/client/Pong.java +++ b/src/test/java/io/reactivesocket/aeron/client/Pong.java @@ -1,3 +1,18 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.client; import com.codahale.metrics.ConsoleReporter; diff --git a/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java index 2a5197798..c15916ab6 100644 --- a/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java @@ -1,3 +1,18 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.client; import io.reactivesocket.ConnectionSetupHandler; @@ -15,6 +30,7 @@ import org.reactivestreams.Subscriber; import rx.Observable; import rx.RxReactiveStreams; +import rx.schedulers.Schedulers; import uk.co.real_logic.aeron.driver.MediaDriver; import java.nio.ByteBuffer; @@ -25,7 +41,7 @@ import java.util.concurrent.locks.LockSupport; /** - * Created by rroeser on 8/14/15. + * Aeron integration tests */ @Ignore public class ReactiveSocketAeronTest { @@ -124,6 +140,94 @@ public void onNext(Payload s) { latch.await(); } + @Test(timeout = 100000) + public void testRequestReponseMultiThreaded() throws Exception { + AtomicLong server = new AtomicLong(); + ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { + @Override + public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + return new RequestHandler() { + Frame frame = Frame.from(ByteBuffer.allocate(1)); + + @Override + public Publisher handleRequestResponse(Payload payload) { + String request = TestUtil.byteToString(payload.getData()); + //System.out.println(Thread.currentThread() + " Server got => " + request); + Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong => " + server.incrementAndGet(), null)); + return RxReactiveStreams.toPublisher(pong); + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher payloads) { + return null; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + return null; + } + + @Override + public Publisher handleSubscription(Payload payload) { + return null; + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return null; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + }; + } + }); + + CountDownLatch latch = new CountDownLatch(10_000); + + + ReactiveSocketAeronClient client = ReactiveSocketAeronClient.create("localhost", "localhost"); + + Observable + .range(1, 10_000) + .flatMap(i -> { + //System.out.println("pinging => " + i); + Payload payload = TestUtil.utf8EncodedPayload("ping =>" + i, null); + return RxReactiveStreams + .toObservable(client.requestResponse(payload)) + .doOnNext(f -> { + if (i % 1000 == 0) { + System.out.println("Got => " + i); + } + }) + .doOnNext(f -> latch.countDown()) + .subscribeOn(Schedulers.newThread()); + } + , 8) + .subscribe(new rx.Subscriber() { + @Override + public void onCompleted() { + System.out.println("I HAVE COMPLETED $$$$$$$$$$$$$$$$$$$$$$$$$$$$"); + } + + @Override + public void onError(Throwable e) { + System.out.println(Thread.currentThread() + " counted to => " + latch.getCount()); + e.printStackTrace(); + } + + @Override + public void onNext(Payload s) { + //System.out.println(Thread.currentThread() + " countdown => " + latch.getCount()); + //latch.countDown(); + } + }); + + latch.await(); + } + @Test(timeout = 10000) public void sendLargeMessage() throws Exception { From c18da4ec39800f8cb6bb5167cc0126d7c7a22317 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Tue, 29 Sep 2015 18:11:05 -0700 Subject: [PATCH 052/105] client refactored --- build.gradle | 1 + .../AbstractClientDuplexConnection.java | 71 ---- .../client/AeronClientDuplexConnection.java | 103 +++--- .../AeronClientDuplexConnectionFactory.java | 233 +++++++++++++ .../aeron/client/ClientAeronManager.java | 105 +----- .../aeron/client/FrameHolder.java | 16 +- .../aeron/client/PollingAction.java | 63 ++++ .../client/ReactiveSocketAeronClient.java | 329 ------------------ .../server/AeronServerDuplexConnection.java | 2 +- .../server/ReactiveSocketAeronServer.java | 2 +- .../{ => old}/server/ServerAeronManager.java | 2 +- .../{ => old}/server/ServerSubscription.java | 4 +- .../aeron/old/client/PollingActionPerf.java | 99 ++++++ .../real_logic/aeron/DummySubscription.java | 58 +++ .../aeron/{ => old}/client/MediaDriver.java | 2 +- .../aeron/{ => old}/client/Ping.java | 29 +- .../aeron/{ => old}/client/Pong.java | 18 +- .../client/ReactiveSocketAeronTest.java | 4 +- 18 files changed, 582 insertions(+), 559 deletions(-) delete mode 100644 src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java create mode 100644 src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java create mode 100644 src/main/java/io/reactivesocket/aeron/client/PollingAction.java delete mode 100644 src/main/java/io/reactivesocket/aeron/client/ReactiveSocketAeronClient.java rename src/main/java/io/reactivesocket/aeron/{ => old}/server/AeronServerDuplexConnection.java (99%) rename src/main/java/io/reactivesocket/aeron/{ => old}/server/ReactiveSocketAeronServer.java (99%) rename src/main/java/io/reactivesocket/aeron/{ => old}/server/ServerAeronManager.java (99%) rename src/main/java/io/reactivesocket/aeron/{ => old}/server/ServerSubscription.java (96%) create mode 100644 src/perf/java/io/reactivesocket/aeron/old/client/PollingActionPerf.java create mode 100644 src/perf/java/uk/co/real_logic/aeron/DummySubscription.java rename src/test/java/io/reactivesocket/aeron/{ => old}/client/MediaDriver.java (97%) rename src/test/java/io/reactivesocket/aeron/{ => old}/client/Ping.java (77%) rename src/test/java/io/reactivesocket/aeron/{ => old}/client/Pong.java (89%) rename src/test/java/io/reactivesocket/aeron/{ => old}/client/ReactiveSocketAeronTest.java (99%) diff --git a/build.gradle b/build.gradle index 807ef7d34..3055b3a34 100644 --- a/build.gradle +++ b/build.gradle @@ -21,6 +21,7 @@ dependencies { compile 'io.reactivesocket:reactivesocket:0.0.1-SNAPSHOT' compile 'uk.co.real-logic:Agrona:0.4.3' compile 'uk.co.real-logic:aeron-all:0.1.3' + compile 'org.hdrhistogram:HdrHistogram:2.1.7' compile 'org.slf4j:slf4j-api:1.7.12' testCompile 'junit:junit-dep:4.10' testCompile 'org.mockito:mockito-core:1.8.5' diff --git a/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java deleted file mode 100644 index fd1a253ae..000000000 --- a/src/main/java/io/reactivesocket/aeron/client/AbstractClientDuplexConnection.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.aeron.client; - -import io.reactivesocket.DuplexConnection; -import io.reactivesocket.Frame; -import io.reactivesocket.rx.Disposable; -import io.reactivesocket.rx.Observable; -import io.reactivesocket.rx.Observer; -import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.agrona.concurrent.AbstractConcurrentArrayQueue; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -public abstract class AbstractClientDuplexConnection, F> implements DuplexConnection { - protected final static AtomicInteger count = new AtomicInteger(); - - protected final int connectionId; - - protected final ArrayList> subjects; - - - public AbstractClientDuplexConnection(Publication publication) { - this.subjects = new ArrayList<>(); - this.connectionId = count.incrementAndGet(); - } - - @Override - public final Observable getInput() { - return new Observable() { - public void subscribe(Observer o) { - o.onSubscribe(new Disposable() { - @Override - public void dispose() { - synchronized (count) { - subjects.removeIf(s -> s == o); - } - } - }); - synchronized (count) { - subjects.add(o); - } - } - }; - } - - public final List> getSubscriber() { - return subjects; - } - - public abstract T getFramesSendQueue(); - - public int getConnectionId() { - return connectionId; - } -} diff --git a/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java index ea6dca420..4acfd2c05 100644 --- a/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java @@ -1,102 +1,111 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ package io.reactivesocket.aeron.client; - +import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; import io.reactivesocket.aeron.internal.Constants; import io.reactivesocket.aeron.internal.Loggable; import io.reactivesocket.rx.Completable; +import io.reactivesocket.rx.Disposable; +import io.reactivesocket.rx.Observable; +import io.reactivesocket.rx.Observer; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; -import rx.exceptions.MissingBackpressureException; import uk.co.real_logic.aeron.Publication; import uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue; -public class AeronClientDuplexConnection extends AbstractClientDuplexConnection, FrameHolder> implements Loggable { - public AeronClientDuplexConnection(Publication publication) { - super(publication); +import java.io.IOException; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; + +public class AeronClientDuplexConnection implements DuplexConnection, Loggable { + + private final Publication publication; + private final CopyOnWriteArrayList> subjects; + private final ManyToOneConcurrentArrayQueue frameSendQueue; + private final Consumer onClose; + + public AeronClientDuplexConnection( + Publication publication, + ManyToOneConcurrentArrayQueue frameSendQueue, + Consumer onClose) { + this.publication = publication; + this.subjects = new CopyOnWriteArrayList<>(); + this.frameSendQueue = frameSendQueue; + this.onClose = onClose; } - protected static final ManyToOneConcurrentArrayQueue framesSendQueue = new ManyToOneConcurrentArrayQueue<>(65536); + @Override + public final Observable getInput() { + if (isTraceEnabled()) { + trace("getting input for publication session id {} ", publication.sessionId()); + } + + return new Observable() { + public void subscribe(Observer o) { + o.onSubscribe(new Disposable() { + @Override + public void dispose() { + if (isTraceEnabled()) { + trace("removing Observer for publication with session id {} ", publication.sessionId()); + } + + subjects.removeIf(s -> s == o); + } + }); + + subjects.add(o); + } + }; + } @Override public void addOutput(Publisher o, Completable callback) { o .subscribe(new Subscriber() { private Subscription s; - @Override public void onSubscribe(Subscription s) { - if (isTraceEnabled()) { - trace("onSubscribe subscription => {} on connection id {} ", s.toString(), connectionId); - } - this.s = s; - s.request(Constants.CONCURRENCY); + s.request(Constants.QUEUE_SIZE); } @Override public void onNext(Frame frame) { if (isTraceEnabled()) { - trace("onNext subscription => {} on connection id {} frame => {}", s.toString(), connectionId, frame.toString()); + trace("onNext subscription => {} and frame => {}", s.toString(), frame.toString()); } - final FrameHolder fh = FrameHolder.get(frame, s); + final FrameHolder fh = FrameHolder.get(frame, publication, s); boolean offer; do { - offer = framesSendQueue.offer(fh); - if (!offer) { - onError(new MissingBackpressureException()); - } + offer = frameSendQueue.offer(fh); } while (!offer); } @Override public void onError(Throwable t) { - if (isTraceEnabled()) { - trace("onError subscription => {} on connection id {} ", s.toString(), connectionId); - } - callback.error(t); } @Override public void onComplete() { - if (isTraceEnabled()) { - trace("onComplete subscription => {} on connection id {} ", s.toString(), connectionId); - } callback.success(); } }); } @Override - public void close() { + public void close() throws IOException { + onClose.accept(publication); } - @Override - public String toString() { - return "AeronClientDuplexConnection => " + connectionId; + public ManyToOneConcurrentArrayQueue getFrameSendQueue() { + return frameSendQueue; } - @Override - public ManyToOneConcurrentArrayQueue getFramesSendQueue() { - return framesSendQueue; + public CopyOnWriteArrayList> getSubjects() { + return subjects; } } diff --git a/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java b/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java new file mode 100644 index 000000000..18e63f1f9 --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java @@ -0,0 +1,233 @@ +package io.reactivesocket.aeron.client; + +import io.reactivesocket.Frame; +import io.reactivesocket.aeron.internal.AeronUtil; +import io.reactivesocket.aeron.internal.Constants; +import io.reactivesocket.aeron.internal.Loggable; +import io.reactivesocket.aeron.internal.MessageType; +import io.reactivesocket.rx.Observer; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.aeron.logbuffer.Header; +import uk.co.real_logic.agrona.BitUtil; +import uk.co.real_logic.agrona.DirectBuffer; +import uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; + +import static io.reactivesocket.aeron.internal.Constants.CONCURRENCY; +import static io.reactivesocket.aeron.internal.Constants.SERVER_STREAM_ID; + +public final class AeronClientDuplexConnectionFactory implements Loggable { + private static final AeronClientDuplexConnectionFactory instance = new AeronClientDuplexConnectionFactory(); + + // Aeron Publication Session Id to ReactiveSocket DuplexConnection implementation + private final ConcurrentSkipListMap connections; + + // TODO - this should be configurable...enough space for 2048 DuplexConnections assuming request(128) + private final ManyToOneConcurrentArrayQueue frameSendQueue = new ManyToOneConcurrentArrayQueue<>(262144); + + private final ConcurrentHashMap establishConnectionSubscribers; + + private final ClientAeronManager manager; + + private AeronClientDuplexConnectionFactory() { + connections = new ConcurrentSkipListMap<>(); + establishConnectionSubscribers = new ConcurrentHashMap<>(); + manager = ClientAeronManager.getInstance(); + + manager.addClientAction(threadId -> { + final boolean traceEnabled = isTraceEnabled(); + frameSendQueue + .drain(fh -> { + final Frame frame = fh.getFrame(); + final ByteBuffer byteBuffer = frame.getByteBuffer(); + final Publication publication = fh.getPublication(); + final int length = frame.length() + BitUtil.SIZE_OF_INT; + + // Can release the FrameHolder at this point as we got everything we need + fh.release(); + + AeronUtil + .tryClaimOrOffer(publication, (offset, buffer) -> { + if (traceEnabled) { + trace("Thread Id {} sending Frame => {} on Aeron", threadId, frame.toString()); + } + + buffer.putShort(offset, (short) 0); + buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); + buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, frame.offset(), frame.length()); + }, length); + }); + }); + } + + public static AeronClientDuplexConnectionFactory getInstance() { + return instance; + } + + /** + * Adds a {@link java.net.SocketAddress} for Aeron to listen to Responses on + * + * @param socketAddress + */ + public void addSocketAddressToHandleResponses(SocketAddress socketAddress) { + if (socketAddress instanceof InetSocketAddress) { + addUDPSocketAddressToHandleResponses((InetSocketAddress) socketAddress); + } + throw new RuntimeException("unknow socket address type => " + socketAddress.getClass()); + } + + void addUDPSocketAddressToHandleResponses(InetSocketAddress socketAddress) { + String serverChannel = "udp://" + socketAddress.getHostName() + ":" + socketAddress.getPort(); + + manager.addSubscription( + serverChannel, + Constants.CLIENT_STREAM_ID, + threadId -> + new ClientAeronManager.ThreadIdAwareFragmentHandler(threadId) { + @Override + public void onFragment(DirectBuffer buffer, int offset, int length, Header header) { + fragmentHandler(getThreadId(), buffer, offset, length, header); + } + }); + } + + public Publisher createAeronClientDuplexConnection(SocketAddress socketAddress) { + if (socketAddress instanceof InetSocketAddress) { + return createUDPConnection((InetSocketAddress) socketAddress); + } + + throw new RuntimeException("unknown socket address type => " + socketAddress.getClass()); + } + + Publisher createUDPConnection(InetSocketAddress inetSocketAddress) { + Publisher publisher = subscriber -> { + final String channel = "udp://" + inetSocketAddress.getHostName() + ":" + inetSocketAddress.getPort(); + debug("Creating a publication to channel => {}", channel); + Publication publication = manager.getAeron().addPublication(channel, SERVER_STREAM_ID); + debug("Created a publication with sessionId => {} to channel => {}", publication.sessionId(), channel); + EstablishConnectionSubscriber establishConnectionSubscriber = new EstablishConnectionSubscriber(publication, subscriber); + establishConnectionSubscribers.put(publication.sessionId(), establishConnectionSubscriber); + }; + + return publisher; + } + + void fragmentHandler(int threadId, DirectBuffer buffer, int offset, int length, Header header) { + try { + short messageCount = buffer.getShort(offset); + short messageTypeInt = buffer.getShort(offset + BitUtil.SIZE_OF_SHORT); + final int currentThreadId = Math.abs(messageCount % CONCURRENCY); + + if (currentThreadId != threadId) { + return; + } + + final MessageType messageType = MessageType.from(messageTypeInt); + if (messageType == MessageType.FRAME) { + AeronClientDuplexConnection aeronClientDuplexConnection = connections.get(header.sessionId()); + if (aeronClientDuplexConnection != null) { + CopyOnWriteArrayList> subjects = aeronClientDuplexConnection.getSubjects(); + if (!subjects.isEmpty()) { + //TODO think about how to recycle these, hard because could be handed to another thread I think? + final ByteBuffer bytes = ByteBuffer.allocate(length); + buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); + final Frame frame = Frame.from(bytes); + int i = 0; + final int size = subjects.size(); + do { + Observer frameObserver = subjects.get(i); + frameObserver.onNext(frame); + + i++; + } while (i < size); + } + } else { + debug("no connection found for Aeron Session Id {}", header.sessionId()); + } + } else if (messageType == MessageType.ESTABLISH_CONNECTION_RESPONSE) { + final int ackSessionId = buffer.getInt(offset + BitUtil.SIZE_OF_INT); + EstablishConnectionSubscriber establishConnectionSubscriber = establishConnectionSubscribers.get(ackSessionId); + if (establishConnectionSubscribers != null) { + try { + AeronClientDuplexConnection aeronClientDuplexConnection + = new AeronClientDuplexConnection(establishConnectionSubscriber.getPublication(), frameSendQueue, new Consumer() { + @Override + public void accept(Publication publication) { + connections.remove(publication.sessionId()); + + // Send a message to the server that the connection is closed and that it needs to clean-up resources on it's side + if (publication != null) { + try { + AeronUtil.tryClaimOrOffer(publication, (offset, buffer) -> { + buffer.putShort(offset, (short) 0); + buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.CONNECTION_DISCONNECT.getEncodedType()); + }, BitUtil.SIZE_OF_INT); + } catch (Throwable t) { + debug("error closing publication with session id => {}", publication.sessionId()); + } + publication.close(); + } + } + }); + establishConnectionSubscriber.onNext(aeronClientDuplexConnection); + establishConnectionSubscriber.onComplete(); + } catch (Throwable t) { + establishConnectionSubscriber.onError(t); + } + } + } else { + debug("Unknown message type => " + messageTypeInt); + } + } catch (Throwable t) { + error("error handling framement", t); + } + } + + /* + * Inner Classes + */ + class EstablishConnectionSubscriber implements Subscriber { + private Publication publication; + private Subscriber child; + + public EstablishConnectionSubscriber(Publication publication, Subscriber child) { + this.publication = publication; + this.child = child; + } + + public Publication getPublication() { + return publication; + } + + @Override + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); + establishConnectionSubscribers.put(publication.sessionId(), this); + } + + @Override + public void onNext(AeronClientDuplexConnection aeronClientDuplexConnection) { + onNext(aeronClientDuplexConnection); + } + + @Override + public void onError(Throwable t) { + onError(t); + } + + @Override + public void onComplete() { + child.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java b/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java index 85b6cab5b..238c7f9f7 100644 --- a/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java +++ b/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java @@ -26,7 +26,6 @@ import uk.co.real_logic.aeron.Subscription; import uk.co.real_logic.aeron.logbuffer.FragmentHandler; -import java.util.List; import java.util.Optional; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; @@ -38,6 +37,8 @@ public class ClientAeronManager implements Loggable { private static final ClientAeronManager INSTANCE = new ClientAeronManager(); + private final CopyOnWriteArrayList clientActions; + private final CopyOnWriteArrayList subscriptionGroups; private final Aeron aeron; @@ -45,6 +46,7 @@ public class ClientAeronManager implements Loggable { private final Scheduler.Worker[] workers = new Scheduler.Worker[Constants.CONCURRENCY]; private ClientAeronManager() { + this.clientActions = new CopyOnWriteArrayList<>(); this.subscriptionGroups = new CopyOnWriteArrayList<>(); final Aeron.Context ctx = new Aeron.Context(); @@ -84,25 +86,13 @@ public Optional find(final int sessionId) { .findFirst(); } - public void removeClientAction(int id) { - subscriptionGroups - .forEach(sg -> - sg - .getClientActions() - .removeIf(c -> { - if (c.id == id) { - debug("removing client action for id => {}", id); - try { - c.close(); - } catch (Throwable e) { - debug("an exception occurred trying to close connection {}", e, id); - } - return true; - } else { - return false; - } - }) - ); + /** + * Adds a ClientAction on the a list that is run by the polling loop. + * + * @param clientAction the {@link io.reactivesocket.aeron.client.ClientAeronManager.ClientAction} to add + */ + public void addClientAction(ClientAction clientAction) { + clientActions.add(clientAction); } @@ -143,49 +133,19 @@ public void addSubscription(String subscriptionChannel, int streamId, Func1 { - try { - for (SubscriptionGroup sg : subscriptionGroups) { - try { - if (pollLock.tryLock()) { - Subscription subscription = sg.getSubscriptions()[threadId]; - subscription.poll(sg.getFragmentAssembler(threadId), Integer.MAX_VALUE); - } - - if (clientActionLock.tryLock()) { - final List clientActions = sg.getClientActions(); - - for (ClientAction a : clientActions) { - a.call(threadId); - } - } - } catch (Throwable t) { - error("error polling aeron subscription on thread with id " + threadId, t); - } finally { - if (pollLock.isHeldByCurrentThread()) { - pollLock.unlock(); - } - - if (clientActionLock.isHeldByCurrentThread()) { - clientActionLock.unlock(); - } - } - } - - } catch (Throwable t) { - error("error in client polling loop on thread with id " + threadId, t); - } - }, 0, 20, TimeUnit.MICROSECONDS); + workers[threadId].schedulePeriodically(new + PollingAction(pollLock, clientActionLock, threadId, subscriptionGroups, clientActions), + 0, 1, TimeUnit.MICROSECONDS); } } @@ -203,13 +163,10 @@ public static class SubscriptionGroup { private final Subscription[] subscriptions; private final Func1 fragmentHandlerFactory; - private final CopyOnWriteArrayList clientActions; - public SubscriptionGroup(String channel, Subscription[] subscriptions, Func1 fragmentHandlerFactory) { this.channel = channel; this.subscriptions = subscriptions; this.fragmentHandlerFactory = fragmentHandlerFactory; - this.clientActions = new CopyOnWriteArrayList<>(); } public String getChannel() { @@ -230,38 +187,14 @@ public FragmentAssembler getFragmentAssembler(int threadId) { return assembler; } - - public List getClientActions() { - return clientActions; - } } - public static abstract class ClientAction implements AutoCloseable { - protected int id; - - public ClientAction(int id) { - this.id = id; - } - - abstract void call(int threadId); - - @Override - public final boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - ClientAction that = (ClientAction) o; - - if (id != that.id) return false; + @FunctionalInterface + public interface ClientAction { + void call(int threadId); + } - return true; - } - @Override - public final int hashCode() { - return id; - } - } /** * FragmentHandler that is aware of the thread that it is running on. This is useful if you only want a one thread diff --git a/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java b/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java index 1fe507c06..98ab29f6e 100644 --- a/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java +++ b/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java @@ -17,7 +17,9 @@ import io.reactivesocket.Frame; import io.reactivesocket.aeron.internal.Constants; +import org.HdrHistogram.Recorder; import org.reactivestreams.Subscription; +import uk.co.real_logic.aeron.Publication; import uk.co.real_logic.agrona.concurrent.OneToOneConcurrentArrayQueue; /** @@ -28,12 +30,16 @@ class FrameHolder { private static final ThreadLocal> FRAME_HOLDER_QUEUE = ThreadLocal.withInitial(() -> new OneToOneConcurrentArrayQueue<>(Constants.QUEUE_SIZE)); + static final Recorder histogram = new Recorder(3600000000000L, 3); + private Frame frame; + private Publication publication; private Subscription s; + private long getTime; private FrameHolder() {} - public static FrameHolder get(Frame frame, Subscription s) { + public static FrameHolder get(Frame frame, Publication publication, Subscription s) { FrameHolder frameHolder = FRAME_HOLDER_QUEUE.get().poll(); if (frameHolder == null) { @@ -43,6 +49,8 @@ public static FrameHolder get(Frame frame, Subscription s) { frameHolder.frame = frame; frameHolder.s = s; + frameHolder.getTime = System.nanoTime(); + return frameHolder; } @@ -50,6 +58,10 @@ public Frame getFrame() { return frame; } + public Publication getPublication() { + return publication; + } + public void release() { if (s != null) { s.request(1); @@ -57,5 +69,7 @@ public void release() { frame.release(); FRAME_HOLDER_QUEUE.get().offer(this); + + histogram.recordValue(System.nanoTime() - getTime); } } diff --git a/src/main/java/io/reactivesocket/aeron/client/PollingAction.java b/src/main/java/io/reactivesocket/aeron/client/PollingAction.java new file mode 100644 index 000000000..fbfc3564f --- /dev/null +++ b/src/main/java/io/reactivesocket/aeron/client/PollingAction.java @@ -0,0 +1,63 @@ +package io.reactivesocket.aeron.client; + +import io.reactivesocket.aeron.internal.Loggable; +import rx.functions.Action0; +import uk.co.real_logic.aeron.Subscription; + +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +class PollingAction implements Action0, Loggable { + + private final ReentrantLock pollLock; + private final ReentrantLock clientActionLock; + private final int threadId; + private final List subscriptionGroups; + private final List clientActions; + + public PollingAction( + ReentrantLock pollLock, + ReentrantLock clientActionLock, + int threadId, + List subscriptionGroups, + List clientActions) { + this.pollLock = pollLock; + this.clientActionLock = clientActionLock; + this.threadId = threadId; + this.subscriptionGroups = subscriptionGroups; + this.clientActions = clientActions; + } + + @Override + public void call() { + try { + for (ClientAeronManager.SubscriptionGroup sg : subscriptionGroups) { + try { + if (pollLock.tryLock()) { + Subscription subscription = sg.getSubscriptions()[threadId]; + subscription.poll(sg.getFragmentAssembler(threadId), Integer.MAX_VALUE); + } + + if (clientActionLock.tryLock()) { + for (ClientAeronManager.ClientAction action : clientActions) { + action.call(threadId); + } + } + } catch (Throwable t) { + error("error polling aeron subscription on thread with id " + threadId, t); + } finally { + if (pollLock.isHeldByCurrentThread()) { + pollLock.unlock(); + } + + if (clientActionLock.isHeldByCurrentThread()) { + clientActionLock.unlock(); + } + } + } + + } catch (Throwable t) { + error("error in client polling loop on thread with id " + threadId, t); + } + } +} diff --git a/src/main/java/io/reactivesocket/aeron/client/ReactiveSocketAeronClient.java b/src/main/java/io/reactivesocket/aeron/client/ReactiveSocketAeronClient.java deleted file mode 100644 index 7f10af2d4..000000000 --- a/src/main/java/io/reactivesocket/aeron/client/ReactiveSocketAeronClient.java +++ /dev/null @@ -1,329 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.aeron.client; - -import io.reactivesocket.ConnectionSetupPayload; -import io.reactivesocket.Frame; -import io.reactivesocket.Payload; -import io.reactivesocket.ReactiveSocket; -import io.reactivesocket.aeron.internal.AeronUtil; -import io.reactivesocket.aeron.internal.Loggable; -import io.reactivesocket.aeron.internal.MessageType; -import io.reactivesocket.rx.Observer; -import org.reactivestreams.Publisher; -import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.aeron.logbuffer.Header; -import uk.co.real_logic.agrona.BitUtil; -import uk.co.real_logic.agrona.DirectBuffer; -import uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue; -import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; - -import java.nio.ByteBuffer; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentSkipListMap; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.LockSupport; - -import static io.reactivesocket.aeron.internal.Constants.CLIENT_STREAM_ID; -import static io.reactivesocket.aeron.internal.Constants.CONCURRENCY; -import static io.reactivesocket.aeron.internal.Constants.EMTPY; -import static io.reactivesocket.aeron.internal.Constants.SERVER_STREAM_ID; - -/** - * Class that exposes ReactiveSocket over Aeron - * - */ -public class ReactiveSocketAeronClient implements Loggable, AutoCloseable { - - static final ConcurrentSkipListMap connections = new ConcurrentSkipListMap<>(); - - static final ConcurrentHashMap establishConnectionLatches = new ConcurrentHashMap<>(); - - static final ConcurrentHashMap publications = new ConcurrentHashMap<>(); - - static final ConcurrentHashMap reactiveSockets = new ConcurrentHashMap<>(); - - static ClientAeronManager manager = ClientAeronManager.getInstance(); - - final int sessionId; - - //For Test - ReactiveSocketAeronClient() { - sessionId = -1; - } - - /** - * Creates a new ReactivesocketAeronClient - * - * @param host the host name that client is listening on - * @param server the host of the server that client will send data too - * @param port the port to send and receive data on - */ - private ReactiveSocketAeronClient(String host, String server, int port) { - final String channel = "udp://" + host + ":" + port; - final String subscriptionChannel = "udp://" + server + ":" + port; - - debug("Creating a publication to channel => {}", channel); - Publication publication = manager.getAeron().addPublication(channel, SERVER_STREAM_ID); - publications.putIfAbsent(publication.sessionId(), publication); - sessionId = publication.sessionId(); - debug("Created a publication with sessionId => {}", sessionId); - - manager.addSubscription(subscriptionChannel, CLIENT_STREAM_ID, (Integer threadId) -> - new ClientAeronManager.ThreadIdAwareFragmentHandler(threadId) { - @Override - public void onFragment(DirectBuffer buffer, int offset, int length, Header header) { - fragmentHandler(getThreadId(), buffer, offset, length, header); - } - } - ); - - establishConnection(publication, sessionId); - } - - void fragmentHandler(int threadId, DirectBuffer buffer, int offset, int length, Header header) { - try { - short messageCount = buffer.getShort(offset); - short messageTypeInt = buffer.getShort(offset + BitUtil.SIZE_OF_SHORT); - final int currentThreadId = Math.abs(messageCount % CONCURRENCY); - - if (currentThreadId != threadId) { - return; - } - - final MessageType messageType = MessageType.from(messageTypeInt); - if (messageType == MessageType.FRAME) { - final AeronClientDuplexConnection connection = connections.get(header.sessionId()); - if (connection != null) { - final List> subscribers = connection.getSubscriber(); - if (!subscribers.isEmpty()) { - //TODO think about how to recycle these, hard because could be handed to another thread I think? - final ByteBuffer bytes = ByteBuffer.allocate(length); - buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); - final Frame frame = Frame.from(bytes); - int i = 0; - final int size = subscribers.size(); - do { - Observer frameObserver = subscribers.get(i); - frameObserver.onNext(frame); - - i++; - } while (i < size); - } - } else { - debug("no connection found for Aeron Session Id {}", sessionId); - } - } else if (messageType == MessageType.ESTABLISH_CONNECTION_RESPONSE) { - final int ackSessionId = buffer.getInt(offset + BitUtil.SIZE_OF_INT); - - CountDownLatch latch = establishConnectionLatches.remove(ackSessionId); - - if (latch == null) { - return; - } - - Publication publication = publications.get(ackSessionId); - final int serverSessionId = header.sessionId(); - debug("Received establish connection ack for session id => {}, and server session id => {}", ackSessionId, serverSessionId); - final AeronClientDuplexConnection connection = - connections - .computeIfAbsent(serverSessionId, (_p) -> - new AeronClientDuplexConnection(publication)); - - ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection( - connection, - ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS), - err -> err.printStackTrace()); - - reactiveSocket.startAndWait(); - - reactiveSockets.putIfAbsent(ackSessionId, reactiveSocket); - - ReactiveSocketAeronClientAction clientAction - = new ReactiveSocketAeronClientAction(ackSessionId, reactiveSocket, connection); - - manager - .find(header.sessionId()) - .ifPresent(sg -> - sg - .getClientActions() - .add(clientAction) - ); - - latch.countDown(); - - debug("ReactiveSocket connected to Aeron session => " + ackSessionId); - } else { - debug("Unknown message type => " + messageTypeInt); - } - } catch (Throwable t) { - error("error handling framement", t); - } - } - - static class ReactiveSocketAeronClientAction extends ClientAeronManager.ClientAction implements Loggable { - private final ReactiveSocket reactiveSocket; - private final AeronClientDuplexConnection connection; - - public ReactiveSocketAeronClientAction(int sessionId, ReactiveSocket reactiveSocket, AeronClientDuplexConnection connection) { - super(sessionId); - this.reactiveSocket = reactiveSocket; - this.connection = connection; - } - - @Override - void call(int threadId) { - final boolean traceEnabled = isTraceEnabled(); - ManyToOneConcurrentArrayQueue framesSendQueue = connection.getFramesSendQueue(); - - framesSendQueue - .drain((FrameHolder fh) -> { - try { - Frame frame = fh.getFrame(); - - final ByteBuffer byteBuffer = frame.getByteBuffer(); - final int length = byteBuffer.capacity() + BitUtil.SIZE_OF_INT; - Publication publication = publications.get(id); - AeronUtil.tryClaimOrOffer(publication, (offset, buffer) -> { - - if (traceEnabled) { - trace("Thread Id {} and connection Id {} sending Frame => {} on Aeron", threadId, connection.getConnectionId(), frame.toString()); - } - - buffer.putShort(offset, (short) 0); - buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); - buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, frame.offset(), frame.length()); - }, length); - - fh.release(); - - } catch (Throwable t) { - fh.release(); - error("error draining send frame queue", t); - } - }); - //} - } - - @Override - public void close() throws Exception { - manager - .find(id) - .ifPresent(sg -> sg.getClientActions().remove(this)); - - reactiveSocket.close(); - connection.close(); - } - } - - /** - * Establishes a connection between the client and server. Waits for 30 seconds before throwing a exception. - */ - void establishConnection(final Publication publication, final int sessionId) { - - try { - final UnsafeBuffer buffer = new UnsafeBuffer(EMTPY); - buffer.wrap(new byte[BitUtil.SIZE_OF_INT]); - buffer.putShort(0, (short) 0); - buffer.putShort(BitUtil.SIZE_OF_SHORT, (short) MessageType.ESTABLISH_CONNECTION_REQUEST.getEncodedType()); - - CountDownLatch latch = new CountDownLatch(1); - establishConnectionLatches.put(sessionId, latch); - - long offer = -1; - final long start = System.nanoTime(); - for (;;) { - final long current = System.nanoTime(); - if ((current - start) > TimeUnit.SECONDS.toNanos(30)) { - throw new RuntimeException("Timed out waiting to establish connection for session id => " + sessionId); - } - - if (offer < 0) { - offer = publication.offer(buffer); - } - LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); - - if (latch.getCount() == 0) { - break; - } - } - - debug("Connection established for channel => {}, stream id => {}", - publication.channel(), - publication.sessionId()); - } finally { - establishConnectionLatches.remove(sessionId); - } - - } - - @Override - public void close() throws Exception { - manager.removeClientAction(sessionId); - - Publication publication = publications.remove(sessionId); - - if (publication != null) { - try { - - AeronUtil.tryClaimOrOffer(publication, (offset, buffer) -> { - buffer.putShort(offset, (short) 0); - buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.CONNECTION_DISCONNECT.getEncodedType()); - }, BitUtil.SIZE_OF_INT); - } catch (Throwable t) { - debug("error closing publication with session id => {}", publication.sessionId()); - } - publication.close(); - } - } - - /* - * ReactiveSocket methods - */ - public Publisher requestResponse(Payload payload) { - ReactiveSocket reactiveSocket = reactiveSockets.get(sessionId); - return reactiveSocket.requestResponse(payload); - } - - public Publisher fireAndForget(Payload payload) { - ReactiveSocket reactiveSocket = reactiveSockets.get(sessionId); - return reactiveSocket.fireAndForget(payload); - } - - public Publisher requestStream(Payload payload) { - ReactiveSocket reactiveSocket = reactiveSockets.get(sessionId); - return reactiveSocket.requestStream(payload); - } - - public Publisher requestSubscription(Payload payload) { - ReactiveSocket reactiveSocket = reactiveSockets.get(sessionId); - return reactiveSocket.requestSubscription(payload); - } - - /* - * Factory Methods - */ - public static ReactiveSocketAeronClient create(String host, String server, int port) { - return new ReactiveSocketAeronClient(host, server, port); - } - - public static ReactiveSocketAeronClient create(String host, String server) { - return create(host, server, 39790); - } - -} diff --git a/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java b/src/main/java/io/reactivesocket/aeron/old/server/AeronServerDuplexConnection.java similarity index 99% rename from src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java rename to src/main/java/io/reactivesocket/aeron/old/server/AeronServerDuplexConnection.java index 0dd311955..738e5f639 100644 --- a/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java +++ b/src/main/java/io/reactivesocket/aeron/old/server/AeronServerDuplexConnection.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.reactivesocket.aeron.server; +package io.reactivesocket.aeron.old.server; import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; diff --git a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java b/src/main/java/io/reactivesocket/aeron/old/server/ReactiveSocketAeronServer.java similarity index 99% rename from src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java rename to src/main/java/io/reactivesocket/aeron/old/server/ReactiveSocketAeronServer.java index b92c377e3..3e93d634c 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java +++ b/src/main/java/io/reactivesocket/aeron/old/server/ReactiveSocketAeronServer.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.reactivesocket.aeron.server; +package io.reactivesocket.aeron.old.server; import io.reactivesocket.ConnectionSetupHandler; import io.reactivesocket.Frame; diff --git a/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java b/src/main/java/io/reactivesocket/aeron/old/server/ServerAeronManager.java similarity index 99% rename from src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java rename to src/main/java/io/reactivesocket/aeron/old/server/ServerAeronManager.java index 6db9b6789..ddea088c4 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java +++ b/src/main/java/io/reactivesocket/aeron/old/server/ServerAeronManager.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.reactivesocket.aeron.server; +package io.reactivesocket.aeron.old.server; import io.reactivesocket.aeron.internal.Loggable; import uk.co.real_logic.aeron.Aeron; diff --git a/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java b/src/main/java/io/reactivesocket/aeron/old/server/ServerSubscription.java similarity index 96% rename from src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java rename to src/main/java/io/reactivesocket/aeron/old/server/ServerSubscription.java index 2e7af781c..256514dd6 100644 --- a/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java +++ b/src/main/java/io/reactivesocket/aeron/old/server/ServerSubscription.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.reactivesocket.aeron.server; +package io.reactivesocket.aeron.old.server; import io.reactivesocket.aeron.internal.Loggable; import io.reactivesocket.rx.Completable; @@ -31,7 +31,7 @@ * Subscription used by the AeronServerDuplexConnection to handle incoming frames and send them * on a publication. * - * @see io.reactivesocket.aeron.server.AeronServerDuplexConnection + * @see io.reactivesocket.aeron.old.server.AeronServerDuplexConnection */ class ServerSubscription implements Subscriber, Loggable { diff --git a/src/perf/java/io/reactivesocket/aeron/old/client/PollingActionPerf.java b/src/perf/java/io/reactivesocket/aeron/old/client/PollingActionPerf.java new file mode 100644 index 000000000..14e25adbf --- /dev/null +++ b/src/perf/java/io/reactivesocket/aeron/old/client/PollingActionPerf.java @@ -0,0 +1,99 @@ +package io.reactivesocket.aeron.old.client; + + +import io.reactivesocket.aeron.client.ClientAeronManager; +import io.reactivesocket.aeron.client.PollingAction; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import rx.functions.Func1; +import uk.co.real_logic.aeron.DummySubscription; +import uk.co.real_logic.aeron.Subscription; +import uk.co.real_logic.aeron.logbuffer.Header; +import uk.co.real_logic.agrona.DirectBuffer; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.ReentrantLock; + +public class PollingActionPerf { + + @State(Scope.Benchmark) + public static class TestState { + PollingAction pa; + + AtomicLong counter = new AtomicLong(); + + @Setup + public void init() { + ClientAeronManager.SubscriptionGroup sg + = new ClientAeronManager + .SubscriptionGroup("foo", + new Subscription[]{new DummySubscription()}, new Func1() { + @Override + public ClientAeronManager.ThreadIdAwareFragmentHandler call(Integer integer) { + return new ClientAeronManager.ThreadIdAwareFragmentHandler(0) { + @Override + public void onFragment(DirectBuffer buffer, int offset, int length, Header header) { + counter.getAndIncrement(); + } + }; + } + }); + + // 5 connections .... + for (int i = 0; i < 5; i++) { + sg.getClientActions().add(new ClientAeronManager.ClientAction(0) { + @Override + void call(int threadId) { + counter.getAndIncrement(); + } + + @Override + public void close() throws Exception { + + } + }); + } + + List group = new CopyOnWriteArrayList<>(); + group.add(sg); + + pa = new PollingAction(0, new ReentrantLock(), new ReentrantLock(), group); + } + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + @Threads(1) + public void call1(TestState state) { + state.pa.call(); + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + @Threads(2) + public void call2(TestState state) { + state.pa.call(); + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + @Threads(3) + public void call3(TestState state) { + state.pa.call(); + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + @Threads(4) + public void call4(TestState state) { + state.pa.call(); + } + +} diff --git a/src/perf/java/uk/co/real_logic/aeron/DummySubscription.java b/src/perf/java/uk/co/real_logic/aeron/DummySubscription.java new file mode 100644 index 000000000..2152128b8 --- /dev/null +++ b/src/perf/java/uk/co/real_logic/aeron/DummySubscription.java @@ -0,0 +1,58 @@ +package uk.co.real_logic.aeron; + + +import uk.co.real_logic.aeron.logbuffer.BlockHandler; +import uk.co.real_logic.aeron.logbuffer.FileBlockHandler; +import uk.co.real_logic.aeron.logbuffer.FragmentHandler; + +import java.util.List; + +public class DummySubscription extends Subscription { + DummySubscription(ClientConductor conductor, String channel, int streamId, long registrationId) { + super(conductor, channel, streamId, registrationId); + } + + public DummySubscription() { + super(null, null, 0, 0); + } + + @Override + public String channel() { + return ""; + } + + @Override + public int streamId() { + return 0; + } + + @Override + public int poll(FragmentHandler fragmentHandler, int fragmentLimit) { + return 0; + } + + @Override + public long blockPoll(BlockHandler blockHandler, int blockLengthLimit) { + return 0; + } + + @Override + public long filePoll(FileBlockHandler fileBlockHandler, int blockLengthLimit) { + return 0; + } + + @Override + public Image getImage(int sessionId) { + return null; + } + + @Override + public List images() { + return null; + } + + @Override + public void close() { + + } +} diff --git a/src/test/java/io/reactivesocket/aeron/client/MediaDriver.java b/src/test/java/io/reactivesocket/aeron/old/client/MediaDriver.java similarity index 97% rename from src/test/java/io/reactivesocket/aeron/client/MediaDriver.java rename to src/test/java/io/reactivesocket/aeron/old/client/MediaDriver.java index 9a527635d..dd67d9351 100644 --- a/src/test/java/io/reactivesocket/aeron/client/MediaDriver.java +++ b/src/test/java/io/reactivesocket/aeron/old/client/MediaDriver.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.reactivesocket.aeron.client; +package io.reactivesocket.aeron.old.client; import uk.co.real_logic.aeron.driver.ThreadingMode; import uk.co.real_logic.agrona.concurrent.BackoffIdleStrategy; diff --git a/src/test/java/io/reactivesocket/aeron/client/Ping.java b/src/test/java/io/reactivesocket/aeron/old/client/Ping.java similarity index 77% rename from src/test/java/io/reactivesocket/aeron/client/Ping.java rename to src/test/java/io/reactivesocket/aeron/old/client/Ping.java index 207c51e57..a9404a65e 100644 --- a/src/test/java/io/reactivesocket/aeron/client/Ping.java +++ b/src/test/java/io/reactivesocket/aeron/old/client/Ping.java @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.reactivesocket.aeron.client; +package io.reactivesocket.aeron.old.client; -import com.codahale.metrics.ConsoleReporter; -import com.codahale.metrics.MetricRegistry; -import com.codahale.metrics.Timer; import io.reactivesocket.Payload; +import io.reactivesocket.aeron.client.FrameHolder; +import org.HdrHistogram.Recorder; import rx.Observable; import rx.RxReactiveStreams; import rx.Subscriber; +import rx.schedulers.Schedulers; import java.nio.ByteBuffer; import java.util.Random; @@ -49,6 +49,7 @@ public static void main(String... args) throws Exception { System.out.println("Sending data of size => " + key.length); + /* final MetricRegistry metrics = new MetricRegistry(); final Timer timer = metrics.timer("pingTimer"); @@ -56,12 +57,28 @@ public static void main(String... args) throws Exception { .convertRatesTo(TimeUnit.SECONDS) .convertDurationsTo(TimeUnit.MICROSECONDS) .build(); - reporter.start(15, TimeUnit.SECONDS); + reporter.start(15, TimeUnit.SECONDS);*/ ReactiveSocketAeronClient client = ReactiveSocketAeronClient.create(host, server); CountDownLatch latch = new CountDownLatch(Integer.MAX_VALUE); + final Recorder histogram = new Recorder(3600000000000L, 3); + + Schedulers + .computation() + .createWorker() + .schedulePeriodically(() -> { + System.out.println("---- FRAME HOLDER HISTO ----"); + FrameHolder.histogram.getIntervalHistogram().outputPercentileDistribution(System.out, 5, 1000.0, false); + System.out.println("---- FRAME HOLDER HISTO ----"); + + System.out.println("---- PING/ PONG HISTO ----"); + histogram.getIntervalHistogram().outputPercentileDistribution(System.out, 5, 1000.0, false); + System.out.println("---- PING/ PONG HISTO ----"); + + + }, 10, 10, TimeUnit.SECONDS); Observable .range(1, Integer.MAX_VALUE) @@ -88,7 +105,7 @@ public ByteBuffer getMetadata() { .requestResponse(keyPayload)) .doOnNext(s -> { long diff = System.nanoTime() - start; - timer.update(diff, TimeUnit.NANOSECONDS); + histogram.recordValue(diff); }); }) .subscribe(new Subscriber() { diff --git a/src/test/java/io/reactivesocket/aeron/client/Pong.java b/src/test/java/io/reactivesocket/aeron/old/client/Pong.java similarity index 89% rename from src/test/java/io/reactivesocket/aeron/client/Pong.java rename to src/test/java/io/reactivesocket/aeron/old/client/Pong.java index db041fae3..615175a87 100644 --- a/src/test/java/io/reactivesocket/aeron/client/Pong.java +++ b/src/test/java/io/reactivesocket/aeron/old/client/Pong.java @@ -13,16 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.reactivesocket.aeron.client; +package io.reactivesocket.aeron.old.client; -import com.codahale.metrics.ConsoleReporter; -import com.codahale.metrics.MetricRegistry; -import com.codahale.metrics.Timer; import io.reactivesocket.ConnectionSetupHandler; import io.reactivesocket.ConnectionSetupPayload; import io.reactivesocket.Payload; import io.reactivesocket.RequestHandler; -import io.reactivesocket.aeron.server.ReactiveSocketAeronServer; +import io.reactivesocket.aeron.old.server.ReactiveSocketAeronServer; import io.reactivesocket.exceptions.SetupException; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; @@ -31,7 +28,6 @@ import java.nio.ByteBuffer; import java.util.Random; -import java.util.concurrent.TimeUnit; /** * Created by rroeser on 8/16/15. @@ -72,14 +68,14 @@ public static void main(String... args) { System.out.println("Sending data of size => " + response.length); - final MetricRegistry metrics = new MetricRegistry(); + /*final MetricRegistry metrics = new MetricRegistry(); final Timer timer = metrics.timer("pongTimer"); final ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics) .convertRatesTo(TimeUnit.SECONDS) .convertDurationsTo(TimeUnit.MILLISECONDS) .build(); - reporter.start(15, TimeUnit.SECONDS); + reporter.start(15, TimeUnit.SECONDS);*/ ReactiveSocketAeronServer server = ReactiveSocketAeronServer.create(host, 39790, new ConnectionSetupHandler() { @Override @@ -87,7 +83,7 @@ public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupExc return new RequestHandler() { @Override public Publisher handleRequestResponse(Payload payload) { - long time = System.nanoTime(); + long time = System.currentTimeMillis(); Publisher publisher = new Publisher() { @Override @@ -108,8 +104,8 @@ public ByteBuffer getMetadata() { s.onNext(responsePayload); - long diff = System.nanoTime() - time; - timer.update(diff, TimeUnit.NANOSECONDS); + long diff = System.currentTimeMillis() - time; + //timer.update(diff, TimeUnit.NANOSECONDS); s.onComplete(); } }; diff --git a/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/old/client/ReactiveSocketAeronTest.java similarity index 99% rename from src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java rename to src/test/java/io/reactivesocket/aeron/old/client/ReactiveSocketAeronTest.java index c15916ab6..9779f216c 100644 --- a/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/old/client/ReactiveSocketAeronTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.reactivesocket.aeron.client; +package io.reactivesocket.aeron.old.client; import io.reactivesocket.ConnectionSetupHandler; import io.reactivesocket.ConnectionSetupPayload; @@ -21,7 +21,7 @@ import io.reactivesocket.Payload; import io.reactivesocket.RequestHandler; import io.reactivesocket.aeron.TestUtil; -import io.reactivesocket.aeron.server.ReactiveSocketAeronServer; +import io.reactivesocket.aeron.old.server.ReactiveSocketAeronServer; import io.reactivesocket.exceptions.SetupException; import org.junit.BeforeClass; import org.junit.Ignore; From 96f7f0eb54a9ff40265b353712150c2c79db9c1a Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Wed, 30 Sep 2015 21:10:25 -0700 Subject: [PATCH 053/105] upgraded to aeron 0.1.4, created sub projects, refactored client to create to DuplexConnections via factory and no longer wrapper ReactiveSocket --- build.gradle | 95 ++++-- reactivesocket-aeron-client/build.gradle | 3 + .../client/AeronClientDuplexConnection.java | 16 +- .../AeronClientDuplexConnectionFactory.java | 127 +++++--- .../aeron/client/ClientAeronManager.java | 12 +- .../aeron/client/FrameHolder.java | 9 +- .../aeron/client/PollingAction.java | 11 +- .../aeron/example}/MediaDriver.java | 11 +- .../reactivesocket/aeron/example}/Ping.java | 30 +- .../reactivesocket/aeron/example}/Pong.java | 13 +- .../aeron/internal/AeronUtil.java | 2 +- .../aeron/internal/Constants.java | 2 +- .../aeron/internal/Loggable.java | 0 .../aeron/internal/MessageType.java | 0 .../aeron/internal/NotConnectedException.java | 0 .../server/AeronServerDuplexConnection.java | 57 +--- .../server/ReactiveSocketAeronServer.java | 9 +- .../aeron}/server/ServerAeronManager.java | 2 +- .../aeron}/server/ServerSubscription.java | 7 +- .../exceptions/SetupException.java | 0 reactivesocket-aeron-core/build.gradle | 0 .../aeron/internal/AeronUtil.java | 163 ++++++++++ .../aeron/internal/Constants.java | 41 +++ .../aeron/internal/Loggable.java | 53 ++++ .../aeron/internal/MessageType.java | 59 ++++ .../aeron/internal/NotConnectedException.java | 21 +- reactivesocket-aeron-examples/build.gradle | 4 + .../aeron/example/MediaDriver.java | 47 +++ .../io/reactivesocket/aeron/example/Ping.java | 141 +++++++++ .../io/reactivesocket/aeron/example/Pong.java | 136 +++++++++ .../main/resources/simplelogger.properties | 35 +++ reactivesocket-aeron-server/build.gradle | 3 + .../client/AeronClientDuplexConnection.java | 109 +++++++ .../AeronClientDuplexConnectionFactory.java | 262 +++++++++++++++++ .../aeron/client/ClientAeronManager.java | 220 ++++++++++++++ .../aeron/client/FrameHolder.java | 74 +++++ .../aeron/client/PollingAction.java | 64 ++++ .../aeron/example/MediaDriver.java | 47 +++ .../io/reactivesocket/aeron/example/Ping.java | 141 +++++++++ .../io/reactivesocket/aeron/example/Pong.java | 136 +++++++++ .../aeron/internal/AeronUtil.java | 163 ++++++++++ .../aeron/internal/Constants.java | 41 +++ .../aeron/internal/Loggable.java | 53 ++++ .../aeron/internal/MessageType.java | 59 ++++ .../aeron/internal/NotConnectedException.java | 25 ++ .../server/AeronServerDuplexConnection.java | 101 +++++++ .../server/ReactiveSocketAeronServer.java | 206 +++++++++++++ .../aeron/server/ServerAeronManager.java | 120 ++++++++ .../aeron/server/ServerSubscription.java | 101 +++++++ .../exceptions/SetupException.java | 23 ++ settings.gradle | 5 + .../AbstractConcurrentArrayQueue.java | 278 ------------------ .../ManyToManyConcurrentArrayQueue.java | 173 ----------- .../aeron/internal/concurrent/Pipe.java | 75 ----- .../{old => }/client/PollingActionPerf.java | 25 +- .../client/ReactiveSocketAeronTest.java | 131 ++++++++- src/test/resources/simplelogger.properties | 4 +- 57 files changed, 2995 insertions(+), 750 deletions(-) create mode 100644 reactivesocket-aeron-client/build.gradle rename {src => reactivesocket-aeron-client/src}/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java (88%) rename {src => reactivesocket-aeron-client/src}/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java (66%) rename {src => reactivesocket-aeron-client/src}/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java (92%) rename {src => reactivesocket-aeron-client/src}/main/java/io/reactivesocket/aeron/client/FrameHolder.java (92%) rename {src => reactivesocket-aeron-client/src}/main/java/io/reactivesocket/aeron/client/PollingAction.java (88%) rename {src/test/java/io/reactivesocket/aeron/old/client => reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/example}/MediaDriver.java (84%) rename {src/test/java/io/reactivesocket/aeron/old/client => reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/example}/Ping.java (75%) rename {src/test/java/io/reactivesocket/aeron/old/client => reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/example}/Pong.java (91%) rename {src => reactivesocket-aeron-client/src}/main/java/io/reactivesocket/aeron/internal/AeronUtil.java (99%) rename {src => reactivesocket-aeron-client/src}/main/java/io/reactivesocket/aeron/internal/Constants.java (96%) rename {src => reactivesocket-aeron-client/src}/main/java/io/reactivesocket/aeron/internal/Loggable.java (100%) rename {src => reactivesocket-aeron-client/src}/main/java/io/reactivesocket/aeron/internal/MessageType.java (100%) rename {src => reactivesocket-aeron-client/src}/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java (100%) rename {src/main/java/io/reactivesocket/aeron/old => reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron}/server/AeronServerDuplexConnection.java (65%) rename {src/main/java/io/reactivesocket/aeron/old => reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron}/server/ReactiveSocketAeronServer.java (96%) rename {src/main/java/io/reactivesocket/aeron/old => reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron}/server/ServerAeronManager.java (99%) rename {src/main/java/io/reactivesocket/aeron/old => reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron}/server/ServerSubscription.java (93%) rename {src => reactivesocket-aeron-client/src}/main/java/io/reactivesocket/exceptions/SetupException.java (100%) create mode 100644 reactivesocket-aeron-core/build.gradle create mode 100644 reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java create mode 100644 reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Constants.java create mode 100644 reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Loggable.java create mode 100644 reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/MessageType.java rename src/main/java/io/reactivesocket/aeron/internal/concurrent/QueuedPipe.java => reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java (63%) create mode 100644 reactivesocket-aeron-examples/build.gradle create mode 100644 reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/MediaDriver.java create mode 100644 reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/Ping.java create mode 100644 reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/Pong.java create mode 100644 reactivesocket-aeron-examples/src/main/resources/simplelogger.properties create mode 100644 reactivesocket-aeron-server/build.gradle create mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java create mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java create mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java create mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java create mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/PollingAction.java create mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/example/MediaDriver.java create mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/example/Ping.java create mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/example/Pong.java create mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java create mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/Constants.java create mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/Loggable.java create mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/MessageType.java create mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java create mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java create mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java create mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java create mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java create mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/exceptions/SetupException.java delete mode 100644 src/main/java/io/reactivesocket/aeron/internal/concurrent/AbstractConcurrentArrayQueue.java delete mode 100644 src/main/java/io/reactivesocket/aeron/internal/concurrent/ManyToManyConcurrentArrayQueue.java delete mode 100644 src/main/java/io/reactivesocket/aeron/internal/concurrent/Pipe.java rename src/perf/java/io/reactivesocket/aeron/{old => }/client/PollingActionPerf.java (72%) rename src/test/java/io/reactivesocket/aeron/{old => }/client/ReactiveSocketAeronTest.java (84%) diff --git a/build.gradle b/build.gradle index 3055b3a34..648625679 100644 --- a/build.gradle +++ b/build.gradle @@ -6,36 +6,89 @@ buildscript { dependencies { classpath 'io.reactivesocket:gradle-nebula-plugin-reactivesocket:1.0.1' } } + +subprojects { + apply plugin: 'java' + + repositories { + maven { url 'https://oss.jfrog.org/libs-snapshot' } + } + + dependencies { + compile 'io.reactivex:rxjava:1.0.13' + compile 'io.reactivex:rxjava-reactive-streams:1.0.1' + compile 'io.reactivesocket:reactivesocket:0.0.1-SNAPSHOT' + compile 'uk.co.real-logic:Agrona:0.4.4' + compile 'uk.co.real-logic:aeron-all:0.1.4' + compile 'org.hdrhistogram:HdrHistogram:2.1.7' + compile 'org.slf4j:slf4j-api:1.7.12' + testCompile 'junit:junit-dep:4.10' + testCompile 'org.mockito:mockito-core:1.8.5' + testCompile 'org.slf4j:slf4j-simple:1.7.12' + } +} + +/* +buildscript { + repositories { + jcenter() + } + + dependencies { classpath 'io.reactivesocket:gradle-nebula-plugin-reactivesocket:1.0.1' } +} + description = 'ReactiveSocket: stream oriented messaging passing with Reactive Stream semantics.' -apply plugin: 'reactivesocket-project' -apply plugin: 'java' repositories { maven { url 'https://oss.jfrog.org/libs-snapshot' } } -dependencies { - compile 'io.reactivex:rxjava:1.0.13' - compile 'io.reactivex:rxjava-reactive-streams:1.0.1' - compile 'io.reactivesocket:reactivesocket:0.0.1-SNAPSHOT' - compile 'uk.co.real-logic:Agrona:0.4.3' - compile 'uk.co.real-logic:aeron-all:0.1.3' - compile 'org.hdrhistogram:HdrHistogram:2.1.7' - compile 'org.slf4j:slf4j-api:1.7.12' - testCompile 'junit:junit-dep:4.10' - testCompile 'org.mockito:mockito-core:1.8.5' - testCompile 'org.slf4j:slf4j-simple:1.7.12' - testCompile 'io.dropwizard.metrics:metrics-core:3.1.2' +subprojects { + apply plugin: 'reactivesocket-project' + apply plugin: 'java' + + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + + dependencies { + compile 'io.reactivex:rxjava:1.0.13' + compile 'io.reactivex:rxjava-reactive-streams:1.0.1' + compile 'io.reactivesocket:reactivesocket:0.0.1-SNAPSHOT' + compile 'uk.co.real-logic:Agrona:0.4.4' + compile 'uk.co.real-logic:aeron-all:0.1.4' + compile 'org.hdrhistogram:HdrHistogram:2.1.7' + compile 'org.slf4j:slf4j-api:1.7.12' + testCompile 'junit:junit-dep:4.10' + testCompile 'org.mockito:mockito-core:1.8.5' + testCompile 'org.slf4j:slf4j-simple:1.7.12' + } + + + // support for snapshot/final releases via versioned branch names like 1.x + nebulaRelease { + addReleaseBranchPattern(/\d+\.\d+\.\d+/) + addReleaseBranchPattern('HEAD') + } + + if (project.hasProperty('release.useLastTag')) { + tasks.prepare.enabled = false + } } -// support for snapshot/final releases via versioned branch names like 1.x -nebulaRelease { - addReleaseBranchPattern(/\d+\.\d+\.\d+/) - addReleaseBranchPattern('HEAD') +task(md, dependsOn: 'classes', type: JavaExec) { + main = 'io.reactivesocket.aeron.example.MediaDriver' + classpath = sourceSets.main.runtimeClasspath } -if (project.hasProperty('release.useLastTag')) { - tasks.prepare.enabled = false -} \ No newline at end of file +task(ping, dependsOn: 'classes', type: JavaExec) { + main = 'io.reactivesocket.aeron.example.Ping' + classpath = sourceSets.main.runtimeClasspath +} + +task(pong, dependsOn: 'classes', type: JavaExec) { + main = 'io.reactivesocket.aeron.example.Pong' + classpath = sourceSets.main.runtimeClasspath +} + */ \ No newline at end of file diff --git a/reactivesocket-aeron-client/build.gradle b/reactivesocket-aeron-client/build.gradle new file mode 100644 index 000000000..72fb47b09 --- /dev/null +++ b/reactivesocket-aeron-client/build.gradle @@ -0,0 +1,3 @@ +dependencies { + compile project(':reactivesocket-aeron-core') +} diff --git a/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java similarity index 88% rename from src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java rename to reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java index 4acfd2c05..c5905d446 100644 --- a/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java +++ b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java @@ -2,7 +2,6 @@ import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; -import io.reactivesocket.aeron.internal.Constants; import io.reactivesocket.aeron.internal.Loggable; import io.reactivesocket.rx.Completable; import io.reactivesocket.rx.Disposable; @@ -12,7 +11,7 @@ import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue; +import uk.co.real_logic.agrona.concurrent.AbstractConcurrentArrayQueue; import java.io.IOException; import java.util.concurrent.CopyOnWriteArrayList; @@ -22,12 +21,12 @@ public class AeronClientDuplexConnection implements DuplexConnection, Loggable { private final Publication publication; private final CopyOnWriteArrayList> subjects; - private final ManyToOneConcurrentArrayQueue frameSendQueue; + private final AbstractConcurrentArrayQueue frameSendQueue; private final Consumer onClose; public AeronClientDuplexConnection( Publication publication, - ManyToOneConcurrentArrayQueue frameSendQueue, + AbstractConcurrentArrayQueue frameSendQueue, Consumer onClose) { this.publication = publication; this.subjects = new CopyOnWriteArrayList<>(); @@ -64,10 +63,11 @@ public void addOutput(Publisher o, Completable callback) { o .subscribe(new Subscriber() { private Subscription s; + @Override public void onSubscribe(Subscription s) { this.s = s; - s.request(Constants.QUEUE_SIZE); + s.request(Long.MAX_VALUE); } @@ -101,11 +101,9 @@ public void close() throws IOException { onClose.accept(publication); } - public ManyToOneConcurrentArrayQueue getFrameSendQueue() { - return frameSendQueue; - } - public CopyOnWriteArrayList> getSubjects() { return subjects; } + + } diff --git a/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java similarity index 66% rename from src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java rename to reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java index 18e63f1f9..d076dc8c9 100644 --- a/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java +++ b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java @@ -8,12 +8,12 @@ import io.reactivesocket.rx.Observer; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; import uk.co.real_logic.aeron.Publication; import uk.co.real_logic.aeron.logbuffer.Header; import uk.co.real_logic.agrona.BitUtil; import uk.co.real_logic.agrona.DirectBuffer; -import uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue; +import uk.co.real_logic.agrona.concurrent.ManyToManyConcurrentArrayQueue; +import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; import java.net.InetSocketAddress; import java.net.SocketAddress; @@ -21,6 +21,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import static io.reactivesocket.aeron.internal.Constants.CONCURRENCY; @@ -29,19 +30,20 @@ public final class AeronClientDuplexConnectionFactory implements Loggable { private static final AeronClientDuplexConnectionFactory instance = new AeronClientDuplexConnectionFactory(); - // Aeron Publication Session Id to ReactiveSocket DuplexConnection implementation + private static ThreadLocal buffers = ThreadLocal.withInitial(() -> new UnsafeBuffer(Constants.EMTPY)); + private final ConcurrentSkipListMap connections; // TODO - this should be configurable...enough space for 2048 DuplexConnections assuming request(128) - private final ManyToOneConcurrentArrayQueue frameSendQueue = new ManyToOneConcurrentArrayQueue<>(262144); + private final ManyToManyConcurrentArrayQueue frameSendQueue = new ManyToManyConcurrentArrayQueue<>(262144); - private final ConcurrentHashMap establishConnectionSubscribers; + private final ConcurrentHashMap establishConnectionHolders; private final ClientAeronManager manager; private AeronClientDuplexConnectionFactory() { connections = new ConcurrentSkipListMap<>(); - establishConnectionSubscribers = new ConcurrentHashMap<>(); + establishConnectionHolders = new ConcurrentHashMap<>(); manager = ClientAeronManager.getInstance(); manager.addClientAction(threadId -> { @@ -59,7 +61,7 @@ private AeronClientDuplexConnectionFactory() { AeronUtil .tryClaimOrOffer(publication, (offset, buffer) -> { if (traceEnabled) { - trace("Thread Id {} sending Frame => {} on Aeron", threadId, frame.toString()); + trace("Thread Id {} sending Frame => {} on Aeron", threadId, frame.toString()); } buffer.putShort(offset, (short) 0); @@ -75,19 +77,22 @@ public static AeronClientDuplexConnectionFactory getInstance() { } /** - * Adds a {@link java.net.SocketAddress} for Aeron to listen to Responses on + * Adds a {@link java.net.SocketAddress} for Aeron to listen for responses on * * @param socketAddress */ public void addSocketAddressToHandleResponses(SocketAddress socketAddress) { if (socketAddress instanceof InetSocketAddress) { addUDPSocketAddressToHandleResponses((InetSocketAddress) socketAddress); + } else { + throw new RuntimeException("unknown socket address type => " + socketAddress.getClass()); } - throw new RuntimeException("unknow socket address type => " + socketAddress.getClass()); } void addUDPSocketAddressToHandleResponses(InetSocketAddress socketAddress) { - String serverChannel = "udp://" + socketAddress.getHostName() + ":" + socketAddress.getPort(); + //String serverChannel = "udp://" + socketAddress.getHostName() + ":" + socketAddress.getPort(); + String serverChannel = "udp://localhost:39790"; + manager.addSubscription( serverChannel, @@ -104,22 +109,56 @@ public void onFragment(DirectBuffer buffer, int offset, int length, Header heade public Publisher createAeronClientDuplexConnection(SocketAddress socketAddress) { if (socketAddress instanceof InetSocketAddress) { return createUDPConnection((InetSocketAddress) socketAddress); + } else { + throw new RuntimeException("unknown socket address type => " + socketAddress.getClass()); } - - throw new RuntimeException("unknown socket address type => " + socketAddress.getClass()); } Publisher createUDPConnection(InetSocketAddress inetSocketAddress) { - Publisher publisher = subscriber -> { - final String channel = "udp://" + inetSocketAddress.getHostName() + ":" + inetSocketAddress.getPort(); - debug("Creating a publication to channel => {}", channel); - Publication publication = manager.getAeron().addPublication(channel, SERVER_STREAM_ID); - debug("Created a publication with sessionId => {} to channel => {}", publication.sessionId(), channel); - EstablishConnectionSubscriber establishConnectionSubscriber = new EstablishConnectionSubscriber(publication, subscriber); - establishConnectionSubscribers.put(publication.sessionId(), establishConnectionSubscriber); + final String channel = "udp://" + inetSocketAddress.getHostName() + ":" + inetSocketAddress.getPort(); + debug("Creating a publication to channel => {}", channel); + final Publication publication = manager.getAeron().addPublication(channel, SERVER_STREAM_ID); + debug("Created a publication with sessionId => {} to channel => {}", publication.sessionId(), channel); + + return subscriber -> { + EstablishConnectionHolder establishConnectionHolder = new EstablishConnectionHolder(publication, subscriber); + establishConnectionHolders.putIfAbsent(publication.sessionId(), establishConnectionHolder); + + establishConnection(publication); }; + } + + /** + * Establishes a connection between the client and server. Waits for 30 seconds before throwing a exception. + */ + void establishConnection(final Publication publication) { + final int sessionId = publication.sessionId(); + + debug("Establishing connection for channel => {}, stream id => {}", + publication.channel(), + publication.sessionId()); + + UnsafeBuffer buffer = buffers.get(); + buffer.wrap(new byte[BitUtil.SIZE_OF_INT]); + buffer.putShort(0, (short) 0); + buffer.putShort(BitUtil.SIZE_OF_SHORT, (short) MessageType.ESTABLISH_CONNECTION_REQUEST.getEncodedType()); + + long offer = -1; + final long start = System.nanoTime(); + for (;;) { + final long current = System.nanoTime(); + if ((current - start) > TimeUnit.SECONDS.toNanos(30)) { + throw new RuntimeException("Timed out waiting to establish connection for session id => " + sessionId); + } + + if (offer < 0) { + offer = publication.offer(buffer); + } else { + break; + } + + } - return publisher; } void fragmentHandler(int threadId, DirectBuffer buffer, int offset, int length, Header header) { @@ -156,11 +195,11 @@ void fragmentHandler(int threadId, DirectBuffer buffer, int offset, int length, } } else if (messageType == MessageType.ESTABLISH_CONNECTION_RESPONSE) { final int ackSessionId = buffer.getInt(offset + BitUtil.SIZE_OF_INT); - EstablishConnectionSubscriber establishConnectionSubscriber = establishConnectionSubscribers.get(ackSessionId); - if (establishConnectionSubscribers != null) { + EstablishConnectionHolder establishConnectionHolder = establishConnectionHolders.remove(ackSessionId); + if (establishConnectionHolder != null) { try { AeronClientDuplexConnection aeronClientDuplexConnection - = new AeronClientDuplexConnection(establishConnectionSubscriber.getPublication(), frameSendQueue, new Consumer() { + = new AeronClientDuplexConnection(establishConnectionHolder.getPublication(), frameSendQueue, new Consumer() { @Override public void accept(Publication publication) { connections.remove(publication.sessionId()); @@ -179,10 +218,17 @@ public void accept(Publication publication) { } } }); - establishConnectionSubscriber.onNext(aeronClientDuplexConnection); - establishConnectionSubscriber.onComplete(); + + connections.put(header.sessionId(), aeronClientDuplexConnection); + + establishConnectionHolder.getSubscriber().onNext(aeronClientDuplexConnection); + establishConnectionHolder.getSubscriber().onComplete(); + + debug("Connection established for channel => {}, stream id => {}", + establishConnectionHolder.getPublication().channel(), + establishConnectionHolder.getPublication().sessionId()); } catch (Throwable t) { - establishConnectionSubscriber.onError(t); + establishConnectionHolder.getSubscriber().onError(t); } } } else { @@ -196,38 +242,21 @@ public void accept(Publication publication) { /* * Inner Classes */ - class EstablishConnectionSubscriber implements Subscriber { + class EstablishConnectionHolder { private Publication publication; - private Subscriber child; + private Subscriber subscriber; - public EstablishConnectionSubscriber(Publication publication, Subscriber child) { + public EstablishConnectionHolder(Publication publication, Subscriber subscriber) { this.publication = publication; - this.child = child; + this.subscriber = subscriber; } public Publication getPublication() { return publication; } - @Override - public void onSubscribe(Subscription s) { - s.request(Long.MAX_VALUE); - establishConnectionSubscribers.put(publication.sessionId(), this); - } - - @Override - public void onNext(AeronClientDuplexConnection aeronClientDuplexConnection) { - onNext(aeronClientDuplexConnection); - } - - @Override - public void onError(Throwable t) { - onError(t); - } - - @Override - public void onComplete() { - child.onComplete(); + public Subscriber getSubscriber() { + return subscriber; } } } diff --git a/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java similarity index 92% rename from src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java rename to reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java index 238c7f9f7..30b71bd6f 100644 --- a/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java +++ b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java @@ -21,6 +21,7 @@ import rx.functions.Func1; import rx.schedulers.Schedulers; import uk.co.real_logic.aeron.Aeron; +import uk.co.real_logic.aeron.AvailableImageHandler; import uk.co.real_logic.aeron.FragmentAssembler; import uk.co.real_logic.aeron.Image; import uk.co.real_logic.aeron.Subscription; @@ -51,6 +52,12 @@ private ClientAeronManager() { final Aeron.Context ctx = new Aeron.Context(); ctx.errorHandler(t -> error("an exception occurred", t)); + ctx.availableImageHandler(new AvailableImageHandler() { + @Override + public void onAvailableImage(Image image, Subscription subscription, long joiningPosition, String sourceIdentity) { + debug("New image available with session id => {} and sourceIdentity => {} and subscription => {}", image.sessionId(), sourceIdentity, subscription.toString()); + } + }); aeron = Aeron.connect(ctx); @@ -116,12 +123,11 @@ public Aeron getAeron() { public void addSubscription(String subscriptionChannel, int streamId, Func1 fragmentHandlerFactory) { if (!hasSubscriptionForChannel(subscriptionChannel)) { - debug("Creating a subscriptions to channel => {}", subscriptionChannel); Subscription[] subscriptions = new Subscription[Constants.CONCURRENCY]; for (int i = 0; i < Constants.CONCURRENCY; i++) { subscriptions[i] = aeron.addSubscription(subscriptionChannel, streamId); - debug("Subscription created with session id => {} and threadId => {}", subscriptions[i].streamId(), i); + debug("Subscription created for threadId => {} and channel => {} ", i, subscriptionChannel); } SubscriptionGroup subscriptionGroup = new SubscriptionGroup(subscriptionChannel, subscriptions, fragmentHandlerFactory); subscriptionGroups.add(subscriptionGroup); @@ -145,7 +151,7 @@ void poll() { workers[threadId] = Schedulers.computation().createWorker(); workers[threadId].schedulePeriodically(new PollingAction(pollLock, clientActionLock, threadId, subscriptionGroups, clientActions), - 0, 1, TimeUnit.MICROSECONDS); + 0, 20, TimeUnit.MICROSECONDS); } } diff --git a/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java similarity index 92% rename from src/main/java/io/reactivesocket/aeron/client/FrameHolder.java rename to reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java index 98ab29f6e..5e910b0b7 100644 --- a/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java +++ b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java @@ -16,7 +16,6 @@ package io.reactivesocket.aeron.client; import io.reactivesocket.Frame; -import io.reactivesocket.aeron.internal.Constants; import org.HdrHistogram.Recorder; import org.reactivestreams.Subscription; import uk.co.real_logic.aeron.Publication; @@ -26,11 +25,11 @@ * Holds a frame and the publication that it's supposed to be sent on. * Pools instances on an {@link uk.co.real_logic.agrona.concurrent.OneToOneConcurrentArrayQueue} */ -class FrameHolder { +public class FrameHolder { private static final ThreadLocal> FRAME_HOLDER_QUEUE - = ThreadLocal.withInitial(() -> new OneToOneConcurrentArrayQueue<>(Constants.QUEUE_SIZE)); + = ThreadLocal.withInitial(() -> new OneToOneConcurrentArrayQueue<>(16)); - static final Recorder histogram = new Recorder(3600000000000L, 3); + public static final Recorder histogram = new Recorder(3600000000000L, 3); private Frame frame; private Publication publication; @@ -48,7 +47,7 @@ public static FrameHolder get(Frame frame, Publication publication, Subscription frameHolder.frame = frame; frameHolder.s = s; - + frameHolder.publication = publication; frameHolder.getTime = System.nanoTime(); return frameHolder; diff --git a/src/main/java/io/reactivesocket/aeron/client/PollingAction.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/PollingAction.java similarity index 88% rename from src/main/java/io/reactivesocket/aeron/client/PollingAction.java rename to reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/PollingAction.java index fbfc3564f..45cdffa8a 100644 --- a/src/main/java/io/reactivesocket/aeron/client/PollingAction.java +++ b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/PollingAction.java @@ -33,16 +33,17 @@ public void call() { try { for (ClientAeronManager.SubscriptionGroup sg : subscriptionGroups) { try { - if (pollLock.tryLock()) { + int poll; + do { Subscription subscription = sg.getSubscriptions()[threadId]; - subscription.poll(sg.getFragmentAssembler(threadId), Integer.MAX_VALUE); - } + poll = subscription.poll(sg.getFragmentAssembler(threadId), Integer.MAX_VALUE); + } while (poll > 0); - if (clientActionLock.tryLock()) { + //if (clientActionLock.tryLock()) { for (ClientAeronManager.ClientAction action : clientActions) { action.call(threadId); } - } + //} } catch (Throwable t) { error("error polling aeron subscription on thread with id " + threadId, t); } finally { diff --git a/src/test/java/io/reactivesocket/aeron/old/client/MediaDriver.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/example/MediaDriver.java similarity index 84% rename from src/test/java/io/reactivesocket/aeron/old/client/MediaDriver.java rename to reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/example/MediaDriver.java index dd67d9351..9dac34fc0 100644 --- a/src/test/java/io/reactivesocket/aeron/old/client/MediaDriver.java +++ b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/example/MediaDriver.java @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.reactivesocket.aeron.old.client; +package io.reactivesocket.aeron.example; import uk.co.real_logic.aeron.driver.ThreadingMode; import uk.co.real_logic.agrona.concurrent.BackoffIdleStrategy; +import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; /** * Created by rroeser on 8/16/15. @@ -27,7 +28,7 @@ public static void main(String... args) { boolean dedicated = Boolean.getBoolean("dedicated"); - if (true) { + if (dedicated) { threadingMode = ThreadingMode.DEDICATED; } @@ -36,9 +37,9 @@ public static void main(String... args) { final uk.co.real_logic.aeron.driver.MediaDriver.Context ctx = new uk.co.real_logic.aeron.driver.MediaDriver.Context() .threadingMode(threadingMode) .dirsDeleteOnStart(true) - .conductorIdleStrategy(new BackoffIdleStrategy(1, 1, 1, 1)) - .receiverIdleStrategy(new BackoffIdleStrategy(1, 1, 1, 1)) - .senderIdleStrategy(new BackoffIdleStrategy(1, 1, 1, 1)); + .conductorIdleStrategy(new BackoffIdleStrategy(1, 1, 100, 1000)) + .receiverIdleStrategy(new NoOpIdleStrategy()) + .senderIdleStrategy(new NoOpIdleStrategy()); final uk.co.real_logic.aeron.driver.MediaDriver ignored = uk.co.real_logic.aeron.driver.MediaDriver.launch(ctx); diff --git a/src/test/java/io/reactivesocket/aeron/old/client/Ping.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/example/Ping.java similarity index 75% rename from src/test/java/io/reactivesocket/aeron/old/client/Ping.java rename to reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/example/Ping.java index a9404a65e..a30943819 100644 --- a/src/test/java/io/reactivesocket/aeron/old/client/Ping.java +++ b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/example/Ping.java @@ -13,16 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.reactivesocket.aeron.old.client; +package io.reactivesocket.aeron.example; +import io.reactivesocket.ConnectionSetupPayload; import io.reactivesocket.Payload; +import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.aeron.client.AeronClientDuplexConnection; +import io.reactivesocket.aeron.client.AeronClientDuplexConnectionFactory; import io.reactivesocket.aeron.client.FrameHolder; import org.HdrHistogram.Recorder; +import org.reactivestreams.Publisher; import rx.Observable; import rx.RxReactiveStreams; import rx.Subscriber; import rx.schedulers.Schedulers; +import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.Random; import java.util.concurrent.CountDownLatch; @@ -49,17 +55,19 @@ public static void main(String... args) throws Exception { System.out.println("Sending data of size => " + key.length); - /* - final MetricRegistry metrics = new MetricRegistry(); - final Timer timer = metrics.timer("pingTimer"); + InetSocketAddress listenAddress = new InetSocketAddress("localhost", 39790); + InetSocketAddress clientAddress = new InetSocketAddress("localhost", 39790); - final ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics) - .convertRatesTo(TimeUnit.SECONDS) - .convertDurationsTo(TimeUnit.MICROSECONDS) - .build(); - reporter.start(15, TimeUnit.SECONDS);*/ + AeronClientDuplexConnectionFactory cf = AeronClientDuplexConnectionFactory.getInstance(); + cf.addSocketAddressToHandleResponses(listenAddress); + Publisher udpConnection = cf.createAeronClientDuplexConnection(clientAddress); - ReactiveSocketAeronClient client = ReactiveSocketAeronClient.create(host, server); + System.out.println("Creating new duplex connection"); + AeronClientDuplexConnection connection = RxReactiveStreams.toObservable(udpConnection).toBlocking().single(); + System.out.println("Created duplex connection"); + + ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection(connection, ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS)); + reactiveSocket.startAndWait(); CountDownLatch latch = new CountDownLatch(Integer.MAX_VALUE); @@ -101,7 +109,7 @@ public ByteBuffer getMetadata() { return RxReactiveStreams .toObservable( - client + reactiveSocket .requestResponse(keyPayload)) .doOnNext(s -> { long diff = System.nanoTime() - start; diff --git a/src/test/java/io/reactivesocket/aeron/old/client/Pong.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/example/Pong.java similarity index 91% rename from src/test/java/io/reactivesocket/aeron/old/client/Pong.java rename to reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/example/Pong.java index 615175a87..a8edd4cc3 100644 --- a/src/test/java/io/reactivesocket/aeron/old/client/Pong.java +++ b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/example/Pong.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.reactivesocket.aeron.old.client; +package io.reactivesocket.aeron.example; import io.reactivesocket.ConnectionSetupHandler; import io.reactivesocket.ConnectionSetupPayload; import io.reactivesocket.Payload; import io.reactivesocket.RequestHandler; -import io.reactivesocket.aeron.old.server.ReactiveSocketAeronServer; +import io.reactivesocket.aeron.server.ReactiveSocketAeronServer; import io.reactivesocket.exceptions.SetupException; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; @@ -68,15 +68,6 @@ public static void main(String... args) { System.out.println("Sending data of size => " + response.length); - /*final MetricRegistry metrics = new MetricRegistry(); - final Timer timer = metrics.timer("pongTimer"); - - final ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics) - .convertRatesTo(TimeUnit.SECONDS) - .convertDurationsTo(TimeUnit.MILLISECONDS) - .build(); - reporter.start(15, TimeUnit.SECONDS);*/ - ReactiveSocketAeronServer server = ReactiveSocketAeronServer.create(host, 39790, new ConnectionSetupHandler() { @Override public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { diff --git a/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java similarity index 99% rename from src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java rename to reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java index 30c4c8576..29ed9fa48 100644 --- a/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java +++ b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java @@ -26,7 +26,7 @@ /** * Utils for dealing with Aeron */ -public class AeronUtil { +public class AeronUtil implements Loggable { private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); diff --git a/src/main/java/io/reactivesocket/aeron/internal/Constants.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/Constants.java similarity index 96% rename from src/main/java/io/reactivesocket/aeron/internal/Constants.java rename to reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/Constants.java index fa2e91057..19a5375cf 100644 --- a/src/main/java/io/reactivesocket/aeron/internal/Constants.java +++ b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/Constants.java @@ -28,7 +28,7 @@ private Constants() {} public static final byte[] EMTPY = new byte[0]; - public static final int QUEUE_SIZE = Integer.getInteger("reactivesocket.aeron.framesSendQueueSize", 128); + public static final int QUEUE_SIZE = Integer.getInteger("reactivesocket.aeron.framesSendQueueSize", 262144); public static final IdleStrategy SERVER_IDLE_STRATEGY = new NoOpIdleStrategy(); diff --git a/src/main/java/io/reactivesocket/aeron/internal/Loggable.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/Loggable.java similarity index 100% rename from src/main/java/io/reactivesocket/aeron/internal/Loggable.java rename to reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/Loggable.java diff --git a/src/main/java/io/reactivesocket/aeron/internal/MessageType.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/MessageType.java similarity index 100% rename from src/main/java/io/reactivesocket/aeron/internal/MessageType.java rename to reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/MessageType.java diff --git a/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java similarity index 100% rename from src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java rename to reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java diff --git a/src/main/java/io/reactivesocket/aeron/old/server/AeronServerDuplexConnection.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java similarity index 65% rename from src/main/java/io/reactivesocket/aeron/old/server/AeronServerDuplexConnection.java rename to reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java index 738e5f639..3aa8a611c 100644 --- a/src/main/java/io/reactivesocket/aeron/old/server/AeronServerDuplexConnection.java +++ b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.reactivesocket.aeron.old.server; +package io.reactivesocket.aeron.server; import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; @@ -26,11 +26,9 @@ import io.reactivesocket.rx.Observable; import io.reactivesocket.rx.Observer; import org.reactivestreams.Publisher; -import rx.RxReactiveStreams; import uk.co.real_logic.aeron.Publication; import uk.co.real_logic.agrona.BitUtil; -import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; @@ -73,67 +71,26 @@ public void dispose() { }; } - private static volatile short count; - - - private short getCount() { - return count++; - } - @Override public void addOutput(Publisher o, Completable callback) { - RxReactiveStreams.toObservable(o).flatMap(frame -> - { - - if (isTraceEnabled()) { - trace("Server with publication session id {} sending frame => {}", publication.sessionId(), frame.toString()); - } - - final ByteBuffer byteBuffer = frame.getByteBuffer(); - final int length = frame.length() + BitUtil.SIZE_OF_INT; - - try { - AeronUtil.tryClaimOrOffer(publication, (offset, buffer) -> { - buffer.putShort(offset, getCount()); - buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); - buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, frame.offset(), frame.length()); - }, length); - } catch (Throwable t) { - return rx.Observable.error(t); - } - - if (isTraceEnabled()) { - trace("Server with publication session id {} sent frame with ReactiveSocket stream id => {}", publication.sessionId(), frame.getStreamId()); - } - - - return rx.Observable.empty(); - } - ) - .doOnCompleted(()-> System.out.println("-----ehere-----")). - subscribe(v -> { - }, callback::error, callback::success); - - - - //o.subscribe(new ServerSubscription(publication, callback)); + o.subscribe(new ServerSubscription(publication, callback)); } + // TODO - this is bad - I need to queue this up somewhere and process this on the polling thread so it doesn't just block everything void ackEstablishConnection(int ackSessionId) { debug("Acking establish connection for session id => {}", ackSessionId); - for (int i = 0; i < 10; i++) { + for (;;) { try { AeronUtil.tryClaimOrOffer(publication, (offset, buffer) -> { buffer.putShort(offset, (short) 0); buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.ESTABLISH_CONNECTION_RESPONSE.getEncodedType()); buffer.putInt(offset + BitUtil.SIZE_OF_INT, ackSessionId); }, 2 * BitUtil.SIZE_OF_INT, 30, TimeUnit.SECONDS); - break; + debug("Ack sent for session i => {}", ackSessionId); } catch (NotConnectedException ne) { - if (i >= 10) { - throw ne; - } + continue; } + break; } } diff --git a/src/main/java/io/reactivesocket/aeron/old/server/ReactiveSocketAeronServer.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java similarity index 96% rename from src/main/java/io/reactivesocket/aeron/old/server/ReactiveSocketAeronServer.java rename to reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java index 3e93d634c..ee678e4c6 100644 --- a/src/main/java/io/reactivesocket/aeron/old/server/ReactiveSocketAeronServer.java +++ b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.reactivesocket.aeron.old.server; +package io.reactivesocket.aeron.server; import io.reactivesocket.ConnectionSetupHandler; import io.reactivesocket.Frame; @@ -87,7 +87,7 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) final Frame frame = Frame.from(buffer, BitUtil.SIZE_OF_INT + offset, length); if (isTraceEnabled()) { - trace("---server received frame payload {} on session id {}", frame.getData(), sessionId); + trace("server received frame payload {} on session id {}", frame.getData(), sessionId); } subscribers.forEach(s -> { @@ -127,7 +127,8 @@ void availableImageHandler(Image image, Subscription subscription, long joiningP final AeronServerDuplexConnection connection = connections.computeIfAbsent(sessionId, (_s) -> { final String responseChannel = "udp://" + sourceIdentity.substring(0, sourceIdentity.indexOf(':')) + ":" + port; Publication publication = manager.getAeron().addPublication(responseChannel, CLIENT_STREAM_ID); - debug("Creating new connection for responseChannel => {}, streamId => {}, and sessionId => {}", responseChannel, streamId, sessionId); + int responseSessionId = publication.sessionId(); + debug("Creating new connection for responseChannel => {}, streamId => {}, and sessionId => {}", responseChannel, streamId, responseSessionId); return new AeronServerDuplexConnection(publication); }); debug("Accepting ReactiveSocket connection"); @@ -195,7 +196,7 @@ public static ReactiveSocketAeronServer create(String host, int port, Connection } public static ReactiveSocketAeronServer create(int port, ConnectionSetupHandler connectionSetupHandler) { - return create("127.0.0.1", port, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); + return create("localhost", port, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); } public static ReactiveSocketAeronServer create(ConnectionSetupHandler connectionSetupHandler) { diff --git a/src/main/java/io/reactivesocket/aeron/old/server/ServerAeronManager.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java similarity index 99% rename from src/main/java/io/reactivesocket/aeron/old/server/ServerAeronManager.java rename to reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java index ddea088c4..6db9b6789 100644 --- a/src/main/java/io/reactivesocket/aeron/old/server/ServerAeronManager.java +++ b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.reactivesocket.aeron.old.server; +package io.reactivesocket.aeron.server; import io.reactivesocket.aeron.internal.Loggable; import uk.co.real_logic.aeron.Aeron; diff --git a/src/main/java/io/reactivesocket/aeron/old/server/ServerSubscription.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java similarity index 93% rename from src/main/java/io/reactivesocket/aeron/old/server/ServerSubscription.java rename to reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java index 256514dd6..d7e6c8c99 100644 --- a/src/main/java/io/reactivesocket/aeron/old/server/ServerSubscription.java +++ b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.reactivesocket.aeron.old.server; +package io.reactivesocket.aeron.server; import io.reactivesocket.aeron.internal.Loggable; import io.reactivesocket.rx.Completable; @@ -31,7 +31,7 @@ * Subscription used by the AeronServerDuplexConnection to handle incoming frames and send them * on a publication. * - * @see io.reactivesocket.aeron.old.server.AeronServerDuplexConnection + * @see AeronServerDuplexConnection */ class ServerSubscription implements Subscriber, Loggable { @@ -51,7 +51,7 @@ public ServerSubscription(Publication publication, Completable completable) { @Override public void onSubscribe(Subscription s) { - s.request(1); + s.request(Long.MAX_VALUE); } @Override @@ -69,7 +69,6 @@ public void onNext(Frame frame) { buffer.putShort(offset, getCount()); buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, frame.offset(), frame.length()); - System.out.println("WOOOHOOOO +> " + System.currentTimeMillis()); }, length); } catch (Throwable t) { onError(t); diff --git a/src/main/java/io/reactivesocket/exceptions/SetupException.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/exceptions/SetupException.java similarity index 100% rename from src/main/java/io/reactivesocket/exceptions/SetupException.java rename to reactivesocket-aeron-client/src/main/java/io/reactivesocket/exceptions/SetupException.java diff --git a/reactivesocket-aeron-core/build.gradle b/reactivesocket-aeron-core/build.gradle new file mode 100644 index 000000000..e69de29bb diff --git a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java new file mode 100644 index 000000000..29ed9fa48 --- /dev/null +++ b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java @@ -0,0 +1,163 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.internal; + +import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.aeron.logbuffer.BufferClaim; +import uk.co.real_logic.agrona.MutableDirectBuffer; +import uk.co.real_logic.agrona.concurrent.OneToOneConcurrentArrayQueue; +import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; + +import java.util.concurrent.TimeUnit; + +/** + * Utils for dealing with Aeron + */ +public class AeronUtil implements Loggable { + + private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); + + private static final ThreadLocal> unsafeBuffers + = ThreadLocal.withInitial(() -> new OneToOneConcurrentArrayQueue<>(16)); + + /** + * Sends a message using offer. This method will spin-lock if Aeron signals back pressure. + *

+ * This method of sending data does need to know how long the message is. + * + * @param publication publication to send the message on + * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} + * that is send over Aeron + */ + public static void offer(Publication publication, BufferFiller fillBuffer, int length, int timeout, TimeUnit timeUnit) { + final MutableDirectBuffer buffer = getDirectBuffer(length); + fillBuffer.fill(0, buffer); + final long start = System.nanoTime(); + do { + if (timeout > 0) { + final long current = System.nanoTime(); + if ((current - start) > timeUnit.toNanos(timeout)) { + throw new RuntimeException("Timed out publishing data"); + } + } + final long offer = publication.offer(buffer); + if (offer >= 0) { + break; + } else if (Publication.NOT_CONNECTED == offer) { + throw new RuntimeException("not connected"); + } + } while (true); + + recycleDirectBuffer(buffer); + } + + /** + * Sends a message using tryClaim. This method will spin-lock if Aeron signals back pressure. The message + * being sent needs to be equal or smaller than Aeron's MTU size or an exception will be thrown. + *

+ * In order to use this method of sending data you need to know the length of data. + * + * @param publication publication to send the message on + * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} + * that is send over Aeron + * @param length the length of data + */ + public static void tryClaim(Publication publication, BufferFiller fillBuffer, int length, int timeout, TimeUnit timeUnit) { + final BufferClaim bufferClaim = bufferClaims.get(); + final long start = System.nanoTime(); + do { + if (timeout > 0) { + final long current = System.nanoTime(); + if ((current - start) > timeUnit.toNanos(timeout)) { + throw new NotConnectedException(); + } + } + + final long offer = publication.tryClaim(length, bufferClaim); + if (offer >= 0) { + try { + final MutableDirectBuffer buffer = bufferClaim.buffer(); + final int offset = bufferClaim.offset(); + fillBuffer.fill(offset, buffer); + break; + } finally { + bufferClaim.commit(); + } + } else if (Publication.NOT_CONNECTED == offer) { + throw new NotConnectedException(); + } + } while (true); + } + + /** + * Attempts to send the data using tryClaim. If the message data length is large then the Aeron MTU + * size it will use offer instead. + * + * @param publication publication to send the message on + * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} + * that is send over Aeron + * @param length the length of data + */ + public static void tryClaimOrOffer(Publication publication, BufferFiller fillBuffer, int length) { + tryClaimOrOffer(publication, fillBuffer, length, -1, null); + } + + public static void tryClaimOrOffer(Publication publication, BufferFiller fillBuffer, int length, int timeout, TimeUnit timeUnit) { + if (length < Constants.AERON_MTU_SIZE) { + tryClaim(publication, fillBuffer, length, timeout, timeUnit); + } else { + offer(publication, fillBuffer, length, timeout, timeUnit); + } + } + + + /** + * Try to get a MutableDirectBuffer from a thread-safe pool for a given length. If the buffer found + * is bigger then the buffer in the pool creates a new buffer. If no buffer is found creates a new buffer + * + * @param length the requested length + * @return either a new MutableDirectBuffer or a recycled one that has the capacity to hold the data from the old one + */ + public static MutableDirectBuffer getDirectBuffer(int length) { + OneToOneConcurrentArrayQueue queue = unsafeBuffers.get(); + MutableDirectBuffer buffer = queue.poll(); + + if (buffer != null && buffer.capacity() < length) { + return buffer; + } else { + byte[] bytes = new byte[length]; + buffer = new UnsafeBuffer(bytes); + return buffer; + } + } + + /** + * Sends a DirectBuffer back to the thread pools to be recycled. + * + * @param directBuffer the DirectBuffer to recycle + */ + public static void recycleDirectBuffer(MutableDirectBuffer directBuffer) { + OneToOneConcurrentArrayQueue queue = unsafeBuffers.get(); + queue.offer(directBuffer); + } + + /** + * Implement this to fill a DirectBuffer passed in by either the offer or tryClaim methods. + */ + public interface BufferFiller { + void fill(int offset, MutableDirectBuffer buffer); + } +} diff --git a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Constants.java b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Constants.java new file mode 100644 index 000000000..19a5375cf --- /dev/null +++ b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Constants.java @@ -0,0 +1,41 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.internal; + +import uk.co.real_logic.agrona.concurrent.IdleStrategy; +import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; + +public final class Constants { + + private Constants() {} + + public static final int SERVER_STREAM_ID = 1; + + public static final int CLIENT_STREAM_ID = 2; + + public static final byte[] EMTPY = new byte[0]; + + public static final int QUEUE_SIZE = Integer.getInteger("reactivesocket.aeron.framesSendQueueSize", 262144); + + public static final IdleStrategy SERVER_IDLE_STRATEGY = new NoOpIdleStrategy(); + + public static final int CONCURRENCY = Integer.getInteger("reactivesocket.aeron.clientConcurrency", 2); + + public static final int AERON_MTU_SIZE = Integer.getInteger("aeron.mtu.length", 4096); + + public static final boolean TRACING_ENABLED = Boolean.getBoolean("reactivesocket.aeron.tracingEnabled"); + +} diff --git a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Loggable.java b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Loggable.java new file mode 100644 index 000000000..d2d540d3d --- /dev/null +++ b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Loggable.java @@ -0,0 +1,53 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.internal; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * No more needed to type Logger LOGGER = LoggerFactory.getLogger.... + */ +public interface Loggable { + + default void info(String message, Object... args) { + logger().debug(message, args); + } + + default void error(String message, Throwable t) { + logger().error(message, t); + } + + default void debug(String message, Object... args) { + logger().debug(message, args); + } + + default void trace(String message, Object... args) { + logger().trace(message, args); + } + + default boolean isTraceEnabled() { + if (Constants.TRACING_ENABLED) { + return logger().isTraceEnabled(); + } else { + return false; + } + } + + default Logger logger() { + return LoggerFactory.getLogger(getClass()); + } +} diff --git a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/MessageType.java b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/MessageType.java new file mode 100644 index 000000000..c294d232d --- /dev/null +++ b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/MessageType.java @@ -0,0 +1,59 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.internal; + +/** + * Type of message being sent. + */ +public enum MessageType { + ESTABLISH_CONNECTION_REQUEST(0x01), + ESTABLISH_CONNECTION_RESPONSE(0x02), + CONNECTION_DISCONNECT(0x3), + FRAME(0x04); + + private static MessageType[] typesById; + + /** + * Index types by id for indexed lookup. + */ + static { + int max = 0; + + for (MessageType t : values()) { + max = Math.max(t.id, max); + } + + typesById = new MessageType[max + 1]; + + for (MessageType t : values()) { + typesById[t.id] = t; + } + } + + private final int id; + + MessageType(int id) { + this.id = id; + } + + public int getEncodedType() { + return id; + } + + public static MessageType from(int id) { + return typesById[id]; + } +} diff --git a/src/main/java/io/reactivesocket/aeron/internal/concurrent/QueuedPipe.java b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java similarity index 63% rename from src/main/java/io/reactivesocket/aeron/internal/concurrent/QueuedPipe.java rename to reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java index 5a9ed0875..ce87e7712 100644 --- a/src/main/java/io/reactivesocket/aeron/internal/concurrent/QueuedPipe.java +++ b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java @@ -1,5 +1,5 @@ -/* - * Copyright 2015 Real Logic Ltd. +/** + * Copyright 2015 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,16 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.reactivesocket.aeron.internal.concurrent; +package io.reactivesocket.aeron.internal; +public class NotConnectedException extends RuntimeException { -import java.util.Queue; + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } -/** - * Composed interface for concurrent queues and sequenced containers. - * - * @param type of the elements stored in the {@link java.util.Queue}. - */ -public interface QueuedPipe extends Queue, Pipe -{ -} \ No newline at end of file +} diff --git a/reactivesocket-aeron-examples/build.gradle b/reactivesocket-aeron-examples/build.gradle new file mode 100644 index 000000000..421e59c8d --- /dev/null +++ b/reactivesocket-aeron-examples/build.gradle @@ -0,0 +1,4 @@ +dependencies { + compile project(':reactivesocket-aeron-client') + compile project(':reactivesocket-aeron-server') +} diff --git a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/MediaDriver.java b/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/MediaDriver.java new file mode 100644 index 000000000..9dac34fc0 --- /dev/null +++ b/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/MediaDriver.java @@ -0,0 +1,47 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.example; + +import uk.co.real_logic.aeron.driver.ThreadingMode; +import uk.co.real_logic.agrona.concurrent.BackoffIdleStrategy; +import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; + +/** + * Created by rroeser on 8/16/15. + */ +public class MediaDriver { + public static void main(String... args) { + ThreadingMode threadingMode = ThreadingMode.SHARED; + + boolean dedicated = Boolean.getBoolean("dedicated"); + + if (dedicated) { + threadingMode = ThreadingMode.DEDICATED; + } + + System.out.println("ThreadingMode => " + threadingMode); + + final uk.co.real_logic.aeron.driver.MediaDriver.Context ctx = new uk.co.real_logic.aeron.driver.MediaDriver.Context() + .threadingMode(threadingMode) + .dirsDeleteOnStart(true) + .conductorIdleStrategy(new BackoffIdleStrategy(1, 1, 100, 1000)) + .receiverIdleStrategy(new NoOpIdleStrategy()) + .senderIdleStrategy(new NoOpIdleStrategy()); + + final uk.co.real_logic.aeron.driver.MediaDriver ignored = uk.co.real_logic.aeron.driver.MediaDriver.launch(ctx); + + } +} diff --git a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/Ping.java b/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/Ping.java new file mode 100644 index 000000000..a30943819 --- /dev/null +++ b/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/Ping.java @@ -0,0 +1,141 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.example; + +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.Payload; +import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.aeron.client.AeronClientDuplexConnection; +import io.reactivesocket.aeron.client.AeronClientDuplexConnectionFactory; +import io.reactivesocket.aeron.client.FrameHolder; +import org.HdrHistogram.Recorder; +import org.reactivestreams.Publisher; +import rx.Observable; +import rx.RxReactiveStreams; +import rx.Subscriber; +import rx.schedulers.Schedulers; + +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Created by rroeser on 8/16/15. + */ +public class Ping { + + public static void main(String... args) throws Exception { + String host = System.getProperty("host", "localhost"); + String server = System.getProperty("server", "localhost"); + + System.out.println("Setting host to => " + host); + + System.out.println("Setting ping is listening to => " + server); + + + byte[] key = new byte[4]; + //byte[] key = new byte[BitUtil.SIZE_OF_INT]; + Random r = new Random(); + r.nextBytes(key); + + System.out.println("Sending data of size => " + key.length); + + InetSocketAddress listenAddress = new InetSocketAddress("localhost", 39790); + InetSocketAddress clientAddress = new InetSocketAddress("localhost", 39790); + + AeronClientDuplexConnectionFactory cf = AeronClientDuplexConnectionFactory.getInstance(); + cf.addSocketAddressToHandleResponses(listenAddress); + Publisher udpConnection = cf.createAeronClientDuplexConnection(clientAddress); + + System.out.println("Creating new duplex connection"); + AeronClientDuplexConnection connection = RxReactiveStreams.toObservable(udpConnection).toBlocking().single(); + System.out.println("Created duplex connection"); + + ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection(connection, ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS)); + reactiveSocket.startAndWait(); + + CountDownLatch latch = new CountDownLatch(Integer.MAX_VALUE); + + final Recorder histogram = new Recorder(3600000000000L, 3); + + Schedulers + .computation() + .createWorker() + .schedulePeriodically(() -> { + System.out.println("---- FRAME HOLDER HISTO ----"); + FrameHolder.histogram.getIntervalHistogram().outputPercentileDistribution(System.out, 5, 1000.0, false); + System.out.println("---- FRAME HOLDER HISTO ----"); + + System.out.println("---- PING/ PONG HISTO ----"); + histogram.getIntervalHistogram().outputPercentileDistribution(System.out, 5, 1000.0, false); + System.out.println("---- PING/ PONG HISTO ----"); + + + }, 10, 10, TimeUnit.SECONDS); + + Observable + .range(1, Integer.MAX_VALUE) + .flatMap(i -> { + long start = System.nanoTime(); + + Payload keyPayload = new Payload() { + ByteBuffer data = ByteBuffer.wrap(key); + ByteBuffer metadata = ByteBuffer.allocate(0); + + public ByteBuffer getData() { + return data; + } + + @Override + public ByteBuffer getMetadata() { + return metadata; + } + }; + + return RxReactiveStreams + .toObservable( + reactiveSocket + .requestResponse(keyPayload)) + .doOnNext(s -> { + long diff = System.nanoTime() - start; + histogram.recordValue(diff); + }); + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + } + + @Override + public void onNext(Payload payload) { + latch.countDown(); + } + }); + + latch.await(); + System.out.println("Sent => " + Integer.MAX_VALUE); + System.exit(0); + } + +} diff --git a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/Pong.java b/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/Pong.java new file mode 100644 index 000000000..a8edd4cc3 --- /dev/null +++ b/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/Pong.java @@ -0,0 +1,136 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.example; + +import io.reactivesocket.ConnectionSetupHandler; +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.Payload; +import io.reactivesocket.RequestHandler; +import io.reactivesocket.aeron.server.ReactiveSocketAeronServer; +import io.reactivesocket.exceptions.SetupException; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import uk.co.real_logic.aeron.driver.ThreadingMode; +import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; + +import java.nio.ByteBuffer; +import java.util.Random; + +/** + * Created by rroeser on 8/16/15. + */ +public class Pong { + + public static void startDriver() { + ThreadingMode threadingMode = ThreadingMode.SHARED; + + boolean dedicated = Boolean.getBoolean("dedicated"); + + if (true) { + threadingMode = ThreadingMode.DEDICATED; + } + + System.out.println("ThreadingMode => " + threadingMode); + + final uk.co.real_logic.aeron.driver.MediaDriver.Context ctx = new uk.co.real_logic.aeron.driver.MediaDriver.Context() + .threadingMode(threadingMode) + .conductorIdleStrategy(new NoOpIdleStrategy()) + .receiverIdleStrategy(new NoOpIdleStrategy()) + .senderIdleStrategy(new NoOpIdleStrategy()); + + final uk.co.real_logic.aeron.driver.MediaDriver ignored = uk.co.real_logic.aeron.driver.MediaDriver.launchEmbedded(ctx); + } + + public static void main(String... args) { + // startDriver(); + + String host = System.getProperty("host", "localhost"); + + System.out.println("Setting host to => " + host); + + byte[] response = new byte[1024]; + //byte[] response = new byte[1024]; + Random r = new Random(); + r.nextBytes(response); + + System.out.println("Sending data of size => " + response.length); + + ReactiveSocketAeronServer server = ReactiveSocketAeronServer.create(host, 39790, new ConnectionSetupHandler() { + @Override + public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + return new RequestHandler() { + @Override + public Publisher handleRequestResponse(Payload payload) { + long time = System.currentTimeMillis(); + + Publisher publisher = new Publisher() { + @Override + public void subscribe(Subscriber s) { + Payload responsePayload = new Payload() { + ByteBuffer data = ByteBuffer.wrap(response); + ByteBuffer metadata = ByteBuffer.allocate(0); + + public ByteBuffer getData() { + return data; + } + + @Override + public ByteBuffer getMetadata() { + return metadata; + } + }; + + s.onNext(responsePayload); + + long diff = System.currentTimeMillis() - time; + //timer.update(diff, TimeUnit.NANOSECONDS); + s.onComplete(); + } + }; + + return publisher; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + return null; + } + + @Override + public Publisher handleSubscription(Payload payload) { + return null; + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return null; + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher payloads) { + return null; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + }; + } + }); + } + +} diff --git a/reactivesocket-aeron-examples/src/main/resources/simplelogger.properties b/reactivesocket-aeron-examples/src/main/resources/simplelogger.properties new file mode 100644 index 000000000..463129958 --- /dev/null +++ b/reactivesocket-aeron-examples/src/main/resources/simplelogger.properties @@ -0,0 +1,35 @@ +# SLF4J's SimpleLogger configuration file +# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. + +# Default logging detail level for all instances of SimpleLogger. +# Must be one of ("trace", "debug", "info", "warn", or "error"). +# If not specified, defaults to "info". +#org.slf4j.simpleLogger.defaultLogLevel=debug +org.slf4j.simpleLogger.defaultLogLevel=trace + +# Logging detail level for a SimpleLogger instance named "xxxxx". +# Must be one of ("trace", "debug", "info", "warn", or "error"). +# If not specified, the default logging detail level is used. +#org.slf4j.simpleLogger.log.xxxxx= + +# Set to true if you want the current date and time to be included in output messages. +# Default is false, and will output the number of milliseconds elapsed since startup. +org.slf4j.simpleLogger.showDateTime=true + +# The date and time format to be used in the output messages. +# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat. +# If the format is not specified or is invalid, the default format is used. +# The default format is yyyy-MM-dd HH:mm:ss:SSS Z. +org.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss + +# Set to true if you want to output the current thread name. +# Defaults to true. +org.slf4j.simpleLogger.showThreadName=true + +# Set to true if you want the Logger instance name to be included in output messages. +# Defaults to true. +org.slf4j.simpleLogger.showLogName=true + +# Set to true if you want the last component of the name to be included in output messages. +# Defaults to false. +org.slf4j.simpleLogger.showShortLogName=true \ No newline at end of file diff --git a/reactivesocket-aeron-server/build.gradle b/reactivesocket-aeron-server/build.gradle new file mode 100644 index 000000000..72fb47b09 --- /dev/null +++ b/reactivesocket-aeron-server/build.gradle @@ -0,0 +1,3 @@ +dependencies { + compile project(':reactivesocket-aeron-core') +} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java new file mode 100644 index 000000000..c5905d446 --- /dev/null +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java @@ -0,0 +1,109 @@ +package io.reactivesocket.aeron.client; + +import io.reactivesocket.DuplexConnection; +import io.reactivesocket.Frame; +import io.reactivesocket.aeron.internal.Loggable; +import io.reactivesocket.rx.Completable; +import io.reactivesocket.rx.Disposable; +import io.reactivesocket.rx.Observable; +import io.reactivesocket.rx.Observer; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.agrona.concurrent.AbstractConcurrentArrayQueue; + +import java.io.IOException; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; + +public class AeronClientDuplexConnection implements DuplexConnection, Loggable { + + private final Publication publication; + private final CopyOnWriteArrayList> subjects; + private final AbstractConcurrentArrayQueue frameSendQueue; + private final Consumer onClose; + + public AeronClientDuplexConnection( + Publication publication, + AbstractConcurrentArrayQueue frameSendQueue, + Consumer onClose) { + this.publication = publication; + this.subjects = new CopyOnWriteArrayList<>(); + this.frameSendQueue = frameSendQueue; + this.onClose = onClose; + } + + @Override + public final Observable getInput() { + if (isTraceEnabled()) { + trace("getting input for publication session id {} ", publication.sessionId()); + } + + return new Observable() { + public void subscribe(Observer o) { + o.onSubscribe(new Disposable() { + @Override + public void dispose() { + if (isTraceEnabled()) { + trace("removing Observer for publication with session id {} ", publication.sessionId()); + } + + subjects.removeIf(s -> s == o); + } + }); + + subjects.add(o); + } + }; + } + + @Override + public void addOutput(Publisher o, Completable callback) { + o + .subscribe(new Subscriber() { + private Subscription s; + + @Override + public void onSubscribe(Subscription s) { + this.s = s; + s.request(Long.MAX_VALUE); + + } + + @Override + public void onNext(Frame frame) { + if (isTraceEnabled()) { + trace("onNext subscription => {} and frame => {}", s.toString(), frame.toString()); + } + + final FrameHolder fh = FrameHolder.get(frame, publication, s); + boolean offer; + do { + offer = frameSendQueue.offer(fh); + } while (!offer); + } + + @Override + public void onError(Throwable t) { + callback.error(t); + } + + @Override + public void onComplete() { + callback.success(); + } + }); + } + + @Override + public void close() throws IOException { + onClose.accept(publication); + } + + public CopyOnWriteArrayList> getSubjects() { + return subjects; + } + + +} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java new file mode 100644 index 000000000..d076dc8c9 --- /dev/null +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java @@ -0,0 +1,262 @@ +package io.reactivesocket.aeron.client; + +import io.reactivesocket.Frame; +import io.reactivesocket.aeron.internal.AeronUtil; +import io.reactivesocket.aeron.internal.Constants; +import io.reactivesocket.aeron.internal.Loggable; +import io.reactivesocket.aeron.internal.MessageType; +import io.reactivesocket.rx.Observer; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.aeron.logbuffer.Header; +import uk.co.real_logic.agrona.BitUtil; +import uk.co.real_logic.agrona.DirectBuffer; +import uk.co.real_logic.agrona.concurrent.ManyToManyConcurrentArrayQueue; +import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import static io.reactivesocket.aeron.internal.Constants.CONCURRENCY; +import static io.reactivesocket.aeron.internal.Constants.SERVER_STREAM_ID; + +public final class AeronClientDuplexConnectionFactory implements Loggable { + private static final AeronClientDuplexConnectionFactory instance = new AeronClientDuplexConnectionFactory(); + + private static ThreadLocal buffers = ThreadLocal.withInitial(() -> new UnsafeBuffer(Constants.EMTPY)); + + private final ConcurrentSkipListMap connections; + + // TODO - this should be configurable...enough space for 2048 DuplexConnections assuming request(128) + private final ManyToManyConcurrentArrayQueue frameSendQueue = new ManyToManyConcurrentArrayQueue<>(262144); + + private final ConcurrentHashMap establishConnectionHolders; + + private final ClientAeronManager manager; + + private AeronClientDuplexConnectionFactory() { + connections = new ConcurrentSkipListMap<>(); + establishConnectionHolders = new ConcurrentHashMap<>(); + manager = ClientAeronManager.getInstance(); + + manager.addClientAction(threadId -> { + final boolean traceEnabled = isTraceEnabled(); + frameSendQueue + .drain(fh -> { + final Frame frame = fh.getFrame(); + final ByteBuffer byteBuffer = frame.getByteBuffer(); + final Publication publication = fh.getPublication(); + final int length = frame.length() + BitUtil.SIZE_OF_INT; + + // Can release the FrameHolder at this point as we got everything we need + fh.release(); + + AeronUtil + .tryClaimOrOffer(publication, (offset, buffer) -> { + if (traceEnabled) { + trace("Thread Id {} sending Frame => {} on Aeron", threadId, frame.toString()); + } + + buffer.putShort(offset, (short) 0); + buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); + buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, frame.offset(), frame.length()); + }, length); + }); + }); + } + + public static AeronClientDuplexConnectionFactory getInstance() { + return instance; + } + + /** + * Adds a {@link java.net.SocketAddress} for Aeron to listen for responses on + * + * @param socketAddress + */ + public void addSocketAddressToHandleResponses(SocketAddress socketAddress) { + if (socketAddress instanceof InetSocketAddress) { + addUDPSocketAddressToHandleResponses((InetSocketAddress) socketAddress); + } else { + throw new RuntimeException("unknown socket address type => " + socketAddress.getClass()); + } + } + + void addUDPSocketAddressToHandleResponses(InetSocketAddress socketAddress) { + //String serverChannel = "udp://" + socketAddress.getHostName() + ":" + socketAddress.getPort(); + String serverChannel = "udp://localhost:39790"; + + + manager.addSubscription( + serverChannel, + Constants.CLIENT_STREAM_ID, + threadId -> + new ClientAeronManager.ThreadIdAwareFragmentHandler(threadId) { + @Override + public void onFragment(DirectBuffer buffer, int offset, int length, Header header) { + fragmentHandler(getThreadId(), buffer, offset, length, header); + } + }); + } + + public Publisher createAeronClientDuplexConnection(SocketAddress socketAddress) { + if (socketAddress instanceof InetSocketAddress) { + return createUDPConnection((InetSocketAddress) socketAddress); + } else { + throw new RuntimeException("unknown socket address type => " + socketAddress.getClass()); + } + } + + Publisher createUDPConnection(InetSocketAddress inetSocketAddress) { + final String channel = "udp://" + inetSocketAddress.getHostName() + ":" + inetSocketAddress.getPort(); + debug("Creating a publication to channel => {}", channel); + final Publication publication = manager.getAeron().addPublication(channel, SERVER_STREAM_ID); + debug("Created a publication with sessionId => {} to channel => {}", publication.sessionId(), channel); + + return subscriber -> { + EstablishConnectionHolder establishConnectionHolder = new EstablishConnectionHolder(publication, subscriber); + establishConnectionHolders.putIfAbsent(publication.sessionId(), establishConnectionHolder); + + establishConnection(publication); + }; + } + + /** + * Establishes a connection between the client and server. Waits for 30 seconds before throwing a exception. + */ + void establishConnection(final Publication publication) { + final int sessionId = publication.sessionId(); + + debug("Establishing connection for channel => {}, stream id => {}", + publication.channel(), + publication.sessionId()); + + UnsafeBuffer buffer = buffers.get(); + buffer.wrap(new byte[BitUtil.SIZE_OF_INT]); + buffer.putShort(0, (short) 0); + buffer.putShort(BitUtil.SIZE_OF_SHORT, (short) MessageType.ESTABLISH_CONNECTION_REQUEST.getEncodedType()); + + long offer = -1; + final long start = System.nanoTime(); + for (;;) { + final long current = System.nanoTime(); + if ((current - start) > TimeUnit.SECONDS.toNanos(30)) { + throw new RuntimeException("Timed out waiting to establish connection for session id => " + sessionId); + } + + if (offer < 0) { + offer = publication.offer(buffer); + } else { + break; + } + + } + + } + + void fragmentHandler(int threadId, DirectBuffer buffer, int offset, int length, Header header) { + try { + short messageCount = buffer.getShort(offset); + short messageTypeInt = buffer.getShort(offset + BitUtil.SIZE_OF_SHORT); + final int currentThreadId = Math.abs(messageCount % CONCURRENCY); + + if (currentThreadId != threadId) { + return; + } + + final MessageType messageType = MessageType.from(messageTypeInt); + if (messageType == MessageType.FRAME) { + AeronClientDuplexConnection aeronClientDuplexConnection = connections.get(header.sessionId()); + if (aeronClientDuplexConnection != null) { + CopyOnWriteArrayList> subjects = aeronClientDuplexConnection.getSubjects(); + if (!subjects.isEmpty()) { + //TODO think about how to recycle these, hard because could be handed to another thread I think? + final ByteBuffer bytes = ByteBuffer.allocate(length); + buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); + final Frame frame = Frame.from(bytes); + int i = 0; + final int size = subjects.size(); + do { + Observer frameObserver = subjects.get(i); + frameObserver.onNext(frame); + + i++; + } while (i < size); + } + } else { + debug("no connection found for Aeron Session Id {}", header.sessionId()); + } + } else if (messageType == MessageType.ESTABLISH_CONNECTION_RESPONSE) { + final int ackSessionId = buffer.getInt(offset + BitUtil.SIZE_OF_INT); + EstablishConnectionHolder establishConnectionHolder = establishConnectionHolders.remove(ackSessionId); + if (establishConnectionHolder != null) { + try { + AeronClientDuplexConnection aeronClientDuplexConnection + = new AeronClientDuplexConnection(establishConnectionHolder.getPublication(), frameSendQueue, new Consumer() { + @Override + public void accept(Publication publication) { + connections.remove(publication.sessionId()); + + // Send a message to the server that the connection is closed and that it needs to clean-up resources on it's side + if (publication != null) { + try { + AeronUtil.tryClaimOrOffer(publication, (offset, buffer) -> { + buffer.putShort(offset, (short) 0); + buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.CONNECTION_DISCONNECT.getEncodedType()); + }, BitUtil.SIZE_OF_INT); + } catch (Throwable t) { + debug("error closing publication with session id => {}", publication.sessionId()); + } + publication.close(); + } + } + }); + + connections.put(header.sessionId(), aeronClientDuplexConnection); + + establishConnectionHolder.getSubscriber().onNext(aeronClientDuplexConnection); + establishConnectionHolder.getSubscriber().onComplete(); + + debug("Connection established for channel => {}, stream id => {}", + establishConnectionHolder.getPublication().channel(), + establishConnectionHolder.getPublication().sessionId()); + } catch (Throwable t) { + establishConnectionHolder.getSubscriber().onError(t); + } + } + } else { + debug("Unknown message type => " + messageTypeInt); + } + } catch (Throwable t) { + error("error handling framement", t); + } + } + + /* + * Inner Classes + */ + class EstablishConnectionHolder { + private Publication publication; + private Subscriber subscriber; + + public EstablishConnectionHolder(Publication publication, Subscriber subscriber) { + this.publication = publication; + this.subscriber = subscriber; + } + + public Publication getPublication() { + return publication; + } + + public Subscriber getSubscriber() { + return subscriber; + } + } +} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java new file mode 100644 index 000000000..30b71bd6f --- /dev/null +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java @@ -0,0 +1,220 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.client; + +import io.reactivesocket.aeron.internal.Constants; +import io.reactivesocket.aeron.internal.Loggable; +import rx.Scheduler; +import rx.functions.Func1; +import rx.schedulers.Schedulers; +import uk.co.real_logic.aeron.Aeron; +import uk.co.real_logic.aeron.AvailableImageHandler; +import uk.co.real_logic.aeron.FragmentAssembler; +import uk.co.real_logic.aeron.Image; +import uk.co.real_logic.aeron.Subscription; +import uk.co.real_logic.aeron.logbuffer.FragmentHandler; + +import java.util.Optional; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Class for managing the Aeron on the client side. + */ +public class ClientAeronManager implements Loggable { + private static final ClientAeronManager INSTANCE = new ClientAeronManager(); + + private final CopyOnWriteArrayList clientActions; + + private final CopyOnWriteArrayList subscriptionGroups; + + private final Aeron aeron; + + private final Scheduler.Worker[] workers = new Scheduler.Worker[Constants.CONCURRENCY]; + + private ClientAeronManager() { + this.clientActions = new CopyOnWriteArrayList<>(); + this.subscriptionGroups = new CopyOnWriteArrayList<>(); + + final Aeron.Context ctx = new Aeron.Context(); + ctx.errorHandler(t -> error("an exception occurred", t)); + ctx.availableImageHandler(new AvailableImageHandler() { + @Override + public void onAvailableImage(Image image, Subscription subscription, long joiningPosition, String sourceIdentity) { + debug("New image available with session id => {} and sourceIdentity => {} and subscription => {}", image.sessionId(), sourceIdentity, subscription.toString()); + } + }); + + aeron = Aeron.connect(ctx); + + poll(); + } + + public static ClientAeronManager getInstance() { + return INSTANCE; + } + + /** + * Finds a SubscriptionGroup from the sessionId of one the servers in the group + * + * @param sessionId the session id whose SubscriptionGroup you want to find + * @return an Optional of SubscriptionGroup + */ + public Optional find(final int sessionId) { + return subscriptionGroups + .stream() + .filter(sg -> { + boolean found = false; + + for (Subscription subscription : sg.subscriptions) { + Image image = subscription.getImage(sessionId); + if (image != null) { + found = true; + break; + } + } + + return found; + }) + .findFirst(); + } + + /** + * Adds a ClientAction on the a list that is run by the polling loop. + * + * @param clientAction the {@link io.reactivesocket.aeron.client.ClientAeronManager.ClientAction} to add + */ + public void addClientAction(ClientAction clientAction) { + clientActions.add(clientAction); + } + + + public boolean hasSubscriptionForChannel(String subscriptionChannel) { + return subscriptionGroups + .stream() + .anyMatch(sg -> sg.getChannel().equals(subscriptionChannel)); + } + + public Aeron getAeron() { + return aeron; + } + + /** + * Adds an Aeron subscription to be polled. This method will create a subscription for each of the polling threads. + * + * @param subscriptionChannel the channel to create subscriptions on + * @param streamId the stream id to create subscriptions on + * @param fragmentHandlerFactory factory that creates a fragment handler that is aware of the thread that is call it. + */ + public void addSubscription(String subscriptionChannel, int streamId, Func1 fragmentHandlerFactory) { + if (!hasSubscriptionForChannel(subscriptionChannel)) { + + debug("Creating a subscriptions to channel => {}", subscriptionChannel); + Subscription[] subscriptions = new Subscription[Constants.CONCURRENCY]; + for (int i = 0; i < Constants.CONCURRENCY; i++) { + subscriptions[i] = aeron.addSubscription(subscriptionChannel, streamId); + debug("Subscription created for threadId => {} and channel => {} ", i, subscriptionChannel); + } + SubscriptionGroup subscriptionGroup = new SubscriptionGroup(subscriptionChannel, subscriptions, fragmentHandlerFactory); + subscriptionGroups.add(subscriptionGroup); + debug("Subscriptions created to channel => {}", subscriptionChannel); + + } else { + debug("Subscription already exists for channel => {}", subscriptionChannel); + } + } + + /* + * Starts polling for the Aeron client. Will run registered client actions and will automatically start polling + * subscriptions + */ + void poll() { + info("ReactiveSocket Aeron Client concurreny is {}", Constants.CONCURRENCY); + final ReentrantLock pollLock = new ReentrantLock(); + final ReentrantLock clientActionLock = new ReentrantLock(); + for (int i = 0; i < Constants.CONCURRENCY; i++) { + final int threadId = i; + workers[threadId] = Schedulers.computation().createWorker(); + workers[threadId].schedulePeriodically(new + PollingAction(pollLock, clientActionLock, threadId, subscriptionGroups, clientActions), + 0, 20, TimeUnit.MICROSECONDS); + } + } + + /* + * Inner Classes + */ + + /** + * Creates a logic group of {@link uk.co.real_logic.aeron.Subscription}s to a particular channel. + */ + public static class SubscriptionGroup { + + private final static ThreadLocal threadLocalFragmentAssembler = new ThreadLocal<>(); + private final String channel; + private final Subscription[] subscriptions; + private final Func1 fragmentHandlerFactory; + + public SubscriptionGroup(String channel, Subscription[] subscriptions, Func1 fragmentHandlerFactory) { + this.channel = channel; + this.subscriptions = subscriptions; + this.fragmentHandlerFactory = fragmentHandlerFactory; + } + + public String getChannel() { + return channel; + } + + public Subscription[] getSubscriptions() { + return subscriptions; + } + + public FragmentAssembler getFragmentAssembler(int threadId) { + FragmentAssembler assembler = threadLocalFragmentAssembler.get(); + + if (assembler == null) { + assembler = new FragmentAssembler(fragmentHandlerFactory.call(threadId)); + threadLocalFragmentAssembler.set(assembler); + } + + return assembler; + } + } + + @FunctionalInterface + public interface ClientAction { + void call(int threadId); + } + + + + /** + * FragmentHandler that is aware of the thread that it is running on. This is useful if you only want a one thread + * to process a particular message. + */ + public static abstract class ThreadIdAwareFragmentHandler implements FragmentHandler { + private int threadId; + + public ThreadIdAwareFragmentHandler(int threadId) { + this.threadId = threadId; + } + + public final int getThreadId() { + return this.threadId; + } + } +} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java new file mode 100644 index 000000000..5e910b0b7 --- /dev/null +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java @@ -0,0 +1,74 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.client; + +import io.reactivesocket.Frame; +import org.HdrHistogram.Recorder; +import org.reactivestreams.Subscription; +import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.agrona.concurrent.OneToOneConcurrentArrayQueue; + +/** + * Holds a frame and the publication that it's supposed to be sent on. + * Pools instances on an {@link uk.co.real_logic.agrona.concurrent.OneToOneConcurrentArrayQueue} + */ +public class FrameHolder { + private static final ThreadLocal> FRAME_HOLDER_QUEUE + = ThreadLocal.withInitial(() -> new OneToOneConcurrentArrayQueue<>(16)); + + public static final Recorder histogram = new Recorder(3600000000000L, 3); + + private Frame frame; + private Publication publication; + private Subscription s; + private long getTime; + + private FrameHolder() {} + + public static FrameHolder get(Frame frame, Publication publication, Subscription s) { + FrameHolder frameHolder = FRAME_HOLDER_QUEUE.get().poll(); + + if (frameHolder == null) { + frameHolder = new FrameHolder(); + } + + frameHolder.frame = frame; + frameHolder.s = s; + frameHolder.publication = publication; + frameHolder.getTime = System.nanoTime(); + + return frameHolder; + } + + public Frame getFrame() { + return frame; + } + + public Publication getPublication() { + return publication; + } + + public void release() { + if (s != null) { + s.request(1); + } + + frame.release(); + FRAME_HOLDER_QUEUE.get().offer(this); + + histogram.recordValue(System.nanoTime() - getTime); + } +} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/PollingAction.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/PollingAction.java new file mode 100644 index 000000000..45cdffa8a --- /dev/null +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/PollingAction.java @@ -0,0 +1,64 @@ +package io.reactivesocket.aeron.client; + +import io.reactivesocket.aeron.internal.Loggable; +import rx.functions.Action0; +import uk.co.real_logic.aeron.Subscription; + +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +class PollingAction implements Action0, Loggable { + + private final ReentrantLock pollLock; + private final ReentrantLock clientActionLock; + private final int threadId; + private final List subscriptionGroups; + private final List clientActions; + + public PollingAction( + ReentrantLock pollLock, + ReentrantLock clientActionLock, + int threadId, + List subscriptionGroups, + List clientActions) { + this.pollLock = pollLock; + this.clientActionLock = clientActionLock; + this.threadId = threadId; + this.subscriptionGroups = subscriptionGroups; + this.clientActions = clientActions; + } + + @Override + public void call() { + try { + for (ClientAeronManager.SubscriptionGroup sg : subscriptionGroups) { + try { + int poll; + do { + Subscription subscription = sg.getSubscriptions()[threadId]; + poll = subscription.poll(sg.getFragmentAssembler(threadId), Integer.MAX_VALUE); + } while (poll > 0); + + //if (clientActionLock.tryLock()) { + for (ClientAeronManager.ClientAction action : clientActions) { + action.call(threadId); + } + //} + } catch (Throwable t) { + error("error polling aeron subscription on thread with id " + threadId, t); + } finally { + if (pollLock.isHeldByCurrentThread()) { + pollLock.unlock(); + } + + if (clientActionLock.isHeldByCurrentThread()) { + clientActionLock.unlock(); + } + } + } + + } catch (Throwable t) { + error("error in client polling loop on thread with id " + threadId, t); + } + } +} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/example/MediaDriver.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/example/MediaDriver.java new file mode 100644 index 000000000..9dac34fc0 --- /dev/null +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/example/MediaDriver.java @@ -0,0 +1,47 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.example; + +import uk.co.real_logic.aeron.driver.ThreadingMode; +import uk.co.real_logic.agrona.concurrent.BackoffIdleStrategy; +import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; + +/** + * Created by rroeser on 8/16/15. + */ +public class MediaDriver { + public static void main(String... args) { + ThreadingMode threadingMode = ThreadingMode.SHARED; + + boolean dedicated = Boolean.getBoolean("dedicated"); + + if (dedicated) { + threadingMode = ThreadingMode.DEDICATED; + } + + System.out.println("ThreadingMode => " + threadingMode); + + final uk.co.real_logic.aeron.driver.MediaDriver.Context ctx = new uk.co.real_logic.aeron.driver.MediaDriver.Context() + .threadingMode(threadingMode) + .dirsDeleteOnStart(true) + .conductorIdleStrategy(new BackoffIdleStrategy(1, 1, 100, 1000)) + .receiverIdleStrategy(new NoOpIdleStrategy()) + .senderIdleStrategy(new NoOpIdleStrategy()); + + final uk.co.real_logic.aeron.driver.MediaDriver ignored = uk.co.real_logic.aeron.driver.MediaDriver.launch(ctx); + + } +} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/example/Ping.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/example/Ping.java new file mode 100644 index 000000000..a30943819 --- /dev/null +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/example/Ping.java @@ -0,0 +1,141 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.example; + +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.Payload; +import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.aeron.client.AeronClientDuplexConnection; +import io.reactivesocket.aeron.client.AeronClientDuplexConnectionFactory; +import io.reactivesocket.aeron.client.FrameHolder; +import org.HdrHistogram.Recorder; +import org.reactivestreams.Publisher; +import rx.Observable; +import rx.RxReactiveStreams; +import rx.Subscriber; +import rx.schedulers.Schedulers; + +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Created by rroeser on 8/16/15. + */ +public class Ping { + + public static void main(String... args) throws Exception { + String host = System.getProperty("host", "localhost"); + String server = System.getProperty("server", "localhost"); + + System.out.println("Setting host to => " + host); + + System.out.println("Setting ping is listening to => " + server); + + + byte[] key = new byte[4]; + //byte[] key = new byte[BitUtil.SIZE_OF_INT]; + Random r = new Random(); + r.nextBytes(key); + + System.out.println("Sending data of size => " + key.length); + + InetSocketAddress listenAddress = new InetSocketAddress("localhost", 39790); + InetSocketAddress clientAddress = new InetSocketAddress("localhost", 39790); + + AeronClientDuplexConnectionFactory cf = AeronClientDuplexConnectionFactory.getInstance(); + cf.addSocketAddressToHandleResponses(listenAddress); + Publisher udpConnection = cf.createAeronClientDuplexConnection(clientAddress); + + System.out.println("Creating new duplex connection"); + AeronClientDuplexConnection connection = RxReactiveStreams.toObservable(udpConnection).toBlocking().single(); + System.out.println("Created duplex connection"); + + ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection(connection, ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS)); + reactiveSocket.startAndWait(); + + CountDownLatch latch = new CountDownLatch(Integer.MAX_VALUE); + + final Recorder histogram = new Recorder(3600000000000L, 3); + + Schedulers + .computation() + .createWorker() + .schedulePeriodically(() -> { + System.out.println("---- FRAME HOLDER HISTO ----"); + FrameHolder.histogram.getIntervalHistogram().outputPercentileDistribution(System.out, 5, 1000.0, false); + System.out.println("---- FRAME HOLDER HISTO ----"); + + System.out.println("---- PING/ PONG HISTO ----"); + histogram.getIntervalHistogram().outputPercentileDistribution(System.out, 5, 1000.0, false); + System.out.println("---- PING/ PONG HISTO ----"); + + + }, 10, 10, TimeUnit.SECONDS); + + Observable + .range(1, Integer.MAX_VALUE) + .flatMap(i -> { + long start = System.nanoTime(); + + Payload keyPayload = new Payload() { + ByteBuffer data = ByteBuffer.wrap(key); + ByteBuffer metadata = ByteBuffer.allocate(0); + + public ByteBuffer getData() { + return data; + } + + @Override + public ByteBuffer getMetadata() { + return metadata; + } + }; + + return RxReactiveStreams + .toObservable( + reactiveSocket + .requestResponse(keyPayload)) + .doOnNext(s -> { + long diff = System.nanoTime() - start; + histogram.recordValue(diff); + }); + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + } + + @Override + public void onNext(Payload payload) { + latch.countDown(); + } + }); + + latch.await(); + System.out.println("Sent => " + Integer.MAX_VALUE); + System.exit(0); + } + +} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/example/Pong.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/example/Pong.java new file mode 100644 index 000000000..a8edd4cc3 --- /dev/null +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/example/Pong.java @@ -0,0 +1,136 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.example; + +import io.reactivesocket.ConnectionSetupHandler; +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.Payload; +import io.reactivesocket.RequestHandler; +import io.reactivesocket.aeron.server.ReactiveSocketAeronServer; +import io.reactivesocket.exceptions.SetupException; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import uk.co.real_logic.aeron.driver.ThreadingMode; +import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; + +import java.nio.ByteBuffer; +import java.util.Random; + +/** + * Created by rroeser on 8/16/15. + */ +public class Pong { + + public static void startDriver() { + ThreadingMode threadingMode = ThreadingMode.SHARED; + + boolean dedicated = Boolean.getBoolean("dedicated"); + + if (true) { + threadingMode = ThreadingMode.DEDICATED; + } + + System.out.println("ThreadingMode => " + threadingMode); + + final uk.co.real_logic.aeron.driver.MediaDriver.Context ctx = new uk.co.real_logic.aeron.driver.MediaDriver.Context() + .threadingMode(threadingMode) + .conductorIdleStrategy(new NoOpIdleStrategy()) + .receiverIdleStrategy(new NoOpIdleStrategy()) + .senderIdleStrategy(new NoOpIdleStrategy()); + + final uk.co.real_logic.aeron.driver.MediaDriver ignored = uk.co.real_logic.aeron.driver.MediaDriver.launchEmbedded(ctx); + } + + public static void main(String... args) { + // startDriver(); + + String host = System.getProperty("host", "localhost"); + + System.out.println("Setting host to => " + host); + + byte[] response = new byte[1024]; + //byte[] response = new byte[1024]; + Random r = new Random(); + r.nextBytes(response); + + System.out.println("Sending data of size => " + response.length); + + ReactiveSocketAeronServer server = ReactiveSocketAeronServer.create(host, 39790, new ConnectionSetupHandler() { + @Override + public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + return new RequestHandler() { + @Override + public Publisher handleRequestResponse(Payload payload) { + long time = System.currentTimeMillis(); + + Publisher publisher = new Publisher() { + @Override + public void subscribe(Subscriber s) { + Payload responsePayload = new Payload() { + ByteBuffer data = ByteBuffer.wrap(response); + ByteBuffer metadata = ByteBuffer.allocate(0); + + public ByteBuffer getData() { + return data; + } + + @Override + public ByteBuffer getMetadata() { + return metadata; + } + }; + + s.onNext(responsePayload); + + long diff = System.currentTimeMillis() - time; + //timer.update(diff, TimeUnit.NANOSECONDS); + s.onComplete(); + } + }; + + return publisher; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + return null; + } + + @Override + public Publisher handleSubscription(Payload payload) { + return null; + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return null; + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher payloads) { + return null; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + }; + } + }); + } + +} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java new file mode 100644 index 000000000..29ed9fa48 --- /dev/null +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java @@ -0,0 +1,163 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.internal; + +import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.aeron.logbuffer.BufferClaim; +import uk.co.real_logic.agrona.MutableDirectBuffer; +import uk.co.real_logic.agrona.concurrent.OneToOneConcurrentArrayQueue; +import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; + +import java.util.concurrent.TimeUnit; + +/** + * Utils for dealing with Aeron + */ +public class AeronUtil implements Loggable { + + private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); + + private static final ThreadLocal> unsafeBuffers + = ThreadLocal.withInitial(() -> new OneToOneConcurrentArrayQueue<>(16)); + + /** + * Sends a message using offer. This method will spin-lock if Aeron signals back pressure. + *

+ * This method of sending data does need to know how long the message is. + * + * @param publication publication to send the message on + * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} + * that is send over Aeron + */ + public static void offer(Publication publication, BufferFiller fillBuffer, int length, int timeout, TimeUnit timeUnit) { + final MutableDirectBuffer buffer = getDirectBuffer(length); + fillBuffer.fill(0, buffer); + final long start = System.nanoTime(); + do { + if (timeout > 0) { + final long current = System.nanoTime(); + if ((current - start) > timeUnit.toNanos(timeout)) { + throw new RuntimeException("Timed out publishing data"); + } + } + final long offer = publication.offer(buffer); + if (offer >= 0) { + break; + } else if (Publication.NOT_CONNECTED == offer) { + throw new RuntimeException("not connected"); + } + } while (true); + + recycleDirectBuffer(buffer); + } + + /** + * Sends a message using tryClaim. This method will spin-lock if Aeron signals back pressure. The message + * being sent needs to be equal or smaller than Aeron's MTU size or an exception will be thrown. + *

+ * In order to use this method of sending data you need to know the length of data. + * + * @param publication publication to send the message on + * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} + * that is send over Aeron + * @param length the length of data + */ + public static void tryClaim(Publication publication, BufferFiller fillBuffer, int length, int timeout, TimeUnit timeUnit) { + final BufferClaim bufferClaim = bufferClaims.get(); + final long start = System.nanoTime(); + do { + if (timeout > 0) { + final long current = System.nanoTime(); + if ((current - start) > timeUnit.toNanos(timeout)) { + throw new NotConnectedException(); + } + } + + final long offer = publication.tryClaim(length, bufferClaim); + if (offer >= 0) { + try { + final MutableDirectBuffer buffer = bufferClaim.buffer(); + final int offset = bufferClaim.offset(); + fillBuffer.fill(offset, buffer); + break; + } finally { + bufferClaim.commit(); + } + } else if (Publication.NOT_CONNECTED == offer) { + throw new NotConnectedException(); + } + } while (true); + } + + /** + * Attempts to send the data using tryClaim. If the message data length is large then the Aeron MTU + * size it will use offer instead. + * + * @param publication publication to send the message on + * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} + * that is send over Aeron + * @param length the length of data + */ + public static void tryClaimOrOffer(Publication publication, BufferFiller fillBuffer, int length) { + tryClaimOrOffer(publication, fillBuffer, length, -1, null); + } + + public static void tryClaimOrOffer(Publication publication, BufferFiller fillBuffer, int length, int timeout, TimeUnit timeUnit) { + if (length < Constants.AERON_MTU_SIZE) { + tryClaim(publication, fillBuffer, length, timeout, timeUnit); + } else { + offer(publication, fillBuffer, length, timeout, timeUnit); + } + } + + + /** + * Try to get a MutableDirectBuffer from a thread-safe pool for a given length. If the buffer found + * is bigger then the buffer in the pool creates a new buffer. If no buffer is found creates a new buffer + * + * @param length the requested length + * @return either a new MutableDirectBuffer or a recycled one that has the capacity to hold the data from the old one + */ + public static MutableDirectBuffer getDirectBuffer(int length) { + OneToOneConcurrentArrayQueue queue = unsafeBuffers.get(); + MutableDirectBuffer buffer = queue.poll(); + + if (buffer != null && buffer.capacity() < length) { + return buffer; + } else { + byte[] bytes = new byte[length]; + buffer = new UnsafeBuffer(bytes); + return buffer; + } + } + + /** + * Sends a DirectBuffer back to the thread pools to be recycled. + * + * @param directBuffer the DirectBuffer to recycle + */ + public static void recycleDirectBuffer(MutableDirectBuffer directBuffer) { + OneToOneConcurrentArrayQueue queue = unsafeBuffers.get(); + queue.offer(directBuffer); + } + + /** + * Implement this to fill a DirectBuffer passed in by either the offer or tryClaim methods. + */ + public interface BufferFiller { + void fill(int offset, MutableDirectBuffer buffer); + } +} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/Constants.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/Constants.java new file mode 100644 index 000000000..19a5375cf --- /dev/null +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/Constants.java @@ -0,0 +1,41 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.internal; + +import uk.co.real_logic.agrona.concurrent.IdleStrategy; +import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; + +public final class Constants { + + private Constants() {} + + public static final int SERVER_STREAM_ID = 1; + + public static final int CLIENT_STREAM_ID = 2; + + public static final byte[] EMTPY = new byte[0]; + + public static final int QUEUE_SIZE = Integer.getInteger("reactivesocket.aeron.framesSendQueueSize", 262144); + + public static final IdleStrategy SERVER_IDLE_STRATEGY = new NoOpIdleStrategy(); + + public static final int CONCURRENCY = Integer.getInteger("reactivesocket.aeron.clientConcurrency", 2); + + public static final int AERON_MTU_SIZE = Integer.getInteger("aeron.mtu.length", 4096); + + public static final boolean TRACING_ENABLED = Boolean.getBoolean("reactivesocket.aeron.tracingEnabled"); + +} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/Loggable.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/Loggable.java new file mode 100644 index 000000000..d2d540d3d --- /dev/null +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/Loggable.java @@ -0,0 +1,53 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.internal; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * No more needed to type Logger LOGGER = LoggerFactory.getLogger.... + */ +public interface Loggable { + + default void info(String message, Object... args) { + logger().debug(message, args); + } + + default void error(String message, Throwable t) { + logger().error(message, t); + } + + default void debug(String message, Object... args) { + logger().debug(message, args); + } + + default void trace(String message, Object... args) { + logger().trace(message, args); + } + + default boolean isTraceEnabled() { + if (Constants.TRACING_ENABLED) { + return logger().isTraceEnabled(); + } else { + return false; + } + } + + default Logger logger() { + return LoggerFactory.getLogger(getClass()); + } +} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/MessageType.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/MessageType.java new file mode 100644 index 000000000..c294d232d --- /dev/null +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/MessageType.java @@ -0,0 +1,59 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.internal; + +/** + * Type of message being sent. + */ +public enum MessageType { + ESTABLISH_CONNECTION_REQUEST(0x01), + ESTABLISH_CONNECTION_RESPONSE(0x02), + CONNECTION_DISCONNECT(0x3), + FRAME(0x04); + + private static MessageType[] typesById; + + /** + * Index types by id for indexed lookup. + */ + static { + int max = 0; + + for (MessageType t : values()) { + max = Math.max(t.id, max); + } + + typesById = new MessageType[max + 1]; + + for (MessageType t : values()) { + typesById[t.id] = t; + } + } + + private final int id; + + MessageType(int id) { + this.id = id; + } + + public int getEncodedType() { + return id; + } + + public static MessageType from(int id) { + return typesById[id]; + } +} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java new file mode 100644 index 000000000..ce87e7712 --- /dev/null +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java @@ -0,0 +1,25 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.internal; + +public class NotConnectedException extends RuntimeException { + + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } + +} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java new file mode 100644 index 000000000..3aa8a611c --- /dev/null +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java @@ -0,0 +1,101 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.server; + +import io.reactivesocket.DuplexConnection; +import io.reactivesocket.Frame; +import io.reactivesocket.aeron.internal.AeronUtil; +import io.reactivesocket.aeron.internal.Loggable; +import io.reactivesocket.aeron.internal.MessageType; +import io.reactivesocket.aeron.internal.NotConnectedException; +import io.reactivesocket.rx.Completable; +import io.reactivesocket.rx.Disposable; +import io.reactivesocket.rx.Observable; +import io.reactivesocket.rx.Observer; +import org.reactivestreams.Publisher; +import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.agrona.BitUtil; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; + +public class AeronServerDuplexConnection implements DuplexConnection, Loggable { + private final Publication publication; + private final CopyOnWriteArrayList> subjects; + + public AeronServerDuplexConnection( + Publication publication) { + this.publication = publication; + this.subjects = new CopyOnWriteArrayList<>(); + } + + public List> getSubscriber() { + return subjects; + } + + @Override + public final Observable getInput() { + if (isTraceEnabled()) { + trace("-------getting input for publication session id {} ", publication.sessionId()); + } + + return new Observable() { + public void subscribe(Observer o) { + o.onSubscribe(new Disposable() { + @Override + public void dispose() { + if (isTraceEnabled()) { + trace("removing Observer for publication with session id {} ", publication.sessionId()); + } + + subjects.removeIf(s -> s == o); + } + }); + + subjects.add(o); + } + }; + } + + @Override + public void addOutput(Publisher o, Completable callback) { + o.subscribe(new ServerSubscription(publication, callback)); + } + + // TODO - this is bad - I need to queue this up somewhere and process this on the polling thread so it doesn't just block everything + void ackEstablishConnection(int ackSessionId) { + debug("Acking establish connection for session id => {}", ackSessionId); + for (;;) { + try { + AeronUtil.tryClaimOrOffer(publication, (offset, buffer) -> { + buffer.putShort(offset, (short) 0); + buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.ESTABLISH_CONNECTION_RESPONSE.getEncodedType()); + buffer.putInt(offset + BitUtil.SIZE_OF_INT, ackSessionId); + }, 2 * BitUtil.SIZE_OF_INT, 30, TimeUnit.SECONDS); + debug("Ack sent for session i => {}", ackSessionId); + } catch (NotConnectedException ne) { + continue; + } + break; + } + } + + @Override + public void close() { + publication.close(); + } +} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java new file mode 100644 index 000000000..ee678e4c6 --- /dev/null +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java @@ -0,0 +1,206 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.server; + +import io.reactivesocket.ConnectionSetupHandler; +import io.reactivesocket.Frame; +import io.reactivesocket.LeaseGovernor; +import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.aeron.internal.Loggable; +import io.reactivesocket.aeron.internal.MessageType; +import io.reactivesocket.rx.Observer; +import uk.co.real_logic.aeron.Aeron; +import uk.co.real_logic.aeron.FragmentAssembler; +import uk.co.real_logic.aeron.Image; +import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.aeron.Subscription; +import uk.co.real_logic.aeron.logbuffer.Header; +import uk.co.real_logic.agrona.BitUtil; +import uk.co.real_logic.agrona.DirectBuffer; + +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import static io.reactivesocket.aeron.internal.Constants.CLIENT_STREAM_ID; +import static io.reactivesocket.aeron.internal.Constants.SERVER_STREAM_ID; + +public class ReactiveSocketAeronServer implements AutoCloseable, Loggable { + private final int port; + + private final ConcurrentHashMap connections = new ConcurrentHashMap<>(); + + private final ConcurrentHashMap sockets = new ConcurrentHashMap<>(); + + private final Subscription subscription; + + private final ConnectionSetupHandler connectionSetupHandler; + + private final LeaseGovernor leaseGovernor; + + private static final ServerAeronManager manager = ServerAeronManager.getInstance(); + + private ReactiveSocketAeronServer(String host, int port, ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { + this.port = port; + this.connectionSetupHandler = connectionSetupHandler; + this.leaseGovernor = leaseGovernor; + + manager.addAvailableImageHander(this::availableImageHandler); + manager.addUnavailableImageHandler(this::unavailableImage); + + Aeron aeron = manager.getAeron(); + + final String serverChannel = "udp://" + host + ":" + port; + info("Starting new ReactiveSocketAeronServer on channel {}", serverChannel); + subscription = aeron.addSubscription(serverChannel, SERVER_STREAM_ID); + + FragmentAssembler fragmentAssembler = new FragmentAssembler(this::fragmentHandler); + manager.addSubscription(subscription, fragmentAssembler); + + + } + + void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) { + final int sessionId = header.sessionId(); + + short messageTypeInt = buffer.getShort(offset + BitUtil.SIZE_OF_SHORT); + MessageType type = MessageType.from(messageTypeInt); + + if (MessageType.FRAME == type) { + AeronServerDuplexConnection connection = connections.get(sessionId); + if (connection != null) { + List> subscribers = connection.getSubscriber(); + final Frame frame = Frame.from(buffer, BitUtil.SIZE_OF_INT + offset, length); + + if (isTraceEnabled()) { + trace("server received frame payload {} on session id {}", frame.getData(), sessionId); + } + + subscribers.forEach(s -> { + try { + s.onNext(frame); + } catch (Throwable t) { + s.onError(t); + } + }); + } + } else if (MessageType.ESTABLISH_CONNECTION_REQUEST == type) { + final long start = System.nanoTime(); + AeronServerDuplexConnection connection = null; + debug("Looking for an AeronServerDuplexConnection connection to ack establish connection for session id => {}", sessionId); + while (connection == null) { + final long current = System.nanoTime(); + + if (current - start > TimeUnit.SECONDS.toNanos(30)) { + throw new RuntimeException("unable to find connection to ack establish connection for session id => " + sessionId); + } + + connection = connections.get(sessionId); + } + debug("Found a connection to ack establish connection for session id => {}", sessionId); + connection.ackEstablishConnection(sessionId); + } else if (MessageType.CONNECTION_DISCONNECT == type) { + closeReactiveSocket(sessionId); + } + + } + + void availableImageHandler(Image image, Subscription subscription, long joiningPosition, String sourceIdentity) { + final int streamId = subscription.streamId(); + final int sessionId = image.sessionId(); + if (SERVER_STREAM_ID == streamId) { + debug("Handling new image for session id => {} and stream id => {}", streamId, sessionId); + final AeronServerDuplexConnection connection = connections.computeIfAbsent(sessionId, (_s) -> { + final String responseChannel = "udp://" + sourceIdentity.substring(0, sourceIdentity.indexOf(':')) + ":" + port; + Publication publication = manager.getAeron().addPublication(responseChannel, CLIENT_STREAM_ID); + int responseSessionId = publication.sessionId(); + debug("Creating new connection for responseChannel => {}, streamId => {}, and sessionId => {}", responseChannel, streamId, responseSessionId); + return new AeronServerDuplexConnection(publication); + }); + debug("Accepting ReactiveSocket connection"); + ReactiveSocket socket = ReactiveSocket.fromServerConnection( + connection, + connectionSetupHandler, + leaseGovernor, + new Consumer() { + @Override + public void accept(Throwable throwable) { + error(String.format("Error creating ReactiveSocket for Aeron session id => %d and stream id => %d", streamId, sessionId), throwable); + } + }); + + sockets.put(sessionId, socket); + + socket.startAndWait(); + } else { + debug("Unsupported stream id {}", streamId); + } + } + + void unavailableImage(Image image, Subscription subscription, long position) { + closeReactiveSocket(image.sessionId()); + } + + private void closeReactiveSocket(int sessionId) { + debug("closing connection for session id => " + sessionId); + ReactiveSocket socket = sockets.remove(sessionId); + connections.remove(sessionId); + + try { + socket.close(); + } catch (Throwable t) { + error("error closing socket for session id => " + sessionId, t); + } + } + + public boolean hasConnections() { + return !connections.isEmpty(); + } + + @Override + public void close() throws Exception { + manager.removeSubscription(subscription); + } + + /* + * Factory Methods + */ + public static ReactiveSocketAeronServer create(String host, int port, ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { + return new ReactiveSocketAeronServer(host, port, connectionSetupHandler, leaseGovernor); + } + + public static ReactiveSocketAeronServer create(int port, ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { + return create("127.0.0.1", port, connectionSetupHandler, leaseGovernor); + } + + public static ReactiveSocketAeronServer create(ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { + return create(39790, connectionSetupHandler, leaseGovernor); + } + + public static ReactiveSocketAeronServer create(String host, int port, ConnectionSetupHandler connectionSetupHandler) { + return new ReactiveSocketAeronServer(host, port, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); + } + + public static ReactiveSocketAeronServer create(int port, ConnectionSetupHandler connectionSetupHandler) { + return create("localhost", port, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); + } + + public static ReactiveSocketAeronServer create(ConnectionSetupHandler connectionSetupHandler) { + return create(39790, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); + } + +} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java new file mode 100644 index 000000000..6db9b6789 --- /dev/null +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java @@ -0,0 +1,120 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.server; + +import io.reactivesocket.aeron.internal.Loggable; +import uk.co.real_logic.aeron.Aeron; +import uk.co.real_logic.aeron.AvailableImageHandler; +import uk.co.real_logic.aeron.FragmentAssembler; +import uk.co.real_logic.aeron.Image; +import uk.co.real_logic.aeron.Subscription; +import uk.co.real_logic.aeron.UnavailableImageHandler; + +import java.util.concurrent.CopyOnWriteArrayList; + +import static io.reactivesocket.aeron.internal.Constants.SERVER_IDLE_STRATEGY; + +/** + * Class that manages the Aeron instance and the server's polling thread. Lets you register more + * than one NewImageHandler to Aeron after the it's the Aeron instance has started + */ +public class ServerAeronManager implements Loggable { + private static final ServerAeronManager INSTANCE = new ServerAeronManager(); + + private final Aeron aeron; + + private CopyOnWriteArrayList availableImageHandlers = new CopyOnWriteArrayList<>(); + + private CopyOnWriteArrayList unavailableImageHandlers = new CopyOnWriteArrayList<>(); + + private CopyOnWriteArrayList fragmentAssemblerHolders = new CopyOnWriteArrayList<>(); + + private class FragmentAssemblerHolder { + private Subscription subscription; + private FragmentAssembler fragmentAssembler; + + public FragmentAssemblerHolder(Subscription subscription, FragmentAssembler fragmentAssembler) { + this.subscription = subscription; + this.fragmentAssembler = fragmentAssembler; + } + } + + public ServerAeronManager() { + final Aeron.Context ctx = new Aeron.Context(); + ctx.availableImageHandler(this::availableImageHandler); + ctx.unavailableImageHandler(this::unavailableImage); + ctx.errorHandler(t -> error("an exception occurred", t)); + + aeron = Aeron.connect(ctx); + + poll(); + } + + public static ServerAeronManager getInstance() { + return INSTANCE; + } + + public void addAvailableImageHander(AvailableImageHandler handler) { + availableImageHandlers.add(handler); + } + + public void addUnavailableImageHandler(UnavailableImageHandler handler) { + unavailableImageHandlers.add(handler); + } + + public void addSubscription(Subscription subscription, FragmentAssembler fragmentAssembler) { + debug("Adding subscription with session id {}", subscription.streamId()); + fragmentAssemblerHolders.add(new FragmentAssemblerHolder(subscription, fragmentAssembler)); + } + + public void removeSubscription(Subscription subscription) { + debug("Removing subscription with session id {}", subscription.streamId()); + fragmentAssemblerHolders.removeIf(s -> s.subscription == subscription); + } + + private void availableImageHandler(Image image, Subscription subscription, long joiningPosition, String sourceIdentity) { + availableImageHandlers + .forEach(handler -> handler.onAvailableImage(image, subscription, joiningPosition, sourceIdentity)); + } + + private void unavailableImage(Image image, Subscription subscription, long position) { + unavailableImageHandlers + .forEach(handler -> handler.onUnavailableImage(image, subscription, position)); + } + + public Aeron getAeron() { + return aeron; + } + + void poll() { + Thread dutyThread = new Thread(() -> { + for (;;) { + int poll = 0; + for (FragmentAssemblerHolder sh : fragmentAssemblerHolders) { + try { + poll += sh.subscription.poll(sh.fragmentAssembler, Integer.MAX_VALUE); + } catch (Throwable t) { + t.printStackTrace(); + } + } + SERVER_IDLE_STRATEGY.idle(poll); + } + }); + dutyThread.setName("reactive-socket-aeron-server"); + dutyThread.setDaemon(true); + dutyThread.start(); + } +} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java new file mode 100644 index 000000000..d7e6c8c99 --- /dev/null +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java @@ -0,0 +1,101 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.server; + +import io.reactivesocket.aeron.internal.Loggable; +import io.reactivesocket.rx.Completable; +import io.reactivesocket.Frame; +import io.reactivesocket.aeron.internal.AeronUtil; +import io.reactivesocket.aeron.internal.MessageType; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.agrona.BitUtil; + +import java.nio.ByteBuffer; + +/** + * Subscription used by the AeronServerDuplexConnection to handle incoming frames and send them + * on a publication. + * + * @see AeronServerDuplexConnection + */ +class ServerSubscription implements Subscriber, Loggable { + + /** + * Count is used to by the client to round-robin request between threads. + */ + private short count; + + private final Publication publication; + + private final Completable completable; + + public ServerSubscription(Publication publication, Completable completable) { + this.publication = publication; + this.completable = completable; + } + + @Override + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); + } + + @Override + public void onNext(Frame frame) { + + if (isTraceEnabled()) { + trace("Server with publication session id {} sending frame => {}", publication.sessionId(), frame.toString()); + } + + final ByteBuffer byteBuffer = frame.getByteBuffer(); + final int length = frame.length() + BitUtil.SIZE_OF_INT; + + try { + AeronUtil.tryClaimOrOffer(publication, (offset, buffer) -> { + buffer.putShort(offset, getCount()); + buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); + buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, frame.offset(), frame.length()); + }, length); + } catch (Throwable t) { + onError(t); + } + + if (isTraceEnabled()) { + trace("Server with publication session id {} sent frame with ReactiveSocket stream id => {}", publication.sessionId(), frame.getStreamId()); + } + + + } + + @Override + public void onError(Throwable t) { + completable.error(t); + } + + @Override + public void onComplete() { + if (isTraceEnabled()) { + trace("Server with publication session id {} completing", publication.sessionId()); + } + completable.success(); + } + + private short getCount() { + return count++; + } + +} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/exceptions/SetupException.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/exceptions/SetupException.java new file mode 100644 index 000000000..bad9572c2 --- /dev/null +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/exceptions/SetupException.java @@ -0,0 +1,23 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.exceptions; + +public class SetupException extends Throwable { + public SetupException(String message) { + super(message); + } + +} diff --git a/settings.gradle b/settings.gradle index a5d203c41..7875e24aa 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,6 @@ rootProject.name='reactivesocket-aeron-rxjava' +include 'reactivesocket-aeron-rxjava', \ + 'reactivesocket-aeron-client', \ + 'reactivesocket-aeron-core', \ + 'reactivesocket-aeron-examples', + 'reactivesocket-aeron-server' diff --git a/src/main/java/io/reactivesocket/aeron/internal/concurrent/AbstractConcurrentArrayQueue.java b/src/main/java/io/reactivesocket/aeron/internal/concurrent/AbstractConcurrentArrayQueue.java deleted file mode 100644 index c8e489afe..000000000 --- a/src/main/java/io/reactivesocket/aeron/internal/concurrent/AbstractConcurrentArrayQueue.java +++ /dev/null @@ -1,278 +0,0 @@ -package io.reactivesocket.aeron.internal.concurrent; - -/* - * Copyright 2015 Real Logic Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import java.util.Collection; - - import uk.co.real_logic.agrona.BitUtil; - - import java.util.*; - - import static uk.co.real_logic.agrona.UnsafeAccess.UNSAFE; - -/** - * Pad out a cacheline to the left of a tail to prevent false sharing. - */ -class AbstractConcurrentArrayQueuePadding1 -{ - @SuppressWarnings("unused") - protected long p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15; -} - -/** - * Value for the tail that is expected to be padded. - */ -class AbstractConcurrentArrayQueueTail extends AbstractConcurrentArrayQueuePadding1 -{ - protected volatile long tail; -} - -/** - * Pad out a cacheline between the tail and the head to prevent false sharing. - */ -class AbstractConcurrentArrayQueuePadding2 extends AbstractConcurrentArrayQueueTail -{ - @SuppressWarnings("unused") - protected long p16, p17, p18, p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30; -} - -/** - * Value for the head that is expected to be padded. - */ -class AbstractConcurrentArrayQueueHead extends AbstractConcurrentArrayQueuePadding2 -{ - protected volatile long head; -} - -/** - * Pad out a cacheline between the tail and the head to prevent false sharing. - */ -class AbstractConcurrentArrayQueuePadding3 extends AbstractConcurrentArrayQueueHead -{ - @SuppressWarnings("unused") - protected long p31, p32, p33, p34, p35, p36, p37, p38, p39, p40, p41, p42, p43, p44, p45; -} - -/** - * Left over immutable queue fields. - */ -public abstract class AbstractConcurrentArrayQueue - extends AbstractConcurrentArrayQueuePadding3 - implements QueuedPipe -{ - protected static final long TAIL_OFFSET; - protected static final long HEAD_OFFSET; - protected static final int BUFFER_ARRAY_BASE; - protected static final int SHIFT_FOR_SCALE; - - static - { - try - { - BUFFER_ARRAY_BASE = UNSAFE.arrayBaseOffset(Object[].class); - SHIFT_FOR_SCALE = BitUtil.calculateShiftForScale(UNSAFE.arrayIndexScale(Object[].class)); - TAIL_OFFSET = UNSAFE.objectFieldOffset(AbstractConcurrentArrayQueueTail.class.getDeclaredField("tail")); - HEAD_OFFSET = UNSAFE.objectFieldOffset(AbstractConcurrentArrayQueueHead.class.getDeclaredField("head")); - } - catch (final Exception ex) - { - throw new RuntimeException(ex); - } - } - - protected final long mask; - protected final int capacity; - protected final E[] buffer; - - @SuppressWarnings("unchecked") - public AbstractConcurrentArrayQueue(final int requestedCapacity) - { - capacity = BitUtil.findNextPositivePowerOfTwo(requestedCapacity); - mask = capacity - 1; - buffer = (E[])new Object[capacity]; - } - - public long addedCount() - { - return tail; - } - - public long removedCount() - { - return head; - } - - public int capacity() - { - return capacity; - } - - public int remainingCapacity() - { - return capacity() - size(); - } - - @SuppressWarnings("unchecked") - public E peek() - { - return (E)UNSAFE.getObjectVolatile(buffer, sequenceToBufferOffset(head, mask)); - } - - public boolean add(final E e) - { - if (offer(e)) - { - return true; - } - - throw new IllegalStateException("Queue is full"); - } - - public E remove() - { - final E e = poll(); - if (null == e) - { - throw new NoSuchElementException("Queue is empty"); - } - - return e; - } - - public E element() - { - final E e = peek(); - if (null == e) - { - throw new NoSuchElementException("Queue is empty"); - } - - return e; - } - - public boolean isEmpty() - { - return tail == head; - } - - public boolean contains(final Object o) - { - if (null == o) - { - return false; - } - - final Object[] buffer = this.buffer; - - for (long i = head, limit = tail; i < limit; i++) - { - final Object e = UNSAFE.getObjectVolatile(buffer, sequenceToBufferOffset(i, mask)); - if (o.equals(e)) - { - return true; - } - } - - return false; - } - - public Iterator iterator() - { - throw new UnsupportedOperationException(); - } - - public Object[] toArray() - { - throw new UnsupportedOperationException(); - } - - public T[] toArray(final T[] a) - { - throw new UnsupportedOperationException(); - } - - public boolean remove(final Object o) - { - throw new UnsupportedOperationException(); - } - - public boolean containsAll(final Collection c) - { - for (final Object o : c) - { - if (!contains(o)) - { - return false; - } - } - - return true; - } - - public boolean addAll(final Collection c) - { - for (final E e : c) - { - add(e); - } - - return true; - } - - public boolean removeAll(final Collection c) - { - throw new UnsupportedOperationException(); - } - - public boolean retainAll(final Collection c) - { - throw new UnsupportedOperationException(); - } - - public void clear() - { - Object value; - do - { - value = poll(); - } - while (null != value); - } - - public int size() - { - long currentHeadBefore; - long currentTail; - long currentHeadAfter = head; - - do - { - currentHeadBefore = currentHeadAfter; - currentTail = tail; - currentHeadAfter = head; - - } - while (currentHeadAfter != currentHeadBefore); - - return (int)(currentTail - currentHeadAfter); - } - - public static long sequenceToBufferOffset(final long sequence, final long mask) - { - return BUFFER_ARRAY_BASE + ((sequence & mask) << SHIFT_FOR_SCALE); - } -} \ No newline at end of file diff --git a/src/main/java/io/reactivesocket/aeron/internal/concurrent/ManyToManyConcurrentArrayQueue.java b/src/main/java/io/reactivesocket/aeron/internal/concurrent/ManyToManyConcurrentArrayQueue.java deleted file mode 100644 index 96e3c0c62..000000000 --- a/src/main/java/io/reactivesocket/aeron/internal/concurrent/ManyToManyConcurrentArrayQueue.java +++ /dev/null @@ -1,173 +0,0 @@ -package io.reactivesocket.aeron.internal.concurrent; - -/* - * Copyright 2015 Real Logic Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -import java.util.Collection; -import java.util.function.Consumer; - -import static uk.co.real_logic.agrona.UnsafeAccess.UNSAFE; - -/** - * Many producer to many consumer concurrent queue that is array backed. - * - * - * This is a Java port of the - * MPMC queue by Dmitry Vyukov. - * - * Note: This queue breaks the contract for peek and poll in that it can return null when the queue has no node available - * but is not empty. This is a conflated design issue in the Queue implementation. If you wish to check for empty then call - * {@link ManyToManyConcurrentArrayQueue#isEmpty()}. - * - * @param type of the elements stored in the {@link java.util.Queue}. - */ -public class ManyToManyConcurrentArrayQueue extends AbstractConcurrentArrayQueue -{ - private static final int SEQUENCES_ARRAY_BASE; - - static - { - try - { - SEQUENCES_ARRAY_BASE = UNSAFE.arrayBaseOffset(long[].class); - } - catch (final Exception ex) - { - throw new RuntimeException(ex); - } - } - - private final long[] sequences; - - public ManyToManyConcurrentArrayQueue(final int requestedCapacity) - { - super(requestedCapacity); - - final long[] sequences = new long[capacity]; - - for (int i = 0, size = capacity; i < size; i++) - { - final long sequenceOffset = sequenceArrayOffset(i, mask); - UNSAFE.putOrderedLong(sequences, sequenceOffset, i); - } - - this.sequences = sequences; - } - - public boolean offer(final E e) - { - if (null == e) - { - throw new NullPointerException("element cannot be null"); - } - - final long mask = this.mask; - final long[] sequences = this.sequences; - - do - { - final long currentTail = tail; - final long sequenceOffset = sequenceArrayOffset(currentTail, mask); - final long sequence = UNSAFE.getLongVolatile(sequences, sequenceOffset); - - if (sequence < currentTail) - { - return false; - } - - if (UNSAFE.compareAndSwapLong(this, TAIL_OFFSET, currentTail, currentTail + 1L)) - { - UNSAFE.putObject(buffer, sequenceToBufferOffset(currentTail, mask), e); - UNSAFE.putOrderedLong(sequences, sequenceOffset, currentTail + 1L); - - return true; - } - } - while (true); - } - - @SuppressWarnings("unchecked") - public E poll() - { - final long[] sequences = this.sequences; - final long mask = this.mask; - - do - { - final long currentHead = head; - final long sequenceOffset = sequenceArrayOffset(currentHead, mask); - final long sequence = UNSAFE.getLongVolatile(sequences, sequenceOffset); - final long attemptedHead = currentHead + 1L; - - if (sequence < attemptedHead) - { - return null; - } - - if (UNSAFE.compareAndSwapLong(this, HEAD_OFFSET, currentHead, attemptedHead)) - { - final long elementOffset = sequenceToBufferOffset(currentHead, mask); - - final Object e = UNSAFE.getObject(buffer, elementOffset); - UNSAFE.putObject(buffer, elementOffset, null); - UNSAFE.putOrderedLong(sequences, sequenceOffset, attemptedHead + mask); - - return (E)e; - } - } - while (true); - } - - public int drain(final Consumer elementHandler) - { - final int size = size(); - int count = 0; - - E e; - while (count < size && null != (e = poll())) - { - elementHandler.accept(e); - ++count; - } - - return count; - } - - public int drainTo(final Collection target, final int limit) - { - int count = 0; - - while (count < limit) - { - final E e = poll(); - if (null == e) - { - break; - } - - target.add(e); - ++count; - } - - return count; - } - - private static long sequenceArrayOffset(final long sequence, final long mask) - { - return SEQUENCES_ARRAY_BASE + ((sequence & mask) << 3); - } -} \ No newline at end of file diff --git a/src/main/java/io/reactivesocket/aeron/internal/concurrent/Pipe.java b/src/main/java/io/reactivesocket/aeron/internal/concurrent/Pipe.java deleted file mode 100644 index f76f3231a..000000000 --- a/src/main/java/io/reactivesocket/aeron/internal/concurrent/Pipe.java +++ /dev/null @@ -1,75 +0,0 @@ -package io.reactivesocket.aeron.internal.concurrent; - -/* - * Copyright 2015 Real Logic Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import java.util.Collection; -import java.util.function.Consumer; - -/** - * A container for items processed in sequence - */ -public interface Pipe -{ - /** - * The number of items added to this container since creation. - * - * @return the number of items added. - */ - long addedCount(); - - /** - * The number of items removed from this container since creation. - * - * @return the number of items removed. - */ - long removedCount(); - - /** - * The maximum capacity of this container to hold items. - * - * @return the capacity of the container. - */ - int capacity(); - - /** - * Get the remaining capacity for elements in the container given the current size. - * - * @return remaining capacity of the container - */ - int remainingCapacity(); - - /** - * Invoke a {@link Consumer} callback on each elements to drain the collection of elements until it is empty. - * - * If possible, implementations should use smart batching to best handle burst traffic. - * - * @param elementHandler to callback for processing elements - * @return the number of elements drained - */ - int drain(Consumer elementHandler); - - /** - * Drain available elements into the provided {@link java.util.Collection} up to a provided maximum limit of elements. - * - * If possible, implementations should use smart batching to best handle burst traffic. - * - * @param target in to which elements are drained. - * @param limit of the maximum number of elements to drain. - * @return the number of elements actually drained. - */ - int drainTo(Collection target, int limit); -} \ No newline at end of file diff --git a/src/perf/java/io/reactivesocket/aeron/old/client/PollingActionPerf.java b/src/perf/java/io/reactivesocket/aeron/client/PollingActionPerf.java similarity index 72% rename from src/perf/java/io/reactivesocket/aeron/old/client/PollingActionPerf.java rename to src/perf/java/io/reactivesocket/aeron/client/PollingActionPerf.java index 14e25adbf..9b65ee478 100644 --- a/src/perf/java/io/reactivesocket/aeron/old/client/PollingActionPerf.java +++ b/src/perf/java/io/reactivesocket/aeron/client/PollingActionPerf.java @@ -1,28 +1,8 @@ -package io.reactivesocket.aeron.old.client; +package io.reactivesocket.aeron.client; -import io.reactivesocket.aeron.client.ClientAeronManager; -import io.reactivesocket.aeron.client.PollingAction; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.Setup; -import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.annotations.Threads; -import rx.functions.Func1; -import uk.co.real_logic.aeron.DummySubscription; -import uk.co.real_logic.aeron.Subscription; -import uk.co.real_logic.aeron.logbuffer.Header; -import uk.co.real_logic.agrona.DirectBuffer; - -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.locks.ReentrantLock; - public class PollingActionPerf { - +/* @State(Scope.Benchmark) public static class TestState { PollingAction pa; @@ -95,5 +75,6 @@ public void call3(TestState state) { public void call4(TestState state) { state.pa.call(); } + */ } diff --git a/src/test/java/io/reactivesocket/aeron/old/client/ReactiveSocketAeronTest.java b/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java similarity index 84% rename from src/test/java/io/reactivesocket/aeron/old/client/ReactiveSocketAeronTest.java rename to src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java index 9779f216c..d0a22904f 100644 --- a/src/test/java/io/reactivesocket/aeron/old/client/ReactiveSocketAeronTest.java +++ b/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java @@ -13,38 +13,41 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.reactivesocket.aeron.old.client; +package io.reactivesocket.aeron.client; import io.reactivesocket.ConnectionSetupHandler; import io.reactivesocket.ConnectionSetupPayload; import io.reactivesocket.Frame; import io.reactivesocket.Payload; +import io.reactivesocket.ReactiveSocket; import io.reactivesocket.RequestHandler; import io.reactivesocket.aeron.TestUtil; -import io.reactivesocket.aeron.old.server.ReactiveSocketAeronServer; +import io.reactivesocket.aeron.server.ReactiveSocketAeronServer; import io.reactivesocket.exceptions.SetupException; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; import rx.Observable; import rx.RxReactiveStreams; -import rx.schedulers.Schedulers; +import rx.Subscriber; import uk.co.real_logic.aeron.driver.MediaDriver; +import java.net.InetSocketAddress; import java.nio.ByteBuffer; -import java.util.Random; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.locks.LockSupport; /** * Aeron integration tests */ @Ignore public class ReactiveSocketAeronTest { + static { + // Uncomment to enable tracing + //System.setProperty("reactivesocket.aeron.tracingEnabled", "true"); + } + @BeforeClass public static void init() { final MediaDriver.Context context = new MediaDriver.Context(); @@ -53,6 +56,118 @@ public static void init() { final MediaDriver mediaDriver = MediaDriver.launch(context); } + @Test(timeout = 3000) + public void testRequestReponse1() throws Exception { + requestResponseN(1); + } + + @Test(timeout = 3000) + public void testRequestReponse10() throws Exception { + requestResponseN(10); + } + + @Test(timeout = 30000) + public void testRequestReponse10_000() throws Exception { + requestResponseN(10_000); + } + + public void requestResponseN(int count) throws Exception { + AtomicLong server = new AtomicLong(); + ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { + @Override + public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + return new RequestHandler() { + Frame frame = Frame.from(ByteBuffer.allocate(1)); + + @Override + public Publisher handleRequestResponse(Payload payload) { + String request = TestUtil.byteToString(payload.getData()); + //System.out.println(Thread.currentThread() + " Server got => " + request); + Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong => " + server.incrementAndGet(), null)); + return RxReactiveStreams.toPublisher(pong); + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher payloads) { + return null; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + return null; + } + + @Override + public Publisher handleSubscription(Payload payload) { + return null; + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return null; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + }; + } + }); + + InetSocketAddress listenAddress = new InetSocketAddress("localhost", 39790); + InetSocketAddress clientAddress = new InetSocketAddress("localhost", 39790); + + AeronClientDuplexConnectionFactory cf = AeronClientDuplexConnectionFactory.getInstance(); + cf.addSocketAddressToHandleResponses(listenAddress); + Publisher udpConnection = cf.createUDPConnection(clientAddress); + + System.out.println("Creating new duplex connection"); + AeronClientDuplexConnection connection = RxReactiveStreams.toObservable(udpConnection).toBlocking().single(); + System.out.println("Created duplex connection"); + + ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection(connection, ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS)); + reactiveSocket.startAndWait(); + + CountDownLatch latch = new CountDownLatch(count); + + Observable + .range(1, count) + .flatMap(i -> { + System.out.println("pinging => " + i); + Payload payload = TestUtil.utf8EncodedPayload("ping => " + i, null); + Publisher publisher = reactiveSocket.requestResponse(payload); + return RxReactiveStreams + .toObservable(publisher) + .doOnNext(f -> { + System.out.println("Got => " + i); + }) + .doOnNext(f -> latch.countDown()); + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println("I HAVE COMPLETED $$$$$$$$$$$$$$$$$$$$$$$$$$$$"); + } + + @Override + public void onError(Throwable e) { + System.out.println(Thread.currentThread() + " counted to => " + latch.getCount()); + e.printStackTrace(); + } + + @Override + public void onNext(Payload payload) { + + } + }); + + latch.await(); + } + +/* + + @Test(timeout = 100000) public void testRequestReponse() throws Exception { AtomicLong server = new AtomicLong(); @@ -711,6 +826,6 @@ public void onNext(Payload s) { System.out.println("--------------------------------------------------------------------------------"); } - } + }*/ } diff --git a/src/test/resources/simplelogger.properties b/src/test/resources/simplelogger.properties index 5dd885ad4..463129958 100644 --- a/src/test/resources/simplelogger.properties +++ b/src/test/resources/simplelogger.properties @@ -4,8 +4,8 @@ # Default logging detail level for all instances of SimpleLogger. # Must be one of ("trace", "debug", "info", "warn", or "error"). # If not specified, defaults to "info". -org.slf4j.simpleLogger.defaultLogLevel=debug -#org.slf4j.simpleLogger.defaultLogLevel=trace +#org.slf4j.simpleLogger.defaultLogLevel=debug +org.slf4j.simpleLogger.defaultLogLevel=trace # Logging detail level for a SimpleLogger instance named "xxxxx". # Must be one of ("trace", "debug", "info", "warn", or "error"). From f24f9f67be995677470988fab8a5e7aaec9fd9cb Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Sun, 4 Oct 2015 21:31:49 -0700 Subject: [PATCH 054/105] created modules for client, server, etc --- .../client/AeronClientDuplexConnection.java | 2 +- .../AeronClientDuplexConnectionFactory.java | 4 +- .../aeron/client/ClientAeronManager.java | 43 +-- .../aeron/client/PollingAction.java | 24 +- .../aeron/example/MediaDriver.java | 47 ---- .../io/reactivesocket/aeron/example/Ping.java | 141 ---------- .../io/reactivesocket/aeron/example/Pong.java | 136 --------- .../aeron/internal/AeronUtil.java | 163 ----------- .../aeron/internal/Constants.java | 41 --- .../aeron/internal/Loggable.java | 53 ---- .../aeron/internal/MessageType.java | 59 ---- .../aeron/internal/NotConnectedException.java | 25 -- .../server/AeronServerDuplexConnection.java | 101 ------- .../server/ReactiveSocketAeronServer.java | 206 -------------- .../aeron/server/ServerAeronManager.java | 120 -------- .../aeron/server/ServerSubscription.java | 101 ------- .../exceptions/SetupException.java | 23 -- .../aeron/internal/AeronUtil.java | 6 +- .../aeron/internal/TimedOutException.java | 9 + .../aeron/internal/AeronUtilTest.java | 41 +++ .../client/AeronClientDuplexConnection.java | 109 -------- .../AeronClientDuplexConnectionFactory.java | 262 ------------------ .../aeron/client/ClientAeronManager.java | 220 --------------- .../aeron/client/FrameHolder.java | 74 ----- .../aeron/client/PollingAction.java | 64 ----- .../aeron/example/MediaDriver.java | 47 ---- .../io/reactivesocket/aeron/example/Ping.java | 141 ---------- .../io/reactivesocket/aeron/example/Pong.java | 136 --------- .../aeron/internal/AeronUtil.java | 163 ----------- .../aeron/internal/Constants.java | 41 --- .../aeron/internal/Loggable.java | 53 ---- .../aeron/internal/MessageType.java | 59 ---- .../aeron/internal/NotConnectedException.java | 25 -- .../exceptions/SetupException.java | 23 -- reactivesocket-aeron-tests/build.gradle | 4 + .../aeron/client/PollingActionPerf.java | 0 .../jmh/InputWithIncrementingInteger.java | 0 .../aeron/jmh/LatchedObserver.java | 0 .../real_logic/aeron/DummySubscription.java | 0 .../io/reactivesocket/aeron/TestUtil.java | 0 .../aeron/client/ReactiveSocketAeronTest.java | 0 .../test/resources/simplelogger.properties | 0 settings.gradle | 6 +- 43 files changed, 69 insertions(+), 2703 deletions(-) delete mode 100644 reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/example/MediaDriver.java delete mode 100644 reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/example/Ping.java delete mode 100644 reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/example/Pong.java delete mode 100644 reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java delete mode 100644 reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/Constants.java delete mode 100644 reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/Loggable.java delete mode 100644 reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/MessageType.java delete mode 100644 reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java delete mode 100644 reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java delete mode 100644 reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java delete mode 100644 reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java delete mode 100644 reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java delete mode 100644 reactivesocket-aeron-client/src/main/java/io/reactivesocket/exceptions/SetupException.java create mode 100644 reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/TimedOutException.java create mode 100644 reactivesocket-aeron-core/src/test/java/io/reactivesocket/aeron/internal/AeronUtilTest.java delete mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java delete mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java delete mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java delete mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java delete mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/PollingAction.java delete mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/example/MediaDriver.java delete mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/example/Ping.java delete mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/example/Pong.java delete mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java delete mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/Constants.java delete mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/Loggable.java delete mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/MessageType.java delete mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java delete mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/exceptions/SetupException.java create mode 100644 reactivesocket-aeron-tests/build.gradle rename {src => reactivesocket-aeron-tests/src}/perf/java/io/reactivesocket/aeron/client/PollingActionPerf.java (100%) rename {src => reactivesocket-aeron-tests/src}/perf/java/io/reactivesocket/aeron/jmh/InputWithIncrementingInteger.java (100%) rename {src => reactivesocket-aeron-tests/src}/perf/java/io/reactivesocket/aeron/jmh/LatchedObserver.java (100%) rename {src => reactivesocket-aeron-tests/src}/perf/java/uk/co/real_logic/aeron/DummySubscription.java (100%) rename {src => reactivesocket-aeron-tests/src}/test/java/io/reactivesocket/aeron/TestUtil.java (100%) rename {src => reactivesocket-aeron-tests/src}/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java (100%) rename {src => reactivesocket-aeron-tests/src}/test/resources/simplelogger.properties (100%) diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java index c5905d446..fb5cf5e0e 100644 --- a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java +++ b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java @@ -67,7 +67,7 @@ public void addOutput(Publisher o, Completable callback) { @Override public void onSubscribe(Subscription s) { this.s = s; - s.request(Long.MAX_VALUE); + s.request(128); } diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java index d076dc8c9..a840c7eec 100644 --- a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java +++ b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java @@ -90,9 +90,7 @@ public void addSocketAddressToHandleResponses(SocketAddress socketAddress) { } void addUDPSocketAddressToHandleResponses(InetSocketAddress socketAddress) { - //String serverChannel = "udp://" + socketAddress.getHostName() + ":" + socketAddress.getPort(); - String serverChannel = "udp://localhost:39790"; - + String serverChannel = "udp://" + socketAddress.getHostName() + ":" + socketAddress.getPort(); manager.addSubscription( serverChannel, diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java index 30b71bd6f..377b6db06 100644 --- a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java +++ b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java @@ -21,16 +21,13 @@ import rx.functions.Func1; import rx.schedulers.Schedulers; import uk.co.real_logic.aeron.Aeron; -import uk.co.real_logic.aeron.AvailableImageHandler; import uk.co.real_logic.aeron.FragmentAssembler; import uk.co.real_logic.aeron.Image; import uk.co.real_logic.aeron.Subscription; import uk.co.real_logic.aeron.logbuffer.FragmentHandler; -import java.util.Optional; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantLock; /** * Class for managing the Aeron on the client side. @@ -52,12 +49,9 @@ private ClientAeronManager() { final Aeron.Context ctx = new Aeron.Context(); ctx.errorHandler(t -> error("an exception occurred", t)); - ctx.availableImageHandler(new AvailableImageHandler() { - @Override - public void onAvailableImage(Image image, Subscription subscription, long joiningPosition, String sourceIdentity) { - debug("New image available with session id => {} and sourceIdentity => {} and subscription => {}", image.sessionId(), sourceIdentity, subscription.toString()); - } - }); + ctx.availableImageHandler((Image image, Subscription subscription, long joiningPosition, String sourceIdentity) -> + debug("New image available with session id => {} and sourceIdentity => {} and subscription => {}", image.sessionId(), sourceIdentity, subscription.toString()) + ); aeron = Aeron.connect(ctx); @@ -68,31 +62,6 @@ public static ClientAeronManager getInstance() { return INSTANCE; } - /** - * Finds a SubscriptionGroup from the sessionId of one the servers in the group - * - * @param sessionId the session id whose SubscriptionGroup you want to find - * @return an Optional of SubscriptionGroup - */ - public Optional find(final int sessionId) { - return subscriptionGroups - .stream() - .filter(sg -> { - boolean found = false; - - for (Subscription subscription : sg.subscriptions) { - Image image = subscription.getImage(sessionId); - if (image != null) { - found = true; - break; - } - } - - return found; - }) - .findFirst(); - } - /** * Adds a ClientAction on the a list that is run by the polling loop. * @@ -144,13 +113,11 @@ public void addSubscription(String subscriptionChannel, int streamId, Func1 subscriptionGroups; private final List clientActions; public PollingAction( - ReentrantLock pollLock, - ReentrantLock clientActionLock, int threadId, List subscriptionGroups, List clientActions) { - this.pollLock = pollLock; - this.clientActionLock = clientActionLock; this.threadId = threadId; this.subscriptionGroups = subscriptionGroups; this.clientActions = clientActions; @@ -39,21 +31,11 @@ public void call() { poll = subscription.poll(sg.getFragmentAssembler(threadId), Integer.MAX_VALUE); } while (poll > 0); - //if (clientActionLock.tryLock()) { - for (ClientAeronManager.ClientAction action : clientActions) { - action.call(threadId); - } - //} + for (ClientAeronManager.ClientAction action : clientActions) { + action.call(threadId); + } } catch (Throwable t) { error("error polling aeron subscription on thread with id " + threadId, t); - } finally { - if (pollLock.isHeldByCurrentThread()) { - pollLock.unlock(); - } - - if (clientActionLock.isHeldByCurrentThread()) { - clientActionLock.unlock(); - } } } diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/example/MediaDriver.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/example/MediaDriver.java deleted file mode 100644 index 9dac34fc0..000000000 --- a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/example/MediaDriver.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.aeron.example; - -import uk.co.real_logic.aeron.driver.ThreadingMode; -import uk.co.real_logic.agrona.concurrent.BackoffIdleStrategy; -import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; - -/** - * Created by rroeser on 8/16/15. - */ -public class MediaDriver { - public static void main(String... args) { - ThreadingMode threadingMode = ThreadingMode.SHARED; - - boolean dedicated = Boolean.getBoolean("dedicated"); - - if (dedicated) { - threadingMode = ThreadingMode.DEDICATED; - } - - System.out.println("ThreadingMode => " + threadingMode); - - final uk.co.real_logic.aeron.driver.MediaDriver.Context ctx = new uk.co.real_logic.aeron.driver.MediaDriver.Context() - .threadingMode(threadingMode) - .dirsDeleteOnStart(true) - .conductorIdleStrategy(new BackoffIdleStrategy(1, 1, 100, 1000)) - .receiverIdleStrategy(new NoOpIdleStrategy()) - .senderIdleStrategy(new NoOpIdleStrategy()); - - final uk.co.real_logic.aeron.driver.MediaDriver ignored = uk.co.real_logic.aeron.driver.MediaDriver.launch(ctx); - - } -} diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/example/Ping.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/example/Ping.java deleted file mode 100644 index a30943819..000000000 --- a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/example/Ping.java +++ /dev/null @@ -1,141 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.aeron.example; - -import io.reactivesocket.ConnectionSetupPayload; -import io.reactivesocket.Payload; -import io.reactivesocket.ReactiveSocket; -import io.reactivesocket.aeron.client.AeronClientDuplexConnection; -import io.reactivesocket.aeron.client.AeronClientDuplexConnectionFactory; -import io.reactivesocket.aeron.client.FrameHolder; -import org.HdrHistogram.Recorder; -import org.reactivestreams.Publisher; -import rx.Observable; -import rx.RxReactiveStreams; -import rx.Subscriber; -import rx.schedulers.Schedulers; - -import java.net.InetSocketAddress; -import java.nio.ByteBuffer; -import java.util.Random; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -/** - * Created by rroeser on 8/16/15. - */ -public class Ping { - - public static void main(String... args) throws Exception { - String host = System.getProperty("host", "localhost"); - String server = System.getProperty("server", "localhost"); - - System.out.println("Setting host to => " + host); - - System.out.println("Setting ping is listening to => " + server); - - - byte[] key = new byte[4]; - //byte[] key = new byte[BitUtil.SIZE_OF_INT]; - Random r = new Random(); - r.nextBytes(key); - - System.out.println("Sending data of size => " + key.length); - - InetSocketAddress listenAddress = new InetSocketAddress("localhost", 39790); - InetSocketAddress clientAddress = new InetSocketAddress("localhost", 39790); - - AeronClientDuplexConnectionFactory cf = AeronClientDuplexConnectionFactory.getInstance(); - cf.addSocketAddressToHandleResponses(listenAddress); - Publisher udpConnection = cf.createAeronClientDuplexConnection(clientAddress); - - System.out.println("Creating new duplex connection"); - AeronClientDuplexConnection connection = RxReactiveStreams.toObservable(udpConnection).toBlocking().single(); - System.out.println("Created duplex connection"); - - ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection(connection, ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS)); - reactiveSocket.startAndWait(); - - CountDownLatch latch = new CountDownLatch(Integer.MAX_VALUE); - - final Recorder histogram = new Recorder(3600000000000L, 3); - - Schedulers - .computation() - .createWorker() - .schedulePeriodically(() -> { - System.out.println("---- FRAME HOLDER HISTO ----"); - FrameHolder.histogram.getIntervalHistogram().outputPercentileDistribution(System.out, 5, 1000.0, false); - System.out.println("---- FRAME HOLDER HISTO ----"); - - System.out.println("---- PING/ PONG HISTO ----"); - histogram.getIntervalHistogram().outputPercentileDistribution(System.out, 5, 1000.0, false); - System.out.println("---- PING/ PONG HISTO ----"); - - - }, 10, 10, TimeUnit.SECONDS); - - Observable - .range(1, Integer.MAX_VALUE) - .flatMap(i -> { - long start = System.nanoTime(); - - Payload keyPayload = new Payload() { - ByteBuffer data = ByteBuffer.wrap(key); - ByteBuffer metadata = ByteBuffer.allocate(0); - - public ByteBuffer getData() { - return data; - } - - @Override - public ByteBuffer getMetadata() { - return metadata; - } - }; - - return RxReactiveStreams - .toObservable( - reactiveSocket - .requestResponse(keyPayload)) - .doOnNext(s -> { - long diff = System.nanoTime() - start; - histogram.recordValue(diff); - }); - }) - .subscribe(new Subscriber() { - @Override - public void onCompleted() { - - } - - @Override - public void onError(Throwable e) { - e.printStackTrace(); - } - - @Override - public void onNext(Payload payload) { - latch.countDown(); - } - }); - - latch.await(); - System.out.println("Sent => " + Integer.MAX_VALUE); - System.exit(0); - } - -} diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/example/Pong.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/example/Pong.java deleted file mode 100644 index a8edd4cc3..000000000 --- a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/example/Pong.java +++ /dev/null @@ -1,136 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.aeron.example; - -import io.reactivesocket.ConnectionSetupHandler; -import io.reactivesocket.ConnectionSetupPayload; -import io.reactivesocket.Payload; -import io.reactivesocket.RequestHandler; -import io.reactivesocket.aeron.server.ReactiveSocketAeronServer; -import io.reactivesocket.exceptions.SetupException; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; -import uk.co.real_logic.aeron.driver.ThreadingMode; -import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; - -import java.nio.ByteBuffer; -import java.util.Random; - -/** - * Created by rroeser on 8/16/15. - */ -public class Pong { - - public static void startDriver() { - ThreadingMode threadingMode = ThreadingMode.SHARED; - - boolean dedicated = Boolean.getBoolean("dedicated"); - - if (true) { - threadingMode = ThreadingMode.DEDICATED; - } - - System.out.println("ThreadingMode => " + threadingMode); - - final uk.co.real_logic.aeron.driver.MediaDriver.Context ctx = new uk.co.real_logic.aeron.driver.MediaDriver.Context() - .threadingMode(threadingMode) - .conductorIdleStrategy(new NoOpIdleStrategy()) - .receiverIdleStrategy(new NoOpIdleStrategy()) - .senderIdleStrategy(new NoOpIdleStrategy()); - - final uk.co.real_logic.aeron.driver.MediaDriver ignored = uk.co.real_logic.aeron.driver.MediaDriver.launchEmbedded(ctx); - } - - public static void main(String... args) { - // startDriver(); - - String host = System.getProperty("host", "localhost"); - - System.out.println("Setting host to => " + host); - - byte[] response = new byte[1024]; - //byte[] response = new byte[1024]; - Random r = new Random(); - r.nextBytes(response); - - System.out.println("Sending data of size => " + response.length); - - ReactiveSocketAeronServer server = ReactiveSocketAeronServer.create(host, 39790, new ConnectionSetupHandler() { - @Override - public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { - return new RequestHandler() { - @Override - public Publisher handleRequestResponse(Payload payload) { - long time = System.currentTimeMillis(); - - Publisher publisher = new Publisher() { - @Override - public void subscribe(Subscriber s) { - Payload responsePayload = new Payload() { - ByteBuffer data = ByteBuffer.wrap(response); - ByteBuffer metadata = ByteBuffer.allocate(0); - - public ByteBuffer getData() { - return data; - } - - @Override - public ByteBuffer getMetadata() { - return metadata; - } - }; - - s.onNext(responsePayload); - - long diff = System.currentTimeMillis() - time; - //timer.update(diff, TimeUnit.NANOSECONDS); - s.onComplete(); - } - }; - - return publisher; - } - - @Override - public Publisher handleRequestStream(Payload payload) { - return null; - } - - @Override - public Publisher handleSubscription(Payload payload) { - return null; - } - - @Override - public Publisher handleFireAndForget(Payload payload) { - return null; - } - - @Override - public Publisher handleChannel(Payload initialPayload, Publisher payloads) { - return null; - } - - @Override - public Publisher handleMetadataPush(Payload payload) { - return null; - } - }; - } - }); - } - -} diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java deleted file mode 100644 index 29ed9fa48..000000000 --- a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java +++ /dev/null @@ -1,163 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.aeron.internal; - -import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.aeron.logbuffer.BufferClaim; -import uk.co.real_logic.agrona.MutableDirectBuffer; -import uk.co.real_logic.agrona.concurrent.OneToOneConcurrentArrayQueue; -import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; - -import java.util.concurrent.TimeUnit; - -/** - * Utils for dealing with Aeron - */ -public class AeronUtil implements Loggable { - - private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); - - private static final ThreadLocal> unsafeBuffers - = ThreadLocal.withInitial(() -> new OneToOneConcurrentArrayQueue<>(16)); - - /** - * Sends a message using offer. This method will spin-lock if Aeron signals back pressure. - *

- * This method of sending data does need to know how long the message is. - * - * @param publication publication to send the message on - * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} - * that is send over Aeron - */ - public static void offer(Publication publication, BufferFiller fillBuffer, int length, int timeout, TimeUnit timeUnit) { - final MutableDirectBuffer buffer = getDirectBuffer(length); - fillBuffer.fill(0, buffer); - final long start = System.nanoTime(); - do { - if (timeout > 0) { - final long current = System.nanoTime(); - if ((current - start) > timeUnit.toNanos(timeout)) { - throw new RuntimeException("Timed out publishing data"); - } - } - final long offer = publication.offer(buffer); - if (offer >= 0) { - break; - } else if (Publication.NOT_CONNECTED == offer) { - throw new RuntimeException("not connected"); - } - } while (true); - - recycleDirectBuffer(buffer); - } - - /** - * Sends a message using tryClaim. This method will spin-lock if Aeron signals back pressure. The message - * being sent needs to be equal or smaller than Aeron's MTU size or an exception will be thrown. - *

- * In order to use this method of sending data you need to know the length of data. - * - * @param publication publication to send the message on - * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} - * that is send over Aeron - * @param length the length of data - */ - public static void tryClaim(Publication publication, BufferFiller fillBuffer, int length, int timeout, TimeUnit timeUnit) { - final BufferClaim bufferClaim = bufferClaims.get(); - final long start = System.nanoTime(); - do { - if (timeout > 0) { - final long current = System.nanoTime(); - if ((current - start) > timeUnit.toNanos(timeout)) { - throw new NotConnectedException(); - } - } - - final long offer = publication.tryClaim(length, bufferClaim); - if (offer >= 0) { - try { - final MutableDirectBuffer buffer = bufferClaim.buffer(); - final int offset = bufferClaim.offset(); - fillBuffer.fill(offset, buffer); - break; - } finally { - bufferClaim.commit(); - } - } else if (Publication.NOT_CONNECTED == offer) { - throw new NotConnectedException(); - } - } while (true); - } - - /** - * Attempts to send the data using tryClaim. If the message data length is large then the Aeron MTU - * size it will use offer instead. - * - * @param publication publication to send the message on - * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} - * that is send over Aeron - * @param length the length of data - */ - public static void tryClaimOrOffer(Publication publication, BufferFiller fillBuffer, int length) { - tryClaimOrOffer(publication, fillBuffer, length, -1, null); - } - - public static void tryClaimOrOffer(Publication publication, BufferFiller fillBuffer, int length, int timeout, TimeUnit timeUnit) { - if (length < Constants.AERON_MTU_SIZE) { - tryClaim(publication, fillBuffer, length, timeout, timeUnit); - } else { - offer(publication, fillBuffer, length, timeout, timeUnit); - } - } - - - /** - * Try to get a MutableDirectBuffer from a thread-safe pool for a given length. If the buffer found - * is bigger then the buffer in the pool creates a new buffer. If no buffer is found creates a new buffer - * - * @param length the requested length - * @return either a new MutableDirectBuffer or a recycled one that has the capacity to hold the data from the old one - */ - public static MutableDirectBuffer getDirectBuffer(int length) { - OneToOneConcurrentArrayQueue queue = unsafeBuffers.get(); - MutableDirectBuffer buffer = queue.poll(); - - if (buffer != null && buffer.capacity() < length) { - return buffer; - } else { - byte[] bytes = new byte[length]; - buffer = new UnsafeBuffer(bytes); - return buffer; - } - } - - /** - * Sends a DirectBuffer back to the thread pools to be recycled. - * - * @param directBuffer the DirectBuffer to recycle - */ - public static void recycleDirectBuffer(MutableDirectBuffer directBuffer) { - OneToOneConcurrentArrayQueue queue = unsafeBuffers.get(); - queue.offer(directBuffer); - } - - /** - * Implement this to fill a DirectBuffer passed in by either the offer or tryClaim methods. - */ - public interface BufferFiller { - void fill(int offset, MutableDirectBuffer buffer); - } -} diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/Constants.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/Constants.java deleted file mode 100644 index 19a5375cf..000000000 --- a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/Constants.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.aeron.internal; - -import uk.co.real_logic.agrona.concurrent.IdleStrategy; -import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; - -public final class Constants { - - private Constants() {} - - public static final int SERVER_STREAM_ID = 1; - - public static final int CLIENT_STREAM_ID = 2; - - public static final byte[] EMTPY = new byte[0]; - - public static final int QUEUE_SIZE = Integer.getInteger("reactivesocket.aeron.framesSendQueueSize", 262144); - - public static final IdleStrategy SERVER_IDLE_STRATEGY = new NoOpIdleStrategy(); - - public static final int CONCURRENCY = Integer.getInteger("reactivesocket.aeron.clientConcurrency", 2); - - public static final int AERON_MTU_SIZE = Integer.getInteger("aeron.mtu.length", 4096); - - public static final boolean TRACING_ENABLED = Boolean.getBoolean("reactivesocket.aeron.tracingEnabled"); - -} diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/Loggable.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/Loggable.java deleted file mode 100644 index d2d540d3d..000000000 --- a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/Loggable.java +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.aeron.internal; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * No more needed to type Logger LOGGER = LoggerFactory.getLogger.... - */ -public interface Loggable { - - default void info(String message, Object... args) { - logger().debug(message, args); - } - - default void error(String message, Throwable t) { - logger().error(message, t); - } - - default void debug(String message, Object... args) { - logger().debug(message, args); - } - - default void trace(String message, Object... args) { - logger().trace(message, args); - } - - default boolean isTraceEnabled() { - if (Constants.TRACING_ENABLED) { - return logger().isTraceEnabled(); - } else { - return false; - } - } - - default Logger logger() { - return LoggerFactory.getLogger(getClass()); - } -} diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/MessageType.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/MessageType.java deleted file mode 100644 index c294d232d..000000000 --- a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/MessageType.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.aeron.internal; - -/** - * Type of message being sent. - */ -public enum MessageType { - ESTABLISH_CONNECTION_REQUEST(0x01), - ESTABLISH_CONNECTION_RESPONSE(0x02), - CONNECTION_DISCONNECT(0x3), - FRAME(0x04); - - private static MessageType[] typesById; - - /** - * Index types by id for indexed lookup. - */ - static { - int max = 0; - - for (MessageType t : values()) { - max = Math.max(t.id, max); - } - - typesById = new MessageType[max + 1]; - - for (MessageType t : values()) { - typesById[t.id] = t; - } - } - - private final int id; - - MessageType(int id) { - this.id = id; - } - - public int getEncodedType() { - return id; - } - - public static MessageType from(int id) { - return typesById[id]; - } -} diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java deleted file mode 100644 index ce87e7712..000000000 --- a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.aeron.internal; - -public class NotConnectedException extends RuntimeException { - - @Override - public synchronized Throwable fillInStackTrace() { - return this; - } - -} diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java deleted file mode 100644 index 3aa8a611c..000000000 --- a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.aeron.server; - -import io.reactivesocket.DuplexConnection; -import io.reactivesocket.Frame; -import io.reactivesocket.aeron.internal.AeronUtil; -import io.reactivesocket.aeron.internal.Loggable; -import io.reactivesocket.aeron.internal.MessageType; -import io.reactivesocket.aeron.internal.NotConnectedException; -import io.reactivesocket.rx.Completable; -import io.reactivesocket.rx.Disposable; -import io.reactivesocket.rx.Observable; -import io.reactivesocket.rx.Observer; -import org.reactivestreams.Publisher; -import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.agrona.BitUtil; - -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.TimeUnit; - -public class AeronServerDuplexConnection implements DuplexConnection, Loggable { - private final Publication publication; - private final CopyOnWriteArrayList> subjects; - - public AeronServerDuplexConnection( - Publication publication) { - this.publication = publication; - this.subjects = new CopyOnWriteArrayList<>(); - } - - public List> getSubscriber() { - return subjects; - } - - @Override - public final Observable getInput() { - if (isTraceEnabled()) { - trace("-------getting input for publication session id {} ", publication.sessionId()); - } - - return new Observable() { - public void subscribe(Observer o) { - o.onSubscribe(new Disposable() { - @Override - public void dispose() { - if (isTraceEnabled()) { - trace("removing Observer for publication with session id {} ", publication.sessionId()); - } - - subjects.removeIf(s -> s == o); - } - }); - - subjects.add(o); - } - }; - } - - @Override - public void addOutput(Publisher o, Completable callback) { - o.subscribe(new ServerSubscription(publication, callback)); - } - - // TODO - this is bad - I need to queue this up somewhere and process this on the polling thread so it doesn't just block everything - void ackEstablishConnection(int ackSessionId) { - debug("Acking establish connection for session id => {}", ackSessionId); - for (;;) { - try { - AeronUtil.tryClaimOrOffer(publication, (offset, buffer) -> { - buffer.putShort(offset, (short) 0); - buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.ESTABLISH_CONNECTION_RESPONSE.getEncodedType()); - buffer.putInt(offset + BitUtil.SIZE_OF_INT, ackSessionId); - }, 2 * BitUtil.SIZE_OF_INT, 30, TimeUnit.SECONDS); - debug("Ack sent for session i => {}", ackSessionId); - } catch (NotConnectedException ne) { - continue; - } - break; - } - } - - @Override - public void close() { - publication.close(); - } -} diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java deleted file mode 100644 index ee678e4c6..000000000 --- a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java +++ /dev/null @@ -1,206 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.aeron.server; - -import io.reactivesocket.ConnectionSetupHandler; -import io.reactivesocket.Frame; -import io.reactivesocket.LeaseGovernor; -import io.reactivesocket.ReactiveSocket; -import io.reactivesocket.aeron.internal.Loggable; -import io.reactivesocket.aeron.internal.MessageType; -import io.reactivesocket.rx.Observer; -import uk.co.real_logic.aeron.Aeron; -import uk.co.real_logic.aeron.FragmentAssembler; -import uk.co.real_logic.aeron.Image; -import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.aeron.Subscription; -import uk.co.real_logic.aeron.logbuffer.Header; -import uk.co.real_logic.agrona.BitUtil; -import uk.co.real_logic.agrona.DirectBuffer; - -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; - -import static io.reactivesocket.aeron.internal.Constants.CLIENT_STREAM_ID; -import static io.reactivesocket.aeron.internal.Constants.SERVER_STREAM_ID; - -public class ReactiveSocketAeronServer implements AutoCloseable, Loggable { - private final int port; - - private final ConcurrentHashMap connections = new ConcurrentHashMap<>(); - - private final ConcurrentHashMap sockets = new ConcurrentHashMap<>(); - - private final Subscription subscription; - - private final ConnectionSetupHandler connectionSetupHandler; - - private final LeaseGovernor leaseGovernor; - - private static final ServerAeronManager manager = ServerAeronManager.getInstance(); - - private ReactiveSocketAeronServer(String host, int port, ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { - this.port = port; - this.connectionSetupHandler = connectionSetupHandler; - this.leaseGovernor = leaseGovernor; - - manager.addAvailableImageHander(this::availableImageHandler); - manager.addUnavailableImageHandler(this::unavailableImage); - - Aeron aeron = manager.getAeron(); - - final String serverChannel = "udp://" + host + ":" + port; - info("Starting new ReactiveSocketAeronServer on channel {}", serverChannel); - subscription = aeron.addSubscription(serverChannel, SERVER_STREAM_ID); - - FragmentAssembler fragmentAssembler = new FragmentAssembler(this::fragmentHandler); - manager.addSubscription(subscription, fragmentAssembler); - - - } - - void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) { - final int sessionId = header.sessionId(); - - short messageTypeInt = buffer.getShort(offset + BitUtil.SIZE_OF_SHORT); - MessageType type = MessageType.from(messageTypeInt); - - if (MessageType.FRAME == type) { - AeronServerDuplexConnection connection = connections.get(sessionId); - if (connection != null) { - List> subscribers = connection.getSubscriber(); - final Frame frame = Frame.from(buffer, BitUtil.SIZE_OF_INT + offset, length); - - if (isTraceEnabled()) { - trace("server received frame payload {} on session id {}", frame.getData(), sessionId); - } - - subscribers.forEach(s -> { - try { - s.onNext(frame); - } catch (Throwable t) { - s.onError(t); - } - }); - } - } else if (MessageType.ESTABLISH_CONNECTION_REQUEST == type) { - final long start = System.nanoTime(); - AeronServerDuplexConnection connection = null; - debug("Looking for an AeronServerDuplexConnection connection to ack establish connection for session id => {}", sessionId); - while (connection == null) { - final long current = System.nanoTime(); - - if (current - start > TimeUnit.SECONDS.toNanos(30)) { - throw new RuntimeException("unable to find connection to ack establish connection for session id => " + sessionId); - } - - connection = connections.get(sessionId); - } - debug("Found a connection to ack establish connection for session id => {}", sessionId); - connection.ackEstablishConnection(sessionId); - } else if (MessageType.CONNECTION_DISCONNECT == type) { - closeReactiveSocket(sessionId); - } - - } - - void availableImageHandler(Image image, Subscription subscription, long joiningPosition, String sourceIdentity) { - final int streamId = subscription.streamId(); - final int sessionId = image.sessionId(); - if (SERVER_STREAM_ID == streamId) { - debug("Handling new image for session id => {} and stream id => {}", streamId, sessionId); - final AeronServerDuplexConnection connection = connections.computeIfAbsent(sessionId, (_s) -> { - final String responseChannel = "udp://" + sourceIdentity.substring(0, sourceIdentity.indexOf(':')) + ":" + port; - Publication publication = manager.getAeron().addPublication(responseChannel, CLIENT_STREAM_ID); - int responseSessionId = publication.sessionId(); - debug("Creating new connection for responseChannel => {}, streamId => {}, and sessionId => {}", responseChannel, streamId, responseSessionId); - return new AeronServerDuplexConnection(publication); - }); - debug("Accepting ReactiveSocket connection"); - ReactiveSocket socket = ReactiveSocket.fromServerConnection( - connection, - connectionSetupHandler, - leaseGovernor, - new Consumer() { - @Override - public void accept(Throwable throwable) { - error(String.format("Error creating ReactiveSocket for Aeron session id => %d and stream id => %d", streamId, sessionId), throwable); - } - }); - - sockets.put(sessionId, socket); - - socket.startAndWait(); - } else { - debug("Unsupported stream id {}", streamId); - } - } - - void unavailableImage(Image image, Subscription subscription, long position) { - closeReactiveSocket(image.sessionId()); - } - - private void closeReactiveSocket(int sessionId) { - debug("closing connection for session id => " + sessionId); - ReactiveSocket socket = sockets.remove(sessionId); - connections.remove(sessionId); - - try { - socket.close(); - } catch (Throwable t) { - error("error closing socket for session id => " + sessionId, t); - } - } - - public boolean hasConnections() { - return !connections.isEmpty(); - } - - @Override - public void close() throws Exception { - manager.removeSubscription(subscription); - } - - /* - * Factory Methods - */ - public static ReactiveSocketAeronServer create(String host, int port, ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { - return new ReactiveSocketAeronServer(host, port, connectionSetupHandler, leaseGovernor); - } - - public static ReactiveSocketAeronServer create(int port, ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { - return create("127.0.0.1", port, connectionSetupHandler, leaseGovernor); - } - - public static ReactiveSocketAeronServer create(ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { - return create(39790, connectionSetupHandler, leaseGovernor); - } - - public static ReactiveSocketAeronServer create(String host, int port, ConnectionSetupHandler connectionSetupHandler) { - return new ReactiveSocketAeronServer(host, port, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); - } - - public static ReactiveSocketAeronServer create(int port, ConnectionSetupHandler connectionSetupHandler) { - return create("localhost", port, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); - } - - public static ReactiveSocketAeronServer create(ConnectionSetupHandler connectionSetupHandler) { - return create(39790, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); - } - -} diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java deleted file mode 100644 index 6db9b6789..000000000 --- a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java +++ /dev/null @@ -1,120 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.aeron.server; - -import io.reactivesocket.aeron.internal.Loggable; -import uk.co.real_logic.aeron.Aeron; -import uk.co.real_logic.aeron.AvailableImageHandler; -import uk.co.real_logic.aeron.FragmentAssembler; -import uk.co.real_logic.aeron.Image; -import uk.co.real_logic.aeron.Subscription; -import uk.co.real_logic.aeron.UnavailableImageHandler; - -import java.util.concurrent.CopyOnWriteArrayList; - -import static io.reactivesocket.aeron.internal.Constants.SERVER_IDLE_STRATEGY; - -/** - * Class that manages the Aeron instance and the server's polling thread. Lets you register more - * than one NewImageHandler to Aeron after the it's the Aeron instance has started - */ -public class ServerAeronManager implements Loggable { - private static final ServerAeronManager INSTANCE = new ServerAeronManager(); - - private final Aeron aeron; - - private CopyOnWriteArrayList availableImageHandlers = new CopyOnWriteArrayList<>(); - - private CopyOnWriteArrayList unavailableImageHandlers = new CopyOnWriteArrayList<>(); - - private CopyOnWriteArrayList fragmentAssemblerHolders = new CopyOnWriteArrayList<>(); - - private class FragmentAssemblerHolder { - private Subscription subscription; - private FragmentAssembler fragmentAssembler; - - public FragmentAssemblerHolder(Subscription subscription, FragmentAssembler fragmentAssembler) { - this.subscription = subscription; - this.fragmentAssembler = fragmentAssembler; - } - } - - public ServerAeronManager() { - final Aeron.Context ctx = new Aeron.Context(); - ctx.availableImageHandler(this::availableImageHandler); - ctx.unavailableImageHandler(this::unavailableImage); - ctx.errorHandler(t -> error("an exception occurred", t)); - - aeron = Aeron.connect(ctx); - - poll(); - } - - public static ServerAeronManager getInstance() { - return INSTANCE; - } - - public void addAvailableImageHander(AvailableImageHandler handler) { - availableImageHandlers.add(handler); - } - - public void addUnavailableImageHandler(UnavailableImageHandler handler) { - unavailableImageHandlers.add(handler); - } - - public void addSubscription(Subscription subscription, FragmentAssembler fragmentAssembler) { - debug("Adding subscription with session id {}", subscription.streamId()); - fragmentAssemblerHolders.add(new FragmentAssemblerHolder(subscription, fragmentAssembler)); - } - - public void removeSubscription(Subscription subscription) { - debug("Removing subscription with session id {}", subscription.streamId()); - fragmentAssemblerHolders.removeIf(s -> s.subscription == subscription); - } - - private void availableImageHandler(Image image, Subscription subscription, long joiningPosition, String sourceIdentity) { - availableImageHandlers - .forEach(handler -> handler.onAvailableImage(image, subscription, joiningPosition, sourceIdentity)); - } - - private void unavailableImage(Image image, Subscription subscription, long position) { - unavailableImageHandlers - .forEach(handler -> handler.onUnavailableImage(image, subscription, position)); - } - - public Aeron getAeron() { - return aeron; - } - - void poll() { - Thread dutyThread = new Thread(() -> { - for (;;) { - int poll = 0; - for (FragmentAssemblerHolder sh : fragmentAssemblerHolders) { - try { - poll += sh.subscription.poll(sh.fragmentAssembler, Integer.MAX_VALUE); - } catch (Throwable t) { - t.printStackTrace(); - } - } - SERVER_IDLE_STRATEGY.idle(poll); - } - }); - dutyThread.setName("reactive-socket-aeron-server"); - dutyThread.setDaemon(true); - dutyThread.start(); - } -} diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java deleted file mode 100644 index d7e6c8c99..000000000 --- a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.aeron.server; - -import io.reactivesocket.aeron.internal.Loggable; -import io.reactivesocket.rx.Completable; -import io.reactivesocket.Frame; -import io.reactivesocket.aeron.internal.AeronUtil; -import io.reactivesocket.aeron.internal.MessageType; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; -import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.agrona.BitUtil; - -import java.nio.ByteBuffer; - -/** - * Subscription used by the AeronServerDuplexConnection to handle incoming frames and send them - * on a publication. - * - * @see AeronServerDuplexConnection - */ -class ServerSubscription implements Subscriber, Loggable { - - /** - * Count is used to by the client to round-robin request between threads. - */ - private short count; - - private final Publication publication; - - private final Completable completable; - - public ServerSubscription(Publication publication, Completable completable) { - this.publication = publication; - this.completable = completable; - } - - @Override - public void onSubscribe(Subscription s) { - s.request(Long.MAX_VALUE); - } - - @Override - public void onNext(Frame frame) { - - if (isTraceEnabled()) { - trace("Server with publication session id {} sending frame => {}", publication.sessionId(), frame.toString()); - } - - final ByteBuffer byteBuffer = frame.getByteBuffer(); - final int length = frame.length() + BitUtil.SIZE_OF_INT; - - try { - AeronUtil.tryClaimOrOffer(publication, (offset, buffer) -> { - buffer.putShort(offset, getCount()); - buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); - buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, frame.offset(), frame.length()); - }, length); - } catch (Throwable t) { - onError(t); - } - - if (isTraceEnabled()) { - trace("Server with publication session id {} sent frame with ReactiveSocket stream id => {}", publication.sessionId(), frame.getStreamId()); - } - - - } - - @Override - public void onError(Throwable t) { - completable.error(t); - } - - @Override - public void onComplete() { - if (isTraceEnabled()) { - trace("Server with publication session id {} completing", publication.sessionId()); - } - completable.success(); - } - - private short getCount() { - return count++; - } - -} diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/exceptions/SetupException.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/exceptions/SetupException.java deleted file mode 100644 index bad9572c2..000000000 --- a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/exceptions/SetupException.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.exceptions; - -public class SetupException extends Throwable { - public SetupException(String message) { - super(message); - } - -} diff --git a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java index 29ed9fa48..965be313e 100644 --- a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java +++ b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java @@ -50,14 +50,14 @@ public static void offer(Publication publication, BufferFiller fillBuffer, int l if (timeout > 0) { final long current = System.nanoTime(); if ((current - start) > timeUnit.toNanos(timeout)) { - throw new RuntimeException("Timed out publishing data"); + throw new TimedOutException(); } } final long offer = publication.offer(buffer); if (offer >= 0) { break; } else if (Publication.NOT_CONNECTED == offer) { - throw new RuntimeException("not connected"); + throw new NotConnectedException(); } } while (true); @@ -82,7 +82,7 @@ public static void tryClaim(Publication publication, BufferFiller fillBuffer, in if (timeout > 0) { final long current = System.nanoTime(); if ((current - start) > timeUnit.toNanos(timeout)) { - throw new NotConnectedException(); + throw new TimedOutException(); } } diff --git a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/TimedOutException.java b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/TimedOutException.java new file mode 100644 index 000000000..28a859fe5 --- /dev/null +++ b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/TimedOutException.java @@ -0,0 +1,9 @@ +package io.reactivesocket.aeron.internal; + +public class TimedOutException extends RuntimeException { + + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } +} diff --git a/reactivesocket-aeron-core/src/test/java/io/reactivesocket/aeron/internal/AeronUtilTest.java b/reactivesocket-aeron-core/src/test/java/io/reactivesocket/aeron/internal/AeronUtilTest.java new file mode 100644 index 000000000..021c477cd --- /dev/null +++ b/reactivesocket-aeron-core/src/test/java/io/reactivesocket/aeron/internal/AeronUtilTest.java @@ -0,0 +1,41 @@ +package io.reactivesocket.aeron.internal; + +import org.junit.Test; +import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.aeron.logbuffer.BufferClaim; +import uk.co.real_logic.agrona.DirectBuffer; + +import java.util.concurrent.TimeUnit; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class AeronUtilTest { + + @Test(expected = TimedOutException.class) + public void testOfferShouldTimeOut() { + Publication publication = mock(Publication.class); + AeronUtil.BufferFiller bufferFiller = mock(AeronUtil.BufferFiller.class); + + when(publication.offer(any(DirectBuffer.class))).thenReturn(Publication.BACK_PRESSURED); + + AeronUtil + .offer(publication, bufferFiller, 1, 100, TimeUnit.MILLISECONDS); + + } + + @Test(expected = TimedOutException.class) + public void testTryClaimShouldTimeOut() { + Publication publication = mock(Publication.class); + AeronUtil.BufferFiller bufferFiller = mock(AeronUtil.BufferFiller.class); + + when(publication.tryClaim(anyInt(), any(BufferClaim.class))) + .thenReturn(Publication.BACK_PRESSURED); + + AeronUtil + .tryClaim(publication, bufferFiller, 1, 100, TimeUnit.MILLISECONDS); + + } +} \ No newline at end of file diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java deleted file mode 100644 index c5905d446..000000000 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java +++ /dev/null @@ -1,109 +0,0 @@ -package io.reactivesocket.aeron.client; - -import io.reactivesocket.DuplexConnection; -import io.reactivesocket.Frame; -import io.reactivesocket.aeron.internal.Loggable; -import io.reactivesocket.rx.Completable; -import io.reactivesocket.rx.Disposable; -import io.reactivesocket.rx.Observable; -import io.reactivesocket.rx.Observer; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; -import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.agrona.concurrent.AbstractConcurrentArrayQueue; - -import java.io.IOException; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.function.Consumer; - -public class AeronClientDuplexConnection implements DuplexConnection, Loggable { - - private final Publication publication; - private final CopyOnWriteArrayList> subjects; - private final AbstractConcurrentArrayQueue frameSendQueue; - private final Consumer onClose; - - public AeronClientDuplexConnection( - Publication publication, - AbstractConcurrentArrayQueue frameSendQueue, - Consumer onClose) { - this.publication = publication; - this.subjects = new CopyOnWriteArrayList<>(); - this.frameSendQueue = frameSendQueue; - this.onClose = onClose; - } - - @Override - public final Observable getInput() { - if (isTraceEnabled()) { - trace("getting input for publication session id {} ", publication.sessionId()); - } - - return new Observable() { - public void subscribe(Observer o) { - o.onSubscribe(new Disposable() { - @Override - public void dispose() { - if (isTraceEnabled()) { - trace("removing Observer for publication with session id {} ", publication.sessionId()); - } - - subjects.removeIf(s -> s == o); - } - }); - - subjects.add(o); - } - }; - } - - @Override - public void addOutput(Publisher o, Completable callback) { - o - .subscribe(new Subscriber() { - private Subscription s; - - @Override - public void onSubscribe(Subscription s) { - this.s = s; - s.request(Long.MAX_VALUE); - - } - - @Override - public void onNext(Frame frame) { - if (isTraceEnabled()) { - trace("onNext subscription => {} and frame => {}", s.toString(), frame.toString()); - } - - final FrameHolder fh = FrameHolder.get(frame, publication, s); - boolean offer; - do { - offer = frameSendQueue.offer(fh); - } while (!offer); - } - - @Override - public void onError(Throwable t) { - callback.error(t); - } - - @Override - public void onComplete() { - callback.success(); - } - }); - } - - @Override - public void close() throws IOException { - onClose.accept(publication); - } - - public CopyOnWriteArrayList> getSubjects() { - return subjects; - } - - -} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java deleted file mode 100644 index d076dc8c9..000000000 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java +++ /dev/null @@ -1,262 +0,0 @@ -package io.reactivesocket.aeron.client; - -import io.reactivesocket.Frame; -import io.reactivesocket.aeron.internal.AeronUtil; -import io.reactivesocket.aeron.internal.Constants; -import io.reactivesocket.aeron.internal.Loggable; -import io.reactivesocket.aeron.internal.MessageType; -import io.reactivesocket.rx.Observer; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; -import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.aeron.logbuffer.Header; -import uk.co.real_logic.agrona.BitUtil; -import uk.co.real_logic.agrona.DirectBuffer; -import uk.co.real_logic.agrona.concurrent.ManyToManyConcurrentArrayQueue; -import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; - -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.nio.ByteBuffer; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentSkipListMap; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; - -import static io.reactivesocket.aeron.internal.Constants.CONCURRENCY; -import static io.reactivesocket.aeron.internal.Constants.SERVER_STREAM_ID; - -public final class AeronClientDuplexConnectionFactory implements Loggable { - private static final AeronClientDuplexConnectionFactory instance = new AeronClientDuplexConnectionFactory(); - - private static ThreadLocal buffers = ThreadLocal.withInitial(() -> new UnsafeBuffer(Constants.EMTPY)); - - private final ConcurrentSkipListMap connections; - - // TODO - this should be configurable...enough space for 2048 DuplexConnections assuming request(128) - private final ManyToManyConcurrentArrayQueue frameSendQueue = new ManyToManyConcurrentArrayQueue<>(262144); - - private final ConcurrentHashMap establishConnectionHolders; - - private final ClientAeronManager manager; - - private AeronClientDuplexConnectionFactory() { - connections = new ConcurrentSkipListMap<>(); - establishConnectionHolders = new ConcurrentHashMap<>(); - manager = ClientAeronManager.getInstance(); - - manager.addClientAction(threadId -> { - final boolean traceEnabled = isTraceEnabled(); - frameSendQueue - .drain(fh -> { - final Frame frame = fh.getFrame(); - final ByteBuffer byteBuffer = frame.getByteBuffer(); - final Publication publication = fh.getPublication(); - final int length = frame.length() + BitUtil.SIZE_OF_INT; - - // Can release the FrameHolder at this point as we got everything we need - fh.release(); - - AeronUtil - .tryClaimOrOffer(publication, (offset, buffer) -> { - if (traceEnabled) { - trace("Thread Id {} sending Frame => {} on Aeron", threadId, frame.toString()); - } - - buffer.putShort(offset, (short) 0); - buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); - buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, frame.offset(), frame.length()); - }, length); - }); - }); - } - - public static AeronClientDuplexConnectionFactory getInstance() { - return instance; - } - - /** - * Adds a {@link java.net.SocketAddress} for Aeron to listen for responses on - * - * @param socketAddress - */ - public void addSocketAddressToHandleResponses(SocketAddress socketAddress) { - if (socketAddress instanceof InetSocketAddress) { - addUDPSocketAddressToHandleResponses((InetSocketAddress) socketAddress); - } else { - throw new RuntimeException("unknown socket address type => " + socketAddress.getClass()); - } - } - - void addUDPSocketAddressToHandleResponses(InetSocketAddress socketAddress) { - //String serverChannel = "udp://" + socketAddress.getHostName() + ":" + socketAddress.getPort(); - String serverChannel = "udp://localhost:39790"; - - - manager.addSubscription( - serverChannel, - Constants.CLIENT_STREAM_ID, - threadId -> - new ClientAeronManager.ThreadIdAwareFragmentHandler(threadId) { - @Override - public void onFragment(DirectBuffer buffer, int offset, int length, Header header) { - fragmentHandler(getThreadId(), buffer, offset, length, header); - } - }); - } - - public Publisher createAeronClientDuplexConnection(SocketAddress socketAddress) { - if (socketAddress instanceof InetSocketAddress) { - return createUDPConnection((InetSocketAddress) socketAddress); - } else { - throw new RuntimeException("unknown socket address type => " + socketAddress.getClass()); - } - } - - Publisher createUDPConnection(InetSocketAddress inetSocketAddress) { - final String channel = "udp://" + inetSocketAddress.getHostName() + ":" + inetSocketAddress.getPort(); - debug("Creating a publication to channel => {}", channel); - final Publication publication = manager.getAeron().addPublication(channel, SERVER_STREAM_ID); - debug("Created a publication with sessionId => {} to channel => {}", publication.sessionId(), channel); - - return subscriber -> { - EstablishConnectionHolder establishConnectionHolder = new EstablishConnectionHolder(publication, subscriber); - establishConnectionHolders.putIfAbsent(publication.sessionId(), establishConnectionHolder); - - establishConnection(publication); - }; - } - - /** - * Establishes a connection between the client and server. Waits for 30 seconds before throwing a exception. - */ - void establishConnection(final Publication publication) { - final int sessionId = publication.sessionId(); - - debug("Establishing connection for channel => {}, stream id => {}", - publication.channel(), - publication.sessionId()); - - UnsafeBuffer buffer = buffers.get(); - buffer.wrap(new byte[BitUtil.SIZE_OF_INT]); - buffer.putShort(0, (short) 0); - buffer.putShort(BitUtil.SIZE_OF_SHORT, (short) MessageType.ESTABLISH_CONNECTION_REQUEST.getEncodedType()); - - long offer = -1; - final long start = System.nanoTime(); - for (;;) { - final long current = System.nanoTime(); - if ((current - start) > TimeUnit.SECONDS.toNanos(30)) { - throw new RuntimeException("Timed out waiting to establish connection for session id => " + sessionId); - } - - if (offer < 0) { - offer = publication.offer(buffer); - } else { - break; - } - - } - - } - - void fragmentHandler(int threadId, DirectBuffer buffer, int offset, int length, Header header) { - try { - short messageCount = buffer.getShort(offset); - short messageTypeInt = buffer.getShort(offset + BitUtil.SIZE_OF_SHORT); - final int currentThreadId = Math.abs(messageCount % CONCURRENCY); - - if (currentThreadId != threadId) { - return; - } - - final MessageType messageType = MessageType.from(messageTypeInt); - if (messageType == MessageType.FRAME) { - AeronClientDuplexConnection aeronClientDuplexConnection = connections.get(header.sessionId()); - if (aeronClientDuplexConnection != null) { - CopyOnWriteArrayList> subjects = aeronClientDuplexConnection.getSubjects(); - if (!subjects.isEmpty()) { - //TODO think about how to recycle these, hard because could be handed to another thread I think? - final ByteBuffer bytes = ByteBuffer.allocate(length); - buffer.getBytes(BitUtil.SIZE_OF_INT + offset, bytes, length); - final Frame frame = Frame.from(bytes); - int i = 0; - final int size = subjects.size(); - do { - Observer frameObserver = subjects.get(i); - frameObserver.onNext(frame); - - i++; - } while (i < size); - } - } else { - debug("no connection found for Aeron Session Id {}", header.sessionId()); - } - } else if (messageType == MessageType.ESTABLISH_CONNECTION_RESPONSE) { - final int ackSessionId = buffer.getInt(offset + BitUtil.SIZE_OF_INT); - EstablishConnectionHolder establishConnectionHolder = establishConnectionHolders.remove(ackSessionId); - if (establishConnectionHolder != null) { - try { - AeronClientDuplexConnection aeronClientDuplexConnection - = new AeronClientDuplexConnection(establishConnectionHolder.getPublication(), frameSendQueue, new Consumer() { - @Override - public void accept(Publication publication) { - connections.remove(publication.sessionId()); - - // Send a message to the server that the connection is closed and that it needs to clean-up resources on it's side - if (publication != null) { - try { - AeronUtil.tryClaimOrOffer(publication, (offset, buffer) -> { - buffer.putShort(offset, (short) 0); - buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.CONNECTION_DISCONNECT.getEncodedType()); - }, BitUtil.SIZE_OF_INT); - } catch (Throwable t) { - debug("error closing publication with session id => {}", publication.sessionId()); - } - publication.close(); - } - } - }); - - connections.put(header.sessionId(), aeronClientDuplexConnection); - - establishConnectionHolder.getSubscriber().onNext(aeronClientDuplexConnection); - establishConnectionHolder.getSubscriber().onComplete(); - - debug("Connection established for channel => {}, stream id => {}", - establishConnectionHolder.getPublication().channel(), - establishConnectionHolder.getPublication().sessionId()); - } catch (Throwable t) { - establishConnectionHolder.getSubscriber().onError(t); - } - } - } else { - debug("Unknown message type => " + messageTypeInt); - } - } catch (Throwable t) { - error("error handling framement", t); - } - } - - /* - * Inner Classes - */ - class EstablishConnectionHolder { - private Publication publication; - private Subscriber subscriber; - - public EstablishConnectionHolder(Publication publication, Subscriber subscriber) { - this.publication = publication; - this.subscriber = subscriber; - } - - public Publication getPublication() { - return publication; - } - - public Subscriber getSubscriber() { - return subscriber; - } - } -} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java deleted file mode 100644 index 30b71bd6f..000000000 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java +++ /dev/null @@ -1,220 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.aeron.client; - -import io.reactivesocket.aeron.internal.Constants; -import io.reactivesocket.aeron.internal.Loggable; -import rx.Scheduler; -import rx.functions.Func1; -import rx.schedulers.Schedulers; -import uk.co.real_logic.aeron.Aeron; -import uk.co.real_logic.aeron.AvailableImageHandler; -import uk.co.real_logic.aeron.FragmentAssembler; -import uk.co.real_logic.aeron.Image; -import uk.co.real_logic.aeron.Subscription; -import uk.co.real_logic.aeron.logbuffer.FragmentHandler; - -import java.util.Optional; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantLock; - -/** - * Class for managing the Aeron on the client side. - */ -public class ClientAeronManager implements Loggable { - private static final ClientAeronManager INSTANCE = new ClientAeronManager(); - - private final CopyOnWriteArrayList clientActions; - - private final CopyOnWriteArrayList subscriptionGroups; - - private final Aeron aeron; - - private final Scheduler.Worker[] workers = new Scheduler.Worker[Constants.CONCURRENCY]; - - private ClientAeronManager() { - this.clientActions = new CopyOnWriteArrayList<>(); - this.subscriptionGroups = new CopyOnWriteArrayList<>(); - - final Aeron.Context ctx = new Aeron.Context(); - ctx.errorHandler(t -> error("an exception occurred", t)); - ctx.availableImageHandler(new AvailableImageHandler() { - @Override - public void onAvailableImage(Image image, Subscription subscription, long joiningPosition, String sourceIdentity) { - debug("New image available with session id => {} and sourceIdentity => {} and subscription => {}", image.sessionId(), sourceIdentity, subscription.toString()); - } - }); - - aeron = Aeron.connect(ctx); - - poll(); - } - - public static ClientAeronManager getInstance() { - return INSTANCE; - } - - /** - * Finds a SubscriptionGroup from the sessionId of one the servers in the group - * - * @param sessionId the session id whose SubscriptionGroup you want to find - * @return an Optional of SubscriptionGroup - */ - public Optional find(final int sessionId) { - return subscriptionGroups - .stream() - .filter(sg -> { - boolean found = false; - - for (Subscription subscription : sg.subscriptions) { - Image image = subscription.getImage(sessionId); - if (image != null) { - found = true; - break; - } - } - - return found; - }) - .findFirst(); - } - - /** - * Adds a ClientAction on the a list that is run by the polling loop. - * - * @param clientAction the {@link io.reactivesocket.aeron.client.ClientAeronManager.ClientAction} to add - */ - public void addClientAction(ClientAction clientAction) { - clientActions.add(clientAction); - } - - - public boolean hasSubscriptionForChannel(String subscriptionChannel) { - return subscriptionGroups - .stream() - .anyMatch(sg -> sg.getChannel().equals(subscriptionChannel)); - } - - public Aeron getAeron() { - return aeron; - } - - /** - * Adds an Aeron subscription to be polled. This method will create a subscription for each of the polling threads. - * - * @param subscriptionChannel the channel to create subscriptions on - * @param streamId the stream id to create subscriptions on - * @param fragmentHandlerFactory factory that creates a fragment handler that is aware of the thread that is call it. - */ - public void addSubscription(String subscriptionChannel, int streamId, Func1 fragmentHandlerFactory) { - if (!hasSubscriptionForChannel(subscriptionChannel)) { - - debug("Creating a subscriptions to channel => {}", subscriptionChannel); - Subscription[] subscriptions = new Subscription[Constants.CONCURRENCY]; - for (int i = 0; i < Constants.CONCURRENCY; i++) { - subscriptions[i] = aeron.addSubscription(subscriptionChannel, streamId); - debug("Subscription created for threadId => {} and channel => {} ", i, subscriptionChannel); - } - SubscriptionGroup subscriptionGroup = new SubscriptionGroup(subscriptionChannel, subscriptions, fragmentHandlerFactory); - subscriptionGroups.add(subscriptionGroup); - debug("Subscriptions created to channel => {}", subscriptionChannel); - - } else { - debug("Subscription already exists for channel => {}", subscriptionChannel); - } - } - - /* - * Starts polling for the Aeron client. Will run registered client actions and will automatically start polling - * subscriptions - */ - void poll() { - info("ReactiveSocket Aeron Client concurreny is {}", Constants.CONCURRENCY); - final ReentrantLock pollLock = new ReentrantLock(); - final ReentrantLock clientActionLock = new ReentrantLock(); - for (int i = 0; i < Constants.CONCURRENCY; i++) { - final int threadId = i; - workers[threadId] = Schedulers.computation().createWorker(); - workers[threadId].schedulePeriodically(new - PollingAction(pollLock, clientActionLock, threadId, subscriptionGroups, clientActions), - 0, 20, TimeUnit.MICROSECONDS); - } - } - - /* - * Inner Classes - */ - - /** - * Creates a logic group of {@link uk.co.real_logic.aeron.Subscription}s to a particular channel. - */ - public static class SubscriptionGroup { - - private final static ThreadLocal threadLocalFragmentAssembler = new ThreadLocal<>(); - private final String channel; - private final Subscription[] subscriptions; - private final Func1 fragmentHandlerFactory; - - public SubscriptionGroup(String channel, Subscription[] subscriptions, Func1 fragmentHandlerFactory) { - this.channel = channel; - this.subscriptions = subscriptions; - this.fragmentHandlerFactory = fragmentHandlerFactory; - } - - public String getChannel() { - return channel; - } - - public Subscription[] getSubscriptions() { - return subscriptions; - } - - public FragmentAssembler getFragmentAssembler(int threadId) { - FragmentAssembler assembler = threadLocalFragmentAssembler.get(); - - if (assembler == null) { - assembler = new FragmentAssembler(fragmentHandlerFactory.call(threadId)); - threadLocalFragmentAssembler.set(assembler); - } - - return assembler; - } - } - - @FunctionalInterface - public interface ClientAction { - void call(int threadId); - } - - - - /** - * FragmentHandler that is aware of the thread that it is running on. This is useful if you only want a one thread - * to process a particular message. - */ - public static abstract class ThreadIdAwareFragmentHandler implements FragmentHandler { - private int threadId; - - public ThreadIdAwareFragmentHandler(int threadId) { - this.threadId = threadId; - } - - public final int getThreadId() { - return this.threadId; - } - } -} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java deleted file mode 100644 index 5e910b0b7..000000000 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.aeron.client; - -import io.reactivesocket.Frame; -import org.HdrHistogram.Recorder; -import org.reactivestreams.Subscription; -import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.agrona.concurrent.OneToOneConcurrentArrayQueue; - -/** - * Holds a frame and the publication that it's supposed to be sent on. - * Pools instances on an {@link uk.co.real_logic.agrona.concurrent.OneToOneConcurrentArrayQueue} - */ -public class FrameHolder { - private static final ThreadLocal> FRAME_HOLDER_QUEUE - = ThreadLocal.withInitial(() -> new OneToOneConcurrentArrayQueue<>(16)); - - public static final Recorder histogram = new Recorder(3600000000000L, 3); - - private Frame frame; - private Publication publication; - private Subscription s; - private long getTime; - - private FrameHolder() {} - - public static FrameHolder get(Frame frame, Publication publication, Subscription s) { - FrameHolder frameHolder = FRAME_HOLDER_QUEUE.get().poll(); - - if (frameHolder == null) { - frameHolder = new FrameHolder(); - } - - frameHolder.frame = frame; - frameHolder.s = s; - frameHolder.publication = publication; - frameHolder.getTime = System.nanoTime(); - - return frameHolder; - } - - public Frame getFrame() { - return frame; - } - - public Publication getPublication() { - return publication; - } - - public void release() { - if (s != null) { - s.request(1); - } - - frame.release(); - FRAME_HOLDER_QUEUE.get().offer(this); - - histogram.recordValue(System.nanoTime() - getTime); - } -} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/PollingAction.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/PollingAction.java deleted file mode 100644 index 45cdffa8a..000000000 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/client/PollingAction.java +++ /dev/null @@ -1,64 +0,0 @@ -package io.reactivesocket.aeron.client; - -import io.reactivesocket.aeron.internal.Loggable; -import rx.functions.Action0; -import uk.co.real_logic.aeron.Subscription; - -import java.util.List; -import java.util.concurrent.locks.ReentrantLock; - -class PollingAction implements Action0, Loggable { - - private final ReentrantLock pollLock; - private final ReentrantLock clientActionLock; - private final int threadId; - private final List subscriptionGroups; - private final List clientActions; - - public PollingAction( - ReentrantLock pollLock, - ReentrantLock clientActionLock, - int threadId, - List subscriptionGroups, - List clientActions) { - this.pollLock = pollLock; - this.clientActionLock = clientActionLock; - this.threadId = threadId; - this.subscriptionGroups = subscriptionGroups; - this.clientActions = clientActions; - } - - @Override - public void call() { - try { - for (ClientAeronManager.SubscriptionGroup sg : subscriptionGroups) { - try { - int poll; - do { - Subscription subscription = sg.getSubscriptions()[threadId]; - poll = subscription.poll(sg.getFragmentAssembler(threadId), Integer.MAX_VALUE); - } while (poll > 0); - - //if (clientActionLock.tryLock()) { - for (ClientAeronManager.ClientAction action : clientActions) { - action.call(threadId); - } - //} - } catch (Throwable t) { - error("error polling aeron subscription on thread with id " + threadId, t); - } finally { - if (pollLock.isHeldByCurrentThread()) { - pollLock.unlock(); - } - - if (clientActionLock.isHeldByCurrentThread()) { - clientActionLock.unlock(); - } - } - } - - } catch (Throwable t) { - error("error in client polling loop on thread with id " + threadId, t); - } - } -} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/example/MediaDriver.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/example/MediaDriver.java deleted file mode 100644 index 9dac34fc0..000000000 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/example/MediaDriver.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.aeron.example; - -import uk.co.real_logic.aeron.driver.ThreadingMode; -import uk.co.real_logic.agrona.concurrent.BackoffIdleStrategy; -import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; - -/** - * Created by rroeser on 8/16/15. - */ -public class MediaDriver { - public static void main(String... args) { - ThreadingMode threadingMode = ThreadingMode.SHARED; - - boolean dedicated = Boolean.getBoolean("dedicated"); - - if (dedicated) { - threadingMode = ThreadingMode.DEDICATED; - } - - System.out.println("ThreadingMode => " + threadingMode); - - final uk.co.real_logic.aeron.driver.MediaDriver.Context ctx = new uk.co.real_logic.aeron.driver.MediaDriver.Context() - .threadingMode(threadingMode) - .dirsDeleteOnStart(true) - .conductorIdleStrategy(new BackoffIdleStrategy(1, 1, 100, 1000)) - .receiverIdleStrategy(new NoOpIdleStrategy()) - .senderIdleStrategy(new NoOpIdleStrategy()); - - final uk.co.real_logic.aeron.driver.MediaDriver ignored = uk.co.real_logic.aeron.driver.MediaDriver.launch(ctx); - - } -} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/example/Ping.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/example/Ping.java deleted file mode 100644 index a30943819..000000000 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/example/Ping.java +++ /dev/null @@ -1,141 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.aeron.example; - -import io.reactivesocket.ConnectionSetupPayload; -import io.reactivesocket.Payload; -import io.reactivesocket.ReactiveSocket; -import io.reactivesocket.aeron.client.AeronClientDuplexConnection; -import io.reactivesocket.aeron.client.AeronClientDuplexConnectionFactory; -import io.reactivesocket.aeron.client.FrameHolder; -import org.HdrHistogram.Recorder; -import org.reactivestreams.Publisher; -import rx.Observable; -import rx.RxReactiveStreams; -import rx.Subscriber; -import rx.schedulers.Schedulers; - -import java.net.InetSocketAddress; -import java.nio.ByteBuffer; -import java.util.Random; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -/** - * Created by rroeser on 8/16/15. - */ -public class Ping { - - public static void main(String... args) throws Exception { - String host = System.getProperty("host", "localhost"); - String server = System.getProperty("server", "localhost"); - - System.out.println("Setting host to => " + host); - - System.out.println("Setting ping is listening to => " + server); - - - byte[] key = new byte[4]; - //byte[] key = new byte[BitUtil.SIZE_OF_INT]; - Random r = new Random(); - r.nextBytes(key); - - System.out.println("Sending data of size => " + key.length); - - InetSocketAddress listenAddress = new InetSocketAddress("localhost", 39790); - InetSocketAddress clientAddress = new InetSocketAddress("localhost", 39790); - - AeronClientDuplexConnectionFactory cf = AeronClientDuplexConnectionFactory.getInstance(); - cf.addSocketAddressToHandleResponses(listenAddress); - Publisher udpConnection = cf.createAeronClientDuplexConnection(clientAddress); - - System.out.println("Creating new duplex connection"); - AeronClientDuplexConnection connection = RxReactiveStreams.toObservable(udpConnection).toBlocking().single(); - System.out.println("Created duplex connection"); - - ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection(connection, ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS)); - reactiveSocket.startAndWait(); - - CountDownLatch latch = new CountDownLatch(Integer.MAX_VALUE); - - final Recorder histogram = new Recorder(3600000000000L, 3); - - Schedulers - .computation() - .createWorker() - .schedulePeriodically(() -> { - System.out.println("---- FRAME HOLDER HISTO ----"); - FrameHolder.histogram.getIntervalHistogram().outputPercentileDistribution(System.out, 5, 1000.0, false); - System.out.println("---- FRAME HOLDER HISTO ----"); - - System.out.println("---- PING/ PONG HISTO ----"); - histogram.getIntervalHistogram().outputPercentileDistribution(System.out, 5, 1000.0, false); - System.out.println("---- PING/ PONG HISTO ----"); - - - }, 10, 10, TimeUnit.SECONDS); - - Observable - .range(1, Integer.MAX_VALUE) - .flatMap(i -> { - long start = System.nanoTime(); - - Payload keyPayload = new Payload() { - ByteBuffer data = ByteBuffer.wrap(key); - ByteBuffer metadata = ByteBuffer.allocate(0); - - public ByteBuffer getData() { - return data; - } - - @Override - public ByteBuffer getMetadata() { - return metadata; - } - }; - - return RxReactiveStreams - .toObservable( - reactiveSocket - .requestResponse(keyPayload)) - .doOnNext(s -> { - long diff = System.nanoTime() - start; - histogram.recordValue(diff); - }); - }) - .subscribe(new Subscriber() { - @Override - public void onCompleted() { - - } - - @Override - public void onError(Throwable e) { - e.printStackTrace(); - } - - @Override - public void onNext(Payload payload) { - latch.countDown(); - } - }); - - latch.await(); - System.out.println("Sent => " + Integer.MAX_VALUE); - System.exit(0); - } - -} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/example/Pong.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/example/Pong.java deleted file mode 100644 index a8edd4cc3..000000000 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/example/Pong.java +++ /dev/null @@ -1,136 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.aeron.example; - -import io.reactivesocket.ConnectionSetupHandler; -import io.reactivesocket.ConnectionSetupPayload; -import io.reactivesocket.Payload; -import io.reactivesocket.RequestHandler; -import io.reactivesocket.aeron.server.ReactiveSocketAeronServer; -import io.reactivesocket.exceptions.SetupException; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; -import uk.co.real_logic.aeron.driver.ThreadingMode; -import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; - -import java.nio.ByteBuffer; -import java.util.Random; - -/** - * Created by rroeser on 8/16/15. - */ -public class Pong { - - public static void startDriver() { - ThreadingMode threadingMode = ThreadingMode.SHARED; - - boolean dedicated = Boolean.getBoolean("dedicated"); - - if (true) { - threadingMode = ThreadingMode.DEDICATED; - } - - System.out.println("ThreadingMode => " + threadingMode); - - final uk.co.real_logic.aeron.driver.MediaDriver.Context ctx = new uk.co.real_logic.aeron.driver.MediaDriver.Context() - .threadingMode(threadingMode) - .conductorIdleStrategy(new NoOpIdleStrategy()) - .receiverIdleStrategy(new NoOpIdleStrategy()) - .senderIdleStrategy(new NoOpIdleStrategy()); - - final uk.co.real_logic.aeron.driver.MediaDriver ignored = uk.co.real_logic.aeron.driver.MediaDriver.launchEmbedded(ctx); - } - - public static void main(String... args) { - // startDriver(); - - String host = System.getProperty("host", "localhost"); - - System.out.println("Setting host to => " + host); - - byte[] response = new byte[1024]; - //byte[] response = new byte[1024]; - Random r = new Random(); - r.nextBytes(response); - - System.out.println("Sending data of size => " + response.length); - - ReactiveSocketAeronServer server = ReactiveSocketAeronServer.create(host, 39790, new ConnectionSetupHandler() { - @Override - public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { - return new RequestHandler() { - @Override - public Publisher handleRequestResponse(Payload payload) { - long time = System.currentTimeMillis(); - - Publisher publisher = new Publisher() { - @Override - public void subscribe(Subscriber s) { - Payload responsePayload = new Payload() { - ByteBuffer data = ByteBuffer.wrap(response); - ByteBuffer metadata = ByteBuffer.allocate(0); - - public ByteBuffer getData() { - return data; - } - - @Override - public ByteBuffer getMetadata() { - return metadata; - } - }; - - s.onNext(responsePayload); - - long diff = System.currentTimeMillis() - time; - //timer.update(diff, TimeUnit.NANOSECONDS); - s.onComplete(); - } - }; - - return publisher; - } - - @Override - public Publisher handleRequestStream(Payload payload) { - return null; - } - - @Override - public Publisher handleSubscription(Payload payload) { - return null; - } - - @Override - public Publisher handleFireAndForget(Payload payload) { - return null; - } - - @Override - public Publisher handleChannel(Payload initialPayload, Publisher payloads) { - return null; - } - - @Override - public Publisher handleMetadataPush(Payload payload) { - return null; - } - }; - } - }); - } - -} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java deleted file mode 100644 index 29ed9fa48..000000000 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java +++ /dev/null @@ -1,163 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.aeron.internal; - -import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.aeron.logbuffer.BufferClaim; -import uk.co.real_logic.agrona.MutableDirectBuffer; -import uk.co.real_logic.agrona.concurrent.OneToOneConcurrentArrayQueue; -import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; - -import java.util.concurrent.TimeUnit; - -/** - * Utils for dealing with Aeron - */ -public class AeronUtil implements Loggable { - - private static final ThreadLocal bufferClaims = ThreadLocal.withInitial(BufferClaim::new); - - private static final ThreadLocal> unsafeBuffers - = ThreadLocal.withInitial(() -> new OneToOneConcurrentArrayQueue<>(16)); - - /** - * Sends a message using offer. This method will spin-lock if Aeron signals back pressure. - *

- * This method of sending data does need to know how long the message is. - * - * @param publication publication to send the message on - * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} - * that is send over Aeron - */ - public static void offer(Publication publication, BufferFiller fillBuffer, int length, int timeout, TimeUnit timeUnit) { - final MutableDirectBuffer buffer = getDirectBuffer(length); - fillBuffer.fill(0, buffer); - final long start = System.nanoTime(); - do { - if (timeout > 0) { - final long current = System.nanoTime(); - if ((current - start) > timeUnit.toNanos(timeout)) { - throw new RuntimeException("Timed out publishing data"); - } - } - final long offer = publication.offer(buffer); - if (offer >= 0) { - break; - } else if (Publication.NOT_CONNECTED == offer) { - throw new RuntimeException("not connected"); - } - } while (true); - - recycleDirectBuffer(buffer); - } - - /** - * Sends a message using tryClaim. This method will spin-lock if Aeron signals back pressure. The message - * being sent needs to be equal or smaller than Aeron's MTU size or an exception will be thrown. - *

- * In order to use this method of sending data you need to know the length of data. - * - * @param publication publication to send the message on - * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} - * that is send over Aeron - * @param length the length of data - */ - public static void tryClaim(Publication publication, BufferFiller fillBuffer, int length, int timeout, TimeUnit timeUnit) { - final BufferClaim bufferClaim = bufferClaims.get(); - final long start = System.nanoTime(); - do { - if (timeout > 0) { - final long current = System.nanoTime(); - if ((current - start) > timeUnit.toNanos(timeout)) { - throw new NotConnectedException(); - } - } - - final long offer = publication.tryClaim(length, bufferClaim); - if (offer >= 0) { - try { - final MutableDirectBuffer buffer = bufferClaim.buffer(); - final int offset = bufferClaim.offset(); - fillBuffer.fill(offset, buffer); - break; - } finally { - bufferClaim.commit(); - } - } else if (Publication.NOT_CONNECTED == offer) { - throw new NotConnectedException(); - } - } while (true); - } - - /** - * Attempts to send the data using tryClaim. If the message data length is large then the Aeron MTU - * size it will use offer instead. - * - * @param publication publication to send the message on - * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} - * that is send over Aeron - * @param length the length of data - */ - public static void tryClaimOrOffer(Publication publication, BufferFiller fillBuffer, int length) { - tryClaimOrOffer(publication, fillBuffer, length, -1, null); - } - - public static void tryClaimOrOffer(Publication publication, BufferFiller fillBuffer, int length, int timeout, TimeUnit timeUnit) { - if (length < Constants.AERON_MTU_SIZE) { - tryClaim(publication, fillBuffer, length, timeout, timeUnit); - } else { - offer(publication, fillBuffer, length, timeout, timeUnit); - } - } - - - /** - * Try to get a MutableDirectBuffer from a thread-safe pool for a given length. If the buffer found - * is bigger then the buffer in the pool creates a new buffer. If no buffer is found creates a new buffer - * - * @param length the requested length - * @return either a new MutableDirectBuffer or a recycled one that has the capacity to hold the data from the old one - */ - public static MutableDirectBuffer getDirectBuffer(int length) { - OneToOneConcurrentArrayQueue queue = unsafeBuffers.get(); - MutableDirectBuffer buffer = queue.poll(); - - if (buffer != null && buffer.capacity() < length) { - return buffer; - } else { - byte[] bytes = new byte[length]; - buffer = new UnsafeBuffer(bytes); - return buffer; - } - } - - /** - * Sends a DirectBuffer back to the thread pools to be recycled. - * - * @param directBuffer the DirectBuffer to recycle - */ - public static void recycleDirectBuffer(MutableDirectBuffer directBuffer) { - OneToOneConcurrentArrayQueue queue = unsafeBuffers.get(); - queue.offer(directBuffer); - } - - /** - * Implement this to fill a DirectBuffer passed in by either the offer or tryClaim methods. - */ - public interface BufferFiller { - void fill(int offset, MutableDirectBuffer buffer); - } -} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/Constants.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/Constants.java deleted file mode 100644 index 19a5375cf..000000000 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/Constants.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.aeron.internal; - -import uk.co.real_logic.agrona.concurrent.IdleStrategy; -import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; - -public final class Constants { - - private Constants() {} - - public static final int SERVER_STREAM_ID = 1; - - public static final int CLIENT_STREAM_ID = 2; - - public static final byte[] EMTPY = new byte[0]; - - public static final int QUEUE_SIZE = Integer.getInteger("reactivesocket.aeron.framesSendQueueSize", 262144); - - public static final IdleStrategy SERVER_IDLE_STRATEGY = new NoOpIdleStrategy(); - - public static final int CONCURRENCY = Integer.getInteger("reactivesocket.aeron.clientConcurrency", 2); - - public static final int AERON_MTU_SIZE = Integer.getInteger("aeron.mtu.length", 4096); - - public static final boolean TRACING_ENABLED = Boolean.getBoolean("reactivesocket.aeron.tracingEnabled"); - -} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/Loggable.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/Loggable.java deleted file mode 100644 index d2d540d3d..000000000 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/Loggable.java +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.aeron.internal; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * No more needed to type Logger LOGGER = LoggerFactory.getLogger.... - */ -public interface Loggable { - - default void info(String message, Object... args) { - logger().debug(message, args); - } - - default void error(String message, Throwable t) { - logger().error(message, t); - } - - default void debug(String message, Object... args) { - logger().debug(message, args); - } - - default void trace(String message, Object... args) { - logger().trace(message, args); - } - - default boolean isTraceEnabled() { - if (Constants.TRACING_ENABLED) { - return logger().isTraceEnabled(); - } else { - return false; - } - } - - default Logger logger() { - return LoggerFactory.getLogger(getClass()); - } -} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/MessageType.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/MessageType.java deleted file mode 100644 index c294d232d..000000000 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/MessageType.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.aeron.internal; - -/** - * Type of message being sent. - */ -public enum MessageType { - ESTABLISH_CONNECTION_REQUEST(0x01), - ESTABLISH_CONNECTION_RESPONSE(0x02), - CONNECTION_DISCONNECT(0x3), - FRAME(0x04); - - private static MessageType[] typesById; - - /** - * Index types by id for indexed lookup. - */ - static { - int max = 0; - - for (MessageType t : values()) { - max = Math.max(t.id, max); - } - - typesById = new MessageType[max + 1]; - - for (MessageType t : values()) { - typesById[t.id] = t; - } - } - - private final int id; - - MessageType(int id) { - this.id = id; - } - - public int getEncodedType() { - return id; - } - - public static MessageType from(int id) { - return typesById[id]; - } -} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java deleted file mode 100644 index ce87e7712..000000000 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.aeron.internal; - -public class NotConnectedException extends RuntimeException { - - @Override - public synchronized Throwable fillInStackTrace() { - return this; - } - -} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/exceptions/SetupException.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/exceptions/SetupException.java deleted file mode 100644 index bad9572c2..000000000 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/exceptions/SetupException.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.exceptions; - -public class SetupException extends Throwable { - public SetupException(String message) { - super(message); - } - -} diff --git a/reactivesocket-aeron-tests/build.gradle b/reactivesocket-aeron-tests/build.gradle new file mode 100644 index 000000000..421e59c8d --- /dev/null +++ b/reactivesocket-aeron-tests/build.gradle @@ -0,0 +1,4 @@ +dependencies { + compile project(':reactivesocket-aeron-client') + compile project(':reactivesocket-aeron-server') +} diff --git a/src/perf/java/io/reactivesocket/aeron/client/PollingActionPerf.java b/reactivesocket-aeron-tests/src/perf/java/io/reactivesocket/aeron/client/PollingActionPerf.java similarity index 100% rename from src/perf/java/io/reactivesocket/aeron/client/PollingActionPerf.java rename to reactivesocket-aeron-tests/src/perf/java/io/reactivesocket/aeron/client/PollingActionPerf.java diff --git a/src/perf/java/io/reactivesocket/aeron/jmh/InputWithIncrementingInteger.java b/reactivesocket-aeron-tests/src/perf/java/io/reactivesocket/aeron/jmh/InputWithIncrementingInteger.java similarity index 100% rename from src/perf/java/io/reactivesocket/aeron/jmh/InputWithIncrementingInteger.java rename to reactivesocket-aeron-tests/src/perf/java/io/reactivesocket/aeron/jmh/InputWithIncrementingInteger.java diff --git a/src/perf/java/io/reactivesocket/aeron/jmh/LatchedObserver.java b/reactivesocket-aeron-tests/src/perf/java/io/reactivesocket/aeron/jmh/LatchedObserver.java similarity index 100% rename from src/perf/java/io/reactivesocket/aeron/jmh/LatchedObserver.java rename to reactivesocket-aeron-tests/src/perf/java/io/reactivesocket/aeron/jmh/LatchedObserver.java diff --git a/src/perf/java/uk/co/real_logic/aeron/DummySubscription.java b/reactivesocket-aeron-tests/src/perf/java/uk/co/real_logic/aeron/DummySubscription.java similarity index 100% rename from src/perf/java/uk/co/real_logic/aeron/DummySubscription.java rename to reactivesocket-aeron-tests/src/perf/java/uk/co/real_logic/aeron/DummySubscription.java diff --git a/src/test/java/io/reactivesocket/aeron/TestUtil.java b/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/TestUtil.java similarity index 100% rename from src/test/java/io/reactivesocket/aeron/TestUtil.java rename to reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/TestUtil.java diff --git a/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java b/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java similarity index 100% rename from src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java rename to reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java diff --git a/src/test/resources/simplelogger.properties b/reactivesocket-aeron-tests/src/test/resources/simplelogger.properties similarity index 100% rename from src/test/resources/simplelogger.properties rename to reactivesocket-aeron-tests/src/test/resources/simplelogger.properties diff --git a/settings.gradle b/settings.gradle index 7875e24aa..dd13e58b9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,6 @@ rootProject.name='reactivesocket-aeron-rxjava' -include 'reactivesocket-aeron-rxjava', \ - 'reactivesocket-aeron-client', \ +include 'reactivesocket-aeron-client', \ 'reactivesocket-aeron-core', \ - 'reactivesocket-aeron-examples', + 'reactivesocket-aeron-tests', \ + 'reactivesocket-aeron-examples', \ 'reactivesocket-aeron-server' From b45e67259afc88de8399d8a2c0775e682eb8f576 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Mon, 5 Oct 2015 10:47:09 -0700 Subject: [PATCH 055/105] re-added nebula plugin --- build.gradle | 12 ++++++++++++ .../src/main/java/package-info.java | 0 2 files changed, 12 insertions(+) create mode 100644 reactivesocket-aeron-tests/src/main/java/package-info.java diff --git a/build.gradle b/build.gradle index 648625679..f5a434395 100644 --- a/build.gradle +++ b/build.gradle @@ -6,8 +6,10 @@ buildscript { dependencies { classpath 'io.reactivesocket:gradle-nebula-plugin-reactivesocket:1.0.1' } } +apply plugin: 'reactivesocket-project' subprojects { + apply plugin: 'reactivesocket-project' apply plugin: 'java' repositories { @@ -26,6 +28,16 @@ subprojects { testCompile 'org.mockito:mockito-core:1.8.5' testCompile 'org.slf4j:slf4j-simple:1.7.12' } + + // support for snapshot/final releases via versioned branch names like 1.x + nebulaRelease { + addReleaseBranchPattern(/\d+\.\d+\.\d+/) + addReleaseBranchPattern('HEAD') + } + + if (project.hasProperty('release.useLastTag')) { + tasks.prepare.enabled = false + } } /* diff --git a/reactivesocket-aeron-tests/src/main/java/package-info.java b/reactivesocket-aeron-tests/src/main/java/package-info.java new file mode 100644 index 000000000..e69de29bb From 274747c69b6017d3cb4b0cca002de50a40490c62 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Wed, 14 Oct 2015 16:01:54 -0700 Subject: [PATCH 056/105] added a fire / forget example --- build.gradle | 65 ------------------- .../example/{ => requestreply}/Ping.java | 2 +- .../example/{ => requestreply}/Pong.java | 27 +------- 3 files changed, 3 insertions(+), 91 deletions(-) rename reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/{ => requestreply}/Ping.java (98%) rename reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/{ => requestreply}/Pong.java (80%) diff --git a/build.gradle b/build.gradle index f5a434395..a032e580d 100644 --- a/build.gradle +++ b/build.gradle @@ -39,68 +39,3 @@ subprojects { tasks.prepare.enabled = false } } - -/* -buildscript { - repositories { - jcenter() - } - - dependencies { classpath 'io.reactivesocket:gradle-nebula-plugin-reactivesocket:1.0.1' } -} - -description = 'ReactiveSocket: stream oriented messaging passing with Reactive Stream semantics.' - - -repositories { - maven { url 'https://oss.jfrog.org/libs-snapshot' } -} - -subprojects { - apply plugin: 'reactivesocket-project' - apply plugin: 'java' - - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - - dependencies { - compile 'io.reactivex:rxjava:1.0.13' - compile 'io.reactivex:rxjava-reactive-streams:1.0.1' - compile 'io.reactivesocket:reactivesocket:0.0.1-SNAPSHOT' - compile 'uk.co.real-logic:Agrona:0.4.4' - compile 'uk.co.real-logic:aeron-all:0.1.4' - compile 'org.hdrhistogram:HdrHistogram:2.1.7' - compile 'org.slf4j:slf4j-api:1.7.12' - testCompile 'junit:junit-dep:4.10' - testCompile 'org.mockito:mockito-core:1.8.5' - testCompile 'org.slf4j:slf4j-simple:1.7.12' - } - - - // support for snapshot/final releases via versioned branch names like 1.x - nebulaRelease { - addReleaseBranchPattern(/\d+\.\d+\.\d+/) - addReleaseBranchPattern('HEAD') - } - - if (project.hasProperty('release.useLastTag')) { - tasks.prepare.enabled = false - } -} - - -task(md, dependsOn: 'classes', type: JavaExec) { - main = 'io.reactivesocket.aeron.example.MediaDriver' - classpath = sourceSets.main.runtimeClasspath -} - -task(ping, dependsOn: 'classes', type: JavaExec) { - main = 'io.reactivesocket.aeron.example.Ping' - classpath = sourceSets.main.runtimeClasspath -} - -task(pong, dependsOn: 'classes', type: JavaExec) { - main = 'io.reactivesocket.aeron.example.Pong' - classpath = sourceSets.main.runtimeClasspath -} - */ \ No newline at end of file diff --git a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/Ping.java b/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/requestreply/Ping.java similarity index 98% rename from reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/Ping.java rename to reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/requestreply/Ping.java index a30943819..6af9f1f0c 100644 --- a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/Ping.java +++ b/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/requestreply/Ping.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.reactivesocket.aeron.example; +package io.reactivesocket.aeron.example.requestreply; import io.reactivesocket.ConnectionSetupPayload; import io.reactivesocket.Payload; diff --git a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/Pong.java b/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/requestreply/Pong.java similarity index 80% rename from reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/Pong.java rename to reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/requestreply/Pong.java index a8edd4cc3..1106edd38 100644 --- a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/Pong.java +++ b/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/requestreply/Pong.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.reactivesocket.aeron.example; +package io.reactivesocket.aeron.example.requestreply; import io.reactivesocket.ConnectionSetupHandler; import io.reactivesocket.ConnectionSetupPayload; @@ -23,8 +23,6 @@ import io.reactivesocket.exceptions.SetupException; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; -import uk.co.real_logic.aeron.driver.ThreadingMode; -import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; import java.nio.ByteBuffer; import java.util.Random; @@ -34,28 +32,7 @@ */ public class Pong { - public static void startDriver() { - ThreadingMode threadingMode = ThreadingMode.SHARED; - - boolean dedicated = Boolean.getBoolean("dedicated"); - - if (true) { - threadingMode = ThreadingMode.DEDICATED; - } - - System.out.println("ThreadingMode => " + threadingMode); - - final uk.co.real_logic.aeron.driver.MediaDriver.Context ctx = new uk.co.real_logic.aeron.driver.MediaDriver.Context() - .threadingMode(threadingMode) - .conductorIdleStrategy(new NoOpIdleStrategy()) - .receiverIdleStrategy(new NoOpIdleStrategy()) - .senderIdleStrategy(new NoOpIdleStrategy()); - - final uk.co.real_logic.aeron.driver.MediaDriver ignored = uk.co.real_logic.aeron.driver.MediaDriver.launchEmbedded(ctx); - } - - public static void main(String... args) { - // startDriver(); + public static void main(String... args) { String host = System.getProperty("host", "localhost"); From 4cafe5a65c6f4c42e7ed59c78068531bf76853cd Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Wed, 14 Oct 2015 16:04:18 -0700 Subject: [PATCH 057/105] adding new example --- .../aeron/example/fireandforget/Fire.java | 137 ++++++++++++++++++ .../aeron/example/fireandforget/Forget.java | 76 ++++++++++ 2 files changed, 213 insertions(+) create mode 100644 reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/fireandforget/Fire.java create mode 100644 reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/fireandforget/Forget.java diff --git a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/fireandforget/Fire.java b/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/fireandforget/Fire.java new file mode 100644 index 000000000..d7160990c --- /dev/null +++ b/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/fireandforget/Fire.java @@ -0,0 +1,137 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.example.fireandforget; + + +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.Payload; +import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.aeron.client.AeronClientDuplexConnection; +import io.reactivesocket.aeron.client.AeronClientDuplexConnectionFactory; +import io.reactivesocket.aeron.client.FrameHolder; +import org.HdrHistogram.Recorder; +import org.reactivestreams.Publisher; +import rx.Observable; +import rx.RxReactiveStreams; +import rx.Subscriber; +import rx.schedulers.Schedulers; + +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class Fire { + public static void main(String... args) throws Exception { + String host = System.getProperty("host", "localhost"); + String server = System.getProperty("server", "localhost"); + + System.out.println("Setting host to => " + host); + + System.out.println("Setting ping is listening to => " + server); + + + byte[] payload = new byte[40]; + Random r = new Random(); + r.nextBytes(payload); + + System.out.println("Sending data of size => " + payload.length); + + InetSocketAddress listenAddress = new InetSocketAddress("localhost", 39790); + InetSocketAddress clientAddress = new InetSocketAddress("localhost", 39790); + + AeronClientDuplexConnectionFactory cf = AeronClientDuplexConnectionFactory.getInstance(); + cf.addSocketAddressToHandleResponses(listenAddress); + Publisher udpConnection = cf.createAeronClientDuplexConnection(clientAddress); + + System.out.println("Creating new duplex connection"); + AeronClientDuplexConnection connection = RxReactiveStreams.toObservable(udpConnection).toBlocking().single(); + System.out.println("Created duplex connection"); + + ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection(connection, ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS)); + reactiveSocket.startAndWait(); + + CountDownLatch latch = new CountDownLatch(Integer.MAX_VALUE); + + final Recorder histogram = new Recorder(3600000000000L, 3); + + Schedulers + .computation() + .createWorker() + .schedulePeriodically(() -> { + System.out.println("---- FRAME HOLDER HISTO ----"); + FrameHolder.histogram.getIntervalHistogram().outputPercentileDistribution(System.out, 5, 1000.0, false); + System.out.println("---- FRAME HOLDER HISTO ----"); + + System.out.println("---- Fire / Forget HISTO ----"); + histogram.getIntervalHistogram().outputPercentileDistribution(System.out, 5, 1000.0, false); + System.out.println("---- Fire / Forget HISTO ----"); + + + }, 10, 10, TimeUnit.SECONDS); + + Observable + .range(1, Integer.MAX_VALUE) + .flatMap(i -> { + long start = System.nanoTime(); + + Payload keyPayload = new Payload() { + ByteBuffer data = ByteBuffer.wrap(payload); + ByteBuffer metadata = ByteBuffer.allocate(0); + + public ByteBuffer getData() { + return data; + } + + @Override + public ByteBuffer getMetadata() { + return metadata; + } + }; + + return RxReactiveStreams + .toObservable( + reactiveSocket + .fireAndForget(keyPayload)) + .finallyDo(() -> { + long diff = System.nanoTime() - start; + histogram.recordValue(diff); + }); + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + } + + @Override + public void onNext(Void v) { + latch.countDown(); + } + }); + + latch.await(); + System.out.println("Sent => " + Integer.MAX_VALUE); + System.exit(0); + } + +} diff --git a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/fireandforget/Forget.java b/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/fireandforget/Forget.java new file mode 100644 index 000000000..a115efddc --- /dev/null +++ b/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/fireandforget/Forget.java @@ -0,0 +1,76 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.example.fireandforget; + +import io.reactivesocket.ConnectionSetupHandler; +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.Payload; +import io.reactivesocket.RequestHandler; +import io.reactivesocket.aeron.server.ReactiveSocketAeronServer; +import io.reactivesocket.exceptions.SetupException; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +public class Forget { + public static void main(String... args) { + + String host = System.getProperty("host", "localhost"); + + System.out.println("Setting host to => " + host); + + ReactiveSocketAeronServer server = ReactiveSocketAeronServer.create(host, 39790, new ConnectionSetupHandler() { + @Override + public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + return new RequestHandler() { + @Override + public Publisher handleRequestResponse(Payload payload) { + return null; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + return null; + } + + @Override + public Publisher handleSubscription(Payload payload) { + return null; + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return new Publisher() { + @Override + public void subscribe(Subscriber s) { + s.onComplete(); + } + }; + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher payloads) { + return null; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + }; + } + }); + } +} From 1f96af3e5aa33f3ee2cf0bc7463fe9a3a0f8244e Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Thu, 5 Nov 2015 14:42:08 -0800 Subject: [PATCH 058/105] frame needs to adjust length for Integer attached to front of frame --- build.gradle | 4 +- .../aeron/internal/Loggable.java | 2 +- .../aeron/example/fireandforget/Fire.java | 74 ++++++++++--------- .../aeron/example/fireandforget/Forget.java | 2 +- .../aeron/example/requestreply/Pong.java | 2 +- .../server/AeronServerDuplexConnection.java | 4 +- .../server/ReactiveSocketAeronServer.java | 2 +- .../aeron/server/ServerAeronManager.java | 21 ++++-- .../aeron/client/ReactiveSocketAeronTest.java | 2 +- 9 files changed, 62 insertions(+), 51 deletions(-) diff --git a/build.gradle b/build.gradle index a032e580d..7db4ece2e 100644 --- a/build.gradle +++ b/build.gradle @@ -20,8 +20,8 @@ subprojects { compile 'io.reactivex:rxjava:1.0.13' compile 'io.reactivex:rxjava-reactive-streams:1.0.1' compile 'io.reactivesocket:reactivesocket:0.0.1-SNAPSHOT' - compile 'uk.co.real-logic:Agrona:0.4.4' - compile 'uk.co.real-logic:aeron-all:0.1.4' + compile 'uk.co.real-logic:Agrona:0.4.5' + compile 'uk.co.real-logic:aeron-all:0.1.5' compile 'org.hdrhistogram:HdrHistogram:2.1.7' compile 'org.slf4j:slf4j-api:1.7.12' testCompile 'junit:junit-dep:4.10' diff --git a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Loggable.java b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Loggable.java index d2d540d3d..7de001478 100644 --- a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Loggable.java +++ b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Loggable.java @@ -24,7 +24,7 @@ public interface Loggable { default void info(String message, Object... args) { - logger().debug(message, args); + logger().info(message, args); } default void error(String message, Throwable t) { diff --git a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/fireandforget/Fire.java b/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/fireandforget/Fire.java index d7160990c..d7b19f6ce 100644 --- a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/fireandforget/Fire.java +++ b/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/fireandforget/Fire.java @@ -24,9 +24,8 @@ import io.reactivesocket.aeron.client.FrameHolder; import org.HdrHistogram.Recorder; import org.reactivestreams.Publisher; -import rx.Observable; +import org.reactivestreams.Subscription; import rx.RxReactiveStreams; -import rx.Subscriber; import rx.schedulers.Schedulers; import java.net.InetSocketAddress; @@ -84,51 +83,54 @@ public static void main(String... args) throws Exception { }, 10, 10, TimeUnit.SECONDS); - Observable - .range(1, Integer.MAX_VALUE) - .flatMap(i -> { - long start = System.nanoTime(); - Payload keyPayload = new Payload() { - ByteBuffer data = ByteBuffer.wrap(payload); - ByteBuffer metadata = ByteBuffer.allocate(0); + for (int i = 0; i < Integer.MAX_VALUE; i++) { + long start = System.nanoTime(); - public ByteBuffer getData() { - return data; + Payload keyPayload = new Payload() { + ByteBuffer data = ByteBuffer.wrap(payload); + ByteBuffer metadata = ByteBuffer.allocate(0); + + public ByteBuffer getData() { + return data; + } + + @Override + public ByteBuffer getMetadata() { + return metadata; + } + }; + + reactiveSocket + .fireAndForget(keyPayload) + .subscribe(new org.reactivestreams.Subscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); } @Override - public ByteBuffer getMetadata() { - return metadata; + public void onNext(Void aVoid) { + } - }; - return RxReactiveStreams - .toObservable( - reactiveSocket - .fireAndForget(keyPayload)) - .finallyDo(() -> { + @Override + public void onError(Throwable t) { + long diff = System.nanoTime() - start; histogram.recordValue(diff); - }); - }) - .subscribe(new Subscriber() { - @Override - public void onCompleted() { - - } - - @Override - public void onError(Throwable e) { - e.printStackTrace(); - } + latch.countDown(); + } - @Override - public void onNext(Void v) { - latch.countDown(); - } - }); + @Override + public void onComplete() { + long diff = System.nanoTime() - start; + histogram.recordValue(diff); + latch.countDown(); + } + }); + } latch.await(); System.out.println("Sent => " + Integer.MAX_VALUE); System.exit(0); diff --git a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/fireandforget/Forget.java b/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/fireandforget/Forget.java index a115efddc..9f6ec0782 100644 --- a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/fireandforget/Forget.java +++ b/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/fireandforget/Forget.java @@ -61,7 +61,7 @@ public void subscribe(Subscriber s) { } @Override - public Publisher handleChannel(Payload initialPayload, Publisher payloads) { + public Publisher handleChannel(Publisher payloads) { return null; } diff --git a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/requestreply/Pong.java b/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/requestreply/Pong.java index 1106edd38..6aab32339 100644 --- a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/requestreply/Pong.java +++ b/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/requestreply/Pong.java @@ -97,7 +97,7 @@ public Publisher handleFireAndForget(Payload payload) { } @Override - public Publisher handleChannel(Payload initialPayload, Publisher payloads) { + public Publisher handleChannel(Publisher payloads) { return null; } diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java index 3aa8a611c..ffb4b1f19 100644 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java @@ -96,6 +96,8 @@ void ackEstablishConnection(int ackSessionId) { @Override public void close() { - publication.close(); + try { + publication.close(); + } catch (Throwable t) {} } } diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java index ee678e4c6..85d62c334 100644 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java @@ -84,7 +84,7 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) AeronServerDuplexConnection connection = connections.get(sessionId); if (connection != null) { List> subscribers = connection.getSubscriber(); - final Frame frame = Frame.from(buffer, BitUtil.SIZE_OF_INT + offset, length); + final Frame frame = Frame.from(buffer, BitUtil.SIZE_OF_INT + offset, length - BitUtil.SIZE_OF_INT); if (isTraceEnabled()) { trace("server received frame payload {} on session id {}", frame.getData(), sessionId); diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java index 6db9b6789..8688a2e05 100644 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java @@ -102,16 +102,23 @@ public Aeron getAeron() { void poll() { Thread dutyThread = new Thread(() -> { for (;;) { - int poll = 0; - for (FragmentAssemblerHolder sh : fragmentAssemblerHolders) { - try { - poll += sh.subscription.poll(sh.fragmentAssembler, Integer.MAX_VALUE); - } catch (Throwable t) { - t.printStackTrace(); + try { + int poll = 0; + for (FragmentAssemblerHolder sh : fragmentAssemblerHolders) { + try { + poll += sh.subscription.poll(sh.fragmentAssembler, Integer.MAX_VALUE); + } catch (Throwable t) { + t.printStackTrace(); + } } + SERVER_IDLE_STRATEGY.idle(poll); + + } catch (Throwable t) { + t.printStackTrace(); } - SERVER_IDLE_STRATEGY.idle(poll); } + + }); dutyThread.setName("reactive-socket-aeron-server"); dutyThread.setDaemon(true); diff --git a/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java b/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java index d0a22904f..e8d88988d 100644 --- a/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java +++ b/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java @@ -88,7 +88,7 @@ public Publisher handleRequestResponse(Payload payload) { } @Override - public Publisher handleChannel(Payload initialPayload, Publisher payloads) { + public Publisher handleChannel(Publisher payloads) { return null; } From 6ff8d3ed7f5f16635c493b6e50c41ce3264d1dc7 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Fri, 6 Nov 2015 15:50:57 -0800 Subject: [PATCH 059/105] added constant so you can switch your idle stratetgy and made the default the backoff strategy --- .../aeron/internal/Constants.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Constants.java b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Constants.java index 19a5375cf..c465805ba 100644 --- a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Constants.java +++ b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Constants.java @@ -15,8 +15,12 @@ */ package io.reactivesocket.aeron.internal; +import uk.co.real_logic.agrona.concurrent.BackoffIdleStrategy; import uk.co.real_logic.agrona.concurrent.IdleStrategy; import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; +import uk.co.real_logic.agrona.concurrent.SleepingIdleStrategy; + +import java.util.concurrent.TimeUnit; public final class Constants { @@ -30,7 +34,7 @@ private Constants() {} public static final int QUEUE_SIZE = Integer.getInteger("reactivesocket.aeron.framesSendQueueSize", 262144); - public static final IdleStrategy SERVER_IDLE_STRATEGY = new NoOpIdleStrategy(); + public static final IdleStrategy SERVER_IDLE_STRATEGY; public static final int CONCURRENCY = Integer.getInteger("reactivesocket.aeron.clientConcurrency", 2); @@ -38,4 +42,15 @@ private Constants() {} public static final boolean TRACING_ENABLED = Boolean.getBoolean("reactivesocket.aeron.tracingEnabled"); + static { + String idlStrategy = System.getProperty("idleStrategy"); + + if (NoOpIdleStrategy.class.getName().equalsIgnoreCase(idlStrategy)) { + SERVER_IDLE_STRATEGY = new NoOpIdleStrategy(); + } else if (SleepingIdleStrategy.class.getName().equalsIgnoreCase(idlStrategy)) { + SERVER_IDLE_STRATEGY = new SleepingIdleStrategy(TimeUnit.MILLISECONDS.toNanos(250)); + } else { + SERVER_IDLE_STRATEGY = new BackoffIdleStrategy(1, 10, 100, 1000); + } + } } From eed57728251e82d22a9c23bd528470a5033ad86e Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Fri, 13 Nov 2015 17:52:40 -0800 Subject: [PATCH 060/105] reproducing bug in junit test --- build.gradle | 4 +-- .../aeron/client/ReactiveSocketAeronTest.java | 36 ++++++++++++++----- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/build.gradle b/build.gradle index 7db4ece2e..d062d9080 100644 --- a/build.gradle +++ b/build.gradle @@ -20,8 +20,8 @@ subprojects { compile 'io.reactivex:rxjava:1.0.13' compile 'io.reactivex:rxjava-reactive-streams:1.0.1' compile 'io.reactivesocket:reactivesocket:0.0.1-SNAPSHOT' - compile 'uk.co.real-logic:Agrona:0.4.5' - compile 'uk.co.real-logic:aeron-all:0.1.5' + compile 'uk.co.real-logic:Agrona:0.4.7' + compile 'uk.co.real-logic:aeron-all:0.2.1' compile 'org.hdrhistogram:HdrHistogram:2.1.7' compile 'org.slf4j:slf4j-api:1.7.12' testCompile 'junit:junit-dep:4.10' diff --git a/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java b/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java index e8d88988d..fbfd1659e 100644 --- a/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java +++ b/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java @@ -23,7 +23,9 @@ import io.reactivesocket.RequestHandler; import io.reactivesocket.aeron.TestUtil; import io.reactivesocket.aeron.server.ReactiveSocketAeronServer; +import io.reactivesocket.exceptions.Exceptions; import io.reactivesocket.exceptions.SetupException; +import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; @@ -31,6 +33,7 @@ import rx.Observable; import rx.RxReactiveStreams; import rx.Subscriber; +import rx.schedulers.Schedulers; import uk.co.real_logic.aeron.driver.MediaDriver; import java.net.InetSocketAddress; @@ -71,6 +74,16 @@ public void testRequestReponse10_000() throws Exception { requestResponseN(10_000); } + @Test(timeout = 120_000) + public void testRequestReponse100_000() throws Exception { + requestResponseN(100_000); + } + + @Test(timeout = 120_000) + public void testRequestReponse1_000_000() throws Exception { + requestResponseN(1_000_000); + } + public void requestResponseN(int count) throws Exception { AtomicLong server = new AtomicLong(); ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { @@ -81,9 +94,13 @@ public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupExc @Override public Publisher handleRequestResponse(Payload payload) { - String request = TestUtil.byteToString(payload.getData()); - //System.out.println(Thread.currentThread() + " Server got => " + request); - Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong => " + server.incrementAndGet(), null)); + + ByteBuffer data = payload.getData(); + String s = TestUtil.byteToString(data); + String m = TestUtil.byteToString(payload.getMetadata()); + Assert.assertEquals(s, "client_request"); + Assert.assertEquals(m, "client_metadata"); + Observable pong = Observable.just(TestUtil.utf8EncodedPayload("server_response", "server_metadata")); return RxReactiveStreams.toPublisher(pong); } @@ -134,16 +151,20 @@ public Publisher handleMetadataPush(Payload payload) { Observable .range(1, count) .flatMap(i -> { - System.out.println("pinging => " + i); - Payload payload = TestUtil.utf8EncodedPayload("ping => " + i, null); + Payload payload = TestUtil.utf8EncodedPayload("client_request", "client_metadata"); Publisher publisher = reactiveSocket.requestResponse(payload); return RxReactiveStreams .toObservable(publisher) - .doOnNext(f -> { - System.out.println("Got => " + i); + .doOnNext(resPayload -> { + ByteBuffer data = resPayload.getData(); + String s = TestUtil.byteToString(data); + String m = TestUtil.byteToString(resPayload.getMetadata()); + Assert.assertEquals(s, "server_response"); + Assert.assertEquals(m, "server_metadata"); }) .doOnNext(f -> latch.countDown()); }) + .subscribeOn(Schedulers.computation()) .subscribe(new Subscriber() { @Override public void onCompleted() { @@ -158,7 +179,6 @@ public void onError(Throwable e) { @Override public void onNext(Payload payload) { - } }); From 54dfb9c877fdebdc058a1ddaad2cd497b0ba732c Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Mon, 16 Nov 2015 18:31:35 -0800 Subject: [PATCH 061/105] found work around to corruption bug, copy data in the fragment handler --- .../server/ReactiveSocketAeronServer.java | 11 ++++++++++- .../aeron/client/ReactiveSocketAeronTest.java | 18 ++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java index 85d62c334..4c6ec3382 100644 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java @@ -30,7 +30,9 @@ import uk.co.real_logic.aeron.logbuffer.Header; import uk.co.real_logic.agrona.BitUtil; import uk.co.real_logic.agrona.DirectBuffer; +import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; +import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @@ -52,6 +54,8 @@ public class ReactiveSocketAeronServer implements AutoCloseable, Loggable { private final LeaseGovernor leaseGovernor; + private static final UnsafeBuffer BUFFER = new UnsafeBuffer(ByteBuffer.allocate(0)); + private static final ServerAeronManager manager = ServerAeronManager.getInstance(); private ReactiveSocketAeronServer(String host, int port, ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { @@ -84,7 +88,12 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) AeronServerDuplexConnection connection = connections.get(sessionId); if (connection != null) { List> subscribers = connection.getSubscriber(); - final Frame frame = Frame.from(buffer, BitUtil.SIZE_OF_INT + offset, length - BitUtil.SIZE_OF_INT); + + ByteBuffer bb = ByteBuffer.allocate(length); + BUFFER.wrap(bb); + buffer.getBytes(offset, BUFFER, 0, length); + + final Frame frame = Frame.from(BUFFER, BitUtil.SIZE_OF_INT, length - BitUtil.SIZE_OF_INT); if (isTraceEnabled()) { trace("server received frame payload {} on session id {}", frame.getData(), sessionId); diff --git a/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java b/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java index fbfd1659e..67090e9bd 100644 --- a/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java +++ b/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java @@ -35,6 +35,7 @@ import rx.Subscriber; import rx.schedulers.Schedulers; import uk.co.real_logic.aeron.driver.MediaDriver; +import uk.co.real_logic.agrona.LangUtil; import java.net.InetSocketAddress; import java.nio.ByteBuffer; @@ -85,7 +86,7 @@ public void testRequestReponse1_000_000() throws Exception { } public void requestResponseN(int count) throws Exception { - AtomicLong server = new AtomicLong(); + AtomicLong counter = new AtomicLong(); ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { @Override public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { @@ -94,12 +95,21 @@ public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupExc @Override public Publisher handleRequestResponse(Payload payload) { - + counter.incrementAndGet(); ByteBuffer data = payload.getData(); String s = TestUtil.byteToString(data); String m = TestUtil.byteToString(payload.getMetadata()); - Assert.assertEquals(s, "client_request"); - Assert.assertEquals(m, "client_metadata"); + + try { + Assert.assertEquals(s, "client_request"); + Assert.assertEquals(m, "client_metadata"); + } catch (Throwable t) { + long l = counter.get(); + System.out.println("Count => " + l); + System.out.println("contains $ => " + s.contains("$")); + throw new RuntimeException(t); + } + Observable pong = Observable.just(TestUtil.utf8EncodedPayload("server_response", "server_metadata")); return RxReactiveStreams.toPublisher(pong); } From a82ce55e506af1058daa2becda1b0ae0741438d3 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Mon, 16 Nov 2015 18:34:10 -0800 Subject: [PATCH 062/105] switching version back to 0.4.5 and aeron 0.1.5 --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index d062d9080..7db4ece2e 100644 --- a/build.gradle +++ b/build.gradle @@ -20,8 +20,8 @@ subprojects { compile 'io.reactivex:rxjava:1.0.13' compile 'io.reactivex:rxjava-reactive-streams:1.0.1' compile 'io.reactivesocket:reactivesocket:0.0.1-SNAPSHOT' - compile 'uk.co.real-logic:Agrona:0.4.7' - compile 'uk.co.real-logic:aeron-all:0.2.1' + compile 'uk.co.real-logic:Agrona:0.4.5' + compile 'uk.co.real-logic:aeron-all:0.1.5' compile 'org.hdrhistogram:HdrHistogram:2.1.7' compile 'org.slf4j:slf4j-api:1.7.12' testCompile 'junit:junit-dep:4.10' From 7c8b20893cc869552bb8131a04a265045a0c5f7b Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Tue, 17 Nov 2015 13:51:16 -0800 Subject: [PATCH 063/105] Upgrading to Agrona 0.4.7 Upgrading to Aeron 0.2.1 Adding checks for closed publications and subscriptions Timeouts are now in the Constants file, need to make them system properties still --- build.gradle | 4 +- .../AeronClientDuplexConnectionFactory.java | 33 ++++--- .../aeron/client/PollingAction.java | 6 +- .../aeron/internal/AeronUtil.java | 8 ++ .../aeron/internal/Constants.java | 16 ++-- .../server/AeronServerDuplexConnection.java | 13 +-- .../server/ReactiveSocketAeronServer.java | 86 ++++++++----------- .../aeron/server/ServerAeronManager.java | 41 +++++---- 8 files changed, 111 insertions(+), 96 deletions(-) diff --git a/build.gradle b/build.gradle index 7db4ece2e..d062d9080 100644 --- a/build.gradle +++ b/build.gradle @@ -20,8 +20,8 @@ subprojects { compile 'io.reactivex:rxjava:1.0.13' compile 'io.reactivex:rxjava-reactive-streams:1.0.1' compile 'io.reactivesocket:reactivesocket:0.0.1-SNAPSHOT' - compile 'uk.co.real-logic:Agrona:0.4.5' - compile 'uk.co.real-logic:aeron-all:0.1.5' + compile 'uk.co.real-logic:Agrona:0.4.7' + compile 'uk.co.real-logic:aeron-all:0.2.1' compile 'org.hdrhistogram:HdrHistogram:2.1.7' compile 'org.slf4j:slf4j-api:1.7.12' testCompile 'junit:junit-dep:4.10' diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java index a840c7eec..c8dc8cc74 100644 --- a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java +++ b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java @@ -34,8 +34,7 @@ public final class AeronClientDuplexConnectionFactory implements Loggable { private final ConcurrentSkipListMap connections; - // TODO - this should be configurable...enough space for 2048 DuplexConnections assuming request(128) - private final ManyToManyConcurrentArrayQueue frameSendQueue = new ManyToManyConcurrentArrayQueue<>(262144); + private final ManyToManyConcurrentArrayQueue frameSendQueue = new ManyToManyConcurrentArrayQueue<>(Constants.QUEUE_SIZE); private final ConcurrentHashMap establishConnectionHolders; @@ -58,16 +57,18 @@ private AeronClientDuplexConnectionFactory() { // Can release the FrameHolder at this point as we got everything we need fh.release(); - AeronUtil - .tryClaimOrOffer(publication, (offset, buffer) -> { - if (traceEnabled) { - trace("Thread Id {} sending Frame => {} on Aeron", threadId, frame.toString()); - } + if (!publication.isClosed()) { + AeronUtil + .tryClaimOrOffer(publication, (offset, buffer) -> { + if (traceEnabled) { + trace("Thread Id {} sending Frame => {} on Aeron", threadId, frame.toString()); + } - buffer.putShort(offset, (short) 0); - buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); - buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, frame.offset(), frame.length()); - }, length); + buffer.putShort(offset, (short) 0); + buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.FRAME.getEncodedType()); + buffer.putBytes(offset + BitUtil.SIZE_OF_INT, byteBuffer, frame.offset(), frame.length()); + }, length); + } }); }); } @@ -145,11 +146,15 @@ void establishConnection(final Publication publication) { final long start = System.nanoTime(); for (;;) { final long current = System.nanoTime(); - if ((current - start) > TimeUnit.SECONDS.toNanos(30)) { + if ((current - start) > TimeUnit.MILLISECONDS.toNanos(Constants.CLIENT_ESTABLISH_CONNECT_TIMEOUT_MS)) { throw new RuntimeException("Timed out waiting to establish connection for session id => " + sessionId); } if (offer < 0) { + if (publication.isClosed()) { + throw new RuntimeException("A closed publication was found when trying to establish for session id => " + sessionId); + } + offer = publication.offer(buffer); } else { break; @@ -203,12 +208,12 @@ public void accept(Publication publication) { connections.remove(publication.sessionId()); // Send a message to the server that the connection is closed and that it needs to clean-up resources on it's side - if (publication != null) { + if (publication != null && !publication.isClosed()) { try { AeronUtil.tryClaimOrOffer(publication, (offset, buffer) -> { buffer.putShort(offset, (short) 0); buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.CONNECTION_DISCONNECT.getEncodedType()); - }, BitUtil.SIZE_OF_INT); + }, BitUtil.SIZE_OF_INT, Constants.CLIENT_SEND_ESTABLISH_CONNECTION_MSG_TIMEOUT_MS, TimeUnit.MILLISECONDS); } catch (Throwable t) { debug("error closing publication with session id => {}", publication.sessionId()); } diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/PollingAction.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/PollingAction.java index 37748aebe..cbb4013dd 100644 --- a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/PollingAction.java +++ b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/PollingAction.java @@ -25,10 +25,12 @@ public void call() { try { for (ClientAeronManager.SubscriptionGroup sg : subscriptionGroups) { try { - int poll; + int poll = 0; do { Subscription subscription = sg.getSubscriptions()[threadId]; - poll = subscription.poll(sg.getFragmentAssembler(threadId), Integer.MAX_VALUE); + if (!subscription.isClosed()) { + poll = subscription.poll(sg.getFragmentAssembler(threadId), Integer.MAX_VALUE); + } } while (poll > 0); for (ClientAeronManager.ClientAction action : clientActions) { diff --git a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java index 965be313e..883c8d230 100644 --- a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java +++ b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java @@ -43,6 +43,10 @@ public class AeronUtil implements Loggable { * that is send over Aeron */ public static void offer(Publication publication, BufferFiller fillBuffer, int length, int timeout, TimeUnit timeUnit) { + if (publication.isClosed()) { + throw new NotConnectedException(); + } + final MutableDirectBuffer buffer = getDirectBuffer(length); fillBuffer.fill(0, buffer); final long start = System.nanoTime(); @@ -76,6 +80,10 @@ public static void offer(Publication publication, BufferFiller fillBuffer, int l * @param length the length of data */ public static void tryClaim(Publication publication, BufferFiller fillBuffer, int length, int timeout, TimeUnit timeUnit) { + if (publication.isClosed()) { + throw new NotConnectedException(); + } + final BufferClaim bufferClaim = bufferClaims.get(); final long start = System.nanoTime(); do { diff --git a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Constants.java b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Constants.java index c465805ba..b04429375 100644 --- a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Constants.java +++ b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Constants.java @@ -24,23 +24,18 @@ public final class Constants { - private Constants() {} - public static final int SERVER_STREAM_ID = 1; - public static final int CLIENT_STREAM_ID = 2; - public static final byte[] EMTPY = new byte[0]; - public static final int QUEUE_SIZE = Integer.getInteger("reactivesocket.aeron.framesSendQueueSize", 262144); - public static final IdleStrategy SERVER_IDLE_STRATEGY; - public static final int CONCURRENCY = Integer.getInteger("reactivesocket.aeron.clientConcurrency", 2); - public static final int AERON_MTU_SIZE = Integer.getInteger("aeron.mtu.length", 4096); - public static final boolean TRACING_ENABLED = Boolean.getBoolean("reactivesocket.aeron.tracingEnabled"); + public static final int CLIENT_ESTABLISH_CONNECT_TIMEOUT_MS = 6000; + public static final int CLIENT_SEND_ESTABLISH_CONNECTION_MSG_TIMEOUT_MS = 5000; + public static final int SERVER_ACK_ESTABLISH_CONNECTION_TIMEOUT_MS = 5000; + public static final int SERVER_ESTABLISH_CONNECTION_REQUEST_TIMEOUT_MS = 5000; static { String idlStrategy = System.getProperty("idleStrategy"); @@ -53,4 +48,7 @@ private Constants() {} SERVER_IDLE_STRATEGY = new BackoffIdleStrategy(1, 10, 100, 1000); } } + + private Constants() { + } } diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java index ffb4b1f19..e01be7481 100644 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java @@ -17,10 +17,7 @@ import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; -import io.reactivesocket.aeron.internal.AeronUtil; -import io.reactivesocket.aeron.internal.Loggable; -import io.reactivesocket.aeron.internal.MessageType; -import io.reactivesocket.aeron.internal.NotConnectedException; +import io.reactivesocket.aeron.internal.*; import io.reactivesocket.rx.Completable; import io.reactivesocket.rx.Disposable; import io.reactivesocket.rx.Observable; @@ -36,6 +33,7 @@ public class AeronServerDuplexConnection implements DuplexConnection, Loggable { private final Publication publication; private final CopyOnWriteArrayList> subjects; + private volatile boolean isClosed; public AeronServerDuplexConnection( Publication publication) { @@ -85,7 +83,7 @@ void ackEstablishConnection(int ackSessionId) { buffer.putShort(offset, (short) 0); buffer.putShort(offset + BitUtil.SIZE_OF_SHORT, (short) MessageType.ESTABLISH_CONNECTION_RESPONSE.getEncodedType()); buffer.putInt(offset + BitUtil.SIZE_OF_INT, ackSessionId); - }, 2 * BitUtil.SIZE_OF_INT, 30, TimeUnit.SECONDS); + }, 2 * BitUtil.SIZE_OF_INT, Constants.SERVER_ACK_ESTABLISH_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS); debug("Ack sent for session i => {}", ackSessionId); } catch (NotConnectedException ne) { continue; @@ -94,8 +92,13 @@ void ackEstablishConnection(int ackSessionId) { } } + public boolean isClosed() { + return isClosed; + } + @Override public void close() { + isClosed = true; try { publication.close(); } catch (Throwable t) {} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java index 4c6ec3382..16565358b 100644 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java @@ -22,11 +22,7 @@ import io.reactivesocket.aeron.internal.Loggable; import io.reactivesocket.aeron.internal.MessageType; import io.reactivesocket.rx.Observer; -import uk.co.real_logic.aeron.Aeron; -import uk.co.real_logic.aeron.FragmentAssembler; -import uk.co.real_logic.aeron.Image; -import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.aeron.Subscription; +import uk.co.real_logic.aeron.*; import uk.co.real_logic.aeron.logbuffer.Header; import uk.co.real_logic.agrona.BitUtil; import uk.co.real_logic.agrona.DirectBuffer; @@ -38,26 +34,18 @@ import java.util.concurrent.TimeUnit; import java.util.function.Consumer; -import static io.reactivesocket.aeron.internal.Constants.CLIENT_STREAM_ID; -import static io.reactivesocket.aeron.internal.Constants.SERVER_STREAM_ID; +import static io.reactivesocket.aeron.internal.Constants.*; public class ReactiveSocketAeronServer implements AutoCloseable, Loggable { + private static final UnsafeBuffer BUFFER = new UnsafeBuffer(ByteBuffer.allocate(0)); + private static final ServerAeronManager manager = ServerAeronManager.getInstance(); private final int port; - private final ConcurrentHashMap connections = new ConcurrentHashMap<>(); - private final ConcurrentHashMap sockets = new ConcurrentHashMap<>(); - private final Subscription subscription; - private final ConnectionSetupHandler connectionSetupHandler; - private final LeaseGovernor leaseGovernor; - private static final UnsafeBuffer BUFFER = new UnsafeBuffer(ByteBuffer.allocate(0)); - - private static final ServerAeronManager manager = ServerAeronManager.getInstance(); - private ReactiveSocketAeronServer(String host, int port, ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { this.port = port; this.connectionSetupHandler = connectionSetupHandler; @@ -74,8 +62,33 @@ private ReactiveSocketAeronServer(String host, int port, ConnectionSetupHandler FragmentAssembler fragmentAssembler = new FragmentAssembler(this::fragmentHandler); manager.addSubscription(subscription, fragmentAssembler); + } + + /* + * Factory Methods + */ + public static ReactiveSocketAeronServer create(String host, int port, ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { + return new ReactiveSocketAeronServer(host, port, connectionSetupHandler, leaseGovernor); + } + + public static ReactiveSocketAeronServer create(int port, ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { + return create("127.0.0.1", port, connectionSetupHandler, leaseGovernor); + } + + public static ReactiveSocketAeronServer create(ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { + return create(39790, connectionSetupHandler, leaseGovernor); + } + + public static ReactiveSocketAeronServer create(String host, int port, ConnectionSetupHandler connectionSetupHandler) { + return new ReactiveSocketAeronServer(host, port, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); + } + public static ReactiveSocketAeronServer create(int port, ConnectionSetupHandler connectionSetupHandler) { + return create("localhost", port, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); + } + public static ReactiveSocketAeronServer create(ConnectionSetupHandler connectionSetupHandler) { + return create(39790, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); } void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) { @@ -86,7 +99,7 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) if (MessageType.FRAME == type) { AeronServerDuplexConnection connection = connections.get(sessionId); - if (connection != null) { + if (connection != null && !connection.isClosed()) { List> subscribers = connection.getSubscriber(); ByteBuffer bb = ByteBuffer.allocate(length); @@ -114,7 +127,7 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) while (connection == null) { final long current = System.nanoTime(); - if (current - start > TimeUnit.SECONDS.toNanos(30)) { + if ((current - start) > TimeUnit.MILLISECONDS.toNanos(SERVER_ESTABLISH_CONNECTION_REQUEST_TIMEOUT_MS)) { throw new RuntimeException("unable to find connection to ack establish connection for session id => " + sessionId); } @@ -169,10 +182,12 @@ private void closeReactiveSocket(int sessionId) { ReactiveSocket socket = sockets.remove(sessionId); connections.remove(sessionId); - try { - socket.close(); - } catch (Throwable t) { - error("error closing socket for session id => " + sessionId, t); + if (socket != null) { + try { + socket.close(); + } catch (Throwable t) { + error("error closing socket for session id => " + sessionId, t); + } } } @@ -185,31 +200,4 @@ public void close() throws Exception { manager.removeSubscription(subscription); } - /* - * Factory Methods - */ - public static ReactiveSocketAeronServer create(String host, int port, ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { - return new ReactiveSocketAeronServer(host, port, connectionSetupHandler, leaseGovernor); - } - - public static ReactiveSocketAeronServer create(int port, ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { - return create("127.0.0.1", port, connectionSetupHandler, leaseGovernor); - } - - public static ReactiveSocketAeronServer create(ConnectionSetupHandler connectionSetupHandler, LeaseGovernor leaseGovernor) { - return create(39790, connectionSetupHandler, leaseGovernor); - } - - public static ReactiveSocketAeronServer create(String host, int port, ConnectionSetupHandler connectionSetupHandler) { - return new ReactiveSocketAeronServer(host, port, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); - } - - public static ReactiveSocketAeronServer create(int port, ConnectionSetupHandler connectionSetupHandler) { - return create("localhost", port, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); - } - - public static ReactiveSocketAeronServer create(ConnectionSetupHandler connectionSetupHandler) { - return create(39790, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); - } - } diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java index 8688a2e05..929899472 100644 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java @@ -16,14 +16,11 @@ package io.reactivesocket.aeron.server; import io.reactivesocket.aeron.internal.Loggable; -import uk.co.real_logic.aeron.Aeron; -import uk.co.real_logic.aeron.AvailableImageHandler; -import uk.co.real_logic.aeron.FragmentAssembler; -import uk.co.real_logic.aeron.Image; -import uk.co.real_logic.aeron.Subscription; -import uk.co.real_logic.aeron.UnavailableImageHandler; +import uk.co.real_logic.aeron.*; +import uk.co.real_logic.agrona.TimerWheel; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; import static io.reactivesocket.aeron.internal.Constants.SERVER_IDLE_STRATEGY; @@ -42,15 +39,7 @@ public class ServerAeronManager implements Loggable { private CopyOnWriteArrayList fragmentAssemblerHolders = new CopyOnWriteArrayList<>(); - private class FragmentAssemblerHolder { - private Subscription subscription; - private FragmentAssembler fragmentAssembler; - - public FragmentAssemblerHolder(Subscription subscription, FragmentAssembler fragmentAssembler) { - this.subscription = subscription; - this.fragmentAssembler = fragmentAssembler; - } - } + private TimerWheel timerWheel; public ServerAeronManager() { final Aeron.Context ctx = new Aeron.Context(); @@ -60,6 +49,8 @@ public ServerAeronManager() { aeron = Aeron.connect(ctx); + this.timerWheel = new TimerWheel(1, TimeUnit.MILLISECONDS, 1024); + poll(); } @@ -99,6 +90,10 @@ public Aeron getAeron() { return aeron; } + public TimerWheel getTimerWheel() { + return timerWheel; + } + void poll() { Thread dutyThread = new Thread(() -> { for (;;) { @@ -106,6 +101,10 @@ void poll() { int poll = 0; for (FragmentAssemblerHolder sh : fragmentAssemblerHolders) { try { + if (sh.subscription.isClosed()) { + continue; + } + poll += sh.subscription.poll(sh.fragmentAssembler, Integer.MAX_VALUE); } catch (Throwable t) { t.printStackTrace(); @@ -116,6 +115,8 @@ void poll() { } catch (Throwable t) { t.printStackTrace(); } + + timerWheel.expireTimers(); } @@ -124,4 +125,14 @@ void poll() { dutyThread.setDaemon(true); dutyThread.start(); } + + private class FragmentAssemblerHolder { + private Subscription subscription; + private FragmentAssembler fragmentAssembler; + + public FragmentAssemblerHolder(Subscription subscription, FragmentAssembler fragmentAssembler) { + this.subscription = subscription; + this.fragmentAssembler = fragmentAssembler; + } + } } From f41aec7f6d2b981fdefe7887a9d7377da710d181 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Tue, 17 Nov 2015 18:09:59 -0800 Subject: [PATCH 064/105] ServerAeronManager exposes an Agrona TimerWheel that runs on the polling event loop Use TImerWheel to remove remove closed sessions to prevent removing entries in a map will trying to add them --- .../aeron/internal/AeronUtil.java | 21 ++++++++-------- .../aeron/internal/Constants.java | 5 +++- .../server/ReactiveSocketAeronServer.java | 22 ++++++++-------- .../aeron/server/ServerAeronManager.java | 25 +++++++++++++------ 4 files changed, 43 insertions(+), 30 deletions(-) diff --git a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java index 883c8d230..b1f24eb90 100644 --- a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java +++ b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java @@ -23,6 +23,8 @@ import java.util.concurrent.TimeUnit; +import static io.reactivesocket.aeron.internal.Constants.DEFAULT_OFFER_TO_AERON_TIMEOUT_MS; + /** * Utils for dealing with Aeron */ @@ -51,12 +53,11 @@ public static void offer(Publication publication, BufferFiller fillBuffer, int l fillBuffer.fill(0, buffer); final long start = System.nanoTime(); do { - if (timeout > 0) { - final long current = System.nanoTime(); - if ((current - start) > timeUnit.toNanos(timeout)) { - throw new TimedOutException(); - } + final long current = System.nanoTime(); + if ((current - start) > timeUnit.toNanos(timeout)) { + throw new TimedOutException(); } + final long offer = publication.offer(buffer); if (offer >= 0) { break; @@ -87,11 +88,9 @@ public static void tryClaim(Publication publication, BufferFiller fillBuffer, in final BufferClaim bufferClaim = bufferClaims.get(); final long start = System.nanoTime(); do { - if (timeout > 0) { - final long current = System.nanoTime(); - if ((current - start) > timeUnit.toNanos(timeout)) { - throw new TimedOutException(); - } + final long current = System.nanoTime(); + if ((current - start) > timeUnit.toNanos(timeout)) { + throw new TimedOutException(); } final long offer = publication.tryClaim(length, bufferClaim); @@ -120,7 +119,7 @@ public static void tryClaim(Publication publication, BufferFiller fillBuffer, in * @param length the length of data */ public static void tryClaimOrOffer(Publication publication, BufferFiller fillBuffer, int length) { - tryClaimOrOffer(publication, fillBuffer, length, -1, null); + tryClaimOrOffer(publication, fillBuffer, length, DEFAULT_OFFER_TO_AERON_TIMEOUT_MS, TimeUnit.MILLISECONDS); } public static void tryClaimOrOffer(Publication publication, BufferFiller fillBuffer, int length, int timeout, TimeUnit timeUnit) { diff --git a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Constants.java b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Constants.java index b04429375..f60db97fe 100644 --- a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Constants.java +++ b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Constants.java @@ -34,8 +34,11 @@ public final class Constants { public static final boolean TRACING_ENABLED = Boolean.getBoolean("reactivesocket.aeron.tracingEnabled"); public static final int CLIENT_ESTABLISH_CONNECT_TIMEOUT_MS = 6000; public static final int CLIENT_SEND_ESTABLISH_CONNECTION_MSG_TIMEOUT_MS = 5000; - public static final int SERVER_ACK_ESTABLISH_CONNECTION_TIMEOUT_MS = 5000; + public static final int SERVER_ACK_ESTABLISH_CONNECTION_TIMEOUT_MS = 3000; public static final int SERVER_ESTABLISH_CONNECTION_REQUEST_TIMEOUT_MS = 5000; + public static final int SERVER_TIMER_WHEEL_TICK_DURATION_MS = 10; + public static final int SERVER_TIMER_WHEEL_BUCKETS = 128; + public static final int DEFAULT_OFFER_TO_AERON_TIMEOUT_MS = 30_000; static { String idlStrategy = System.getProperty("idleStrategy"); diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java index 16565358b..6c1dc0453 100644 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java @@ -178,17 +178,19 @@ void unavailableImage(Image image, Subscription subscription, long position) { } private void closeReactiveSocket(int sessionId) { - debug("closing connection for session id => " + sessionId); - ReactiveSocket socket = sockets.remove(sessionId); - connections.remove(sessionId); - - if (socket != null) { - try { - socket.close(); - } catch (Throwable t) { - error("error closing socket for session id => " + sessionId, t); + ServerAeronManager.getInstance().getTimerWheel().newTimeout(200, TimeUnit.MILLISECONDS, () -> { + debug("closing connection for session id => " + sessionId); + ReactiveSocket socket = sockets.remove(sessionId); + connections.remove(sessionId); + + if (socket != null) { + try { + socket.close(); + } catch (Throwable t) { + error("error closing socket for session id => " + sessionId, t); + } } - } + }); } public boolean hasConnections() { diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java index 929899472..ed361223e 100644 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java @@ -1,12 +1,12 @@ /** * Copyright 2015 Netflix, Inc. - * + *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + *

* http://www.apache.org/licenses/LICENSE-2.0 - * + *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,6 +15,7 @@ */ package io.reactivesocket.aeron.server; +import io.reactivesocket.aeron.internal.Constants; import io.reactivesocket.aeron.internal.Loggable; import uk.co.real_logic.aeron.*; import uk.co.real_logic.agrona.TimerWheel; @@ -49,7 +50,7 @@ public ServerAeronManager() { aeron = Aeron.connect(ctx); - this.timerWheel = new TimerWheel(1, TimeUnit.MILLISECONDS, 1024); + this.timerWheel = new TimerWheel(Constants.SERVER_TIMER_WHEEL_TICK_DURATION_MS, TimeUnit.MILLISECONDS, Constants.SERVER_TIMER_WHEEL_BUCKETS); poll(); } @@ -78,12 +79,12 @@ public void removeSubscription(Subscription subscription) { private void availableImageHandler(Image image, Subscription subscription, long joiningPosition, String sourceIdentity) { availableImageHandlers - .forEach(handler -> handler.onAvailableImage(image, subscription, joiningPosition, sourceIdentity)); + .forEach(handler -> handler.onAvailableImage(image, subscription, joiningPosition, sourceIdentity)); } private void unavailableImage(Image image, Subscription subscription, long position) { unavailableImageHandlers - .forEach(handler -> handler.onUnavailableImage(image, subscription, position)); + .forEach(handler -> handler.onUnavailableImage(image, subscription, position)); } public Aeron getAeron() { @@ -96,7 +97,7 @@ public TimerWheel getTimerWheel() { void poll() { Thread dutyThread = new Thread(() -> { - for (;;) { + for (; ; ) { try { int poll = 0; for (FragmentAssemblerHolder sh : fragmentAssemblerHolders) { @@ -110,13 +111,21 @@ void poll() { t.printStackTrace(); } } + SERVER_IDLE_STRATEGY.idle(poll); + try { + if (timerWheel.computeDelayInMs() < 0) { + timerWheel.expireTimers(); + } + } catch (Throwable t) { + t.printStackTrace(); + } + } catch (Throwable t) { t.printStackTrace(); } - timerWheel.expireTimers(); } From 37dc88fa61926f8df5f15f97f5d911770b69e802 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Tue, 17 Nov 2015 18:17:10 -0800 Subject: [PATCH 065/105] Added test to reconnect in a loop --- .../aeron/client/ReactiveSocketAeronTest.java | 217 ++++++++++-------- 1 file changed, 120 insertions(+), 97 deletions(-) diff --git a/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java b/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java index 67090e9bd..e719a26a3 100644 --- a/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java +++ b/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java @@ -15,15 +15,9 @@ */ package io.reactivesocket.aeron.client; -import io.reactivesocket.ConnectionSetupHandler; -import io.reactivesocket.ConnectionSetupPayload; -import io.reactivesocket.Frame; -import io.reactivesocket.Payload; -import io.reactivesocket.ReactiveSocket; -import io.reactivesocket.RequestHandler; +import io.reactivesocket.*; import io.reactivesocket.aeron.TestUtil; import io.reactivesocket.aeron.server.ReactiveSocketAeronServer; -import io.reactivesocket.exceptions.Exceptions; import io.reactivesocket.exceptions.SetupException; import org.junit.Assert; import org.junit.BeforeClass; @@ -35,12 +29,13 @@ import rx.Subscriber; import rx.schedulers.Schedulers; import uk.co.real_logic.aeron.driver.MediaDriver; -import uk.co.real_logic.agrona.LangUtil; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.LockSupport; /** * Aeron integration tests @@ -54,10 +49,11 @@ public class ReactiveSocketAeronTest { @BeforeClass public static void init() { + final MediaDriver.Context context = new MediaDriver.Context(); context.dirsDeleteOnStart(true); - final MediaDriver mediaDriver = MediaDriver.launch(context); + } @Test(timeout = 3000) @@ -195,6 +191,121 @@ public void onNext(Payload payload) { latch.await(); } + @Test(timeout = 75000) + public void testReconnection() throws Exception { + System.out.println("--------------------------------------------------------------------------------"); + + ReactiveSocketAeronServer server = ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { + @Override + public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + return new RequestHandler() { + Frame frame = Frame.from(ByteBuffer.allocate(1)); + + @Override + public Publisher handleRequestResponse(Payload payload) { + String request = TestUtil.byteToString(payload.getData()); + System.out.println(Thread.currentThread() + " Server got => " + request); + Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong", null)); + return RxReactiveStreams.toPublisher(pong); + } + + @Override + public Publisher handleChannel(Publisher payloads) { + return null; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + return null; + } + + @Override + public Publisher handleSubscription(Payload payload) { + return null; + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return null; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + }; + } + }); + + System.out.println("--------------------------------------------------------------------------------"); + + + InetSocketAddress listenAddress = new InetSocketAddress("localhost", 39790); + InetSocketAddress clientAddress = new InetSocketAddress("localhost", 39790); + + AeronClientDuplexConnectionFactory cf = AeronClientDuplexConnectionFactory.getInstance(); + cf.addSocketAddressToHandleResponses(listenAddress); + + int j; + for (j = 0; j < 30; j++) { + CountDownLatch latch = new CountDownLatch(10); + + Publisher udpConnection = cf.createUDPConnection(clientAddress); + + System.out.println("Creating new duplex connection => " + j); + AeronClientDuplexConnection connection = RxReactiveStreams.toObservable(udpConnection).toBlocking().single(); + System.out.println("Created duplex connection => " + j); + + ReactiveSocket client = ReactiveSocket.fromClientConnection(connection, ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS)); + client.startAndWait(); + + Observable + .range(1, 10) + .flatMap(i -> { + Payload payload = TestUtil.utf8EncodedPayload("ping =>" + i, null); + return RxReactiveStreams.toObservable(client.requestResponse(payload)); + } + ) + .doOnNext(p -> { + Assert.assertEquals("pong", TestUtil.byteToString(p.getData())); + }) + .subscribe(new rx.Subscriber() { + @Override + public void onCompleted() { + System.out.println("I HAVE COMPLETED $$$$$$$$$$$$$$$$$$$$$$$$$$$$"); + } + + @Override + public void onError(Throwable e) { + System.out.println(Thread.currentThread() + " counted to => " + latch.getCount()); + e.printStackTrace(); + } + + @Override + public void onNext(Payload s) { + System.out.println(Thread.currentThread() + " countdown => " + latch.getCount()); + latch.countDown(); + } + }); + + latch.await(); + + + client.close(); + + while (server.hasConnections()) { + LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); + } + + System.out.println("--------------------------------------------------------------------------------"); + + } + + Assert.assertEquals(j, 30); + + System.out.println("+++ GOT HERE"); + } + /* @@ -766,95 +877,7 @@ public Publisher handleMetadataPush(Payload payload) { latch.await(); } - @Test(timeout = 75000) - public void testReconnection() throws Exception { - System.out.println("--------------------------------------------------------------------------------"); - - ReactiveSocketAeronServer server = ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { - @Override - public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { - return new RequestHandler() { - Frame frame = Frame.from(ByteBuffer.allocate(1)); - - @Override - public Publisher handleRequestResponse(Payload payload) { - String request = TestUtil.byteToString(payload.getData()); - System.out.println(Thread.currentThread() + " Server got => " + request); - Observable pong = Observable.just(TestUtil.utf8EncodedPayload("pong", null)); - return RxReactiveStreams.toPublisher(pong); - } - - @Override - public Publisher handleChannel(Payload initialPayload, Publisher payloads) { - return null; - } - - @Override - public Publisher handleRequestStream(Payload payload) { - return null; - } - - @Override - public Publisher handleSubscription(Payload payload) { - return null; - } - - @Override - public Publisher handleFireAndForget(Payload payload) { - return null; - } - - @Override - public Publisher handleMetadataPush(Payload payload) { - return null; - } - }; - } - }); - - System.out.println("--------------------------------------------------------------------------------"); - - for (int j = 0; j < 3; j++) { - CountDownLatch latch = new CountDownLatch(1); - - ReactiveSocketAeronClient client = ReactiveSocketAeronClient.create("localhost", "localhost"); - - Observable - .range(1, 1) - .flatMap(i -> { - Payload payload = TestUtil.utf8EncodedPayload("ping =>" + System.nanoTime(), null); - return RxReactiveStreams.toObservable(client.requestResponse(payload)); - } - ) - .subscribe(new rx.Subscriber() { - @Override - public void onCompleted() { - System.out.println("I HAVE COMPLETED $$$$$$$$$$$$$$$$$$$$$$$$$$$$"); - } - - @Override - public void onError(Throwable e) { - System.out.println(Thread.currentThread() + " counted to => " + latch.getCount()); - e.printStackTrace(); - } - - @Override - public void onNext(Payload s) { - System.out.println(Thread.currentThread() + " countdown => " + latch.getCount()); - latch.countDown(); - } - }); - latch.await(); - - client.close(); - - while (server.hasConnections()) { - LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); - } - - System.out.println("--------------------------------------------------------------------------------"); - } }*/ From e64eb3a25b754052d27c742fc636ee3bd9fc9521 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Thu, 26 Nov 2015 12:36:51 -0800 Subject: [PATCH 066/105] adding a timerwheel based lease manager --- build.gradle | 4 +- .../aeron/server/ServerAeronManager.java | 15 +- .../server/TimerWheelFairLeaseGovernor.java | 126 +++++++ .../rx/ReactiveSocketAeronScheduler.java | 71 ++++ .../rx/ReactiveSocketAeronSchedulerTest.java | 101 +++++ .../driver/media/UdpChannelTransport.java | 344 ++++++++++++++++++ 6 files changed, 649 insertions(+), 12 deletions(-) create mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/TimerWheelFairLeaseGovernor.java create mode 100644 reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronScheduler.java create mode 100644 reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronSchedulerTest.java create mode 100644 reactivesocket-aeron-tests/src/test/java/uk/co/real_logic/aeron/driver/media/UdpChannelTransport.java diff --git a/build.gradle b/build.gradle index d062d9080..c60603903 100644 --- a/build.gradle +++ b/build.gradle @@ -20,8 +20,8 @@ subprojects { compile 'io.reactivex:rxjava:1.0.13' compile 'io.reactivex:rxjava-reactive-streams:1.0.1' compile 'io.reactivesocket:reactivesocket:0.0.1-SNAPSHOT' - compile 'uk.co.real-logic:Agrona:0.4.7' - compile 'uk.co.real-logic:aeron-all:0.2.1' + compile 'uk.co.real-logic:Agrona:0.4.8' + compile 'uk.co.real-logic:aeron-all:0.2.2' compile 'org.hdrhistogram:HdrHistogram:2.1.7' compile 'org.slf4j:slf4j-api:1.7.12' testCompile 'junit:junit-dep:4.10' diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java index ed361223e..9bae90f35 100644 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java @@ -42,7 +42,7 @@ public class ServerAeronManager implements Loggable { private TimerWheel timerWheel; - public ServerAeronManager() { + private ServerAeronManager() { final Aeron.Context ctx = new Aeron.Context(); ctx.availableImageHandler(this::availableImageHandler); ctx.unavailableImageHandler(this::unavailableImage); @@ -112,23 +112,18 @@ void poll() { } } - SERVER_IDLE_STRATEGY.idle(poll); - - try { - if (timerWheel.computeDelayInMs() < 0) { - timerWheel.expireTimers(); - } - } catch (Throwable t) { - t.printStackTrace(); + if (timerWheel.computeDelayInMs() < 0) { + poll += timerWheel.expireTimers(); } + SERVER_IDLE_STRATEGY.idle(poll); + } catch (Throwable t) { t.printStackTrace(); } } - }); dutyThread.setName("reactive-socket-aeron-server"); dutyThread.setDaemon(true); diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/TimerWheelFairLeaseGovernor.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/TimerWheelFairLeaseGovernor.java new file mode 100644 index 000000000..2b35abd08 --- /dev/null +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/TimerWheelFairLeaseGovernor.java @@ -0,0 +1,126 @@ +package io.reactivesocket.aeron.server; + +import io.reactivesocket.Frame; +import io.reactivesocket.LeaseGovernor; +import io.reactivesocket.internal.Responder; +import uk.co.real_logic.agrona.TimerWheel; +import uk.co.real_logic.agrona.collections.Int2IntHashMap; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * Lease Governor that evenly distributes requests all connected clients. The work is done using the + * {@link ServerAeronManager}'s {@link uk.co.real_logic.agrona.TimerWheel} + */ +public class TimerWheelFairLeaseGovernor implements LeaseGovernor, Runnable { + private final int tickets; + private final long period; + private final int ttlMs; + private final TimeUnit unit; + private final TimerWheel.Timer timer; + private final List responders; + private final Int2IntHashMap leaseCount; + + private volatile boolean running = false; + private long p0, p1, p2, p3, p4, p5, p6; + + private volatile int ticketsPerResponder = 0; + private long p10, p11, p12, p13, p14, p15, p16; + + private volatile int extra = 0; + private long p20, p21, p22, p23, p24, p25, p26; + + public TimerWheelFairLeaseGovernor(int tickets, long period, TimeUnit unit) { + this.responders = new ArrayList<>(); + this.leaseCount = new Int2IntHashMap(0); + this.tickets = tickets; + this.period = period; + this.unit = unit; + this.ttlMs = (int) unit.toMillis(period); + this.timer = ServerAeronManager + .getInstance() + .getTimerWheel() + .newBlankTimer(); + } + + @Override + public void run() { + if (!running) { + try { + synchronized (responders) { + final int numResponders = responders.size(); + if (numResponders > 0) { + final int extraWinner = ((int) System.nanoTime()) % numResponders; + + for (int i = 0; i < numResponders; i++) { + int amountToSend = ticketsPerResponder; + if (i == extraWinner) { + amountToSend += extra; + } + Responder responder = responders.get(i); + leaseCount.put(responder.hashCode(), amountToSend); + responder.sendLease(ttlMs, amountToSend); + } + } + } + } finally { + ServerAeronManager + .getInstance() + .getTimerWheel() + .rescheduleTimeout(period, unit, timer, this::run); + } + } + } + + @Override + public void register(Responder responder) { + synchronized (responders) { + responders.add(responder); + + calculateTicketsToSendPerResponder(); + + if (!running) { + running = false; + run(); + } + } + } + + @Override + public void unregister(Responder responder) { + synchronized (responders) { + responders.remove(responder); + + calculateTicketsToSendPerResponder(); + + if (running && responders.isEmpty()) { + running = false; + } + } + } + + void calculateTicketsToSendPerResponder() { + int size = this.responders.size(); + if (size > 0) { + ticketsPerResponder = tickets / size; + extra = tickets - ticketsPerResponder * size; + } + } + + @Override + public boolean accept(Responder responder, Frame frame) { + int count; + synchronized (responders) { + count = leaseCount.get(responder.hashCode()) - 1; + + if (count >= 0) { + leaseCount.put(responder.hashCode(), count); + } + } + + return count > 0; + + } +} \ No newline at end of file diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronScheduler.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronScheduler.java new file mode 100644 index 000000000..14711a61e --- /dev/null +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronScheduler.java @@ -0,0 +1,71 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.server.rx; + +import io.reactivesocket.aeron.server.ServerAeronManager; +import rx.Scheduler; +import rx.Subscription; +import rx.functions.Action0; +import uk.co.real_logic.agrona.TimerWheel; + +import java.util.concurrent.TimeUnit; + +/** + * An implementation of {@link Scheduler} that lets you schedule work on the {@link ServerAeronManager} polling thread. + * The work is scheduled on to the thread use a {@link TimerWheel}. This is useful if you have done work on another + * thread, and than want the work to end up back on the polling thread. + */ +public class ReactiveSocketAeronScheduler extends Scheduler { + private static final ReactiveSocketAeronScheduler instance = new ReactiveSocketAeronScheduler(); + + private ReactiveSocketAeronScheduler() {} + + public static ReactiveSocketAeronScheduler getInstance() { + return instance; + } + + @Override + public Worker createWorker() { + return new Worker(); + } + + static class Worker extends Scheduler.Worker { + private volatile boolean subscribed = true; + + @Override + public Subscription schedule(Action0 action) { + return schedule(action, 0, TimeUnit.MILLISECONDS); + } + + @Override + public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) { + TimerWheel timerWheel = + ServerAeronManager.getInstance().getTimerWheel(); + timerWheel.newTimeout(delayTime, unit, action::call); + return this; + } + + @Override + public void unsubscribe() { + subscribed = false; + } + + @Override + public boolean isUnsubscribed() { + return !subscribed; + } + } +} diff --git a/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronSchedulerTest.java b/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronSchedulerTest.java new file mode 100644 index 000000000..9f10028d1 --- /dev/null +++ b/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronSchedulerTest.java @@ -0,0 +1,101 @@ +package io.reactivesocket.aeron.server.rx; + +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import rx.Observable; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import uk.co.real_logic.aeron.driver.MediaDriver; + +import java.util.concurrent.TimeUnit; + + +@Ignore +public class ReactiveSocketAeronSchedulerTest { + @BeforeClass + public static void init() { + + final MediaDriver.Context context = new MediaDriver.Context(); + context.dirsDeleteOnStart(true); + final MediaDriver mediaDriver = MediaDriver.launch(context); + + } + + @Test + public void test() { + TestSubscriber testSubscriber = new TestSubscriber(); + + Observable + .range(0, 10) + .subscribeOn(ReactiveSocketAeronScheduler.getInstance()) + .doOnNext(i -> { + String name = Thread.currentThread().getName(); + Assert.assertTrue(name.contains("reactive-socket-aeron-server")); + System.out.println(name + " - " + i); + }) + .observeOn(Schedulers.computation()) + .doOnNext(i -> { + String name = Thread.currentThread().getName(); + Assert.assertTrue(name.contains("RxComputationThreadPool")); + System.out.println(name + " - " + i); + }) + .subscribe(testSubscriber); + + testSubscriber.awaitTerminalEvent(1, TimeUnit.SECONDS); + testSubscriber.assertValueCount(10); + } + + @Test + public void testWithFlatMap() { + TestSubscriber testSubscriber = new TestSubscriber(); + + Observable + .range(0, 10) + .flatMap(i -> + Observable + .just(i) + .subscribeOn(ReactiveSocketAeronScheduler.getInstance()) + ) + .subscribe(testSubscriber); + + testSubscriber.awaitTerminalEvent(1, TimeUnit.SECONDS); + testSubscriber.assertValueCount(10); + + } + + @Test + public void testMovingOnAndOffAndOnThePollingThread() { + TestSubscriber testSubscriber = new TestSubscriber(); + Observable + .range(0, 10) + .subscribeOn(ReactiveSocketAeronScheduler.getInstance()) + .doOnNext(i -> { + String name = Thread.currentThread().getName(); + Assert.assertTrue(name.contains("reactive-socket-aeron-server")); + System.out.println(name + " - " + i); + }) + .flatMap(i -> + Observable + .just(i) + .subscribeOn(Schedulers.computation()) + .doOnNext(j -> { + String name = Thread.currentThread().getName(); + Assert.assertTrue(name.contains("RxComputationThreadPool")); + System.out.println(name + " - " + i); + }) + ) + .observeOn(ReactiveSocketAeronScheduler.getInstance()) + .doOnNext(i -> { + String name = Thread.currentThread().getName(); + Assert.assertTrue(name.contains("reactive-socket-aeron-server")); + System.out.println(name + " - " + i); + }) + .subscribe(testSubscriber); + + testSubscriber.awaitTerminalEvent(1, TimeUnit.SECONDS); + testSubscriber.assertValueCount(10); + } + +} \ No newline at end of file diff --git a/reactivesocket-aeron-tests/src/test/java/uk/co/real_logic/aeron/driver/media/UdpChannelTransport.java b/reactivesocket-aeron-tests/src/test/java/uk/co/real_logic/aeron/driver/media/UdpChannelTransport.java new file mode 100644 index 000000000..a1f1b1b9b --- /dev/null +++ b/reactivesocket-aeron-tests/src/test/java/uk/co/real_logic/aeron/driver/media/UdpChannelTransport.java @@ -0,0 +1,344 @@ +/* + * Copyright 2014 - 2015 Real Logic Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package uk.co.real_logic.aeron.driver.media; + +import uk.co.real_logic.aeron.driver.Configuration; +import uk.co.real_logic.aeron.driver.LossGenerator; +import uk.co.real_logic.aeron.driver.event.EventCode; +import uk.co.real_logic.aeron.driver.event.EventLogger; +import uk.co.real_logic.aeron.protocol.HeaderFlyweight; +import uk.co.real_logic.agrona.LangUtil; +import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; + +import java.io.IOException; +import java.net.*; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.DatagramChannel; +import java.nio.channels.SelectionKey; + +import static uk.co.real_logic.aeron.logbuffer.FrameDescriptor.frameLength; +import static uk.co.real_logic.aeron.logbuffer.FrameDescriptor.frameVersion; + +public abstract class UdpChannelTransport implements AutoCloseable +{ + private final UdpChannel udpChannel; + private final LossGenerator lossGenerator; + private final EventLogger logger; + private final ByteBuffer receiveByteBuffer = ByteBuffer.allocateDirect(Configuration.RECEIVE_BYTE_BUFFER_LENGTH); + private final UnsafeBuffer receiveBuffer = new UnsafeBuffer(receiveByteBuffer); + private DatagramChannel sendDatagramChannel; + private DatagramChannel receiveDatagramChannel; + private SelectionKey selectionKey; + private UdpTransportPoller transportPoller; + private InetSocketAddress bindSocketAddress; + private InetSocketAddress endPointSocketAddress; + private InetSocketAddress connectAddress; + + public UdpChannelTransport( + final UdpChannel udpChannel, + final InetSocketAddress endPointSocketAddress, + final InetSocketAddress bindSocketAddress, + final InetSocketAddress connectAddress, + final LossGenerator lossGenerator, + final EventLogger logger) + { + this.udpChannel = udpChannel; + this.lossGenerator = lossGenerator; + this.logger = logger; + this.endPointSocketAddress = endPointSocketAddress; + this.bindSocketAddress = bindSocketAddress; + this.connectAddress = connectAddress; + } + + /** + * Create the underlying channel for reading and writing. + */ + public void openDatagramChannel() + { + try + { + sendDatagramChannel = DatagramChannel.open(udpChannel.protocolFamily()); + receiveDatagramChannel = sendDatagramChannel; + if (udpChannel.isMulticast()) + { + final NetworkInterface localInterface = udpChannel.localInterface(); + + if (null != connectAddress) + { + receiveDatagramChannel = DatagramChannel.open(udpChannel.protocolFamily()); + } + + receiveDatagramChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true); + receiveDatagramChannel.bind(new InetSocketAddress(endPointSocketAddress.getPort())); + receiveDatagramChannel.join(endPointSocketAddress.getAddress(), localInterface); + sendDatagramChannel.setOption(StandardSocketOptions.IP_MULTICAST_IF, localInterface); + + if (null != connectAddress) + { + sendDatagramChannel.connect(connectAddress); + } + } + else + { + sendDatagramChannel.bind(bindSocketAddress); + + if (null != connectAddress) + { + sendDatagramChannel.connect(connectAddress); + } + } + + if (0 != Configuration.SOCKET_SNDBUF_LENGTH) + { + sendDatagramChannel.setOption(StandardSocketOptions.SO_SNDBUF, Configuration.SOCKET_SNDBUF_LENGTH); + } + + if (0 != Configuration.SOCKET_RCVBUF_LENGTH) + { + receiveDatagramChannel.setOption(StandardSocketOptions.SO_RCVBUF, Configuration.SOCKET_RCVBUF_LENGTH); + } + + sendDatagramChannel.configureBlocking(false); + receiveDatagramChannel.configureBlocking(false); + } + catch (final IOException ex) + { + throw new RuntimeException(String.format( + "channel \"%s\" : %s", udpChannel.originalUriString(), ex.toString()), ex); + } + } + + /** + * Register this transport for reading from a {@link UdpTransportPoller}. + * + * @param transportPoller to register read with + */ + public void registerForRead(final UdpTransportPoller transportPoller) + { + this.transportPoller = transportPoller; + selectionKey = transportPoller.registerForRead(this); + } + + /** + * Return underlying {@link UdpChannel} + * + * @return underlying channel + */ + public UdpChannel udpChannel() + { + return udpChannel; + } + + /** + * The {@link DatagramChannel} for this transport channel. + * + * @return {@link DatagramChannel} for this transport channel. + */ + public DatagramChannel receiveDatagramChannel() + { + return receiveDatagramChannel; + } + + /** + * Send contents of {@link ByteBuffer} to connected address + * + * @param buffer to send + * @return number of bytes sent + */ + public int send(final ByteBuffer buffer) + { + logger.logFrameOut(buffer, connectAddress); + + int byteSent = 0; + try + { + byteSent = sendDatagramChannel.write(buffer); + } + catch (final PortUnreachableException | ClosedChannelException ex) + { + // ignore + } + catch (final IOException ex) + { + LangUtil.rethrowUnchecked(ex); + } + + return byteSent; + } + + /** + * Send contents of {@link java.nio.ByteBuffer} to remote address + * + * @param buffer to send + * @param remoteAddress to send to + * @return number of bytes sent + */ + public int sendTo(final ByteBuffer buffer, final InetSocketAddress remoteAddress) + { + logger.logFrameOut(buffer, remoteAddress); + + int bytesSent = 0; + try + { + bytesSent = sendDatagramChannel.send(buffer, remoteAddress); + } + catch (final IOException ex) + { + LangUtil.rethrowUnchecked(ex); + } + + return bytesSent; + } + + /** + * Close transport, canceling any pending read operations and closing channel + */ + public void close() + { + try + { + if (null != selectionKey) + { + selectionKey.cancel(); + } + + if (null != transportPoller) + { + transportPoller.cancelRead(this); + } + + sendDatagramChannel.close(); + + if (receiveDatagramChannel != sendDatagramChannel) + { + receiveDatagramChannel.close(); + } + } + catch (final Exception ex) + { + logger.logException(ex); + } + } + + /** + * Is transport representing a multicast media or unicast + * + * @return if transport is multicast media + */ + public boolean isMulticast() + { + return udpChannel.isMulticast(); + } + + /** + * Return socket option value + * + * @param name of the socket option + * @param type of option + * @return option value + */ + public T getOption(final SocketOption name) + { + T option = null; + try + { + option = sendDatagramChannel.getOption(name); + } + catch (final IOException ex) + { + LangUtil.rethrowUnchecked(ex); + } + + return option; + } + + /** + * Return the capacity of the {@link ByteBuffer} used for reception + * + * @return capacity of receiving byte buffer + */ + public int receiveBufferCapacity() + { + return receiveByteBuffer.capacity(); + } + + /** + * Attempt to receive waiting data. + * + * @return number of bytes received. + */ + public abstract int pollForData(); + + public final boolean isValidFrame(final UnsafeBuffer receiveBuffer, final int length) + { + boolean isFrameValid = true; + + if (frameVersion(receiveBuffer, 0) != HeaderFlyweight.CURRENT_VERSION) + { + logger.log(EventCode.INVALID_VERSION, receiveBuffer, 0, frameLength(receiveBuffer, 0)); + isFrameValid = false; + } + else if (length < HeaderFlyweight.HEADER_LENGTH) + { + logger.log(EventCode.MALFORMED_FRAME_LENGTH, receiveBuffer, 0, length); + isFrameValid = false; + } + + return isFrameValid; + } + + protected final UnsafeBuffer receiveBuffer() + { + return receiveBuffer; + } + + protected final ByteBuffer receiveByteBuffer() + { + return receiveByteBuffer; + } + + protected final EventLogger logger() + { + return logger; + } + + protected final LossGenerator lossGenerator() + { + return lossGenerator; + } + + protected final InetSocketAddress receive() + { + receiveByteBuffer.clear(); + + InetSocketAddress address = null; + try + { + address = (InetSocketAddress)receiveDatagramChannel.receive(receiveByteBuffer); + } + catch (final PortUnreachableException | ClosedChannelException ignored) + { + // do nothing + } + catch (final Exception ex) + { + LangUtil.rethrowUnchecked(ex); + } + + return address; + } +} \ No newline at end of file From cdb5dbfd5b42742dbcbe27b838a38dbad695feb8 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Thu, 26 Nov 2015 12:50:35 -0800 Subject: [PATCH 067/105] updating to Aeron 0.2.2 --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index d062d9080..c60603903 100644 --- a/build.gradle +++ b/build.gradle @@ -20,8 +20,8 @@ subprojects { compile 'io.reactivex:rxjava:1.0.13' compile 'io.reactivex:rxjava-reactive-streams:1.0.1' compile 'io.reactivesocket:reactivesocket:0.0.1-SNAPSHOT' - compile 'uk.co.real-logic:Agrona:0.4.7' - compile 'uk.co.real-logic:aeron-all:0.2.1' + compile 'uk.co.real-logic:Agrona:0.4.8' + compile 'uk.co.real-logic:aeron-all:0.2.2' compile 'org.hdrhistogram:HdrHistogram:2.1.7' compile 'org.slf4j:slf4j-api:1.7.12' testCompile 'junit:junit-dep:4.10' From 79e2530e40111db6958b03dd72591139bf52fcd0 Mon Sep 17 00:00:00 2001 From: Kristoffer Sjogren Date: Thu, 10 Dec 2015 22:17:05 +0100 Subject: [PATCH 068/105] Make client single threaded. - Included integration tests for requestStream. --- .../AeronClientDuplexConnectionFactory.java | 26 ++-- .../aeron/client/ClientAeronManager.java | 66 +++----- .../aeron/client/PollingAction.java | 13 +- .../aeron/internal/Constants.java | 1 - .../aeron/client/ReactiveSocketAeronTest.java | 147 +++++++++++++----- 5 files changed, 139 insertions(+), 114 deletions(-) diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java index c8dc8cc74..3aaf12069 100644 --- a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java +++ b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java @@ -9,6 +9,7 @@ import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import uk.co.real_logic.aeron.Publication; +import uk.co.real_logic.aeron.logbuffer.FragmentHandler; import uk.co.real_logic.aeron.logbuffer.Header; import uk.co.real_logic.agrona.BitUtil; import uk.co.real_logic.agrona.DirectBuffer; @@ -24,7 +25,6 @@ import java.util.concurrent.TimeUnit; import java.util.function.Consumer; -import static io.reactivesocket.aeron.internal.Constants.CONCURRENCY; import static io.reactivesocket.aeron.internal.Constants.SERVER_STREAM_ID; public final class AeronClientDuplexConnectionFactory implements Loggable { @@ -45,7 +45,7 @@ private AeronClientDuplexConnectionFactory() { establishConnectionHolders = new ConcurrentHashMap<>(); manager = ClientAeronManager.getInstance(); - manager.addClientAction(threadId -> { + manager.addClientAction(() -> { final boolean traceEnabled = isTraceEnabled(); frameSendQueue .drain(fh -> { @@ -61,7 +61,7 @@ private AeronClientDuplexConnectionFactory() { AeronUtil .tryClaimOrOffer(publication, (offset, buffer) -> { if (traceEnabled) { - trace("Thread Id {} sending Frame => {} on Aeron", threadId, frame.toString()); + trace("Sending Frame => {} on Aeron", frame.toString()); } buffer.putShort(offset, (short) 0); @@ -96,13 +96,12 @@ void addUDPSocketAddressToHandleResponses(InetSocketAddress socketAddress) { manager.addSubscription( serverChannel, Constants.CLIENT_STREAM_ID, - threadId -> - new ClientAeronManager.ThreadIdAwareFragmentHandler(threadId) { - @Override - public void onFragment(DirectBuffer buffer, int offset, int length, Header header) { - fragmentHandler(getThreadId(), buffer, offset, length, header); - } - }); + new FragmentHandler() { + @Override + public void onFragment(DirectBuffer buffer, int offset, int length, Header header) { + fragmentHandler(buffer, offset, length, header); + } + }); } public Publisher createAeronClientDuplexConnection(SocketAddress socketAddress) { @@ -164,15 +163,10 @@ void establishConnection(final Publication publication) { } - void fragmentHandler(int threadId, DirectBuffer buffer, int offset, int length, Header header) { + void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) { try { short messageCount = buffer.getShort(offset); short messageTypeInt = buffer.getShort(offset + BitUtil.SIZE_OF_SHORT); - final int currentThreadId = Math.abs(messageCount % CONCURRENCY); - - if (currentThreadId != threadId) { - return; - } final MessageType messageType = MessageType.from(messageTypeInt); if (messageType == MessageType.FRAME) { diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java index 377b6db06..2b2bc559f 100644 --- a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java +++ b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java @@ -15,10 +15,8 @@ */ package io.reactivesocket.aeron.client; -import io.reactivesocket.aeron.internal.Constants; import io.reactivesocket.aeron.internal.Loggable; import rx.Scheduler; -import rx.functions.Func1; import rx.schedulers.Schedulers; import uk.co.real_logic.aeron.Aeron; import uk.co.real_logic.aeron.FragmentAssembler; @@ -41,7 +39,7 @@ public class ClientAeronManager implements Loggable { private final Aeron aeron; - private final Scheduler.Worker[] workers = new Scheduler.Worker[Constants.CONCURRENCY]; + private final Scheduler.Worker worker; private ClientAeronManager() { this.clientActions = new CopyOnWriteArrayList<>(); @@ -54,7 +52,7 @@ private ClientAeronManager() { ); aeron = Aeron.connect(ctx); - + worker = Schedulers.computation().createWorker(); poll(); } @@ -87,18 +85,15 @@ public Aeron getAeron() { * * @param subscriptionChannel the channel to create subscriptions on * @param streamId the stream id to create subscriptions on - * @param fragmentHandlerFactory factory that creates a fragment handler that is aware of the thread that is call it. + * @param fragmentHandler fragment handler that is aware of the thread that is call it. */ - public void addSubscription(String subscriptionChannel, int streamId, Func1 fragmentHandlerFactory) { + public void addSubscription(String subscriptionChannel, int streamId, FragmentHandler fragmentHandler) { if (!hasSubscriptionForChannel(subscriptionChannel)) { debug("Creating a subscriptions to channel => {}", subscriptionChannel); - Subscription[] subscriptions = new Subscription[Constants.CONCURRENCY]; - for (int i = 0; i < Constants.CONCURRENCY; i++) { - subscriptions[i] = aeron.addSubscription(subscriptionChannel, streamId); - debug("Subscription created for threadId => {} and channel => {} ", i, subscriptionChannel); - } - SubscriptionGroup subscriptionGroup = new SubscriptionGroup(subscriptionChannel, subscriptions, fragmentHandlerFactory); + Subscription subscription = aeron.addSubscription(subscriptionChannel, streamId); + debug("Subscription created channel => {} ", subscriptionChannel); + SubscriptionGroup subscriptionGroup = new SubscriptionGroup(subscriptionChannel, subscription, fragmentHandler); subscriptionGroups.add(subscriptionGroup); debug("Subscriptions created to channel => {}", subscriptionChannel); @@ -112,14 +107,9 @@ public void addSubscription(String subscriptionChannel, int streamId, Func1 threadLocalFragmentAssembler = new ThreadLocal<>(); private final String channel; - private final Subscription[] subscriptions; - private final Func1 fragmentHandlerFactory; + private final Subscription subscription; + private final FragmentHandler fragmentHandler; - public SubscriptionGroup(String channel, Subscription[] subscriptions, Func1 fragmentHandlerFactory) { + public SubscriptionGroup(String channel, Subscription subscription, FragmentHandler fragmentHandler) { this.channel = channel; - this.subscriptions = subscriptions; - this.fragmentHandlerFactory = fragmentHandlerFactory; + this.subscription = subscription; + this.fragmentHandler = fragmentHandler; } public String getChannel() { return channel; } - public Subscription[] getSubscriptions() { - return subscriptions; + public Subscription getSubscription() { + return subscription; } - public FragmentAssembler getFragmentAssembler(int threadId) { + public FragmentAssembler getFragmentAssembler() { FragmentAssembler assembler = threadLocalFragmentAssembler.get(); if (assembler == null) { - assembler = new FragmentAssembler(fragmentHandlerFactory.call(threadId)); + assembler = new FragmentAssembler(fragmentHandler); threadLocalFragmentAssembler.set(assembler); } @@ -164,22 +154,6 @@ public FragmentAssembler getFragmentAssembler(int threadId) { @FunctionalInterface public interface ClientAction { - void call(int threadId); - } - - /** - * FragmentHandler that is aware of the thread that it is running on. This is useful if you only want a one thread - * to process a particular message. - */ - public static abstract class ThreadIdAwareFragmentHandler implements FragmentHandler { - private int threadId; - - public ThreadIdAwareFragmentHandler(int threadId) { - this.threadId = threadId; - } - - public final int getThreadId() { - return this.threadId; - } + void call(); } } diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/PollingAction.java b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/PollingAction.java index cbb4013dd..6136511e8 100644 --- a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/PollingAction.java +++ b/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/PollingAction.java @@ -7,15 +7,12 @@ import java.util.List; class PollingAction implements Action0, Loggable { - private final int threadId; private final List subscriptionGroups; private final List clientActions; public PollingAction( - int threadId, List subscriptionGroups, List clientActions) { - this.threadId = threadId; this.subscriptionGroups = subscriptionGroups; this.clientActions = clientActions; } @@ -27,22 +24,22 @@ public void call() { try { int poll = 0; do { - Subscription subscription = sg.getSubscriptions()[threadId]; + Subscription subscription = sg.getSubscription(); if (!subscription.isClosed()) { - poll = subscription.poll(sg.getFragmentAssembler(threadId), Integer.MAX_VALUE); + poll = subscription.poll(sg.getFragmentAssembler(), Integer.MAX_VALUE); } } while (poll > 0); for (ClientAeronManager.ClientAction action : clientActions) { - action.call(threadId); + action.call(); } } catch (Throwable t) { - error("error polling aeron subscription on thread with id " + threadId, t); + error("error polling aeron subscription", t); } } } catch (Throwable t) { - error("error in client polling loop on thread with id " + threadId, t); + error("error in client polling loop", t); } } } diff --git a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Constants.java b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Constants.java index f60db97fe..6fbe2c1b9 100644 --- a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Constants.java +++ b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Constants.java @@ -29,7 +29,6 @@ public final class Constants { public static final byte[] EMTPY = new byte[0]; public static final int QUEUE_SIZE = Integer.getInteger("reactivesocket.aeron.framesSendQueueSize", 262144); public static final IdleStrategy SERVER_IDLE_STRATEGY; - public static final int CONCURRENCY = Integer.getInteger("reactivesocket.aeron.clientConcurrency", 2); public static final int AERON_MTU_SIZE = Integer.getInteger("aeron.mtu.length", 4096); public static final boolean TRACING_ENABLED = Boolean.getBoolean("reactivesocket.aeron.tracingEnabled"); public static final int CLIENT_ESTABLISH_CONNECT_TIMEOUT_MS = 6000; diff --git a/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java b/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java index e719a26a3..b8452f842 100644 --- a/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java +++ b/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java @@ -36,6 +36,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.LockSupport; +import java.util.function.Function; /** * Aeron integration tests @@ -66,7 +67,7 @@ public void testRequestReponse10() throws Exception { requestResponseN(10); } - @Test(timeout = 30000) + @Test(timeout = 30_000) public void testRequestReponse10_000() throws Exception { requestResponseN(10_000); } @@ -81,60 +82,55 @@ public void testRequestReponse1_000_000() throws Exception { requestResponseN(1_000_000); } + @Test(timeout = 10_000) + public void testRequestStream1() throws Exception { + requestStreamN(1); + } + + @Test(timeout = 10_000) + public void testRequestStream10() throws Exception { + requestStreamN(10); + } + + @Test(timeout = 30_000) + public void testRequestStream10_000() throws Exception { + requestStreamN(10_000); + } + + @Test(timeout = 120_000) + public void testRequestStream100_000() throws Exception { + requestStreamN(100_000); + } + public void requestResponseN(int count) throws Exception { AtomicLong counter = new AtomicLong(); ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { @Override public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { - return new RequestHandler() { - Frame frame = Frame.from(ByteBuffer.allocate(1)); - - @Override - public Publisher handleRequestResponse(Payload payload) { - counter.incrementAndGet(); - ByteBuffer data = payload.getData(); - String s = TestUtil.byteToString(data); - String m = TestUtil.byteToString(payload.getMetadata()); - - try { + return new RequestHandler.Builder() + .withRequestResponse(new Function>() { + Frame frame = Frame.from(ByteBuffer.allocate(1)); + @Override + public Publisher apply(Payload payload) { + counter.incrementAndGet(); + ByteBuffer data = payload.getData(); + String s = TestUtil.byteToString(data); + String m = TestUtil.byteToString(payload.getMetadata()); + + try { Assert.assertEquals(s, "client_request"); Assert.assertEquals(m, "client_metadata"); - } catch (Throwable t) { + } catch (Throwable t) { long l = counter.get(); System.out.println("Count => " + l); System.out.println("contains $ => " + s.contains("$")); throw new RuntimeException(t); - } - - Observable pong = Observable.just(TestUtil.utf8EncodedPayload("server_response", "server_metadata")); - return RxReactiveStreams.toPublisher(pong); - } - - @Override - public Publisher handleChannel(Publisher payloads) { - return null; - } - - @Override - public Publisher handleRequestStream(Payload payload) { - return null; - } - - @Override - public Publisher handleSubscription(Payload payload) { - return null; - } - - @Override - public Publisher handleFireAndForget(Payload payload) { - return null; - } + } - @Override - public Publisher handleMetadataPush(Payload payload) { - return null; - } - }; + Observable pong = Observable.just(TestUtil.utf8EncodedPayload("server_response", "server_metadata")); + return RxReactiveStreams.toPublisher(pong); + } + }).build(); } }); @@ -191,6 +187,71 @@ public void onNext(Payload payload) { latch.await(); } + public void requestStreamN(int count) throws Exception { + ReactiveSocketAeronServer.create(setupPayload -> + new RequestHandler.Builder() + .withRequestStream(payload -> { + ByteBuffer data = payload.getData(); + String s = TestUtil.byteToString(data); + String m = TestUtil.byteToString(payload.getMetadata()); + + try { + Assert.assertEquals(s, "client_request"); + Assert.assertEquals(m, "client_metadata"); + } catch (Throwable t) { + System.out.println("contains $ => " + s.contains("$")); + throw new RuntimeException(t); + } + + Observable payloadObservable = Observable.range(1, count) + .map(i -> TestUtil.utf8EncodedPayload("server_response", "server_metadata")); + return RxReactiveStreams.toPublisher(payloadObservable); + }).build()); + + InetSocketAddress listenAddress = new InetSocketAddress("localhost", 39790); + InetSocketAddress clientAddress = new InetSocketAddress("localhost", 39790); + + AeronClientDuplexConnectionFactory cf = AeronClientDuplexConnectionFactory.getInstance(); + cf.addSocketAddressToHandleResponses(listenAddress); + Publisher udpConnection = cf.createUDPConnection(clientAddress); + + System.out.println("Creating new duplex connection"); + AeronClientDuplexConnection connection = RxReactiveStreams.toObservable(udpConnection).toBlocking().single(); + System.out.println("Created duplex connection"); + + ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection(connection, ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS)); + reactiveSocket.startAndWait(); + + CountDownLatch latch = new CountDownLatch(count); + Payload payload = TestUtil.utf8EncodedPayload("client_request", "client_metadata"); + RxReactiveStreams.toObservable(reactiveSocket.requestStream(payload)) + .subscribeOn(Schedulers.computation()) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println("I HAVE COMPLETED $$$$$$$$$$$$$$$$$$$$$$$$$$$$"); + } + + @Override + public void onError(Throwable e) { + System.out.println(Thread.currentThread() + " counted to => " + latch.getCount()); + e.printStackTrace(); + } + + @Override + public void onNext(Payload payload) { + ByteBuffer data = payload.getData(); + String s = TestUtil.byteToString(data); + String m = TestUtil.byteToString(payload.getMetadata()); + Assert.assertEquals(s, "server_response"); + Assert.assertEquals(m, "server_metadata"); + latch.countDown(); + } + }); + latch.await(); + } + + @Test(timeout = 75000) public void testReconnection() throws Exception { System.out.println("--------------------------------------------------------------------------------"); From 879cd6b20890bfc95b9ce018edf0258daba2a462 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Mon, 29 Feb 2016 13:46:15 -0800 Subject: [PATCH 069/105] switching check to prevent out of bounds exception --- .../main/java/io/reactivesocket/aeron/internal/AeronUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java index b1f24eb90..3c72112d6 100644 --- a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java +++ b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java @@ -142,7 +142,7 @@ public static MutableDirectBuffer getDirectBuffer(int length) { OneToOneConcurrentArrayQueue queue = unsafeBuffers.get(); MutableDirectBuffer buffer = queue.poll(); - if (buffer != null && buffer.capacity() < length) { + if (buffer != null && buffer.capacity() >= length) { return buffer; } else { byte[] bytes = new byte[length]; From 8a8d03451008444c910265b4042acf61b649b0ac Mon Sep 17 00:00:00 2001 From: Steve Gury Date: Tue, 8 Mar 2016 10:20:06 -0800 Subject: [PATCH 070/105] Upgrade to reactivesocket 0.0.2 --- build.gradle | 2 +- .../io/reactivesocket/aeron/example/fireandforget/Forget.java | 2 +- .../java/io/reactivesocket/aeron/example/requestreply/Pong.java | 2 +- .../io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index c60603903..b5d945065 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ subprojects { dependencies { compile 'io.reactivex:rxjava:1.0.13' compile 'io.reactivex:rxjava-reactive-streams:1.0.1' - compile 'io.reactivesocket:reactivesocket:0.0.1-SNAPSHOT' + compile 'io.reactivesocket:reactivesocket:0.0.2' compile 'uk.co.real-logic:Agrona:0.4.8' compile 'uk.co.real-logic:aeron-all:0.2.2' compile 'org.hdrhistogram:HdrHistogram:2.1.7' diff --git a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/fireandforget/Forget.java b/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/fireandforget/Forget.java index 9f6ec0782..e780d48c9 100644 --- a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/fireandforget/Forget.java +++ b/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/fireandforget/Forget.java @@ -61,7 +61,7 @@ public void subscribe(Subscriber s) { } @Override - public Publisher handleChannel(Publisher payloads) { + public Publisher handleChannel(Payload initial, Publisher payloads) { return null; } diff --git a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/requestreply/Pong.java b/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/requestreply/Pong.java index 6aab32339..eac0c5f9f 100644 --- a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/requestreply/Pong.java +++ b/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/requestreply/Pong.java @@ -97,7 +97,7 @@ public Publisher handleFireAndForget(Payload payload) { } @Override - public Publisher handleChannel(Publisher payloads) { + public Publisher handleChannel(Payload initial, Publisher payloads) { return null; } diff --git a/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java b/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java index b8452f842..f59059874 100644 --- a/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java +++ b/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java @@ -271,7 +271,7 @@ public Publisher handleRequestResponse(Payload payload) { } @Override - public Publisher handleChannel(Publisher payloads) { + public Publisher handleChannel(Payload initial, Publisher payloads) { return null; } From bdb955aee1bb20e12991f51803761c4416798ae7 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Thu, 10 Mar 2016 15:26:14 -0800 Subject: [PATCH 071/105] removing file --- .../driver/media/UdpChannelTransport.java | 344 ------------------ 1 file changed, 344 deletions(-) delete mode 100644 reactivesocket-aeron-tests/src/test/java/uk/co/real_logic/aeron/driver/media/UdpChannelTransport.java diff --git a/reactivesocket-aeron-tests/src/test/java/uk/co/real_logic/aeron/driver/media/UdpChannelTransport.java b/reactivesocket-aeron-tests/src/test/java/uk/co/real_logic/aeron/driver/media/UdpChannelTransport.java deleted file mode 100644 index a1f1b1b9b..000000000 --- a/reactivesocket-aeron-tests/src/test/java/uk/co/real_logic/aeron/driver/media/UdpChannelTransport.java +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Copyright 2014 - 2015 Real Logic Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package uk.co.real_logic.aeron.driver.media; - -import uk.co.real_logic.aeron.driver.Configuration; -import uk.co.real_logic.aeron.driver.LossGenerator; -import uk.co.real_logic.aeron.driver.event.EventCode; -import uk.co.real_logic.aeron.driver.event.EventLogger; -import uk.co.real_logic.aeron.protocol.HeaderFlyweight; -import uk.co.real_logic.agrona.LangUtil; -import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; - -import java.io.IOException; -import java.net.*; -import java.nio.ByteBuffer; -import java.nio.channels.ClosedChannelException; -import java.nio.channels.DatagramChannel; -import java.nio.channels.SelectionKey; - -import static uk.co.real_logic.aeron.logbuffer.FrameDescriptor.frameLength; -import static uk.co.real_logic.aeron.logbuffer.FrameDescriptor.frameVersion; - -public abstract class UdpChannelTransport implements AutoCloseable -{ - private final UdpChannel udpChannel; - private final LossGenerator lossGenerator; - private final EventLogger logger; - private final ByteBuffer receiveByteBuffer = ByteBuffer.allocateDirect(Configuration.RECEIVE_BYTE_BUFFER_LENGTH); - private final UnsafeBuffer receiveBuffer = new UnsafeBuffer(receiveByteBuffer); - private DatagramChannel sendDatagramChannel; - private DatagramChannel receiveDatagramChannel; - private SelectionKey selectionKey; - private UdpTransportPoller transportPoller; - private InetSocketAddress bindSocketAddress; - private InetSocketAddress endPointSocketAddress; - private InetSocketAddress connectAddress; - - public UdpChannelTransport( - final UdpChannel udpChannel, - final InetSocketAddress endPointSocketAddress, - final InetSocketAddress bindSocketAddress, - final InetSocketAddress connectAddress, - final LossGenerator lossGenerator, - final EventLogger logger) - { - this.udpChannel = udpChannel; - this.lossGenerator = lossGenerator; - this.logger = logger; - this.endPointSocketAddress = endPointSocketAddress; - this.bindSocketAddress = bindSocketAddress; - this.connectAddress = connectAddress; - } - - /** - * Create the underlying channel for reading and writing. - */ - public void openDatagramChannel() - { - try - { - sendDatagramChannel = DatagramChannel.open(udpChannel.protocolFamily()); - receiveDatagramChannel = sendDatagramChannel; - if (udpChannel.isMulticast()) - { - final NetworkInterface localInterface = udpChannel.localInterface(); - - if (null != connectAddress) - { - receiveDatagramChannel = DatagramChannel.open(udpChannel.protocolFamily()); - } - - receiveDatagramChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true); - receiveDatagramChannel.bind(new InetSocketAddress(endPointSocketAddress.getPort())); - receiveDatagramChannel.join(endPointSocketAddress.getAddress(), localInterface); - sendDatagramChannel.setOption(StandardSocketOptions.IP_MULTICAST_IF, localInterface); - - if (null != connectAddress) - { - sendDatagramChannel.connect(connectAddress); - } - } - else - { - sendDatagramChannel.bind(bindSocketAddress); - - if (null != connectAddress) - { - sendDatagramChannel.connect(connectAddress); - } - } - - if (0 != Configuration.SOCKET_SNDBUF_LENGTH) - { - sendDatagramChannel.setOption(StandardSocketOptions.SO_SNDBUF, Configuration.SOCKET_SNDBUF_LENGTH); - } - - if (0 != Configuration.SOCKET_RCVBUF_LENGTH) - { - receiveDatagramChannel.setOption(StandardSocketOptions.SO_RCVBUF, Configuration.SOCKET_RCVBUF_LENGTH); - } - - sendDatagramChannel.configureBlocking(false); - receiveDatagramChannel.configureBlocking(false); - } - catch (final IOException ex) - { - throw new RuntimeException(String.format( - "channel \"%s\" : %s", udpChannel.originalUriString(), ex.toString()), ex); - } - } - - /** - * Register this transport for reading from a {@link UdpTransportPoller}. - * - * @param transportPoller to register read with - */ - public void registerForRead(final UdpTransportPoller transportPoller) - { - this.transportPoller = transportPoller; - selectionKey = transportPoller.registerForRead(this); - } - - /** - * Return underlying {@link UdpChannel} - * - * @return underlying channel - */ - public UdpChannel udpChannel() - { - return udpChannel; - } - - /** - * The {@link DatagramChannel} for this transport channel. - * - * @return {@link DatagramChannel} for this transport channel. - */ - public DatagramChannel receiveDatagramChannel() - { - return receiveDatagramChannel; - } - - /** - * Send contents of {@link ByteBuffer} to connected address - * - * @param buffer to send - * @return number of bytes sent - */ - public int send(final ByteBuffer buffer) - { - logger.logFrameOut(buffer, connectAddress); - - int byteSent = 0; - try - { - byteSent = sendDatagramChannel.write(buffer); - } - catch (final PortUnreachableException | ClosedChannelException ex) - { - // ignore - } - catch (final IOException ex) - { - LangUtil.rethrowUnchecked(ex); - } - - return byteSent; - } - - /** - * Send contents of {@link java.nio.ByteBuffer} to remote address - * - * @param buffer to send - * @param remoteAddress to send to - * @return number of bytes sent - */ - public int sendTo(final ByteBuffer buffer, final InetSocketAddress remoteAddress) - { - logger.logFrameOut(buffer, remoteAddress); - - int bytesSent = 0; - try - { - bytesSent = sendDatagramChannel.send(buffer, remoteAddress); - } - catch (final IOException ex) - { - LangUtil.rethrowUnchecked(ex); - } - - return bytesSent; - } - - /** - * Close transport, canceling any pending read operations and closing channel - */ - public void close() - { - try - { - if (null != selectionKey) - { - selectionKey.cancel(); - } - - if (null != transportPoller) - { - transportPoller.cancelRead(this); - } - - sendDatagramChannel.close(); - - if (receiveDatagramChannel != sendDatagramChannel) - { - receiveDatagramChannel.close(); - } - } - catch (final Exception ex) - { - logger.logException(ex); - } - } - - /** - * Is transport representing a multicast media or unicast - * - * @return if transport is multicast media - */ - public boolean isMulticast() - { - return udpChannel.isMulticast(); - } - - /** - * Return socket option value - * - * @param name of the socket option - * @param type of option - * @return option value - */ - public T getOption(final SocketOption name) - { - T option = null; - try - { - option = sendDatagramChannel.getOption(name); - } - catch (final IOException ex) - { - LangUtil.rethrowUnchecked(ex); - } - - return option; - } - - /** - * Return the capacity of the {@link ByteBuffer} used for reception - * - * @return capacity of receiving byte buffer - */ - public int receiveBufferCapacity() - { - return receiveByteBuffer.capacity(); - } - - /** - * Attempt to receive waiting data. - * - * @return number of bytes received. - */ - public abstract int pollForData(); - - public final boolean isValidFrame(final UnsafeBuffer receiveBuffer, final int length) - { - boolean isFrameValid = true; - - if (frameVersion(receiveBuffer, 0) != HeaderFlyweight.CURRENT_VERSION) - { - logger.log(EventCode.INVALID_VERSION, receiveBuffer, 0, frameLength(receiveBuffer, 0)); - isFrameValid = false; - } - else if (length < HeaderFlyweight.HEADER_LENGTH) - { - logger.log(EventCode.MALFORMED_FRAME_LENGTH, receiveBuffer, 0, length); - isFrameValid = false; - } - - return isFrameValid; - } - - protected final UnsafeBuffer receiveBuffer() - { - return receiveBuffer; - } - - protected final ByteBuffer receiveByteBuffer() - { - return receiveByteBuffer; - } - - protected final EventLogger logger() - { - return logger; - } - - protected final LossGenerator lossGenerator() - { - return lossGenerator; - } - - protected final InetSocketAddress receive() - { - receiveByteBuffer.clear(); - - InetSocketAddress address = null; - try - { - address = (InetSocketAddress)receiveDatagramChannel.receive(receiveByteBuffer); - } - catch (final PortUnreachableException | ClosedChannelException ignored) - { - // do nothing - } - catch (final Exception ex) - { - LangUtil.rethrowUnchecked(ex); - } - - return address; - } -} \ No newline at end of file From 47cbe78c6de69806afc8c0f00a5fb7e6bb32ac2b Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Mon, 14 Mar 2016 13:39:42 -0700 Subject: [PATCH 072/105] updated timer wheeler, and remove padding, added the ability to execute an Action on the Aeron polling thread. updated the RXJava scheduler to use use thread-safe call to the timer wheel and the submitting an action --- .../server/ReactiveSocketAeronServer.java | 6 +- .../aeron/server/ServerAeronManager.java | 117 ++++++++++++------ .../server/TimerWheelFairLeaseGovernor.java | 68 +++++----- .../rx/ReactiveSocketAeronScheduler.java | 15 ++- 4 files changed, 126 insertions(+), 80 deletions(-) diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java index 6c1dc0453..cbbdb3f1f 100644 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java @@ -80,15 +80,15 @@ public static ReactiveSocketAeronServer create(ConnectionSetupHandler connection } public static ReactiveSocketAeronServer create(String host, int port, ConnectionSetupHandler connectionSetupHandler) { - return new ReactiveSocketAeronServer(host, port, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); + return new ReactiveSocketAeronServer(host, port, connectionSetupHandler, LeaseGovernor.UNLIMITED_LEASE_GOVERNOR); } public static ReactiveSocketAeronServer create(int port, ConnectionSetupHandler connectionSetupHandler) { - return create("localhost", port, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); + return create("localhost", port, connectionSetupHandler, LeaseGovernor.UNLIMITED_LEASE_GOVERNOR); } public static ReactiveSocketAeronServer create(ConnectionSetupHandler connectionSetupHandler) { - return create(39790, connectionSetupHandler, LeaseGovernor.NULL_LEASE_GOVERNOR); + return create(39790, connectionSetupHandler, LeaseGovernor.UNLIMITED_LEASE_GOVERNOR); } void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) { diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java index 9bae90f35..73ea796f0 100644 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java @@ -17,8 +17,15 @@ import io.reactivesocket.aeron.internal.Constants; import io.reactivesocket.aeron.internal.Loggable; -import uk.co.real_logic.aeron.*; +import rx.functions.Action0; +import uk.co.real_logic.aeron.Aeron; +import uk.co.real_logic.aeron.AvailableImageHandler; +import uk.co.real_logic.aeron.FragmentAssembler; +import uk.co.real_logic.aeron.Image; +import uk.co.real_logic.aeron.Subscription; +import uk.co.real_logic.aeron.UnavailableImageHandler; import uk.co.real_logic.agrona.TimerWheel; +import uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; @@ -34,13 +41,17 @@ public class ServerAeronManager implements Loggable { private final Aeron aeron; - private CopyOnWriteArrayList availableImageHandlers = new CopyOnWriteArrayList<>(); + private final CopyOnWriteArrayList availableImageHandlers = new CopyOnWriteArrayList<>(); - private CopyOnWriteArrayList unavailableImageHandlers = new CopyOnWriteArrayList<>(); + private final CopyOnWriteArrayList unavailableImageHandlers = new CopyOnWriteArrayList<>(); - private CopyOnWriteArrayList fragmentAssemblerHolders = new CopyOnWriteArrayList<>(); + private final CopyOnWriteArrayList fragmentAssemblerHolders = new CopyOnWriteArrayList<>(); - private TimerWheel timerWheel; + private final ManyToOneConcurrentArrayQueue actions = new ManyToOneConcurrentArrayQueue<>(1024); + + private final TimerWheel timerWheel; + + private final Thread dutyThread; private ServerAeronManager() { final Aeron.Context ctx = new Aeron.Context(); @@ -52,7 +63,40 @@ private ServerAeronManager() { this.timerWheel = new TimerWheel(Constants.SERVER_TIMER_WHEEL_TICK_DURATION_MS, TimeUnit.MILLISECONDS, Constants.SERVER_TIMER_WHEEL_BUCKETS); - poll(); + dutyThread = new Thread(() -> { + for (; ; ) { + try { + int poll = 0; + for (FragmentAssemblerHolder sh : fragmentAssemblerHolders) { + try { + if (sh.subscription.isClosed()) { + continue; + } + + poll += sh.subscription.poll(sh.fragmentAssembler, Integer.MAX_VALUE); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + poll += actions.drain(Action0::call); + + if (timerWheel.computeDelayInMs() < 0) { + poll += timerWheel.expireTimers(); + } + + SERVER_IDLE_STRATEGY.idle(poll); + + } catch (Throwable t) { + t.printStackTrace(); + } + + } + + }); + dutyThread.setName("reactive-socket-aeron-server"); + dutyThread.setDaemon(true); + dutyThread.start(); } public static ServerAeronManager getInstance() { @@ -95,39 +139,40 @@ public TimerWheel getTimerWheel() { return timerWheel; } - void poll() { - Thread dutyThread = new Thread(() -> { - for (; ; ) { - try { - int poll = 0; - for (FragmentAssemblerHolder sh : fragmentAssemblerHolders) { - try { - if (sh.subscription.isClosed()) { - continue; - } - - poll += sh.subscription.poll(sh.fragmentAssembler, Integer.MAX_VALUE); - } catch (Throwable t) { - t.printStackTrace(); - } - } - - if (timerWheel.computeDelayInMs() < 0) { - poll += timerWheel.expireTimers(); - } - - SERVER_IDLE_STRATEGY.idle(poll); + /** + * Submits an Action0 to be run but the duty thread. + * @param action the action to be executed + * @return true if it was successfully submitted + */ + public boolean submitAction(Action0 action) { + boolean submitted = true; + Thread currentThread = Thread.currentThread(); + if (currentThread.equals(dutyThread)) { + action.call(); + } else { + submitted = actions.offer(action); + } - } catch (Throwable t) { - t.printStackTrace(); - } + return submitted; + } - } + /** + * Schedules timeout on the TimerWheel in a thread-safe many + * @param delayTime + * @param unit + * @param action + * @return true if it was successfully scheduled, otherwise false. + */ + public boolean threadSafeTimeout(long delayTime, TimeUnit unit, Action0 action) { + boolean scheduled = true; + Thread currentThread = Thread.currentThread(); + if (currentThread.equals(dutyThread)) { + timerWheel.newTimeout(delayTime, unit, action::call); + } else { + scheduled = actions.offer(() -> timerWheel.newTimeout(delayTime, unit, action::call)); + } - }); - dutyThread.setName("reactive-socket-aeron-server"); - dutyThread.setDaemon(true); - dutyThread.start(); + return scheduled; } private class FragmentAssemblerHolder { diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/TimerWheelFairLeaseGovernor.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/TimerWheelFairLeaseGovernor.java index 2b35abd08..b10d4a273 100644 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/TimerWheelFairLeaseGovernor.java +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/TimerWheelFairLeaseGovernor.java @@ -23,14 +23,11 @@ public class TimerWheelFairLeaseGovernor implements LeaseGovernor, Runnable { private final List responders; private final Int2IntHashMap leaseCount; - private volatile boolean running = false; - private long p0, p1, p2, p3, p4, p5, p6; + private boolean running = false; - private volatile int ticketsPerResponder = 0; - private long p10, p11, p12, p13, p14, p15, p16; + private int ticketsPerResponder = 0; - private volatile int extra = 0; - private long p20, p21, p22, p23, p24, p25, p26; + private int extra = 0; public TimerWheelFairLeaseGovernor(int tickets, long period, TimeUnit unit) { this.responders = new ArrayList<>(); @@ -40,57 +37,57 @@ public TimerWheelFairLeaseGovernor(int tickets, long period, TimeUnit unit) { this.unit = unit; this.ttlMs = (int) unit.toMillis(period); this.timer = ServerAeronManager - .getInstance() - .getTimerWheel() - .newBlankTimer(); + .getInstance() + .getTimerWheel() + .newBlankTimer(); } @Override public void run() { - if (!running) { + if (running) { try { - synchronized (responders) { - final int numResponders = responders.size(); - if (numResponders > 0) { - final int extraWinner = ((int) System.nanoTime()) % numResponders; - - for (int i = 0; i < numResponders; i++) { - int amountToSend = ticketsPerResponder; - if (i == extraWinner) { - amountToSend += extra; - } - Responder responder = responders.get(i); - leaseCount.put(responder.hashCode(), amountToSend); - responder.sendLease(ttlMs, amountToSend); + final int numResponders = responders.size(); + if (numResponders > 0) { + int extraTicketsLeft = extra; + + for (int i = 0; i < numResponders; i++) { + int amountToSend = ticketsPerResponder; + if (extraTicketsLeft > 0) { + amountToSend++; + extraTicketsLeft--; } + Responder responder = responders.get(i); + leaseCount.put(responder.hashCode(), amountToSend); + responder.sendLease(ttlMs, amountToSend); } + } } finally { ServerAeronManager - .getInstance() - .getTimerWheel() - .rescheduleTimeout(period, unit, timer, this::run); + .getInstance() + .getTimerWheel() + .rescheduleTimeout(period, unit, timer, this::run); } } } @Override public void register(Responder responder) { - synchronized (responders) { + ServerAeronManager.getInstance().submitAction(() -> { responders.add(responder); calculateTicketsToSendPerResponder(); if (!running) { - running = false; + running = true; run(); } - } + }); } @Override public void unregister(Responder responder) { - synchronized (responders) { + ServerAeronManager.getInstance().submitAction(() -> { responders.remove(responder); calculateTicketsToSendPerResponder(); @@ -98,7 +95,7 @@ public void unregister(Responder responder) { if (running && responders.isEmpty()) { running = false; } - } + }); } void calculateTicketsToSendPerResponder() { @@ -111,13 +108,10 @@ void calculateTicketsToSendPerResponder() { @Override public boolean accept(Responder responder, Frame frame) { - int count; - synchronized (responders) { - count = leaseCount.get(responder.hashCode()) - 1; + int count = leaseCount.get(responder.hashCode()) - 1; - if (count >= 0) { - leaseCount.put(responder.hashCode(), count); - } + if (count >= 0) { + leaseCount.put(responder.hashCode(), count); } return count > 0; diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronScheduler.java b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronScheduler.java index 14711a61e..5b4f9eea0 100644 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronScheduler.java +++ b/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronScheduler.java @@ -47,14 +47,21 @@ static class Worker extends Scheduler.Worker { @Override public Subscription schedule(Action0 action) { - return schedule(action, 0, TimeUnit.MILLISECONDS); + boolean submitted; + do { + submitted = ServerAeronManager.getInstance().submitAction(action); + } while (!submitted); + + return this; } @Override public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) { - TimerWheel timerWheel = - ServerAeronManager.getInstance().getTimerWheel(); - timerWheel.newTimeout(delayTime, unit, action::call); + boolean scheduled; + do { + scheduled = ServerAeronManager.getInstance().threadSafeTimeout(delayTime, unit, action); + } while (!scheduled); + return this; } From 568edf0a461f5d929c438a09ef999b18a107ca08 Mon Sep 17 00:00:00 2001 From: Ryland Degnan Date: Fri, 18 Mar 2016 15:30:40 -0700 Subject: [PATCH 073/105] Upgrade gradle-reactivesocket-plugin to 1.0.3 --- build.gradle | 9 +- gradle/wrapper/gradle-wrapper.jar | Bin 51018 -> 53638 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 10 +- gradlew.bat | 180 +++++++++++------------ 5 files changed, 98 insertions(+), 105 deletions(-) diff --git a/build.gradle b/build.gradle index b5d945065..43dd32830 100644 --- a/build.gradle +++ b/build.gradle @@ -1,9 +1,6 @@ buildscript { - repositories { - jcenter() - } - - dependencies { classpath 'io.reactivesocket:gradle-nebula-plugin-reactivesocket:1.0.1' } + repositories { jcenter() } + dependencies { classpath 'io.reactivesocket:gradle-nebula-plugin-reactivesocket:1.0.3' } } apply plugin: 'reactivesocket-project' @@ -17,7 +14,7 @@ subprojects { } dependencies { - compile 'io.reactivex:rxjava:1.0.13' + compile 'io.reactivex:rxjava:1.1.1' compile 'io.reactivex:rxjava-reactive-streams:1.0.1' compile 'io.reactivesocket:reactivesocket:0.0.2' compile 'uk.co.real-logic:Agrona:0.4.8' diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c97a8bdb9088d370da7e88784a7a093b971aa23a..5ccda13e9cb94678ba179b32452cf3d60dc36353 100644 GIT binary patch delta 25651 zcmZ6yQ;;SNv@F`TZQHhO+qUhmr)}G|ZQHhc+IIIe=FI-jjkvMT(^?Ugj~T17GApOz zK*Neb5tL*>!C-)ZprC+oq@<)05y;{HXIl2WI|=~-0jVbnsiEUMx;(f51O5L<#Q#B7 z5rO?T=P3X0{9ycVCer~QApg&U(Y??$G!P&lComu&vE)KJjO2kHsAQ%N>SQwzd_Zg@ zG_!n_yquuDF{ovorfP|%HQ&<=A8+^D@!9$v(3F=A zNo)`_EI6nw^$tg4Tr3)gs@B>qKB{hqEeXQ-K-({0xJgWdgG&9d4plxgsg%$Olh@H>R0RS|)iZ)E<8p3B%a)np(4P+H>5|XEh(mRD5t?}fihxrP8f$6{ z7OfH-S2Je1w$#fMo<9?JB*gH14N5_>p7)R)2R3HoLYqUPd`tKQxL!jIpMYWWiEj;V z;F`G{AW;vYXYU1f(?Nx;F>Vz@rwh2#@<R}yvpO8Ql%HswQyS?>+>So`gQ_(N^Ft`% z0HS$LuWmuj_6R3yGwW0S_l@4?=lr}O(CIx@xNOFx5l1v$4BV}=;P@;b=Ub=uz^n*P zv^AgE!mGXD{j~;XgMPSigR@lt3;Z|$CZ=}wr5rAF?@!c)E?j9vxhao*u{3)*{aY_< zFlboep4!tuj-i&FM?BV&;q|c}p5sQ`OPlXXQ_q`4 zHR|5YZDy|ha`T5a8S3D#8`c$^x^<4Nyw3f`nE5J`|P!;vK(j zm^Fr;9;Mnz$N5he)kdGj@aD8q<;7*1@?(a&%IoT^@T$MBRT7fjNtR-p$d&Duhok}f zDsgIr3WAbzcl=_hRvi@)qRTfvzH6}AcL8dx2iiY4@aS-h28Ip{_3W43y0d>yZ>wWD z-Qj~LSmAO4h<|7w*wGT!m5#~)GqGX7@Zx)1FZqV4bv%Yg5?2dbuuQ-|;Xo=850Pl2 z?>B{vP=;uIWPaSwphh|Q*G#z{RPz(@SAql2D%W7ny(wJ6EHSFaIX@UvjLG;CNp|4S zQ7$ReMkz(iF-jWI5Xu%Ye8cE^=;>I$z3D3flBZXj4Jk-D(aDxa&U&bTBl63*2M7N@ zV41EddKtMyv$IY^PAlXT_foL?Q%?AtXhhD~W-8Du!pG?Uwh{@PUPP$BOkYR3G6^Mc zEX81*wGh%%M6M;7cc3arM9DGM@V4cMflT$?T(oZFbQp$mH)WRw(6RzY^>hMWP z!fDdYOOPdGLN+o9EpTKinnC9_|CT0&taX2(DG)QBm7Q?0X@9MI?S?j2G@8geUo8e z?>m6qq(6LAUp_40xmW3*tiaLTR+cqhWeUa171LQh7E-v8X672%xkAoAX}p_4KvZg6J<@=ya-V_M%8p5dFDwOq_r>KLU^&j;kOSno6qCH z5o8(JQ2DV#5R=2f#w$w{(Gg*d_3dCh6vjbRmMV^r6yd`-fm(`{nq@Qm+`K zeU3KLrY@?H@%>FeUQeI|)>lRuydQN~V{ZvYPX`R38NMR*lViHIC#6+3TAga# zcR~j60>7&R&3zoCAjb8l_tO>9hV&^Af5M*c;N=dZ%iI=E1M3tsm_E;ap2rBKds=Gg zhH63^kV@?aPX}z^GSHEQBb=+%{_7pOLD z{M%j&TU%RKHEbQPZhCv)UiY>?Y2E-99-P@`NJotQ-xjC7IcI-!?)(f5zCV{GBdv%5 zNCg6O>0^|!B!F1MtRN6JVC%8Ta-0Gdg8F+f#E0y8aH;`RbGszAWKg{c)sjq{1Cl(9 z0^Sa-tP2=OP_|UdiLXqU`NyYgpm}5Y!(~u6oO$?rY(7OFnG$Z*!w=xB;bTy}DRbbe zW0#Mbgff+^Drdb}b{NL{Cy`Lh$(T{#ta;fIKxKaVB*3*Z!Z$4@7r(%y?&S%_yN>R_ z)k{5a{^SY7t6J5|Juod8bL_IzI-yLNssV>z#iHB?2;O|7p$ZDJ4xua*-{%&?b!{m&0#e99&&-k;r3Aa`-jR$ zhg=p-DBuOC)wz495Xk<_Looc??!8m=_YK_v1BZC^^mmVH9x^gTl@py1egj$7JJ;0MGe5~yy z2d86KOJSw^@>tnwHYaNqL*cPqEXf&o}$~D8^yEJNX3Qp`R=YnQQ0QJhzx*h4>fw^g=yKYJyIV2 z!ufEiR#)GBvIN;a9_pmi0hZ(U++fji%$^ciDw`*^?M#F+CJgrEZyx@#?WWZZWh=fz z5J19HEt_dsYt%*3QvBl8F*|qXhPiG=&&xx@AY0>R3^skGRd?b>iK!AF65>T7z0Xj} zom=_(B}=j#t3G$MBMGt;Arv{>E(^{~$*5afHH2<1CEk{?M;S_&`Isr~6kd7u15RUP z2T2O=oSMf}Nr5ufHL8WLOdlsDi5x24BY=6hYO|*iS~be0yv5@pI*q|rUObaRQtTxx zM>w=z)bnY=fufZf1N1Hep?HW%R>9`kyEsA!NgT|Y2~`_AT&`fN$&R)A1h)*_F2+GS zP3@ia9tA?Q5~?Pb^zY8nq1x0=(B!iDGfQKnAu8Usaq@gLiHHUFaW82N>!w!CwgP+g^|NOnqW(H7s5&1o{i zlweszZN<)O+LbDURjkjHR2D5=MhRcth*{7y5``NhwjxIXJ`quqxY7nS4KTwXJ@;%d z4&lV{*^o|@ro~g#)+XAjSsy`F-fg;u-h)!XId^L(_Nsq3b#4M5@`2`gC+i7sVQmM*)#0ew80CcF$Xfd*a3YuaVM9~Pk! zMDJb-IdauArCvw=keHEqHupE)#+J2BNX9W2OG3j-oCQYV!X~+L{S(?cyM)VOW~vlY zN3Gq=74Bk6htA$4t=Gwt{2nFgswnDaLbbP>o*bM}C9Ch6+^vSN1~8(JkEKu)qr+UV zkDk+QFalO>zCuXbydBGldi24RYF5e;7p=j7p?#ZW8J=?goL=L?7Ksj7m}*3ws{O^% zf{~xIChaO4wuVxu&W@q89frY}I*OS$bkumd+Osz*s+R_xj&ADV>Cj>(MNin=*3_}8 zaE5TS`rKoaS+?}_3<#3xFZ5LBk^83k&Iu@7BH}Mza(~87tt+%u_X#0TzmVu7h#6ON znkt&FYRPs`y72i^Gt{l#_sdc3gJQ4v{_OX$MXKYWuKEw(e$>s!h|DZ#jr6&{@CoB% zdVzRd64d!xtGjxID%wxel)KD3lD=JI;WVA> z%=WM&amB=0R#e#L!a{D@)ITF_AOJTVPotK-jMH#7$`)Vhch+LGKhIJ*{mk4|wL~WK z2*CgI*T(5~{HZvqHe3JQB@d*j4en1J9 zJ}y6U0HE?0vx9yMbz{py77m0~=?$C|nU#owdYy6=@`&`x+c#s{IHErbKQjhvKO&m{)s@ zsRbk&qEs5Bou(RNOkw!Y^iTOJsYVErr5Q}i0dNf(D!)K8IX;hVIm#aYc+sG=iSm1+ zfH&$oMqv4p68`2^pSm$m>@aZ zZgmZL5@TuqcJdB)(v(tJDlh+-1BlWDqr!8Z!czdKSZ_}723?R+oZvN=r*#q3v?g1R zxm{@8iU};niX1{U-VBRs^cI@11$u8z144K6J07teT2(dXddZ$HE{^4LuQo>{I9`X8 zV6X)(R#!y1A{UI;@#aopz%Bh|^daVEl=jycF@kKY|e;%le#jz#OeJ^^)WW@-nY|hn+S2ZWjEdm93L$_x!jT-E{Ol0C-T0 zjm>yWmMLrTdHKC2m3)nL4$I#fe5B(chv)(+EXlEp4zQA9hT*wsH?A{Om)nxvtj-y4MgDZXn(-wy!9|uIW}6YxfW9vV z)04cx7=e~x^UNtYUxaaYozRkK0%RrXt$jBhxcaVFG>J4>L99Mci|tl&r@2@+6R0eHl>Ckb$uN@tyMgAF^HN9jQ#sp$K-=<^w~NyJdGumZ6%SUl@o z%qQYna9E&cEOuTzihJ_30~!a3Ipex&Dkob98=@;v56!D^=_>}uav|Asj*a!;))P10UFca0p?QBdOpDplOgGLw zzi0NESCfS(ui&YGEPYt|vljRSjcTtnz(@3#@c!bb)u0&0VMR^i&b4}r-RlCS_v=vD! zFwXYD&<$HSL)k|QXjoUs90~p@)ZzzPS*+QD`HC=w`x3g<=Yl7_xE-p8(BO2#9|Nq) zK!$?SXZWsoz*mvmS`FvIHxmj8L^VMHB=M-&miT@ND3dKQ`2}&!eUL;*pE%-hP4&Rb zw%5~PXB5Jc=RuhssGvxE8SoRF1egu|NrP>G?5e68#W3eGq;-IzOS>+B{hXbwI)xKO0K5Tt~i_3(DggHWs z54I@(pOIIdLeC-m#n!CX-4eV|6(~6m(m0n#k-J5^UUxOV$6o3|pRFR0-MKm*nnJQu2!3B(A#b=My3Mx#E^CD|Cj z;}kF>u?zS9%-wSm?W7HUwG{9ioaM#Hc*S3SCjIQU&T;d0@0(}fv_#zx$^d@;7b$|r zlF%CwUd35g{ICj`#}mIDZpque&$6;^R<#4CjS-)*~r&l7JenrNi%XQ;V???lPaelUF z17Ni8gR}z83wE#0G>;|3^YHs7v()tq$#Y)2{_l;j*KqB508~ZPS@{C)vnuWuK>XZ$ zILsqY&!Uub&i)-`BVWVo0=etjdFivVx3h?8Me(o)l4{>~!IcTuCzK<;cwnA63!+6H z(nc)U8X=U;F_mIL*F)=MGSoY3J~%<8p|fWwF5HGM#ywBHnWhms$G~B@+GUO8z&5a_ z>6el2Bi_zUqSoL_sHh0n!(S3Oz%zU_Fum-F}XdMRgZM#JQBbGAD*4-ZXZg{c3Oso=IJ~Nd*@>@a2or0^R@m;Xs>U^|M zRriY%YeHKuTLDC%`^(Aj2CS2@I(2>##_cEjXFN9U?EvfIi}BlVYX;W{Uz$B21N)`n zEP-`yloE;xXym-ee1n@_aFED z{OyV;2xHWX@_0-iu!B6H8%#Br8?b{ehK&G#C_He`?i`PA2Y0F`bVNMD_p(Gn?_i9O z!FxW|7gjqq0_WbRANDolA{+KSdHyu>>TmEP3PhZG6K#4l&h#e>#QxZ09F_!q&+sEm zxEW@dhG3%Ihcx%13JgEtr5qmr_96?E9E}AaiBpWqji0_&W%+ai%10)Qdw&3g z+~XzYeww{%3kqP4Y;s`@BtL%p0U1IaL<9R#2Qot5rvmlSOe=mov=ViN++00=hrubT zA7#I%Nqw{t?n4)0?xU0M!ybLU|H^nBCJG24gnU5#VhhZtuGn9~#mBzA!NI@4$J^lN z*=hH+@^Y%M^fHqZuDD~duU|`CQ9%XZD^JUnP;gLdE!meWrKL4_SWU*%8JXt=*3DVP zON#uQDxFPm%%4W}%$~Q+?c&by*?PLQ7!E$__lkBGlLim-hobQFf*%Dfmoe97YltY) zYMo}}HKI($74GoN*6SQ>Wl1gl(1f!ZyFt+-+%C_c`Qy)MEo0MCPDvEH`se@@M_Zk< z8~H3sUdbrXYOYvkBIBS{qbXnF{L3ULnoY}}ZrZ$A$}GrZ&=6OzndNZYG6|a7$mHU} zyoClwXFoL1G`;!w`9EK@!iFk_*6l|wwA>9zN)gS<#@t=S;$-<^GtzbnGocH2#edDV zh7lLK*9{bcpw2&+WfadnQV0eNRw$mQVIl?%ruNXOEi8SBsI1j*C!luAW;614k)tKA z(9)A+(siwyV`1DZmwmE7NU?&GJ1>-HqKcK2MIhiLI*HL(apj4Cw;qfCCM=eS&Kinw z+GxX8eeO)U?@To6S?2%{+V6CkyjD5nb2Al0rnpAs=+=~U^hr*WoqgVPMklpYS5yR6 z8YUXAM>fNFQd4bTMbwyLW0$SwOO;QyAJ{S3tjDPSZ3TZ>XLv-f`T3hFgV{q}SI`-_ zPrR+yEZ~*3<-Z*$Z%TD za#Q7UNRY$U(AuA0vbLP1o4~w!zfqr|~#8rx|OI9WC{0j7mQ! zr9n85L8r!|`3V$M(cV$5*Voa}e4ykjK63An9?cimCm2t#kgs}=cKTB1xa!GXl#Wfe zVq+~ma*T_~R_6oI+FhnbUQiVm^5Xn3bpM^$b^`mW%JD;v%P#K2mUzv3E#mI80rJ@Dd)p7#ES5c$iHul&e+4sF1wJ?gCWwgl_z&xfD0Zc9{50|VpCS)CjS zcKXt*<;-s;T5WZ-(vZwuWuOezUoyD-j|_JUvn@A^EO#~lPZd-2Yv~9hL*7E=R24o} zM2P@L&HH!f<)}N*=vZ?6xHE>>hP)yg7xqpG&epJOm{@u}S9*IG+e)vWxu910o5MtD z{@U<2@}$Z84g!uBYEwq^GI-Ke0s@&!IA5-05BX$=Dcf3qc-3d?(KF2{uwEDRc9@^( z-j~Zx6D$S5Xgv*Z=}&4mT`CI0-i04UuVF8 za(3RDsfj}l1lnwl_qA3qrB5=EPoUE6TZys!Ed<_kO`gxS=ZatSiMZ+(ooT23wRQGR zs^%jCEqBgzuh;y`o{B;|)n4CK)Gn5qVJ<^YMLGxIOi(_gy6k`_{m5L@j-jfGLO-Tc zDJ11%v1jfw3S>`@_fX?chOThlR%(XF3iz_Y(57=pH!lJ*_YXS5Q#_ETr$ z2Y4x9MA>m@%VgdZyxhCgL(#?}1+y(L>cZRNw`vo;NU7d_N!j_89Hwvla|VjK9q^ zLoG!OdM%mYVS!dE-sc=*iI?wrvc%KpTvZy-KOtH#F6H1cC+gCCKfOT#_Wnqw7Wb&H$MQQa`TO31V_ zRyQJ{8k@G1+_sdww#FtNG8omlqpG>164}nN&24Mi zwoU6R9V7}mmo#>MexO7zlVG$Hnj+ADO ziYWeMIGRRF5J&1WKWvwjiZO46R~%?9TS8?!Uyv7%Q@+3~x~8_)VO2Tr-s6thc7(RhG^l7wjEbcsVi5mc6R`V z<;>9xwvM}oF#^2uiAULaM6HUuSO-mS6brZbJj>?EZ+Mw~yg3VyTvX^>S$J(bIQt_c zXnT*!C%elpzDtnwt^`qxTr|LQY|A^eJ;9Am;7?f~ASMtn)WuB0|a?NZ8y zp5j76u^=vuteMkA!(_T=Gu0cmlBHX@L2DD8!|r+YGp?X#j}x%$17=>!T74@f9`ey^ zFQL8Ap4BAO9h9OauT9*ONw6~%{lGynbFKjvW7Sed$(T4uW_4O0<=*ynAq|VO4y;|0DRnRdEPTNp;gdEi3}| zPYl!iCx#XIumO->XyfQVkP2JNHtJl^CBrG8uqz33fw7GPNZKew2}lJNzwCQ*FfQ&d zGSvq_480wYoWzfM<^ZiU2oDNo%59CFAOvD z3!vP#qAb2oVEjtrV9jfJ?qDw%R|ylMitp(yu5WFwF6JLD9}?f(-``wJ|HY6EK1)io zgA*%RYYzf@bqNXPXMf66n(8ioJhr#8flRtsP8f-;BF$Pf4)HGxxz#V2$BCr?U-QU} z1SH-|i4ewNA0|Wl`|U`Dcy4VATdvni9805_u%VKiB zmeyF&@qwm$@bvttyk>BX@xU0HNkp!^7B^5nZ$gyTJQd+lz)nmtN9iAf1vqh}x%rM) zMljnegbMKGT!H>db#E?a_N4@^<}tyG)%;zsem3TkSS{Zc^Vr45CTuW`Z?w}e1o(#J z@l_nB|5lSQK6*Em)BVVm#>Q0dT0&B1&SJ&!X-ChwqR%D&II}OnSk&nva??8NbUU5Y z1G`NJZqos=Zi6*QkK-^Vj}v!D0njLh)hC1RA6a%&uj9pL|4HR7k*7A{h-IN>`-17$ zxW|R>AH9ok)hjsgqOn=9#QhsjTXFdI9vmikqzLmvFIXnMpfqJkhAVo z*_Km9X5C820`h1PBJxx*75A#_$|2iY5_QHMsO%u4n`9#Bqe%*;of(R;nqrX@GUAye zQ|ygWU}(yPkur^1$g*t84Is$AlYV3|7V0amR=! zoM$=C*sQKUA8dOf%YfB4m-z=H1PuOVcel8vm17o0g4N{O$i$TlU z6=;JRc5ideeXPw#nCnW@zBR8naBVY>MN?WF=9=p|E&kocM@aC|TvM>REXm@DqoJve zQUOcG@QRdGnredr2F&!ay1siVVRLUxNLSu&)mjf<1*Eo%TEIv=;!|SDs*$T>-mt1y`X4ygwArtB$8o8KQYIYytvCKuuCNa;GNlNnEN zqCK58>0vW%-2F7=t7_hJ3`SBxKeLjPm6Mmg-FEu93N9HMfrSn+f zt?jJ_*AE4@3x~3?Av@af4h<%ZLTTd& z_Ro?ML~vh_C4SALOEs zM1*O@T2kC^#}FeCt%T#uVO+fd{JjoIvc%kavQ{84=B>_D_@Vhr91n|WB<-v#S6~V( zB;|J8AiH0u)pPJ}Aw3xP?P+~EA~#B+1po(-m*j%rYCR=W468BXQZ+a$NpPO?B>XmI zSv-x7M45pf9t+&kDaWx>}iKA1vXbsHzDGjbz_b*!* zHpn4sJ(gcDo!4E>8|vm84==Y(-RX77K6iBkVQd^_&ek~TrH$6}{!|xPL#Ksf&;UK> zI2Iic-K7m5#42(or_wuQJ6xTO1>VRTRIfE|&CE4uJ@>UVb%lMsTbrgJmOz6c2&fxT z@;{k8!E!~_-T*|49?i*w-KevKz8vW%J-&FSen0x|E`eDjjC;?(E{LrqtNrDBFc~|4 z9z1dnCU;K{c9mrP*k5yO9-j5tV}K1(=Czv>WzFSH4Dax+pgvdM>*n5`bN;glfAY44 z!KF$2SH_0i$^F^8-Pd~4d5Ax#Uo=IhN>Geh{?M(Kbtj2Fl z+NH7ZDGO4vj^#H^GOb78Uc`sA(EiBDT1d?q1YV8!#bmcmH+z=l4Bs0_RQfWe`@0ox zNvYZ2)_4g5`C@l+zjLo!sE9*L<^E>EX&D>hPVNrnGQ7<)ye&~bCt5?3%M}Q6#yiX$ zsa(1s_t>CZmHxl}@8%q`R{;bNPzyqGfdn-`)7BGr6@7OdM>~p78V?v)IUJ0Mh;~B; z4w_6s1eG^Xn3-7NXiQ#rY>6nIHBVWRl(G=9pl6750i-m;lMu;D7FVZ+s{}sYqqc4} z;94>O98h3R7;84&bVTu@Bk;DzdY0FG$NeerJpb3vg!RbI>y^+g`iL(2?+ zLZW3};$9DolGMwf(0{6d*PhdMqgVrQs4&feu3<(ILrrSmX^s6x-4ZE3NAm|Xt=vi5 z{f>4D$u*KVkNO=v|KK z#xH!Bc*=AZy^Iix;w>FUALVFePkgn7Kf8F%eJrNjr%Ki?GtaNMrYhlH5>qY<_jK5r zsaZ8Dz%YLbj+uh&(V{8X*XbHF=3@2QB`oRGe4DBp`I?lgJ?SS+6& zJ`0?kFzcvK(!;zaKEFO(#@So}qrh(!rri<>!m%iscNi?EDb~r1VrvFx6vII<81wG_ zp{uZ(UOh^>CtYFG+ZZBy#8e)H(^=q?w#-48P;G5>G68qE*-XtS)|(Bfo^buY7q8|< zA>5t+g#?pNa^s3`tg0X-+8f60zs+Zj9(>sQ`Ax=9ZatdXbj^(4GJZM$)2;{QJON>t z?skJ3__z(qlJya~PStVp8#TuqJ9Wi~jEkE7RQQah?L(=tA)D$=*Ets#@7xB953oH` zJ}(`TZgSHdi1im<_>SjZsw28ZzPNW(A-*tSXIJl~6@XAfR-IZCw=M;~Z;BvK_nvT7 zp1pBMOCiE(KR-tz6xO|odVMRvs*EIZ&4LWEJZKUg& z)?%w{U+4P$cqSHUHhVOmCFgy~tllH^mf1f;&1M!lbLN8xmt5-UdOe7(SB1qvz;JRb z=vuw~OJ!45Zvh*iYT7M#i9UPj>_p&FFSO@zkpO9n+jIxQYxENldwSYsn+&hH7!(6j zM(%pW8jjS_NG^QB0h2E z7V+1AV{CJKb2}PCY-2A&M-Vi%%Nv5e@)kQp1==1$hXy%Mm@CyPSJ?Ivj)ZWr37nTo zmzyxg53&?saIqiZSVZ{9btErB>*M%d4dxpfH@&}9FoQ^kqqmb1KR>B8+`k0n# zO3OPe9^FRNye4Zb>&1MeyfF)geClA>yt(7cjfwRKhh&S|s_+JXIYVpdFS_ebc-OQR zKZ$^)v-v7h0J@-RX=-ADI<_X{em`;7ZSy%)A+Hml&7vgC0%INSVOb}%R!4*v)}U&6 zgQ3?JT|m^$wu5-gJth&N_hCPB;#V?93S&t18CKifIoF+}9p-!*Lw^TO$5T;_SH9Z6zjEI>Jq~G~tGOW5Aome%ozuJUgKa?GDvl>Ak~fMC1`~X8 z`B2NQkLwFiuXm-(!nj=dZ%r6u7$-BknW5x6R8EvtAKje7vbh+#-Y zXX+jChU`8FSXh4D`2lar#mcNZeaQiDkb1v)43yFH!c@(7{CuEE!mEK!ca#V!HV8+g zO207>)cazOw&j4{T@!h6Qv2)wpXxyKy&MF{e^r(wQyE-rC?FtNL?9sI|F7kT00`BL z1fW@B2Us+Bv+}}mW1whJC6vM2-IBm0wXBdOcxTJiTG+Fo3=!nOvCLhZ%1j_&X6b-g~)dz1s;Ze*7@@kHy?L?jAjb+XkbSiW`W&?eRk=R;m>iTORhT@rdwlSH^?QEsea(AN0J!4hdtub_ zdtywvIUniqI0}()bY_`xyoLKXGafYl18STt-F16yj&V2k@ZtG;dnW3Te~k>dzA#a6n_ zAx3L5Mp97mgsbrvpxV1~ zh$H&Ere&SALwi}So zn>CmTsVmK_UAf3lNeg##mKo#nFL-yY7_&8{CKxkF-x)=Gh94U3s34&qRRjv7V zhITCh1=pmXhG*=>2^D!wtjB6`qDDaOrxQgD0`*hSU&p~ zxZ}io^*?t;9v(1$h4VvVoc=9^Ubo2E87X$+)r^fKyH9*hRqG##lc0q?_7RWfcWy z!m|{nd$b4*ty}C)W7=!GqTIfxL2?hHuKU4L|6WrOcX-Qw`s|m6>29h%SiO0+xlr{n z?n^$jYJW>Tm!0nDT{;1dc`IvfH%?m{o(7*HQxXzr+t{W?tzn}l`@E%t8@X1mow^|N zr)N?QP^3+*gThhcq^!EIby?q#!OW>T_U(=F2*a>_d7$zacWnYpU0o9Ts)|KTmt%be zxfx^I03T5dG2U&|hRW5bucL)0vmG2{`e{o>H)ZMTG6uC;Z|?H4H-B*rx>!c;FE?|s z2EPQ1(a+gg9vqTtGJ|8K20`xnO@~xXCYy~EfdBdigV0@~xs6~4&T?_C<9GH<%ZLd{ zAVi-7m7)dxctx+aK|+klToa}nfw5;UDIS5LHrC}X>(0d6u}9zTUL+8Q5hnyU!BO`@ zxGr~ptxX@K@q_;%7o7mJf#Br}@a<-_zuh>OYv=lWfd3mx$!%Wqz5839xTxp8_B%BU zAm^cGchCbg$d1$b%_5`$Jl1L)N1_D*zayD;SDZKEkm0VjYwwBCn-uxiJZ>Zw4%g{; z9oit)naTQ=(>>nrX{#O}a$fisgAuzVz}mBNDUG|CWaq7kqygt){y!W1%~gPjC(0pkI)!GUR^ zsrI9Yr6R!qLDlLe0J-m89(>1d0Trivr7vP>0f(mGU%4UgRihoFfgYs;jvR%GXv-6O z*B{(1cT7H5=i6PoMzTfL(rX1s;V~5GNB{Yc2?-$)?g&6mfQ2>C(-_^ugw!Ju^^QWO zyOU*N5vkJ&H*tSBWG@cPyD%9Q@RkUV(D_cZ`3|M}L1oM(ZNVq*z$eO)_M_K?Xf_3i>gnw!`NlyCFJJvo@Q}kZl}nmR85?B|2Bg9ONuBDN+ReX1TXOzx|B1| z3gNYp*#7A3KJIL)^6nmQZsDOQ^cP-oYiF>YyUO z`|{qXoiWI1Z!~qCV;HF*Z*9y0)nAYncCiYHj6DI2yqa_aLW~|RIT3o9==g;@pe*!1 zTfzgYe?k6t5~NEMK#=UAA&KoIE3d-Tpv2VB!ot+R!lC5k0k6!&!o+r*yrw}75Bjey zp0vfmJpZpjih(LSI??)|GBtHChW&C7mz)4%NAtt!5Qk>cc+DaUK$o=@u#fnX?Hx;? zK+lLl^Y#_Q*&B!i%`H@3hEe|RGvTe^xF0fv*QExgToC8dN|_hS_#AG2e`*BVgeVZB-kUbk6aO`Ck1B~3?_2x`IOJ(hoi zkeXVa{pwOAEqx)7wCuEe-GYUre}z(*T?Nxg&=FOz~2=r`Lu#yaqdc-uZ=$C zKs&z$JsBZy&1oSoTaX}EI%N;I65Zdh52Z4Lt-qzDw^1;dcd}wcrhj^&yFP>PAf?$1 zkl9wUO7QzpqzAm3IR)H9$;nC(?jBF$)~^!<-KQLn0r3f-f?&6!SN^n3LU=%hIA1Z`1V>)#rcLxpUOd62D-~K^`T!59o*mn!NO7!|_^w13(3S^CxP_I%y8j}=f6sVk zN-@*U&~>WD0i4;FoFDo#N>Ho%bZogwhaz%>vycd5vP37r9}@mrZo%j|Pl(^Mw!UaH z0;JFw9Y!<1V>-`6+sOjc02F&$6&_U8hV*2+rx9T|0_rtYA;#bLHL^!9ii}k^+Ix}# zewd%LSrb4y?MMn{nie4P+K@?^Rr}0pOcRAtu20pswqGpayZ2kx;ly%H5$ zesS>0{ysRkDh%G}J;+3+XSGI~<>JiZ^-J7nkox#kPk2XGK3aw!gwlai zp;P?_(AEb*o2-IZ)CBqBBF%_xjR2i7fykM(Ls%9EhrtG)-!)J3zUYYT_3@gYj3!PY zOcvIUTjfSlQIPV*m@_n1I3ftWjZv6w>a68&ZZ-rH=2p=aub-R9l~wrbEe9#WQXN)<>17E8iNkf(T!|714r(1?FG4<>q6L@T>Qyb z-wHD|fAD+?N-j~3UrbT3?qT11A<@{jg?25|^Q^B9ntG%`&=i!5e?w9DZJn2cfjWu6 zG14kUC8@K)xw>2pn2@tS0OC8a)3Uqz`dJq#xtN?INoI}RIha=3H+g3`r3O)Ie>lt% zj6sTFu0a>hKYndfL(=5RFyi+eVO&Qom;-;LM%jzK@9JAd%wU3w7NtkHpT%(@ov1ai zrXY078NQ1O*#ave3%!YP>pPJHBjZ=KweM@LN>5i@(%B7|Rct_%ScjS)7{JnUp9#sa zQ2g`)4yj9KwtinxO$YH7-4&>5FGay&)B~N~+}`Yl`%O`e2u#kT>GitEOXB|TqTlTz zt~+ull?S|AMU1*Ne@XjdbGl5iLp>?$bJU@xMQVMx#?LAYIYcX(=0JB^bh*Zi6a!iL zh7 zYxYq6vT{ zCYMr6z(lMPFUx6L5hdy2tKqK%Zg_IW;4lh zdXM1E(57*oRt;Uir)GCa?BaO%3eiYd*8fiIOCs3iyh4)HIyHO2kJ^zeN7gGi57&Zc z`SqN5g*NFL@ZE)dm-emlBEs2o;f;--iY*9mp73+3)6@&qm}aT{r8^Wnd#65NHHG942m>T86V|v*#$5o#c4$zGPZ1_ z(W&0j=Qd8z^(%c?f+YzAnO+HYz=9Na6a2o_vXN}G{$jkTCpSmgzV!f#&Wj}G+_9)s zR+dU7Kt`TgtZO2L1Y!~}`tBjuCnR4LWTQy!twG2u{WYS2{A=w{+|55J0u=fkBfb1$F6I=kG2jU&T21avHo2)iqIp&8z;AkTOe!?_Z{qLgy zlHP}$(rVH0?8t#ZF7irIKK(D^;sGjo857K>Duo$uJT>!+HDsQDR4FPxcxPGYxgctB zuy?wsJLhigKIdkxyWM&gAOH0QCjHsk+OCLuY4glr$mjf7;J#-y69OPK=?#wp6YU3A5UkK`@8mb@j zijb;krL=Ojl@mp3r~SHW+L2x|p?!7Fv4hUQF^@r-ZMf2)OS)2Zll(_Q^Cs!s<8D-t zW5Y{h!sB}2^iPL!cD){Z4?PAA0cGQuYtvkLf!Cr$TOQPQ8k~ghIpUA0$=}Fqo0{ZOxAJDJmI}`snpuE zORha65KL^E?ERL3ax|)nS8(x>+}f z^sAD^yfHVbbAyxyv7&@MuZ4%*6&=Xz^&s`*wl&C!v8suibLr3}tgv zVkn*Dy64nX;^A1C5a2F-G^9G&lCSCxp@;NQ5L5%Kiw6k$BX78?HNc4PGxE(FCo#T% zHw$c_ns^_urgGUlZxzZJ>6!qHQI9OCgHOv0>=RFtBLB(XXK3qXN6h_%D>~3F1-r;+ zYVz&AaYBNnMDJm8Om+JBXI6*Vz;TYXI=B6iUdWo@d5t_aPE8)IrKdhEenrBU-ek#H zm?B|*`mX+M`I-+qyzF3{c~wYxG)sk18vzH4dY?)r)T*>ok}sIfu_- z0z(H3--D8!X(65S6S+fMYRo^4?JEgF6%&Q*>Bmo^(k0UH5yXe~$aF|h;Dg5`)r!+09o zh$Z5JgfnNj@-Kr)f`i?V&YYN;x1l5aRwGz-gK3>jc$980Mb81)NW3oTB<8*JwyQ#0 zjA+hgO>@guEn{gx+Z;Ko=@EVsyOBM>*G7jFi!A73Pf`ox3xb5t$aZwJJ;Qsh(OY6l z$6Z?6Q#W=nev>VM0ZD@qtbW$(a3B3bQ2oS)`Jkj~Ox(>s3AOpwi1YHkyIih*+dy3E zKtgZ%()#Am@c-ZOD zx6C#!f+*JU>4Qhtaw&4H1rp`}uOH=FDLotwUMrnfOY+KcwaTEcU%W9!r8jAvQm|ty zeQGa;mv3vzyb0_?=Uj1&n#~X?{qZg+sa^S(Dv54>xa>D80Zwi&pbris?^f&yi{GaZ znA8;$4ACBCu-W|k5#{>nsr!g;(@wpx8?=!#|GW^3Z9@f-EY`HNHo_&>0@?{stJ^cv$Mz3(ZGts&7`W$A*_jm3K(7x zoF>)4Ffg4S<`8+5Y2T(Ff>Z_n0;t1gb>TAE$LuV(RSp9wMEO{j!`a0~aI61a-DT3P zO*{StcWm~`vgeOXxy$Ws@>guPP1_OO#Ss!%C}*}!Hfcq}f_duNVs0a>V84bRCh3F& zBBdmFyHTj7Ys(E~4mR;(G;j@V;siI#+$C8j4&dy}KimO@TijEZJ)h$DIvM=~TNE%o ziBht&2r#E$Pu19MjgYF0E!g&$U-P@**Uek?0E-rEPt*JwM#(kAWMiWR36?I&jn6ge zo8{K}=L}VXrL-F*78#rz;?uPVCC2orv^e@c{Ap6;P8$8Tw>m$sH9e(8lM$+*!5_2w zgciItUQ^co7VXd1y}6@gV?)U}V(sSXuVg*;;!~ADgy^+ejT`!H;jG2jl%+O{2_;kt zbS&eX4AUMuGp@m0EKcR~3C%eTC$RctuBkaX5MMEh=L-uUGIh!s<8GBx^t{qJ0@XfT z?&H8B8{H=T04DU$L@0dH9nX<|unes8#)N{sN_g|X4mpL0p^>cDC7=@RB&)slE#rraeVry3WTOo_O;OId1G- zx35Yeog)?={8oqmRG5u=?Xj}FTTp8VctpF(FX3Y!Xf4}CmQBv;pzB0tvX%UgZKUR{ zUjb{hyQs=<{(6?xb7<6XHsT=m(43=ww9`~u%rzk*sy4RD;Fm}-@qB1$M2sm@c$WRP z=m~ma(ta+|NBlx0ydqwR+^Evioov0b@kBSK1LFD-o`ivgX6{wuBfy%ZJiSgJ_yD%7 zFcZZg1(&_YI0~Dpxp9m7<99i%@mLNM^W@AtyTO4^<9!ssQ?-y{&Fxy|aCNH?^=BfI zpWgjOzN*+OUH;Q~LR{~kze9C03`goLw!wF*Dxo@4x&AX#GRHtLGgfx`TvbI?+uPmj zg_J90c=u%Qp7Z%Ae~y&LD>ttkYw*YgP&r^}T*`!EmEk6(14V@U2z%pvJd0jiuzm%3 zDRgC(LU0E=9J>*b>6?p< z7Rh4P_6)SLJT+E9&}f4Q%B!+d_a>%>e!2Wzb^@OUvWXz3(p1<(weC}Vs(B2Plb1a9;-^Hn}-m@(nd&-58d;l`k$NT(0s!v{O*0zr8r z2Ouk=f)N;!l)2^nA=Uu`h`N~caXHIPl;^j+Q7-MDC^2p!8}os;^&aaJgL0-!4*kzH z`8c52uUOf!*xLg}$G`{=VQ^(e&<`Zq6g}pYh*yB6aPA1+QME1?R(&O+Q9>P%=I3DW zoEeLwDwn<@s{@|4e7T0SHmL|`?*tcNyRjGbh%lJN(tkR2#Te#)@1nMrq@<3Z)2A;yD9{tcpl#WHE17v-fWlHx3^`dkz|s z(l7PYITiq9i{%DV1u_M=XRHW%NXgpk=6SEBcZ)SEaQy zrLRg?&#CV*{lc%OlvE&eHJ)~X>z^;^Z+9a4F>-ZF(RIKE>sbZVpl zW%r@bv0I6CYnmWhm?9Npp!eX#f6ByyW6*Ha~5-JzjG`*?3SN7-Mnxx$6Lhs}#wR z;#G|$&NTBOT*5D~?rl|7epBoag2xoBMl25u!j9W^Q(y>w%h{-1Tt@0p(Dt)DTTMpu z2|y?A89!6AzeE#nB3i&V)G`(Mk72Eh{p;`8wS&J-Sv;%%Jr5u2I0h20dPenX&&xaK z!c&QOT<-LZlRrJy3uR(q&z~x-rDZGp#lHA?Wjfzm6kJ2j!EeVg4WI6KIs}8Z zOlYXq(@dj9LY)jy5VIF(x9O_JLxg>mCwt8-tC3cyLC?sWvbd@ZleN-6O%-k^T*Uiy z49>H?RE5BrPBeszRN;{7V9W2D;MKf>ADPp0sc&XdMs$S;wT~LO{G|6^iOE%^cA ziPICI?8>3Wf+ZVgr7YX1wEPdLuIp zh@&&Kr@rQJLu=xZZ%uBIvVJ1GlxqfPWZ<|K@;^JD0EbMDoHBMSfOrh0qLC^v5WIlO zugq-GN4_K0{Sj>rE$3Oz>!HnqnE5P1IUSHM^}OP}q{U=$FD|vOP`GVIEn1Lf7iFz> zcM-1g6}X-!2$))mfMdpvHv#IpQ*7+U$1YF0*F>_ipRo-FUv0*krX%T3C~LOG8#lW@ zzcgQG1?yE-6n(16A)@CNQHh&yp{K~12Mpc(IE4||)ydj~t++u>sYYzeel@0X;zLhk zPn$(JQVK)y2Q$r;Qv5iT5#bNompN-w0nfCia`;*JmsYndx1?u34a5?Hi29?l@>-_5cGS)~yO3&KFB1l4wKWcq0>Ugel^V2K&r{kkiE1f6_^1R=? z_yE^EUOMx%*(Pj`Q%Lw1S!?H6A!^KxUkvueabXtG(8<^)xnY;YMoq7{sBWbal)*lh zqTr0dEo(-a-%9T6&a7c}Xj~#g{2bq9hS&0*OJ18s6U$9JIan+{8WOMRV)c0ICv zo%k^QahOXM_%U*`({80vXO(HwZV4|m?PwU5f?ix6KsSd6KzW=9oy1bqO*C_pLn*E( z-+t`=18Q~F+8uZ{mQ3`gUBzzbkoFJd?I`#y2bL7)?rH8sah}dM>~JwZr<0fXTKl4s zR?}Q+8pJ|MM<$FXRRz?DA^soM`-D$^7twltEFugelhLm$4Sq+Iu@Ek#%Qwq1`b^55 zU{=KR+_%?c#Gv@5&hM<=v4L>UkvP@gNo;mFqnYAZ##Aim46%x@!F~%cJM8zHzKW07 z&x(BHgxq>x?7vvZ@84Wu#T`Dx#NSFcNK%Q8s*(Cv zq>^3rpGc)%4K@57l{&I;6dZ=YcEzss>1$2o2&{+nGRlv0-5G;U1{i?}@RW z)&d1v|6Vk)duwQ6^gkHrRlpsO@ZA9=ZT)*-18yaL-sNJk{9C7~uc2`!6g3GwpZq@@ z`dQ^e4r#CDr1~G;_D2{PSV%bs0px+ko#!6BxvOPg00qrKPnt(yXZU+?Av_jjs0sZZ z{13x70KobP{1)LJ{J{B5bq^+6)t~Ezj^!29BV3Qb$jJ9#VPvZR>EA!h;QzKj0r^}n z{NVJFdLZl-_Pvf)`Q70AZ-oke_I7rV8?Y|U;7jYA#HqU)gz*iM@oG7ptTCP>|DRTd_K^#9fRPa^uihb>y^9*o*Vdf$f+R8UrH7yv*64glbPq}4(F zZ)edVREoq9@ZT|hln?kwz)SO9b6XSeUh`e7|0NWl1U(;5#i`d2Zw)#KWivX}-JSNI zNI$e0(7yfWsPZv+1HsV*KK<{pxD&WT{Qpe>%}V@x+}#uwZy}M*G zDaP^t80cq}PvTq1P_x+mPXz?01qe%T2*F8w0*$ca-3z{wL?O?E3Z6sr_=O({;u_rp zmvl)XeQoFu3Pzy{@J*rhY>+(zY{=8rzxBecKo|gox{Vyt^Ogug*8aCTu@wj#V{xx$ zww3YT9+^2|`8TM&IjB9pNA~I*pg<-FsEzEwmU$ZxcGme`xS);sUihEjJpe%eNSN3i z3M7Mu^NAmXpSA;G_r32G*0w#k=%1MWzlC>vAtmkH_ZC7Wcq}}iBZCH=AcBu9bcR6S ze1svQ9e?|#)B%Kz4ZCk%cn8P5@PA?q&EYYz{s1KN+83vDk z3l7uo6^h5<{?9ac^gwvjvh^&e5D%oU^I?Ly(66e!f&NJa5DfqjdIapvg-!($2u2tC zgOen>fUw?0(7w|^z+HbGmk6y3EQKm$f-HB@+&7m2ULKtU>JyW@CO=Z>S^));K}5Sr z9~A0$17T+>@4JxH&2TRa0SLw4IrIi9EbvGen!Tk^BJ__#pY;G?59{xR#mjIYUOj&u z9RaPEYkH`ULx3bz5d4pC?w($5yqcJjnd$F^PB?C(gZHpoV`-#l5AuA!InePRzre|5_rkk341pWR0 zBIQSqQ7lrW*DEpArw+=aU#>|Rvxb;m@31t4Y*HLJBG}@!55@bQK;~|kh4YSphlVX3zu@Q(P70y0=R%+dDJ)$>k_nYVvb2AM7+mGyJYFWhSJ>2B|r$5#Dj=aoPR0X-|Rp=r&Jj+Vh6^%zjl*;i;CVi6vMeA=l``0!iqi zsVhfJYyILnHOgSNdL&swNbk%YGRiT>eI#GA3fU$D3LB%;Du#~x5GQ37&bO44$CAe| z(|a53TdIu>4VWCdvi8YLD}_A(J)%X8uy;zO524E>qrG&bswy`IKYcuML+WDo5U- z@n?Rpkdut(2^M}JFAK76T`mfh!j52*NR|+H?YVBQY3u#0%UfrghO3Ow*8!3VWXyq{ z`{whOWtfmo9Rce2)jNOpYG$oPACCXk+5m6Wk_GE$?Qg+Gu(aHypA&&c*uMyXE%A&r=S%F#ErTtQhIYnV z_TB_3N^0HZ-@3Hc1hUJs&fJvnR z#2(A*8%{jRuxp9SqvL_gP`F1WVkW^I_P@&oaJU$1R4S^46v#9IE#IGPT*oHp&^fGK zc*1~hS;%jo5v)oxjs!JNx)=TZ$Z%Tv9C*RFCDxs5v{&F)<~?}0vO)~8T{0_KyjHeZ z!)MF{)&j#mv5+wo86<=8rCGGb9Vq@RuCY4~{|y2IrKsG~lLAYx=*^{96P>I<4vOsQ z0d0ER2(MO7;qTE87-uXhxTzg6`4l&;$f`snw#g=7Ekr^sywD=wcqPgaR4mG`VlNZ| z_7)uCr=a-(b&~MmAX^w=`v~D5gg@cgpK*wFz?^kq5|4!m;e<6th_wliP^}5)K&UB* zJ%Wi@kQ?bCI%uI1yyDBs*Kx*&f22}K!wq}QMC80mjGKF=O-O@6$)yF39 zj7ISs_$9fKfZg3iHa8n6cE#o7=x9r6W$C@wEPugMV!utn)%sKSexcNDsabxBfoT+xbo^la?a?~&m4v+uRnq3>ZR&5*p1{_~z#{9Lj4y%fl{n|Puq zyYQJ2{lzEc_Eaje`+@>Id?toGWBhr|Sn{qAzf*V%b?)<|DeN8prIXpGeC80OCJdz1 z{Wd7LOSo1pTlG`68E!d8!lEk^iQ6{3gu*j{gz_eAE%wlyL^B$$!(&d`mro zYyUA!IQRy863ytSWTKu1WdOp~L!)0IE}FkcA0jsn2BVX)$Wm+GScbp{+PqA*`_KG}<|&e8dCw^|1C(1UzHdXaT*{QvBT-`)X*(H_Hnk@?XF5n=6Fm4%9{=UL` zyZC+$oCEx#@;cuBwX+fQ(@Bo-&Pdm>i$}b}F~j>7S=rDPW}fl(zsUro*ekBN>+r|EQS^zaj5DWJ!k)XE0-Pd%pWIcRSTH46J2 zg-I!gC2t?Qu(a!1T_BfmK`m$C$6IH{(7XR*H%6(qr1mk z%|eIU^2gJ@ny*M3l9d+5pNu4(t9o;shn(D}jk4$;OB;pRBf!Q6Y%v4{olf&hKZ!;2 z&X)3Y$t}X0be2-pRC7FY6UC%UmZ2+@k&&IG_e(kX+jI=J$iN;mx#Au z%BB*%#v(>SdB6ly=@18t%FLB(UlV9~^ww)zx*tvW4^whFSq`ZJ{M=SJthJFl<;KD- z>Nl{}V?0m|SQZ*sBF6R<;9+-(JwEUq*F)<<)9N_ML$bwoI8c1B_*&Jx z9pn^^0-)1n5D;X>qR)fP%jo<%m{SuON%izq+WuHPPXLqYKcX)*wxlRVPCy1!tiwXc zF)tb)`70`@w-<|`-RLhu%@<=csozQ{;-3ANB5nS?;N2*SSw1UkKy&S!9D>_x^b(6zo=TM<+2xkHZK}lW+F9-DGuJD%eH&;E}-B}PZ>(I{i{D#hpezvM+ z(l6T1G;EMozx2{q6?X9UwVPYo^$65lo+KdD*ojnKOIQ-j5bQ97y>8Y2eGtwhX*mo% zjt35znonjf>*rUT`&~E6m*nN=shz_=b<6aL%~d`rf27Mmd0?>Du&JvN=l>-va=}m2mj+GZ-m3wJT)`(d zNBn*q@6MD`AJd*IuOj!lb3TuBs#$q=5(+#zaa>+b+7Gr0b7HJ#CqrF9b6GP#Pim!R z-Xy%q%NYnGMBFs@gqxtPp0iGJw`J%i?{cUm5N8+z{ybnSRVUn6M*U<+L4Lqqy0?K# zILU#4XT=A4pq2ZEk0rpm^k28q*(uCdJs5jS9FzIBO`_ru9GlYo4b-CNGXAj`iUm}@ z6@KW_K%`b$Kr}C&o=|l{_u6T)8AD2mdxR};Ryp?3R`nD)mL^o?s$5Jf9$#kQ&nv+v zT(YxFq0ZXR(A!2(%+S!(I;PImc*npde$G>RYo2_)*B|d6k)>pK5O>b*Ds_%NB()I1 zZR$5)U#u)N$Q6QekmC3;ob+nZ3IL``rO>apeqC4nP<5%xh!iKtC$B4_xoIn6)|UmN zGS^}l3k=5~HD<7t3pt%ud!sYJ3k<<{Su1;T#ML|m|5ac@v58JqPD8n_!Bz5rBT)4~ zS5!-FvAt3C!0n$miF_AC(`Trpq}qJY&y$FT`A!ldnEwC`$&LIN#=>E!K?qbAV7&1X zqpH=vNKFXr)J`KBdY3&&K#mHEcQz-_uTuqjjqOuK6Z!GM(LsBLPHhrej*tnQEkcAZorg`mX;`n-8~=uYEK=a7}dWI*s(tn)El&s~zcHCs!y0p46bp=lPL%PM<`pnXq$KcM30 zrI0e}-;ZkeB^9sV;ia>(1y<|4CDT$bW{Lda)(dB7UpT@}+wI36aa=&D4!FQ)Ygkre z5NI&O(u>hT?^=60=RS84P=;3g>xa4HTui_e1v$J`XbhjOmf}fbE8$#UW^ADJjr9eV z+(b}eP5MNd{BbTnn)f!kG;3<3o95UrJM&6SbyRZ{M~PmO@I_~QhIWV56LFFK?T*?v zdA(@06kVCV%by&-p3i{!kDi-8q9}SV5XoB8z4eoKpsHjbkjGg&2`w)__4Xwqiy^&K zV&lw!50knmR2wJxUYI!0;xuDqKt*LjxXL&IUKY2Qb{t2-CTT2r@m&}H*NE_qopgt& z!7w%X1xS$n@|A;l^MAn=-*P5kb5>xT|vkrN6-YKU;aMBHTa*A49hz zyrR8pPx?JItRix(s&wj=8X9M~P_#QKa-J13_s5(Tgodanz!rxhZCAIYrrSG{Hs%Apig+>i5xkc!R=IO-r9s)n9Akn7 z`i2Be8O8XroK5-f1dBXbLE1U@%o6VLy&@zY$y@Lc=WL+wF9DE_Dw4m@=f-C(P(Y%= zamiDHAr}6?98bIw`4@2&CRw(vtwy1p{noVd&~vok^oti#W;L&X$|z5rF@xfHuo^GK zD$aqZJO#!&{mv_|h?Nv29kRHh1zgDs)aD|`6@4{D(`ALQ@-!q~9rmuRCLKgx=O$Kf z3>2z?32@*OsyD(DA*Gj!llKYibn>_l<8{EUWL^HPLGg0}(BE#N~>y_-gOM*bj{S8x;FG8+k zF1JP4PQc-&)u8eo$P1eaq#wS?ALBR757ZBc*;U|zd8d>h+_Qb)V&1Tn-e!(^GjEM0 z$g)^ZVzu|pGkf?6>D}C(@xqpe*&tF4*WLW5Vkssk)>N4IZZD5(+Zo;z{^7 zz&hHKCTo;*XY!;Wyv{9GXB4x%WeV?MR#i8R@hGe8h0^NKWw}8rr~Y^9WX)qU;-A){ ztTG_MWAh`+wPCS-g7^kO!gU*i(kj^X`2*vk0e*XpEb8TTlq=*;{zkltR~LMUl96rh z4VUx#g@@=QpBu7a>2HQr1B9t9<8Q}Jyk_pW*RPW91`RLD4_c+}(A?6g<-?~rWkVhX z1tt0o>U4kH6P9wg1nr>otx$~lkm32D;(XwMo<;p1lJrkz?7tQ$RfLxqOMlMQuRbvgPk9}qxZFc09eW7|?taL9p|H=Fot5?9gVhw}LEVD1ZzEUc zg+_Jtf3rR{Io`9b{lWN)f0FAVp~(B@PkZ^&cGeWTS#@Uf^oSi1Y8WD?iu6y0$d3V! zm3>pBKGg_46QFX7UZ~oCo^rZ^q%U;9N+og(7f7W#A=@BF!u~SS;_3xpO zA;)xS3HnA@izjKkbAjofoZA~cT}EMm_Qy%Utze$5fo(4xJ0N+PU3iZ72~h$et|%Fo z;ILjOMJH|2>ikvGcW9G9EimA^-63%apfQFCFz`_H^H`7c;6ZDVqUOgvMv$Y*4Up zb8*{z!xBRmG247bVbFrL;0+&Wck<8?fh-2w0nLUJN0Jo0ff3okSt1ApG;qxz*b7E zL7%F6RGxd$QXXLJHWHl2P?Vllgp%=K7bizBzphMQ%Q7#KRvfmc#}9(_ee@gZuQ4-8 z2iLU?OvJjN^UF7LJbeUSHH7rLkJRYZF!a}7!T-CLZu-Rv5BWdt&VV@ar0BmLbqgj4 z2-W}B-GNTZ8$kl5Xn7dn{l@vuA(L!$b_%z(*#89s%G+qhDk~*o$p= z6S(WC=zlc#iw1YN!ejs#s46NSuo-NpkJQ1B78wBAn4P*AcMAk^2H%fT*^dz@ zFYnJ%VeT1WJ4BkGBCRAm%&>Hehqg`LaqRcXhIWM3cP+J@8keuutgqjk2G|1WEgXI~it7(V!hz~%sq_fI%C@~C z-b%$F#XM=`sWl=x;P&vP*IfL_Y@6=3Uk^12t7cTS<7i}S+$mw3 zR?v>Z@?oQu3=55P@3l-5 z)7AlWX%kg#X4_f)?9<#!c%DIPeh5#W|gJE8~j*J*plDI3AXVu_DqPANIcT;gg9E41;p zML$W`!e(;?k=`OlPzwUm}SHQE(vpFmNIKBTCEWD2tczIZFSpZ z6u6~qb2m!^JLR;s{8pA(hI+;9xT)D7+rkq}OU^gcq|v=ToTrsNeEhI?&6g=%H{*cN z1A7YMm^79)emvB8D+J>a+v+u%1iG)ni66ztKbrz9^h>Rl1n33xBP27)sUm_%W-^IM z5kYw@gZCrV=6y*+;Bd#lkBG`7z~zl4+JB~HsXlpu?mv;QDQKuj>{bApT;yBa+ju#dU*&;QgiLUdiz)QcrP*R0QLPP?mONs;f$HCV-Q}x! zX>dAKXWM3ry6(xFm62?{qQ8YB=qz?qNQw@8-UOnmuI{9mE$~#mD{ukLJ{%)cgNM9U za^1((BC>p@&Ka!N?Y(L5I|E{jQ@FTkshFe4YM&^AXd!~;6*)gAtJY$x)S`hVQIR!k zr3dTSs>Vg-gZ&Hu!2!hzxDu3#*ixvq1 z@pQMeXHDn3%SiCFLr&*AU2aR5v-IHdO*(T{B!orn10OJyrfF*E2Bd*~4;neAgICfw z41NPXJoNtW5oG9QHA!t!0LVahSCP zlW^5VO}r9w?kx4iQ?{kyw@+>fy%T*}1s}EU<{~Ol&N95WZ(PF6=os+1y+kpitD@d0 zvm^G+QNv>Ga3s(o^NGWNg}~zYhhZMf;ceiKpxPGc8{;!pVcotg*LTH%;hP8cxAMJV zMYZ1=U^I#5YfOkLvT~W~5yKXj?NDJgpZn$Kr?p>RRcrj}l8JpN6omOKzB^s5>R;*j z4N0okkHz`|S0coB_cm>IkGoM^9m@zKTk_W=&uexOc_(tYeg5 zdj<#OvqPn!T6r%AR-1+ZMsQ`pg>%Y*S(y!@+HWNo(ppRsGk?08Ez6-^c$L6U7JHh6vaM<9qyW&0 z7J1TP7^_)XMd62Hd+9neCC6co)57k3E6CUMHVxDHCe;CM|BCIT%#_7C>WhwM17nSm zOMfTz2OdU@^9En?HmKgdYy?;?VxjYDYGYle&oua@@9ah8MbvfmF`ifXl=^Uo8}Ko?d^XHkD~pIgae`$qX@D)J6l&v_qEg zTRoWv^w|3msLq{ltgIe`Wh~M=8htfbblOim7+$rBVKyMChtWFS#i+@`e2^9s2V+7f z@&y8>0G&5_z{Le~oGD&N^U@4b-bV3hrf-Ke$sDm}O=Bv4%T@<3bC7qWf!IPB{=k9Q zAJkzqJIVQYDMDdWyqHdcKbsr3qN)c5gmXp{Y^gkX4Q4Fzrtb(Ea))Xjb?e)@=T|u= zH#s*qBRRkx)~z1(o1Ki=oeNP*11x7g!OiDxc|JqJOyQ}a*ZG5{qm-eA=MMO?aiBiR zpf%l~m*wy~!W|?RB39xF>5uwUL#!g?OPEJ*y&%M+zGO6pftj`NuKJ0Ab1+gz)>M%( zg-KG(^#0;CgHWKab8BXM^9y=4dz?u}67g%HF)r*4 zlU{*UGm$;*7~P5GWxHY8V#YpN}k!%+}PT9^n*B20BPE1*a zqC+aoxx%E0jO+n!R<2j38lE~Hb*KvG@C>elay5O_T$#Q?U+D$(X{&v{{HM7C-;)W* zh&5oyGE#ETv2_3fM*7-U`ZcBt}a~%|BWQi+R)-wcDbtveC*>5DKGaMcJhRMj{ zwV4keR8_-I`T0IjXM>?7+}z-tJ@_kVJ_Mk(BR`x%9aRx(N?Uu_sJ{uxzAksv4l`J{ zSANWk!D!zUG6@frOCq5*_>yX_;2G zrE76Q&%h)I-?~G=b~HW{v~>GvD4Vh`S9*hPJy#WP@R_xyep4hUp_ zY!NuwKzoFbsEm?Mm5^nPXzALh)54NzrMK7+yv(h<@?mb+iW~8it^g^)Kbd40ef%4n zicM{BF-#tsw$Jk*VfFG1L9PZSbTF3(p9_AOvj{O5!4V=~vJrCW3&3QcMmU6`TxM4Q zIzhsYq8V9PGGjW989h*0CxvZtw*rY2n4oJc@HUCi*)CIX)+bJr9Q+I z%D^c;V@$5fewq{8^1F%UqBUr#3Em%+VF~Aq} zlz~?d6d9haa6N!&51dX)1kF~`5%*v_{8GCsAoWJDvrIkx#gDydY{yUb(h#4?k^ zYN_joof)US$Xv1f?VsM;r{3GRaIA}@*eWvt>=f?R(5%QL zcRBV2wt3yV11|580JpKBto)1jn1Q>w+BK86_oJ2`Nao+dRuus&Cgqn9&6;t%i zZ_!ws6So#UZ`qM^hwyN{XCrAx`;1j@aJ@8Pbro+sy#)tZZ>d6mZxU{Hly>)niu}mJ zUJ)3qsa}#;0KR(y_%*q5LCHhuwgi zFC{|(AIN7(%%TshL*BM>iW0|sU1trY zMbrU^B&^(kY?+IMbX30FWefA@pN?((1Rmypo-6|&ynqLvaMiZlYBW7F`icsubt`7N zLNlbuXVL*BNaD)~1_UuUTlzdnY#mnOG0A$UN{OXb&l1wQ%te%{P{3;XWLEqrD4%Ih zJw>tN#vu;G0Gc851MGmK0J{-r3B^)6GJ$Lhb&&ZaUI~Pe?1n+W3Tal_crN zBrIDC9mnHA##8!5y_1*~9jODo`{F}2TL_=Ej?6ej!wOdv><}Cw{y7nUM*S1ISAg_k z3^U>JtuNhA>5`a6+tKKslcnWT_f#@zrbq$kXFv>ctKw-_7F*XcDbq8j0D#x}2Rf_O zl=g|T9ScE>xI}b4YEHw$PY&OLWUQ9#&2RFHs-jjD1?)Pyi6SKFT4RJmTvz<1_|Os( zut-!cO<$Wgj$Tpms2Jg_N5dEwSC`D>tg>?z#NCls3}3?`tts%ub zRUo9`(w)m^pm9CQrq9#zlYpPZ5aWGtsNw!9?ty1)=;c6C-+%+)Q(@@#9y&msL3o?7 zN}rCRe#5M3l`l6u!#nu=>frHELI4lR%2t0t& z>I0WdH0MJxugX|fl`uN|SRRcPSLSB(%0X>qYtVc9F{UHGEbac4pW~jDq_6|Xl&nUf zJ^wAtYLo=g_#;41Mm5-#RD{9JK13b;xlkD0zd>eC#pEZvD6z3-sF3w2jAWcxj1+Mu z7r1{IsfNt~Saefr=Q5ag?U>x-7SII#*f#Nal_W*KC|p9GEdr!TQLj9_B~vR553oR| zdYDf4yCL1{)9Ao55L6(Uc5jBx>2zV6X1bayt>a~{NeJQCdP6&MS_!k;TIXK@v~1Cd z%s5B3nXlHiTe48~{D-Q!pY%4Jx>6e%LRGyeJMgGe(uz}~ zF%2?6|5W)LUPfQK*M_(x{~;!3@Q%6pH0==;Ltk>^*!Mus*vslzQ#KMK`_CZp=(uL6+~$ZhS$j}w z5w%~~pBy}=to5;f-Z^Qy0Zxa~&Nt0KHnszAjPr+&#EVl=J#RL(tUlKgswgwuVijp0 zUDhEGZIseh+32mNhh4n}q)OYJ-rwIRufbYujcX)Vyd|9{Z5zB4!5!bmz2lWH0g*-ZJCJg7nx?fV z{``b3>9*m((7Ek*UF5qtnpiW5_aDP4+P4zz%pYtQs2Q`Y1B8s_)UN*aMubf-cgopW zzd6VeWw6-VY8pJQ&cSil(prk`l8|BOsj)wubwqvP(yR`}T=ko%u`bBYJ<`3OUW*E; za~mk49As9-{A_K4240&;Vt7<1PRgCun;v(jRjr>T5!4pu!Sn9!a%2;c=W(!Uy@1}j z-*5Gtfnu}F6rJPfh10Kr8o_>I4^TlMtu@KaB4bV;e`~1$a5ph5+cLIgNQd$}jf+M*p-vKH>$Y=o%e1iRq`$x(ns0JBj1ry+&0D#{6C^X*3m z0_UFdmaM337rvA0m@!|DA@a?>jcsCUt}ljor56ZKa{DZgm5 zF~;h`%AfQ6qHy8;_MEhC>5XToW)jdOOID7~euIZYU=!74I}>W^U01?O=GaFB$8M-L zw^cz!qwF=LEc^vy30Hqg`H1@3FMHjT6eJ9lpNM}nhc(1g<|gTe?AE0!3bS-_`pO~? zO4^7GDs63kp`7!Hd8F3vU~&a=_wDUtcK6`Nqcr`hCEE%cX1nAgK z7}-E!0nHqppwg~-h<+D%>G3QGL`^9qjUD%1ah+0CNf#$nU6y!H6pDfrARANUfoK|S&Nx_2ArYQ2qGa)FVW1JT*W z=&?c}gA&|-?!4#4AUvY$mTe(xUL0JO>~+oX{16mMo>TJO?WrUD1)`+(Cyyp;!DYs@ zJi?y&MoSJR5=(t6t6CR!F*w3p4}wyKDIRyT>QYvDTQ$wLCx!iqH@art$b8Dawm3ZB zroszq-ko+&*!6q~!KQBt4#G|>52Y42&ZLpnhA+||EWTT@GW3@!>??TGc}q(8Dx_Q+ zRl!@}|CO0HKAxSp{~Fj95R&2`aFgzap_1fBVSxV%;F{=~7~lLXB+Su~R65mEHO-+^ z!!0#|RA4&RVvvw6t!`4e#;)lRNwDGD9$jl2J!^g~?&7ql&)K=Thi(4%rr(iY@OHN| zKZHbz#}bEhZ+dQD{XM*HXNZ7?e-Qg|^>Raat`5xwIr+Dqpil-uiIAX_B1HxG@P83O zdjg%)CF9=isb1q6j$k-(IsLZ8z|TacktdRhnouPBh6Sd#6TuO-hqkl zZ_wu~?#~`)wo`(?)JQ*#0W-5jUrJKQvIoLX&&}BOFdmY~_Q&M^;&-IEJ_&yJjN)?Iyup{mh{ z=F7lakYCThJxh8KsEPBd3a_u?&#Zamqg6|<1c6vaT1s}WDj-iv{2^;$q}CNWP78#P zm`#Jegfqo-ghE3{eIDj=B=@fA2V0E?vk2zJx3Y;fYkL1>PL$SaBe0pwl4{!&DmS!f z0(OAH>{2Zzh9nc@EHph?)-OhdH9gWwCuwBd8#WY?oyHC+oB2S^$r|;Nq@1yrP2uO6 zJAU%X!p2Itv+W@#;Z>%+?Y*^}oeY#Or(Z%Wy+Uu9ww!D&ISH|+W55LtI#8mUB25k>y#GM=R@Aqsbs2wM!i}Q7;cVi@?T^M3U*(vp zD+)}D(+2kg#J?(2tgJ=cX!927{X(}YUVI4o#Yw6IV3gzhY8kq$s;q8Y1DyVeH94NOq(w4DS2|-?!$FH4oYfXLWQ1!F8hKKEGndeXGs^X@`62wQSvTsL`4gmi=&2@ zGE%t1dj1#prPoU0_A-*jg|g-z(@nt3>cbR$$ZX&fi&W5ldP=cG{J41zEnPF-ZPp_8 zW#hY4zuxnMouATB1n6_QZt?5p#z_DEw7{e@d2qXqocCBdP-$8&t*9f`YGoH`hFYNZ zbR_2c5B-T{rD_p>ME8EH)%}DAW1<50FzNFAscbmmMT*ko)cXr`xx@I&6Vy*{Z`?ER z8PV^{`rK;H`Eo?^JC#1JW@iM_W@`lUf)8d-5%o#zJ-kBi)BLvfPYZ?LRnnq!(&xM(A?xK!BB*hTpr z>Noaa!(;LThL9^L^&O|J-#%Y?c&c=_?itDLt$?ZRiZVy)tadu8Gu(rjd;YtU{s)@1jFF>}EdxVl(FsoW)bbBQ*he8~n zaTJVEmDVrJ2AEATUY!wv<|uz#EXsOOghDb^5d9}m|IEZ)Iq{PMb=vw634_mwnkSop zLGpO|xcs#!yJ7$rUFAY1b!XPoV4Eo~UoamsuT<6EfUR*yC(@u__zyT~_tZ9}2=Og6 zG6%8JPm%5dw!k9ROnk$i|IEAAwNCkTPp?gFXjqEWt4|KPt)UDzRE} zmCkZ4kzQ@L>s+g#*Aq#W-7YVTv=KkUL5IsN_bc6F?`dww_s85eGD!43 z5zhw+DfVz7eyS_d6fW?AhheWUG7t$MPJn4kK;HcWTJpxp&6BFCz4)LpmP2m=bwr&Y zZ|Jn-10J`x{7|-?B8>6!pEH0P_|3VES8!Qp_0}1ic8^FTyj!L6P#NlA z<0(Jn!RD(u^vd>c+jvRipMMzUDLRrx<6nF*2AAJ2rJd?X0S7F-gwqr@{HVN)IEXe) z3qCv!nOaY&qMY1mtnc<+d9sxhb~S&QW^GD3e|nFwI4xDARZ@X-ms?A{%t+;Qz?>`; zFRjdX^SxQFm{@;2UtX7vL~5b6WR)!md`fPsv1+2i7%pW~i!YHh9k6LC;>Y8-w>7mTRpNI^^SzF8ucAp;| zchVquPk9yAdrZ2gN~GblYN8aS5tNc5UJ zG-aH)qPS{wl^M({((JmTKC3-;DZNUWBrL!(PuK|>4u8MdKucjaiKv!KE-0^~sawiZjnI|)fiO4;aaD6izXsPvXzzmWh)*tRKq z4o)yEX>Ty_YLgV0hd5~Thd-GTM>v5?IxhF4J9B{sV(PziFG@*kgiPy>C?b9$2x>Zh zf&yB;zlnHo?8HYH5xPU#)IS&d>+Wp8|G5H)*}ze0Kp*QJQf(6(=RW>x2S$g4j+CV7 zGh5})U%ONL$TVM;d-|Uf{o2`N5D)yJeqd^!>-~m~)BT1{q9gL)xBCuI2L1FJ!{L#e zP&8WVPGkMMPOJU68i>`hTE2u54C>mhH`tONaRJE?;I$wQfcB4@UHMoc`{3xC57jTf(Seyf~17LB>6>Za;1uLhr>MOH~wW ztmhsjP-?t2mFsSd& z(N0JIsK(UThX!*n+oLR1{?Kf;)*yf=tilmv>$*DPcP?hJq&Wua_)0?dno;T2>jN8F zNd3Z&92VRoDTeqLer$DATtioC`yHU}=zVyVV2hx1d2xG3KMr*_LEI_V3a-@q<1?>w zamP*UOjv|>{)lz*O{7io@zfVO?Wai&7}$hCK-iXm*Br>|4JzYu7u|_?VOlc~yJKQh z`g2x`HAa2C%`~?3)(*70cp9Sqk^@2^+>QC`BYH%3mDJK=b_U(?i>>CHC#zd}pj3=S zeFzYRg#LYUo{uxNT~j}#O7EAxNfkJ4*S>s}!ose;x@KC{V}V+D(ZR9tc`)Clrl~R--Y-6>h7?ZnjOw_3+FpMQm63pSS!QRi}$m}tE zr^drrllfs(_skc$%ak8*buWGpE`YzP<1Rt7=JU7jwfJTq?qx*uHGf1D@PxE0`t1lA zrzdH+Xj!RI!ej62nRJUb$PTQ&x}*{|8Iw9w zUi@IYw8-|Hp1(*MW|f@~Cp}m3j*;pWp)(|ltwSH-jM#%SyH$E&Fyo}^5?{y7ea)a^ zmZ7~pQ`HN@ITn{=4ZUD>2}NbIMmj{VK8NWM4ACwwkcG0|s#$HY0bH8;2@v@tAG+EKj{RhE7ZMO@2zo$` zB}hbOPqkd#kpz~8Xau-YaHY=%9~8J!;3Jo@TImu_|M~pI ziohoevv_8-*p>bwP-^HKFOB?XFHlFY;Q~JFj{55e$L6?K@sCAykb%IEHvF|J*PM2| z!RWpAtjLpF^ok_xEfcK%-9BPGTZUw;>(mKKzCDWNYB}DpWd1ku|7GG`5q?0@nV}@k z-4vrL6N@qvTRjVN8w-bqqdTH96AKgDev*_CHR9I4qalbhPW^NL$_oTZ{zfFg8T(;k zgy7q5nHCsy5qD%g?<%$H67{+i5{fArA&4Irhn#Xus1d>n>|_uxeIzOloDEva@4^-^W$*ibU*^z;MU1k zQ<)NRtiS~T_!b8pa&*ga!LmJiN9KI9DQf`E&q5VZ1*o_Fk+yic)yJ7n!o`m!Mxgf? zIaZiImt1l=#8_%#-BG3&M*Trmmr*cX5tqCUG^)=r>|MwALjicp!Y zl4_;Xie(e+6<$*q?6aOKt~>oZeQol-Lj5n(z9KtsdsxxXaIHTAI!<@EH~-|i%!LC# zPvd}4jtG6fm%@d!o!c)6wos#WpfKuI8Mig$!$ycX3FXFJt#82F^n}$wsHDYcE zb(0YW+EDFY2fxk+yFZQR-pp70%vYUnN9n#a(B9{F%azRY@JIEfRtgYJu{1S?oy^ds z#@T;Ei=OH%y+sBvRKr~w*i_>%)@`?5p8Sv-G?;vy{6IcNl{)ijvOXzj#iiclxoDux zOfy1VWs;kO3V-;t)pC)p-gMwC`FmxrsZ=)QkFFvZn2h8JV3nC7Tg5Kq=FHL~X+2pT zkPFp{Z+A$;VXjW+p$8o3l}j4LS#^yLY1GCRs3Ue>WXL4dF3hN?p-Z1-J;(_hPVTWx zp2LeJfB7;FlVlc!329A-6IuBcxKn!dlWEocO~Ekb$qRyd{3==hpx!S_RM#%Bnd~M} z9dX|7QBnVQZCVv)I0@v02JVXHr5Zl+JW-dAcOhL!zV+3(h8mFWX5;zmW(ENoAPnp^ z?>Z4Hc?-6KEewhx`iWEZk*ls)4tHa7V?CoDZ_Uc3+%ze!nyk@MSw>$bLw{UYZi~|0 zk3_K9U~kk*AS=E50$>w${-KEEYMcyIO(q)@gI?dj+)jm97 zWCpq3kF}Z4hYQ4K#XcROuU;;=`s{p4mU~@JB}puAlX=AE-g0k8{G~6^K*gWSImzyV zSG%6Iz<%oo{d^fLh?np4YVa+%cM;oRQkU`U8Dtkk-p}ju+x$AS$uhTf*iNCfg9ugn z(i7VuVO~CwBIW{35R)gCc=aVvR9})1nQE=+-DJ=y+6_2R6AOJ$2ryi_ck>k;;dJwb z*L3TSaiEpjr8mR0u?RkUL+Y(M@cFd2Ezuu&b^8PnRCAms@+mqWux8l)7(lh4A3eoP zU9aPrHIW&MyEFAyXw87g{_bdw&D$%(hY;nN-Ge0{>JM}KM8Ko*uRRRnsyoDbBOh~F z>NyRe!T}z8LzUIiWeS}9B`kfKH*%xWZ`gKS=$0!UK~+wN1oSOBA>#4)$H4RW2f<&9 zwKfzJXQLh~HqQ~vYD|6WHIV7tNGw^`95+>p(Y4{?NDV&W}M| zT0$HDQ&L}boNqRFVv53@5NENG)0PKej?!hzOACbZ<3n@gW&Gh`-DgY#7P zvU}sp1=X;bw$>Q-$p@SX2Fxc2iXtElf*>p?I?%|yMS*j}g(+em9EI8FjWKXEcF_z0+q z!+*!otap%$*Y3fP#zmZ_)@+4ExZD2tx`p`E*A%jg@J)sr`ld-v)2s+t(J=4~|s8zr(2RT@DZH7G<^0!r=#h-Xb%1+?;Ri)&svepia<( zP}glF#Vodm_xp}GHfc#vXYb+k^7-a|Fi!kf${i{V=JW6L%orYlQTE52dzU*VcM(!+ zzFXPxtF7=T>J>%)=fknp?-hAY*A+5kql_rQxZK)P?~(e{uc-<3B;?O}K;jg^EWK*o z!>{cQPDP)^WoNVFgmQIeILM_z=pS_cI2G^Fd72MjVzdY$C1=JGS>BAx*qZ0 zl3b$iQkK9&0%;a`ltItGk;u0{H@NHkVORbC`VRl0?52gt`TwQnlIY6+(l`~YzPcJ3 z2uKCZKguOex^sp4U&_tVhV@nZ-SOQ!?QCJsfI&Sy6JG3VCz^4s_HXeKU?S3%6>ZHD+7nE#zNmv{V#_5P_|)CYNf`_!Ny zz*DehNFAm zIuW8I+UkUa)uI!kMGGQ9lxSH*CxnO~78}Hhx_a~yy{}I69;{BZ=p}k2N|66pzAr!D zf6kdbGk2c*yw82_otZuJ-uoWLv$JYqOAu@9?qT#nQ3uWmoH5UrV?og?TCH^7G_1N8 zG(bFa~9=g zk-GD2;t8zUx;cYy_)FhKTX{r1q#|g3dXKfCyOr}5zmtoXOBWA#!6~?RdH2nLe)i7c zAu^E{O0dlt^gUyONx$IQHYm*9&WkrfUo)Zt!P-o$S+&FS~WG9=&vK`6cvis zL5i_$Sz%61JNHY9ZNLj_d}-XziYBdtTJwPil&KZ=&?f)9_Q>Z1)k8gOB_h_rr>dEG zP@_`&#Wg=};U}4s2fvX zXy+hAu%8@*Y!P$TtK+lps)S4$=T2K^Ek&y$IyE(_6Fc>(Y>Myey&pym7Z1Y=uglt& zagNw3jC|ZFI(EUU>-@#n5_5E=dOR^~LuKHl7)z%oZWT=R^FzHR!Fsd#XW*zM`wZwo zg_$kEwZk54sp8_Rw8;zbsUB@kCP*1B8;us2Ilxl1c7uqmXrA|zfIqTrPi7pO#}oY3 z=Yw~R4hzx*2$*))yyvwl9KY{WB)qh~amQEA$W@%5ia490+3SQVcva=F z+8aY|=UtBxRE`B$QuuC8^ZZyyZPKc#Y!(97^{UWbgbzWLWc+k< zo34Znl}@eU*{%z+8jcNF$RivN>zeEXOTyk%gQA`k7o_7P=f3NZj+dif6YVf}d}vKA z$Xfc&&*3vcp7+QgWYat@%0^~%J0UR*rQAVTl|Ae7rLrJVWN&Aoy0?a#QsEspPp7Ui zRp9+R^F_ysP!~)vWAGKnm+axt{OM|9Rh<1HGnNhbq~tgFyGS0hWBr<^`mML-@$HZX zJsmFj8u!eSwy=q)q6192Cxya0A)G(61;i8J)#H=4Y~+K>J&8SnX=awBx8*TIE8KrI zbm5llewfXp(?X`}a^0Xj>(&%irj_ZA8>5;=p z{Ra!E~#&+96wKL!Ppu1&Wm6JvM2TmUJvdJX_-@YH>ed=Ns>EWt;Uf4H}la)8= zx*FcIh&R(QO5<^T#&(J@|1Sshhq-u@-JgkMrH1qZBIdu*jgKfyFKn zmAcSYDhIq`jO^1PJ_Sid4GcE^SaHc^qmTaK-zl+wYQ8;xd_Rh_Y^QRZJJYvKorRAR zPJD!WGVm?4#Y_DM87v7pdrQ!BBsppOG0gpCR3-^2%$;qnR)Aa}RnOa^xwNtU;3@3) zftW47+?#Ah?}*zxQMhCECRi5L z>moq-YHoIEZppL9*SW>R?=|J%G#YsYF+xw-N8`hM^(Vz4INXz9&hg3?oZ*fi-n!nI#oKFLX8Lg$dq zRaSXfxlsSNRL(GiJx5=05fQxi?m*D4t( zn`B__Pe^HE3y9c`R>Xw$nwyyYWEXI!);W#OB<7~GD|YFgnz9-ZrrRgIuRyv0P6%LIiv6yNlJ0 zVx010GVs?ZTO9}?_Ck9qaj|8WH!hq%qm3}EZ;+jBY!zx>yi7Sp^DM<$gyfn%j;6lE z$Z!=i*KbO5S(t>4(qs>v_>TmF9+{}2l&q!Wu{>e&L!1Md>Xk>gPel2|!R;H0QDNf* zmb81e;C|pEo6(;5VyuaGpqif*+aH%){aA~I2D3VjksW2}CSJ^+CaIQKW1e*OlsNL#@VyrON*&N%v^f)#@3 z?>Qe-$5J96FIt>zFa_B-zLH~nrsY3SM0Q{;|1qD7cD(!hhJ2ub=xF9aP}ethC>7Qx zq;7!2q_whfB@SjaUy2Z`!7%ah_3-IMUPfooMlw4KbaxOZJG{ReK`VY|>eErhycEy( z9H?}e@5|_0oYcJ0vd0X#PYrr@PRKpEnR(r)xxe2s9wgh{40t!AgwPUyjU7b=UG4X%9NBWk=sT}nN&Z(>I`9a|qDl2oq_T)kBFioD1m9~sq z3`65FWKaHk;kQ3>d$-@b%U;Gfyv=A^d!mIJQcwHXmB^;fNWuSefTK?E)8{03To-3$ zbenL*e!r-JV=eOw#vlvvju*6YC23?}6=>C>+y6CFxRl=*)eK9DKSk9azr6#gS5J{iP@+ca1M7p@IiA(q7kR{41tBu#% z|M)Fb5KMw8pzV5?Kp*M6m)KmRpcxS-S5;V&l+$48G8@kj2X1k4Um>!}9LACLW)T0u zMB#_U@rs#nimCYr$YZ!MH902lNaiU<{4e5x27 z766^F0>9D}FjVGtX4UH`(!Q@pE2*-vNAA0N7a8P9n6u9z%lH^SA^CPlGIdDW!^Hb{ zD`Vyi9w;ZO8%`?SeZ#sjXE)T7;4HS40Y1R{+8ly1@+7Jc8MtS#S~#m?B~>psy5sLG z3H=jnKPQFaKGvy@l=?LB)bfvXEVuz;Ed5#E0k4eqY8w!~HKS6^;$ULJ7; zM+#YcL4qZx!xK$O>wA_YAf@ECH^^D$`_%7LVW?^|mkNDU;k)5nnHQai-CymcRJQLK z2O_M5s4@=2#vyV1e3FjadjX2F-c5d{NKlRRJE5juPr7t!Ny4-%DY2&?@ulzgLwy-i zl7hTKVX2RuDOZoli5^sU4mhO`!aaP{hubX;%SdLKRd_(P=>m0Tb-F3Q%V*L0hbEYi z_t$4>H8sNI$7{mBPztw^MJyS8>{%K(s0jnt?EDDcz}n954oIrRN-{|0;;J)&QE&r! z=oJ=kIi*<*B@wDCwRjK+i(Q%$bkI;qv$Nzk)3D>a>3XIHPd~mRjM-iNcPL|0LB>J- zyuk^tmKRQ#b-Sc_JGK7RU~5$$@kq**w@IE}p(MNAdvZr);Y=-k10l@b{!6=4p}(|u zWlO16ZgKY9wCcTY(|tIIjF-aB`s#i{XDS>bG9WRn_;hX8XMISjUizxo%CToPQLo_l zI+bo?R_N^4C(qv^g-ZESXdXf#8uK5W-}_LA{`e@O)d3l0kt7LS^xw`C!r&>rX(!L1 z#Nr;@QjoT)g8{6tM0sE&AF6Bl{PFGD7)AQr)0);LwBmeZe*!+Cb-tRi<#jK(%NRnR zJ&j+K-_ZKmc$V?C;MPviy}G*dFB1`1NBij8G3ji+8uS#}&sPku}E-~;PgOphEM0dhH|UPq<| z-{rG`Obr!&69L7jQM}*aAj(sMc%z-(vYzHNGQI;$z@&LPAx58zf?KpSO}D>XTVMO& z)UWu5m8kdty841|8Kil^*vY=0UNYzArUG^T) z>26A@8ubZiX~&2vbXwfrMdmQ$!QWXgba?EYrn?*w=y6kv!K)n%uQ&F})zIpc+asgX z41<3WBHdA&xFLPXbsC3`_!~dgoQ1yH@ZF=?@I)rbyK^rEqCBr{ys|9ZU+O2elp;Bz z?VrY}{z;eq`lJzx@rd}}xiaW)R;MZ3SY&5Miv8y6;XuGC9v1^c0FhG2S;~=-brEwK z&Md<@k0k}?VO_+RA_})IuKJf4TzvLgE|tE(fh+qi%1)~^FK|z4!?6j@Obv`@Y$CSm zgwK-60l`8HKrea&vd&#>q!=eM@xbxf zGNnfQkfwfB)c{>rbji9>?7Z=ahF#SKw8O=Ye*UwJS+ZpTkv#CUsod4mp^f5C} z12DtD&Uy|;|02B^5JiprjQ>B!=U))g8w-Kiq{(!KSzgz5$^kA-06WWH2nN#I4F_?j zjS>NG0-=F8*=|z?fWpT>o4GFm&2Z7VA2(a^5Hn3oXfWNJ;Y2IYp+|rd1TKNE5}?6i z1WbshEktPW$gnv6b`ldq^ zw}Y^bbPzr5^ylZBz&VF5g0Ghbfr5Y#k{v|n!X2>p3@;t{vjal1YxBbq07Cx@DX~Jl!(nQL};QFR^nYnK&1*mcIOgN zFa)SThbZb~MFXX%EdR6vL1us8?vcC%ylIMNrs(281Lvbc3V4F^ViEHJ^3{Sx?-BUQTk812kFNYTVDMinR=xo6HX@+!T)Q#Jy@52qD;$7V zF5lXiF=)on9!50dr3d>ZIX~n6Z6rqq5!e4dF3$$1EBl<*GvFx$x{85u_fo~N1Yqzf zpAWjXy&$ZxRKNi||GKrjp~xu)kd_1V?ByX4o$;?lx;_@PyjwU*L^Oc6EY8|;$qGCR z0Mj9e0!hw1h6c;^Q!cFmZuI~pT^`PB*=Xj`zT0RZ0^@G%Ssxq&tYVi~c?uAWD8+N_ z)+i7bW)Zp~Pc`}37&&- +cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" -cd "$SAVED" >&- +cd "$SAVED" >/dev/null CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -114,6 +109,7 @@ fi if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` diff --git a/gradlew.bat b/gradlew.bat index 8a0b282aa..72d362daf 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,90 +1,90 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From 005ae7cfae24500b5886425abf6128666bc0b759 Mon Sep 17 00:00:00 2001 From: Ryland Degnan Date: Mon, 21 Mar 2016 14:03:06 -0700 Subject: [PATCH 074/105] Consolidate java implementations --- build.gradle | 6 +- reactivesocket-aeron-client/build.gradle | 3 - reactivesocket-aeron-core/build.gradle | 0 .../aeron/internal/TimedOutException.java | 9 - reactivesocket-aeron-examples/build.gradle | 4 - reactivesocket-aeron-server/build.gradle | 3 - reactivesocket-aeron-tests/build.gradle | 4 - .../src/main/java/package-info.java | 0 reactivesocket-aeron/build.gradle | 4 + .../aeron/example/MediaDriver.java | 3 - .../aeron/example/fireandforget/Fire.java | 0 .../aeron/example/fireandforget/Forget.java | 0 .../aeron/example/requestreply/Ping.java | 3 - .../aeron/example/requestreply/Pong.java | 3 - .../resources/simplelogger.properties | 0 .../client/AeronClientDuplexConnection.java | 15 + .../AeronClientDuplexConnectionFactory.java | 15 + .../aeron/client/ClientAeronManager.java | 0 .../aeron/client/FrameHolder.java | 0 .../aeron/client/PollingAction.java | 15 + .../aeron/internal/AeronUtil.java | 0 .../aeron/internal/Constants.java | 0 .../aeron/internal/Loggable.java | 0 .../aeron/internal/MessageType.java | 0 .../aeron/internal/NotConnectedException.java | 0 .../aeron/internal/TimedOutException.java | 24 ++ .../server/AeronServerDuplexConnection.java | 0 .../server/ReactiveSocketAeronServer.java | 0 .../aeron/server/ServerAeronManager.java | 0 .../aeron/server/ServerSubscription.java | 0 .../server/TimerWheelFairLeaseGovernor.java | 15 + .../rx/ReactiveSocketAeronScheduler.java | 0 .../aeron/client/PollingActionPerf.java | 15 + .../jmh/InputWithIncrementingInteger.java | 15 + .../aeron/jmh/LatchedObserver.java | 15 + .../real_logic/aeron/DummySubscription.java | 15 + .../io/reactivesocket/aeron/TestUtil.java | 15 + .../aeron/client/ReactiveSocketAeronTest.java | 0 .../aeron/internal/AeronUtilTest.java | 15 + .../rx/ReactiveSocketAeronSchedulerTest.java | 15 + .../test/resources/simplelogger.properties | 0 reactivesocket-jsr-356/build.gradle | 13 + .../websocket/WebSocketDuplexConnection.java | 98 +++++ .../client/ReactiveSocketWebSocketClient.java | 56 +++ .../server/ReactiveSocketWebSocketServer.java | 98 +++++ .../javax/websocket/ClientServerEndpoint.java | 76 ++++ .../javax/websocket/ClientServerTest.java | 165 ++++++++ .../reactivesocket/javax/websocket/Ping.java | 113 +++++ .../reactivesocket/javax/websocket/Pong.java | 67 +++ .../javax/websocket/PongEndpoint.java | 103 +++++ .../javax/websocket/TestUtil.java | 122 ++++++ .../test/resources/simplelogger.properties | 35 ++ reactivesocket-netty/build.gradle | 3 + .../netty/MutableDirectByteBuf.java | 389 ++++++++++++++++++ .../ClientWebSocketDuplexConnection.java | 153 +++++++ .../client/ReactiveSocketClientHandler.java | 69 ++++ .../server/ReactiveSocketServerHandler.java | 76 ++++ .../ServerWebSocketDuplexConnection.java | 102 +++++ .../netty/websocket/ClientServerTest.java | 215 ++++++++++ .../reactivesocket/netty/websocket/Ping.java | 107 +++++ .../reactivesocket/netty/websocket/Pong.java | 177 ++++++++ .../netty/websocket/TestUtil.java | 160 +++++++ .../test/resources/simplelogger.properties | 35 ++ settings.gradle | 8 +- 64 files changed, 2629 insertions(+), 42 deletions(-) delete mode 100644 reactivesocket-aeron-client/build.gradle delete mode 100644 reactivesocket-aeron-core/build.gradle delete mode 100644 reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/TimedOutException.java delete mode 100644 reactivesocket-aeron-examples/build.gradle delete mode 100644 reactivesocket-aeron-server/build.gradle delete mode 100644 reactivesocket-aeron-tests/build.gradle delete mode 100644 reactivesocket-aeron-tests/src/main/java/package-info.java create mode 100644 reactivesocket-aeron/build.gradle rename {reactivesocket-aeron-examples/src/main => reactivesocket-aeron/src/examples}/java/io/reactivesocket/aeron/example/MediaDriver.java (97%) rename {reactivesocket-aeron-examples/src/main => reactivesocket-aeron/src/examples}/java/io/reactivesocket/aeron/example/fireandforget/Fire.java (100%) rename {reactivesocket-aeron-examples/src/main => reactivesocket-aeron/src/examples}/java/io/reactivesocket/aeron/example/fireandforget/Forget.java (100%) rename {reactivesocket-aeron-examples/src/main => reactivesocket-aeron/src/examples}/java/io/reactivesocket/aeron/example/requestreply/Ping.java (99%) rename {reactivesocket-aeron-examples/src/main => reactivesocket-aeron/src/examples}/java/io/reactivesocket/aeron/example/requestreply/Pong.java (98%) rename {reactivesocket-aeron-examples/src/main => reactivesocket-aeron/src/examples}/resources/simplelogger.properties (100%) rename {reactivesocket-aeron-client => reactivesocket-aeron}/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java (85%) rename {reactivesocket-aeron-client => reactivesocket-aeron}/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java (95%) rename {reactivesocket-aeron-client => reactivesocket-aeron}/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java (100%) rename {reactivesocket-aeron-client => reactivesocket-aeron}/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java (100%) rename {reactivesocket-aeron-client => reactivesocket-aeron}/src/main/java/io/reactivesocket/aeron/client/PollingAction.java (72%) rename {reactivesocket-aeron-core => reactivesocket-aeron}/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java (100%) rename {reactivesocket-aeron-core => reactivesocket-aeron}/src/main/java/io/reactivesocket/aeron/internal/Constants.java (100%) rename {reactivesocket-aeron-core => reactivesocket-aeron}/src/main/java/io/reactivesocket/aeron/internal/Loggable.java (100%) rename {reactivesocket-aeron-core => reactivesocket-aeron}/src/main/java/io/reactivesocket/aeron/internal/MessageType.java (100%) rename {reactivesocket-aeron-core => reactivesocket-aeron}/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java (100%) create mode 100644 reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/TimedOutException.java rename {reactivesocket-aeron-server => reactivesocket-aeron}/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java (100%) rename {reactivesocket-aeron-server => reactivesocket-aeron}/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java (100%) rename {reactivesocket-aeron-server => reactivesocket-aeron}/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java (100%) rename {reactivesocket-aeron-server => reactivesocket-aeron}/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java (100%) rename {reactivesocket-aeron-server => reactivesocket-aeron}/src/main/java/io/reactivesocket/aeron/server/TimerWheelFairLeaseGovernor.java (85%) rename {reactivesocket-aeron-server => reactivesocket-aeron}/src/main/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronScheduler.java (100%) rename {reactivesocket-aeron-tests => reactivesocket-aeron}/src/perf/java/io/reactivesocket/aeron/client/PollingActionPerf.java (79%) rename {reactivesocket-aeron-tests => reactivesocket-aeron}/src/perf/java/io/reactivesocket/aeron/jmh/InputWithIncrementingInteger.java (83%) rename {reactivesocket-aeron-tests => reactivesocket-aeron}/src/perf/java/io/reactivesocket/aeron/jmh/LatchedObserver.java (66%) rename {reactivesocket-aeron-tests => reactivesocket-aeron}/src/perf/java/uk/co/real_logic/aeron/DummySubscription.java (67%) rename {reactivesocket-aeron-tests => reactivesocket-aeron}/src/test/java/io/reactivesocket/aeron/TestUtil.java (84%) rename {reactivesocket-aeron-tests => reactivesocket-aeron}/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java (100%) rename {reactivesocket-aeron-core => reactivesocket-aeron}/src/test/java/io/reactivesocket/aeron/internal/AeronUtilTest.java (69%) rename {reactivesocket-aeron-tests => reactivesocket-aeron}/src/test/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronSchedulerTest.java (85%) rename {reactivesocket-aeron-tests => reactivesocket-aeron}/src/test/resources/simplelogger.properties (100%) create mode 100644 reactivesocket-jsr-356/build.gradle create mode 100644 reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/WebSocketDuplexConnection.java create mode 100644 reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/ReactiveSocketWebSocketClient.java create mode 100644 reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/server/ReactiveSocketWebSocketServer.java create mode 100644 reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerEndpoint.java create mode 100644 reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerTest.java create mode 100644 reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/Ping.java create mode 100644 reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/Pong.java create mode 100644 reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/PongEndpoint.java create mode 100644 reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/TestUtil.java create mode 100644 reactivesocket-jsr-356/src/test/resources/simplelogger.properties create mode 100644 reactivesocket-netty/build.gradle create mode 100644 reactivesocket-netty/src/main/java/io/reactivesocket/netty/MutableDirectByteBuf.java create mode 100644 reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java create mode 100644 reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ReactiveSocketClientHandler.java create mode 100644 reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ReactiveSocketServerHandler.java create mode 100644 reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ServerWebSocketDuplexConnection.java create mode 100644 reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java create mode 100644 reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Ping.java create mode 100644 reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Pong.java create mode 100644 reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/TestUtil.java create mode 100644 reactivesocket-netty/src/test/resources/simplelogger.properties diff --git a/build.gradle b/build.gradle index 43dd32830..3f254471f 100644 --- a/build.gradle +++ b/build.gradle @@ -17,13 +17,11 @@ subprojects { compile 'io.reactivex:rxjava:1.1.1' compile 'io.reactivex:rxjava-reactive-streams:1.0.1' compile 'io.reactivesocket:reactivesocket:0.0.2' - compile 'uk.co.real-logic:Agrona:0.4.8' - compile 'uk.co.real-logic:aeron-all:0.2.2' compile 'org.hdrhistogram:HdrHistogram:2.1.7' compile 'org.slf4j:slf4j-api:1.7.12' - testCompile 'junit:junit-dep:4.10' + testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.8.5' - testCompile 'org.slf4j:slf4j-simple:1.7.12' + testRuntime 'org.slf4j:slf4j-simple:1.7.12' } // support for snapshot/final releases via versioned branch names like 1.x diff --git a/reactivesocket-aeron-client/build.gradle b/reactivesocket-aeron-client/build.gradle deleted file mode 100644 index 72fb47b09..000000000 --- a/reactivesocket-aeron-client/build.gradle +++ /dev/null @@ -1,3 +0,0 @@ -dependencies { - compile project(':reactivesocket-aeron-core') -} diff --git a/reactivesocket-aeron-core/build.gradle b/reactivesocket-aeron-core/build.gradle deleted file mode 100644 index e69de29bb..000000000 diff --git a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/TimedOutException.java b/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/TimedOutException.java deleted file mode 100644 index 28a859fe5..000000000 --- a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/TimedOutException.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.reactivesocket.aeron.internal; - -public class TimedOutException extends RuntimeException { - - @Override - public synchronized Throwable fillInStackTrace() { - return this; - } -} diff --git a/reactivesocket-aeron-examples/build.gradle b/reactivesocket-aeron-examples/build.gradle deleted file mode 100644 index 421e59c8d..000000000 --- a/reactivesocket-aeron-examples/build.gradle +++ /dev/null @@ -1,4 +0,0 @@ -dependencies { - compile project(':reactivesocket-aeron-client') - compile project(':reactivesocket-aeron-server') -} diff --git a/reactivesocket-aeron-server/build.gradle b/reactivesocket-aeron-server/build.gradle deleted file mode 100644 index 72fb47b09..000000000 --- a/reactivesocket-aeron-server/build.gradle +++ /dev/null @@ -1,3 +0,0 @@ -dependencies { - compile project(':reactivesocket-aeron-core') -} diff --git a/reactivesocket-aeron-tests/build.gradle b/reactivesocket-aeron-tests/build.gradle deleted file mode 100644 index 421e59c8d..000000000 --- a/reactivesocket-aeron-tests/build.gradle +++ /dev/null @@ -1,4 +0,0 @@ -dependencies { - compile project(':reactivesocket-aeron-client') - compile project(':reactivesocket-aeron-server') -} diff --git a/reactivesocket-aeron-tests/src/main/java/package-info.java b/reactivesocket-aeron-tests/src/main/java/package-info.java deleted file mode 100644 index e69de29bb..000000000 diff --git a/reactivesocket-aeron/build.gradle b/reactivesocket-aeron/build.gradle new file mode 100644 index 000000000..3890fdd4c --- /dev/null +++ b/reactivesocket-aeron/build.gradle @@ -0,0 +1,4 @@ +dependencies { + compile 'uk.co.real-logic:Agrona:0.4.8' + compile 'uk.co.real-logic:aeron-all:0.2.2' +} \ No newline at end of file diff --git a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/MediaDriver.java b/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/MediaDriver.java similarity index 97% rename from reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/MediaDriver.java rename to reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/MediaDriver.java index 9dac34fc0..1fd41739b 100644 --- a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/MediaDriver.java +++ b/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/MediaDriver.java @@ -19,9 +19,6 @@ import uk.co.real_logic.agrona.concurrent.BackoffIdleStrategy; import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; -/** - * Created by rroeser on 8/16/15. - */ public class MediaDriver { public static void main(String... args) { ThreadingMode threadingMode = ThreadingMode.SHARED; diff --git a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/fireandforget/Fire.java b/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/fireandforget/Fire.java similarity index 100% rename from reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/fireandforget/Fire.java rename to reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/fireandforget/Fire.java diff --git a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/fireandforget/Forget.java b/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/fireandforget/Forget.java similarity index 100% rename from reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/fireandforget/Forget.java rename to reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/fireandforget/Forget.java diff --git a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/requestreply/Ping.java b/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/requestreply/Ping.java similarity index 99% rename from reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/requestreply/Ping.java rename to reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/requestreply/Ping.java index 6af9f1f0c..38281a3ab 100644 --- a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/requestreply/Ping.java +++ b/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/requestreply/Ping.java @@ -34,9 +34,6 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -/** - * Created by rroeser on 8/16/15. - */ public class Ping { public static void main(String... args) throws Exception { diff --git a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/requestreply/Pong.java b/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/requestreply/Pong.java similarity index 98% rename from reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/requestreply/Pong.java rename to reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/requestreply/Pong.java index eac0c5f9f..25a7dfc1f 100644 --- a/reactivesocket-aeron-examples/src/main/java/io/reactivesocket/aeron/example/requestreply/Pong.java +++ b/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/requestreply/Pong.java @@ -27,9 +27,6 @@ import java.nio.ByteBuffer; import java.util.Random; -/** - * Created by rroeser on 8/16/15. - */ public class Pong { public static void main(String... args) { diff --git a/reactivesocket-aeron-examples/src/main/resources/simplelogger.properties b/reactivesocket-aeron/src/examples/resources/simplelogger.properties similarity index 100% rename from reactivesocket-aeron-examples/src/main/resources/simplelogger.properties rename to reactivesocket-aeron/src/examples/resources/simplelogger.properties diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java similarity index 85% rename from reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java rename to reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java index fb5cf5e0e..1c1288a21 100644 --- a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java @@ -1,3 +1,18 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.client; import io.reactivesocket.DuplexConnection; diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java similarity index 95% rename from reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java rename to reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java index 3aaf12069..00e54eb6e 100644 --- a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java @@ -1,3 +1,18 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.client; import io.reactivesocket.Frame; diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java similarity index 100% rename from reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java rename to reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java similarity index 100% rename from reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java rename to reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java diff --git a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/PollingAction.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/PollingAction.java similarity index 72% rename from reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/PollingAction.java rename to reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/PollingAction.java index 6136511e8..4b89a08c4 100644 --- a/reactivesocket-aeron-client/src/main/java/io/reactivesocket/aeron/client/PollingAction.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/PollingAction.java @@ -1,3 +1,18 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.client; import io.reactivesocket.aeron.internal.Loggable; diff --git a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java similarity index 100% rename from reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java rename to reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java diff --git a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Constants.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/Constants.java similarity index 100% rename from reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Constants.java rename to reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/Constants.java diff --git a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Loggable.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/Loggable.java similarity index 100% rename from reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/Loggable.java rename to reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/Loggable.java diff --git a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/MessageType.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/MessageType.java similarity index 100% rename from reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/MessageType.java rename to reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/MessageType.java diff --git a/reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java similarity index 100% rename from reactivesocket-aeron-core/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java rename to reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/TimedOutException.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/TimedOutException.java new file mode 100644 index 000000000..67d2fbc00 --- /dev/null +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/TimedOutException.java @@ -0,0 +1,24 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.aeron.internal; + +public class TimedOutException extends RuntimeException { + + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } +} diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java similarity index 100% rename from reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java rename to reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java similarity index 100% rename from reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java rename to reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java similarity index 100% rename from reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java rename to reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java similarity index 100% rename from reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java rename to reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/TimerWheelFairLeaseGovernor.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/TimerWheelFairLeaseGovernor.java similarity index 85% rename from reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/TimerWheelFairLeaseGovernor.java rename to reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/TimerWheelFairLeaseGovernor.java index b10d4a273..a38758add 100644 --- a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/TimerWheelFairLeaseGovernor.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/TimerWheelFairLeaseGovernor.java @@ -1,3 +1,18 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.server; import io.reactivesocket.Frame; diff --git a/reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronScheduler.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronScheduler.java similarity index 100% rename from reactivesocket-aeron-server/src/main/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronScheduler.java rename to reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronScheduler.java diff --git a/reactivesocket-aeron-tests/src/perf/java/io/reactivesocket/aeron/client/PollingActionPerf.java b/reactivesocket-aeron/src/perf/java/io/reactivesocket/aeron/client/PollingActionPerf.java similarity index 79% rename from reactivesocket-aeron-tests/src/perf/java/io/reactivesocket/aeron/client/PollingActionPerf.java rename to reactivesocket-aeron/src/perf/java/io/reactivesocket/aeron/client/PollingActionPerf.java index 9b65ee478..661d08358 100644 --- a/reactivesocket-aeron-tests/src/perf/java/io/reactivesocket/aeron/client/PollingActionPerf.java +++ b/reactivesocket-aeron/src/perf/java/io/reactivesocket/aeron/client/PollingActionPerf.java @@ -1,3 +1,18 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.client; diff --git a/reactivesocket-aeron-tests/src/perf/java/io/reactivesocket/aeron/jmh/InputWithIncrementingInteger.java b/reactivesocket-aeron/src/perf/java/io/reactivesocket/aeron/jmh/InputWithIncrementingInteger.java similarity index 83% rename from reactivesocket-aeron-tests/src/perf/java/io/reactivesocket/aeron/jmh/InputWithIncrementingInteger.java rename to reactivesocket-aeron/src/perf/java/io/reactivesocket/aeron/jmh/InputWithIncrementingInteger.java index a6b8da055..bd7cfbf04 100644 --- a/reactivesocket-aeron-tests/src/perf/java/io/reactivesocket/aeron/jmh/InputWithIncrementingInteger.java +++ b/reactivesocket-aeron/src/perf/java/io/reactivesocket/aeron/jmh/InputWithIncrementingInteger.java @@ -1,3 +1,18 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.jmh; /** * Copyright 2014 Netflix, Inc. diff --git a/reactivesocket-aeron-tests/src/perf/java/io/reactivesocket/aeron/jmh/LatchedObserver.java b/reactivesocket-aeron/src/perf/java/io/reactivesocket/aeron/jmh/LatchedObserver.java similarity index 66% rename from reactivesocket-aeron-tests/src/perf/java/io/reactivesocket/aeron/jmh/LatchedObserver.java rename to reactivesocket-aeron/src/perf/java/io/reactivesocket/aeron/jmh/LatchedObserver.java index 18b262cc0..a2f89c239 100644 --- a/reactivesocket-aeron-tests/src/perf/java/io/reactivesocket/aeron/jmh/LatchedObserver.java +++ b/reactivesocket-aeron/src/perf/java/io/reactivesocket/aeron/jmh/LatchedObserver.java @@ -1,3 +1,18 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.jmh; /** diff --git a/reactivesocket-aeron-tests/src/perf/java/uk/co/real_logic/aeron/DummySubscription.java b/reactivesocket-aeron/src/perf/java/uk/co/real_logic/aeron/DummySubscription.java similarity index 67% rename from reactivesocket-aeron-tests/src/perf/java/uk/co/real_logic/aeron/DummySubscription.java rename to reactivesocket-aeron/src/perf/java/uk/co/real_logic/aeron/DummySubscription.java index 2152128b8..38b0bfe34 100644 --- a/reactivesocket-aeron-tests/src/perf/java/uk/co/real_logic/aeron/DummySubscription.java +++ b/reactivesocket-aeron/src/perf/java/uk/co/real_logic/aeron/DummySubscription.java @@ -1,3 +1,18 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package uk.co.real_logic.aeron; diff --git a/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/TestUtil.java b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/TestUtil.java similarity index 84% rename from reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/TestUtil.java rename to reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/TestUtil.java index 1df48fb11..3328c38f4 100644 --- a/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/TestUtil.java +++ b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/TestUtil.java @@ -1,3 +1,18 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron; import io.reactivesocket.Frame; diff --git a/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java similarity index 100% rename from reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java rename to reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java diff --git a/reactivesocket-aeron-core/src/test/java/io/reactivesocket/aeron/internal/AeronUtilTest.java b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/internal/AeronUtilTest.java similarity index 69% rename from reactivesocket-aeron-core/src/test/java/io/reactivesocket/aeron/internal/AeronUtilTest.java rename to reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/internal/AeronUtilTest.java index 021c477cd..a61c093ab 100644 --- a/reactivesocket-aeron-core/src/test/java/io/reactivesocket/aeron/internal/AeronUtilTest.java +++ b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/internal/AeronUtilTest.java @@ -1,3 +1,18 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.internal; import org.junit.Test; diff --git a/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronSchedulerTest.java b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronSchedulerTest.java similarity index 85% rename from reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronSchedulerTest.java rename to reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronSchedulerTest.java index 9f10028d1..d5cd32f2a 100644 --- a/reactivesocket-aeron-tests/src/test/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronSchedulerTest.java +++ b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronSchedulerTest.java @@ -1,3 +1,18 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.server.rx; import org.junit.Assert; diff --git a/reactivesocket-aeron-tests/src/test/resources/simplelogger.properties b/reactivesocket-aeron/src/test/resources/simplelogger.properties similarity index 100% rename from reactivesocket-aeron-tests/src/test/resources/simplelogger.properties rename to reactivesocket-aeron/src/test/resources/simplelogger.properties diff --git a/reactivesocket-jsr-356/build.gradle b/reactivesocket-jsr-356/build.gradle new file mode 100644 index 000000000..31d37a5b5 --- /dev/null +++ b/reactivesocket-jsr-356/build.gradle @@ -0,0 +1,13 @@ +buildscript { + repositories { maven { url 'http://repo.spring.io/plugins-release' } } + dependencies { classpath 'org.springframework.build.gradle:propdeps-plugin:0.0.7' } +} + +apply plugin: 'propdeps' + +dependencies { + provided 'javax.websocket:javax.websocket-api:1.1' + testCompile 'org.glassfish.tyrus:tyrus-client:1.12' + testCompile 'org.glassfish.tyrus:tyrus-server:1.12' + testCompile 'org.glassfish.tyrus:tyrus-container-grizzly-server:1.12' +} \ No newline at end of file diff --git a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/WebSocketDuplexConnection.java b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/WebSocketDuplexConnection.java new file mode 100644 index 000000000..8de736588 --- /dev/null +++ b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/WebSocketDuplexConnection.java @@ -0,0 +1,98 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.javax.websocket; + +import io.reactivesocket.DuplexConnection; +import io.reactivesocket.Frame; +import io.reactivesocket.rx.Completable; +import io.reactivesocket.rx.Observable; +import io.reactivesocket.rx.Observer; +import org.reactivestreams.Publisher; +import rx.RxReactiveStreams; +import rx.Subscriber; + +import javax.websocket.*; +import java.io.IOException; +import java.util.concurrent.CopyOnWriteArrayList; + +public class WebSocketDuplexConnection implements DuplexConnection { + private Session session; + + private CopyOnWriteArrayList> observers; + + private WebSocketDuplexConnection(Session session, rx.Observable input) { + this.session = session; + this.observers = new CopyOnWriteArrayList<>(); + input.subscribe(new Subscriber() { + @Override + public void onNext(Frame frame) { + observers.forEach(o -> o.onNext(frame)); + } + + @Override + public void onError(Throwable e) { + observers.forEach(o -> o.onError(e)); + } + + @Override + public void onCompleted() { + observers.forEach(Observer::onComplete); + } + }); + } + + public static WebSocketDuplexConnection create(Session session, rx.Observable input) { + return new WebSocketDuplexConnection(session, input); + } + + @Override + public Observable getInput() { + return new Observable() { + @Override + public void subscribe(Observer o) { + observers.add(o); + + o.onSubscribe(() -> + observers.removeIf(s -> s == o) + ); + } + }; + } + + @Override + public void addOutput(Publisher o, Completable callback) { + rx.Observable sent = RxReactiveStreams.toObservable(o).concatMap(frame -> + rx.Observable.create(subscriber -> { + session.getAsyncRemote().sendBinary(frame.getByteBuffer(), result -> { + if (result.isOK()) { + subscriber.onCompleted(); + } else { + subscriber.onError(result.getException()); + } + }); + }) + ); + + sent.doOnCompleted(callback::success) + .doOnError(callback::error) + .subscribe(); + } + + @Override + public void close() throws IOException { + session.close(); + } +} diff --git a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/ReactiveSocketWebSocketClient.java b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/ReactiveSocketWebSocketClient.java new file mode 100644 index 000000000..4e7ad3ca3 --- /dev/null +++ b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/ReactiveSocketWebSocketClient.java @@ -0,0 +1,56 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.javax.websocket.client; + +import io.reactivesocket.Frame; +import rx.Observable; +import rx.subjects.PublishSubject; + +import javax.websocket.*; +import java.nio.ByteBuffer; + +public class ReactiveSocketWebSocketClient extends Endpoint { + private final PublishSubject input; + + public ReactiveSocketWebSocketClient() { + this.input = PublishSubject.create(); + } + + public Observable getInput() { + return input; + } + + @Override + public void onOpen(Session session, EndpointConfig config) { + session.addMessageHandler(new MessageHandler.Whole() { + @Override + public void onMessage(ByteBuffer message) { + Frame frame = Frame.from(message); + input.onNext(frame); + } + }); + } + + @Override + public void onClose(Session session, CloseReason closeReason) { + input.onCompleted(); + } + + @Override + public void onError(Session session, Throwable thr) { + input.onError(thr); + } +} diff --git a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/server/ReactiveSocketWebSocketServer.java b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/server/ReactiveSocketWebSocketServer.java new file mode 100644 index 000000000..b16630899 --- /dev/null +++ b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/server/ReactiveSocketWebSocketServer.java @@ -0,0 +1,98 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.javax.websocket.server; + +import io.reactivesocket.ConnectionSetupHandler; +import io.reactivesocket.Frame; +import io.reactivesocket.LeaseGovernor; +import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.rx.Completable; +import io.reactivesocket.javax.websocket.WebSocketDuplexConnection; +import rx.subjects.PublishSubject; +import uk.co.real_logic.agrona.LangUtil; + +import javax.websocket.*; +import java.nio.ByteBuffer; +import java.util.concurrent.ConcurrentHashMap; + +public class ReactiveSocketWebSocketServer extends Endpoint { + private final PublishSubject input; + private final ConcurrentHashMap reactiveSockets; + private final ConnectionSetupHandler setupHandler; + private final LeaseGovernor leaseGovernor; + + protected ReactiveSocketWebSocketServer(ConnectionSetupHandler setupHandler, LeaseGovernor leaseGovernor) { + this.input = PublishSubject.create(); + this.reactiveSockets = new ConcurrentHashMap<>(); + this.setupHandler = setupHandler; + this.leaseGovernor = leaseGovernor; + } + + protected ReactiveSocketWebSocketServer(ConnectionSetupHandler setupHandler) { + this(setupHandler, LeaseGovernor.UNLIMITED_LEASE_GOVERNOR); + } + + @Override + public void onOpen(Session session, EndpointConfig config) { + session.addMessageHandler(new MessageHandler.Whole() { + @Override + public void onMessage(ByteBuffer message) { + Frame frame = Frame.from(message); + input.onNext(frame); + } + }); + + WebSocketDuplexConnection webSocketDuplexConnection = WebSocketDuplexConnection.create(session, input); + + final ReactiveSocket reactiveSocket = reactiveSockets.computeIfAbsent(session.getId(), id -> + ReactiveSocket.fromServerConnection( + webSocketDuplexConnection, + setupHandler, + leaseGovernor, + t -> t.printStackTrace() + ) + ); + + reactiveSocket.start(new Completable() { + @Override + public void success() { + } + + @Override + public void error(Throwable e) { + e.printStackTrace(); + } + }); + } + + @Override + public void onClose(Session session, CloseReason closeReason) { + input.onCompleted(); + try { + ReactiveSocket reactiveSocket = reactiveSockets.remove(session.getId()); + if (reactiveSocket != null) { + reactiveSocket.close(); + } + } catch (Exception e) { + LangUtil.rethrowUnchecked(e); + } + } + + @Override + public void onError(Session session, Throwable thr) { + input.onError(thr); + } +} diff --git a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerEndpoint.java b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerEndpoint.java new file mode 100644 index 000000000..f1b56ecde --- /dev/null +++ b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerEndpoint.java @@ -0,0 +1,76 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.javax.websocket; + +import io.reactivesocket.Payload; +import io.reactivesocket.RequestHandler; +import io.reactivesocket.javax.websocket.server.ReactiveSocketWebSocketServer; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import rx.Observable; +import rx.RxReactiveStreams; + +public class ClientServerEndpoint extends ReactiveSocketWebSocketServer { + public ClientServerEndpoint() { + super(setupPayload -> new RequestHandler() { + @Override + public Publisher handleRequestResponse(Payload payload) { + return s -> { + //System.out.println("Handling request/response payload => " + s.toString()); + Payload response = TestUtil.utf8EncodedPayload("hello world", "metadata"); + s.onNext(response); + s.onComplete(); + }; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + Payload response = TestUtil.utf8EncodedPayload("hello world", "metadata"); + + return RxReactiveStreams + .toPublisher(Observable + .range(1, 10) + .map(i -> response)); + } + + @Override + public Publisher handleSubscription(Payload payload) { + Payload response = TestUtil.utf8EncodedPayload("hello world", "metadata"); + + return RxReactiveStreams + .toPublisher(Observable + .range(1, 10) + .map(i -> response) + .repeat()); + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return Subscriber::onComplete; + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher inputs) { + return null; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + }); + } +} diff --git a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerTest.java b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerTest.java new file mode 100644 index 000000000..215b609fd --- /dev/null +++ b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerTest.java @@ -0,0 +1,165 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.javax.websocket; + +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.Payload; +import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.javax.websocket.client.ReactiveSocketWebSocketClient; +import org.glassfish.tyrus.client.ClientManager; +import org.glassfish.tyrus.server.Server; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import rx.Observable; +import rx.RxReactiveStreams; +import rx.observers.TestSubscriber; + +import javax.websocket.ClientEndpointConfig; +import javax.websocket.DeploymentException; +import javax.websocket.Endpoint; +import javax.websocket.Session; +import javax.websocket.server.ServerApplicationConfig; +import javax.websocket.server.ServerEndpointConfig; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +public class ClientServerTest { + + static ReactiveSocket client; + static Server server; + + public static class ApplicationConfig implements ServerApplicationConfig { + @Override + public Set getEndpointConfigs(Set> endpointClasses) { + Set cfgs = new HashSet<>(); + cfgs.add(ServerEndpointConfig.Builder + .create(ClientServerEndpoint.class, "/rs") + .build()); + return cfgs; + } + + @Override + public Set> getAnnotatedEndpointClasses(Set> scanned) { + return Collections.emptySet(); + } + } + + @BeforeClass + public static void setup() throws URISyntaxException, DeploymentException, IOException { + server = new Server("localhost", 8025, null, null, ApplicationConfig.class); + server.start(); + + ReactiveSocketWebSocketClient endpoint = new ReactiveSocketWebSocketClient(); + ClientManager clientManager = ClientManager.createClient(); + ClientEndpointConfig cec = ClientEndpointConfig.Builder.create().build(); + Session session = clientManager.connectToServer(endpoint, cec, new URI("ws://localhost:8025/rs")); + + client = ReactiveSocket.fromClientConnection( + WebSocketDuplexConnection.create(session, endpoint.getInput()), + ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS)); + + client.startAndWait(); + } + + @AfterClass + public static void tearDown() { + //server.shutdown(); + server.stop(); + } + + @Test + public void testRequestResponse1() { + requestResponseN(1500, 1); + } + + @Test + public void testRequestResponse10() { + requestResponseN(1500, 10); + } + + + @Test + public void testRequestResponse100() { + requestResponseN(1500, 100); + } + + @Test + public void testRequestResponse10_000() { + requestResponseN(60_000, 10_000); + } + + @Test + public void testRequestStream() { + TestSubscriber ts = TestSubscriber.create(); + + RxReactiveStreams + .toObservable(client.requestStream(TestUtil.utf8EncodedPayload("hello", "metadata"))) + .subscribe(ts); + + + ts.awaitTerminalEvent(3_000, TimeUnit.MILLISECONDS); + ts.assertValueCount(10); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void testRequestSubscription() throws InterruptedException { + TestSubscriber ts = TestSubscriber.create(); + + RxReactiveStreams + .toObservable(client.requestSubscription(TestUtil.utf8EncodedPayload("hello sub", "metadata sub"))) + .take(10) + .subscribe(ts); + + ts.awaitTerminalEvent(3_000, TimeUnit.MILLISECONDS); + ts.assertValueCount(10); + ts.assertNoErrors(); + } + + + public void requestResponseN(int timeout, int count) { + + TestSubscriber ts = TestSubscriber.create(); + + Observable + .range(1, count) + .flatMap(i -> + RxReactiveStreams + .toObservable( + client.requestResponse(TestUtil.utf8EncodedPayload("hello", "metadata")) + ) + .map(payload -> + TestUtil.byteToString(payload.getData()) + ) + //.doOnNext(System.out::println) + ) + .subscribe(ts); + + ts.awaitTerminalEvent(timeout, TimeUnit.MILLISECONDS); + ts.assertValueCount(count); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + +} diff --git a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/Ping.java b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/Ping.java new file mode 100644 index 000000000..6958ab88a --- /dev/null +++ b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/Ping.java @@ -0,0 +1,113 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.javax.websocket; + +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.Payload; +import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.javax.websocket.client.ReactiveSocketWebSocketClient; +import org.HdrHistogram.Recorder; +import org.glassfish.tyrus.client.ClientManager; +import rx.Observable; +import rx.RxReactiveStreams; +import rx.Subscriber; +import rx.schedulers.Schedulers; + +import javax.websocket.ClientEndpointConfig; +import javax.websocket.Session; +import java.net.URI; +import java.nio.ByteBuffer; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class Ping { + public static void main(String... args) throws Exception { + + ReactiveSocketWebSocketClient endpoint = new ReactiveSocketWebSocketClient(); + ClientManager clientManager = ClientManager.createClient(); + ClientEndpointConfig cec = ClientEndpointConfig.Builder.create().build(); + Session session = clientManager.connectToServer(endpoint, cec, new URI("ws://localhost:8025/rs")); + + ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection( + WebSocketDuplexConnection.create(session, endpoint.getInput()), ConnectionSetupPayload.create("UTF-8", "UTF-8") + ); + + reactiveSocket.startAndWait(); + + byte[] data = "hello".getBytes(); + + Payload keyPayload = new Payload() { + @Override + public ByteBuffer getData() { + return ByteBuffer.wrap(data); + } + + @Override + public ByteBuffer getMetadata() { + return null; + } + }; + + int n = 1_000_000; + CountDownLatch latch = new CountDownLatch(n); + final Recorder histogram = new Recorder(3600000000000L, 3); + + Schedulers + .computation() + .createWorker() + .schedulePeriodically(() -> { + System.out.println("---- PING/ PONG HISTO ----"); + histogram.getIntervalHistogram() + .outputPercentileDistribution(System.out, 5, 1000.0, false); + System.out.println("---- PING/ PONG HISTO ----"); + }, 10, 10, TimeUnit.SECONDS); + + Observable + .range(1, Integer.MAX_VALUE) + .flatMap(i -> { + long start = System.nanoTime(); + + return RxReactiveStreams + .toObservable( + reactiveSocket + .requestResponse(keyPayload)) + .doOnNext(s -> { + long diff = System.nanoTime() - start; + histogram.recordValue(diff); + }); + }) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + } + + @Override + public void onNext(Payload payload) { + latch.countDown(); + } + }); + + latch.await(1, TimeUnit.HOURS); + System.out.println("Sent => " + n); + System.exit(0); + } +} diff --git a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/Pong.java b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/Pong.java new file mode 100644 index 000000000..c55e3dc87 --- /dev/null +++ b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/Pong.java @@ -0,0 +1,67 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.javax.websocket; + +import org.glassfish.tyrus.server.Server; + +import javax.websocket.DeploymentException; +import javax.websocket.Endpoint; +import javax.websocket.server.ServerApplicationConfig; +import javax.websocket.server.ServerEndpointConfig; +import java.util.Collections; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class Pong { + public static class ApplicationConfig implements ServerApplicationConfig { + @Override + public Set getEndpointConfigs(Set> endpointClasses) { + Set cfgs = new HashSet<>(); + cfgs.add(ServerEndpointConfig.Builder + .create(PongEndpoint.class, "/rs") + .build()); + return cfgs; + } + + @Override + public Set> getAnnotatedEndpointClasses(Set> scanned) { + return Collections.emptySet(); + } + } + + public static void main(String... args) throws DeploymentException { + byte[] response = new byte[1024]; + Random r = new Random(); + r.nextBytes(response); + + Server server = new Server("localhost", 8025, null, null, ApplicationConfig.class); + server.start(); + + // Tyrus spawns all of its threads as daemon threads so we need to prop open the JVM with a blocking call. + CountDownLatch latch = new CountDownLatch(1); + try { + latch.await(1, TimeUnit.HOURS); + } catch (InterruptedException e) { + System.out.println("Interrupted main thread"); + } finally { + server.stop(); + } + System.exit(0); + } +} diff --git a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/PongEndpoint.java b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/PongEndpoint.java new file mode 100644 index 000000000..0c242e3aa --- /dev/null +++ b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/PongEndpoint.java @@ -0,0 +1,103 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.javax.websocket; + +import io.reactivesocket.Payload; +import io.reactivesocket.RequestHandler; +import io.reactivesocket.javax.websocket.server.ReactiveSocketWebSocketServer; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import rx.Observable; +import rx.RxReactiveStreams; + +import java.nio.ByteBuffer; +import java.util.Random; + +public class PongEndpoint extends ReactiveSocketWebSocketServer { + static byte[] response = new byte[1024]; + static { + Random r = new Random(); + r.nextBytes(response); + } + + public PongEndpoint() { + super(setupPayload -> new RequestHandler() { + @Override + public Publisher handleRequestResponse(Payload payload) { + return new Publisher() { + @Override + public void subscribe(Subscriber s) { + Payload responsePayload = new Payload() { + ByteBuffer data = ByteBuffer.wrap(response); + ByteBuffer metadata = ByteBuffer.allocate(0); + + public ByteBuffer getData() { + return data; + } + + @Override + public ByteBuffer getMetadata() { + return metadata; + } + }; + + s.onNext(responsePayload); + s.onComplete(); + } + }; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + Payload response1 = + TestUtil.utf8EncodedPayload("hello world", "metadata"); + return RxReactiveStreams + .toPublisher(Observable + .range(1, 10) + .map(i -> response1)); + } + + @Override + public Publisher handleSubscription(Payload payload) { + Payload response1 = + TestUtil.utf8EncodedPayload("hello world", "metadata"); + return RxReactiveStreams + .toPublisher(Observable + .range(1, 10) + .map(i -> response1)); + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return Subscriber::onComplete; + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher inputs) { + Observable observable = + RxReactiveStreams + .toObservable(inputs) + .map(input -> input); + return RxReactiveStreams.toPublisher(observable); + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + }); + } +} diff --git a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/TestUtil.java b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/TestUtil.java new file mode 100644 index 000000000..5e2e0c3d0 --- /dev/null +++ b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/TestUtil.java @@ -0,0 +1,122 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.javax.websocket; + +import io.reactivesocket.Frame; +import io.reactivesocket.FrameType; +import io.reactivesocket.Payload; +import uk.co.real_logic.agrona.MutableDirectBuffer; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; + +public class TestUtil +{ + public static Frame utf8EncodedRequestFrame(final int streamId, final FrameType type, final String data, final int initialRequestN) + { + return Frame.Request.from(streamId, type, new Payload() + { + public ByteBuffer getData() + { + return byteBufferFromUtf8String(data); + } + + public ByteBuffer getMetadata() + { + return Frame.NULL_BYTEBUFFER; + } + }, initialRequestN); + } + + public static Frame utf8EncodedResponseFrame(final int streamId, final FrameType type, final String data) + { + return Frame.Response.from(streamId, type, utf8EncodedPayload(data, null)); + } + + public static Frame utf8EncodedErrorFrame(final int streamId, final String data) + { + return Frame.Error.from(streamId, new Exception(data)); + } + + public static Payload utf8EncodedPayload(final String data, final String metadata) + { + return new PayloadImpl(data, metadata); + } + + public static String byteToString(final ByteBuffer byteBuffer) + { + final byte[] bytes = new byte[byteBuffer.remaining()]; + byteBuffer.get(bytes); + return new String(bytes, Charset.forName("UTF-8")); + } + + public static ByteBuffer byteBufferFromUtf8String(final String data) + { + final byte[] bytes = data.getBytes(Charset.forName("UTF-8")); + return ByteBuffer.wrap(bytes); + } + + public static void copyFrame(final MutableDirectBuffer dst, final int offset, final Frame frame) + { + dst.putBytes(offset, frame.getByteBuffer(), frame.offset(), frame.length()); + } + + private static class PayloadImpl implements Payload // some JDK shoutout + { + private ByteBuffer data; + private ByteBuffer metadata; + + public PayloadImpl(final String data, final String metadata) + { + if (null == data) + { + this.data = ByteBuffer.allocate(0); + } + else + { + this.data = byteBufferFromUtf8String(data); + } + + if (null == metadata) + { + this.metadata = ByteBuffer.allocate(0); + } + else + { + this.metadata = byteBufferFromUtf8String(metadata); + } + } + + public boolean equals(Object obj) + { + System.out.println("equals: " + obj); + final Payload rhs = (Payload)obj; + + return (TestUtil.byteToString(data).equals(TestUtil.byteToString(rhs.getData()))) && + (TestUtil.byteToString(metadata).equals(TestUtil.byteToString(rhs.getMetadata()))); + } + + public ByteBuffer getData() + { + return data; + } + + public ByteBuffer getMetadata() + { + return metadata; + } + } +} diff --git a/reactivesocket-jsr-356/src/test/resources/simplelogger.properties b/reactivesocket-jsr-356/src/test/resources/simplelogger.properties new file mode 100644 index 000000000..463129958 --- /dev/null +++ b/reactivesocket-jsr-356/src/test/resources/simplelogger.properties @@ -0,0 +1,35 @@ +# SLF4J's SimpleLogger configuration file +# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. + +# Default logging detail level for all instances of SimpleLogger. +# Must be one of ("trace", "debug", "info", "warn", or "error"). +# If not specified, defaults to "info". +#org.slf4j.simpleLogger.defaultLogLevel=debug +org.slf4j.simpleLogger.defaultLogLevel=trace + +# Logging detail level for a SimpleLogger instance named "xxxxx". +# Must be one of ("trace", "debug", "info", "warn", or "error"). +# If not specified, the default logging detail level is used. +#org.slf4j.simpleLogger.log.xxxxx= + +# Set to true if you want the current date and time to be included in output messages. +# Default is false, and will output the number of milliseconds elapsed since startup. +org.slf4j.simpleLogger.showDateTime=true + +# The date and time format to be used in the output messages. +# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat. +# If the format is not specified or is invalid, the default format is used. +# The default format is yyyy-MM-dd HH:mm:ss:SSS Z. +org.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss + +# Set to true if you want to output the current thread name. +# Defaults to true. +org.slf4j.simpleLogger.showThreadName=true + +# Set to true if you want the Logger instance name to be included in output messages. +# Defaults to true. +org.slf4j.simpleLogger.showLogName=true + +# Set to true if you want the last component of the name to be included in output messages. +# Defaults to false. +org.slf4j.simpleLogger.showShortLogName=true \ No newline at end of file diff --git a/reactivesocket-netty/build.gradle b/reactivesocket-netty/build.gradle new file mode 100644 index 000000000..006bb8454 --- /dev/null +++ b/reactivesocket-netty/build.gradle @@ -0,0 +1,3 @@ +dependencies { + compile 'io.netty:netty-all:4.1.0.CR3' +} \ No newline at end of file diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/MutableDirectByteBuf.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/MutableDirectByteBuf.java new file mode 100644 index 000000000..507a4494e --- /dev/null +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/MutableDirectByteBuf.java @@ -0,0 +1,389 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.netty; + +import io.netty.buffer.ByteBuf; +import uk.co.real_logic.agrona.BitUtil; +import uk.co.real_logic.agrona.DirectBuffer; +import uk.co.real_logic.agrona.MutableDirectBuffer; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.Charset; + +public class MutableDirectByteBuf implements MutableDirectBuffer +{ + private ByteBuf byteBuf; + + public MutableDirectByteBuf(final ByteBuf byteBuf) + { + this.byteBuf = byteBuf; + } + + public void wrap(final ByteBuf byteBuf) + { + this.byteBuf = byteBuf; + } + + public ByteBuf byteBuf() + { + return byteBuf; + } + + // TODO: make utility in reactivesocket-java + public static ByteBuffer slice(final ByteBuffer byteBuffer, final int position, final int limit) + { + final int savedPosition = byteBuffer.position(); + final int savedLimit = byteBuffer.limit(); + + byteBuffer.limit(limit).position(position); + + final ByteBuffer result = byteBuffer.slice(); + + byteBuffer.limit(savedLimit).position(savedPosition); + return result; + } + + @Override + public void setMemory(int index, int length, byte value) + { + for (int i = index; i < (index + length); i++) + { + byteBuf.setByte(i, value); + } + } + + @Override + public void putLong(int index, long value, ByteOrder byteOrder) + { + ensureByteOrder(byteOrder); + byteBuf.setLong(index, value); + } + + @Override + public void putLong(int index, long value) + { + byteBuf.setLong(index, value); + } + + @Override + public void putInt(int index, int value, ByteOrder byteOrder) + { + ensureByteOrder(byteOrder); + byteBuf.setInt(index, value); + } + + @Override + public void putInt(int index, int value) + { + byteBuf.setInt(index, value); + } + + @Override + public void putDouble(int index, double value, ByteOrder byteOrder) + { + ensureByteOrder(byteOrder); + byteBuf.setDouble(index, value); + } + + @Override + public void putDouble(int index, double value) + { + byteBuf.setDouble(index, value); + } + + @Override + public void putFloat(int index, float value, ByteOrder byteOrder) + { + ensureByteOrder(byteOrder); + byteBuf.setFloat(index, value); + } + + @Override + public void putFloat(int index, float value) + { + byteBuf.setFloat(index, value); + } + + @Override + public void putShort(int index, short value, ByteOrder byteOrder) + { + ensureByteOrder(byteOrder); + byteBuf.setShort(index, value); + } + + @Override + public void putShort(int index, short value) + { + byteBuf.setShort(index, value); + } + + @Override + public void putByte(int index, byte value) + { + byteBuf.setByte(index, value); + } + + @Override + public void putBytes(int index, byte[] src) + { + byteBuf.setBytes(index, src); + } + + @Override + public void putBytes(int index, byte[] src, int offset, int length) + { + byteBuf.setBytes(index, src, offset, length); + } + + @Override + public void putBytes(int index, ByteBuffer srcBuffer, int length) + { + final ByteBuffer sliceBuffer = slice(srcBuffer, 0, length); + byteBuf.setBytes(index, sliceBuffer); + } + + @Override + public void putBytes(int index, ByteBuffer srcBuffer, int srcIndex, int length) + { + final ByteBuffer sliceBuffer = slice(srcBuffer, srcIndex, srcIndex + length); + byteBuf.setBytes(index, sliceBuffer); + } + + @Override + public void putBytes(int index, DirectBuffer srcBuffer, int srcIndex, int length) + { + throw new UnsupportedOperationException("putBytes(DirectBuffer) not supported"); + } + + @Override + public int putStringUtf8(int offset, String value, ByteOrder byteOrder) + { + throw new UnsupportedOperationException("putStringUtf8 not supported"); + } + + @Override + public int putStringUtf8(int offset, String value, ByteOrder byteOrder, int maxEncodedSize) + { + throw new UnsupportedOperationException("putStringUtf8 not supported"); + } + + @Override + public int putStringWithoutLengthUtf8(int offset, String value) + { + throw new UnsupportedOperationException("putStringUtf8 not supported"); + } + + @Override + public void wrap(byte[] buffer) + { + throw new UnsupportedOperationException("wrap(byte[]) not supported"); + } + + @Override + public void wrap(byte[] buffer, int offset, int length) + { + throw new UnsupportedOperationException("wrap(byte[]) not supported"); + } + + @Override + public void wrap(ByteBuffer buffer) + { + throw new UnsupportedOperationException("wrap(ByteBuffer) not supported"); + } + + @Override + public void wrap(ByteBuffer buffer, int offset, int length) + { + throw new UnsupportedOperationException("wrap(ByteBuffer) not supported"); + } + + @Override + public void wrap(DirectBuffer buffer) + { + throw new UnsupportedOperationException("wrap(DirectBuffer) not supported"); + } + + @Override + public void wrap(DirectBuffer buffer, int offset, int length) + { + throw new UnsupportedOperationException("wrap(DirectBuffer) not supported"); + } + + @Override + public void wrap(long address, int length) + { + throw new UnsupportedOperationException("wrap(address) not supported"); + } + + @Override + public long addressOffset() + { + return byteBuf.memoryAddress(); + } + + @Override + public byte[] byteArray() + { + return byteBuf.array(); + } + + @Override + public ByteBuffer byteBuffer() + { + return byteBuf.nioBuffer(); + } + + @Override + public int capacity() + { + return byteBuf.capacity(); + } + + @Override + public void checkLimit(int limit) + { + throw new UnsupportedOperationException("checkLimit not supported"); + } + + @Override + public long getLong(int index, ByteOrder byteOrder) + { + ensureByteOrder(byteOrder); + return byteBuf.getLong(index); + } + + @Override + public long getLong(int index) + { + return byteBuf.getLong(index); + } + + @Override + public int getInt(int index, ByteOrder byteOrder) + { + ensureByteOrder(byteOrder); + return byteBuf.getInt(index); + } + + @Override + public int getInt(int index) + { + return byteBuf.getInt(index); + } + + @Override + public double getDouble(int index, ByteOrder byteOrder) + { + ensureByteOrder(byteOrder); + return byteBuf.getDouble(index); + } + + @Override + public double getDouble(int index) + { + return byteBuf.getDouble(index); + } + + @Override + public float getFloat(int index, ByteOrder byteOrder) + { + ensureByteOrder(byteOrder); + return byteBuf.getFloat(index); + } + + @Override + public float getFloat(int index) + { + return byteBuf.getFloat(index); + } + + @Override + public short getShort(int index, ByteOrder byteOrder) + { + ensureByteOrder(byteOrder); + return byteBuf.getShort(index); + } + + @Override + public short getShort(int index) + { + return byteBuf.getShort(index); + } + + @Override + public byte getByte(int index) + { + return byteBuf.getByte(index); + } + + @Override + public void getBytes(int index, byte[] dst) + { + byteBuf.getBytes(index, dst); + } + + @Override + public void getBytes(int index, byte[] dst, int offset, int length) + { + byteBuf.getBytes(index, dst, offset, length); + } + + @Override + public void getBytes(int index, MutableDirectBuffer dstBuffer, int dstIndex, int length) + { + throw new UnsupportedOperationException("getBytes(MutableDirectBuffer) not supported"); + } + + @Override + public void getBytes(int index, ByteBuffer dstBuffer, int length) + { + throw new UnsupportedOperationException("getBytes(ByteBuffer) not supported"); + } + + @Override + public String getStringUtf8(int offset, ByteOrder byteOrder) + { + final int length = getInt(offset, byteOrder); + return byteBuf.toString(offset + BitUtil.SIZE_OF_INT, length, Charset.forName("UTF-8")); + } + + @Override + public String getStringUtf8(int offset, int length) + { + return byteBuf.toString(offset, length, Charset.forName("UTF-8")); + } + + @Override + public String getStringWithoutLengthUtf8(int offset, int length) + { + return byteBuf.toString(offset, length, Charset.forName("UTF-8")); + } + + @Override + public void boundsCheck(int index, int length) + { + throw new UnsupportedOperationException("boundsCheck not supported"); + } + + private void ensureByteOrder(final ByteOrder byteOrder) + { + if (byteBuf.order() != byteOrder) + { + byteBuf.order(byteOrder); + } + } +} diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java new file mode 100644 index 000000000..1632a6886 --- /dev/null +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java @@ -0,0 +1,153 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.netty.websocket.client; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.*; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.HttpClientCodec; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.websocketx.*; +import io.netty.util.concurrent.Future; +import io.reactivesocket.DuplexConnection; +import io.reactivesocket.Frame; +import io.reactivesocket.rx.Completable; +import io.reactivesocket.rx.Observable; +import io.reactivesocket.rx.Observer; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.URI; +import java.util.concurrent.CopyOnWriteArrayList; + +public class ClientWebSocketDuplexConnection implements DuplexConnection { + private Channel channel; + + private Bootstrap bootstrap; + + private final CopyOnWriteArrayList> subjects; + + private ClientWebSocketDuplexConnection(Channel channel, Bootstrap bootstrap, CopyOnWriteArrayList> subjects) { + this.subjects = subjects; + this.channel = channel; + this.bootstrap = bootstrap; + } + + public static Publisher create(InetSocketAddress address, EventLoopGroup eventLoopGroup) { + return s -> { + WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker( + URI.create("ws://" + address.getHostName() + ":" + address.getPort() + "/rs"), + WebSocketVersion.V13, + null, + false, + new DefaultHttpHeaders()); + + CopyOnWriteArrayList> subjects = new CopyOnWriteArrayList<>(); + ReactiveSocketClientHandler clientHandler = new ReactiveSocketClientHandler(subjects); + Bootstrap bootstrap = new Bootstrap(); + ChannelFuture connect = bootstrap + .group(eventLoopGroup) + .channel(NioSocketChannel.class) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) throws Exception { + ChannelPipeline p = ch.pipeline(); + p.addLast( + new HttpClientCodec(), + new HttpObjectAggregator(8192), + new WebSocketClientProtocolHandler(handshaker), + clientHandler + ); + } + }).connect(address); + + connect.addListener(connectFuture -> { + if (connectFuture.isSuccess()) { + final Channel ch = connect.channel(); + clientHandler + .getHandshakePromise() + .addListener(handshakeFuture -> { + if (handshakeFuture.isSuccess()) { + s.onNext(new ClientWebSocketDuplexConnection(ch, bootstrap, subjects)); + s.onComplete(); + } else { + s.onError(handshakeFuture.cause()); + } + }); + } else { + s.onError(connectFuture.cause()); + } + }); + }; + } + + @Override + public final Observable getInput() { + return o -> { + o.onSubscribe(() -> subjects.removeIf(s -> s == o)); + subjects.add(o); + }; + } + + @Override + public void addOutput(Publisher o, Completable callback) { + o.subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); + } + + @Override + public void onNext(Frame frame) { + try { + ByteBuf byteBuf = Unpooled.wrappedBuffer(frame.getByteBuffer()); + BinaryWebSocketFrame binaryWebSocketFrame = new BinaryWebSocketFrame(byteBuf); + ChannelFuture channelFuture = channel.writeAndFlush(binaryWebSocketFrame); + channelFuture.addListener((Future future) -> { + Throwable cause = future.cause(); + if (cause != null) { + callback.error(cause); + } + }); + } catch (Throwable t) { + onError(t); + } + } + + @Override + public void onError(Throwable t) { + callback.error(t); + } + + @Override + public void onComplete() { + callback.success(); + } + }); + } + + @Override + public void close() throws IOException { + channel.close(); + } +} diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ReactiveSocketClientHandler.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ReactiveSocketClientHandler.java new file mode 100644 index 000000000..fc1eb7d2e --- /dev/null +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ReactiveSocketClientHandler.java @@ -0,0 +1,69 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.netty.websocket.client; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; +import io.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandler; +import io.reactivesocket.Frame; +import io.reactivesocket.netty.MutableDirectByteBuf; +import io.reactivesocket.rx.Observer; + + +import java.util.concurrent.CopyOnWriteArrayList; + +@ChannelHandler.Sharable +public class ReactiveSocketClientHandler extends SimpleChannelInboundHandler { + + private final CopyOnWriteArrayList> subjects; + + private ChannelPromise handshakePromise; + + public ReactiveSocketClientHandler(CopyOnWriteArrayList> subjects) { + this.subjects = subjects; + } + + @Override + public void channelRegistered(ChannelHandlerContext ctx) throws Exception { + this.handshakePromise = ctx.newPromise(); + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, BinaryWebSocketFrame bFrame) throws Exception { + ByteBuf content = bFrame.content(); + MutableDirectByteBuf mutableDirectByteBuf = new MutableDirectByteBuf(content); + final Frame from = Frame.from(mutableDirectByteBuf, 0, mutableDirectByteBuf.capacity()); + subjects.forEach(o -> o.onNext(from)); + } + + public ChannelPromise getHandshakePromise() { + return handshakePromise; + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + if (evt instanceof WebSocketClientProtocolHandler.ClientHandshakeStateEvent) { + WebSocketClientProtocolHandler.ClientHandshakeStateEvent evt1 = (WebSocketClientProtocolHandler.ClientHandshakeStateEvent) evt; + if (evt1.equals(WebSocketClientProtocolHandler.ClientHandshakeStateEvent.HANDSHAKE_COMPLETE)) { + handshakePromise.setSuccess(); + } + } + } +} diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ReactiveSocketServerHandler.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ReactiveSocketServerHandler.java new file mode 100644 index 000000000..20fedf1a6 --- /dev/null +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ReactiveSocketServerHandler.java @@ -0,0 +1,76 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.netty.websocket.server; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelId; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; +import io.reactivesocket.ConnectionSetupHandler; +import io.reactivesocket.Frame; +import io.reactivesocket.LeaseGovernor; +import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.netty.MutableDirectByteBuf; + +import java.util.concurrent.ConcurrentHashMap; + +@ChannelHandler.Sharable +public class ReactiveSocketServerHandler extends SimpleChannelInboundHandler { + private ConcurrentHashMap duplexConnections = new ConcurrentHashMap<>(); + + private ConnectionSetupHandler setupHandler; + + private LeaseGovernor leaseGovernor; + + private ReactiveSocketServerHandler(ConnectionSetupHandler setupHandler, LeaseGovernor leaseGovernor) { + this.setupHandler = setupHandler; + this.leaseGovernor = leaseGovernor; + } + + public static ReactiveSocketServerHandler create(ConnectionSetupHandler setupHandler) { + return create(setupHandler, LeaseGovernor.UNLIMITED_LEASE_GOVERNOR); + } + + public static ReactiveSocketServerHandler create(ConnectionSetupHandler setupHandler, LeaseGovernor leaseGovernor) { + return new + ReactiveSocketServerHandler( + setupHandler, + leaseGovernor); + + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, BinaryWebSocketFrame msg) throws Exception { + ByteBuf content = msg.content(); + MutableDirectByteBuf mutableDirectByteBuf = new MutableDirectByteBuf(content); + Frame from = Frame.from(mutableDirectByteBuf, 0, mutableDirectByteBuf.capacity()); + channelRegistered(ctx); + ServerWebSocketDuplexConnection connection = duplexConnections.computeIfAbsent(ctx.channel().id(), i -> { + System.out.println("No connection found for channel id: " + i); + ServerWebSocketDuplexConnection c = new ServerWebSocketDuplexConnection(ctx); + ReactiveSocket reactiveSocket = ReactiveSocket.fromServerConnection(c, setupHandler, leaseGovernor, throwable -> throwable.printStackTrace()); + reactiveSocket.startAndWait(); + return c; + }); + if (connection != null) { + connection + .getSubscribers() + .forEach(o -> o.onNext(from)); + } + } +} diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ServerWebSocketDuplexConnection.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ServerWebSocketDuplexConnection.java new file mode 100644 index 000000000..4929376f2 --- /dev/null +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ServerWebSocketDuplexConnection.java @@ -0,0 +1,102 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.netty.websocket.server; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; +import io.reactivesocket.DuplexConnection; +import io.reactivesocket.Frame; +import io.reactivesocket.rx.Completable; +import io.reactivesocket.rx.Observable; +import io.reactivesocket.rx.Observer; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +public class ServerWebSocketDuplexConnection implements DuplexConnection { + private final CopyOnWriteArrayList> subjects; + + private final ChannelHandlerContext ctx; + + public ServerWebSocketDuplexConnection(ChannelHandlerContext ctx) { + this.subjects = new CopyOnWriteArrayList<>(); + this.ctx = ctx; + } + + public List> getSubscribers() { + return subjects; + } + + @Override + public final Observable getInput() { + return o -> { + o.onSubscribe(() -> subjects.removeIf(s -> s == o)); + subjects.add(o); + }; + } + + @Override + public void addOutput(Publisher o, Completable callback) { + o.subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); + } + + @Override + public void onNext(Frame frame) { + try { + ByteBuffer data = frame.getByteBuffer(); + ByteBuf byteBuf = Unpooled.wrappedBuffer(data); + BinaryWebSocketFrame binaryWebSocketFrame = new BinaryWebSocketFrame(byteBuf); + ChannelFuture channelFuture = ctx.writeAndFlush(binaryWebSocketFrame); + channelFuture.addListener(future -> { + Throwable cause = future.cause(); + if (cause != null) { + cause.printStackTrace(); + callback.error(cause); + } + }); + } catch (Throwable t) { + onError(t); + } + } + + @Override + public void onError(Throwable t) { + callback.error(t); + } + + @Override + public void onComplete() { + callback.success(); + } + }); + } + + @Override + public void close() throws IOException { + + } +} diff --git a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java new file mode 100644 index 000000000..f932a14bd --- /dev/null +++ b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java @@ -0,0 +1,215 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.netty.websocket; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.Payload; +import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.RequestHandler; +import io.reactivesocket.netty.websocket.client.ClientWebSocketDuplexConnection; +import io.reactivesocket.netty.websocket.server.ReactiveSocketServerHandler; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import rx.Observable; +import rx.RxReactiveStreams; +import rx.observers.TestSubscriber; + +import java.net.InetSocketAddress; +import java.util.concurrent.TimeUnit; + +public class ClientServerTest { + + static ReactiveSocket client; + static Channel serverChannel; + + static EventLoopGroup bossGroup = new NioEventLoopGroup(1); + static EventLoopGroup workerGroup = new NioEventLoopGroup(4); + + static ReactiveSocketServerHandler serverHandler = ReactiveSocketServerHandler.create(setupPayload -> + new RequestHandler() { + @Override + public Publisher handleRequestResponse(Payload payload) { + return s -> { + //System.out.println("Handling request/response payload => " + s.toString()); + Payload response = TestUtil.utf8EncodedPayload("hello world", "metadata"); + s.onNext(response); + s.onComplete(); + }; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + Payload response = TestUtil.utf8EncodedPayload("hello world", "metadata"); + + return RxReactiveStreams + .toPublisher(Observable + .range(1, 10) + .map(i -> response)); + } + + @Override + public Publisher handleSubscription(Payload payload) { + Payload response = TestUtil.utf8EncodedPayload("hello world", "metadata"); + + return RxReactiveStreams + .toPublisher(Observable + .range(1, 10) + .map(i -> response) + .repeat()); + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return Subscriber::onComplete; + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher inputs) { + return null; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + } + ); + + @BeforeClass + public static void setup() throws Exception { + ServerBootstrap b = new ServerBootstrap(); + b.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .handler(new LoggingHandler(LogLevel.INFO)) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ChannelPipeline pipeline = ch.pipeline(); + pipeline.addLast(new HttpServerCodec()); + pipeline.addLast(new HttpObjectAggregator(64 * 1024)); + pipeline.addLast(new WebSocketServerProtocolHandler("/rs")); + pipeline.addLast(serverHandler); + } + }); + + serverChannel = b.bind("localhost", 8025).sync().channel(); + + ClientWebSocketDuplexConnection duplexConnection = RxReactiveStreams.toObservable( + ClientWebSocketDuplexConnection.create(InetSocketAddress.createUnresolved("localhost", 8025), new NioEventLoopGroup()) + ).toBlocking().single(); + + client = ReactiveSocket + .fromClientConnection(duplexConnection, ConnectionSetupPayload.create("UTF-8", "UTF-8"), t -> t.printStackTrace()); + + client.startAndWait(); + + } + + @AfterClass + public static void tearDown() { + serverChannel.close(); + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + + @Test + public void testRequestResponse1() { + requestResponseN(1500, 1); + } + + @Test + public void testRequestResponse10() { + requestResponseN(1500, 10); + } + + + @Test + public void testRequestResponse100() { + requestResponseN(1500, 100); + } + + @Test + public void testRequestResponse10_000() { + requestResponseN(60_000, 10_000); + } + + @Test + public void testRequestStream() { + TestSubscriber ts = TestSubscriber.create(); + + RxReactiveStreams + .toObservable(client.requestStream(TestUtil.utf8EncodedPayload("hello", "metadata"))) + .subscribe(ts); + + + ts.awaitTerminalEvent(3_000, TimeUnit.MILLISECONDS); + ts.assertValueCount(10); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void testRequestSubscription() throws InterruptedException { + TestSubscriber ts = TestSubscriber.create(); + + RxReactiveStreams + .toObservable(client.requestSubscription(TestUtil.utf8EncodedPayload("hello sub", "metadata sub"))) + .take(10) + .subscribe(ts); + + ts.awaitTerminalEvent(3_000, TimeUnit.MILLISECONDS); + ts.assertValueCount(10); + ts.assertNoErrors(); + } + + + public void requestResponseN(int timeout, int count) { + + TestSubscriber ts = TestSubscriber.create(); + + Observable + .range(1, count) + .flatMap(i -> + RxReactiveStreams + .toObservable(client.requestResponse(TestUtil.utf8EncodedPayload("hello", "metadata"))) + .map(payload -> TestUtil.byteToString(payload.getData())) + ) + .doOnError(Throwable::printStackTrace) + .subscribe(ts); + + ts.awaitTerminalEvent(timeout, TimeUnit.MILLISECONDS); + ts.assertValueCount(count); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + +} \ No newline at end of file diff --git a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Ping.java b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Ping.java new file mode 100644 index 000000000..b60440a53 --- /dev/null +++ b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Ping.java @@ -0,0 +1,107 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.netty.websocket; + +import io.netty.channel.nio.NioEventLoopGroup; +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.Payload; +import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.netty.websocket.client.ClientWebSocketDuplexConnection; +import org.HdrHistogram.Recorder; +import org.reactivestreams.Publisher; +import rx.Observable; +import rx.RxReactiveStreams; +import rx.Subscriber; +import rx.schedulers.Schedulers; + +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class Ping { + public static void main(String... args) throws Exception { + Publisher publisher = ClientWebSocketDuplexConnection.create(InetSocketAddress.createUnresolved("localhost", 8025), new NioEventLoopGroup(1)); + + ClientWebSocketDuplexConnection duplexConnection = RxReactiveStreams.toObservable(publisher).toBlocking().last(); + ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection(duplexConnection, ConnectionSetupPayload.create("UTF-8", "UTF-8"), t -> t.printStackTrace()); + + reactiveSocket.startAndWait(); + + byte[] data = "hello".getBytes(); + + Payload keyPayload = new Payload() { + @Override + public ByteBuffer getData() { + return ByteBuffer.wrap(data); + } + + @Override + public ByteBuffer getMetadata() { + return null; + } + }; + + int n = 1_000_000; + CountDownLatch latch = new CountDownLatch(n); + final Recorder histogram = new Recorder(3600000000000L, 3); + + Schedulers + .computation() + .createWorker() + .schedulePeriodically(() -> { + System.out.println("---- PING/ PONG HISTO ----"); + histogram.getIntervalHistogram() + .outputPercentileDistribution(System.out, 5, 1000.0, false); + System.out.println("---- PING/ PONG HISTO ----"); + }, 1, 1, TimeUnit.SECONDS); + + Observable + .range(1, Integer.MAX_VALUE) + .flatMap(i -> { + long start = System.nanoTime(); + + return RxReactiveStreams + .toObservable( + reactiveSocket + .requestResponse(keyPayload)) + .doOnNext(s -> { + long diff = System.nanoTime() - start; + histogram.recordValue(diff); + }); + }, 16) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + } + + @Override + public void onNext(Payload payload) { + latch.countDown(); + } + }); + + latch.await(1, TimeUnit.HOURS); + System.out.println("Sent => " + n); + System.exit(0); + } +} diff --git a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Pong.java b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Pong.java new file mode 100644 index 000000000..b20069954 --- /dev/null +++ b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Pong.java @@ -0,0 +1,177 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.netty.websocket; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; +import io.reactivesocket.Payload; +import io.reactivesocket.RequestHandler; +import io.reactivesocket.netty.websocket.server.ReactiveSocketServerHandler; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import rx.Observable; +import rx.RxReactiveStreams; + +import java.nio.ByteBuffer; +import java.util.Random; + +public class Pong { + public static void main(String... args) throws Exception { + byte[] response = new byte[1024]; + Random r = new Random(); + r.nextBytes(response); + + ReactiveSocketServerHandler serverHandler = + ReactiveSocketServerHandler.create(setupPayload -> new RequestHandler() { + @Override + public Publisher handleRequestResponse(Payload payload) { + return new Publisher() { + @Override + public void subscribe(Subscriber s) { + Payload responsePayload = new Payload() { + ByteBuffer data = ByteBuffer.wrap(response); + ByteBuffer metadata = ByteBuffer.allocate(0); + + public ByteBuffer getData() { + return data; + } + + @Override + public ByteBuffer getMetadata() { + return metadata; + } + }; + + s.onNext(responsePayload); + s.onComplete(); + } + }; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + Payload response = + TestUtil.utf8EncodedPayload("hello world", "metadata"); + return RxReactiveStreams + .toPublisher(Observable + .range(1, 10) + .map(i -> response)); + } + + @Override + public Publisher handleSubscription(Payload payload) { + Payload response = + TestUtil.utf8EncodedPayload("hello world", "metadata"); + return RxReactiveStreams + .toPublisher(Observable + .range(1, 10) + .map(i -> response)); + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return Subscriber::onComplete; + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher inputs) { + Observable observable = + RxReactiveStreams + .toObservable(inputs) + .map(input -> input); + return RxReactiveStreams.toPublisher(observable); + +// return outputSubscriber -> { +// inputs.subscribe(new Subscriber() { +// private int count = 0; +// private boolean completed = false; +// +// @Override +// public void onSubscribe(Subscription s) { +// //outputSubscriber.onSubscribe(s); +// s.request(128); +// } +// +// @Override +// public void onNext(Payload input) { +// if (completed) { +// return; +// } +// count += 1; +// outputSubscriber.onNext(input); +// outputSubscriber.onNext(input); +// if (count > 10) { +// completed = true; +// outputSubscriber.onComplete(); +// } +// } +// +// @Override +// public void onError(Throwable t) { +// if (!completed) { +// outputSubscriber.onError(t); +// } +// } +// +// @Override +// public void onComplete() { +// if (!completed) { +// outputSubscriber.onComplete(); +// } +// } +// }); +// }; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + }); + + EventLoopGroup bossGroup = new NioEventLoopGroup(1); + EventLoopGroup workerGroup = new NioEventLoopGroup(); + + ServerBootstrap b = new ServerBootstrap(); + b.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .handler(new LoggingHandler(LogLevel.INFO)) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ChannelPipeline pipeline = ch.pipeline(); + pipeline.addLast(new HttpServerCodec()); + pipeline.addLast(new HttpObjectAggregator(64 * 1024)); + pipeline.addLast(new WebSocketServerProtocolHandler("/rs")); + pipeline.addLast(serverHandler); + } + }); + + Channel localhost = b.bind("localhost", 8025).sync().channel(); + localhost.closeFuture().sync(); + + } +} diff --git a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/TestUtil.java b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/TestUtil.java new file mode 100644 index 000000000..8d8ab65b0 --- /dev/null +++ b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/TestUtil.java @@ -0,0 +1,160 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.netty.websocket; + +import io.netty.buffer.ByteBuf; +import io.reactivesocket.Frame; +import io.reactivesocket.FrameType; +import io.reactivesocket.Payload; +import uk.co.real_logic.agrona.MutableDirectBuffer; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; + +public class TestUtil +{ + public static Frame utf8EncodedRequestFrame(final int streamId, final FrameType type, final String data, final int initialRequestN) + { + return Frame.Request.from(streamId, type, new Payload() + { + public ByteBuffer getData() + { + return byteBufferFromUtf8String(data); + } + + public ByteBuffer getMetadata() + { + return Frame.NULL_BYTEBUFFER; + } + }, initialRequestN); + } + + public static Frame utf8EncodedResponseFrame(final int streamId, final FrameType type, final String data) + { + return Frame.Response.from(streamId, type, utf8EncodedPayload(data, null)); + } + + public static Frame utf8EncodedErrorFrame(final int streamId, final String data) + { + return Frame.Error.from(streamId, new Exception(data)); + } + + public static Payload utf8EncodedPayload(final String data, final String metadata) + { + return new PayloadImpl(data, metadata); + } + + public static String byteToString(final ByteBuffer byteBuffer) + { + final byte[] bytes = new byte[byteBuffer.remaining()]; + byteBuffer.get(bytes); + return new String(bytes, Charset.forName("UTF-8")); + } + + public static ByteBuffer byteBufferFromUtf8String(final String data) + { + final byte[] bytes = data.getBytes(Charset.forName("UTF-8")); + return ByteBuffer.wrap(bytes); + } + + public static void copyFrame(final MutableDirectBuffer dst, final int offset, final Frame frame) + { + dst.putBytes(offset, frame.getByteBuffer(), frame.offset(), frame.length()); + } + + private static class PayloadImpl implements Payload // some JDK shoutout + { + private ByteBuffer data; + private ByteBuffer metadata; + + public PayloadImpl(final String data, final String metadata) + { + if (null == data) + { + this.data = ByteBuffer.allocate(0); + } + else + { + this.data = byteBufferFromUtf8String(data); + } + + if (null == metadata) + { + this.metadata = ByteBuffer.allocate(0); + } + else + { + this.metadata = byteBufferFromUtf8String(metadata); + } + } + + public boolean equals(Object obj) + { + System.out.println("equals: " + obj); + final Payload rhs = (Payload)obj; + + return (TestUtil.byteToString(data).equals(TestUtil.byteToString(rhs.getData()))) && + (TestUtil.byteToString(metadata).equals(TestUtil.byteToString(rhs.getMetadata()))); + } + + public ByteBuffer getData() + { + return data; + } + + public ByteBuffer getMetadata() + { + return metadata; + } + } + + public static String byteBufToString(ByteBuf buf) { + byte[] bytes = new byte[buf.readableBytes()]; + int readerIndex = buf.readerIndex(); + buf.getBytes(readerIndex, bytes); + buf.readerIndex(readerIndex); + + StringBuilder result = new StringBuilder(); + StringBuilder ascii = new StringBuilder(); + int i = 0; + for (i=0; i Date: Tue, 22 Mar 2016 11:16:08 -0700 Subject: [PATCH 075/105] Update encrypted keys --- .travis.yml | 21 +++++++-------------- buildViaTravis.sh | 16 ---------------- 2 files changed, 7 insertions(+), 30 deletions(-) delete mode 100755 buildViaTravis.sh diff --git a/.travis.yml b/.travis.yml index 50633ccb0..456e13dfb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,26 +1,19 @@ language: java jdk: - oraclejdk8 - -# force upgrade Java8 as per https://github.com/travis-ci/travis-ci/issues/4042 (fixes compilation issue) addons: apt: packages: - - oracle-java8-installer - + - oracle-java8-installer sudo: false -# as per http://blog.travis-ci.com/2014-12-17-faster-builds-with-container-based-infrastructure/ - -# script for build and release via Travis to Bintray script: gradle/buildViaTravis.sh - -# cache between builds cache: directories: - - $HOME/.m2 - - $HOME/.gradle - + - "$HOME/.m2" + - "$HOME/.gradle" env: global: - - secure: U4mRou9dXE0QDYx7Wm4lhZ2yNe+/XoWN0wdkZsc+cdDFicPQU4o/tHWlZ2zdBaP4lPKyc/JBIaCS6wdIEIj5oaZQ7NCKpgNpz4YguoLmPrApO7qoD3Aa48NbylKlyAl5Lc7dJ11Ru1BouwBR17EM9HVOFfLu+IN16wppHd4LBbFu6PRUs5m4JAkx3G4fZRPtikuxHt/vJDAZbE1g/wNQJ3chiq6FWTFkgLtCABkUulD/pv7iDP9xKrVMxQcKY+CQOJSoJbteIJMmxv6GSpIbNA91+YWdPpXh0l8BYzzz1RbmqqrNyB6qdHWjET7Bq6UMjPNTND+Qedps6Rqnv1EZbvybeUqExSRoJ9EwOq5T44T2afIL6+zjE/5chSWBKuylHQR/ZOfer97UGtDkcjIB5dmsmpfb9Ibyf8wdpAbMt50xP+NsBYGtAiiEtnmdYsdFbCPH6n9/SGPRJMJtqZJCB1bYuScKB2fXn+Gfqhhl2+OfHy5q8G6Ir9Lkbh95Ke9bFUgjKnFyL4lnXDUs80guZSQ2eZi21c4o3UYQfdIunzj3VSgLGmRacV6OEtE45hSGcfZ5nLXgojsZp6fb3Td9dfIbUUnAFeGhCBY0gPCCO5hBdLAGVJbLkwXm2TSW4ILZBO9U0ykbNB8wFzGpdbkATYtOrGvbtd0njG3TAnCBR+k= - - secure: F+xT9kf1wSMwt4dCbN7qf7SicJSlYYW2Sl/WlrhA3STXUV3md+i/THYTi2cBZdoF63reZDMGa84szGw7X+y6V9Pr5zUrbEfAAi8FtvGG0wgw89Eksd8XcCOM3/rDKkavbVAGChL5Q8PwugA8U0Tarzbc+RSFpYgLPh74qsk3OECSavMOXRd9UPGqA52VAJyU1qkwc2PD641R4oDvUQdla/xKhKYUm4LCojZRtEEeZQLDykxXrVITGtA7LO8LzHvqJdt1A4GsykzfbKTAAGKi9BbFMt/F5ZCa5fIGewdjpjxbdDsCMdvASmhlpP2BiH6wuV9CyyK4EGeAQczboR5AamhYoIcxA4VgOrGWkdHCWMBDVz10JDLfAthsOVA57aSHFO255jJAq4S0xpXqPpshDJJYZbH+33PQKvCzwpw03KhNEReykmuOk03/1NpXqMwr+Zd/5wo/243ToTLvbtBvZBqLM6aRBUntxnencOv7pWljRzfhPiZrNG1nwTxQuGJwgY8+xYZYXNlyRObMdmiDMI2xDkaXr168Nx/wVci+0lnj4YD9Xlyqa1+MxC9W7GC8l3x2PbvVZ/XiOgWimDZMz9fKc/QnVXy/Mv9dM+kjin02DKswk4tlxhZ8mheBhAH7ejHCVtdAwZxNRXK+uf/MnDIjpMJ81Cbjfh/+KJ8PV1U= + - secure: FARoyTHeAFf6RBkiWAQ5XmsZiD6/VacZWeywZ0ZwImlMgyRdVveIRsVRGeH2b9loqG7Gz4VZchpfSBwHPWTDJWBgVCkMOsLbbjPjptHBj+VjpCihYycCrp4scFBH5enT82nxEsk90s5gP4l/Y9jZS9z1LGVX8vn1R5H4oR4rS6yGqhUf2IVDRFtikx3c91meOjoHAd+kMrbk9ODFR0CWZJOkWLbxGP5Evp17Ti4Gvo0GopkEjWBDa+a65HUqUSDgpgk2FF6Isz69H8Ealfl2nrYVl1pfBJTLhyqvpNAeVodgb7EGwDpD4eU6nuAuwCsQwR8Tcmv85LTv6Xev7ODT2ocyZCrrgksNE8UMX+yS9rg4QQgd/OmWI+R0uMM1bNO0bBqt262dZsXwuqgVAqO0HtLH+AM21J0HibwfqNroySXZsb6o5Jo3MB/+yOfMDEXHSdcWlefCceXMLMNvQezQeXr4eCBlkHLGylOXEvhf1b+rC3ZzoBTFRLldhvn406bMc8nx31PgdUUmMVeQiLjiFx6hDwxm2tXPjW5AWIEZBMHCjvF8yCILuInSzhMEMfMCQrPPt8hQ1a/VuhhELdQktIqMzI0jklBVr5jpJsMzf+yynYa0fwN+fzCCBOuft5muraAxwsgjpQbpXhLHk/EStCcRsk9Hwf+q+sQoIK5oSqA= + - secure: JWHkDpNF8xzal1AIK+8E2TwTrnc6k3EQH2kCtWE5TZk34+kwejXl+fVLPnRvVysyrH4uMyHskJkm70E8woVoEs2A6XkvEI8qAsJ0GrAR2JlIc4zRttlYq6+iVOBvHmOwNlIk/PD/SyXLMcp+G0kE+PUCrabbU0FsZPPbh+g0fJ+c0DcS3WFC9Bg+tYpidPs1bl/QImnvIduyv8pgzZG/6uzDkNAob9eCUVtjIHOa8XhvTzLTOmstsdChIVrQDZSGw4qUaXdWuDxaxw2NMtEcnt/3OvbUkaMu08uh3X4guKuioV+XphybcAQZdtbJnry+UYaeYAIEOSgNS18c4lz6bNo2ysg4tOcyIxrjv/vUPvc6gUbFL7kW9bb3mLJAWSmyz0Cw1LgvAfXyL2W3DK3Kp8ksl5VNGylBxIZoGZKRd2SAjFaHBLndE3dN1E6SGpxOWsBV6Xq3O/iSWOd3uQIALfaTiWUUFSbn4BcGmgMbpB2rOHRAQDu9+j0OC/4CmO2pmO8b/JNvs6nx8RGEB28XqObU5zM5BKxxXLgu25I1732FPIroDDJrpSn0X7w6Z3JHbfy4GoC/OL0BsNDimCFc53flAZ7YiWxxeW7FDNpVzEkBQ5illnmYtZFEJDWwsph+3nlDEJoMKh/Sh9TrP3D+QMb+GEIeAf+gE5nVz5JHlxI= + - secure: y1d19JOLulgyC7JInX6C4Jhbzi2kFk0xNRjaq5yyzxwSiuX0GSheXdo8r8GErxiFEuh005/LgFMnI7LPR+M/qDJehUeQuFechX6jvttC9uScSKPqU67Y8yU75hKIgCBPqHA4MoX/pL8Tg2p4MD6UdeSwiC5/zrPYuJ5CeCAf1woXRkg5w7/F2fgztqpw6SjgxA3fvqFzX7l9zCPVlBCmVKldz7xh6p/wzSnNZ9VmlXZYXtPoI/WrAKYkMAu47UJYg/QIB/B9YXpFrYICc06Hxt36fTrzWWlRXSnWiQPVjCoHzE676Mg9491pOK3TiuZh6ryndvog2l4us0JigubN3I3aOTX760+1wxVDTV2DdeRFDYVlCJ4eDaqMCE7KLeY6wqn65Xmu4BeoEduB4dfIYoBC5m/oI8TgOgFzVcJ5VAPa69zfQQtO2hdIWlCayQf9wUYX4Czk584Rc9/oJKcG+V55YOVhwaLEHZzzekwbn1Ndhy7rLSKmU2s7cc6JNvcjnJYkibS+b5JH1qyoWmqyoC/dxaqfMIn2AyAktdHesmpSGcJ9VmjAPtFkY1fhDOfr0WFlkal+IvK/+Hnh21zSl4S5jzFq8103KKgmV1GBwbOhc0BPzkbMa9HYR88XkiN4ocTNgH0Caw7N0eP4NCnNi/gUr+Dzr5G/ajyd68UNOvk= + - secure: rWDOnTYxnfXmJ2SedJ7o2s0j2wJK9v05108FPMbdyrYHnyqjZBDVx9qeppx2O9jIjGzj0doxPMqkxzDqLw5Dc/Bsko97fNMLn3hr+cbn8jhTHNTIVxkdGMOtuVpCeGpe+Gv4xKVhPw3a5olkeUtaE+awNcHqUUHDeiIPj3Fnuh51E4UT+pthE+3ffrkwrqCo0LhzwINW+Vxo4LrHPMNzUMTdD/AWPERqnhxdYn3ovnagEe1eHzbk4OM1GMKQwZ3hIwOaDf4wGdUgnea0BbOxGmHCH5FXU/Q+cNWaeI1vK2DYf2yH/EfJr5tSyVeYGXWIy7wOMAyInWcZYH9jxs9OpkhxPmeJ2ytSccu++GZKscwChNJuSd0T2EDekxelyeJGNdYdz2deSEw/wjuXfVzabTyxBDYFv04qd1c6qJG3PPdsan1NMrehy2M0LieorZ8ZclJcH7GWN1fhuXzol5u17oG2BVjws/pCFvVynGijQ8cPsyvoCAz6iWVSdhwCC+KdviGt93w73DWImsDWq5f7Hv9Jn60CKp+nevaSvVPEPhyfJUVWpw4lCxxmGmPvYosFCmL+Ihz7WjI0VGS1hXtrbuIrzDLcWisAZ25Yn0eA5e8vKmPawLeRxBHSI/ee5Sic4BzpYymOL2fPwITeg641WIDVcC8Vq2prst3EtpJd58g= diff --git a/buildViaTravis.sh b/buildViaTravis.sh deleted file mode 100755 index d98e5eb60..000000000 --- a/buildViaTravis.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -# This script will build the project. - -if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then - echo -e "Build Pull Request #$TRAVIS_PULL_REQUEST => Branch [$TRAVIS_BRANCH]" - ./gradlew -Prelease.useLastTag=true build -elif [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" == "" ]; then - echo -e 'Build Branch with Snapshot => Branch ['$TRAVIS_BRANCH']' - ./gradlew -Prelease.travisci=true -PbintrayUser="${bintrayUser}" -PbintrayKey="${bintrayKey}" -PsonatypeUsername="${sonatypeUsername}" -PsonatypePassword="${sonatypePassword}" build snapshot --stacktrace -elif [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" != "" ]; then - echo -e 'Build Branch for Release => Branch ['$TRAVIS_BRANCH'] Tag ['$TRAVIS_TAG']' - ./gradlew -Prelease.travisci=true -Prelease.useLastTag=true -PbintrayUser="${bintrayUser}" -PbintrayKey="${bintrayKey}" -PsonatypeUsername="${sonatypeUsername}" -PsonatypePassword="${sonatypePassword}" final --stacktrace -else - echo -e 'WARN: Should not be here => Branch ['$TRAVIS_BRANCH'] Tag ['$TRAVIS_TAG'] Pull Request ['$TRAVIS_PULL_REQUEST']' - ./gradlew -Prelease.useLastTag=true build -fi From 8db8dd5a11e3bd19a5bbf8ecbd9686f4bddf31f2 Mon Sep 17 00:00:00 2001 From: Steve Gury Date: Tue, 22 Mar 2016 11:19:10 -0700 Subject: [PATCH 076/105] Update README --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 25f194c86..5890ec127 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ -# ReactiveSocket over Aeron +# ReactiveSocket Java Implementation -This is an implementation over Aeron. +Java implementations. ## Master Build Status - + ## Bugs and Feedback -For bugs, questions and discussions please use the [Github Issues](https://github.com/ReactiveSocket/reactivesocket-aeron-rxjava/issues). +For bugs, questions and discussions please use the [Github Issues](https://github.com/ReactiveSocket/reactivesocket-java-impl/issues). ## LICENSE From a9aa87145d01a021c225a68f0e2467f0613e7d53 Mon Sep 17 00:00:00 2001 From: Steve Gury Date: Tue, 22 Mar 2016 11:21:03 -0700 Subject: [PATCH 077/105] Touch README to trigger travis build --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5890ec127..d0ee99bf2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ReactiveSocket Java Implementation +# ReactiveSocket Java Implementations Java implementations. From db575e4f4daea079d2c4521373a4495b2c54e12c Mon Sep 17 00:00:00 2001 From: Ryland Degnan Date: Wed, 23 Mar 2016 13:27:19 -0700 Subject: [PATCH 078/105] Added AeronReactiveSocketFactory --- build.gradle | 4 +- .../client/AeronReactiveSocketFactory.java | 125 ++++++++++++++++++ 2 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketFactory.java diff --git a/build.gradle b/build.gradle index 3f254471f..d6f60c5ad 100644 --- a/build.gradle +++ b/build.gradle @@ -14,9 +14,9 @@ subprojects { } dependencies { - compile 'io.reactivex:rxjava:1.1.1' + compile 'io.reactivex:rxjava:1.1.2' compile 'io.reactivex:rxjava-reactive-streams:1.0.1' - compile 'io.reactivesocket:reactivesocket:0.0.2' + compile 'io.reactivesocket:reactivesocket:0.0.4' compile 'org.hdrhistogram:HdrHistogram:2.1.7' compile 'org.slf4j:slf4j-api:1.7.12' testCompile 'junit:junit:4.12' diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketFactory.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketFactory.java new file mode 100644 index 000000000..9f64580b2 --- /dev/null +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketFactory.java @@ -0,0 +1,125 @@ +package io.reactivesocket.aeron.client; + +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.ReactiveSocketFactory; +import io.reactivesocket.internal.rx.EmptySubscription; +import io.reactivesocket.rx.Completable; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import uk.co.real_logic.agrona.LangUtil; + +import java.net.*; +import java.util.Enumeration; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * An implementation of {@link ReactiveSocketFactory} that creates Aeron ReactiveSockets. + */ +public class AeronReactiveSocketFactory implements ReactiveSocketFactory { + private static final Logger logger = LoggerFactory.getLogger(AeronReactiveSocketFactory.class); + + private final ConnectionSetupPayload connectionSetupPayload; + private final Consumer errorStream; + + public AeronReactiveSocketFactory(ConnectionSetupPayload connectionSetupPayload, Consumer errorStream) { + this(getIPv4InetAddress().getHostAddress(), 39790, connectionSetupPayload, errorStream); + } + + public AeronReactiveSocketFactory(String host, int port, ConnectionSetupPayload connectionSetupPayload, Consumer errorStream) { + this.connectionSetupPayload = connectionSetupPayload; + this.errorStream = errorStream; + + try { + InetSocketAddress inetSocketAddress = new InetSocketAddress(host, port); + logger.info("Listen to ReactiveSocket Aeron response on host {} port {}", host, port); + AeronClientDuplexConnectionFactory.getInstance().addSocketAddressToHandleResponses(inetSocketAddress); + } catch (Exception e) { + logger.error(e.getMessage(), e); + LangUtil.rethrowUnchecked(e); + } + } + + @Override + public Publisher call(SocketAddress address, long timeout, TimeUnit timeUnit) { + Publisher aeronClientDuplexConnection + = AeronClientDuplexConnectionFactory.getInstance().createAeronClientDuplexConnection(address); + + return (Subscriber s) -> { + s.onSubscribe(EmptySubscription.INSTANCE); + aeronClientDuplexConnection + .subscribe(new Subscriber() { + + @Override + public void onSubscribe(Subscription s) { + s.request(1); + } + + @Override + public void onNext(AeronClientDuplexConnection connection) { + ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection(connection, connectionSetupPayload, errorStream); + CountDownLatch latch = new CountDownLatch(1); + reactiveSocket.start(new Completable() { + @Override + public void success() { + latch.countDown(); + s.onNext(reactiveSocket); + s.onComplete(); + } + + @Override + public void error(Throwable e) { + s.onError(e); + } + }); + + try { + latch.await(timeout, timeUnit); + } catch (InterruptedException e) { + logger.error(e.getMessage(), e); + s.onError(e); + } + } + + @Override + public void onError(Throwable t) { + s.onError(t); + } + + @Override + public void onComplete() { + } + }); + }; + } + + private static InetAddress getIPv4InetAddress() { + InetAddress iaddress = null; + try { + String os = System.getProperty("os.name").toLowerCase(); + + if (os.contains("nix") || os.contains("nux")) { + NetworkInterface ni = NetworkInterface.getByName("eth0"); + + Enumeration ias = ni.getInetAddresses(); + + do { + iaddress = ias.nextElement(); + } while (!(iaddress instanceof Inet4Address)); + + } + + iaddress = InetAddress.getLocalHost(); // for Windows and OS X it should work well + } catch (Exception e) { + logger.error(e.getMessage(), e); + LangUtil.rethrowUnchecked(e); + } + + return iaddress; + } +} From 0804807bd291e9cd0a3d603aa2213c44207bc41f Mon Sep 17 00:00:00 2001 From: Ryland Degnan Date: Fri, 25 Mar 2016 10:14:16 -0700 Subject: [PATCH 079/105] Added ReactiveSocketFactory implementations for netty and jsr-356 --- .../client/AeronReactiveSocketFactory.java | 84 ++++++++--------- reactivesocket-jsr-356/build.gradle | 10 +- .../websocket/WebSocketDuplexConnection.java | 92 ++++++++----------- .../client/ReactiveSocketWebSocketClient.java | 49 +++++++++- .../WebSocketReactiveSocketFactory.java | 81 ++++++++++++++++ .../server/ReactiveSocketWebSocketServer.java | 13 ++- .../javax/websocket/ClientServerTest.java | 15 +-- .../reactivesocket/javax/websocket/Ping.java | 16 +--- .../ClientWebSocketDuplexConnection.java | 28 ++++-- .../WebSocketReactiveSocketFactory.java | 80 ++++++++++++++++ .../netty/websocket/ClientServerTest.java | 2 +- .../reactivesocket/netty/websocket/Ping.java | 2 +- 12 files changed, 322 insertions(+), 150 deletions(-) create mode 100644 reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketFactory.java create mode 100644 reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketFactory.java diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketFactory.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketFactory.java index 9f64580b2..b5888d224 100644 --- a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketFactory.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketFactory.java @@ -3,18 +3,18 @@ import io.reactivesocket.ConnectionSetupPayload; import io.reactivesocket.ReactiveSocket; import io.reactivesocket.ReactiveSocketFactory; -import io.reactivesocket.internal.rx.EmptySubscription; import io.reactivesocket.rx.Completable; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import rx.Observable; +import rx.RxReactiveStreams; import uk.co.real_logic.agrona.LangUtil; import java.net.*; import java.util.Enumeration; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -47,55 +47,45 @@ public AeronReactiveSocketFactory(String host, int port, ConnectionSetupPayload @Override public Publisher call(SocketAddress address, long timeout, TimeUnit timeUnit) { - Publisher aeronClientDuplexConnection + Publisher connection = AeronClientDuplexConnectionFactory.getInstance().createAeronClientDuplexConnection(address); - return (Subscriber s) -> { - s.onSubscribe(EmptySubscription.INSTANCE); - aeronClientDuplexConnection - .subscribe(new Subscriber() { - - @Override - public void onSubscribe(Subscription s) { - s.request(1); - } - - @Override - public void onNext(AeronClientDuplexConnection connection) { - ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection(connection, connectionSetupPayload, errorStream); - CountDownLatch latch = new CountDownLatch(1); - reactiveSocket.start(new Completable() { - @Override - public void success() { - latch.countDown(); - s.onNext(reactiveSocket); - s.onComplete(); - } - - @Override - public void error(Throwable e) { - s.onError(e); - } - }); - - try { - latch.await(timeout, timeUnit); - } catch (InterruptedException e) { - logger.error(e.getMessage(), e); + Observable result = Observable.create(s -> + connection.subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(1); + } + + @Override + public void onNext(AeronClientDuplexConnection connection) { + ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection(connection, connectionSetupPayload, errorStream); + reactiveSocket.start(new Completable() { + @Override + public void success() { + s.onNext(reactiveSocket); + s.onCompleted(); + } + + @Override + public void error(Throwable e) { s.onError(e); } - } - - @Override - public void onError(Throwable t) { - s.onError(t); - } - - @Override - public void onComplete() { - } - }); - }; + }); + } + + @Override + public void onError(Throwable t) { + s.onError(t); + } + + @Override + public void onComplete() { + } + }) + ); + + return RxReactiveStreams.toPublisher(result.timeout(timeout, timeUnit)); } private static InetAddress getIPv4InetAddress() { diff --git a/reactivesocket-jsr-356/build.gradle b/reactivesocket-jsr-356/build.gradle index 31d37a5b5..99e05d82c 100644 --- a/reactivesocket-jsr-356/build.gradle +++ b/reactivesocket-jsr-356/build.gradle @@ -1,13 +1,5 @@ -buildscript { - repositories { maven { url 'http://repo.spring.io/plugins-release' } } - dependencies { classpath 'org.springframework.build.gradle:propdeps-plugin:0.0.7' } -} - -apply plugin: 'propdeps' - dependencies { - provided 'javax.websocket:javax.websocket-api:1.1' - testCompile 'org.glassfish.tyrus:tyrus-client:1.12' + compile 'org.glassfish.tyrus:tyrus-client:1.12' testCompile 'org.glassfish.tyrus:tyrus-server:1.12' testCompile 'org.glassfish.tyrus:tyrus-container-grizzly-server:1.12' } \ No newline at end of file diff --git a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/WebSocketDuplexConnection.java b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/WebSocketDuplexConnection.java index 8de736588..c57028db3 100644 --- a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/WebSocketDuplexConnection.java +++ b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/WebSocketDuplexConnection.java @@ -19,76 +19,64 @@ import io.reactivesocket.Frame; import io.reactivesocket.rx.Completable; import io.reactivesocket.rx.Observable; -import io.reactivesocket.rx.Observer; import org.reactivestreams.Publisher; import rx.RxReactiveStreams; -import rx.Subscriber; +import rx.Subscription; +import rx.subscriptions.BooleanSubscription; -import javax.websocket.*; +import javax.websocket.Session; import java.io.IOException; -import java.util.concurrent.CopyOnWriteArrayList; public class WebSocketDuplexConnection implements DuplexConnection { - private Session session; + private final Session session; + private final rx.Observable input; - private CopyOnWriteArrayList> observers; - - private WebSocketDuplexConnection(Session session, rx.Observable input) { + public WebSocketDuplexConnection(Session session, rx.Observable input) { this.session = session; - this.observers = new CopyOnWriteArrayList<>(); - input.subscribe(new Subscriber() { - @Override - public void onNext(Frame frame) { - observers.forEach(o -> o.onNext(frame)); - } - - @Override - public void onError(Throwable e) { - observers.forEach(o -> o.onError(e)); - } - - @Override - public void onCompleted() { - observers.forEach(Observer::onComplete); - } - }); - } - - public static WebSocketDuplexConnection create(Session session, rx.Observable input) { - return new WebSocketDuplexConnection(session, input); + this.input = input; } @Override public Observable getInput() { - return new Observable() { - @Override - public void subscribe(Observer o) { - observers.add(o); - - o.onSubscribe(() -> - observers.removeIf(s -> s == o) - ); - } + return o -> { + Subscription subscription = input.subscribe(o::onNext, o::onError, o::onComplete); + o.onSubscribe(subscription::unsubscribe); }; } @Override public void addOutput(Publisher o, Completable callback) { - rx.Observable sent = RxReactiveStreams.toObservable(o).concatMap(frame -> - rx.Observable.create(subscriber -> { - session.getAsyncRemote().sendBinary(frame.getByteBuffer(), result -> { - if (result.isOK()) { - subscriber.onCompleted(); - } else { - subscriber.onError(result.getException()); - } - }); - }) - ); + rx.Completable sent = rx.Completable.concat(RxReactiveStreams.toObservable(o).map(frame -> + rx.Completable.create(s -> { + BooleanSubscription bs = new BooleanSubscription(); + s.onSubscribe(bs); + session.getAsyncRemote().sendBinary(frame.getByteBuffer(), result -> { + if (!bs.isUnsubscribed()) { + if (result.isOK()) { + s.onCompleted(); + } else { + s.onError(result.getException()); + } + } + }); + }) + )); + + sent.subscribe(new rx.Completable.CompletableSubscriber() { + @Override + public void onCompleted() { + callback.success(); + } - sent.doOnCompleted(callback::success) - .doOnError(callback::error) - .subscribe(); + @Override + public void onError(Throwable e) { + callback.error(e); + } + + @Override + public void onSubscribe(Subscription s) { + } + }); } @Override diff --git a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/ReactiveSocketWebSocketClient.java b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/ReactiveSocketWebSocketClient.java index 4e7ad3ca3..d952b8ee5 100644 --- a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/ReactiveSocketWebSocketClient.java +++ b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/ReactiveSocketWebSocketClient.java @@ -16,25 +16,68 @@ package io.reactivesocket.javax.websocket.client; import io.reactivesocket.Frame; +import io.reactivesocket.javax.websocket.WebSocketDuplexConnection; +import org.glassfish.tyrus.client.ClientManager; +import org.glassfish.tyrus.client.ClientProperties; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; import rx.Observable; import rx.subjects.PublishSubject; import javax.websocket.*; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.URI; +import java.net.URISyntaxException; import java.nio.ByteBuffer; public class ReactiveSocketWebSocketClient extends Endpoint { - private final PublishSubject input; + private final PublishSubject input = PublishSubject.create(); + private final Subscriber subscriber; - public ReactiveSocketWebSocketClient() { - this.input = PublishSubject.create(); + public ReactiveSocketWebSocketClient(Subscriber subscriber) { + this.subscriber = subscriber; } public Observable getInput() { return input; } + public static Publisher create(SocketAddress socketAddress, String path, ClientManager clientManager) { + if (socketAddress instanceof InetSocketAddress) { + InetSocketAddress address = (InetSocketAddress)socketAddress; + try { + return create(new URI("ws", null, address.getHostName(), address.getPort(), path, null, null), clientManager); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } else { + throw new IllegalArgumentException("unknown socket address type => " + socketAddress.getClass()); + } + } + + public static Publisher create(URI uri, ClientManager clientManager) { + return s -> { + try { + clientManager.getProperties().put(ClientProperties.RECONNECT_HANDLER, new ClientManager.ReconnectHandler() { + @Override + public boolean onConnectFailure(Exception exception) { + s.onError(exception); + return false; + } + }); + ReactiveSocketWebSocketClient endpoint = new ReactiveSocketWebSocketClient(s); + clientManager.asyncConnectToServer(endpoint, null, uri); + } catch (DeploymentException e) { + s.onError(e); + } + }; + } + @Override public void onOpen(Session session, EndpointConfig config) { + subscriber.onNext(new WebSocketDuplexConnection(session, input)); + subscriber.onComplete(); session.addMessageHandler(new MessageHandler.Whole() { @Override public void onMessage(ByteBuffer message) { diff --git a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketFactory.java b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketFactory.java new file mode 100644 index 000000000..8e4f19e5d --- /dev/null +++ b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketFactory.java @@ -0,0 +1,81 @@ +package io.reactivesocket.javax.websocket.client; + +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.ReactiveSocketFactory; +import io.reactivesocket.javax.websocket.WebSocketDuplexConnection; +import io.reactivesocket.rx.Completable; +import org.glassfish.tyrus.client.ClientManager; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.Observable; +import rx.RxReactiveStreams; + +import java.net.SocketAddress; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * An implementation of {@link ReactiveSocketFactory} that creates JSR-356 WebSocket ReactiveSockets. + */ +public class WebSocketReactiveSocketFactory implements ReactiveSocketFactory { + private static final Logger logger = LoggerFactory.getLogger(WebSocketReactiveSocketFactory.class); + + private final ConnectionSetupPayload connectionSetupPayload; + private final Consumer errorStream; + private final String path; + private final ClientManager clientManager; + + public WebSocketReactiveSocketFactory(String path, ClientManager clientManager, ConnectionSetupPayload connectionSetupPayload, Consumer errorStream) { + this.connectionSetupPayload = connectionSetupPayload; + this.errorStream = errorStream; + this.path = path; + this.clientManager = clientManager; + } + + @Override + public Publisher call(SocketAddress address, long timeout, TimeUnit timeUnit) { + Publisher connection + = ReactiveSocketWebSocketClient.create(address, path, clientManager); + + Observable result = Observable.create(s -> + connection.subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(1); + } + + @Override + public void onNext(WebSocketDuplexConnection connection) { + ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection(connection, connectionSetupPayload, errorStream); + reactiveSocket.start(new Completable() { + @Override + public void success() { + s.onNext(reactiveSocket); + s.onCompleted(); + } + + @Override + public void error(Throwable e) { + s.onError(e); + } + }); + } + + @Override + public void onError(Throwable t) { + s.onError(t); + } + + @Override + public void onComplete() { + } + }) + ); + + return RxReactiveStreams.toPublisher(result.timeout(timeout, timeUnit)); + } +} diff --git a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/server/ReactiveSocketWebSocketServer.java b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/server/ReactiveSocketWebSocketServer.java index b16630899..826cfe8ea 100644 --- a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/server/ReactiveSocketWebSocketServer.java +++ b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/server/ReactiveSocketWebSocketServer.java @@ -19,8 +19,8 @@ import io.reactivesocket.Frame; import io.reactivesocket.LeaseGovernor; import io.reactivesocket.ReactiveSocket; -import io.reactivesocket.rx.Completable; import io.reactivesocket.javax.websocket.WebSocketDuplexConnection; +import io.reactivesocket.rx.Completable; import rx.subjects.PublishSubject; import uk.co.real_logic.agrona.LangUtil; @@ -29,14 +29,13 @@ import java.util.concurrent.ConcurrentHashMap; public class ReactiveSocketWebSocketServer extends Endpoint { - private final PublishSubject input; - private final ConcurrentHashMap reactiveSockets; + private final PublishSubject input = PublishSubject.create(); + private final ConcurrentHashMap reactiveSockets = new ConcurrentHashMap<>(); + private final ConnectionSetupHandler setupHandler; private final LeaseGovernor leaseGovernor; protected ReactiveSocketWebSocketServer(ConnectionSetupHandler setupHandler, LeaseGovernor leaseGovernor) { - this.input = PublishSubject.create(); - this.reactiveSockets = new ConcurrentHashMap<>(); this.setupHandler = setupHandler; this.leaseGovernor = leaseGovernor; } @@ -55,11 +54,11 @@ public void onMessage(ByteBuffer message) { } }); - WebSocketDuplexConnection webSocketDuplexConnection = WebSocketDuplexConnection.create(session, input); + WebSocketDuplexConnection connection = new WebSocketDuplexConnection(session, input); final ReactiveSocket reactiveSocket = reactiveSockets.computeIfAbsent(session.getId(), id -> ReactiveSocket.fromServerConnection( - webSocketDuplexConnection, + connection, setupHandler, leaseGovernor, t -> t.printStackTrace() diff --git a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerTest.java b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerTest.java index 215b609fd..ffb94ec91 100644 --- a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerTest.java +++ b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerTest.java @@ -28,14 +28,12 @@ import rx.RxReactiveStreams; import rx.observers.TestSubscriber; -import javax.websocket.ClientEndpointConfig; import javax.websocket.DeploymentException; import javax.websocket.Endpoint; -import javax.websocket.Session; import javax.websocket.server.ServerApplicationConfig; import javax.websocket.server.ServerEndpointConfig; import java.io.IOException; -import java.net.URI; +import java.net.InetSocketAddress; import java.net.URISyntaxException; import java.util.Collections; import java.util.HashSet; @@ -68,14 +66,11 @@ public static void setup() throws URISyntaxException, DeploymentException, IOExc server = new Server("localhost", 8025, null, null, ApplicationConfig.class); server.start(); - ReactiveSocketWebSocketClient endpoint = new ReactiveSocketWebSocketClient(); - ClientManager clientManager = ClientManager.createClient(); - ClientEndpointConfig cec = ClientEndpointConfig.Builder.create().build(); - Session session = clientManager.connectToServer(endpoint, cec, new URI("ws://localhost:8025/rs")); + WebSocketDuplexConnection duplexConnection = RxReactiveStreams.toObservable( + ReactiveSocketWebSocketClient.create(InetSocketAddress.createUnresolved("localhost", 8025), "/rs", ClientManager.createClient()) + ).toBlocking().single(); - client = ReactiveSocket.fromClientConnection( - WebSocketDuplexConnection.create(session, endpoint.getInput()), - ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS)); + client = ReactiveSocket.fromClientConnection(duplexConnection, ConnectionSetupPayload.create("UTF-8", "UTF-8"), t -> t.printStackTrace()); client.startAndWait(); } diff --git a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/Ping.java b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/Ping.java index 6958ab88a..bc3bb6941 100644 --- a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/Ping.java +++ b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/Ping.java @@ -21,29 +21,23 @@ import io.reactivesocket.javax.websocket.client.ReactiveSocketWebSocketClient; import org.HdrHistogram.Recorder; import org.glassfish.tyrus.client.ClientManager; +import org.reactivestreams.Publisher; import rx.Observable; import rx.RxReactiveStreams; import rx.Subscriber; import rx.schedulers.Schedulers; -import javax.websocket.ClientEndpointConfig; -import javax.websocket.Session; -import java.net.URI; +import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; public class Ping { public static void main(String... args) throws Exception { + Publisher publisher = ReactiveSocketWebSocketClient.create(InetSocketAddress.createUnresolved("localhost", 8025), "/rs", ClientManager.createClient()); - ReactiveSocketWebSocketClient endpoint = new ReactiveSocketWebSocketClient(); - ClientManager clientManager = ClientManager.createClient(); - ClientEndpointConfig cec = ClientEndpointConfig.Builder.create().build(); - Session session = clientManager.connectToServer(endpoint, cec, new URI("ws://localhost:8025/rs")); - - ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection( - WebSocketDuplexConnection.create(session, endpoint.getInput()), ConnectionSetupPayload.create("UTF-8", "UTF-8") - ); + WebSocketDuplexConnection duplexConnection = RxReactiveStreams.toObservable(publisher).toBlocking().single(); + ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection(duplexConnection, ConnectionSetupPayload.create("UTF-8", "UTF-8")); reactiveSocket.startAndWait(); diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java index 1632a6886..301b1227e 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java @@ -25,7 +25,6 @@ import io.netty.handler.codec.http.HttpClientCodec; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.websocketx.*; -import io.netty.util.concurrent.Future; import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; import io.reactivesocket.rx.Completable; @@ -37,7 +36,9 @@ import java.io.IOException; import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.net.URI; +import java.net.URISyntaxException; import java.util.concurrent.CopyOnWriteArrayList; public class ClientWebSocketDuplexConnection implements DuplexConnection { @@ -53,14 +54,23 @@ private ClientWebSocketDuplexConnection(Channel channel, Bootstrap bootstrap, C this.bootstrap = bootstrap; } - public static Publisher create(InetSocketAddress address, EventLoopGroup eventLoopGroup) { + public static Publisher create(SocketAddress socketAddress, String path, EventLoopGroup eventLoopGroup) { + if (socketAddress instanceof InetSocketAddress) { + InetSocketAddress address = (InetSocketAddress)socketAddress; + try { + return create(new URI("ws", null, address.getHostName(), address.getPort(), path, null, null), eventLoopGroup); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } else { + throw new IllegalArgumentException("unknown socket address type => " + socketAddress.getClass()); + } + } + + public static Publisher create(URI uri, EventLoopGroup eventLoopGroup) { return s -> { WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker( - URI.create("ws://" + address.getHostName() + ":" + address.getPort() + "/rs"), - WebSocketVersion.V13, - null, - false, - new DefaultHttpHeaders()); + uri, WebSocketVersion.V13, null, false, new DefaultHttpHeaders()); CopyOnWriteArrayList> subjects = new CopyOnWriteArrayList<>(); ReactiveSocketClientHandler clientHandler = new ReactiveSocketClientHandler(subjects); @@ -79,7 +89,7 @@ protected void initChannel(SocketChannel ch) throws Exception { clientHandler ); } - }).connect(address); + }).connect(uri.getHost(), uri.getPort()); connect.addListener(connectFuture -> { if (connectFuture.isSuccess()) { @@ -123,7 +133,7 @@ public void onNext(Frame frame) { ByteBuf byteBuf = Unpooled.wrappedBuffer(frame.getByteBuffer()); BinaryWebSocketFrame binaryWebSocketFrame = new BinaryWebSocketFrame(byteBuf); ChannelFuture channelFuture = channel.writeAndFlush(binaryWebSocketFrame); - channelFuture.addListener((Future future) -> { + channelFuture.addListener(future -> { Throwable cause = future.cause(); if (cause != null) { callback.error(cause); diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketFactory.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketFactory.java new file mode 100644 index 000000000..5be815420 --- /dev/null +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketFactory.java @@ -0,0 +1,80 @@ +package io.reactivesocket.netty.websocket.client; + +import io.netty.channel.EventLoopGroup; +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.ReactiveSocketFactory; +import io.reactivesocket.rx.Completable; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.Observable; +import rx.RxReactiveStreams; + +import java.net.SocketAddress; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * An implementation of {@link ReactiveSocketFactory} that creates Netty WebSocket ReactiveSockets. + */ +public class WebSocketReactiveSocketFactory implements ReactiveSocketFactory { + private static final Logger logger = LoggerFactory.getLogger(WebSocketReactiveSocketFactory.class); + + private final ConnectionSetupPayload connectionSetupPayload; + private final Consumer errorStream; + private final String path; + private final EventLoopGroup eventLoopGroup; + + public WebSocketReactiveSocketFactory(String path, EventLoopGroup eventLoopGroup, ConnectionSetupPayload connectionSetupPayload, Consumer errorStream) { + this.connectionSetupPayload = connectionSetupPayload; + this.errorStream = errorStream; + this.path = path; + this.eventLoopGroup = eventLoopGroup; + } + + @Override + public Publisher call(SocketAddress address, long timeout, TimeUnit timeUnit) { + Publisher connection + = ClientWebSocketDuplexConnection.create(address, path, eventLoopGroup); + + Observable result = Observable.create(s -> + connection.subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(1); + } + + @Override + public void onNext(ClientWebSocketDuplexConnection connection) { + ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection(connection, connectionSetupPayload, errorStream); + reactiveSocket.start(new Completable() { + @Override + public void success() { + s.onNext(reactiveSocket); + s.onCompleted(); + } + + @Override + public void error(Throwable e) { + s.onError(e); + } + }); + } + + @Override + public void onError(Throwable t) { + s.onError(t); + } + + @Override + public void onComplete() { + } + }) + ); + + return RxReactiveStreams.toPublisher(result.timeout(timeout, timeUnit)); + } +} diff --git a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java index f932a14bd..593c28553 100644 --- a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java +++ b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java @@ -123,7 +123,7 @@ protected void initChannel(Channel ch) throws Exception { serverChannel = b.bind("localhost", 8025).sync().channel(); ClientWebSocketDuplexConnection duplexConnection = RxReactiveStreams.toObservable( - ClientWebSocketDuplexConnection.create(InetSocketAddress.createUnresolved("localhost", 8025), new NioEventLoopGroup()) + ClientWebSocketDuplexConnection.create(InetSocketAddress.createUnresolved("localhost", 8025), "/rs", new NioEventLoopGroup()) ).toBlocking().single(); client = ReactiveSocket diff --git a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Ping.java b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Ping.java index b60440a53..0c3eba5d1 100644 --- a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Ping.java +++ b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Ping.java @@ -34,7 +34,7 @@ public class Ping { public static void main(String... args) throws Exception { - Publisher publisher = ClientWebSocketDuplexConnection.create(InetSocketAddress.createUnresolved("localhost", 8025), new NioEventLoopGroup(1)); + Publisher publisher = ClientWebSocketDuplexConnection.create(InetSocketAddress.createUnresolved("localhost", 8025), "/rs", new NioEventLoopGroup(1)); ClientWebSocketDuplexConnection duplexConnection = RxReactiveStreams.toObservable(publisher).toBlocking().last(); ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection(duplexConnection, ConnectionSetupPayload.create("UTF-8", "UTF-8"), t -> t.printStackTrace()); From 8a75602ef058c528a3c20c70818dc625b89d5da3 Mon Sep 17 00:00:00 2001 From: Ryland Degnan Date: Fri, 25 Mar 2016 11:25:57 -0700 Subject: [PATCH 080/105] Fix publishing --- build.gradle | 18 +++++++++--------- .../client/WebSocketReactiveSocketFactory.java | 15 +++++++++++++++ .../client/WebSocketReactiveSocketFactory.java | 15 +++++++++++++++ 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index d6f60c5ad..03f77020c 100644 --- a/build.gradle +++ b/build.gradle @@ -23,14 +23,14 @@ subprojects { testCompile 'org.mockito:mockito-core:1.8.5' testRuntime 'org.slf4j:slf4j-simple:1.7.12' } +} - // support for snapshot/final releases via versioned branch names like 1.x - nebulaRelease { - addReleaseBranchPattern(/\d+\.\d+\.\d+/) - addReleaseBranchPattern('HEAD') - } - - if (project.hasProperty('release.useLastTag')) { - tasks.prepare.enabled = false - } +// support for snapshot/final releases via versioned branch names like 1.x +nebulaRelease { + addReleaseBranchPattern(/\d+\.\d+\.\d+/) + addReleaseBranchPattern('HEAD') } + +if (project.hasProperty('release.useLastTag')) { + tasks.prepare.enabled = false +} \ No newline at end of file diff --git a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketFactory.java b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketFactory.java index 8e4f19e5d..28ec2a7e5 100644 --- a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketFactory.java +++ b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketFactory.java @@ -1,3 +1,18 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.javax.websocket.client; import io.reactivesocket.ConnectionSetupPayload; diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketFactory.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketFactory.java index 5be815420..f36731151 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketFactory.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketFactory.java @@ -1,3 +1,18 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.netty.websocket.client; import io.netty.channel.EventLoopGroup; From 60b90a50bf9ccac792fbc39768ef398d8c37aa9c Mon Sep 17 00:00:00 2001 From: Ryland Degnan Date: Fri, 25 Mar 2016 17:02:52 -0700 Subject: [PATCH 081/105] Upgrade gradle-reactivesocket-plugin to 1.0.4 --- build.gradle | 4 ++-- reactivesocket-netty/build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 03f77020c..c4da2900a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { repositories { jcenter() } - dependencies { classpath 'io.reactivesocket:gradle-nebula-plugin-reactivesocket:1.0.3' } + dependencies { classpath 'io.reactivesocket:gradle-nebula-plugin-reactivesocket:1.0.4' } } apply plugin: 'reactivesocket-project' @@ -10,7 +10,7 @@ subprojects { apply plugin: 'java' repositories { - maven { url 'https://oss.jfrog.org/libs-snapshot' } + maven { url 'https://dl.bintray.com/reactivesocket/ReactiveSocket' } } dependencies { diff --git a/reactivesocket-netty/build.gradle b/reactivesocket-netty/build.gradle index 006bb8454..4fc5e17ef 100644 --- a/reactivesocket-netty/build.gradle +++ b/reactivesocket-netty/build.gradle @@ -1,3 +1,3 @@ dependencies { - compile 'io.netty:netty-all:4.1.0.CR3' + compile 'io.netty:netty-all:4.1.0.CR4' } \ No newline at end of file From cf65dd695e979e375188ee92a32da5865c22394b Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Thu, 31 Mar 2016 09:07:04 -0700 Subject: [PATCH 082/105] updated to use use reactivesocket 0.0.5, and added ability to schedule tasks to run on the server event loop --- build.gradle | 2 +- reactivesocket-aeron/build.gradle | 3 +- .../aeron/example/MediaDriver.java | 13 ++-- .../aeron/example/fireandforget/Fire.java | 3 +- .../aeron/example/requestreply/Ping.java | 7 +- .../client/AeronClientDuplexConnection.java | 4 +- .../AeronClientDuplexConnectionFactory.java | 14 ++-- .../client/AeronReactiveSocketFactory.java | 19 +++-- .../aeron/client/ClientAeronManager.java | 16 ++-- .../aeron/client/FrameHolder.java | 7 +- .../aeron/client/PollingAction.java | 2 +- .../aeron/internal/AeronUtil.java | 16 ++-- .../aeron/internal/Constants.java | 9 ++- .../server/AeronServerDuplexConnection.java | 10 ++- .../server/ReactiveSocketAeronServer.java | 27 ++++--- .../aeron/server/ServerAeronManager.java | 73 +++++++++++++++---- .../aeron/server/ServerSubscription.java | 8 +- .../server/TimerWheelFairLeaseGovernor.java | 6 +- .../rx/ReactiveSocketAeronScheduler.java | 3 +- .../aeron/DummySubscription.java | 8 +- .../io/reactivesocket/aeron/TestUtil.java | 2 +- .../aeron/client/ReactiveSocketAeronTest.java | 18 +++-- .../aeron/internal/AeronUtilTest.java | 6 +- .../aeron/server/ServerAeronManagerTest.java | 72 ++++++++++++++++++ .../rx/ReactiveSocketAeronSchedulerTest.java | 2 +- .../WebSocketReactiveSocketFactory.java | 11 +-- .../server/ReactiveSocketWebSocketServer.java | 11 ++- .../javax/websocket/ClientServerTest.java | 3 +- .../reactivesocket/javax/websocket/Ping.java | 3 +- .../javax/websocket/TestUtil.java | 2 +- .../netty/MutableDirectByteBuf.java | 41 ++++++++++- .../WebSocketReactiveSocketFactory.java | 11 +-- .../server/ReactiveSocketServerHandler.java | 3 +- 33 files changed, 310 insertions(+), 125 deletions(-) rename reactivesocket-aeron/src/perf/java/{uk/co/real_logic => io}/aeron/DummySubscription.java (88%) create mode 100644 reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/server/ServerAeronManagerTest.java diff --git a/build.gradle b/build.gradle index c4da2900a..8096de800 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ subprojects { dependencies { compile 'io.reactivex:rxjava:1.1.2' compile 'io.reactivex:rxjava-reactive-streams:1.0.1' - compile 'io.reactivesocket:reactivesocket:0.0.4' + compile 'io.reactivesocket:reactivesocket:0.0.5' compile 'org.hdrhistogram:HdrHistogram:2.1.7' compile 'org.slf4j:slf4j-api:1.7.12' testCompile 'junit:junit:4.12' diff --git a/reactivesocket-aeron/build.gradle b/reactivesocket-aeron/build.gradle index 3890fdd4c..1af7d4015 100644 --- a/reactivesocket-aeron/build.gradle +++ b/reactivesocket-aeron/build.gradle @@ -1,4 +1,3 @@ dependencies { - compile 'uk.co.real-logic:Agrona:0.4.8' - compile 'uk.co.real-logic:aeron-all:0.2.2' + compile 'io.aeron:aeron-all:0.9.5' } \ No newline at end of file diff --git a/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/MediaDriver.java b/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/MediaDriver.java index 1fd41739b..3d89d02e0 100644 --- a/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/MediaDriver.java +++ b/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/MediaDriver.java @@ -15,9 +15,8 @@ */ package io.reactivesocket.aeron.example; -import uk.co.real_logic.aeron.driver.ThreadingMode; -import uk.co.real_logic.agrona.concurrent.BackoffIdleStrategy; -import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; +import io.aeron.driver.ThreadingMode; +import org.agrona.concurrent.BackoffIdleStrategy; public class MediaDriver { public static void main(String... args) { @@ -31,14 +30,14 @@ public static void main(String... args) { System.out.println("ThreadingMode => " + threadingMode); - final uk.co.real_logic.aeron.driver.MediaDriver.Context ctx = new uk.co.real_logic.aeron.driver.MediaDriver.Context() + final io.aeron.driver.MediaDriver.Context ctx = new io.aeron.driver.MediaDriver.Context() .threadingMode(threadingMode) .dirsDeleteOnStart(true) .conductorIdleStrategy(new BackoffIdleStrategy(1, 1, 100, 1000)) - .receiverIdleStrategy(new NoOpIdleStrategy()) - .senderIdleStrategy(new NoOpIdleStrategy()); + .receiverIdleStrategy(new BackoffIdleStrategy(1, 1, 100, 1000)) + .senderIdleStrategy(new BackoffIdleStrategy(1, 1, 100, 1000)); - final uk.co.real_logic.aeron.driver.MediaDriver ignored = uk.co.real_logic.aeron.driver.MediaDriver.launch(ctx); + final io.aeron.driver.MediaDriver ignored = io.aeron.driver.MediaDriver.launch(ctx); } } diff --git a/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/fireandforget/Fire.java b/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/fireandforget/Fire.java index d7b19f6ce..2b3e7db61 100644 --- a/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/fireandforget/Fire.java +++ b/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/fireandforget/Fire.java @@ -17,6 +17,7 @@ import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.DefaultReactiveSocket; import io.reactivesocket.Payload; import io.reactivesocket.ReactiveSocket; import io.reactivesocket.aeron.client.AeronClientDuplexConnection; @@ -61,7 +62,7 @@ public static void main(String... args) throws Exception { AeronClientDuplexConnection connection = RxReactiveStreams.toObservable(udpConnection).toBlocking().single(); System.out.println("Created duplex connection"); - ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection(connection, ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS)); + ReactiveSocket reactiveSocket = DefaultReactiveSocket.fromClientConnection(connection, ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS)); reactiveSocket.startAndWait(); CountDownLatch latch = new CountDownLatch(Integer.MAX_VALUE); diff --git a/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/requestreply/Ping.java b/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/requestreply/Ping.java index 38281a3ab..7e7e7fa68 100644 --- a/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/requestreply/Ping.java +++ b/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/requestreply/Ping.java @@ -16,6 +16,7 @@ package io.reactivesocket.aeron.example.requestreply; import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.DefaultReactiveSocket; import io.reactivesocket.Payload; import io.reactivesocket.ReactiveSocket; import io.reactivesocket.aeron.client.AeronClientDuplexConnection; @@ -63,7 +64,7 @@ public static void main(String... args) throws Exception { AeronClientDuplexConnection connection = RxReactiveStreams.toObservable(udpConnection).toBlocking().single(); System.out.println("Created duplex connection"); - ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection(connection, ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS)); + ReactiveSocket reactiveSocket = DefaultReactiveSocket.fromClientConnection(connection, ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS)); reactiveSocket.startAndWait(); CountDownLatch latch = new CountDownLatch(Integer.MAX_VALUE); @@ -83,7 +84,7 @@ public static void main(String... args) throws Exception { System.out.println("---- PING/ PONG HISTO ----"); - }, 10, 10, TimeUnit.SECONDS); + }, 1, 1, TimeUnit.SECONDS); Observable .range(1, Integer.MAX_VALUE) @@ -112,7 +113,7 @@ public ByteBuffer getMetadata() { long diff = System.nanoTime() - start; histogram.recordValue(diff); }); - }) + }, 16) .subscribe(new Subscriber() { @Override public void onCompleted() { diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java index 1c1288a21..7d7c4a3cd 100644 --- a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java @@ -15,6 +15,7 @@ */ package io.reactivesocket.aeron.client; +import io.aeron.Publication; import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; import io.reactivesocket.aeron.internal.Loggable; @@ -22,11 +23,10 @@ import io.reactivesocket.rx.Disposable; import io.reactivesocket.rx.Observable; import io.reactivesocket.rx.Observer; +import org.agrona.concurrent.AbstractConcurrentArrayQueue; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; -import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.agrona.concurrent.AbstractConcurrentArrayQueue; import java.io.IOException; import java.util.concurrent.CopyOnWriteArrayList; diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java index 00e54eb6e..576a2e4ba 100644 --- a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java @@ -23,13 +23,13 @@ import io.reactivesocket.rx.Observer; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; -import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.aeron.logbuffer.FragmentHandler; -import uk.co.real_logic.aeron.logbuffer.Header; -import uk.co.real_logic.agrona.BitUtil; -import uk.co.real_logic.agrona.DirectBuffer; -import uk.co.real_logic.agrona.concurrent.ManyToManyConcurrentArrayQueue; -import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; +import io.aeron.Publication; +import io.aeron.logbuffer.FragmentHandler; +import io.aeron.logbuffer.Header; +import org.agrona.BitUtil; +import org.agrona.DirectBuffer; +import org.agrona.concurrent.ManyToManyConcurrentArrayQueue; +import org.agrona.concurrent.UnsafeBuffer; import java.net.InetSocketAddress; import java.net.SocketAddress; diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketFactory.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketFactory.java index b5888d224..460aa3d0a 100644 --- a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketFactory.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketFactory.java @@ -1,9 +1,12 @@ package io.reactivesocket.aeron.client; import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.DefaultReactiveSocket; import io.reactivesocket.ReactiveSocket; import io.reactivesocket.ReactiveSocketFactory; +import io.reactivesocket.ReactiveSocketSocketAddressFactory; import io.reactivesocket.rx.Completable; +import org.agrona.LangUtil; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; @@ -11,17 +14,19 @@ import org.slf4j.LoggerFactory; import rx.Observable; import rx.RxReactiveStreams; -import uk.co.real_logic.agrona.LangUtil; -import java.net.*; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.NetworkInterface; +import java.net.SocketAddress; import java.util.Enumeration; -import java.util.concurrent.TimeUnit; import java.util.function.Consumer; /** * An implementation of {@link ReactiveSocketFactory} that creates Aeron ReactiveSockets. */ -public class AeronReactiveSocketFactory implements ReactiveSocketFactory { +public class AeronReactiveSocketFactory implements ReactiveSocketSocketAddressFactory { private static final Logger logger = LoggerFactory.getLogger(AeronReactiveSocketFactory.class); private final ConnectionSetupPayload connectionSetupPayload; @@ -46,7 +51,7 @@ public AeronReactiveSocketFactory(String host, int port, ConnectionSetupPayload } @Override - public Publisher call(SocketAddress address, long timeout, TimeUnit timeUnit) { + public Publisher call(SocketAddress address) { Publisher connection = AeronClientDuplexConnectionFactory.getInstance().createAeronClientDuplexConnection(address); @@ -59,7 +64,7 @@ public void onSubscribe(Subscription s) { @Override public void onNext(AeronClientDuplexConnection connection) { - ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection(connection, connectionSetupPayload, errorStream); + ReactiveSocket reactiveSocket = DefaultReactiveSocket.fromClientConnection(connection, connectionSetupPayload, errorStream); reactiveSocket.start(new Completable() { @Override public void success() { @@ -85,7 +90,7 @@ public void onComplete() { }) ); - return RxReactiveStreams.toPublisher(result.timeout(timeout, timeUnit)); + return RxReactiveStreams.toPublisher(result); } private static InetAddress getIPv4InetAddress() { diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java index 2b2bc559f..f98881af9 100644 --- a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java @@ -18,11 +18,11 @@ import io.reactivesocket.aeron.internal.Loggable; import rx.Scheduler; import rx.schedulers.Schedulers; -import uk.co.real_logic.aeron.Aeron; -import uk.co.real_logic.aeron.FragmentAssembler; -import uk.co.real_logic.aeron.Image; -import uk.co.real_logic.aeron.Subscription; -import uk.co.real_logic.aeron.logbuffer.FragmentHandler; +import io.aeron.Aeron; +import io.aeron.FragmentAssembler; +import io.aeron.Image; +import io.aeron.Subscription; +import io.aeron.logbuffer.FragmentHandler; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; @@ -47,8 +47,8 @@ private ClientAeronManager() { final Aeron.Context ctx = new Aeron.Context(); ctx.errorHandler(t -> error("an exception occurred", t)); - ctx.availableImageHandler((Image image, Subscription subscription, long joiningPosition, String sourceIdentity) -> - debug("New image available with session id => {} and sourceIdentity => {} and subscription => {}", image.sessionId(), sourceIdentity, subscription.toString()) + ctx.availableImageHandler((Image image) -> + debug("New image available with session id => {} and sourceIdentity => {} and subscription => {}", image.sessionId(), image.sourceIdentity(), image.subscription().toString()) ); aeron = Aeron.connect(ctx); @@ -117,7 +117,7 @@ void poll() { */ /** - * Creates a logic group of {@link uk.co.real_logic.aeron.Subscription}s to a particular channel. + * Creates a logic group of {@link io.aeron.Subscription}s to a particular channel. */ public static class SubscriptionGroup { diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java index 5e910b0b7..6e5a2017c 100644 --- a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java @@ -15,15 +15,14 @@ */ package io.reactivesocket.aeron.client; +import io.aeron.Publication; import io.reactivesocket.Frame; import org.HdrHistogram.Recorder; +import org.agrona.concurrent.OneToOneConcurrentArrayQueue; import org.reactivestreams.Subscription; -import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.agrona.concurrent.OneToOneConcurrentArrayQueue; - /** * Holds a frame and the publication that it's supposed to be sent on. - * Pools instances on an {@link uk.co.real_logic.agrona.concurrent.OneToOneConcurrentArrayQueue} + * Pools instances on an {@link OneToOneConcurrentArrayQueue} */ public class FrameHolder { private static final ThreadLocal> FRAME_HOLDER_QUEUE diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/PollingAction.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/PollingAction.java index 4b89a08c4..2a4b8140a 100644 --- a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/PollingAction.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/PollingAction.java @@ -15,9 +15,9 @@ */ package io.reactivesocket.aeron.client; +import io.aeron.Subscription; import io.reactivesocket.aeron.internal.Loggable; import rx.functions.Action0; -import uk.co.real_logic.aeron.Subscription; import java.util.List; diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java index 3c72112d6..5088ca33b 100644 --- a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java @@ -15,11 +15,11 @@ */ package io.reactivesocket.aeron.internal; -import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.aeron.logbuffer.BufferClaim; -import uk.co.real_logic.agrona.MutableDirectBuffer; -import uk.co.real_logic.agrona.concurrent.OneToOneConcurrentArrayQueue; -import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; +import io.aeron.Publication; +import io.aeron.logbuffer.BufferClaim; +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.OneToOneConcurrentArrayQueue; +import org.agrona.concurrent.UnsafeBuffer; import java.util.concurrent.TimeUnit; @@ -41,7 +41,7 @@ public class AeronUtil implements Loggable { * This method of sending data does need to know how long the message is. * * @param publication publication to send the message on - * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} + * @param fillBuffer closure passed in to fill a {@link MutableDirectBuffer} * that is send over Aeron */ public static void offer(Publication publication, BufferFiller fillBuffer, int length, int timeout, TimeUnit timeUnit) { @@ -76,7 +76,7 @@ public static void offer(Publication publication, BufferFiller fillBuffer, int l * In order to use this method of sending data you need to know the length of data. * * @param publication publication to send the message on - * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} + * @param fillBuffer closure passed in to fill a {@link MutableDirectBuffer} * that is send over Aeron * @param length the length of data */ @@ -114,7 +114,7 @@ public static void tryClaim(Publication publication, BufferFiller fillBuffer, in * size it will use offer instead. * * @param publication publication to send the message on - * @param fillBuffer closure passed in to fill a {@link uk.co.real_logic.agrona.MutableDirectBuffer} + * @param fillBuffer closure passed in to fill a {@link MutableDirectBuffer} * that is send over Aeron * @param length the length of data */ diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/Constants.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/Constants.java index 6fbe2c1b9..dd28369b8 100644 --- a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/Constants.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/Constants.java @@ -15,10 +15,11 @@ */ package io.reactivesocket.aeron.internal; -import uk.co.real_logic.agrona.concurrent.BackoffIdleStrategy; -import uk.co.real_logic.agrona.concurrent.IdleStrategy; -import uk.co.real_logic.agrona.concurrent.NoOpIdleStrategy; -import uk.co.real_logic.agrona.concurrent.SleepingIdleStrategy; + +import org.agrona.concurrent.BackoffIdleStrategy; +import org.agrona.concurrent.IdleStrategy; +import org.agrona.concurrent.NoOpIdleStrategy; +import org.agrona.concurrent.SleepingIdleStrategy; import java.util.concurrent.TimeUnit; diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java index e01be7481..0418a21c7 100644 --- a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java @@ -15,16 +15,20 @@ */ package io.reactivesocket.aeron.server; +import io.aeron.Publication; import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; -import io.reactivesocket.aeron.internal.*; +import io.reactivesocket.aeron.internal.AeronUtil; +import io.reactivesocket.aeron.internal.Constants; +import io.reactivesocket.aeron.internal.Loggable; +import io.reactivesocket.aeron.internal.MessageType; +import io.reactivesocket.aeron.internal.NotConnectedException; import io.reactivesocket.rx.Completable; import io.reactivesocket.rx.Disposable; import io.reactivesocket.rx.Observable; import io.reactivesocket.rx.Observer; +import org.agrona.BitUtil; import org.reactivestreams.Publisher; -import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.agrona.BitUtil; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java index cbbdb3f1f..452158029 100644 --- a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java @@ -15,18 +15,23 @@ */ package io.reactivesocket.aeron.server; +import io.aeron.Aeron; +import io.aeron.FragmentAssembler; +import io.aeron.Image; +import io.aeron.Publication; +import io.aeron.Subscription; +import io.aeron.logbuffer.Header; import io.reactivesocket.ConnectionSetupHandler; +import io.reactivesocket.DefaultReactiveSocket; import io.reactivesocket.Frame; import io.reactivesocket.LeaseGovernor; import io.reactivesocket.ReactiveSocket; import io.reactivesocket.aeron.internal.Loggable; import io.reactivesocket.aeron.internal.MessageType; import io.reactivesocket.rx.Observer; -import uk.co.real_logic.aeron.*; -import uk.co.real_logic.aeron.logbuffer.Header; -import uk.co.real_logic.agrona.BitUtil; -import uk.co.real_logic.agrona.DirectBuffer; -import uk.co.real_logic.agrona.concurrent.UnsafeBuffer; +import org.agrona.BitUtil; +import org.agrona.DirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; import java.nio.ByteBuffer; import java.util.List; @@ -34,7 +39,9 @@ import java.util.concurrent.TimeUnit; import java.util.function.Consumer; -import static io.reactivesocket.aeron.internal.Constants.*; +import static io.reactivesocket.aeron.internal.Constants.CLIENT_STREAM_ID; +import static io.reactivesocket.aeron.internal.Constants.SERVER_ESTABLISH_CONNECTION_REQUEST_TIMEOUT_MS; +import static io.reactivesocket.aeron.internal.Constants.SERVER_STREAM_ID; public class ReactiveSocketAeronServer implements AutoCloseable, Loggable { private static final UnsafeBuffer BUFFER = new UnsafeBuffer(ByteBuffer.allocate(0)); @@ -141,20 +148,20 @@ void fragmentHandler(DirectBuffer buffer, int offset, int length, Header header) } - void availableImageHandler(Image image, Subscription subscription, long joiningPosition, String sourceIdentity) { + void availableImageHandler(Image image) { final int streamId = subscription.streamId(); final int sessionId = image.sessionId(); if (SERVER_STREAM_ID == streamId) { debug("Handling new image for session id => {} and stream id => {}", streamId, sessionId); final AeronServerDuplexConnection connection = connections.computeIfAbsent(sessionId, (_s) -> { - final String responseChannel = "udp://" + sourceIdentity.substring(0, sourceIdentity.indexOf(':')) + ":" + port; + final String responseChannel = "udp://" + image.sourceIdentity().substring(0, image.sourceIdentity().indexOf(':')) + ":" + port; Publication publication = manager.getAeron().addPublication(responseChannel, CLIENT_STREAM_ID); int responseSessionId = publication.sessionId(); debug("Creating new connection for responseChannel => {}, streamId => {}, and sessionId => {}", responseChannel, streamId, responseSessionId); return new AeronServerDuplexConnection(publication); }); debug("Accepting ReactiveSocket connection"); - ReactiveSocket socket = ReactiveSocket.fromServerConnection( + ReactiveSocket socket = DefaultReactiveSocket.fromServerConnection( connection, connectionSetupHandler, leaseGovernor, @@ -173,7 +180,7 @@ public void accept(Throwable throwable) { } } - void unavailableImage(Image image, Subscription subscription, long position) { + void unavailableImage(Image image) { closeReactiveSocket(image.sessionId()); } diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java index 73ea796f0..bfec5d023 100644 --- a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java @@ -15,17 +15,22 @@ */ package io.reactivesocket.aeron.server; +import io.aeron.Aeron; +import io.aeron.AvailableImageHandler; +import io.aeron.FragmentAssembler; +import io.aeron.Image; +import io.aeron.Subscription; +import io.aeron.UnavailableImageHandler; import io.reactivesocket.aeron.internal.Constants; import io.reactivesocket.aeron.internal.Loggable; +import org.agrona.TimerWheel; +import org.agrona.concurrent.ManyToOneConcurrentArrayQueue; +import rx.Observable; +import rx.Scheduler; +import rx.Single; import rx.functions.Action0; -import uk.co.real_logic.aeron.Aeron; -import uk.co.real_logic.aeron.AvailableImageHandler; -import uk.co.real_logic.aeron.FragmentAssembler; -import uk.co.real_logic.aeron.Image; -import uk.co.real_logic.aeron.Subscription; -import uk.co.real_logic.aeron.UnavailableImageHandler; -import uk.co.real_logic.agrona.TimerWheel; -import uk.co.real_logic.agrona.concurrent.ManyToOneConcurrentArrayQueue; +import rx.functions.Func0; +import rx.schedulers.Schedulers; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; @@ -121,14 +126,14 @@ public void removeSubscription(Subscription subscription) { fragmentAssemblerHolders.removeIf(s -> s.subscription == subscription); } - private void availableImageHandler(Image image, Subscription subscription, long joiningPosition, String sourceIdentity) { + private void availableImageHandler(Image image) { availableImageHandlers - .forEach(handler -> handler.onAvailableImage(image, subscription, joiningPosition, sourceIdentity)); + .forEach(handler -> handler.onAvailableImage(image)); } - private void unavailableImage(Image image, Subscription subscription, long position) { + private void unavailableImage(Image image) { unavailableImageHandlers - .forEach(handler -> handler.onUnavailableImage(image, subscription, position)); + .forEach(handler -> handler.onUnavailableImage(image)); } public Aeron getAeron() { @@ -157,7 +162,49 @@ public boolean submitAction(Action0 action) { } /** - * Schedules timeout on the TimerWheel in a thread-safe many + * Submits a task that is implemeted as a {@link Func0} that runs on the + * server polling thread and returns an {@link Single} + * @param task task to the run + * @param expected return type + * @return an {@link Single} of type R + */ + public Single submitTask(Func0 task) { + return Single.create(s -> + submitAction(() -> { + try { + s.onSuccess(task.call()); + } catch (Throwable t) { + s.onError(t); + } + }) + ); + } + + /** + * + * @param tasks + * @param + * @return + */ + public Observable submitTasks(Observable> tasks) { + return submitTasks(tasks, Schedulers.computation()); + } + + /** + * Submits an observable of tasks to be run on a specific scheduler + * @param tasks + * @param scheduler + * @param + * @return + */ + public Observable submitTasks(Observable> tasks, Scheduler scheduler) { + return tasks + .observeOn(scheduler, true) + .concatMap(task -> submitTask(task).toObservable()); + } + + /** + * Schedules timeout on the TimerWheel in a thread-safe manner * @param delayTime * @param unit * @param action diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java index d7e6c8c99..cd8f2bb12 100644 --- a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java @@ -15,15 +15,15 @@ */ package io.reactivesocket.aeron.server; -import io.reactivesocket.aeron.internal.Loggable; -import io.reactivesocket.rx.Completable; +import io.aeron.Publication; import io.reactivesocket.Frame; import io.reactivesocket.aeron.internal.AeronUtil; +import io.reactivesocket.aeron.internal.Loggable; import io.reactivesocket.aeron.internal.MessageType; +import io.reactivesocket.rx.Completable; +import org.agrona.BitUtil; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; -import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.agrona.BitUtil; import java.nio.ByteBuffer; diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/TimerWheelFairLeaseGovernor.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/TimerWheelFairLeaseGovernor.java index a38758add..98d6ad4d2 100644 --- a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/TimerWheelFairLeaseGovernor.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/TimerWheelFairLeaseGovernor.java @@ -18,8 +18,8 @@ import io.reactivesocket.Frame; import io.reactivesocket.LeaseGovernor; import io.reactivesocket.internal.Responder; -import uk.co.real_logic.agrona.TimerWheel; -import uk.co.real_logic.agrona.collections.Int2IntHashMap; +import org.agrona.TimerWheel; +import org.agrona.collections.Int2IntHashMap; import java.util.ArrayList; import java.util.List; @@ -27,7 +27,7 @@ /** * Lease Governor that evenly distributes requests all connected clients. The work is done using the - * {@link ServerAeronManager}'s {@link uk.co.real_logic.agrona.TimerWheel} + * {@link ServerAeronManager}'s {@link TimerWheel} */ public class TimerWheelFairLeaseGovernor implements LeaseGovernor, Runnable { private final int tickets; diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronScheduler.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronScheduler.java index 5b4f9eea0..9af4546ed 100644 --- a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronScheduler.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronScheduler.java @@ -19,13 +19,12 @@ import rx.Scheduler; import rx.Subscription; import rx.functions.Action0; -import uk.co.real_logic.agrona.TimerWheel; import java.util.concurrent.TimeUnit; /** * An implementation of {@link Scheduler} that lets you schedule work on the {@link ServerAeronManager} polling thread. - * The work is scheduled on to the thread use a {@link TimerWheel}. This is useful if you have done work on another + * The work is scheduled on to the thread use a {@link org.agrona.TimerWheel}. This is useful if you have done work on another * thread, and than want the work to end up back on the polling thread. */ public class ReactiveSocketAeronScheduler extends Scheduler { diff --git a/reactivesocket-aeron/src/perf/java/uk/co/real_logic/aeron/DummySubscription.java b/reactivesocket-aeron/src/perf/java/io/aeron/DummySubscription.java similarity index 88% rename from reactivesocket-aeron/src/perf/java/uk/co/real_logic/aeron/DummySubscription.java rename to reactivesocket-aeron/src/perf/java/io/aeron/DummySubscription.java index 38b0bfe34..8a3ea7135 100644 --- a/reactivesocket-aeron/src/perf/java/uk/co/real_logic/aeron/DummySubscription.java +++ b/reactivesocket-aeron/src/perf/java/io/aeron/DummySubscription.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package uk.co.real_logic.aeron; +package io.aeron; -import uk.co.real_logic.aeron.logbuffer.BlockHandler; -import uk.co.real_logic.aeron.logbuffer.FileBlockHandler; -import uk.co.real_logic.aeron.logbuffer.FragmentHandler; +import io.aeron.logbuffer.BlockHandler; +import io.aeron.logbuffer.FileBlockHandler; +import io.aeron.logbuffer.FragmentHandler; import java.util.List; diff --git a/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/TestUtil.java b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/TestUtil.java index 3328c38f4..f653f0957 100644 --- a/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/TestUtil.java +++ b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/TestUtil.java @@ -18,7 +18,7 @@ import io.reactivesocket.Frame; import io.reactivesocket.FrameType; import io.reactivesocket.Payload; -import uk.co.real_logic.agrona.MutableDirectBuffer; +import org.agrona.MutableDirectBuffer; import java.nio.ByteBuffer; import java.nio.charset.Charset; diff --git a/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java index f59059874..2d0fa566e 100644 --- a/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java +++ b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java @@ -15,7 +15,14 @@ */ package io.reactivesocket.aeron.client; -import io.reactivesocket.*; +import io.aeron.driver.MediaDriver; +import io.reactivesocket.ConnectionSetupHandler; +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.DefaultReactiveSocket; +import io.reactivesocket.Frame; +import io.reactivesocket.Payload; +import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.RequestHandler; import io.reactivesocket.aeron.TestUtil; import io.reactivesocket.aeron.server.ReactiveSocketAeronServer; import io.reactivesocket.exceptions.SetupException; @@ -28,7 +35,6 @@ import rx.RxReactiveStreams; import rx.Subscriber; import rx.schedulers.Schedulers; -import uk.co.real_logic.aeron.driver.MediaDriver; import java.net.InetSocketAddress; import java.nio.ByteBuffer; @@ -145,7 +151,7 @@ public Publisher apply(Payload payload) { AeronClientDuplexConnection connection = RxReactiveStreams.toObservable(udpConnection).toBlocking().single(); System.out.println("Created duplex connection"); - ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection(connection, ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS)); + ReactiveSocket reactiveSocket = DefaultReactiveSocket.fromClientConnection(connection, ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS)); reactiveSocket.startAndWait(); CountDownLatch latch = new CountDownLatch(count); @@ -165,7 +171,7 @@ public Publisher apply(Payload payload) { Assert.assertEquals(m, "server_metadata"); }) .doOnNext(f -> latch.countDown()); - }) + }, 8) .subscribeOn(Schedulers.computation()) .subscribe(new Subscriber() { @Override @@ -219,7 +225,7 @@ public void requestStreamN(int count) throws Exception { AeronClientDuplexConnection connection = RxReactiveStreams.toObservable(udpConnection).toBlocking().single(); System.out.println("Created duplex connection"); - ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection(connection, ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS)); + ReactiveSocket reactiveSocket = DefaultReactiveSocket.fromClientConnection(connection, ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS)); reactiveSocket.startAndWait(); CountDownLatch latch = new CountDownLatch(count); @@ -317,7 +323,7 @@ public Publisher handleMetadataPush(Payload payload) { AeronClientDuplexConnection connection = RxReactiveStreams.toObservable(udpConnection).toBlocking().single(); System.out.println("Created duplex connection => " + j); - ReactiveSocket client = ReactiveSocket.fromClientConnection(connection, ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS)); + ReactiveSocket client = DefaultReactiveSocket.fromClientConnection(connection, ConnectionSetupPayload.create("UTF-8", "UTF-8", ConnectionSetupPayload.NO_FLAGS)); client.startAndWait(); Observable diff --git a/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/internal/AeronUtilTest.java b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/internal/AeronUtilTest.java index a61c093ab..b494fbc02 100644 --- a/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/internal/AeronUtilTest.java +++ b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/internal/AeronUtilTest.java @@ -15,10 +15,10 @@ */ package io.reactivesocket.aeron.internal; +import io.aeron.Publication; +import io.aeron.logbuffer.BufferClaim; +import org.agrona.DirectBuffer; import org.junit.Test; -import uk.co.real_logic.aeron.Publication; -import uk.co.real_logic.aeron.logbuffer.BufferClaim; -import uk.co.real_logic.agrona.DirectBuffer; import java.util.concurrent.TimeUnit; diff --git a/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/server/ServerAeronManagerTest.java b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/server/ServerAeronManagerTest.java new file mode 100644 index 000000000..9d491d46b --- /dev/null +++ b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/server/ServerAeronManagerTest.java @@ -0,0 +1,72 @@ +package io.reactivesocket.aeron.server; + +import io.aeron.driver.MediaDriver; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import rx.Observable; +import rx.Single; +import rx.functions.Func0; +import rx.observers.TestSubscriber; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +@Ignore +public class ServerAeronManagerTest { + @BeforeClass + public static void init() { + + final MediaDriver.Context context = new MediaDriver.Context(); + context.dirsDeleteOnStart(true); + final MediaDriver mediaDriver = MediaDriver.launch(context); + + } + + @Test(timeout = 2_000) + public void testSubmitAction() throws Exception { + ServerAeronManager instance = ServerAeronManager.getInstance(); + CountDownLatch latch = new CountDownLatch(1); + instance.submitAction(() -> latch.countDown()); + latch.await(); + } + + @Test(timeout = 2_000) + public void testSubmitTask() { + ServerAeronManager instance = ServerAeronManager.getInstance(); + CountDownLatch latch = new CountDownLatch(1); + Single longSingle = instance.submitTask(() -> + { + latch.countDown(); + return latch.getCount(); + }); + + TestSubscriber testSubscriber = new TestSubscriber(); + longSingle.subscribe(testSubscriber); + testSubscriber.awaitTerminalEvent(); + testSubscriber.assertCompleted(); + } + + @Test(timeout = 2_0000) + public void testSubmitTasks() { + ServerAeronManager instance = ServerAeronManager.getInstance(); + int number = 1; + List> func0List = new ArrayList<>(); + for (int i = 0; i < 100_000; i++) { + func0List.add(() -> number + 1); + } + + TestSubscriber testSubscriber = new TestSubscriber(); + + instance + .submitTasks(Observable.from(func0List)) + .reduce((a,b) -> a + b) + .doOnError(t -> t.printStackTrace()) + .subscribe(testSubscriber); + + testSubscriber.awaitTerminalEvent(); + testSubscriber.assertCompleted(); + testSubscriber.assertValue(200_000); + } +} \ No newline at end of file diff --git a/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronSchedulerTest.java b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronSchedulerTest.java index d5cd32f2a..b9220c64d 100644 --- a/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronSchedulerTest.java +++ b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronSchedulerTest.java @@ -15,6 +15,7 @@ */ package io.reactivesocket.aeron.server.rx; +import io.aeron.driver.MediaDriver; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Ignore; @@ -22,7 +23,6 @@ import rx.Observable; import rx.observers.TestSubscriber; import rx.schedulers.Schedulers; -import uk.co.real_logic.aeron.driver.MediaDriver; import java.util.concurrent.TimeUnit; diff --git a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketFactory.java b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketFactory.java index 28ec2a7e5..666aa37c9 100644 --- a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketFactory.java +++ b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketFactory.java @@ -16,8 +16,10 @@ package io.reactivesocket.javax.websocket.client; import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.DefaultReactiveSocket; import io.reactivesocket.ReactiveSocket; import io.reactivesocket.ReactiveSocketFactory; +import io.reactivesocket.ReactiveSocketSocketAddressFactory; import io.reactivesocket.javax.websocket.WebSocketDuplexConnection; import io.reactivesocket.rx.Completable; import org.glassfish.tyrus.client.ClientManager; @@ -30,13 +32,12 @@ import rx.RxReactiveStreams; import java.net.SocketAddress; -import java.util.concurrent.TimeUnit; import java.util.function.Consumer; /** * An implementation of {@link ReactiveSocketFactory} that creates JSR-356 WebSocket ReactiveSockets. */ -public class WebSocketReactiveSocketFactory implements ReactiveSocketFactory { +public class WebSocketReactiveSocketFactory implements ReactiveSocketSocketAddressFactory { private static final Logger logger = LoggerFactory.getLogger(WebSocketReactiveSocketFactory.class); private final ConnectionSetupPayload connectionSetupPayload; @@ -52,7 +53,7 @@ public WebSocketReactiveSocketFactory(String path, ClientManager clientManager, } @Override - public Publisher call(SocketAddress address, long timeout, TimeUnit timeUnit) { + public Publisher call(SocketAddress address) { Publisher connection = ReactiveSocketWebSocketClient.create(address, path, clientManager); @@ -65,7 +66,7 @@ public void onSubscribe(Subscription s) { @Override public void onNext(WebSocketDuplexConnection connection) { - ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection(connection, connectionSetupPayload, errorStream); + ReactiveSocket reactiveSocket = DefaultReactiveSocket.fromClientConnection(connection, connectionSetupPayload, errorStream); reactiveSocket.start(new Completable() { @Override public void success() { @@ -91,6 +92,6 @@ public void onComplete() { }) ); - return RxReactiveStreams.toPublisher(result.timeout(timeout, timeUnit)); + return RxReactiveStreams.toPublisher(result); } } diff --git a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/server/ReactiveSocketWebSocketServer.java b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/server/ReactiveSocketWebSocketServer.java index 826cfe8ea..085e57cd9 100644 --- a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/server/ReactiveSocketWebSocketServer.java +++ b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/server/ReactiveSocketWebSocketServer.java @@ -16,15 +16,20 @@ package io.reactivesocket.javax.websocket.server; import io.reactivesocket.ConnectionSetupHandler; +import io.reactivesocket.DefaultReactiveSocket; import io.reactivesocket.Frame; import io.reactivesocket.LeaseGovernor; import io.reactivesocket.ReactiveSocket; import io.reactivesocket.javax.websocket.WebSocketDuplexConnection; import io.reactivesocket.rx.Completable; +import org.agrona.LangUtil; import rx.subjects.PublishSubject; -import uk.co.real_logic.agrona.LangUtil; -import javax.websocket.*; +import javax.websocket.CloseReason; +import javax.websocket.Endpoint; +import javax.websocket.EndpointConfig; +import javax.websocket.MessageHandler; +import javax.websocket.Session; import java.nio.ByteBuffer; import java.util.concurrent.ConcurrentHashMap; @@ -57,7 +62,7 @@ public void onMessage(ByteBuffer message) { WebSocketDuplexConnection connection = new WebSocketDuplexConnection(session, input); final ReactiveSocket reactiveSocket = reactiveSockets.computeIfAbsent(session.getId(), id -> - ReactiveSocket.fromServerConnection( + DefaultReactiveSocket.fromServerConnection( connection, setupHandler, leaseGovernor, diff --git a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerTest.java b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerTest.java index ffb94ec91..c783eb2c2 100644 --- a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerTest.java +++ b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerTest.java @@ -16,6 +16,7 @@ package io.reactivesocket.javax.websocket; import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.DefaultReactiveSocket; import io.reactivesocket.Payload; import io.reactivesocket.ReactiveSocket; import io.reactivesocket.javax.websocket.client.ReactiveSocketWebSocketClient; @@ -70,7 +71,7 @@ public static void setup() throws URISyntaxException, DeploymentException, IOExc ReactiveSocketWebSocketClient.create(InetSocketAddress.createUnresolved("localhost", 8025), "/rs", ClientManager.createClient()) ).toBlocking().single(); - client = ReactiveSocket.fromClientConnection(duplexConnection, ConnectionSetupPayload.create("UTF-8", "UTF-8"), t -> t.printStackTrace()); + client = DefaultReactiveSocket.fromClientConnection(duplexConnection, ConnectionSetupPayload.create("UTF-8", "UTF-8"), t -> t.printStackTrace()); client.startAndWait(); } diff --git a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/Ping.java b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/Ping.java index bc3bb6941..20193551f 100644 --- a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/Ping.java +++ b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/Ping.java @@ -16,6 +16,7 @@ package io.reactivesocket.javax.websocket; import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.DefaultReactiveSocket; import io.reactivesocket.Payload; import io.reactivesocket.ReactiveSocket; import io.reactivesocket.javax.websocket.client.ReactiveSocketWebSocketClient; @@ -37,7 +38,7 @@ public static void main(String... args) throws Exception { Publisher publisher = ReactiveSocketWebSocketClient.create(InetSocketAddress.createUnresolved("localhost", 8025), "/rs", ClientManager.createClient()); WebSocketDuplexConnection duplexConnection = RxReactiveStreams.toObservable(publisher).toBlocking().single(); - ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection(duplexConnection, ConnectionSetupPayload.create("UTF-8", "UTF-8")); + ReactiveSocket reactiveSocket = DefaultReactiveSocket.fromClientConnection(duplexConnection, ConnectionSetupPayload.create("UTF-8", "UTF-8")); reactiveSocket.startAndWait(); diff --git a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/TestUtil.java b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/TestUtil.java index 5e2e0c3d0..d4c87889e 100644 --- a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/TestUtil.java +++ b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/TestUtil.java @@ -18,7 +18,7 @@ import io.reactivesocket.Frame; import io.reactivesocket.FrameType; import io.reactivesocket.Payload; -import uk.co.real_logic.agrona.MutableDirectBuffer; +import org.agrona.MutableDirectBuffer; import java.nio.ByteBuffer; import java.nio.charset.Charset; diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/MutableDirectByteBuf.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/MutableDirectByteBuf.java index 507a4494e..92cd10bea 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/MutableDirectByteBuf.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/MutableDirectByteBuf.java @@ -16,9 +16,9 @@ package io.reactivesocket.netty; import io.netty.buffer.ByteBuf; -import uk.co.real_logic.agrona.BitUtil; -import uk.co.real_logic.agrona.DirectBuffer; -import uk.co.real_logic.agrona.MutableDirectBuffer; +import org.agrona.BitUtil; +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -386,4 +386,39 @@ private void ensureByteOrder(final ByteOrder byteOrder) byteBuf.order(byteOrder); } } + + @Override + public void putChar(int index, char value, ByteOrder byteOrder) { + throw new UnsupportedOperationException("getBytes(MutableDirectBuffer) not supported"); + } + + @Override + public void putChar(int index, char value) { + throw new UnsupportedOperationException("getBytes(MutableDirectBuffer) not supported"); + } + + @Override + public int putStringUtf8(int offset, String value) { + throw new UnsupportedOperationException("getBytes(MutableDirectBuffer) not supported"); + } + + @Override + public int putStringUtf8(int index, String value, int maxEncodedSize) { + throw new UnsupportedOperationException("getBytes(MutableDirectBuffer) not supported"); + } + + @Override + public char getChar(int index, ByteOrder byteOrder) { + throw new UnsupportedOperationException("getBytes(MutableDirectBuffer) not supported"); + } + + @Override + public char getChar(int index) { + throw new UnsupportedOperationException("getBytes(MutableDirectBuffer) not supported"); + } + + @Override + public String getStringUtf8(int index) { + return null; + } } diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketFactory.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketFactory.java index f36731151..1ae5c2ed2 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketFactory.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketFactory.java @@ -17,8 +17,10 @@ import io.netty.channel.EventLoopGroup; import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.DefaultReactiveSocket; import io.reactivesocket.ReactiveSocket; import io.reactivesocket.ReactiveSocketFactory; +import io.reactivesocket.ReactiveSocketSocketAddressFactory; import io.reactivesocket.rx.Completable; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; @@ -29,13 +31,12 @@ import rx.RxReactiveStreams; import java.net.SocketAddress; -import java.util.concurrent.TimeUnit; import java.util.function.Consumer; /** * An implementation of {@link ReactiveSocketFactory} that creates Netty WebSocket ReactiveSockets. */ -public class WebSocketReactiveSocketFactory implements ReactiveSocketFactory { +public class WebSocketReactiveSocketFactory implements ReactiveSocketSocketAddressFactory { private static final Logger logger = LoggerFactory.getLogger(WebSocketReactiveSocketFactory.class); private final ConnectionSetupPayload connectionSetupPayload; @@ -51,7 +52,7 @@ public WebSocketReactiveSocketFactory(String path, EventLoopGroup eventLoopGroup } @Override - public Publisher call(SocketAddress address, long timeout, TimeUnit timeUnit) { + public Publisher call(SocketAddress address) { Publisher connection = ClientWebSocketDuplexConnection.create(address, path, eventLoopGroup); @@ -64,7 +65,7 @@ public void onSubscribe(Subscription s) { @Override public void onNext(ClientWebSocketDuplexConnection connection) { - ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection(connection, connectionSetupPayload, errorStream); + ReactiveSocket reactiveSocket = DefaultReactiveSocket.fromClientConnection(connection, connectionSetupPayload, errorStream); reactiveSocket.start(new Completable() { @Override public void success() { @@ -90,6 +91,6 @@ public void onComplete() { }) ); - return RxReactiveStreams.toPublisher(result.timeout(timeout, timeUnit)); + return RxReactiveStreams.toPublisher(result); } } diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ReactiveSocketServerHandler.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ReactiveSocketServerHandler.java index 20fedf1a6..86fe057e7 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ReactiveSocketServerHandler.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ReactiveSocketServerHandler.java @@ -22,6 +22,7 @@ import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; import io.reactivesocket.ConnectionSetupHandler; +import io.reactivesocket.DefaultReactiveSocket; import io.reactivesocket.Frame; import io.reactivesocket.LeaseGovernor; import io.reactivesocket.ReactiveSocket; @@ -63,7 +64,7 @@ protected void channelRead0(ChannelHandlerContext ctx, BinaryWebSocketFrame msg) ServerWebSocketDuplexConnection connection = duplexConnections.computeIfAbsent(ctx.channel().id(), i -> { System.out.println("No connection found for channel id: " + i); ServerWebSocketDuplexConnection c = new ServerWebSocketDuplexConnection(ctx); - ReactiveSocket reactiveSocket = ReactiveSocket.fromServerConnection(c, setupHandler, leaseGovernor, throwable -> throwable.printStackTrace()); + ReactiveSocket reactiveSocket = DefaultReactiveSocket.fromServerConnection(c, setupHandler, leaseGovernor, throwable -> throwable.printStackTrace()); reactiveSocket.startAndWait(); return c; }); From 98692fe20fc34ea2f7e4f337feef30093ad45ff8 Mon Sep 17 00:00:00 2001 From: Ryland Degnan Date: Thu, 31 Mar 2016 14:36:20 -0700 Subject: [PATCH 083/105] Fix build --- .../reactivesocket/netty/websocket/ClientServerTest.java | 7 ++----- .../test/java/io/reactivesocket/netty/websocket/Ping.java | 3 ++- .../java/io/reactivesocket/netty/websocket/TestUtil.java | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java index 593c28553..ca7a82657 100644 --- a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java +++ b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java @@ -27,10 +27,7 @@ import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; -import io.reactivesocket.ConnectionSetupPayload; -import io.reactivesocket.Payload; -import io.reactivesocket.ReactiveSocket; -import io.reactivesocket.RequestHandler; +import io.reactivesocket.*; import io.reactivesocket.netty.websocket.client.ClientWebSocketDuplexConnection; import io.reactivesocket.netty.websocket.server.ReactiveSocketServerHandler; import org.junit.AfterClass; @@ -126,7 +123,7 @@ protected void initChannel(Channel ch) throws Exception { ClientWebSocketDuplexConnection.create(InetSocketAddress.createUnresolved("localhost", 8025), "/rs", new NioEventLoopGroup()) ).toBlocking().single(); - client = ReactiveSocket + client = DefaultReactiveSocket .fromClientConnection(duplexConnection, ConnectionSetupPayload.create("UTF-8", "UTF-8"), t -> t.printStackTrace()); client.startAndWait(); diff --git a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Ping.java b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Ping.java index 0c3eba5d1..0275983a7 100644 --- a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Ping.java +++ b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Ping.java @@ -17,6 +17,7 @@ import io.netty.channel.nio.NioEventLoopGroup; import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.DefaultReactiveSocket; import io.reactivesocket.Payload; import io.reactivesocket.ReactiveSocket; import io.reactivesocket.netty.websocket.client.ClientWebSocketDuplexConnection; @@ -37,7 +38,7 @@ public static void main(String... args) throws Exception { Publisher publisher = ClientWebSocketDuplexConnection.create(InetSocketAddress.createUnresolved("localhost", 8025), "/rs", new NioEventLoopGroup(1)); ClientWebSocketDuplexConnection duplexConnection = RxReactiveStreams.toObservable(publisher).toBlocking().last(); - ReactiveSocket reactiveSocket = ReactiveSocket.fromClientConnection(duplexConnection, ConnectionSetupPayload.create("UTF-8", "UTF-8"), t -> t.printStackTrace()); + ReactiveSocket reactiveSocket = DefaultReactiveSocket.fromClientConnection(duplexConnection, ConnectionSetupPayload.create("UTF-8", "UTF-8"), t -> t.printStackTrace()); reactiveSocket.startAndWait(); diff --git a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/TestUtil.java b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/TestUtil.java index 8d8ab65b0..54d380b5b 100644 --- a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/TestUtil.java +++ b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/TestUtil.java @@ -19,7 +19,7 @@ import io.reactivesocket.Frame; import io.reactivesocket.FrameType; import io.reactivesocket.Payload; -import uk.co.real_logic.agrona.MutableDirectBuffer; +import org.agrona.MutableDirectBuffer; import java.nio.ByteBuffer; import java.nio.charset.Charset; From 8b55a635953078d4d606ac2b8a8ac1add45b1e6b Mon Sep 17 00:00:00 2001 From: Ryland Degnan Date: Thu, 31 Mar 2016 15:32:47 -0700 Subject: [PATCH 084/105] Upgrade gradle-reactivesocket-plugin to 1.0.5 --- build.gradle | 2 +- reactivesocket-netty/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 8096de800..c2f2a8def 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { repositories { jcenter() } - dependencies { classpath 'io.reactivesocket:gradle-nebula-plugin-reactivesocket:1.0.4' } + dependencies { classpath 'io.reactivesocket:gradle-nebula-plugin-reactivesocket:1.0.5' } } apply plugin: 'reactivesocket-project' diff --git a/reactivesocket-netty/build.gradle b/reactivesocket-netty/build.gradle index 4fc5e17ef..fd627f7e5 100644 --- a/reactivesocket-netty/build.gradle +++ b/reactivesocket-netty/build.gradle @@ -1,3 +1,3 @@ dependencies { - compile 'io.netty:netty-all:4.1.0.CR4' + compile 'io.netty:netty-all:4.1.0.CR5' } \ No newline at end of file From b83de2ebbeddc6f10bfae43e8028e8e11e801592 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Sat, 2 Apr 2016 23:30:37 -0700 Subject: [PATCH 085/105] added impl that lets you run reactivesocket client/server in the same JVM --- .../local/LocalClientDuplexConnection.java | 80 ++++++++ .../LocalClientReactiveSocketFactory.java | 59 ++++++ .../local/LocalReactiveSocketManager.java | 39 ++++ .../local/LocalServerDuplexConection.java | 79 ++++++++ .../LocalServerReactiveSocketFactory.java | 52 +++++ .../io/reactivesocket/local/package-info.java | 6 + .../local/ClientServerTest.java | 181 ++++++++++++++++++ .../io/reactivesocket/local/TestUtil.java | 124 ++++++++++++ settings.gradle | 4 +- 9 files changed, 623 insertions(+), 1 deletion(-) create mode 100644 reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientDuplexConnection.java create mode 100644 reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientReactiveSocketFactory.java create mode 100644 reactivesocket-local/src/main/java/io/reactivesocket/local/LocalReactiveSocketManager.java create mode 100644 reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerDuplexConection.java create mode 100644 reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerReactiveSocketFactory.java create mode 100644 reactivesocket-local/src/main/java/io/reactivesocket/local/package-info.java create mode 100644 reactivesocket-local/src/test/java/io/reactivesocket/local/ClientServerTest.java create mode 100644 reactivesocket-local/src/test/java/io/reactivesocket/local/TestUtil.java diff --git a/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientDuplexConnection.java b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientDuplexConnection.java new file mode 100644 index 000000000..efb4487da --- /dev/null +++ b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientDuplexConnection.java @@ -0,0 +1,80 @@ +package io.reactivesocket.local; + +import io.reactivesocket.DuplexConnection; +import io.reactivesocket.Frame; +import io.reactivesocket.rx.Completable; +import io.reactivesocket.rx.Observable; +import io.reactivesocket.rx.Observer; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import java.io.IOException; +import java.util.concurrent.CopyOnWriteArrayList; + +class LocalClientDuplexConnection implements DuplexConnection { + private final String name; + + private final CopyOnWriteArrayList> subjects; + + public LocalClientDuplexConnection(String name) { + this.name = name; + this.subjects = new CopyOnWriteArrayList<>(); + } + + @Override + public Observable getInput() { + return o -> { + o.onSubscribe(() -> subjects.removeIf(s -> s == o)); + subjects.add(o); + }; + } + + @Override + public void addOutput(Publisher o, Completable callback) { + + o + .subscribe(new Subscriber() { + + @Override + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); + } + + @Override + public void onNext(Frame frame) { + try { + LocalReactiveSocketManager + .getInstance() + .getServerConnection(name) + .write(frame); + } catch (Throwable t) { + onError(t); + } + } + + @Override + public void onError(Throwable t) { + callback.error(t); + } + + @Override + public void onComplete() { + callback.success(); + } + }); + } + + void write(Frame frame) { + subjects + .forEach(o -> o.onNext(frame)); + } + + @Override + public void close() throws IOException { + LocalReactiveSocketManager + .getInstance() + .removeClientConnection(name); + + } +} diff --git a/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientReactiveSocketFactory.java b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientReactiveSocketFactory.java new file mode 100644 index 000000000..1ededde10 --- /dev/null +++ b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientReactiveSocketFactory.java @@ -0,0 +1,59 @@ +package io.reactivesocket.local; + +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.DefaultReactiveSocket; +import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.ReactiveSocketFactory; +import io.reactivesocket.internal.rx.EmptySubscription; +import org.reactivestreams.Publisher; + +public class LocalClientReactiveSocketFactory implements ReactiveSocketFactory { + public static final LocalClientReactiveSocketFactory INSTANCE = new LocalClientReactiveSocketFactory(); + + private LocalClientReactiveSocketFactory() {} + + @Override + public Publisher call(Config config) { + return s -> { + try { + s.onSubscribe(EmptySubscription.INSTANCE); + LocalClientDuplexConnection clientConnection = LocalReactiveSocketManager + .getInstance() + .getClientConnection(config.getName()); + ReactiveSocket reactiveSocket = DefaultReactiveSocket + .fromClientConnection(clientConnection, ConnectionSetupPayload.create(config.getMetadataMimeType(), config.getDataMimeType())); + + reactiveSocket.startAndWait(); + + s.onNext(reactiveSocket); + s.onComplete(); + } catch (Throwable t) { + s.onError(t); + } + }; + } + + public static class Config { + final String name; + final String metadataMimeType; + final String dataMimeType; + + public Config(String name, String metadataMimeType, String dataMimeType) { + this.name = name; + this.metadataMimeType = metadataMimeType; + this.dataMimeType = dataMimeType; + } + + public String getName() { + return name; + } + + public String getMetadataMimeType() { + return metadataMimeType; + } + + public String getDataMimeType() { + return dataMimeType; + } + } +} diff --git a/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalReactiveSocketManager.java b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalReactiveSocketManager.java new file mode 100644 index 000000000..fbe9bab79 --- /dev/null +++ b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalReactiveSocketManager.java @@ -0,0 +1,39 @@ +package io.reactivesocket.local; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * Created by rroeser on 4/2/16. + */ +class LocalReactiveSocketManager { + private static final LocalReactiveSocketManager INSTANCE = new LocalReactiveSocketManager(); + + private final ConcurrentHashMap serverConnections; + private final ConcurrentHashMap clientConnections; + + private LocalReactiveSocketManager() { + serverConnections = new ConcurrentHashMap<>(); + clientConnections = new ConcurrentHashMap<>(); + } + + public static LocalReactiveSocketManager getInstance() { + return INSTANCE; + } + + public LocalClientDuplexConnection getClientConnection(String name) { + return clientConnections.computeIfAbsent(name, LocalClientDuplexConnection::new); + } + + public void removeClientConnection(String name) { + clientConnections.remove(name); + } + + public LocalServerDuplexConection getServerConnection(String name) { + return serverConnections.computeIfAbsent(name, LocalServerDuplexConection::new); + } + + public void removeServerDuplexConnection(String name) { + serverConnections.remove(name); + } + +} diff --git a/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerDuplexConection.java b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerDuplexConection.java new file mode 100644 index 000000000..00f83491a --- /dev/null +++ b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerDuplexConection.java @@ -0,0 +1,79 @@ +package io.reactivesocket.local; + +import io.reactivesocket.DuplexConnection; +import io.reactivesocket.Frame; +import io.reactivesocket.rx.Completable; +import io.reactivesocket.rx.Observable; +import io.reactivesocket.rx.Observer; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import java.io.IOException; +import java.util.concurrent.CopyOnWriteArrayList; + +class LocalServerDuplexConection implements DuplexConnection { + private final String name; + + private final CopyOnWriteArrayList> subjects; + + public LocalServerDuplexConection(String name) { + this.name = name; + this.subjects = new CopyOnWriteArrayList<>(); + } + + @Override + public Observable getInput() { + return o -> { + o.onSubscribe(() -> subjects.removeIf(s -> s == o)); + subjects.add(o); + }; + } + + @Override + public void addOutput(Publisher o, Completable callback) { + o + .subscribe(new Subscriber() { + + @Override + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); + } + + @Override + public void onNext(Frame frame) { + try { + LocalReactiveSocketManager + .getInstance() + .getClientConnection(name) + .write(frame); + } catch (Throwable t) { + onError(t); + } + } + + @Override + public void onError(Throwable t) { + callback.error(t); + } + + @Override + public void onComplete() { + callback.success(); + } + }); + } + + void write(Frame frame) { + subjects + .forEach(o -> o.onNext(frame)); + } + + @Override + public void close() throws IOException { + LocalReactiveSocketManager + .getInstance() + .removeServerDuplexConnection(name); + + } +} diff --git a/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerReactiveSocketFactory.java b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerReactiveSocketFactory.java new file mode 100644 index 000000000..f361dcedd --- /dev/null +++ b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerReactiveSocketFactory.java @@ -0,0 +1,52 @@ +package io.reactivesocket.local; + +import io.reactivesocket.ConnectionSetupHandler; +import io.reactivesocket.DefaultReactiveSocket; +import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.ReactiveSocketFactory; +import io.reactivesocket.internal.rx.EmptySubscription; +import org.reactivestreams.Publisher; + +public class LocalServerReactiveSocketFactory implements ReactiveSocketFactory { + public static final LocalServerReactiveSocketFactory INSTANCE = new LocalServerReactiveSocketFactory(); + + private LocalServerReactiveSocketFactory() {} + + @Override + public Publisher call(Config config) { + return s -> { + try { + s.onSubscribe(EmptySubscription.INSTANCE); + LocalServerDuplexConection clientConnection = LocalReactiveSocketManager + .getInstance() + .getServerConnection(config.getName()); + ReactiveSocket reactiveSocket = DefaultReactiveSocket + .fromServerConnection(clientConnection, config.getConnectionSetupHandler()); + + reactiveSocket.startAndWait(); + s.onNext(reactiveSocket); + s.onComplete(); + } catch (Throwable t) { + s.onError(t); + } + }; + } + + public static class Config { + final String name; + final ConnectionSetupHandler connectionSetupHandler; + + public Config(String name, ConnectionSetupHandler connectionSetupHandler) { + this.name = name; + this.connectionSetupHandler = connectionSetupHandler; + } + + public ConnectionSetupHandler getConnectionSetupHandler() { + return connectionSetupHandler; + } + + public String getName() { + return name; + } + } +} diff --git a/reactivesocket-local/src/main/java/io/reactivesocket/local/package-info.java b/reactivesocket-local/src/main/java/io/reactivesocket/local/package-info.java new file mode 100644 index 000000000..f75336228 --- /dev/null +++ b/reactivesocket-local/src/main/java/io/reactivesocket/local/package-info.java @@ -0,0 +1,6 @@ +/** + * An implementation of ReactiveSocket that lets you run the client and server in the same local JVM. To create + * a client use {@link io.reactivesocket.local.LocalClientReactiveSocketFactory} and to create a server use + * {@link io.reactivesocket.local.LocalServerReactiveSocketFactory} factories classes. + */ +package io.reactivesocket.local; \ No newline at end of file diff --git a/reactivesocket-local/src/test/java/io/reactivesocket/local/ClientServerTest.java b/reactivesocket-local/src/test/java/io/reactivesocket/local/ClientServerTest.java new file mode 100644 index 000000000..90f1251d1 --- /dev/null +++ b/reactivesocket-local/src/test/java/io/reactivesocket/local/ClientServerTest.java @@ -0,0 +1,181 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.local; + +import io.reactivesocket.ConnectionSetupHandler; +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.Payload; +import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.RequestHandler; +import io.reactivesocket.exceptions.SetupException; +import org.junit.BeforeClass; +import org.junit.Test; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import rx.Observable; +import rx.RxReactiveStreams; +import rx.observers.TestSubscriber; + +import java.util.concurrent.TimeUnit; + +public class ClientServerTest { + + static ReactiveSocket client; + + static ReactiveSocket server; + + @BeforeClass + public static void setup() throws Exception { + server = LocalServerReactiveSocketFactory.INSTANCE.callAndWait(new LocalServerReactiveSocketFactory.Config("test", new ConnectionSetupHandler() { + @Override + public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + return new RequestHandler() { + @Override + public Publisher handleRequestResponse(Payload payload) { + return s -> { + //System.out.println("Handling request/response payload => " + s.toString()); + Payload response = TestUtil.utf8EncodedPayload("hello world", "metadata"); + s.onNext(response); + s.onComplete(); + }; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + Payload response = TestUtil.utf8EncodedPayload("hello world", "metadata"); + + return RxReactiveStreams + .toPublisher(Observable + .range(1, 10) + .map(i -> response)); + } + + @Override + public Publisher handleSubscription(Payload payload) { + Payload response = TestUtil.utf8EncodedPayload("hello world", "metadata"); + + return RxReactiveStreams + .toPublisher(Observable + .range(1, 10) + .map(i -> response) + .repeat()); + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return Subscriber::onComplete; + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher inputs) { + return null; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + }; + } + })); + + client = LocalClientReactiveSocketFactory.INSTANCE.callAndWait(new LocalClientReactiveSocketFactory.Config("test", "text", "text")); + + } + + @Test + public void testRequestResponse1() { + requestResponseN(1500, 1); + } + + @Test + public void testRequestResponse10() { + requestResponseN(1500, 10); + } + + + @Test + public void testRequestResponse100() { + requestResponseN(1500, 100); + } + + @Test + public void testRequestResponse10_000() { + requestResponseN(60_000, 10_000); + } + + + @Test + public void testRequestResponse100_000() { + requestResponseN(60_000, 10_000); + } + @Test + public void testRequestResponse1_000_000() { + requestResponseN(60_000, 10_000); + } + + @Test + public void testRequestStream() { + TestSubscriber ts = TestSubscriber.create(); + + RxReactiveStreams + .toObservable(client.requestStream(TestUtil.utf8EncodedPayload("hello", "metadata"))) + .subscribe(ts); + + + ts.awaitTerminalEvent(3_000, TimeUnit.MILLISECONDS); + ts.assertValueCount(10); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void testRequestSubscription() throws InterruptedException { + TestSubscriber ts = TestSubscriber.create(); + + RxReactiveStreams + .toObservable(client.requestSubscription(TestUtil.utf8EncodedPayload("hello sub", "metadata sub"))) + .take(10) + .subscribe(ts); + + ts.awaitTerminalEvent(3_000, TimeUnit.MILLISECONDS); + ts.assertValueCount(10); + ts.assertNoErrors(); + } + + + public void requestResponseN(int timeout, int count) { + + TestSubscriber ts = TestSubscriber.create(); + + Observable + .range(1, count) + .flatMap(i -> + RxReactiveStreams + .toObservable(client.requestResponse(TestUtil.utf8EncodedPayload("hello", "metadata"))) + .map(payload -> TestUtil.byteToString(payload.getData())) + ) + .doOnError(Throwable::printStackTrace) + .subscribe(ts); + + ts.awaitTerminalEvent(timeout, TimeUnit.MILLISECONDS); + ts.assertValueCount(count); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + +} \ No newline at end of file diff --git a/reactivesocket-local/src/test/java/io/reactivesocket/local/TestUtil.java b/reactivesocket-local/src/test/java/io/reactivesocket/local/TestUtil.java new file mode 100644 index 000000000..ddbab746e --- /dev/null +++ b/reactivesocket-local/src/test/java/io/reactivesocket/local/TestUtil.java @@ -0,0 +1,124 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.local; + +import io.reactivesocket.Frame; +import io.reactivesocket.FrameType; +import io.reactivesocket.Payload; +import org.agrona.MutableDirectBuffer; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; + +public class TestUtil +{ + public static Frame utf8EncodedRequestFrame(final int streamId, final FrameType type, final String data, final int initialRequestN) + { + return Frame.Request.from(streamId, type, new Payload() + { + public ByteBuffer getData() + { + return byteBufferFromUtf8String(data); + } + + public ByteBuffer getMetadata() + { + return Frame.NULL_BYTEBUFFER; + } + }, initialRequestN); + } + + public static Frame utf8EncodedResponseFrame(final int streamId, final FrameType type, final String data) + { + return Frame.Response.from(streamId, type, utf8EncodedPayload(data, null)); + } + + public static Frame utf8EncodedErrorFrame(final int streamId, final String data) + { + return Frame.Error.from(streamId, new Exception(data)); + } + + public static Payload utf8EncodedPayload(final String data, final String metadata) + { + return new PayloadImpl(data, metadata); + } + + public static String byteToString(final ByteBuffer byteBuffer) + { + final byte[] bytes = new byte[byteBuffer.remaining()]; + byteBuffer.get(bytes); + return new String(bytes, Charset.forName("UTF-8")); + } + + public static ByteBuffer byteBufferFromUtf8String(final String data) + { + final byte[] bytes = data.getBytes(Charset.forName("UTF-8")); + return ByteBuffer.wrap(bytes); + } + + public static void copyFrame(final MutableDirectBuffer dst, final int offset, final Frame frame) + { + dst.putBytes(offset, frame.getByteBuffer(), frame.offset(), frame.length()); + } + + private static class PayloadImpl implements Payload // some JDK shoutout + { + private ByteBuffer data; + private ByteBuffer metadata; + + public PayloadImpl(final String data, final String metadata) + { + if (null == data) + { + this.data = ByteBuffer.allocate(0); + } + else + { + this.data = byteBufferFromUtf8String(data); + } + + if (null == metadata) + { + this.metadata = ByteBuffer.allocate(0); + } + else + { + this.metadata = byteBufferFromUtf8String(metadata); + } + } + + public boolean equals(Object obj) + { + System.out.println("equals: " + obj); + final Payload rhs = (Payload)obj; + + return (TestUtil.byteToString(data).equals(TestUtil.byteToString(rhs.getData()))) && + (TestUtil.byteToString(metadata).equals(TestUtil.byteToString(rhs.getMetadata()))); + } + + public ByteBuffer getData() + { + return data; + } + + public ByteBuffer getMetadata() + { + return metadata; + } + } + + +} diff --git a/settings.gradle b/settings.gradle index 782d21a8d..eae8b7692 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,4 @@ rootProject.name='reactivesocket-java-impl' -include 'reactivesocket-aeron', 'reactivesocket-jsr-356', 'reactivesocket-netty' \ No newline at end of file +include 'reactivesocket-aeron', 'reactivesocket-jsr-356', 'reactivesocket-netty' +include 'reactivesocket-local' + From 32a526f0a5a3f6a43217543cdb1ae3d9c18e3db1 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Thu, 7 Apr 2016 11:05:54 -0700 Subject: [PATCH 086/105] added method to log an exception in the ReactiveSocketServerHandler --- reactivesocket-local/build.gradle | 14 ++++++++++++++ .../server/ReactiveSocketServerHandler.java | 11 +++++++++++ 2 files changed, 25 insertions(+) create mode 100644 reactivesocket-local/build.gradle diff --git a/reactivesocket-local/build.gradle b/reactivesocket-local/build.gradle new file mode 100644 index 000000000..6750744e2 --- /dev/null +++ b/reactivesocket-local/build.gradle @@ -0,0 +1,14 @@ +group 'io.reactivesocket' +version '0.1.3-SNAPSHOT' + +apply plugin: 'java' + +sourceCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.11' +} diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ReactiveSocketServerHandler.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ReactiveSocketServerHandler.java index 86fe057e7..5d8153a45 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ReactiveSocketServerHandler.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ReactiveSocketServerHandler.java @@ -27,11 +27,15 @@ import io.reactivesocket.LeaseGovernor; import io.reactivesocket.ReactiveSocket; import io.reactivesocket.netty.MutableDirectByteBuf; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.concurrent.ConcurrentHashMap; @ChannelHandler.Sharable public class ReactiveSocketServerHandler extends SimpleChannelInboundHandler { + private Logger logger = LoggerFactory.getLogger(ReactiveSocketServerHandler.class); + private ConcurrentHashMap duplexConnections = new ConcurrentHashMap<>(); private ConnectionSetupHandler setupHandler; @@ -74,4 +78,11 @@ protected void channelRead0(ChannelHandlerContext ctx, BinaryWebSocketFrame msg) .forEach(o -> o.onNext(from)); } } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + super.exceptionCaught(ctx, cause); + + logger.error("caught an unhandled exception", cause); + } } From e6804cb8f0006633559726fe7ac7ad068c322c3b Mon Sep 17 00:00:00 2001 From: Ryland Degnan Date: Thu, 7 Apr 2016 11:34:16 -0700 Subject: [PATCH 087/105] Update to latest reactivesocket --- build.gradle | 12 ++++++------ .../client/AeronReactiveSocketFactory.java | 18 ++++++++++++++++-- .../aeron/server/ServerAeronManagerTest.java | 15 +++++++++++++++ .../client/WebSocketReactiveSocketFactory.java | 3 +-- reactivesocket-local/build.gradle | 14 -------------- .../local/LocalClientDuplexConnection.java | 15 +++++++++++++++ .../LocalClientReactiveSocketFactory.java | 15 +++++++++++++++ .../local/LocalReactiveSocketManager.java | 15 +++++++++++++++ .../local/LocalServerDuplexConection.java | 15 +++++++++++++++ .../LocalServerReactiveSocketFactory.java | 15 +++++++++++++++ .../io/reactivesocket/local/package-info.java | 6 ------ reactivesocket-netty/build.gradle | 3 ++- .../client/WebSocketReactiveSocketFactory.java | 3 +-- 13 files changed, 116 insertions(+), 33 deletions(-) delete mode 100644 reactivesocket-local/src/main/java/io/reactivesocket/local/package-info.java diff --git a/build.gradle b/build.gradle index c2f2a8def..28a2bdcfc 100644 --- a/build.gradle +++ b/build.gradle @@ -14,13 +14,13 @@ subprojects { } dependencies { - compile 'io.reactivex:rxjava:1.1.2' - compile 'io.reactivex:rxjava-reactive-streams:1.0.1' - compile 'io.reactivesocket:reactivesocket:0.0.5' - compile 'org.hdrhistogram:HdrHistogram:2.1.7' - compile 'org.slf4j:slf4j-api:1.7.12' + compile 'io.reactivex:rxjava:latest.release' + compile 'io.reactivex:rxjava-reactive-streams:latest.release' + compile 'io.reactivesocket:reactivesocket:latest.release' + compile 'org.hdrhistogram:HdrHistogram:latest.release' + compile 'org.slf4j:slf4j-api:latest.release' testCompile 'junit:junit:4.12' - testCompile 'org.mockito:mockito-core:1.8.5' + testCompile 'org.mockito:mockito-core:1.10.19' testRuntime 'org.slf4j:slf4j-simple:1.7.12' } } diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketFactory.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketFactory.java index 460aa3d0a..475a54688 100644 --- a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketFactory.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketFactory.java @@ -1,10 +1,24 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.client; import io.reactivesocket.ConnectionSetupPayload; import io.reactivesocket.DefaultReactiveSocket; import io.reactivesocket.ReactiveSocket; import io.reactivesocket.ReactiveSocketFactory; -import io.reactivesocket.ReactiveSocketSocketAddressFactory; import io.reactivesocket.rx.Completable; import org.agrona.LangUtil; import org.reactivestreams.Publisher; @@ -26,7 +40,7 @@ /** * An implementation of {@link ReactiveSocketFactory} that creates Aeron ReactiveSockets. */ -public class AeronReactiveSocketFactory implements ReactiveSocketSocketAddressFactory { +public class AeronReactiveSocketFactory implements ReactiveSocketFactory { private static final Logger logger = LoggerFactory.getLogger(AeronReactiveSocketFactory.class); private final ConnectionSetupPayload connectionSetupPayload; diff --git a/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/server/ServerAeronManagerTest.java b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/server/ServerAeronManagerTest.java index 9d491d46b..3e7ae5fad 100644 --- a/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/server/ServerAeronManagerTest.java +++ b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/server/ServerAeronManagerTest.java @@ -1,3 +1,18 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.aeron.server; import io.aeron.driver.MediaDriver; diff --git a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketFactory.java b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketFactory.java index 666aa37c9..581a25302 100644 --- a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketFactory.java +++ b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketFactory.java @@ -19,7 +19,6 @@ import io.reactivesocket.DefaultReactiveSocket; import io.reactivesocket.ReactiveSocket; import io.reactivesocket.ReactiveSocketFactory; -import io.reactivesocket.ReactiveSocketSocketAddressFactory; import io.reactivesocket.javax.websocket.WebSocketDuplexConnection; import io.reactivesocket.rx.Completable; import org.glassfish.tyrus.client.ClientManager; @@ -37,7 +36,7 @@ /** * An implementation of {@link ReactiveSocketFactory} that creates JSR-356 WebSocket ReactiveSockets. */ -public class WebSocketReactiveSocketFactory implements ReactiveSocketSocketAddressFactory { +public class WebSocketReactiveSocketFactory implements ReactiveSocketFactory { private static final Logger logger = LoggerFactory.getLogger(WebSocketReactiveSocketFactory.class); private final ConnectionSetupPayload connectionSetupPayload; diff --git a/reactivesocket-local/build.gradle b/reactivesocket-local/build.gradle index 6750744e2..e69de29bb 100644 --- a/reactivesocket-local/build.gradle +++ b/reactivesocket-local/build.gradle @@ -1,14 +0,0 @@ -group 'io.reactivesocket' -version '0.1.3-SNAPSHOT' - -apply plugin: 'java' - -sourceCompatibility = 1.8 - -repositories { - mavenCentral() -} - -dependencies { - testCompile group: 'junit', name: 'junit', version: '4.11' -} diff --git a/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientDuplexConnection.java b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientDuplexConnection.java index efb4487da..e4d82aa8a 100644 --- a/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientDuplexConnection.java +++ b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientDuplexConnection.java @@ -1,3 +1,18 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.local; import io.reactivesocket.DuplexConnection; diff --git a/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientReactiveSocketFactory.java b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientReactiveSocketFactory.java index 1ededde10..e7fe1ccae 100644 --- a/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientReactiveSocketFactory.java +++ b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientReactiveSocketFactory.java @@ -1,3 +1,18 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.local; import io.reactivesocket.ConnectionSetupPayload; diff --git a/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalReactiveSocketManager.java b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalReactiveSocketManager.java index fbe9bab79..60d246266 100644 --- a/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalReactiveSocketManager.java +++ b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalReactiveSocketManager.java @@ -1,3 +1,18 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.local; import java.util.concurrent.ConcurrentHashMap; diff --git a/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerDuplexConection.java b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerDuplexConection.java index 00f83491a..2ecc5cad3 100644 --- a/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerDuplexConection.java +++ b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerDuplexConection.java @@ -1,3 +1,18 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.local; import io.reactivesocket.DuplexConnection; diff --git a/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerReactiveSocketFactory.java b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerReactiveSocketFactory.java index f361dcedd..03faaf522 100644 --- a/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerReactiveSocketFactory.java +++ b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerReactiveSocketFactory.java @@ -1,3 +1,18 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.reactivesocket.local; import io.reactivesocket.ConnectionSetupHandler; diff --git a/reactivesocket-local/src/main/java/io/reactivesocket/local/package-info.java b/reactivesocket-local/src/main/java/io/reactivesocket/local/package-info.java deleted file mode 100644 index f75336228..000000000 --- a/reactivesocket-local/src/main/java/io/reactivesocket/local/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * An implementation of ReactiveSocket that lets you run the client and server in the same local JVM. To create - * a client use {@link io.reactivesocket.local.LocalClientReactiveSocketFactory} and to create a server use - * {@link io.reactivesocket.local.LocalServerReactiveSocketFactory} factories classes. - */ -package io.reactivesocket.local; \ No newline at end of file diff --git a/reactivesocket-netty/build.gradle b/reactivesocket-netty/build.gradle index fd627f7e5..ff40ec133 100644 --- a/reactivesocket-netty/build.gradle +++ b/reactivesocket-netty/build.gradle @@ -1,3 +1,4 @@ dependencies { - compile 'io.netty:netty-all:4.1.0.CR5' + compile 'io.netty:netty-handler:4.1.0.CR6' + compile 'io.netty:netty-codec-http:4.1.0.CR6' } \ No newline at end of file diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketFactory.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketFactory.java index 1ae5c2ed2..fe54dd80b 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketFactory.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketFactory.java @@ -20,7 +20,6 @@ import io.reactivesocket.DefaultReactiveSocket; import io.reactivesocket.ReactiveSocket; import io.reactivesocket.ReactiveSocketFactory; -import io.reactivesocket.ReactiveSocketSocketAddressFactory; import io.reactivesocket.rx.Completable; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; @@ -36,7 +35,7 @@ /** * An implementation of {@link ReactiveSocketFactory} that creates Netty WebSocket ReactiveSockets. */ -public class WebSocketReactiveSocketFactory implements ReactiveSocketSocketAddressFactory { +public class WebSocketReactiveSocketFactory implements ReactiveSocketFactory { private static final Logger logger = LoggerFactory.getLogger(WebSocketReactiveSocketFactory.class); private final ConnectionSetupPayload connectionSetupPayload; From cdd239be8f3e1f9d99afd180201b6ad45b2aa284 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Fri, 8 Apr 2016 14:53:31 -0700 Subject: [PATCH 088/105] adding netty tcp support for rs --- .../tcp/client/ClientTcpDuplexConnection.java | 148 ++++++++++++ .../client/ReactiveSocketClientHandler.java | 60 +++++ .../tcp/client/TcpReactiveSocketFactory.java | 94 ++++++++ .../server/ReactiveSocketServerHandler.java | 113 ++++++++++ .../tcp/server/ServerTcpDuplexConnection.java | 100 +++++++++ .../netty/{websocket => }/TestUtil.java | 2 +- .../netty/tcp/ClientServerTest.java | 211 ++++++++++++++++++ .../io/reactivesocket/netty/tcp/Ping.java | 109 +++++++++ .../io/reactivesocket/netty/tcp/Pong.java | 172 ++++++++++++++ .../netty/websocket/ClientServerTest.java | 1 + .../reactivesocket/netty/websocket/Pong.java | 1 + .../test/resources/simplelogger.properties | 2 +- 12 files changed, 1011 insertions(+), 2 deletions(-) create mode 100644 reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java create mode 100644 reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ReactiveSocketClientHandler.java create mode 100644 reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketFactory.java create mode 100644 reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/server/ReactiveSocketServerHandler.java create mode 100644 reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/server/ServerTcpDuplexConnection.java rename reactivesocket-netty/src/test/java/io/reactivesocket/netty/{websocket => }/TestUtil.java (99%) create mode 100644 reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/ClientServerTest.java create mode 100644 reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/Ping.java create mode 100644 reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/Pong.java diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java new file mode 100644 index 000000000..cf1ab6d31 --- /dev/null +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java @@ -0,0 +1,148 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.netty.tcp.client; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.reactivesocket.DuplexConnection; +import io.reactivesocket.Frame; +import io.reactivesocket.rx.Completable; +import io.reactivesocket.rx.Observable; +import io.reactivesocket.rx.Observer; +import org.agrona.BitUtil; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.concurrent.CopyOnWriteArrayList; + +public class ClientTcpDuplexConnection implements DuplexConnection { + private Channel channel; + + private Bootstrap bootstrap; + + private final CopyOnWriteArrayList> subjects; + + private ClientTcpDuplexConnection(Channel channel, Bootstrap bootstrap, CopyOnWriteArrayList> subjects) { + this.subjects = subjects; + this.channel = channel; + this.bootstrap = bootstrap; + } + + public static Publisher create(SocketAddress socketAddress, EventLoopGroup eventLoopGroup) { + if (socketAddress instanceof InetSocketAddress) { + try { + return create(socketAddress, eventLoopGroup); + } catch (Exception e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } else { + throw new IllegalArgumentException("unknown socket address type => " + socketAddress.getClass()); + } + } + + public static Publisher create(InetSocketAddress address, EventLoopGroup eventLoopGroup) { + return s -> { + CopyOnWriteArrayList> subjects = new CopyOnWriteArrayList<>(); + ReactiveSocketClientHandler clientHandler = new ReactiveSocketClientHandler(subjects); + Bootstrap bootstrap = new Bootstrap(); + ChannelFuture connect = bootstrap + .group(eventLoopGroup) + .channel(NioSocketChannel.class) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) throws Exception { + ChannelPipeline p = ch.pipeline(); + p.addLast( + new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE >> 1, 0, BitUtil.SIZE_OF_INT, -1 * BitUtil.SIZE_OF_INT, 0), + clientHandler + ); + } + }).connect(address.getHostName(), address.getPort()); + + connect.addListener(connectFuture -> { + if (connectFuture.isSuccess()) { + final Channel ch = connect.channel(); + s.onNext(new ClientTcpDuplexConnection(ch, bootstrap, subjects)); + s.onComplete(); + } else { + s.onError(connectFuture.cause()); + } + }); + }; + } + + @Override + public final Observable getInput() { + return o -> { + o.onSubscribe(() -> subjects.removeIf(s -> s == o)); + subjects.add(o); + }; + } + + @Override + public void addOutput(Publisher o, Completable callback) { + o.subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); + } + + @Override + public void onNext(Frame frame) { + try { + ByteBuf byteBuf = Unpooled.wrappedBuffer(frame.getByteBuffer()); + ChannelFuture channelFuture = channel.writeAndFlush(byteBuf); + channelFuture.addListener(future -> { + Throwable cause = future.cause(); + if (cause != null) { + callback.error(cause); + } + }); + } catch (Throwable t) { + onError(t); + } + } + + @Override + public void onError(Throwable t) { + callback.error(t); + } + + @Override + public void onComplete() { + callback.success(); + } + }); + } + + @Override + public void close() throws IOException { + channel.close(); + } +} diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ReactiveSocketClientHandler.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ReactiveSocketClientHandler.java new file mode 100644 index 000000000..5632cf03b --- /dev/null +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ReactiveSocketClientHandler.java @@ -0,0 +1,60 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.netty.tcp.client; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.reactivesocket.Frame; +import io.reactivesocket.netty.MutableDirectByteBuf; +import io.reactivesocket.rx.Observer; + +import java.util.concurrent.CopyOnWriteArrayList; + +@ChannelHandler.Sharable +public class ReactiveSocketClientHandler extends ChannelInboundHandlerAdapter { + + private final CopyOnWriteArrayList> subjects; + + public ReactiveSocketClientHandler(CopyOnWriteArrayList> subjects) { + this.subjects = subjects; + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object content) { + ByteBuf byteBuf = (ByteBuf) content; + try { + MutableDirectByteBuf mutableDirectByteBuf = new MutableDirectByteBuf(byteBuf); + final Frame from = Frame.from(mutableDirectByteBuf, 0, mutableDirectByteBuf.capacity()); + subjects.forEach(o -> o.onNext(from)); + } finally { + byteBuf.release(); + } + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) { + ctx.flush(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + // Close the connection when an exception is raised. + cause.printStackTrace(); + ctx.close(); + } +} diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketFactory.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketFactory.java new file mode 100644 index 000000000..20d74270c --- /dev/null +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketFactory.java @@ -0,0 +1,94 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.netty.tcp.client; + +import io.netty.channel.EventLoopGroup; +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.DefaultReactiveSocket; +import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.ReactiveSocketFactory; +import io.reactivesocket.ReactiveSocketSocketAddressFactory; +import io.reactivesocket.rx.Completable; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.Observable; +import rx.RxReactiveStreams; + +import java.net.SocketAddress; +import java.util.function.Consumer; + +/** + * An implementation of {@link ReactiveSocketFactory} that creates Netty WebSocket ReactiveSockets. + */ +public class TcpReactiveSocketFactory implements ReactiveSocketSocketAddressFactory { + private static final Logger logger = LoggerFactory.getLogger(TcpReactiveSocketFactory.class); + + private final ConnectionSetupPayload connectionSetupPayload; + private final Consumer errorStream; + private final EventLoopGroup eventLoopGroup; + + public TcpReactiveSocketFactory(EventLoopGroup eventLoopGroup, ConnectionSetupPayload connectionSetupPayload, Consumer errorStream) { + this.connectionSetupPayload = connectionSetupPayload; + this.errorStream = errorStream; + this.eventLoopGroup = eventLoopGroup; + } + + @Override + public Publisher call(SocketAddress address) { + Publisher connection + = ClientTcpDuplexConnection.create(address, eventLoopGroup); + + Observable result = Observable.create(s -> + connection.subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(1); + } + + @Override + public void onNext(ClientTcpDuplexConnection connection) { + ReactiveSocket reactiveSocket = DefaultReactiveSocket.fromClientConnection(connection, connectionSetupPayload, errorStream); + reactiveSocket.start(new Completable() { + @Override + public void success() { + s.onNext(reactiveSocket); + s.onCompleted(); + } + + @Override + public void error(Throwable e) { + s.onError(e); + } + }); + } + + @Override + public void onError(Throwable t) { + s.onError(t); + } + + @Override + public void onComplete() { + } + }) + ); + + return RxReactiveStreams.toPublisher(result); + } +} diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/server/ReactiveSocketServerHandler.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/server/ReactiveSocketServerHandler.java new file mode 100644 index 000000000..f81e4de6c --- /dev/null +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/server/ReactiveSocketServerHandler.java @@ -0,0 +1,113 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.netty.tcp.server; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelId; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelPipeline; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.reactivesocket.ConnectionSetupHandler; +import io.reactivesocket.DefaultReactiveSocket; +import io.reactivesocket.Frame; +import io.reactivesocket.LeaseGovernor; +import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.netty.MutableDirectByteBuf; +import org.agrona.BitUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.ConcurrentHashMap; + +@ChannelHandler.Sharable +public class ReactiveSocketServerHandler extends ChannelInboundHandlerAdapter { + private Logger logger = LoggerFactory.getLogger(ReactiveSocketServerHandler.class); + + private ConcurrentHashMap duplexConnections = new ConcurrentHashMap<>(); + + private ConnectionSetupHandler setupHandler; + + private LeaseGovernor leaseGovernor; + + private ReactiveSocketServerHandler(ConnectionSetupHandler setupHandler, LeaseGovernor leaseGovernor) { + this.setupHandler = setupHandler; + this.leaseGovernor = leaseGovernor; + } + + public static ReactiveSocketServerHandler create(ConnectionSetupHandler setupHandler) { + return create(setupHandler, LeaseGovernor.UNLIMITED_LEASE_GOVERNOR); + } + + public static ReactiveSocketServerHandler create(ConnectionSetupHandler setupHandler, LeaseGovernor leaseGovernor) { + return new + ReactiveSocketServerHandler( + setupHandler, + leaseGovernor); + + } + + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + ChannelPipeline cp = ctx.pipeline(); + if (cp.get(LengthFieldBasedFrameDecoder.class) == null) { + ctx + .pipeline() + .addBefore( + ctx.name(), + LengthFieldBasedFrameDecoder.class.getName(), + new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE >> 1, 0, BitUtil.SIZE_OF_INT, -1 * BitUtil.SIZE_OF_INT, 0)); + } + + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + ByteBuf content = (ByteBuf) msg; + try { + MutableDirectByteBuf mutableDirectByteBuf = new MutableDirectByteBuf(content); + Frame from = Frame.from(mutableDirectByteBuf, 0, mutableDirectByteBuf.capacity()); + channelRegistered(ctx); + ServerTcpDuplexConnection connection = duplexConnections.computeIfAbsent(ctx.channel().id(), i -> { + logger.info("No connection found for channel id: " + i + " from host " + ctx.channel().remoteAddress().toString()); + ServerTcpDuplexConnection c = new ServerTcpDuplexConnection(ctx); + ReactiveSocket reactiveSocket = DefaultReactiveSocket.fromServerConnection(c, setupHandler, leaseGovernor, throwable -> throwable.printStackTrace()); + reactiveSocket.startAndWait(); + return c; + }); + if (connection != null) { + connection + .getSubscribers() + .forEach(o -> o.onNext(from)); + } + } finally { + content.release(); + } + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) { + ctx.flush(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + super.exceptionCaught(ctx, cause); + + logger.error("caught an unhandled exception", cause); + } +} diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/server/ServerTcpDuplexConnection.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/server/ServerTcpDuplexConnection.java new file mode 100644 index 000000000..c126bc78d --- /dev/null +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/server/ServerTcpDuplexConnection.java @@ -0,0 +1,100 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.netty.tcp.server; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.reactivesocket.DuplexConnection; +import io.reactivesocket.Frame; +import io.reactivesocket.rx.Completable; +import io.reactivesocket.rx.Observable; +import io.reactivesocket.rx.Observer; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +public class ServerTcpDuplexConnection implements DuplexConnection { + private final CopyOnWriteArrayList> subjects; + + private final ChannelHandlerContext ctx; + + public ServerTcpDuplexConnection(ChannelHandlerContext ctx) { + this.subjects = new CopyOnWriteArrayList<>(); + this.ctx = ctx; + } + + public List> getSubscribers() { + return subjects; + } + + @Override + public final Observable getInput() { + return o -> { + o.onSubscribe(() -> subjects.removeIf(s -> s == o)); + subjects.add(o); + }; + } + + @Override + public void addOutput(Publisher o, Completable callback) { + o.subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); + } + + @Override + public void onNext(Frame frame) { + try { + ByteBuffer data = frame.getByteBuffer(); + ByteBuf byteBuf = Unpooled.wrappedBuffer(data); + ChannelFuture channelFuture = ctx.writeAndFlush(byteBuf); + channelFuture.addListener(future -> { + Throwable cause = future.cause(); + if (cause != null) { + cause.printStackTrace(); + callback.error(cause); + } + }); + } catch (Throwable t) { + onError(t); + } + } + + @Override + public void onError(Throwable t) { + callback.error(t); + } + + @Override + public void onComplete() { + callback.success(); + } + }); + } + + @Override + public void close() throws IOException { + + } +} diff --git a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/TestUtil.java b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/TestUtil.java similarity index 99% rename from reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/TestUtil.java rename to reactivesocket-netty/src/test/java/io/reactivesocket/netty/TestUtil.java index 54d380b5b..d0d0bd2de 100644 --- a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/TestUtil.java +++ b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/TestUtil.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.reactivesocket.netty.websocket; +package io.reactivesocket.netty; import io.netty.buffer.ByteBuf; import io.reactivesocket.Frame; diff --git a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/ClientServerTest.java b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/ClientServerTest.java new file mode 100644 index 000000000..29f206bcc --- /dev/null +++ b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/ClientServerTest.java @@ -0,0 +1,211 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.netty.tcp; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.DefaultReactiveSocket; +import io.reactivesocket.Payload; +import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.RequestHandler; +import io.reactivesocket.netty.TestUtil; +import io.reactivesocket.netty.tcp.client.ClientTcpDuplexConnection; +import io.reactivesocket.netty.tcp.server.ReactiveSocketServerHandler; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import rx.Observable; +import rx.RxReactiveStreams; +import rx.observers.TestSubscriber; + +import java.net.InetSocketAddress; +import java.util.concurrent.TimeUnit; + +public class ClientServerTest { + + static ReactiveSocket client; + static Channel serverChannel; + + static EventLoopGroup bossGroup = new NioEventLoopGroup(1); + static EventLoopGroup workerGroup = new NioEventLoopGroup(4); + + static ReactiveSocketServerHandler serverHandler = ReactiveSocketServerHandler.create(setupPayload -> + new RequestHandler() { + @Override + public Publisher handleRequestResponse(Payload payload) { + return s -> { + //System.out.println("Handling request/response payload => " + s.toString()); + Payload response = TestUtil.utf8EncodedPayload("hello world", "metadata"); + s.onNext(response); + s.onComplete(); + }; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + Payload response = TestUtil.utf8EncodedPayload("hello world", "metadata"); + + return RxReactiveStreams + .toPublisher(Observable + .range(1, 10) + .map(i -> response)); + } + + @Override + public Publisher handleSubscription(Payload payload) { + Payload response = TestUtil.utf8EncodedPayload("hello world", "metadata"); + + return RxReactiveStreams + .toPublisher(Observable + .range(1, 10) + .map(i -> response) + .repeat()); + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return Subscriber::onComplete; + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher inputs) { + return null; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + } + ); + + @BeforeClass + public static void setup() throws Exception { + ServerBootstrap b = new ServerBootstrap(); + b.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .handler(new LoggingHandler(LogLevel.INFO)) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ChannelPipeline pipeline = ch.pipeline(); + pipeline.addLast(serverHandler); + } + }); + + serverChannel = b.bind("localhost", 7878).sync().channel(); + + ClientTcpDuplexConnection duplexConnection = RxReactiveStreams.toObservable( + ClientTcpDuplexConnection.create(InetSocketAddress.createUnresolved("localhost", 7878), new NioEventLoopGroup()) + ).toBlocking().single(); + + client = DefaultReactiveSocket + .fromClientConnection(duplexConnection, ConnectionSetupPayload.create("UTF-8", "UTF-8"), t -> t.printStackTrace()); + + client.startAndWait(); + } + + @AfterClass + public static void tearDown() { + serverChannel.close(); + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + + @Test + public void testRequestResponse1() { + requestResponseN(1500, 1); + } + + @Test + public void testRequestResponse10() { + requestResponseN(1500, 10); + } + + + @Test + public void testRequestResponse100() { + requestResponseN(1500, 100); + } + + @Test + public void testRequestResponse10_000() { + requestResponseN(60_000, 10_000); + } + + @Test + public void testRequestStream() { + TestSubscriber ts = TestSubscriber.create(); + + RxReactiveStreams + .toObservable(client.requestStream(TestUtil.utf8EncodedPayload("hello", "metadata"))) + .subscribe(ts); + + + ts.awaitTerminalEvent(3_000, TimeUnit.MILLISECONDS); + ts.assertValueCount(10); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + @Test + public void testRequestSubscription() throws InterruptedException { + TestSubscriber ts = TestSubscriber.create(); + + RxReactiveStreams + .toObservable(client.requestSubscription(TestUtil.utf8EncodedPayload("hello sub", "metadata sub"))) + .take(10) + .subscribe(ts); + + ts.awaitTerminalEvent(3_000, TimeUnit.MILLISECONDS); + ts.assertValueCount(10); + ts.assertNoErrors(); + } + + + public void requestResponseN(int timeout, int count) { + + TestSubscriber ts = TestSubscriber.create(); + + Observable + .range(1, count) + .flatMap(i -> + RxReactiveStreams + .toObservable(client + .requestResponse(TestUtil.utf8EncodedPayload("hello", "metadata"))) + .map(payload -> TestUtil.byteToString(payload.getData())) + ) + .doOnError(Throwable::printStackTrace) + .subscribe(ts); + + ts.awaitTerminalEvent(timeout, TimeUnit.MILLISECONDS); + ts.assertValueCount(count); + ts.assertNoErrors(); + ts.assertCompleted(); + } + + +} \ No newline at end of file diff --git a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/Ping.java b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/Ping.java new file mode 100644 index 000000000..d8c95aba3 --- /dev/null +++ b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/Ping.java @@ -0,0 +1,109 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.netty.tcp; + +import io.netty.channel.nio.NioEventLoopGroup; +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.DefaultReactiveSocket; +import io.reactivesocket.Payload; +import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.netty.tcp.client.ClientTcpDuplexConnection; +import org.HdrHistogram.Recorder; +import org.reactivestreams.Publisher; +import rx.Observable; +import rx.RxReactiveStreams; +import rx.Subscriber; +import rx.schedulers.Schedulers; + +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class Ping { + public static void main(String... args) throws Exception { + Publisher publisher = ClientTcpDuplexConnection + .create(InetSocketAddress.createUnresolved("localhost", 7878), new NioEventLoopGroup(1)); + + ClientTcpDuplexConnection duplexConnection = RxReactiveStreams.toObservable(publisher).toBlocking().last(); + ReactiveSocket reactiveSocket = DefaultReactiveSocket.fromClientConnection(duplexConnection, ConnectionSetupPayload.create("UTF-8", "UTF-8"), t -> t.printStackTrace()); + + reactiveSocket.startAndWait(); + + byte[] data = "hello".getBytes(); + + Payload keyPayload = new Payload() { + @Override + public ByteBuffer getData() { + return ByteBuffer.wrap(data); + } + + @Override + public ByteBuffer getMetadata() { + return null; + } + }; + + int n = 1_000_000; + CountDownLatch latch = new CountDownLatch(n); + final Recorder histogram = new Recorder(3600000000000L, 3); + + Schedulers + .computation() + .createWorker() + .schedulePeriodically(() -> { + System.out.println("---- PING/ PONG HISTO ----"); + histogram.getIntervalHistogram() + .outputPercentileDistribution(System.out, 5, 1000.0, false); + System.out.println("---- PING/ PONG HISTO ----"); + }, 1, 1, TimeUnit.SECONDS); + + Observable + .range(1, Integer.MAX_VALUE) + .flatMap(i -> { + long start = System.nanoTime(); + + return RxReactiveStreams + .toObservable( + reactiveSocket + .requestResponse(keyPayload)) + .doOnNext(s -> { + long diff = System.nanoTime() - start; + histogram.recordValue(diff); + }); + }, 16) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + } + + @Override + public void onNext(Payload payload) { + latch.countDown(); + } + }); + + latch.await(1, TimeUnit.HOURS); + System.out.println("Sent => " + n); + System.exit(0); + } +} diff --git a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/Pong.java b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/Pong.java new file mode 100644 index 000000000..30e508c46 --- /dev/null +++ b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/Pong.java @@ -0,0 +1,172 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.netty.tcp; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; +import io.reactivesocket.Payload; +import io.reactivesocket.RequestHandler; +import io.reactivesocket.netty.TestUtil; +import io.reactivesocket.netty.tcp.server.ReactiveSocketServerHandler; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import rx.Observable; +import rx.RxReactiveStreams; + +import java.nio.ByteBuffer; +import java.util.Random; + +public class Pong { + public static void main(String... args) throws Exception { + byte[] response = new byte[1024]; + Random r = new Random(); + r.nextBytes(response); + + ReactiveSocketServerHandler serverHandler = + ReactiveSocketServerHandler.create(setupPayload -> new RequestHandler() { + @Override + public Publisher handleRequestResponse(Payload payload) { + return new Publisher() { + @Override + public void subscribe(Subscriber s) { + Payload responsePayload = new Payload() { + ByteBuffer data = ByteBuffer.wrap(response); + ByteBuffer metadata = ByteBuffer.allocate(0); + + public ByteBuffer getData() { + return data; + } + + @Override + public ByteBuffer getMetadata() { + return metadata; + } + }; + + s.onNext(responsePayload); + s.onComplete(); + } + }; + } + + @Override + public Publisher handleRequestStream(Payload payload) { + Payload response = + TestUtil.utf8EncodedPayload("hello world", "metadata"); + return RxReactiveStreams + .toPublisher(Observable + .range(1, 10) + .map(i -> response)); + } + + @Override + public Publisher handleSubscription(Payload payload) { + Payload response = + TestUtil.utf8EncodedPayload("hello world", "metadata"); + return RxReactiveStreams + .toPublisher(Observable + .range(1, 10) + .map(i -> response)); + } + + @Override + public Publisher handleFireAndForget(Payload payload) { + return Subscriber::onComplete; + } + + @Override + public Publisher handleChannel(Payload initialPayload, Publisher inputs) { + Observable observable = + RxReactiveStreams + .toObservable(inputs) + .map(input -> input); + return RxReactiveStreams.toPublisher(observable); + +// return outputSubscriber -> { +// inputs.subscribe(new Subscriber() { +// private int count = 0; +// private boolean completed = false; +// +// @Override +// public void onSubscribe(Subscription s) { +// //outputSubscriber.onSubscribe(s); +// s.request(128); +// } +// +// @Override +// public void onNext(Payload input) { +// if (completed) { +// return; +// } +// count += 1; +// outputSubscriber.onNext(input); +// outputSubscriber.onNext(input); +// if (count > 10) { +// completed = true; +// outputSubscriber.onComplete(); +// } +// } +// +// @Override +// public void onError(Throwable t) { +// if (!completed) { +// outputSubscriber.onError(t); +// } +// } +// +// @Override +// public void onComplete() { +// if (!completed) { +// outputSubscriber.onComplete(); +// } +// } +// }); +// }; + } + + @Override + public Publisher handleMetadataPush(Payload payload) { + return null; + } + }); + + EventLoopGroup bossGroup = new NioEventLoopGroup(1); + EventLoopGroup workerGroup = new NioEventLoopGroup(); + + ServerBootstrap b = new ServerBootstrap(); + b.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .handler(new LoggingHandler(LogLevel.INFO)) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ChannelPipeline pipeline = ch.pipeline(); + pipeline.addLast(serverHandler); + } + }); + + Channel localhost = b.bind("localhost", 7878).sync().channel(); + localhost.closeFuture().sync(); + + } +} diff --git a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java index ca7a82657..2b034472c 100644 --- a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java +++ b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java @@ -28,6 +28,7 @@ import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import io.reactivesocket.*; +import io.reactivesocket.netty.TestUtil; import io.reactivesocket.netty.websocket.client.ClientWebSocketDuplexConnection; import io.reactivesocket.netty.websocket.server.ReactiveSocketServerHandler; import org.junit.AfterClass; diff --git a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Pong.java b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Pong.java index b20069954..d8ae24db7 100644 --- a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Pong.java +++ b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Pong.java @@ -29,6 +29,7 @@ import io.netty.handler.logging.LoggingHandler; import io.reactivesocket.Payload; import io.reactivesocket.RequestHandler; +import io.reactivesocket.netty.TestUtil; import io.reactivesocket.netty.websocket.server.ReactiveSocketServerHandler; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; diff --git a/reactivesocket-netty/src/test/resources/simplelogger.properties b/reactivesocket-netty/src/test/resources/simplelogger.properties index 463129958..e82e3ef30 100644 --- a/reactivesocket-netty/src/test/resources/simplelogger.properties +++ b/reactivesocket-netty/src/test/resources/simplelogger.properties @@ -5,7 +5,7 @@ # Must be one of ("trace", "debug", "info", "warn", or "error"). # If not specified, defaults to "info". #org.slf4j.simpleLogger.defaultLogLevel=debug -org.slf4j.simpleLogger.defaultLogLevel=trace +org.slf4j.simpleLogger.defaultLogLevel=info # Logging detail level for a SimpleLogger instance named "xxxxx". # Must be one of ("trace", "debug", "info", "warn", or "error"). From e75d0db7ddca0516942bc2573ca1206f5fc805b0 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Fri, 8 Apr 2016 15:04:51 -0700 Subject: [PATCH 089/105] updated TcpReactiveSocketFactory to use ReactiveSocketFactory class --- .../netty/tcp/client/TcpReactiveSocketFactory.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketFactory.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketFactory.java index 20d74270c..6eea5fa49 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketFactory.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketFactory.java @@ -20,7 +20,6 @@ import io.reactivesocket.DefaultReactiveSocket; import io.reactivesocket.ReactiveSocket; import io.reactivesocket.ReactiveSocketFactory; -import io.reactivesocket.ReactiveSocketSocketAddressFactory; import io.reactivesocket.rx.Completable; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; @@ -36,7 +35,7 @@ /** * An implementation of {@link ReactiveSocketFactory} that creates Netty WebSocket ReactiveSockets. */ -public class TcpReactiveSocketFactory implements ReactiveSocketSocketAddressFactory { +public class TcpReactiveSocketFactory implements ReactiveSocketFactory { private static final Logger logger = LoggerFactory.getLogger(TcpReactiveSocketFactory.class); private final ConnectionSetupPayload connectionSetupPayload; From ddba337dc90570d18c3e20dcbe9edf3fec2535b1 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Mon, 2 May 2016 16:33:45 -0700 Subject: [PATCH 090/105] throwing a TransportException when a connection is closed --- .../aeron/client/AeronClientDuplexConnection.java | 8 +++++++- .../netty/tcp/client/ClientTcpDuplexConnection.java | 8 +++++++- .../websocket/client/ClientWebSocketDuplexConnection.java | 8 +++++++- .../io/reactivesocket/netty/tcp/ClientServerTest.java | 2 +- .../src/test/java/io/reactivesocket/netty/tcp/Ping.java | 2 ++ .../src/test/java/io/reactivesocket/netty/tcp/Pong.java | 2 +- .../reactivesocket/netty/websocket/ClientServerTest.java | 2 +- .../test/java/io/reactivesocket/netty/websocket/Pong.java | 2 +- 8 files changed, 27 insertions(+), 7 deletions(-) diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java index 7d7c4a3cd..b57007342 100644 --- a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java @@ -19,6 +19,8 @@ import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; import io.reactivesocket.aeron.internal.Loggable; +import io.reactivesocket.aeron.internal.NotConnectedException; +import io.reactivesocket.exceptions.TransportException; import io.reactivesocket.rx.Completable; import io.reactivesocket.rx.Disposable; import io.reactivesocket.rx.Observable; @@ -101,7 +103,11 @@ public void onNext(Frame frame) { @Override public void onError(Throwable t) { - callback.error(t); + if (t instanceof NotConnectedException) { + callback.error(new TransportException(t)); + } else { + callback.error(t); + } } @Override diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java index cf1ab6d31..10f285da9 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java @@ -28,6 +28,7 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; +import io.reactivesocket.exceptions.TransportException; import io.reactivesocket.rx.Completable; import io.reactivesocket.rx.Observable; import io.reactivesocket.rx.Observer; @@ -39,6 +40,7 @@ import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.nio.channels.ClosedChannelException; import java.util.concurrent.CopyOnWriteArrayList; public class ClientTcpDuplexConnection implements DuplexConnection { @@ -121,7 +123,11 @@ public void onNext(Frame frame) { channelFuture.addListener(future -> { Throwable cause = future.cause(); if (cause != null) { - callback.error(cause); + if (cause instanceof ClosedChannelException) { + onError(new TransportException(cause)); + } else { + onError(cause); + } } }); } catch (Throwable t) { diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java index 301b1227e..05bd5a31f 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java @@ -27,6 +27,7 @@ import io.netty.handler.codec.http.websocketx.*; import io.reactivesocket.DuplexConnection; import io.reactivesocket.Frame; +import io.reactivesocket.exceptions.TransportException; import io.reactivesocket.rx.Completable; import io.reactivesocket.rx.Observable; import io.reactivesocket.rx.Observer; @@ -39,6 +40,7 @@ import java.net.SocketAddress; import java.net.URI; import java.net.URISyntaxException; +import java.nio.channels.ClosedChannelException; import java.util.concurrent.CopyOnWriteArrayList; public class ClientWebSocketDuplexConnection implements DuplexConnection { @@ -136,7 +138,11 @@ public void onNext(Frame frame) { channelFuture.addListener(future -> { Throwable cause = future.cause(); if (cause != null) { - callback.error(cause); + if (cause instanceof ClosedChannelException) { + onError(new TransportException(cause)); + } else { + onError(cause); + } } }); } catch (Throwable t) { diff --git a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/ClientServerTest.java b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/ClientServerTest.java index 29f206bcc..ccf5bdc8d 100644 --- a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/ClientServerTest.java +++ b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/ClientServerTest.java @@ -52,7 +52,7 @@ public class ClientServerTest { static EventLoopGroup bossGroup = new NioEventLoopGroup(1); static EventLoopGroup workerGroup = new NioEventLoopGroup(4); - static ReactiveSocketServerHandler serverHandler = ReactiveSocketServerHandler.create(setupPayload -> + static ReactiveSocketServerHandler serverHandler = ReactiveSocketServerHandler.create((setupPayload, rs) -> new RequestHandler() { @Override public Publisher handleRequestResponse(Payload payload) { diff --git a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/Ping.java b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/Ping.java index d8c95aba3..61905bf71 100644 --- a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/Ping.java +++ b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/Ping.java @@ -80,11 +80,13 @@ public ByteBuffer getMetadata() { .toObservable( reactiveSocket .requestResponse(keyPayload)) + .doOnError(t -> t.printStackTrace()) .doOnNext(s -> { long diff = System.nanoTime() - start; histogram.recordValue(diff); }); }, 16) + .doOnError(t -> t.printStackTrace()) .subscribe(new Subscriber() { @Override public void onCompleted() { diff --git a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/Pong.java b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/Pong.java index 30e508c46..2dd251a50 100644 --- a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/Pong.java +++ b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/Pong.java @@ -43,7 +43,7 @@ public static void main(String... args) throws Exception { r.nextBytes(response); ReactiveSocketServerHandler serverHandler = - ReactiveSocketServerHandler.create(setupPayload -> new RequestHandler() { + ReactiveSocketServerHandler.create((setupPayload, rs) -> new RequestHandler() { @Override public Publisher handleRequestResponse(Payload payload) { return new Publisher() { diff --git a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java index 2b034472c..bdc4c101a 100644 --- a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java +++ b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java @@ -51,7 +51,7 @@ public class ClientServerTest { static EventLoopGroup bossGroup = new NioEventLoopGroup(1); static EventLoopGroup workerGroup = new NioEventLoopGroup(4); - static ReactiveSocketServerHandler serverHandler = ReactiveSocketServerHandler.create(setupPayload -> + static ReactiveSocketServerHandler serverHandler = ReactiveSocketServerHandler.create((setupPayload, rs) -> new RequestHandler() { @Override public Publisher handleRequestResponse(Payload payload) { diff --git a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Pong.java b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Pong.java index d8ae24db7..0b6d80391 100644 --- a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Pong.java +++ b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Pong.java @@ -46,7 +46,7 @@ public static void main(String... args) throws Exception { r.nextBytes(response); ReactiveSocketServerHandler serverHandler = - ReactiveSocketServerHandler.create(setupPayload -> new RequestHandler() { + ReactiveSocketServerHandler.create((setupPayload, rs) -> new RequestHandler() { @Override public Publisher handleRequestResponse(Payload payload) { return new Publisher() { From b30241f1eaaff30729a468fe1199cb134af5aefc Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Mon, 2 May 2016 16:52:02 -0700 Subject: [PATCH 091/105] fixed tests --- .../reactivesocket/aeron/example/fireandforget/Forget.java | 3 ++- .../io/reactivesocket/aeron/example/requestreply/Pong.java | 3 ++- .../aeron/client/ReactiveSocketAeronTest.java | 6 +++--- .../javax/websocket/ClientServerEndpoint.java | 2 +- .../io/reactivesocket/javax/websocket/PongEndpoint.java | 2 +- .../test/java/io/reactivesocket/local/ClientServerTest.java | 2 +- 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/fireandforget/Forget.java b/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/fireandforget/Forget.java index e780d48c9..37230114d 100644 --- a/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/fireandforget/Forget.java +++ b/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/fireandforget/Forget.java @@ -18,6 +18,7 @@ import io.reactivesocket.ConnectionSetupHandler; import io.reactivesocket.ConnectionSetupPayload; import io.reactivesocket.Payload; +import io.reactivesocket.ReactiveSocket; import io.reactivesocket.RequestHandler; import io.reactivesocket.aeron.server.ReactiveSocketAeronServer; import io.reactivesocket.exceptions.SetupException; @@ -33,7 +34,7 @@ public static void main(String... args) { ReactiveSocketAeronServer server = ReactiveSocketAeronServer.create(host, 39790, new ConnectionSetupHandler() { @Override - public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + public RequestHandler apply(ConnectionSetupPayload setupPayload, ReactiveSocket rs) throws SetupException { return new RequestHandler() { @Override public Publisher handleRequestResponse(Payload payload) { diff --git a/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/requestreply/Pong.java b/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/requestreply/Pong.java index 25a7dfc1f..baa733309 100644 --- a/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/requestreply/Pong.java +++ b/reactivesocket-aeron/src/examples/java/io/reactivesocket/aeron/example/requestreply/Pong.java @@ -18,6 +18,7 @@ import io.reactivesocket.ConnectionSetupHandler; import io.reactivesocket.ConnectionSetupPayload; import io.reactivesocket.Payload; +import io.reactivesocket.ReactiveSocket; import io.reactivesocket.RequestHandler; import io.reactivesocket.aeron.server.ReactiveSocketAeronServer; import io.reactivesocket.exceptions.SetupException; @@ -44,7 +45,7 @@ public static void main(String... args) { ReactiveSocketAeronServer server = ReactiveSocketAeronServer.create(host, 39790, new ConnectionSetupHandler() { @Override - public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + public RequestHandler apply(ConnectionSetupPayload setupPayload, ReactiveSocket rs) throws SetupException { return new RequestHandler() { @Override public Publisher handleRequestResponse(Payload payload) { diff --git a/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java index 2d0fa566e..3d01d46b1 100644 --- a/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java +++ b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java @@ -112,7 +112,7 @@ public void requestResponseN(int count) throws Exception { AtomicLong counter = new AtomicLong(); ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { @Override - public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + public RequestHandler apply(ConnectionSetupPayload setupPayload, ReactiveSocket rs) throws SetupException { return new RequestHandler.Builder() .withRequestResponse(new Function>() { Frame frame = Frame.from(ByteBuffer.allocate(1)); @@ -194,7 +194,7 @@ public void onNext(Payload payload) { } public void requestStreamN(int count) throws Exception { - ReactiveSocketAeronServer.create(setupPayload -> + ReactiveSocketAeronServer.create((setupPayload, rs) -> new RequestHandler.Builder() .withRequestStream(payload -> { ByteBuffer data = payload.getData(); @@ -264,7 +264,7 @@ public void testReconnection() throws Exception { ReactiveSocketAeronServer server = ReactiveSocketAeronServer.create(new ConnectionSetupHandler() { @Override - public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + public RequestHandler apply(ConnectionSetupPayload setupPayload, ReactiveSocket rs) throws SetupException { return new RequestHandler() { Frame frame = Frame.from(ByteBuffer.allocate(1)); diff --git a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerEndpoint.java b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerEndpoint.java index f1b56ecde..4b4c948aa 100644 --- a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerEndpoint.java +++ b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerEndpoint.java @@ -25,7 +25,7 @@ public class ClientServerEndpoint extends ReactiveSocketWebSocketServer { public ClientServerEndpoint() { - super(setupPayload -> new RequestHandler() { + super((setupPayload, rs) -> new RequestHandler() { @Override public Publisher handleRequestResponse(Payload payload) { return s -> { diff --git a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/PongEndpoint.java b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/PongEndpoint.java index 0c242e3aa..bac0ae100 100644 --- a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/PongEndpoint.java +++ b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/PongEndpoint.java @@ -34,7 +34,7 @@ public class PongEndpoint extends ReactiveSocketWebSocketServer { } public PongEndpoint() { - super(setupPayload -> new RequestHandler() { + super((setupPayload, rs) -> new RequestHandler() { @Override public Publisher handleRequestResponse(Payload payload) { return new Publisher() { diff --git a/reactivesocket-local/src/test/java/io/reactivesocket/local/ClientServerTest.java b/reactivesocket-local/src/test/java/io/reactivesocket/local/ClientServerTest.java index 90f1251d1..7966b33a4 100644 --- a/reactivesocket-local/src/test/java/io/reactivesocket/local/ClientServerTest.java +++ b/reactivesocket-local/src/test/java/io/reactivesocket/local/ClientServerTest.java @@ -41,7 +41,7 @@ public class ClientServerTest { public static void setup() throws Exception { server = LocalServerReactiveSocketFactory.INSTANCE.callAndWait(new LocalServerReactiveSocketFactory.Config("test", new ConnectionSetupHandler() { @Override - public RequestHandler apply(ConnectionSetupPayload setupPayload) throws SetupException { + public RequestHandler apply(ConnectionSetupPayload setupPayload, ReactiveSocket rs) throws SetupException { return new RequestHandler() { @Override public Publisher handleRequestResponse(Payload payload) { From 19dd2a6a112dd3e988ca0065b7e5147ee6db12c0 Mon Sep 17 00:00:00 2001 From: Ryland Degnan Date: Tue, 3 May 2016 00:23:32 -0700 Subject: [PATCH 092/105] Added EchoServer --- build.gradle | 2 +- reactivesocket-netty/build.gradle | 11 ++- .../io/reactivesocket/netty/EchoServer.java | 38 +++++++++++ .../netty/EchoServerHandler.java | 67 +++++++++++++++++++ .../netty/HttpServerHandler.java | 24 +++++++ 5 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 reactivesocket-netty/src/examples/java/io/reactivesocket/netty/EchoServer.java create mode 100644 reactivesocket-netty/src/examples/java/io/reactivesocket/netty/EchoServerHandler.java create mode 100644 reactivesocket-netty/src/examples/java/io/reactivesocket/netty/HttpServerHandler.java diff --git a/build.gradle b/build.gradle index 28a2bdcfc..a2ff9e459 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { repositories { jcenter() } - dependencies { classpath 'io.reactivesocket:gradle-nebula-plugin-reactivesocket:1.0.5' } + dependencies { classpath 'io.reactivesocket:gradle-nebula-plugin-reactivesocket:1.0.6' } } apply plugin: 'reactivesocket-project' diff --git a/reactivesocket-netty/build.gradle b/reactivesocket-netty/build.gradle index ff40ec133..2ad8e600e 100644 --- a/reactivesocket-netty/build.gradle +++ b/reactivesocket-netty/build.gradle @@ -1,4 +1,9 @@ dependencies { - compile 'io.netty:netty-handler:4.1.0.CR6' - compile 'io.netty:netty-codec-http:4.1.0.CR6' -} \ No newline at end of file + compile 'io.netty:netty-handler:4.1.0.CR7' + compile 'io.netty:netty-codec-http:4.1.0.CR7' +} + +task echoServer(type: JavaExec) { + classpath = sourceSets.examples.runtimeClasspath + main = 'io.reactivesocket.netty.EchoServer' +} diff --git a/reactivesocket-netty/src/examples/java/io/reactivesocket/netty/EchoServer.java b/reactivesocket-netty/src/examples/java/io/reactivesocket/netty/EchoServer.java new file mode 100644 index 000000000..78eebb6ac --- /dev/null +++ b/reactivesocket-netty/src/examples/java/io/reactivesocket/netty/EchoServer.java @@ -0,0 +1,38 @@ +package io.reactivesocket.netty; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; + +public class EchoServer { + public static void main(String... args) throws Exception { + EventLoopGroup bossGroup = new NioEventLoopGroup(1); + EventLoopGroup workerGroup = new NioEventLoopGroup(); + + try { + ServerBootstrap b = new ServerBootstrap(); + b.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .handler(new LoggingHandler(LogLevel.INFO)) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ChannelPipeline pipeline = ch.pipeline(); + pipeline.addLast(new EchoServerHandler()); + } + }); + + Channel localhost = b.bind("localhost", 8025).sync().channel(); + localhost.closeFuture().sync(); + } finally { + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + } +} \ No newline at end of file diff --git a/reactivesocket-netty/src/examples/java/io/reactivesocket/netty/EchoServerHandler.java b/reactivesocket-netty/src/examples/java/io/reactivesocket/netty/EchoServerHandler.java new file mode 100644 index 000000000..79888faeb --- /dev/null +++ b/reactivesocket-netty/src/examples/java/io/reactivesocket/netty/EchoServerHandler.java @@ -0,0 +1,67 @@ +package io.reactivesocket.netty; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.ByteToMessageDecoder; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpServerCodec; +import io.reactivesocket.RequestHandler; +import io.reactivesocket.netty.tcp.server.ReactiveSocketServerHandler; + +import java.util.List; + +public class EchoServerHandler extends ByteToMessageDecoder { + private static SimpleChannelInboundHandler httpHandler = new HttpServerHandler(); + + private static ReactiveSocketServerHandler reactiveSocketHandler = ReactiveSocketServerHandler.create((setupPayload, rs) -> + new RequestHandler.Builder().withRequestResponse(payload -> s -> { + s.onNext(payload); + s.onComplete(); + }).build()); + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + // Will use the first five bytes to detect a protocol. + if (in.readableBytes() < 5) { + return; + } + + final int magic1 = in.getUnsignedByte(in.readerIndex()); + final int magic2 = in.getUnsignedByte(in.readerIndex() + 1); + if (isHttp(magic1, magic2)) { + switchToHttp(ctx); + } else { + switchToReactiveSocket(ctx); + } + } + + private static boolean isHttp(int magic1, int magic2) { + return + magic1 == 'G' && magic2 == 'E' || // GET + magic1 == 'P' && magic2 == 'O' || // POST + magic1 == 'P' && magic2 == 'U' || // PUT + magic1 == 'H' && magic2 == 'E' || // HEAD + magic1 == 'O' && magic2 == 'P' || // OPTIONS + magic1 == 'P' && magic2 == 'A' || // PATCH + magic1 == 'D' && magic2 == 'E' || // DELETE + magic1 == 'T' && magic2 == 'R' || // TRACE + magic1 == 'C' && magic2 == 'O'; // CONNECT + } + + private void switchToHttp(ChannelHandlerContext ctx) { + ChannelPipeline p = ctx.pipeline(); + p.addLast(new HttpServerCodec()); + p.addLast(new HttpObjectAggregator(64 * 1024)); + p.addLast(httpHandler); + p.remove(this); + } + + private void switchToReactiveSocket(ChannelHandlerContext ctx) { + ChannelPipeline p = ctx.pipeline(); + p.addLast(reactiveSocketHandler); + p.remove(this); + } +} diff --git a/reactivesocket-netty/src/examples/java/io/reactivesocket/netty/HttpServerHandler.java b/reactivesocket-netty/src/examples/java/io/reactivesocket/netty/HttpServerHandler.java new file mode 100644 index 000000000..d1106ac67 --- /dev/null +++ b/reactivesocket-netty/src/examples/java/io/reactivesocket/netty/HttpServerHandler.java @@ -0,0 +1,24 @@ +package io.reactivesocket.netty; + +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.FullHttpRequest; + +import static io.netty.handler.codec.http.HttpResponseStatus.OK; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; + +@ChannelHandler.Sharable +public class HttpServerHandler extends SimpleChannelInboundHandler { + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + ctx.flush(); + ctx.close(); + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception { + ctx.write(new DefaultFullHttpResponse(HTTP_1_1, OK, msg.content().copy())); + } +} From dad30199993b3cbf6e0572a5195fb334cf25d8fc Mon Sep 17 00:00:00 2001 From: Ryland Degnan Date: Tue, 3 May 2016 11:13:34 -0700 Subject: [PATCH 093/105] Handle keep alive in echo server --- .../io/reactivesocket/netty/EchoServer.java | 2 +- .../netty/EchoServerHandler.java | 2 +- .../netty/HttpServerHandler.java | 24 +++++++++---------- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/reactivesocket-netty/src/examples/java/io/reactivesocket/netty/EchoServer.java b/reactivesocket-netty/src/examples/java/io/reactivesocket/netty/EchoServer.java index 78eebb6ac..a15d96854 100644 --- a/reactivesocket-netty/src/examples/java/io/reactivesocket/netty/EchoServer.java +++ b/reactivesocket-netty/src/examples/java/io/reactivesocket/netty/EchoServer.java @@ -28,7 +28,7 @@ protected void initChannel(Channel ch) throws Exception { } }); - Channel localhost = b.bind("localhost", 8025).sync().channel(); + Channel localhost = b.bind("0.0.0.0", 8025).sync().channel(); localhost.closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); diff --git a/reactivesocket-netty/src/examples/java/io/reactivesocket/netty/EchoServerHandler.java b/reactivesocket-netty/src/examples/java/io/reactivesocket/netty/EchoServerHandler.java index 79888faeb..dd1939b80 100644 --- a/reactivesocket-netty/src/examples/java/io/reactivesocket/netty/EchoServerHandler.java +++ b/reactivesocket-netty/src/examples/java/io/reactivesocket/netty/EchoServerHandler.java @@ -54,7 +54,7 @@ private static boolean isHttp(int magic1, int magic2) { private void switchToHttp(ChannelHandlerContext ctx) { ChannelPipeline p = ctx.pipeline(); p.addLast(new HttpServerCodec()); - p.addLast(new HttpObjectAggregator(64 * 1024)); + p.addLast(new HttpObjectAggregator(256 * 1024)); p.addLast(httpHandler); p.remove(this); } diff --git a/reactivesocket-netty/src/examples/java/io/reactivesocket/netty/HttpServerHandler.java b/reactivesocket-netty/src/examples/java/io/reactivesocket/netty/HttpServerHandler.java index d1106ac67..248fbd682 100644 --- a/reactivesocket-netty/src/examples/java/io/reactivesocket/netty/HttpServerHandler.java +++ b/reactivesocket-netty/src/examples/java/io/reactivesocket/netty/HttpServerHandler.java @@ -1,24 +1,22 @@ package io.reactivesocket.netty; +import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; -import io.netty.handler.codec.http.DefaultFullHttpResponse; -import io.netty.handler.codec.http.FullHttpRequest; - -import static io.netty.handler.codec.http.HttpResponseStatus.OK; -import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import io.netty.handler.codec.http.*; @ChannelHandler.Sharable public class HttpServerHandler extends SimpleChannelInboundHandler { @Override - public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { - ctx.flush(); - ctx.close(); - } - - @Override - protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception { - ctx.write(new DefaultFullHttpResponse(HTTP_1_1, OK, msg.content().copy())); + protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception { + FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, request.content().retain()); + HttpUtil.setContentLength(response, response.content().readableBytes()); + if (HttpUtil.isKeepAlive(request)) { + response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); + ctx.writeAndFlush(response); + } else { + ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); + } } } From 3f9a384bc524fb6f47b93ae50e84361c713a6d10 Mon Sep 17 00:00:00 2001 From: Robert Roeser Date: Wed, 11 May 2016 11:18:57 -0700 Subject: [PATCH 094/105] embedded Aeron MediaDriver in the client (#6) * aeron client can run embededded media driver so you don't have to run the external driver * added toString methods for duplex connections for debugging and access log purposes --- .../client/AeronClientDuplexConnection.java | 10 +++++++ .../aeron/client/ClientAeronManager.java | 29 +++++++++++++++++-- .../aeron/internal/Constants.java | 1 + .../server/AeronServerDuplexConnection.java | 12 ++++++++ .../websocket/WebSocketDuplexConnection.java | 9 ++++++ .../tcp/client/ClientTcpDuplexConnection.java | 16 ++++++++++ .../tcp/server/ServerTcpDuplexConnection.java | 18 ++++++++++++ .../ClientWebSocketDuplexConnection.java | 16 ++++++++++ .../ServerWebSocketDuplexConnection.java | 18 ++++++++++++ 9 files changed, 126 insertions(+), 3 deletions(-) diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java index b57007342..27736b6b6 100644 --- a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java @@ -126,5 +126,15 @@ public CopyOnWriteArrayList> getSubjects() { return subjects; } + public String toString() { + if (publication == null) { + return getClass().getName() + ":publication=null"; + } + + return getClass().getName() + ":publication=[" + + "channel=" + publication.channel() + "," + + "streamId=" + publication.streamId() + "," + + "sessionId=" + publication.sessionId() + "]"; + } } diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java index f98881af9..ab4d63f92 100644 --- a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java @@ -15,14 +15,19 @@ */ package io.reactivesocket.aeron.client; -import io.reactivesocket.aeron.internal.Loggable; -import rx.Scheduler; -import rx.schedulers.Schedulers; import io.aeron.Aeron; import io.aeron.FragmentAssembler; import io.aeron.Image; import io.aeron.Subscription; +import io.aeron.driver.MediaDriver; +import io.aeron.driver.ThreadingMode; import io.aeron.logbuffer.FragmentHandler; +import io.reactivesocket.aeron.internal.Constants; +import io.reactivesocket.aeron.internal.Loggable; +import org.agrona.concurrent.BackoffIdleStrategy; +import org.agrona.concurrent.SleepingIdleStrategy; +import rx.Scheduler; +import rx.schedulers.Schedulers; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; @@ -33,6 +38,24 @@ public class ClientAeronManager implements Loggable { private static final ClientAeronManager INSTANCE = new ClientAeronManager(); + /** + * Enables running the client with an embedded Aeron {@link MediaDriver} so you don't have to run + * the driver in a separate process. To enable this option you need to set the reactivesocket.aeron.clientEmbeddedDriver + * to true + */ + static { + if (Constants.CLIENT_EMBEDDED_AERON_DRIVER) { + System.out.println("+++ Launching embedded media driver"); + final MediaDriver.Context context = new MediaDriver.Context(); + context.dirsDeleteOnStart(true); + context.threadingMode(ThreadingMode.SHARED_NETWORK); + context.conductorIdleStrategy(new SleepingIdleStrategy(TimeUnit.MILLISECONDS.toNanos(10))); + context.senderIdleStrategy(new BackoffIdleStrategy(5, 10, 100, 1000)); + context.receiverIdleStrategy(new BackoffIdleStrategy(5, 10, 100, 1000)); + MediaDriver.launch(context); + } + } + private final CopyOnWriteArrayList clientActions; private final CopyOnWriteArrayList subscriptionGroups; diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/Constants.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/Constants.java index dd28369b8..4ba15f277 100644 --- a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/Constants.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/internal/Constants.java @@ -39,6 +39,7 @@ public final class Constants { public static final int SERVER_TIMER_WHEEL_TICK_DURATION_MS = 10; public static final int SERVER_TIMER_WHEEL_BUCKETS = 128; public static final int DEFAULT_OFFER_TO_AERON_TIMEOUT_MS = 30_000; + public static final boolean CLIENT_EMBEDDED_AERON_DRIVER = Boolean.getBoolean("reactivesocket.aeron.clientEmbeddedDriver"); static { String idlStrategy = System.getProperty("idleStrategy"); diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java index 0418a21c7..0940c89d2 100644 --- a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java @@ -107,4 +107,16 @@ public void close() { publication.close(); } catch (Throwable t) {} } + + public String toString() { + if (publication == null) { + return getClass().getName() + ":publication=null"; + } + + return getClass().getName() + ":publication=[" + + "channel=" + publication.channel() + "," + + "streamId=" + publication.streamId() + "," + + "sessionId=" + publication.sessionId() + "]"; + + } } diff --git a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/WebSocketDuplexConnection.java b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/WebSocketDuplexConnection.java index c57028db3..bd14568df 100644 --- a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/WebSocketDuplexConnection.java +++ b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/WebSocketDuplexConnection.java @@ -83,4 +83,13 @@ public void onSubscribe(Subscription s) { public void close() throws IOException { session.close(); } + + public String toString() { + if (session == null) { + return getClass().getName() + ":session=null"; + } + + return getClass().getName() + ":session=[" + session.toString() + "]"; + + } } diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java index 10f285da9..facc2fb9b 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java @@ -151,4 +151,20 @@ public void onComplete() { public void close() throws IOException { channel.close(); } + + public String toString() { + if (channel == null) { + return getClass().getName() + ":channel=null"; + } + + return getClass().getName() + ":channel=[" + + "remoteAddress=" + channel.remoteAddress() + "," + + "isActive=" + channel.isActive() + "," + + "isOpen=" + channel.isOpen() + "," + + "isRegistered=" + channel.isRegistered() + "," + + "isWritable=" + channel.isWritable() + "," + + "channelId=" + channel.id().asLongText() + + "]"; + + } } diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/server/ServerTcpDuplexConnection.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/server/ServerTcpDuplexConnection.java index c126bc78d..7a99b223f 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/server/ServerTcpDuplexConnection.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/server/ServerTcpDuplexConnection.java @@ -17,6 +17,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.reactivesocket.DuplexConnection; @@ -97,4 +98,21 @@ public void onComplete() { public void close() throws IOException { } + + public String toString() { + if (ctx ==null || ctx.channel() == null) { + return getClass().getName() + ":channel=null"; + } + + Channel channel = ctx.channel(); + return getClass().getName() + ":channel=[" + + "remoteAddress=" + channel.remoteAddress() + "," + + "isActive=" + channel.isActive() + "," + + "isOpen=" + channel.isOpen() + "," + + "isRegistered=" + channel.isRegistered() + "," + + "isWritable=" + channel.isWritable() + "," + + "channelId=" + channel.id().asLongText() + + "]"; + + } } diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java index 05bd5a31f..08851d959 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java @@ -166,4 +166,20 @@ public void onComplete() { public void close() throws IOException { channel.close(); } + + public String toString() { + if (channel == null) { + return getClass().getName() + ":channel=null"; + } + + return getClass().getName() + ":channel=[" + + "remoteAddress=" + channel.remoteAddress() + "," + + "isActive=" + channel.isActive() + "," + + "isOpen=" + channel.isOpen() + "," + + "isRegistered=" + channel.isRegistered() + "," + + "isWritable=" + channel.isWritable() + "," + + "channelId=" + channel.id().asLongText() + + "]"; + + } } diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ServerWebSocketDuplexConnection.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ServerWebSocketDuplexConnection.java index 4929376f2..8e4f8b1d4 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ServerWebSocketDuplexConnection.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ServerWebSocketDuplexConnection.java @@ -17,6 +17,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; @@ -99,4 +100,21 @@ public void onComplete() { public void close() throws IOException { } + + public String toString() { + if (ctx ==null || ctx.channel() == null) { + return getClass().getName() + ":channel=null"; + } + + Channel channel = ctx.channel(); + return getClass().getName() + ":channel=[" + + "remoteAddress=" + channel.remoteAddress() + "," + + "isActive=" + channel.isActive() + "," + + "isOpen=" + channel.isOpen() + "," + + "isRegistered=" + channel.isRegistered() + "," + + "isWritable=" + channel.isWritable() + "," + + "channelId=" + channel.id().asLongText() + + "]"; + + } } From 40b1452a89c8fa762c9c957f4843dc99caf92d65 Mon Sep 17 00:00:00 2001 From: Andrey Radchenko Date: Wed, 11 May 2016 13:26:27 -0700 Subject: [PATCH 095/105] changed constructor access level to protected to enable subclassing --- gradle/wrapper/gradle-wrapper.properties | 2 +- .../netty/tcp/server/ReactiveSocketServerHandler.java | 2 +- .../netty/websocket/server/ReactiveSocketServerHandler.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6a8e75478..509a17552 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -#Fri Mar 18 15:29:39 PDT 2016 +#Wed May 11 13:20:01 PDT 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/server/ReactiveSocketServerHandler.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/server/ReactiveSocketServerHandler.java index f81e4de6c..7557a7d52 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/server/ReactiveSocketServerHandler.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/server/ReactiveSocketServerHandler.java @@ -44,7 +44,7 @@ public class ReactiveSocketServerHandler extends ChannelInboundHandlerAdapter { private LeaseGovernor leaseGovernor; - private ReactiveSocketServerHandler(ConnectionSetupHandler setupHandler, LeaseGovernor leaseGovernor) { + protected ReactiveSocketServerHandler(ConnectionSetupHandler setupHandler, LeaseGovernor leaseGovernor) { this.setupHandler = setupHandler; this.leaseGovernor = leaseGovernor; } diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ReactiveSocketServerHandler.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ReactiveSocketServerHandler.java index 5d8153a45..8e77d56e2 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ReactiveSocketServerHandler.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ReactiveSocketServerHandler.java @@ -42,7 +42,7 @@ public class ReactiveSocketServerHandler extends SimpleChannelInboundHandler Date: Wed, 11 May 2016 13:48:34 -0700 Subject: [PATCH 096/105] use StandardCharsets --- .../src/test/java/io/reactivesocket/aeron/TestUtil.java | 6 +++--- .../java/io/reactivesocket/javax/websocket/TestUtil.java | 6 +++--- .../src/test/java/io/reactivesocket/local/TestUtil.java | 6 +++--- .../io/reactivesocket/netty/MutableDirectByteBuf.java | 8 ++++---- .../src/test/java/io/reactivesocket/netty/TestUtil.java | 6 +++--- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/TestUtil.java b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/TestUtil.java index f653f0957..1978a1675 100644 --- a/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/TestUtil.java +++ b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/TestUtil.java @@ -21,7 +21,7 @@ import org.agrona.MutableDirectBuffer; import java.nio.ByteBuffer; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; public class TestUtil { @@ -60,12 +60,12 @@ public static String byteToString(final ByteBuffer byteBuffer) { final byte[] bytes = new byte[byteBuffer.capacity()]; byteBuffer.get(bytes); - return new String(bytes, Charset.forName("UTF-8")); + return new String(bytes, StandardCharsets.UTF_8); } public static ByteBuffer byteBufferFromUtf8String(final String data) { - final byte[] bytes = data.getBytes(Charset.forName("UTF-8")); + final byte[] bytes = data.getBytes(StandardCharsets.UTF_8); return ByteBuffer.wrap(bytes); } diff --git a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/TestUtil.java b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/TestUtil.java index d4c87889e..3e79cf490 100644 --- a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/TestUtil.java +++ b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/TestUtil.java @@ -21,7 +21,7 @@ import org.agrona.MutableDirectBuffer; import java.nio.ByteBuffer; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; public class TestUtil { @@ -60,12 +60,12 @@ public static String byteToString(final ByteBuffer byteBuffer) { final byte[] bytes = new byte[byteBuffer.remaining()]; byteBuffer.get(bytes); - return new String(bytes, Charset.forName("UTF-8")); + return new String(bytes, StandardCharsets.UTF_8); } public static ByteBuffer byteBufferFromUtf8String(final String data) { - final byte[] bytes = data.getBytes(Charset.forName("UTF-8")); + final byte[] bytes = data.getBytes(StandardCharsets.UTF_8); return ByteBuffer.wrap(bytes); } diff --git a/reactivesocket-local/src/test/java/io/reactivesocket/local/TestUtil.java b/reactivesocket-local/src/test/java/io/reactivesocket/local/TestUtil.java index ddbab746e..a4dadfc07 100644 --- a/reactivesocket-local/src/test/java/io/reactivesocket/local/TestUtil.java +++ b/reactivesocket-local/src/test/java/io/reactivesocket/local/TestUtil.java @@ -21,7 +21,7 @@ import org.agrona.MutableDirectBuffer; import java.nio.ByteBuffer; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; public class TestUtil { @@ -60,12 +60,12 @@ public static String byteToString(final ByteBuffer byteBuffer) { final byte[] bytes = new byte[byteBuffer.remaining()]; byteBuffer.get(bytes); - return new String(bytes, Charset.forName("UTF-8")); + return new String(bytes, StandardCharsets.UTF_8); } public static ByteBuffer byteBufferFromUtf8String(final String data) { - final byte[] bytes = data.getBytes(Charset.forName("UTF-8")); + final byte[] bytes = data.getBytes(StandardCharsets.UTF_8); return ByteBuffer.wrap(bytes); } diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/MutableDirectByteBuf.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/MutableDirectByteBuf.java index 92cd10bea..a72f16b6e 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/MutableDirectByteBuf.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/MutableDirectByteBuf.java @@ -22,7 +22,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; public class MutableDirectByteBuf implements MutableDirectBuffer { @@ -358,19 +358,19 @@ public void getBytes(int index, ByteBuffer dstBuffer, int length) public String getStringUtf8(int offset, ByteOrder byteOrder) { final int length = getInt(offset, byteOrder); - return byteBuf.toString(offset + BitUtil.SIZE_OF_INT, length, Charset.forName("UTF-8")); + return byteBuf.toString(offset + BitUtil.SIZE_OF_INT, length, StandardCharsets.UTF_8); } @Override public String getStringUtf8(int offset, int length) { - return byteBuf.toString(offset, length, Charset.forName("UTF-8")); + return byteBuf.toString(offset, length, StandardCharsets.UTF_8); } @Override public String getStringWithoutLengthUtf8(int offset, int length) { - return byteBuf.toString(offset, length, Charset.forName("UTF-8")); + return byteBuf.toString(offset, length, StandardCharsets.UTF_8); } @Override diff --git a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/TestUtil.java b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/TestUtil.java index d0d0bd2de..bee1203a9 100644 --- a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/TestUtil.java +++ b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/TestUtil.java @@ -22,7 +22,7 @@ import org.agrona.MutableDirectBuffer; import java.nio.ByteBuffer; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; public class TestUtil { @@ -61,12 +61,12 @@ public static String byteToString(final ByteBuffer byteBuffer) { final byte[] bytes = new byte[byteBuffer.remaining()]; byteBuffer.get(bytes); - return new String(bytes, Charset.forName("UTF-8")); + return new String(bytes, StandardCharsets.UTF_8); } public static ByteBuffer byteBufferFromUtf8String(final String data) { - final byte[] bytes = data.getBytes(Charset.forName("UTF-8")); + final byte[] bytes = data.getBytes(StandardCharsets.UTF_8); return ByteBuffer.wrap(bytes); } From 740397a763099da20cccf4d4bfc5ee1cbf9ae79e Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Wed, 11 May 2016 14:29:54 -0700 Subject: [PATCH 097/105] share test utils --- reactivesocket-aeron/build.gradle | 1 + .../io/reactivesocket/aeron/TestUtil.java | 123 -------------- .../aeron/client/ReactiveSocketAeronTest.java | 2 +- reactivesocket-jsr-356/build.gradle | 1 + .../javax/websocket/ClientServerEndpoint.java | 1 + .../javax/websocket/ClientServerTest.java | 1 + .../javax/websocket/PongEndpoint.java | 1 + .../javax/websocket/TestUtil.java | 122 ------------- reactivesocket-local/build.gradle | 3 + .../local/ClientServerTest.java | 1 + reactivesocket-netty/build.gradle | 1 + .../reactivesocket/netty/NettyTestUtil.java | 58 +++++++ .../io/reactivesocket/netty/TestUtil.java | 160 ------------------ .../netty/tcp/ClientServerTest.java | 6 +- .../io/reactivesocket/netty/tcp/Pong.java | 2 +- .../netty/websocket/ClientServerTest.java | 14 +- .../reactivesocket/netty/websocket/Pong.java | 2 +- reactivesocket-test/build.gradle | 0 .../io/reactivesocket/test}/TestUtil.java | 8 +- settings.gradle | 1 + 20 files changed, 90 insertions(+), 418 deletions(-) delete mode 100644 reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/TestUtil.java delete mode 100644 reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/TestUtil.java create mode 100644 reactivesocket-netty/src/test/java/io/reactivesocket/netty/NettyTestUtil.java delete mode 100644 reactivesocket-netty/src/test/java/io/reactivesocket/netty/TestUtil.java create mode 100644 reactivesocket-test/build.gradle rename {reactivesocket-local/src/test/java/io/reactivesocket/local => reactivesocket-test/src/main/java/io/reactivesocket/test}/TestUtil.java (96%) diff --git a/reactivesocket-aeron/build.gradle b/reactivesocket-aeron/build.gradle index 1af7d4015..2c620f4ac 100644 --- a/reactivesocket-aeron/build.gradle +++ b/reactivesocket-aeron/build.gradle @@ -1,3 +1,4 @@ dependencies { compile 'io.aeron:aeron-all:0.9.5' + testCompile project(':reactivesocket-test') } \ No newline at end of file diff --git a/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/TestUtil.java b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/TestUtil.java deleted file mode 100644 index f653f0957..000000000 --- a/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/TestUtil.java +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Copyright 2016 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.aeron; - -import io.reactivesocket.Frame; -import io.reactivesocket.FrameType; -import io.reactivesocket.Payload; -import org.agrona.MutableDirectBuffer; - -import java.nio.ByteBuffer; -import java.nio.charset.Charset; - -public class TestUtil -{ - public static Frame utf8EncodedRequestFrame(final int streamId, final FrameType type, final String data, final int initialRequestN) - { - return Frame.Request.from(streamId, type, new Payload() - { - public ByteBuffer getData() - { - return byteBufferFromUtf8String(data); - } - - public ByteBuffer getMetadata() - { - return Frame.NULL_BYTEBUFFER; - } - }, initialRequestN); - } - - public static Frame utf8EncodedResponseFrame(final int streamId, final FrameType type, final String data) - { - return Frame.Response.from(streamId, type, utf8EncodedPayload(data, null)); - } - - public static Frame utf8EncodedErrorFrame(final int streamId, final String data) - { - return Frame.Error.from(streamId, new Exception(data)); - } - - public static Payload utf8EncodedPayload(final String data, final String metadata) - { - return new PayloadImpl(data, metadata); - } - - public static String byteToString(final ByteBuffer byteBuffer) - { - final byte[] bytes = new byte[byteBuffer.capacity()]; - byteBuffer.get(bytes); - return new String(bytes, Charset.forName("UTF-8")); - } - - public static ByteBuffer byteBufferFromUtf8String(final String data) - { - final byte[] bytes = data.getBytes(Charset.forName("UTF-8")); - return ByteBuffer.wrap(bytes); - } - - public static void copyFrame(final MutableDirectBuffer dst, final int offset, final Frame frame) - { - dst.putBytes(offset, frame.getByteBuffer(), frame.offset(), frame.length()); - } - - private static class PayloadImpl implements Payload // some JDK shoutout - { - private ByteBuffer data; - private ByteBuffer metadata; - - public PayloadImpl(final String data, final String metadata) - { - if (null == data) - { - this.data = ByteBuffer.allocate(0); - } - else - { - this.data = byteBufferFromUtf8String(data); - } - - if (null == metadata) - { - this.metadata = ByteBuffer.allocate(0); - } - else - { - this.metadata = byteBufferFromUtf8String(metadata); - } - } - - public boolean equals(Object obj) - { - System.out.println("equals: " + obj); - final Payload rhs = (Payload)obj; - - return (TestUtil.byteToString(data).equals(TestUtil.byteToString(rhs.getData()))) && - (TestUtil.byteToString(metadata).equals(TestUtil.byteToString(rhs.getMetadata()))); - } - - public ByteBuffer getData() - { - return data; - } - - public ByteBuffer getMetadata() - { - return metadata; - } - } - -} \ No newline at end of file diff --git a/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java index 3d01d46b1..f87f2eca5 100644 --- a/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java +++ b/reactivesocket-aeron/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java @@ -23,7 +23,7 @@ import io.reactivesocket.Payload; import io.reactivesocket.ReactiveSocket; import io.reactivesocket.RequestHandler; -import io.reactivesocket.aeron.TestUtil; +import io.reactivesocket.test.TestUtil; import io.reactivesocket.aeron.server.ReactiveSocketAeronServer; import io.reactivesocket.exceptions.SetupException; import org.junit.Assert; diff --git a/reactivesocket-jsr-356/build.gradle b/reactivesocket-jsr-356/build.gradle index 99e05d82c..0e5e1d7f3 100644 --- a/reactivesocket-jsr-356/build.gradle +++ b/reactivesocket-jsr-356/build.gradle @@ -2,4 +2,5 @@ dependencies { compile 'org.glassfish.tyrus:tyrus-client:1.12' testCompile 'org.glassfish.tyrus:tyrus-server:1.12' testCompile 'org.glassfish.tyrus:tyrus-container-grizzly-server:1.12' + testCompile project(':reactivesocket-test') } \ No newline at end of file diff --git a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerEndpoint.java b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerEndpoint.java index 4b4c948aa..669e21d3f 100644 --- a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerEndpoint.java +++ b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerEndpoint.java @@ -18,6 +18,7 @@ import io.reactivesocket.Payload; import io.reactivesocket.RequestHandler; import io.reactivesocket.javax.websocket.server.ReactiveSocketWebSocketServer; +import io.reactivesocket.test.TestUtil; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import rx.Observable; diff --git a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerTest.java b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerTest.java index c783eb2c2..bab516104 100644 --- a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerTest.java +++ b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerTest.java @@ -20,6 +20,7 @@ import io.reactivesocket.Payload; import io.reactivesocket.ReactiveSocket; import io.reactivesocket.javax.websocket.client.ReactiveSocketWebSocketClient; +import io.reactivesocket.test.TestUtil; import org.glassfish.tyrus.client.ClientManager; import org.glassfish.tyrus.server.Server; import org.junit.AfterClass; diff --git a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/PongEndpoint.java b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/PongEndpoint.java index bac0ae100..127df6b8c 100644 --- a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/PongEndpoint.java +++ b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/PongEndpoint.java @@ -18,6 +18,7 @@ import io.reactivesocket.Payload; import io.reactivesocket.RequestHandler; import io.reactivesocket.javax.websocket.server.ReactiveSocketWebSocketServer; +import io.reactivesocket.test.TestUtil; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import rx.Observable; diff --git a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/TestUtil.java b/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/TestUtil.java deleted file mode 100644 index d4c87889e..000000000 --- a/reactivesocket-jsr-356/src/test/java/io/reactivesocket/javax/websocket/TestUtil.java +++ /dev/null @@ -1,122 +0,0 @@ -/** - * Copyright 2015 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.javax.websocket; - -import io.reactivesocket.Frame; -import io.reactivesocket.FrameType; -import io.reactivesocket.Payload; -import org.agrona.MutableDirectBuffer; - -import java.nio.ByteBuffer; -import java.nio.charset.Charset; - -public class TestUtil -{ - public static Frame utf8EncodedRequestFrame(final int streamId, final FrameType type, final String data, final int initialRequestN) - { - return Frame.Request.from(streamId, type, new Payload() - { - public ByteBuffer getData() - { - return byteBufferFromUtf8String(data); - } - - public ByteBuffer getMetadata() - { - return Frame.NULL_BYTEBUFFER; - } - }, initialRequestN); - } - - public static Frame utf8EncodedResponseFrame(final int streamId, final FrameType type, final String data) - { - return Frame.Response.from(streamId, type, utf8EncodedPayload(data, null)); - } - - public static Frame utf8EncodedErrorFrame(final int streamId, final String data) - { - return Frame.Error.from(streamId, new Exception(data)); - } - - public static Payload utf8EncodedPayload(final String data, final String metadata) - { - return new PayloadImpl(data, metadata); - } - - public static String byteToString(final ByteBuffer byteBuffer) - { - final byte[] bytes = new byte[byteBuffer.remaining()]; - byteBuffer.get(bytes); - return new String(bytes, Charset.forName("UTF-8")); - } - - public static ByteBuffer byteBufferFromUtf8String(final String data) - { - final byte[] bytes = data.getBytes(Charset.forName("UTF-8")); - return ByteBuffer.wrap(bytes); - } - - public static void copyFrame(final MutableDirectBuffer dst, final int offset, final Frame frame) - { - dst.putBytes(offset, frame.getByteBuffer(), frame.offset(), frame.length()); - } - - private static class PayloadImpl implements Payload // some JDK shoutout - { - private ByteBuffer data; - private ByteBuffer metadata; - - public PayloadImpl(final String data, final String metadata) - { - if (null == data) - { - this.data = ByteBuffer.allocate(0); - } - else - { - this.data = byteBufferFromUtf8String(data); - } - - if (null == metadata) - { - this.metadata = ByteBuffer.allocate(0); - } - else - { - this.metadata = byteBufferFromUtf8String(metadata); - } - } - - public boolean equals(Object obj) - { - System.out.println("equals: " + obj); - final Payload rhs = (Payload)obj; - - return (TestUtil.byteToString(data).equals(TestUtil.byteToString(rhs.getData()))) && - (TestUtil.byteToString(metadata).equals(TestUtil.byteToString(rhs.getMetadata()))); - } - - public ByteBuffer getData() - { - return data; - } - - public ByteBuffer getMetadata() - { - return metadata; - } - } -} diff --git a/reactivesocket-local/build.gradle b/reactivesocket-local/build.gradle index e69de29bb..6a3482730 100644 --- a/reactivesocket-local/build.gradle +++ b/reactivesocket-local/build.gradle @@ -0,0 +1,3 @@ +dependencies { + testCompile project(':reactivesocket-test') +} diff --git a/reactivesocket-local/src/test/java/io/reactivesocket/local/ClientServerTest.java b/reactivesocket-local/src/test/java/io/reactivesocket/local/ClientServerTest.java index 7966b33a4..30ca11a28 100644 --- a/reactivesocket-local/src/test/java/io/reactivesocket/local/ClientServerTest.java +++ b/reactivesocket-local/src/test/java/io/reactivesocket/local/ClientServerTest.java @@ -21,6 +21,7 @@ import io.reactivesocket.ReactiveSocket; import io.reactivesocket.RequestHandler; import io.reactivesocket.exceptions.SetupException; +import io.reactivesocket.test.TestUtil; import org.junit.BeforeClass; import org.junit.Test; import org.reactivestreams.Publisher; diff --git a/reactivesocket-netty/build.gradle b/reactivesocket-netty/build.gradle index 2ad8e600e..1d3683434 100644 --- a/reactivesocket-netty/build.gradle +++ b/reactivesocket-netty/build.gradle @@ -1,6 +1,7 @@ dependencies { compile 'io.netty:netty-handler:4.1.0.CR7' compile 'io.netty:netty-codec-http:4.1.0.CR7' + testCompile project(':reactivesocket-test') } task echoServer(type: JavaExec) { diff --git a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/NettyTestUtil.java b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/NettyTestUtil.java new file mode 100644 index 000000000..b7a44421e --- /dev/null +++ b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/NettyTestUtil.java @@ -0,0 +1,58 @@ +/** + * Copyright 2015 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.netty; + +import io.netty.buffer.ByteBuf; + +public class NettyTestUtil +{ + public static String byteBufToString(ByteBuf buf) { + byte[] bytes = new byte[buf.readableBytes()]; + int readerIndex = buf.readerIndex(); + buf.getBytes(readerIndex, bytes); + buf.readerIndex(readerIndex); + + StringBuilder result = new StringBuilder(); + StringBuilder ascii = new StringBuilder(); + int i = 0; + for (i=0; i ts = TestSubscriber.create(); RxReactiveStreams - .toObservable(client.requestSubscription(TestUtil.utf8EncodedPayload("hello sub", "metadata sub"))) + .toObservable(client.requestSubscription( + TestUtil.utf8EncodedPayload("hello sub", "metadata sub"))) .take(10) .subscribe(ts); diff --git a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/Pong.java b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/Pong.java index 2dd251a50..76cc1bac2 100644 --- a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/Pong.java +++ b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/tcp/Pong.java @@ -26,8 +26,8 @@ import io.netty.handler.logging.LoggingHandler; import io.reactivesocket.Payload; import io.reactivesocket.RequestHandler; -import io.reactivesocket.netty.TestUtil; import io.reactivesocket.netty.tcp.server.ReactiveSocketServerHandler; +import io.reactivesocket.test.TestUtil; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import rx.Observable; diff --git a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java index bdc4c101a..6b4deee30 100644 --- a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java +++ b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java @@ -27,10 +27,14 @@ import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; -import io.reactivesocket.*; -import io.reactivesocket.netty.TestUtil; +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.DefaultReactiveSocket; +import io.reactivesocket.Payload; +import io.reactivesocket.ReactiveSocket; +import io.reactivesocket.RequestHandler; import io.reactivesocket.netty.websocket.client.ClientWebSocketDuplexConnection; import io.reactivesocket.netty.websocket.server.ReactiveSocketServerHandler; +import io.reactivesocket.test.TestUtil; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -179,7 +183,8 @@ public void testRequestSubscription() throws InterruptedException { TestSubscriber ts = TestSubscriber.create(); RxReactiveStreams - .toObservable(client.requestSubscription(TestUtil.utf8EncodedPayload("hello sub", "metadata sub"))) + .toObservable(client.requestSubscription( + TestUtil.utf8EncodedPayload("hello sub", "metadata sub"))) .take(10) .subscribe(ts); @@ -197,7 +202,8 @@ public void requestResponseN(int timeout, int count) { .range(1, count) .flatMap(i -> RxReactiveStreams - .toObservable(client.requestResponse(TestUtil.utf8EncodedPayload("hello", "metadata"))) + .toObservable(client.requestResponse( + TestUtil.utf8EncodedPayload("hello", "metadata"))) .map(payload -> TestUtil.byteToString(payload.getData())) ) .doOnError(Throwable::printStackTrace) diff --git a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Pong.java b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Pong.java index 0b6d80391..bb9c7ab3f 100644 --- a/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Pong.java +++ b/reactivesocket-netty/src/test/java/io/reactivesocket/netty/websocket/Pong.java @@ -29,8 +29,8 @@ import io.netty.handler.logging.LoggingHandler; import io.reactivesocket.Payload; import io.reactivesocket.RequestHandler; -import io.reactivesocket.netty.TestUtil; import io.reactivesocket.netty.websocket.server.ReactiveSocketServerHandler; +import io.reactivesocket.test.TestUtil; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import rx.Observable; diff --git a/reactivesocket-test/build.gradle b/reactivesocket-test/build.gradle new file mode 100644 index 000000000..e69de29bb diff --git a/reactivesocket-local/src/test/java/io/reactivesocket/local/TestUtil.java b/reactivesocket-test/src/main/java/io/reactivesocket/test/TestUtil.java similarity index 96% rename from reactivesocket-local/src/test/java/io/reactivesocket/local/TestUtil.java rename to reactivesocket-test/src/main/java/io/reactivesocket/test/TestUtil.java index ddbab746e..ecd14daff 100644 --- a/reactivesocket-local/src/test/java/io/reactivesocket/local/TestUtil.java +++ b/reactivesocket-test/src/main/java/io/reactivesocket/test/TestUtil.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.reactivesocket.local; +package io.reactivesocket.test; import io.reactivesocket.Frame; import io.reactivesocket.FrameType; @@ -56,8 +56,10 @@ public static Payload utf8EncodedPayload(final String data, final String metadat return new PayloadImpl(data, metadata); } - public static String byteToString(final ByteBuffer byteBuffer) + public static String byteToString(ByteBuffer byteBuffer) { + byteBuffer = byteBuffer.duplicate(); + final byte[] bytes = new byte[byteBuffer.remaining()]; byteBuffer.get(bytes); return new String(bytes, Charset.forName("UTF-8")); @@ -119,6 +121,4 @@ public ByteBuffer getMetadata() return metadata; } } - - } diff --git a/settings.gradle b/settings.gradle index eae8b7692..d21c281c2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,5 @@ rootProject.name='reactivesocket-java-impl' include 'reactivesocket-aeron', 'reactivesocket-jsr-356', 'reactivesocket-netty' include 'reactivesocket-local' +include 'reactivesocket-test' From df1d295f77664860c6d2a10ddf0afb823c4d9de1 Mon Sep 17 00:00:00 2001 From: Ryland Degnan Date: Fri, 13 May 2016 16:31:39 -0700 Subject: [PATCH 098/105] Verify precondition in TcpReactiveSocketFactory --- .../tcp/client/ClientTcpDuplexConnection.java | 12 ---- .../tcp/client/TcpReactiveSocketFactory.java | 71 ++++++++++--------- .../ClientWebSocketDuplexConnection.java | 15 ++-- .../WebSocketReactiveSocketFactory.java | 71 ++++++++++--------- 4 files changed, 81 insertions(+), 88 deletions(-) diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java index facc2fb9b..657fc2f89 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java @@ -56,18 +56,6 @@ private ClientTcpDuplexConnection(Channel channel, Bootstrap bootstrap, CopyOnWr this.bootstrap = bootstrap; } - public static Publisher create(SocketAddress socketAddress, EventLoopGroup eventLoopGroup) { - if (socketAddress instanceof InetSocketAddress) { - try { - return create(socketAddress, eventLoopGroup); - } catch (Exception e) { - throw new IllegalArgumentException(e.getMessage(), e); - } - } else { - throw new IllegalArgumentException("unknown socket address type => " + socketAddress.getClass()); - } - } - public static Publisher create(InetSocketAddress address, EventLoopGroup eventLoopGroup) { return s -> { CopyOnWriteArrayList> subjects = new CopyOnWriteArrayList<>(); diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketFactory.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketFactory.java index 6eea5fa49..67aebb07a 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketFactory.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketFactory.java @@ -29,6 +29,7 @@ import rx.Observable; import rx.RxReactiveStreams; +import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.function.Consumer; @@ -50,44 +51,48 @@ public TcpReactiveSocketFactory(EventLoopGroup eventLoopGroup, ConnectionSetupPa @Override public Publisher call(SocketAddress address) { - Publisher connection - = ClientTcpDuplexConnection.create(address, eventLoopGroup); + if (address instanceof InetSocketAddress) { + Publisher connection + = ClientTcpDuplexConnection.create((InetSocketAddress)address, eventLoopGroup); - Observable result = Observable.create(s -> - connection.subscribe(new Subscriber() { - @Override - public void onSubscribe(Subscription s) { - s.request(1); - } + Observable result = Observable.create(s -> + connection.subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(1); + } - @Override - public void onNext(ClientTcpDuplexConnection connection) { - ReactiveSocket reactiveSocket = DefaultReactiveSocket.fromClientConnection(connection, connectionSetupPayload, errorStream); - reactiveSocket.start(new Completable() { - @Override - public void success() { - s.onNext(reactiveSocket); - s.onCompleted(); - } + @Override + public void onNext(ClientTcpDuplexConnection connection) { + ReactiveSocket reactiveSocket = DefaultReactiveSocket.fromClientConnection(connection, connectionSetupPayload, errorStream); + reactiveSocket.start(new Completable() { + @Override + public void success() { + s.onNext(reactiveSocket); + s.onCompleted(); + } - @Override - public void error(Throwable e) { - s.onError(e); - } - }); - } + @Override + public void error(Throwable e) { + s.onError(e); + } + }); + } - @Override - public void onError(Throwable t) { - s.onError(t); - } + @Override + public void onError(Throwable t) { + s.onError(t); + } - @Override - public void onComplete() { - } - }) - ); + @Override + public void onComplete() { + } + }) + ); - return RxReactiveStreams.toPublisher(result); + return RxReactiveStreams.toPublisher(result); + } else { + throw new IllegalArgumentException("unknown socket address type => " + address.getClass()); + } } } diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java index 08851d959..1eef1fa28 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java @@ -56,16 +56,11 @@ private ClientWebSocketDuplexConnection(Channel channel, Bootstrap bootstrap, C this.bootstrap = bootstrap; } - public static Publisher create(SocketAddress socketAddress, String path, EventLoopGroup eventLoopGroup) { - if (socketAddress instanceof InetSocketAddress) { - InetSocketAddress address = (InetSocketAddress)socketAddress; - try { - return create(new URI("ws", null, address.getHostName(), address.getPort(), path, null, null), eventLoopGroup); - } catch (URISyntaxException e) { - throw new IllegalArgumentException(e.getMessage(), e); - } - } else { - throw new IllegalArgumentException("unknown socket address type => " + socketAddress.getClass()); + public static Publisher create(InetSocketAddress address, String path, EventLoopGroup eventLoopGroup) { + try { + return create(new URI("ws", null, address.getHostName(), address.getPort(), path, null, null), eventLoopGroup); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e.getMessage(), e); } } diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketFactory.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketFactory.java index fe54dd80b..f8c73bc4d 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketFactory.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketFactory.java @@ -29,6 +29,7 @@ import rx.Observable; import rx.RxReactiveStreams; +import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.function.Consumer; @@ -52,44 +53,48 @@ public WebSocketReactiveSocketFactory(String path, EventLoopGroup eventLoopGroup @Override public Publisher call(SocketAddress address) { - Publisher connection - = ClientWebSocketDuplexConnection.create(address, path, eventLoopGroup); + if (address instanceof InetSocketAddress) { + Publisher connection + = ClientWebSocketDuplexConnection.create((InetSocketAddress)address, path, eventLoopGroup); - Observable result = Observable.create(s -> - connection.subscribe(new Subscriber() { - @Override - public void onSubscribe(Subscription s) { - s.request(1); - } + Observable result = Observable.create(s -> + connection.subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(1); + } - @Override - public void onNext(ClientWebSocketDuplexConnection connection) { - ReactiveSocket reactiveSocket = DefaultReactiveSocket.fromClientConnection(connection, connectionSetupPayload, errorStream); - reactiveSocket.start(new Completable() { - @Override - public void success() { - s.onNext(reactiveSocket); - s.onCompleted(); - } + @Override + public void onNext(ClientWebSocketDuplexConnection connection) { + ReactiveSocket reactiveSocket = DefaultReactiveSocket.fromClientConnection(connection, connectionSetupPayload, errorStream); + reactiveSocket.start(new Completable() { + @Override + public void success() { + s.onNext(reactiveSocket); + s.onCompleted(); + } - @Override - public void error(Throwable e) { - s.onError(e); - } - }); - } + @Override + public void error(Throwable e) { + s.onError(e); + } + }); + } - @Override - public void onError(Throwable t) { - s.onError(t); - } + @Override + public void onError(Throwable t) { + s.onError(t); + } - @Override - public void onComplete() { - } - }) - ); + @Override + public void onComplete() { + } + }) + ); - return RxReactiveStreams.toPublisher(result); + return RxReactiveStreams.toPublisher(result); + } else { + throw new IllegalArgumentException("unknown socket address type => " + address.getClass()); + } } } From 98dafba0dafc27d04924bbc99e107b80e4dcac04 Mon Sep 17 00:00:00 2001 From: Nitesh Kant Date: Mon, 16 May 2016 15:20:58 -0700 Subject: [PATCH 099/105] Adding reactivesocket-mime-types module. (#11) This module provides support for encoding/decoding ReactiveSocket data and metadata into using different mime types as defined by [ReactiveSocket protocol](https://github.com/ReactiveSocket/reactivesocket/blob/master/Protocol.md#setup-frame). The support for mime types is not comprehensive but it will atleast support the [default metadata mime type](https://github.com/ReactiveSocket/reactivesocket/blob/mimetypes/MimeTypes.md) See README.md for usage. --- reactivesocket-mime-types/README.md | 66 ++++++ reactivesocket-mime-types/build.gradle | 26 +++ .../reactivesocket/mimetypes/KVMetadata.java | 24 +++ .../io/reactivesocket/mimetypes/MimeType.java | 175 +++++++++++++++ .../mimetypes/MimeTypeFactory.java | 179 +++++++++++++++ .../mimetypes/SupportedMimeTypes.java | 59 +++++ .../internal/AbstractJacksonCodec.java | 129 +++++++++++ .../internal/ByteBufferInputStream.java | 52 +++++ .../internal/ByteBufferOutputStream.java | 38 ++++ .../mimetypes/internal/Codec.java | 21 ++ .../mimetypes/internal/KVMetadataImpl.java | 135 ++++++++++++ .../ReactiveSocketDefaultMetadataCodec.java | 63 ++++++ .../mimetypes/internal/cbor/CborCodec.java | 33 +++ .../mimetypes/internal/json/JsonCodec.java | 32 +++ .../mimetypes/MimeTypeFactoryTest.java | 204 ++++++++++++++++++ .../internal/AbstractJacksonCodecTest.java | 81 +++++++ .../mimetypes/internal/CodecRule.java | 48 +++++ .../mimetypes/internal/CustomObject.java | 92 ++++++++ .../mimetypes/internal/CustomObjectRule.java | 53 +++++ .../internal/KVMetadataImplTest.java | 66 ++++++ .../mimetypes/internal/MetadataRule.java | 62 ++++++ ...eactiveSocketDefaultMetadataCodecTest.java | 60 ++++++ .../internal/cbor/CborCodecTest.java | 33 +++ .../internal/json/JsonCodecTest.java | 33 +++ settings.gradle | 5 +- 25 files changed, 1768 insertions(+), 1 deletion(-) create mode 100644 reactivesocket-mime-types/README.md create mode 100644 reactivesocket-mime-types/build.gradle create mode 100644 reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/KVMetadata.java create mode 100644 reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeType.java create mode 100644 reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeTypeFactory.java create mode 100644 reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/SupportedMimeTypes.java create mode 100644 reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodec.java create mode 100644 reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ByteBufferInputStream.java create mode 100644 reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ByteBufferOutputStream.java create mode 100644 reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/Codec.java create mode 100644 reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/KVMetadataImpl.java create mode 100644 reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ReactiveSocketDefaultMetadataCodec.java create mode 100644 reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborCodec.java create mode 100644 reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/json/JsonCodec.java create mode 100644 reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/MimeTypeFactoryTest.java create mode 100644 reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodecTest.java create mode 100644 reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CodecRule.java create mode 100644 reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CustomObject.java create mode 100644 reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CustomObjectRule.java create mode 100644 reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/KVMetadataImplTest.java create mode 100644 reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/MetadataRule.java create mode 100644 reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/ReactiveSocketDefaultMetadataCodecTest.java create mode 100644 reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborCodecTest.java create mode 100644 reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/json/JsonCodecTest.java diff --git a/reactivesocket-mime-types/README.md b/reactivesocket-mime-types/README.md new file mode 100644 index 000000000..02c28524b --- /dev/null +++ b/reactivesocket-mime-types/README.md @@ -0,0 +1,66 @@ +## Overview + +This module provides support for encoding/decoding ReactiveSocket data and metadata into using different mime types as defined by [ReactiveSocket protocol](https://github.com/ReactiveSocket/reactivesocket/blob/master/Protocol.md#setup-frame). +The support for mime types is not comprehensive but it will at least support the [default metadata mime type](https://github.com/ReactiveSocket/reactivesocket/blob/mimetypes/MimeTypes.md) + +## Usage + +#### Supported Codecs + +Supported mime types are listed as [SupportedMimeTypes](src/main/java/io/reactivesocket/mimetypes/SupportedMimeTypes.java). + +#### Obtaining the appropriate codec + +[MimeType](src/main/java/io/reactivesocket/mimetypes/MimeType.java) is the interface that provides different methods for encoding/decoding ReactiveSocket data and metadata. +An instance of `MimeType` can be obtained via [MimeTypeFactory](src/main/java/io/reactivesocket/mimetypes/MimeTypeFactory.java). + +A simple usage of `MimeType` is as follows: + +```java +public class ConnectionSetupHandlerImpl implements ConnectionSetupHandler { + + @Override + public RequestHandler apply(ConnectionSetupPayload setupPayload, ReactiveSocket reactiveSocket) throws SetupException { + + final MimeType mimeType = MimeTypeFactory.from(setupPayload); // If the mime types aren't supported, throws an error. + + return new RequestHandler() { + + // Not a complete implementation, just a method to demonstrate usage. + @Override + public Publisher handleRequestResponse(Payload payload) { + // use (en/de)codeMetadata() methods to encode/decode metadata + mimeType.decodeMetadata(payload.getMetadata(), KVMetadata.class); + // use (en/de)codeData() methods to encode/decode data + mimeType.decodeData(payload.getData(), Person.class); + return PublisherUtils.empty(); // Do something useful in reality! + } + }; + } +} +``` + +## Build and Binaries + + + +Artifacts are available via JCenter. + +Example: + +```groovy +repositories { + maven { url 'https://jcenter.bintray.com' } +} + +dependencies { + compile 'io.reactivesocket:reactivesocket-mime-types:x.y.z' +} +``` + +No releases to Maven Central have occurred yet. + + +## Bugs and Feedback + +For bugs, questions and discussions please use the [Github Issues](https://github.com/ReactiveSocket/reactivesocket-java-impl/issues). diff --git a/reactivesocket-mime-types/build.gradle b/reactivesocket-mime-types/build.gradle new file mode 100644 index 000000000..1c6952b9f --- /dev/null +++ b/reactivesocket-mime-types/build.gradle @@ -0,0 +1,26 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +dependencies { + + compile 'com.fasterxml.jackson.core:jackson-core:latest.release' + compile 'com.fasterxml.jackson.core:jackson-databind:latest.release' + compile 'com.fasterxml.jackson.module:jackson-module-afterburner:latest.release' + compile 'com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:latest.release' + + testCompile "org.hamcrest:hamcrest-library:1.3" +} \ No newline at end of file diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/KVMetadata.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/KVMetadata.java new file mode 100644 index 000000000..7bf9ca71b --- /dev/null +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/KVMetadata.java @@ -0,0 +1,24 @@ +package io.reactivesocket.mimetypes; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.Map; + +/** + * A representation of ReactiveSocket metadata as a key-value pair. + * + * Implementations are not required to be thread-safe. + */ +public interface KVMetadata extends Map { + + /** + * Lookup the value for the passed key and return the value as a string. + * + * @param key To Lookup. + * @param valueEncoding Encoding for the value. + * + * @return Value as a string with the passed {@code valueEncoding} + */ + String getAsString(String key, Charset valueEncoding); + +} diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeType.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeType.java new file mode 100644 index 000000000..442ea399e --- /dev/null +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeType.java @@ -0,0 +1,175 @@ +package io.reactivesocket.mimetypes; + +import io.reactivesocket.Frame; +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; + +import java.nio.ByteBuffer; + +/** + * Encoding and decoding operations for a ReactiveSocket. Since, mime-types for data and metadata do not change once + * setup, a MimeType instance can be stored per ReactiveSocket instance and can be used for repeated encode/decode of + * data and metadata. + */ +public interface MimeType { + + /** + * Decodes metadata of the passed frame to the specified {@code clazz}. + * + * @param toDecode Frame for which metadata is to be decoded. + * @param clazz Class to which metadata will be decoded. + * + * @param Type of the class to which metadata will be decoded. + * + * @return Instance of the class post decode. + */ + default T decodeMetadata(Frame toDecode, Class clazz) { + return decodeMetadata(toDecode.getMetadata(), clazz); + } + + /** + * Decodes the passed buffer to the specified {@code clazz}. + * + * @param toDecode buffer to be decoded. + * @param clazz Class to which the buffer will be decoded. + * + * @param Type of the class to which the buffer will be decoded. + * + * @return Instance of the class post decode. + */ + T decodeMetadata(ByteBuffer toDecode, Class clazz); + + /** + * Decodes the passed buffer to the specified {@code clazz}. + * + * @param toDecode buffer to be decoded. + * @param clazz Class to which the buffer will be decoded. + * + * @param Type of the class to which the buffer will be decoded. + * + * @return Instance of the class post decode. + */ + T decodeMetadata(DirectBuffer toDecode, Class clazz); + + /** + * Encodes passed metadata to a buffer. + * + * @param toEncode Object to encode as metadata. + * + * @param Type of the object to encode. + * + * @return Buffer with encoded data. + */ + ByteBuffer encodeMetadata(T toEncode); + + /** + * Encodes passed metadata to a buffer. + * + * @param toEncode Object to encode as metadata. + * + * @param Type of the object to encode. + * + * @return Buffer with encoded data. + */ + DirectBuffer encodeMetadataDirect(T toEncode); + + /** + * Encodes passed metadata to the passed buffer. + * + * @param buffer Encodes the metadata to this buffer. + * @param toEncode Metadata to encode. + * + * @param Type of the object to encode. + */ + void encodeMetadataTo(MutableDirectBuffer buffer, T toEncode); + + /** + * Encodes passed metadata to the passed buffer. + * + * @param buffer Encodes the metadata to this buffer. + * @param toEncode Metadata to encode. + * + * @param Type of the object to encode. + */ + void encodeMetadataTo(ByteBuffer buffer, T toEncode); + + /** + * Decodes data of the passed frame to the specified {@code clazz}. + * + * @param toDecode Frame for which metadata is to be decoded. + * @param clazz Class to which metadata will be decoded. + * + * @param Type of the class to which metadata will be decoded. + * + * @return Instance of the class post decode. + */ + default T decodeData(Frame toDecode, Class clazz) { + return decodeData(toDecode.getData(), clazz); + } + + /** + * Decodes the passed buffer to the specified {@code clazz}. + * + * @param toDecode buffer to be decoded. + * @param clazz Class to which the buffer will be decoded. + * + * @param Type of the class to which the buffer will be decoded. + * + * @return Instance of the class post decode. + */ + T decodeData(ByteBuffer toDecode, Class clazz); + + /** + * Decodes the passed buffer to the specified {@code clazz}. + * + * @param toDecode buffer to be decoded. + * @param clazz Class to which the buffer will be decoded. + * + * @param Type of the class to which the buffer will be decoded. + * + * @return Instance of the class post decode. + */ + T decodeData(DirectBuffer toDecode, Class clazz); + + /** + * Encodes passed data to a buffer. + * + * @param toEncode Object to encode as data. + * + * @param Type of the object to encode. + * + * @return Buffer with encoded data. + */ + ByteBuffer encodeData(T toEncode); + + /** + * Encodes passed data to a buffer. + * + * @param toEncode Object to encode as data. + * + * @param Type of the object to encode. + * + * @return Buffer with encoded data. + */ + DirectBuffer encodeDataDirect(T toEncode); + + /** + * Encodes passed data to the passed buffer. + * + * @param buffer Encodes the data to this buffer. + * @param toEncode Data to encode. + * + * @param Type of the object to encode. + */ + void encodeDataTo(MutableDirectBuffer buffer, T toEncode); + + /** + * Encodes passed data to the passed buffer. + * + * @param buffer Encodes the data to this buffer. + * @param toEncode Data to encode. + * + * @param Type of the object to encode. + */ + void encodeDataTo(ByteBuffer buffer, T toEncode); +} diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeTypeFactory.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeTypeFactory.java new file mode 100644 index 000000000..4d8e65a6c --- /dev/null +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeTypeFactory.java @@ -0,0 +1,179 @@ +package io.reactivesocket.mimetypes; + +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.mimetypes.internal.Codec; +import io.reactivesocket.mimetypes.internal.ReactiveSocketDefaultMetadataCodec; +import io.reactivesocket.mimetypes.internal.cbor.CborCodec; +import io.reactivesocket.mimetypes.internal.json.JsonCodec; +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; + +import java.nio.ByteBuffer; +import java.util.EnumMap; + +import static io.reactivesocket.mimetypes.SupportedMimeTypes.*; + +/** + * A factory to retrieve {@link MimeType} instances for {@link SupportedMimeTypes}. The retrieved mime type instances + * are thread-safe. + */ +public final class MimeTypeFactory { + + private static final EnumMap codecs; + private static final EnumMap> mimeTypes; + + static { + codecs = new EnumMap(SupportedMimeTypes.class); + codecs.put(CBOR, CborCodec.create()); + codecs.put(JSON, JsonCodec.create()); + codecs.put(ReactiveSocketDefaultMetadata, ReactiveSocketDefaultMetadataCodec.create()); + + mimeTypes = new EnumMap<>(SupportedMimeTypes.class); + mimeTypes.put(CBOR, getEnumMapForMetadataCodec(CBOR)); + mimeTypes.put(JSON, getEnumMapForMetadataCodec(JSON)); + mimeTypes.put(ReactiveSocketDefaultMetadata, getEnumMapForMetadataCodec(ReactiveSocketDefaultMetadata)); + } + + private MimeTypeFactory() { + } + + /** + * Provides an appropriate {@link MimeType} for the passed {@link ConnectionSetupPayload}. + * Only the mime types represented by {@link SupportedMimeTypes} are supported by this factory. For any other mime + * type, this method will throw an exception. + * It is safer to first retrieve the mime types and then use {@link #from(SupportedMimeTypes, SupportedMimeTypes)} + * method. + * + * @param setup Setup for which the mime type is to be fetched. + * + * @return Appropriate {@link MimeType} for the passed {@code setup}. + * + * @throws IllegalArgumentException If the mime type for either data or metadata is not supported by this factory. + */ + public static MimeType from(ConnectionSetupPayload setup) { + SupportedMimeTypes metaMimeType = parseOrDie(setup.metadataMimeType()); + SupportedMimeTypes dataMimeType = parseOrDie(setup.dataMimeType()); + + return from(metaMimeType, dataMimeType); + } + + /** + * Same as calling {@code from(mimeType, mimeType}. + * + * @param mimeType Mime type to be used for both data and metadata. + * + * @return MimeType for the passed {@code mimeType} + */ + public static MimeType from(SupportedMimeTypes mimeType) { + return from(mimeType, mimeType); + } + + /** + * Provides an appropriate {@link MimeType} for the passed date and metadata mime types. + * + * @param metadataMimeType Mime type for metadata. + * @param dataMimeType Mime type for data. + * + * @return Appropriate {@link MimeType} to use. + */ + public static MimeType from(SupportedMimeTypes metadataMimeType, SupportedMimeTypes dataMimeType) { + if (null == metadataMimeType) { + throw new IllegalArgumentException("Metadata mime type can not be null."); + } + if (null == dataMimeType) { + throw new IllegalArgumentException("Data mime type can not be null."); + } + + return mimeTypes.get(metadataMimeType).get(dataMimeType); + } + + private static EnumMap getEnumMapForMetadataCodec(SupportedMimeTypes metaMime) { + + final Codec metaMimeCodec = codecs.get(metaMime); + + EnumMap toReturn = + new EnumMap(SupportedMimeTypes.class); + + toReturn.put(CBOR, new MimeTypeImpl(metaMimeCodec, codecs.get(CBOR))); + toReturn.put(JSON, new MimeTypeImpl(metaMimeCodec, codecs.get(JSON))); + toReturn.put(ReactiveSocketDefaultMetadata, + new MimeTypeImpl(metaMimeCodec, codecs.get(ReactiveSocketDefaultMetadata))); + + return toReturn; + } + + /*Visible for testing*/ Codec getCodec(SupportedMimeTypes mimeType) { + return codecs.get(mimeType); + } + + private static class MimeTypeImpl implements MimeType { + + private final Codec metaCodec; + private final Codec dataCodec; + + public MimeTypeImpl(Codec metaCodec, Codec dataCodec) { + this.metaCodec = metaCodec; + this.dataCodec = dataCodec; + } + + @Override + public T decodeMetadata(ByteBuffer toDecode, Class clazz) { + return metaCodec.decode(toDecode, clazz); + } + + @Override + public T decodeMetadata(DirectBuffer toDecode, Class clazz) { + return metaCodec.decode(toDecode, clazz); + } + + @Override + public ByteBuffer encodeMetadata(T toEncode) { + return metaCodec.encode(toEncode); + } + + @Override + public DirectBuffer encodeMetadataDirect(T toEncode) { + return metaCodec.encodeDirect(toEncode); + } + + @Override + public void encodeMetadataTo(MutableDirectBuffer buffer, T toEncode) { + metaCodec.encodeTo(buffer, toEncode); + } + + @Override + public void encodeMetadataTo(ByteBuffer buffer, T toEncode) { + metaCodec.encodeTo(buffer, toEncode); + } + + @Override + public T decodeData(ByteBuffer toDecode, Class clazz) { + return dataCodec.decode(toDecode, clazz); + } + + @Override + public T decodeData(DirectBuffer toDecode, Class clazz) { + return dataCodec.decode(toDecode, clazz); + } + + @Override + public ByteBuffer encodeData(T toEncode) { + return dataCodec.encode(toEncode); + } + + @Override + public DirectBuffer encodeDataDirect(T toEncode) { + return dataCodec.encodeDirect(toEncode); + } + + @Override + public void encodeDataTo(MutableDirectBuffer buffer, T toEncode) { + dataCodec.encodeTo(buffer, toEncode); + } + + @Override + public void encodeDataTo(ByteBuffer buffer, T toEncode) { + dataCodec.encodeTo(buffer, toEncode); + } + } +} diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/SupportedMimeTypes.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/SupportedMimeTypes.java new file mode 100644 index 000000000..c1d83cb1b --- /dev/null +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/SupportedMimeTypes.java @@ -0,0 +1,59 @@ +package io.reactivesocket.mimetypes; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public enum SupportedMimeTypes { + + /*CBOR encoding*/ + CBOR ("application/cbor"), + /*JSON encoding*/ + JSON ("application/json"), + /*Default ReactiveSocket metadata encoding as specified in + https://github.com/ReactiveSocket/reactivesocket/blob/mimetypes/MimeTypes.md*/ + ReactiveSocketDefaultMetadata ("application/x.reactivesocket.meta+cbor"); + + private final List mimeTypes; + + SupportedMimeTypes(String... mimeTypes) { + this.mimeTypes = Collections.unmodifiableList(Arrays.asList(mimeTypes)); + } + + /** + * Parses the passed string to this enum. + * + * @param mimeType Mimetype to parse. + * + * @return This enum if the mime type is supported, else {@code null} + */ + public static SupportedMimeTypes parse(String mimeType) { + for (SupportedMimeTypes aMimeType : SupportedMimeTypes.values()) { + if (aMimeType.mimeTypes.contains(mimeType)) { + return aMimeType; + } + } + return null; + } + + /** + * Same as {@link #parse(String)} but throws an exception if the passed mime type is not supported. + * + * @param mimeType Mime-type to parse. + * + * @return This enum instance. + * + * @throws IllegalArgumentException If the mime-type is not supported. + */ + public static SupportedMimeTypes parseOrDie(String mimeType) { + SupportedMimeTypes parsed = parse(mimeType); + if (null == parsed) { + throw new IllegalArgumentException("Unsupported mime-type: " + mimeType); + } + return parsed; + } + + public List getMimeTypes() { + return mimeTypes; + } +} diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodec.java new file mode 100644 index 000000000..fa3a17bf8 --- /dev/null +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodec.java @@ -0,0 +1,129 @@ +package io.reactivesocket.mimetypes.internal; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonGenerator.Feature; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.module.afterburner.AfterburnerModule; +import org.agrona.DirectBuffer; +import org.agrona.LangUtil; +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import org.agrona.io.DirectBufferInputStream; +import org.agrona.io.MutableDirectBufferOutputStream; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +public abstract class AbstractJacksonCodec implements Codec { + + private static final ThreadLocal directInWrappers = + ThreadLocal.withInitial(DirectBufferInputStream::new); + + private static final ThreadLocal directOutWrappers = + ThreadLocal.withInitial(MutableDirectBufferOutputStream::new); + + private static final ThreadLocal bbInWrappers = + ThreadLocal.withInitial(ByteBufferInputStream::new); + + private static final ThreadLocal bbOutWrappers = + ThreadLocal.withInitial(ByteBufferOutputStream::new); + + private static final byte[] emptyByteArray = new byte[0]; + private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0); + private static final DirectBuffer EMPTY_DIRECT_BUFFER = new UnsafeBuffer(emptyByteArray); + + private final ObjectMapper mapper; + + protected AbstractJacksonCodec(ObjectMapper mapper) { + this.mapper = mapper; + } + + protected static void configureDefaults(ObjectMapper mapper) { + mapper.registerModule(new AfterburnerModule()); + mapper.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true); + mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); + mapper.configure(SerializationFeature.INDENT_OUTPUT, true); + mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + mapper.enable(Feature.AUTO_CLOSE_TARGET); // encodeTo methods do not close the OutputStream. + SimpleModule module = new SimpleModule(Version.unknownVersion()); + mapper.registerModule(module); + } + + @Override + public T decode(ByteBuffer buffer, Class tClass) { + return _decode(bbInWrappers.get().wrap(buffer), tClass); + } + + @Override + public T decode(DirectBuffer buffer, Class tClass) { + DirectBufferInputStream stream = directInWrappers.get(); + stream.wrap(buffer); + return _decode(stream, tClass); + } + + @Override + public ByteBuffer encode(T toEncode) { + byte[] bytes = _encode(toEncode); + return bytes == emptyByteArray ? EMPTY_BUFFER : ByteBuffer.wrap(bytes); + } + + @Override + public DirectBuffer encodeDirect(T toEncode) { + byte[] bytes = _encode(toEncode); + return bytes == emptyByteArray ? EMPTY_DIRECT_BUFFER : new UnsafeBuffer(bytes); + } + + @Override + public void encodeTo(ByteBuffer buffer, T toEncode) { + _encodeTo(bbOutWrappers.get().wrap(buffer), toEncode); + } + + @Override + public void encodeTo(MutableDirectBuffer buffer, T toEncode) { + MutableDirectBufferOutputStream stream = directOutWrappers.get(); + stream.wrap(buffer); + _encodeTo(stream, toEncode); + } + + private T _decode(InputStream stream, Class clazz) { + T v = null; + try { + v = mapper.readValue(stream, clazz); + } catch (IOException e) { + LangUtil.rethrowUnchecked(e); + } + + return v; + } + + private byte[] _encode(Object toEncode) { + byte[] encode = emptyByteArray; + + try { + encode = mapper.writeValueAsBytes(toEncode); + } catch (JsonProcessingException e) { + LangUtil.rethrowUnchecked(e); + } + + return encode; + } + + private void _encodeTo(OutputStream stream, Object toEncode) { + try { + mapper.writeValue(stream, toEncode); + } catch (JsonProcessingException e) { + LangUtil.rethrowUnchecked(e); + } catch (IOException e) { + LangUtil.rethrowUnchecked(e); + } + } +} diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ByteBufferInputStream.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ByteBufferInputStream.java new file mode 100644 index 000000000..9997317f9 --- /dev/null +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ByteBufferInputStream.java @@ -0,0 +1,52 @@ +package io.reactivesocket.mimetypes.internal; + +import java.io.InputStream; +import java.nio.ByteBuffer; + +/** + * This code is copied from {@link com.fasterxml.jackson.databind.util.ByteBufferBackedInputStream} with the + * modifications to make the stream mutable per thread. + */ +final class ByteBufferInputStream extends InputStream { + + private ByteBuffer _b; + + public ByteBufferInputStream() { + } + + public ByteBufferInputStream(ByteBuffer _b) { + this._b = _b; + } + + /** + * Returns a {@link ThreadLocal} instance of the {@link InputStream} which wraps the passed buffer. + * + * This instance must not leak from the calling thread. + * + * @param buffer Buffer to wrap. + */ + public ByteBufferInputStream wrap(ByteBuffer buffer) { + _b = buffer; + return this; + } + + @Override + public int available() { + return _b.remaining(); + } + + @Override + public int read() { + return _b.hasRemaining() ? _b.get() & 0xFF : -1; + } + + @Override + public int read(byte[] bytes, int off, int len) { + if (!_b.hasRemaining()) { + return -1; + } + len = Math.min(len, _b.remaining()); + _b.get(bytes, off, len); + return len; + } +} \ No newline at end of file diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ByteBufferOutputStream.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ByteBufferOutputStream.java new file mode 100644 index 000000000..975803c53 --- /dev/null +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ByteBufferOutputStream.java @@ -0,0 +1,38 @@ +package io.reactivesocket.mimetypes.internal; + +import java.io.OutputStream; +import java.nio.ByteBuffer; + +/** + * An {@link OutputStream} backed by a {@link ByteBuffer} which must only be used within the thread that retrieved it + * via {@link #wrap(ByteBuffer)} method. + *

+ * This code is copied from {@link com.fasterxml.jackson.databind.util.ByteBufferBackedOutputStream} with the + * modifications to make the stream mutable per thread. + */ +final class ByteBufferOutputStream extends OutputStream { + + private ByteBuffer _b; + + public ByteBufferOutputStream() { + } + + public ByteBufferOutputStream(ByteBuffer _b) { + this._b = _b; + } + + public ByteBufferOutputStream wrap(ByteBuffer buffer) { + _b = buffer; + return this; + } + + @Override + public void write(int b) { + _b.put((byte) b); + } + + @Override + public void write(byte[] bytes, int off, int len) { + _b.put(bytes, off, len); + } +} \ No newline at end of file diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/Codec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/Codec.java new file mode 100644 index 000000000..cd97983d2 --- /dev/null +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/Codec.java @@ -0,0 +1,21 @@ +package io.reactivesocket.mimetypes.internal; + +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; + +import java.nio.ByteBuffer; + +public interface Codec { + + T decode(ByteBuffer buffer, Class tClass); + + T decode(DirectBuffer buffer, Class tClass); + + ByteBuffer encode(T toEncode); + + DirectBuffer encodeDirect(T toEncode); + + void encodeTo(ByteBuffer buffer, T toEncode); + + void encodeTo(MutableDirectBuffer buffer, T toEncode); +} diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/KVMetadataImpl.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/KVMetadataImpl.java new file mode 100644 index 000000000..8b2fdd187 --- /dev/null +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/KVMetadataImpl.java @@ -0,0 +1,135 @@ +package io.reactivesocket.mimetypes.internal; + +import io.reactivesocket.mimetypes.KVMetadata; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class KVMetadataImpl implements KVMetadata { + + private Map store; + + public KVMetadataImpl(Map store) { + this.store = store; + } + + public KVMetadataImpl() { + store = new HashMap<>(); + } + + public void setStore(Map store) { + if (null == store) { + throw new IllegalArgumentException("Store can not be null"); + } + this.store = store; + } + + public Map getStore() { + return store; + } + + @Override + public String getAsString(String key, Charset valueEncoding) { + ByteBuffer toReturn = get(key); + + if (null != toReturn) { + byte[] dst = new byte[toReturn.limit() - toReturn.position()]; + toReturn.get(dst); + return new String(dst, valueEncoding); + } + + return null; + } + + @Override + public int size() { + return store.size(); + } + + @Override + public boolean isEmpty() { + return store.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return store.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return store.containsValue(value); + } + + @Override + public ByteBuffer get(Object key) { + return store.get(key); + } + + @Override + public ByteBuffer put(String key, ByteBuffer value) { + return store.put(key, value); + } + + @Override + public ByteBuffer remove(Object key) { + return store.remove(key); + } + + @Override + public void putAll(Map m) { + store.putAll(m); + } + + @Override + public void clear() { + store.clear(); + } + + @Override + public Set keySet() { + return store.keySet(); + } + + @Override + public Collection values() { + return store.values(); + } + + @Override + public Set> entrySet() { + return store.entrySet(); + } + + @Override + public String toString() { + return "KVMetadataImpl{" + "store=" + store + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof KVMetadataImpl)) { + return false; + } + + KVMetadataImpl that = (KVMetadataImpl) o; + + if (!store.equals(that.store)) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return store.hashCode(); + } +} diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ReactiveSocketDefaultMetadataCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ReactiveSocketDefaultMetadataCodec.java new file mode 100644 index 000000000..7b2d69a29 --- /dev/null +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ReactiveSocketDefaultMetadataCodec.java @@ -0,0 +1,63 @@ +package io.reactivesocket.mimetypes.internal; + +import io.reactivesocket.mimetypes.KVMetadata; +import io.reactivesocket.mimetypes.internal.cbor.CborCodec; +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; + +import java.nio.ByteBuffer; + +public class ReactiveSocketDefaultMetadataCodec implements Codec { + + private final CborCodec cborCodec; + + private ReactiveSocketDefaultMetadataCodec(CborCodec cborCodec) { + this.cborCodec = cborCodec; + } + + public KVMetadata decodeDefault(ByteBuffer buffer) { + return decode(buffer, KVMetadataImpl.class); + } + + public KVMetadata decodeDefault(DirectBuffer buffer) { + return decode(buffer, KVMetadataImpl.class); + } + + @Override + public T decode(ByteBuffer buffer, Class tClass) { + return cborCodec.decode(buffer, tClass); + } + + @Override + public T decode(DirectBuffer buffer, Class tClass) { + return cborCodec.decode(buffer, tClass); + } + + @Override + public ByteBuffer encode(T toEncode) { + return cborCodec.encode(toEncode); + } + + @Override + public DirectBuffer encodeDirect(T toEncode) { + return cborCodec.encodeDirect(toEncode); + } + + @Override + public void encodeTo(ByteBuffer buffer, T toEncode) { + cborCodec.encodeTo(buffer, toEncode); + } + + @Override + public void encodeTo(MutableDirectBuffer buffer, T toEncode) { + cborCodec.encodeTo(buffer, toEncode); + } + + public static ReactiveSocketDefaultMetadataCodec create() { + return new ReactiveSocketDefaultMetadataCodec(CborCodec.create()); + } + + public static ReactiveSocketDefaultMetadataCodec create(CborCodec cborCodec) { + return new ReactiveSocketDefaultMetadataCodec(cborCodec); + } +} diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborCodec.java new file mode 100644 index 000000000..9dd7dac38 --- /dev/null +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborCodec.java @@ -0,0 +1,33 @@ +package io.reactivesocket.mimetypes.internal.cbor; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.cbor.CBORFactory; +import io.reactivesocket.mimetypes.internal.AbstractJacksonCodec; + +public class CborCodec extends AbstractJacksonCodec { + + private CborCodec(ObjectMapper mapper) { + super(mapper); + } + + /** + * Creates a {@link CborCodec} with default configurations. Use {@link #create(ObjectMapper)} for custom mapper + * configurations. + * + * @return A new instance of {@link CborCodec} with default mapper configurations. + */ + public static CborCodec create() { + ObjectMapper mapper = new ObjectMapper(new CBORFactory()); + configureDefaults(mapper); + return create(mapper); + } + + /** + * Creates a {@link CborCodec} with custom mapper. Use {@link #create()} for default mapper configurations. + * + * @return A new instance of {@link CborCodec} with the passed mapper. + */ + public static CborCodec create(ObjectMapper mapper) { + return new CborCodec(mapper); + } +} diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/json/JsonCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/json/JsonCodec.java new file mode 100644 index 000000000..2dd535d67 --- /dev/null +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/json/JsonCodec.java @@ -0,0 +1,32 @@ +package io.reactivesocket.mimetypes.internal.json; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.reactivesocket.mimetypes.internal.AbstractJacksonCodec; + +public class JsonCodec extends AbstractJacksonCodec { + + private JsonCodec(ObjectMapper mapper) { + super(mapper); + } + + /** + * Creates a {@link JsonCodec} with default configurations. Use {@link #create(ObjectMapper)} for custom mapper + * configurations. + * + * @return A new instance of {@link JsonCodec} with default mapper configurations. + */ + public static JsonCodec create() { + ObjectMapper mapper = new ObjectMapper(); + configureDefaults(mapper); + return create(mapper); + } + + /** + * Creates a {@link JsonCodec} with custom mapper. Use {@link #create()} for default mapper configurations. + * + * @return A new instance of {@link JsonCodec} with the passed mapper. + */ + public static JsonCodec create(ObjectMapper mapper) { + return new JsonCodec(mapper); + } +} diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/MimeTypeFactoryTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/MimeTypeFactoryTest.java new file mode 100644 index 000000000..cdee4bbeb --- /dev/null +++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/MimeTypeFactoryTest.java @@ -0,0 +1,204 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes; + +import io.reactivesocket.ConnectionSetupPayload; +import io.reactivesocket.mimetypes.internal.Codec; +import io.reactivesocket.mimetypes.internal.CustomObject; +import io.reactivesocket.mimetypes.internal.CustomObjectRule; +import io.reactivesocket.mimetypes.internal.KVMetadataImpl; +import io.reactivesocket.mimetypes.internal.MetadataRule; +import io.reactivesocket.mimetypes.internal.ReactiveSocketDefaultMetadataCodec; +import io.reactivesocket.mimetypes.internal.cbor.CborCodec; +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExternalResource; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.nio.ByteBuffer; + +import static io.reactivesocket.mimetypes.SupportedMimeTypes.*; + +public class MimeTypeFactoryTest { + + @Rule + public final CustomObjectRule objectRule = new CustomObjectRule(); + @Rule + public final MetadataRule metadataRule = new MetadataRule(); + + @Test(timeout = 60000) + public void testFromSetup() throws Exception { + objectRule.populateDefaultData(); + metadataRule.populateDefaultMetadataData(); + + MimeType mimeType = getMimeTypeFromSetup(ReactiveSocketDefaultMetadata, CBOR); + + testMetadataCodec(mimeType, ReactiveSocketDefaultMetadataCodec.create()); + testDataCodec(mimeType, CborCodec.create()); + } + + @Test(expected = IllegalArgumentException.class) + public void testUnsupportedMimetype() throws Exception { + ConnectionSetupPayload setup = new ConnectionSetupPayloadImpl("blah", "blah"); + MimeTypeFactory.from(setup); + } + + @Test(timeout = 60000) + public void testOneMimetype() throws Exception { + objectRule.populateDefaultData(); + metadataRule.populateDefaultMetadataData(); + + MimeType mimeType = MimeTypeFactory.from(CBOR); + + testMetadataCodec(mimeType, CborCodec.create()); + testDataCodec(mimeType, CborCodec.create()); + } + + @Test(timeout = 60000) + public void testDifferentMimetypes() throws Exception { + objectRule.populateDefaultData(); + metadataRule.populateDefaultMetadataData(); + + MimeType mimeType = MimeTypeFactory.from(ReactiveSocketDefaultMetadata, CBOR); + + testMetadataCodec(mimeType, CborCodec.create()); + testDataCodec(mimeType, CborCodec.create()); + } + + private void testMetadataCodec(MimeType mimeType, Codec expectedCodec) { + ByteBuffer encode = mimeType.encodeMetadata(metadataRule.getKvMetadata()); + ByteBuffer encode1 = expectedCodec.encode(metadataRule.getKvMetadata()); + + MatcherAssert.assertThat("Unexpected encode from mime type.", encode, Matchers.equalTo(encode1)); + MatcherAssert.assertThat("Unexpected decode from encode.", metadataRule.getKvMetadata(), + Matchers.equalTo(mimeType.decodeMetadata(encode, KVMetadataImpl.class))); + + + DirectBuffer dencode = mimeType.encodeMetadataDirect(metadataRule.getKvMetadata()); + DirectBuffer dencode1 = expectedCodec.encodeDirect(metadataRule.getKvMetadata()); + + MatcherAssert.assertThat("Unexpected direct buffer encode from mime type.", dencode, Matchers.equalTo(dencode1)); + MatcherAssert.assertThat("Unexpected decode from direct encode.", metadataRule.getKvMetadata(), + Matchers.equalTo(mimeType.decodeMetadata(dencode, KVMetadataImpl.class))); + + ByteBuffer dst = ByteBuffer.allocate(100); + ByteBuffer dst1 = ByteBuffer.allocate(100); + + mimeType.encodeMetadataTo(dst, metadataRule.getKvMetadata()); + dst.flip(); + expectedCodec.encodeTo(dst1, metadataRule.getKvMetadata()); + dst1.flip(); + + MatcherAssert.assertThat("Unexpected encodeTo buffer encode from mime type.", dst, Matchers.equalTo(dst1)); + MatcherAssert.assertThat("Unexpected decode from encodeTo encode.", metadataRule.getKvMetadata(), + Matchers.equalTo(mimeType.decodeMetadata(dst, KVMetadataImpl.class))); + + MutableDirectBuffer mdst = new UnsafeBuffer(new byte[100]); + MutableDirectBuffer mdst1 = new UnsafeBuffer(new byte[100]); + + mimeType.encodeMetadataTo(mdst, metadataRule.getKvMetadata()); + expectedCodec.encodeTo(mdst1, metadataRule.getKvMetadata()); + + MatcherAssert.assertThat("Unexpected encodeTo buffer encode from mime type.", mdst, Matchers.equalTo(mdst1)); + MatcherAssert.assertThat("Unexpected decode from encodeTo encode.", metadataRule.getKvMetadata(), + Matchers.equalTo(mimeType.decodeMetadata(mdst, KVMetadataImpl.class))); + } + + private void testDataCodec(MimeType mimeType, Codec expectedCodec) { + ByteBuffer encode = mimeType.encodeData(objectRule.getData()); + ByteBuffer encode1 = expectedCodec.encode(objectRule.getData()); + + MatcherAssert.assertThat("Unexpected encode from mime type.", encode, Matchers.equalTo(encode1)); + MatcherAssert.assertThat("Unexpected decode from encode.", objectRule.getData(), + Matchers.equalTo(mimeType.decodeData(encode, CustomObject.class))); + + + DirectBuffer dencode = mimeType.encodeDataDirect(objectRule.getData()); + DirectBuffer dencode1 = expectedCodec.encodeDirect(objectRule.getData()); + + MatcherAssert.assertThat("Unexpected direct buffer encode from mime type.", dencode, Matchers.equalTo(dencode1)); + MatcherAssert.assertThat("Unexpected decode from direct encode.", objectRule.getData(), + Matchers.equalTo(mimeType.decodeData(dencode, CustomObject.class))); + + ByteBuffer dst = ByteBuffer.allocate(100); + ByteBuffer dst1 = ByteBuffer.allocate(100); + + mimeType.encodeDataTo(dst, objectRule.getData()); + dst.flip(); + expectedCodec.encodeTo(dst1, objectRule.getData()); + dst1.flip(); + + MatcherAssert.assertThat("Unexpected encodeTo buffer encode from mime type.", dst, Matchers.equalTo(dst1)); + MatcherAssert.assertThat("Unexpected decode from encodeTo encode.", objectRule.getData(), + Matchers.equalTo(mimeType.decodeData(dst, CustomObject.class))); + + MutableDirectBuffer mdst = new UnsafeBuffer(new byte[100]); + MutableDirectBuffer mdst1 = new UnsafeBuffer(new byte[100]); + + mimeType.encodeDataTo(mdst, objectRule.getData()); + expectedCodec.encodeTo(mdst1, objectRule.getData()); + + MatcherAssert.assertThat("Unexpected encodeTo buffer encode from mime type.", mdst, Matchers.equalTo(mdst1)); + MatcherAssert.assertThat("Unexpected decode from encodeTo encode.", objectRule.getData(), + Matchers.equalTo(mimeType.decodeData(mdst, CustomObject.class))); + } + + private static MimeType getMimeTypeFromSetup(SupportedMimeTypes metaMime, SupportedMimeTypes dataMime) { + ConnectionSetupPayload setup = new ConnectionSetupPayloadImpl(metaMime.getMimeTypes().get(0), + dataMime.getMimeTypes().get(0)); + + return MimeTypeFactory.from(setup); + } + + private static class ConnectionSetupPayloadImpl extends ConnectionSetupPayload { + + private final String dataMime; + private final String metadataMime; + + private ConnectionSetupPayloadImpl(String dataMime, String metadataMime) { + this.dataMime = dataMime; + this.metadataMime = metadataMime; + } + + @Override + public String metadataMimeType() { + return metadataMime; + } + + @Override + public String dataMimeType() { + return dataMime; + } + + @Override + public ByteBuffer getData() { + return ByteBuffer.allocate(0); + } + + @Override + public ByteBuffer getMetadata() { + return ByteBuffer.allocate(0); + } + } +} \ No newline at end of file diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodecTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodecTest.java new file mode 100644 index 000000000..b211225a0 --- /dev/null +++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodecTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal; + +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.Rule; +import org.junit.Test; + +import java.nio.ByteBuffer; + +public abstract class AbstractJacksonCodecTest { + + @Rule + public final CustomObjectRule customObjectRule = new CustomObjectRule(); + @Rule + public final MetadataRule metadataRule = new MetadataRule(); + + @Test(timeout = 60000) + public void encodeDecode() throws Exception { + customObjectRule.populateDefaultData(); + ByteBuffer encode = getCodecRule().getCodec().encode(customObjectRule.getData()); + + CustomObject decode = getCodecRule().getCodec().decode(encode, CustomObject.class); + MatcherAssert.assertThat("Unexpected decode.", decode, Matchers.equalTo(customObjectRule.getData())); + } + + @Test(timeout = 60000) + public void encodeDecodeDirect() throws Exception { + customObjectRule.populateDefaultData(); + DirectBuffer encode = getCodecRule().getCodec().encodeDirect(customObjectRule.getData()); + + CustomObject decode = getCodecRule().getCodec().decode(encode, CustomObject.class); + MatcherAssert.assertThat("Unexpected decode.", decode, Matchers.equalTo(customObjectRule.getData())); + } + + @Test(timeout = 60000) + public void encodeTo() throws Exception { + customObjectRule.populateDefaultData(); + ByteBuffer encodeDest = ByteBuffer.allocate(10000); + getCodecRule().getCodec().encodeTo(encodeDest, customObjectRule.getData()); + + encodeDest.flip(); /*Since we want to decode it now*/ + + CustomObject decode = getCodecRule().getCodec().decode(encodeDest, CustomObject.class); + + MatcherAssert.assertThat("Unexpected decode.", decode, Matchers.equalTo(customObjectRule.getData())); + } + + @Test(timeout = 60000) + public void encodeToDirect() throws Exception { + customObjectRule.populateDefaultData(); + byte[] destArr = new byte[1000]; + MutableDirectBuffer encodeDest = new UnsafeBuffer(destArr); + getCodecRule().getCodec().encodeTo(encodeDest, customObjectRule.getData()); + + CustomObject decode = getCodecRule().getCodec().decode(encodeDest, CustomObject.class); + + MatcherAssert.assertThat("Unexpected decode.", decode, Matchers.equalTo(customObjectRule.getData())); + } + + protected abstract CodecRule getCodecRule(); +} diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CodecRule.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CodecRule.java new file mode 100644 index 000000000..4a15b8c4d --- /dev/null +++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CodecRule.java @@ -0,0 +1,48 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal; + +import org.junit.rules.ExternalResource; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; +import rx.functions.Func0; + +public class CodecRule extends ExternalResource { + + private T codec; + private final Func0 codecFactory; + + public CodecRule(Func0 codecFactory) { + this.codecFactory = codecFactory; + } + + @Override + public Statement apply(final Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + codec = codecFactory.call(); + base.evaluate(); + } + }; + } + + public T getCodec() { + return codec; + } +} diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CustomObject.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CustomObject.java new file mode 100644 index 000000000..82a7f69ba --- /dev/null +++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CustomObject.java @@ -0,0 +1,92 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal; + +import java.util.Map; + +public class CustomObject { + + private String name; + private int age; + private Map attributes; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public Map getAttributes() { + return attributes; + } + + public void setAttributes(Map attributes) { + this.attributes = attributes; + } + + @Override + public String toString() { + String sb = "CustomObject{" + "name='" + name + '\'' + + ", age=" + age + + ", attributes=" + attributes + + '}'; + return sb; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof CustomObject)) { + return false; + } + + CustomObject that = (CustomObject) o; + + if (age != that.age) { + return false; + } + if (name != null? !name.equals(that.name) : that.name != null) { + return false; + } + if (attributes != null? !attributes.equals(that.attributes) : that.attributes != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = name != null? name.hashCode() : 0; + result = 31 * result + age; + result = 31 * result + (attributes != null? attributes.hashCode() : 0); + return result; + } +} diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CustomObjectRule.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CustomObjectRule.java new file mode 100644 index 000000000..9e002d8ac --- /dev/null +++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CustomObjectRule.java @@ -0,0 +1,53 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal; + +import org.junit.rules.ExternalResource; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.util.HashMap; +import java.util.Map; + +public class CustomObjectRule extends ExternalResource { + + private CustomObject data; + + @Override + public Statement apply(final Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + data = new CustomObject(); + base.evaluate(); + } + }; + } + public CustomObject getData() { + return data; + } + + public void populateDefaultData() { + data.setAge(100); + data.setName("Dummy"); + Map attribs = new HashMap<>(); + attribs.put("1K", 1); + attribs.put("2K", 2); + data.setAttributes(attribs); + } +} diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/KVMetadataImplTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/KVMetadataImplTest.java new file mode 100644 index 000000000..b3e4836b9 --- /dev/null +++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/KVMetadataImplTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal; + +import org.hamcrest.MatcherAssert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExternalResource; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +import static org.hamcrest.Matchers.*; + +public class KVMetadataImplTest { + + @Rule + public final MetadataRule metadataRule = new MetadataRule(); + + @Test(timeout = 60000) + public void testGetAsString() throws Exception { + String key = "Key1"; + String value = "Value1"; + metadataRule.addMetadata(key, value); + + String lookup = metadataRule.getKvMetadata().getAsString(key, StandardCharsets.UTF_8); + + MatcherAssert.assertThat("Unexpected lookup value.", lookup, equalTo(value)); + } + + @Test(timeout = 60000) + public void testGetAsEmptyString() throws Exception { + String key = "Key1"; + String value = ""; + metadataRule.addMetadata(key, value); + + String lookup = metadataRule.getKvMetadata().getAsString(key, StandardCharsets.UTF_8); + + MatcherAssert.assertThat("Unexpected lookup value.", lookup, equalTo(value)); + } + + @Test(timeout = 60000) + public void testGetAsStringInvalid() throws Exception { + + String lookup = metadataRule.getKvMetadata().getAsString("Key", StandardCharsets.UTF_8); + + MatcherAssert.assertThat("Unexpected lookup value.", lookup, nullValue()); + } +} \ No newline at end of file diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/MetadataRule.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/MetadataRule.java new file mode 100644 index 000000000..ffa4bb484 --- /dev/null +++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/MetadataRule.java @@ -0,0 +1,62 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal; + +import org.junit.rules.ExternalResource; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; + +public class MetadataRule extends ExternalResource { + + private KVMetadataImpl kvMetadata; + + @Override + public Statement apply(final Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + kvMetadata = new KVMetadataImpl(new HashMap<>()); + base.evaluate(); + } + }; + } + + public void populateDefaultMetadataData() { + addMetadata("Hello1", "HelloVal1"); + addMetadata("Hello2", "HelloVal2"); + } + + public void addMetadata(String key, String value) { + ByteBuffer allocate = ByteBuffer.allocate(value.length()); + allocate.put(value.getBytes(StandardCharsets.UTF_8)).flip(); + kvMetadata.put(key, allocate); + } + + public void addMetadata(String key, ByteBuffer value) { + kvMetadata.put(key, value); + } + + public KVMetadataImpl getKvMetadata() { + return kvMetadata; + } + +} diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/ReactiveSocketDefaultMetadataCodecTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/ReactiveSocketDefaultMetadataCodecTest.java new file mode 100644 index 000000000..c0c8617da --- /dev/null +++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/ReactiveSocketDefaultMetadataCodecTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal; + +import io.reactivesocket.mimetypes.KVMetadata; +import org.agrona.DirectBuffer; +import org.hamcrest.MatcherAssert; +import org.junit.Rule; +import org.junit.Test; + +import java.nio.ByteBuffer; + +import static org.hamcrest.Matchers.*; + +public class ReactiveSocketDefaultMetadataCodecTest { + + @Rule + public final CodecRule codecRule = + new CodecRule<>(ReactiveSocketDefaultMetadataCodec::create); + @Rule + public final MetadataRule metadataRule = new MetadataRule(); + + @Test(timeout = 60000) + public void testDecodeDefault() throws Exception { + + metadataRule.populateDefaultMetadataData(); + + ByteBuffer encode = codecRule.getCodec().encode(metadataRule.getKvMetadata()); + KVMetadata kvMetadata = codecRule.getCodec().decodeDefault(encode); + + MatcherAssert.assertThat("Unexpected decoded metadata.", kvMetadata, equalTo(metadataRule.getKvMetadata())); + } + + @Test(timeout = 60000) + public void testDecodeDefaultDirect() throws Exception { + + metadataRule.populateDefaultMetadataData(); + + DirectBuffer encode = codecRule.getCodec().encodeDirect(metadataRule.getKvMetadata()); + KVMetadata kvMetadata = codecRule.getCodec().decodeDefault(encode); + + MatcherAssert.assertThat("Unexpected decoded metadata.", kvMetadata, equalTo(metadataRule.getKvMetadata())); + } + +} \ No newline at end of file diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborCodecTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborCodecTest.java new file mode 100644 index 000000000..f70564d63 --- /dev/null +++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborCodecTest.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal.cbor; + +import io.reactivesocket.mimetypes.internal.AbstractJacksonCodecTest; +import io.reactivesocket.mimetypes.internal.CodecRule; +import org.junit.Rule; + +public class CborCodecTest extends AbstractJacksonCodecTest { + + @Rule + public final CodecRule codecRule = new CodecRule<>(CborCodec::create); + + @Override + protected CodecRule getCodecRule() { + return codecRule; + } +} \ No newline at end of file diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/json/JsonCodecTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/json/JsonCodecTest.java new file mode 100644 index 000000000..757e12c3a --- /dev/null +++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/json/JsonCodecTest.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal.json; + +import io.reactivesocket.mimetypes.internal.AbstractJacksonCodecTest; +import io.reactivesocket.mimetypes.internal.CodecRule; +import org.junit.Rule; + +public class JsonCodecTest extends AbstractJacksonCodecTest { + + @Rule + public final CodecRule codecRule = new CodecRule<>(JsonCodec::create); + + @Override + protected CodecRule getCodecRule() { + return codecRule; + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index eae8b7692..bc5788409 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,7 @@ rootProject.name='reactivesocket-java-impl' -include 'reactivesocket-aeron', 'reactivesocket-jsr-356', 'reactivesocket-netty' +include 'reactivesocket-aeron' +include 'reactivesocket-jsr-356' +include 'reactivesocket-netty' include 'reactivesocket-local' +include 'reactivesocket-mime-types' From b5b04418583d9840356b9f8ae32427b51b75f736 Mon Sep 17 00:00:00 2001 From: Nitesh Kant Date: Mon, 23 May 2016 22:06:16 -0700 Subject: [PATCH 100/105] Custom CBOR codec (for metadata) For default metadata mime-type, using Jackson CBOR codec, produces more allocations (one buffer allocation per value) which are unnecessary. This was the major driving factor to invest in a _simple_ codec that can reduce allocations and be more suitable for ReactiveSocket design (using thread-local pooled buffers). Although, the codec is intended to be used only for a map type, but it is rather generic to be used for a majority of CBOR data types. If this proves useful, we can extend this for the remaining data types. --- build.gradle | 6 + .../reactivesocket/mimetypes/KVMetadata.java | 13 + .../io/reactivesocket/mimetypes/MimeType.java | 26 +- .../mimetypes/MimeTypeFactory.java | 18 +- .../internal/AbstractJacksonCodec.java | 8 +- .../mimetypes/internal/Codec.java | 4 +- .../mimetypes/internal/KVMetadataImpl.java | 10 +- .../internal/MalformedInputException.java | 40 +++ .../ReactiveSocketDefaultMetadataCodec.java | 63 ---- .../mimetypes/internal/cbor/CBORMap.java | 293 ++++++++++++++++++ .../mimetypes/internal/cbor/CBORUtils.java | 125 ++++++++ .../internal/cbor/CborBinaryStringCodec.java | 85 +++++ .../mimetypes/internal/cbor/CborHeader.java | 221 +++++++++++++ .../internal/cbor/CborMajorType.java | 82 +++++ .../mimetypes/internal/cbor/CborMapCodec.java | 107 +++++++ .../internal/cbor/CborUtf8StringCodec.java | 89 ++++++ .../internal/cbor/IndexedUnsafeBuffer.java | 201 ++++++++++++ .../internal/cbor/MetadataCodec.java | 158 ++++++++++ .../ReactiveSocketDefaultMetadataCodec.java | 104 +++++++ .../internal/cbor/SlicedBufferKVMetadata.java | 84 +++++ .../mimetypes/MimeTypeFactoryTest.java | 34 +- .../internal/AbstractJacksonCodecTest.java | 6 +- ...eactiveSocketDefaultMetadataCodecTest.java | 9 +- .../internal/cbor/AbstractCborMapRule.java | 87 ++++++ .../internal/cbor/ByteBufferMapMatcher.java | 61 ++++ .../mimetypes/internal/cbor/CBORMapTest.java | 168 ++++++++++ .../internal/cbor/CBORMapValueMaskTest.java | 56 ++++ .../internal/cbor/CBORUtilsTest.java | 154 +++++++++ .../cbor/CborBinaryStringCodecTest.java | 131 ++++++++ .../internal/cbor/CborHeaderTest.java | 131 ++++++++ .../internal/cbor/CborMapCodecTest.java | 125 ++++++++ .../cbor/CborUtf8StringCodecTest.java | 85 +++++ .../cbor/IndexedUnsafeBufferTest.java | 115 +++++++ .../internal/cbor/MetadataCodecTest.java | 148 +++++++++ .../cbor/SlicedBufferKVMetadataTest.java | 103 ++++++ .../src/test/resources/log4j.properties | 21 ++ 36 files changed, 3055 insertions(+), 116 deletions(-) create mode 100644 reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/MalformedInputException.java delete mode 100644 reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ReactiveSocketDefaultMetadataCodec.java create mode 100644 reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CBORMap.java create mode 100644 reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CBORUtils.java create mode 100644 reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborBinaryStringCodec.java create mode 100644 reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborHeader.java create mode 100644 reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborMajorType.java create mode 100644 reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborMapCodec.java create mode 100644 reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborUtf8StringCodec.java create mode 100644 reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/IndexedUnsafeBuffer.java create mode 100644 reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/MetadataCodec.java create mode 100644 reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/ReactiveSocketDefaultMetadataCodec.java create mode 100644 reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/SlicedBufferKVMetadata.java create mode 100644 reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/AbstractCborMapRule.java create mode 100644 reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/ByteBufferMapMatcher.java create mode 100644 reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORMapTest.java create mode 100644 reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORMapValueMaskTest.java create mode 100644 reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORUtilsTest.java create mode 100644 reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborBinaryStringCodecTest.java create mode 100644 reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborHeaderTest.java create mode 100644 reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborMapCodecTest.java create mode 100644 reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborUtf8StringCodecTest.java create mode 100644 reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/IndexedUnsafeBufferTest.java create mode 100644 reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/MetadataCodecTest.java create mode 100644 reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/SlicedBufferKVMetadataTest.java create mode 100644 reactivesocket-mime-types/src/test/resources/log4j.properties diff --git a/build.gradle b/build.gradle index a2ff9e459..3f658ff31 100644 --- a/build.gradle +++ b/build.gradle @@ -23,6 +23,12 @@ subprojects { testCompile 'org.mockito:mockito-core:1.10.19' testRuntime 'org.slf4j:slf4j-simple:1.7.12' } + + test { + testLogging { + showStandardStreams = true + } + } } // support for snapshot/final releases via versioned branch names like 1.x diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/KVMetadata.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/KVMetadata.java index 7bf9ca71b..47f333ffd 100644 --- a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/KVMetadata.java +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/KVMetadata.java @@ -1,8 +1,11 @@ package io.reactivesocket.mimetypes; +import org.agrona.MutableDirectBuffer; + import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.Map; +import java.util.function.Function; /** * A representation of ReactiveSocket metadata as a key-value pair. @@ -18,7 +21,17 @@ public interface KVMetadata extends Map { * @param valueEncoding Encoding for the value. * * @return Value as a string with the passed {@code valueEncoding} + * @throws NullPointerException If the key does not exist. */ String getAsString(String key, Charset valueEncoding); + /** + * Creates a new copy of this metadata. + * + * @param newBufferFactory A factory to create new buffer instances to copy, if required. The argument to the + * function is the capacity of the new buffer. + * + * @return New copy of this metadata. + */ + KVMetadata duplicate(Function newBufferFactory); } diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeType.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeType.java index 442ea399e..68caac399 100644 --- a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeType.java +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeType.java @@ -42,14 +42,15 @@ default T decodeMetadata(Frame toDecode, Class clazz) { /** * Decodes the passed buffer to the specified {@code clazz}. * + * @param Type of the class to which the buffer will be decoded. + * * @param toDecode buffer to be decoded. * @param clazz Class to which the buffer will be decoded. - * - * @param Type of the class to which the buffer will be decoded. + * @param offset Offset in the buffer. * * @return Instance of the class post decode. */ - T decodeMetadata(DirectBuffer toDecode, Class clazz); + T decodeMetadata(DirectBuffer toDecode, Class clazz, int offset); /** * Encodes passed metadata to a buffer. @@ -76,12 +77,12 @@ default T decodeMetadata(Frame toDecode, Class clazz) { /** * Encodes passed metadata to the passed buffer. * + * @param Type of the object to encode. * @param buffer Encodes the metadata to this buffer. * @param toEncode Metadata to encode. - * - * @param Type of the object to encode. + * @param offset Offset in the buffer to start writing. */ - void encodeMetadataTo(MutableDirectBuffer buffer, T toEncode); + void encodeMetadataTo(MutableDirectBuffer buffer, T toEncode, int offset); /** * Encodes passed metadata to the passed buffer. @@ -122,14 +123,15 @@ default T decodeData(Frame toDecode, Class clazz) { /** * Decodes the passed buffer to the specified {@code clazz}. * + * @param Type of the class to which the buffer will be decoded. + * * @param toDecode buffer to be decoded. * @param clazz Class to which the buffer will be decoded. - * - * @param Type of the class to which the buffer will be decoded. + * @param offset Offset in the buffer. * * @return Instance of the class post decode. */ - T decodeData(DirectBuffer toDecode, Class clazz); + T decodeData(DirectBuffer toDecode, Class clazz, int offset); /** * Encodes passed data to a buffer. @@ -156,12 +158,12 @@ default T decodeData(Frame toDecode, Class clazz) { /** * Encodes passed data to the passed buffer. * + * @param Type of the object to encode. * @param buffer Encodes the data to this buffer. * @param toEncode Data to encode. - * - * @param Type of the object to encode. + * @param offset Offset in the buffer to start writing. */ - void encodeDataTo(MutableDirectBuffer buffer, T toEncode); + void encodeDataTo(MutableDirectBuffer buffer, T toEncode, int offset); /** * Encodes passed data to the passed buffer. diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeTypeFactory.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeTypeFactory.java index 4d8e65a6c..6842d7abc 100644 --- a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeTypeFactory.java +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeTypeFactory.java @@ -2,8 +2,8 @@ import io.reactivesocket.ConnectionSetupPayload; import io.reactivesocket.mimetypes.internal.Codec; -import io.reactivesocket.mimetypes.internal.ReactiveSocketDefaultMetadataCodec; import io.reactivesocket.mimetypes.internal.cbor.CborCodec; +import io.reactivesocket.mimetypes.internal.cbor.ReactiveSocketDefaultMetadataCodec; import io.reactivesocket.mimetypes.internal.json.JsonCodec; import org.agrona.DirectBuffer; import org.agrona.MutableDirectBuffer; @@ -122,8 +122,8 @@ public T decodeMetadata(ByteBuffer toDecode, Class clazz) { } @Override - public T decodeMetadata(DirectBuffer toDecode, Class clazz) { - return metaCodec.decode(toDecode, clazz); + public T decodeMetadata(DirectBuffer toDecode, Class clazz, int offset) { + return metaCodec.decode(toDecode, offset, clazz); } @Override @@ -137,8 +137,8 @@ public DirectBuffer encodeMetadataDirect(T toEncode) { } @Override - public void encodeMetadataTo(MutableDirectBuffer buffer, T toEncode) { - metaCodec.encodeTo(buffer, toEncode); + public void encodeMetadataTo(MutableDirectBuffer buffer, T toEncode, int offset) { + metaCodec.encodeTo(buffer, toEncode, offset); } @Override @@ -152,8 +152,8 @@ public T decodeData(ByteBuffer toDecode, Class clazz) { } @Override - public T decodeData(DirectBuffer toDecode, Class clazz) { - return dataCodec.decode(toDecode, clazz); + public T decodeData(DirectBuffer toDecode, Class clazz, int offset) { + return dataCodec.decode(toDecode, offset, clazz); } @Override @@ -167,8 +167,8 @@ public DirectBuffer encodeDataDirect(T toEncode) { } @Override - public void encodeDataTo(MutableDirectBuffer buffer, T toEncode) { - dataCodec.encodeTo(buffer, toEncode); + public void encodeDataTo(MutableDirectBuffer buffer, T toEncode, int offset) { + dataCodec.encodeTo(buffer, toEncode, offset); } @Override diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodec.java index fa3a17bf8..ef3119d5c 100644 --- a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodec.java +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodec.java @@ -64,9 +64,9 @@ public T decode(ByteBuffer buffer, Class tClass) { } @Override - public T decode(DirectBuffer buffer, Class tClass) { + public T decode(DirectBuffer buffer, int offset, Class tClass) { DirectBufferInputStream stream = directInWrappers.get(); - stream.wrap(buffer); + stream.wrap(buffer, offset, buffer.capacity()); return _decode(stream, tClass); } @@ -88,9 +88,9 @@ public void encodeTo(ByteBuffer buffer, T toEncode) { } @Override - public void encodeTo(MutableDirectBuffer buffer, T toEncode) { + public void encodeTo(MutableDirectBuffer buffer, T toEncode, int offset) { MutableDirectBufferOutputStream stream = directOutWrappers.get(); - stream.wrap(buffer); + stream.wrap(buffer, offset, buffer.capacity()); _encodeTo(stream, toEncode); } diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/Codec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/Codec.java index cd97983d2..d04acfe6e 100644 --- a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/Codec.java +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/Codec.java @@ -9,7 +9,7 @@ public interface Codec { T decode(ByteBuffer buffer, Class tClass); - T decode(DirectBuffer buffer, Class tClass); + T decode(DirectBuffer buffer, int offset, Class tClass); ByteBuffer encode(T toEncode); @@ -17,5 +17,5 @@ public interface Codec { void encodeTo(ByteBuffer buffer, T toEncode); - void encodeTo(MutableDirectBuffer buffer, T toEncode); + void encodeTo(MutableDirectBuffer buffer, T toEncode, int offset); } diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/KVMetadataImpl.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/KVMetadataImpl.java index 8b2fdd187..e8bf2f937 100644 --- a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/KVMetadataImpl.java +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/KVMetadataImpl.java @@ -1,6 +1,7 @@ package io.reactivesocket.mimetypes.internal; import io.reactivesocket.mimetypes.KVMetadata; +import org.agrona.MutableDirectBuffer; import java.nio.ByteBuffer; import java.nio.charset.Charset; @@ -8,6 +9,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.function.Function; public class KVMetadataImpl implements KVMetadata { @@ -37,7 +39,7 @@ public String getAsString(String key, Charset valueEncoding) { ByteBuffer toReturn = get(key); if (null != toReturn) { - byte[] dst = new byte[toReturn.limit() - toReturn.position()]; + byte[] dst = new byte[toReturn.remaining()]; toReturn.get(dst); return new String(dst, valueEncoding); } @@ -45,6 +47,12 @@ public String getAsString(String key, Charset valueEncoding) { return null; } + @Override + public KVMetadata duplicate(Function newBufferFactory) { + Map copy = new HashMap<>(store); + return new KVMetadataImpl(copy); + } + @Override public int size() { return store.size(); diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/MalformedInputException.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/MalformedInputException.java new file mode 100644 index 000000000..95d1d435a --- /dev/null +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/MalformedInputException.java @@ -0,0 +1,40 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal; + +public class MalformedInputException extends RuntimeException { + + private static final long serialVersionUID = 3130502874275862715L; + + public MalformedInputException(String message) { + super(message); + } + + public MalformedInputException(String message, Throwable cause) { + super(message, cause); + } + + public MalformedInputException(Throwable cause) { + super(cause); + } + + public MalformedInputException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ReactiveSocketDefaultMetadataCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ReactiveSocketDefaultMetadataCodec.java deleted file mode 100644 index 7b2d69a29..000000000 --- a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ReactiveSocketDefaultMetadataCodec.java +++ /dev/null @@ -1,63 +0,0 @@ -package io.reactivesocket.mimetypes.internal; - -import io.reactivesocket.mimetypes.KVMetadata; -import io.reactivesocket.mimetypes.internal.cbor.CborCodec; -import org.agrona.DirectBuffer; -import org.agrona.MutableDirectBuffer; - -import java.nio.ByteBuffer; - -public class ReactiveSocketDefaultMetadataCodec implements Codec { - - private final CborCodec cborCodec; - - private ReactiveSocketDefaultMetadataCodec(CborCodec cborCodec) { - this.cborCodec = cborCodec; - } - - public KVMetadata decodeDefault(ByteBuffer buffer) { - return decode(buffer, KVMetadataImpl.class); - } - - public KVMetadata decodeDefault(DirectBuffer buffer) { - return decode(buffer, KVMetadataImpl.class); - } - - @Override - public T decode(ByteBuffer buffer, Class tClass) { - return cborCodec.decode(buffer, tClass); - } - - @Override - public T decode(DirectBuffer buffer, Class tClass) { - return cborCodec.decode(buffer, tClass); - } - - @Override - public ByteBuffer encode(T toEncode) { - return cborCodec.encode(toEncode); - } - - @Override - public DirectBuffer encodeDirect(T toEncode) { - return cborCodec.encodeDirect(toEncode); - } - - @Override - public void encodeTo(ByteBuffer buffer, T toEncode) { - cborCodec.encodeTo(buffer, toEncode); - } - - @Override - public void encodeTo(MutableDirectBuffer buffer, T toEncode) { - cborCodec.encodeTo(buffer, toEncode); - } - - public static ReactiveSocketDefaultMetadataCodec create() { - return new ReactiveSocketDefaultMetadataCodec(CborCodec.create()); - } - - public static ReactiveSocketDefaultMetadataCodec create(CborCodec cborCodec) { - return new ReactiveSocketDefaultMetadataCodec(cborCodec); - } -} diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CBORMap.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CBORMap.java new file mode 100644 index 000000000..7a7f43ac5 --- /dev/null +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CBORMap.java @@ -0,0 +1,293 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal.cbor; + +import io.reactivesocket.internal.frame.ByteBufferUtil; +import org.agrona.DirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; + +import java.nio.ByteBuffer; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static io.reactivesocket.mimetypes.internal.cbor.CborMajorType.*; + +/** + * A representation of CBOR map as defined in the spec.

+ * + * The benefit of this class is that it does not create additional buffers for the values of the metadata, when + * possible. Instead it holds views into the original buffer and when queried creates a slice of the underlying buffer + * that represents the value. Thus, it is lean in terms of memory usage as compared to other standard libraries that + * allocate memory for all values. + * + *

Allocations

+ * + *

Modifications

+ * + * Any additions to the map (adding one or more key-value pairs) will create a map with all keys and values, where + * values are the buffer slices of the original buffer. From then onwards, the newly created map will be used for all + * further queries.

+ * So, additions to this map will result in more allocations than usual but it still does not allocate fresh memory + * for existing entries. + * + *

Access

+ * + * Bulk queries like {@link #entrySet()}, {@link #values()} and value queries like {@link #containsValue(Object)} will + * switch to a new map as described in case of modifications above. + * + *

Structure

+ * + * In absence of the above cases for allocations, this map uses an index of {@code String} keys to a {@code Long}. The + * first 32 bits of this {@code Long} holds the length of the value and the next 32 bits contain the offset in the + * original buffer. {@link #encodeValueMask(int, long)} encodes this mask and {@link #decodeLengthFromMask(long)}, + * {@link #decodeOffsetFromMask(long)} decodes the mask. + */ +public class CBORMap implements Map { + + protected final DirectBuffer backingBuffer; + protected final int offset; + protected final Map keysVsOffsets; + protected Map storeWhenModified; + + public CBORMap(DirectBuffer backingBuffer, int offset) { + this(backingBuffer, offset, 16, 0.75f); + } + + public CBORMap(DirectBuffer backingBuffer, int offset, int initialCapacity) { + this(backingBuffer, offset, initialCapacity, 0.75f); + } + + public CBORMap(DirectBuffer backingBuffer, int offset, int initialCapacity, float loadFactor) { + this.backingBuffer = backingBuffer; + this.offset = offset; + keysVsOffsets = new HashMap<>(initialCapacity, loadFactor); + } + + protected CBORMap(Map storeWhenModified) { + backingBuffer = new UnsafeBuffer(IndexedUnsafeBuffer.EMPTY_ARRAY); + offset = 0; + this.storeWhenModified = storeWhenModified; + keysVsOffsets = Collections.emptyMap(); + } + + protected CBORMap(DirectBuffer backingBuffer, int offset, Map keysVsOffsets) { + this.backingBuffer = backingBuffer; + this.offset = offset; + this.keysVsOffsets = keysVsOffsets; + storeWhenModified = null; + } + + public Long putValueOffset(String key, int offset, int length) { + return keysVsOffsets.put(key, encodeValueMask(offset, length)); + } + + @Override + public int size() { + return null != storeWhenModified ? storeWhenModified.size() : keysVsOffsets.size(); + } + + @Override + public boolean isEmpty() { + return null != storeWhenModified ? storeWhenModified.isEmpty() : keysVsOffsets.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return null != storeWhenModified ? storeWhenModified.containsKey(key) : keysVsOffsets.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + switchToAlternateMap(); + return storeWhenModified.containsValue(value); + } + + @Override + public ByteBuffer get(Object key) { + return getByteBuffer((String) key); + } + + @Override + public ByteBuffer put(String key, ByteBuffer value) { + if (null != storeWhenModified) { + return storeWhenModified.put(key, value); + } + + switchToAlternateMap(); + return storeWhenModified.put(key, value); + } + + @Override + public ByteBuffer remove(Object key) { + if (null != storeWhenModified) { + return storeWhenModified.remove(key); + } + + Long removed = keysVsOffsets.remove(key); + return getFromBackingBuffer(removed); + } + + @Override + public void putAll(Map m) { + if (null != storeWhenModified) { + storeWhenModified.putAll(m); + } else { + switchToAlternateMap(); + storeWhenModified.putAll(m); + } + } + + @Override + public void clear() { + if (null != storeWhenModified) { + storeWhenModified.clear(); + } else { + keysVsOffsets.clear(); + } + } + + @Override + public Set keySet() { + return null != storeWhenModified ? storeWhenModified.keySet() : keysVsOffsets.keySet(); + } + + @Override + public Collection values() { + switchToAlternateMap(); + return storeWhenModified.values(); + } + + @Override + public Set> entrySet() { + switchToAlternateMap(); + return storeWhenModified.entrySet(); + } + + public void encodeTo(IndexedUnsafeBuffer dst) { + if (null == storeWhenModified) { + final int size = keysVsOffsets.size(); + CborHeader.forLengthToEncode(size).encode(dst, MAP, size); + for (Entry entry : keysVsOffsets.entrySet()) { + CborUtf8StringCodec.encode(dst, entry.getKey()); + + Long valueMask = entry.getValue(); + int valueLength = decodeLengthFromMask(valueMask); + int valueOffset = decodeOffsetFromMask(valueMask); + CborBinaryStringCodec.encode(dst, backingBuffer, valueOffset, valueLength); + } + } else { + CborMapCodec.encode(dst, storeWhenModified); + } + } + + DirectBuffer getBackingBuffer() { + return backingBuffer; + } + + private ByteBuffer getByteBuffer(String key) { + if (null == storeWhenModified) { + Long valueMask = keysVsOffsets.get(key); + return null == valueMask ? null : getFromBackingBuffer(valueMask); + } else { + return storeWhenModified.get(key); + } + } + + private void switchToAlternateMap() { + if (null != storeWhenModified) { + return; + } + storeWhenModified = new HashMap<>(keysVsOffsets.size()); + for (Entry entry : keysVsOffsets.entrySet()) { + storeWhenModified.put(entry.getKey(), getFromBackingBuffer(entry.getValue())); + } + } + + private ByteBuffer getFromBackingBuffer(Long valueMask) { + int offset = this.offset + decodeOffsetFromMask(valueMask); + int length = decodeLengthFromMask(valueMask); + + ByteBuffer bb = backingBuffer.byteBuffer(); + if (null == bb) { + bb = ByteBuffer.wrap(backingBuffer.byteArray()); + } + return ByteBufferUtil.preservingSlice(bb, offset, offset + length); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof CBORMap)) { + return false; + } + + CBORMap that = (CBORMap) o; + + if (offset != that.offset) { + return false; + } + if (backingBuffer != null? !backingBuffer.equals(that.backingBuffer) : that.backingBuffer != null) { + return false; + } + if (keysVsOffsets != null? !keysVsOffsets.equals(that.keysVsOffsets) : that.keysVsOffsets != null) { + return false; + } + if (storeWhenModified != null? !storeWhenModified.equals(that.storeWhenModified) : + that.storeWhenModified != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = backingBuffer != null? backingBuffer.hashCode() : 0; + result = 31 * result + offset; + result = 31 * result + (keysVsOffsets != null? keysVsOffsets.hashCode() : 0); + result = 31 * result + (storeWhenModified != null? storeWhenModified.hashCode() : 0); + return result; + } + + @Override + public String toString() { + String sb = "CBORMap{" + "backingBuffer=" + backingBuffer + + ", offset=" + offset + + ", keysVsOffsets=" + keysVsOffsets + + ", storeWhenModified=" + (null == storeWhenModified ? "null" : storeWhenModified) + + '}'; + return sb; + } + + static long encodeValueMask(int offset, long length) { + return length << 32 | offset & 0xFFFFFFFFL; + } + + static int decodeLengthFromMask(long valueMask) { + return (int) (valueMask >> 32); + } + + static int decodeOffsetFromMask(long valueMask) { + return (int) valueMask; + } +} diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CBORUtils.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CBORUtils.java new file mode 100644 index 000000000..a61b2f320 --- /dev/null +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CBORUtils.java @@ -0,0 +1,125 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal.cbor; + +import io.reactivesocket.mimetypes.internal.MalformedInputException; + +import java.util.function.Function; + +import static io.reactivesocket.mimetypes.internal.cbor.CborMajorType.*; + +public final class CBORUtils { + + public static final StackTraceElement[] EMPTY_STACK = new StackTraceElement[0]; + public static final Function BREAK_SCANNER = aByte -> aByte != CBOR_BREAK; + + @SuppressWarnings("ThrowableInstanceNeverThrown") + private static final MalformedInputException TOO_LONG_LENGTH = + new MalformedInputException("Length of a field is longer than: " + Integer.MAX_VALUE + " bytes."); + + static { + TOO_LONG_LENGTH.setStackTrace(EMPTY_STACK); + } + + private CBORUtils() { + } + + /** + * Parses the passed {@code buffer} and returns the length of the data following the index at the + * {@link IndexedUnsafeBuffer#getReaderIndex()}.

+ * + *

Special cases

+ *
    +
  • If the next data is a "Break" then -1 is + returned.
  • +
+ * + * @param buffer Buffer which will be parsed to determine the length of the next data item. + * @param expectedType {@link CborMajorType} to expect. + * @param errorIfMismatchType Error to throw if the type is not as expected. + * + * @return Length of the following data item. {@code -1} if the type is a Break. + */ + public static long parseDataLengthOrDie(IndexedUnsafeBuffer buffer, CborMajorType expectedType, + RuntimeException errorIfMismatchType) { + final byte header = (byte) buffer.readUnsignedByte(); + final CborMajorType type = fromUnsignedByte(header); + + if (type == Break) { + return -1; + } + + if (type != expectedType) { + throw errorIfMismatchType; + } + + return CborHeader.readDataLength(buffer, header); + } + + /** + * Returns the length in bytes that the passed {@code bytesToEncode} will be when encoded as CBOR. + * + * @param bytesToEncode Length in bytes to encode. + * + * @return Length in bytes post encode. + */ + public static long getEncodeLength(long bytesToEncode) { + CborHeader header = CborHeader.forLengthToEncode(bytesToEncode); + return bytesToEncode + header.getSizeInBytes(); + } + + /** + * Encodes the passed {@code type} with {@code length} as a CBOR data header. The encoding is written on to the + * passed {@code buffer} + * + * @param buffer Buffer to encode to. + * @param type Type to encode. + * @param length Length of data that will be encoded. + * + * @return Number of bytes written on to the buffer for this encoding. + */ + public static int encodeTypeHeader(IndexedUnsafeBuffer buffer, CborMajorType type, long length) { + CborHeader header = CborHeader.forLengthToEncode(length); + header.encode(buffer, type, length); + return header.getSizeInBytes(); + } + + /** + * Encodes the passed {@code type} with indefinite length. The encoding is written on to the passed {@code buffer} + * + * @param buffer Buffer to encode to. + * @param type Type to encode. + * + * @return Number of bytes written on to the buffer for this encoding. + */ + public static int encodeIndefiniteTypeHeader(IndexedUnsafeBuffer buffer, CborMajorType type) { + return encodeTypeHeader(buffer, type, -1); + } + + /** + * Returns the length(in bytes) till the next CBOR break i.e. {@link CborMajorType#CBOR_BREAK}. + * This method does not move the {@code readerIndex} for the passed buffer. + * @param src Buffer to scan. + * + * @return Index of the next CBOR break in the source buffer. {@code -1} if break is not found. + */ + public static int scanToBreak(IndexedUnsafeBuffer src) { + int i = src.forEachByte(BREAK_SCANNER); + return i == src.getBackingBuffer().capacity() ? -1 : i; + } +} diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborBinaryStringCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborBinaryStringCodec.java new file mode 100644 index 000000000..7a2bd8be2 --- /dev/null +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborBinaryStringCodec.java @@ -0,0 +1,85 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal.cbor; + +import io.reactivesocket.mimetypes.internal.MalformedInputException; +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; + +import java.nio.ByteBuffer; + +import static io.reactivesocket.mimetypes.internal.cbor.CBORUtils.*; +import static io.reactivesocket.mimetypes.internal.cbor.CborMajorType.*; + +final class CborBinaryStringCodec { + + @SuppressWarnings("ThrowableInstanceNeverThrown") + static final MalformedInputException NOT_BINARY_STRING = + new MalformedInputException("Data is not a definite length binary string."); + + @SuppressWarnings("ThrowableInstanceNeverThrown") + static final MalformedInputException INDEFINITE_LENGTH_NOT_SUPPORTED = + new MalformedInputException("Indefinite length binary string parsing not supported."); + + static { + NOT_BINARY_STRING.setStackTrace(EMPTY_STACK); + INDEFINITE_LENGTH_NOT_SUPPORTED.setStackTrace(EMPTY_STACK); + } + + private CborBinaryStringCodec() { + } + + public static void encode(IndexedUnsafeBuffer dst, DirectBuffer src, int offset, int length) { + encodeTypeHeader(dst, ByteString, length); + dst.writeBytes(src, offset, length); + } + + public static void encode(IndexedUnsafeBuffer dst, ByteBuffer src) { + encodeTypeHeader(dst, ByteString, src.remaining()); + dst.writeBytes(src, src.remaining()); + } + + public static void decode(IndexedUnsafeBuffer src, IndexedUnsafeBuffer dst) { + int length = decode(src, dst.getBackingBuffer(), 0); + dst.incrementWriterIndex(length); + } + + public static int decode(IndexedUnsafeBuffer src, MutableDirectBuffer dst, int offset) { + int length = (int) parseDataLengthOrDie(src, ByteString, NOT_BINARY_STRING); + if (length < 0) { + throw NOT_BINARY_STRING; + } + + if (length == CborHeader.INDEFINITE.getCode()) { + while (true) { + byte aByte = src.getBackingBuffer().getByte(src.getReaderIndex()); + if (aByte == CBOR_BREAK) { + break; + } + + int chunkLength = (int) parseDataLengthOrDie(src, ByteString, NOT_BINARY_STRING); + src.readBytes(dst, offset, chunkLength); + offset += chunkLength; + } + } else { + src.readBytes(dst, offset, length); + } + + return length; + } +} diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborHeader.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborHeader.java new file mode 100644 index 000000000..da9813634 --- /dev/null +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborHeader.java @@ -0,0 +1,221 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal.cbor; + +import org.agrona.BitUtil; +import rx.functions.Action2; +import rx.functions.Actions; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +/** + * CBOR uses a compact format to encode the length and type of data that is written. More details can be found in the + * spec but it follows the following format: + * + *

First Byte

+ * The first byte of the header has the following data encoded: + *
    +
  • Data type as specified by {@link CborMajorType#getTypeCode()}.
  • +
  • Actual length of the following data element.
  • +
+ * + * The byte layout is as follows: + *
+ 0
+ 0 1 2 3 4 5 6 7
+ +-+-+-+-+-+-+-+
+ | T |  Code   |
+ +-------------+
+ * 
+ * + * <T> above is the Data type.

+ * <Code> above is the actual length for Header {@link #SMALL} and the code {@link #getCode()} for all other + * headers. + * + *

Remaining Bytes

+ * + * Headers {@link #SMALL} and {@link #INDEFINITE} does not contain any other bytes after the first bytes. The other + * headers contain {@link #getSizeInBytes()} {@code - 1} more bytes containing the actual length of the following data. + * + * This class abstracts all the above rules to correctly encode and decode this type headers. + */ +public enum CborHeader { + + INDEFINITE(1, 31, Actions.empty(), + aLong -> aLong < 0, + buffer -> 31L, + aLong -> (byte) 31), + SMALL(1, -1, + Actions.empty(), + aLong -> aLong < 24, buffer -> -1L, + aLong -> aLong.byteValue()), + BYTE(1 + BitUtil.SIZE_OF_BYTE, 24, + (buffer, aLong) -> buffer.writeByte((byte) aLong.shortValue()), + aLong -> aLong <= Byte.MAX_VALUE, + buffer -> (long)buffer.readByte(), aLong -> (byte) 24), + SHORT(1 + BitUtil.SIZE_OF_SHORT, 25, + (buffer, aLong) -> buffer.writeShort(aLong.shortValue()), + aLong -> aLong <= Short.MAX_VALUE, + buffer -> (long)buffer.readShort(), + aLong -> (byte) 25), + INT(1 + BitUtil.SIZE_OF_INT, 26, + (buffer, aLong) -> buffer.writeInt(aLong.intValue()), + aLong -> aLong <= Integer.MAX_VALUE, + buffer -> (long)buffer.readInt(), + aLong -> (byte) 26), + LONG(1 + BitUtil.SIZE_OF_LONG, 27, + (buffer, aLong) -> buffer.writeLong(aLong), + aLong -> aLong <= Long.MAX_VALUE, + buffer -> (long)buffer.readLong(), + aLong -> (byte) 27); + + private static final int LENGTH_MASK = 0b000_11111; + private final static Map reverseIndex; + + static { + reverseIndex = new HashMap<>(CborHeader.values().length); + for (CborHeader h : CborHeader.values()) { + reverseIndex.put(h.code, h); + } + } + + private final short sizeInBytes; + private final int code; + private final Action2 encodeFunction; + private final Function matchFunction; + private final Function decodeFunction; + private final Function codeFunction; + + CborHeader(int sizeInBytes, int code, Action2 encodeFunction, + Function matchFunction, Function decodeFunction, + Function codeFunction) { + this.sizeInBytes = (short) sizeInBytes; + this.code = code; + this.encodeFunction = encodeFunction; + this.matchFunction = matchFunction; + this.decodeFunction = decodeFunction; + this.codeFunction = codeFunction; + } + + + + /** + * Returns {@link CborHeader} instance appropriate for encoding the passed {@code bytesToEncode}. + * + * @param bytesToEncode Number of bytes to encode. + * + * @return {@link CborHeader} appropriate for encoding the passed number of bytes. + */ + public static CborHeader forLengthToEncode(long bytesToEncode) { + if (INDEFINITE.matchFunction.apply(bytesToEncode)) { + return INDEFINITE; + } + + if (SMALL.matchFunction.apply(bytesToEncode)) { + return SMALL; + } + + if (BYTE.matchFunction.apply(bytesToEncode)) { + return BYTE; + } + + if (SHORT.matchFunction.apply(bytesToEncode)) { + return SHORT; + } + + if (INT.matchFunction.apply(bytesToEncode)) { + return INT; + } + + return LONG; + } + + /** + * Returns the number of bytes that this header will encode to. + * + * @return The number of bytes that this header will encode to. + */ + public short getSizeInBytes() { + return sizeInBytes; + } + + /** + * The CBOR code that will be encoded in the first byte of the header. + * {@link CborHeader#SMALL} will return -1 as there is no code for it. Instead it encodes the actual length. + * + * @return The number of bytes that this header will encode to. + */ + public int getCode() { + return code; + } + + /** + * Encodes the passed {@code type} and {@code length} into the passed {@code buffer}. + * + * @param buffer Destination for the encoding. + * @param type Type to encode. + * @param length Length to encode. Can be {@code -1} for {@link #INDEFINITE}, otherwise has to be a positive + * number. + * + * @throws IllegalArgumentException If the length is negative (for all headers except {@link #INDEFINITE}. + */ + public void encode(IndexedUnsafeBuffer buffer, CborMajorType type, long length) { + if (length == -1 && this != INDEFINITE && length < 0) { + throw new IllegalArgumentException("Length must be positive."); + } + + byte code = codeFunction.apply(length); + int firstByte = type.getTypeCode() << 5 | code; + + buffer.writeByte((byte) firstByte); + encodeFunction.call(buffer, length); + } + + /** + * Encodes the passed {@code type} for indefinite length into the passed {@code buffer}. Same as calling + * {@link #encode(IndexedUnsafeBuffer, CborMajorType, long)} with {@code -1} as length. + */ + public void encodeIndefiniteLength(IndexedUnsafeBuffer buffer, CborMajorType type) { + encode(buffer, type, -1); + } + + /** + * Given the first byte of a CBOR data type and length header, returns the length of the data item that follows the + * header. This will read {@link #getSizeInBytes()} number of bytes from the passed buffer corresponding to the + * {@link CborHeader} encoded in the first byte. + */ + public static long readDataLength(IndexedUnsafeBuffer buffer, short firstHeaderByte) { + int firstLength = readLengthFromFirstHeaderByte(firstHeaderByte); + CborHeader cborHeader = reverseIndex.get(firstLength); + if (null != cborHeader) { + return cborHeader.decodeFunction.apply(buffer); + } + + return firstLength; + } + + /** + * Reads the length code from the first byte of CBOR header. This can be the actual length if the header is of type + * {@link #SMALL} or {@link #getCode()} for all other types. + */ + public static int readLengthFromFirstHeaderByte(short firstHeaderByte) { + return firstHeaderByte & LENGTH_MASK; + } +} diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborMajorType.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborMajorType.java new file mode 100644 index 000000000..66342b425 --- /dev/null +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborMajorType.java @@ -0,0 +1,82 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal.cbor; + +import java.util.HashMap; +import java.util.Map; + +/** + * A representation of all supported CBOR major types as defined in the spec. + */ +public enum CborMajorType { + + UnsignedInteger(0), + NegativeInteger(1), + ByteString(2), + Utf8String(3), + ARRAY(4), + MAP(5), + Break(7), + Unknown(-1); + + private final int typeCode; + private final static Map reverseIndex; + + public static final byte CBOR_BREAK = (byte) 0b111_11111; + + static { + reverseIndex = new HashMap<>(CborMajorType.values().length); + for (CborMajorType type : CborMajorType.values()) { + reverseIndex.put(type.typeCode, type); + } + } + + CborMajorType(int typeCode) { + this.typeCode = typeCode; + } + + public int getTypeCode() { + return typeCode; + } + + /** + * Reads the first byte of the CBOR type header ({@link CborHeader}) to determine which type is encoded in the + * header. + * + * @param unsignedByte First byte of the type header. + * + * @return The major type as encoded in the header. + */ + public static CborMajorType fromUnsignedByte(short unsignedByte) { + int type = unsignedByte >> 5 & 0x7; + CborMajorType t = reverseIndex.get(type); + if (null == t) { + return Unknown; + } + + if (t == Break) { + final int length = CborHeader.readLengthFromFirstHeaderByte(unsignedByte); + if (31 == length) { + return Break; + } + return Unknown; + } + + return t; + } +} diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborMapCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborMapCodec.java new file mode 100644 index 000000000..f7aa4fb96 --- /dev/null +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborMapCodec.java @@ -0,0 +1,107 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal.cbor; + +import io.reactivesocket.mimetypes.internal.MalformedInputException; +import org.agrona.DirectBuffer; + +import java.nio.ByteBuffer; +import java.util.Map; +import java.util.Map.Entry; + +import static io.reactivesocket.mimetypes.internal.cbor.CBORUtils.*; +import static io.reactivesocket.mimetypes.internal.cbor.CborMajorType.*; + +final class CborMapCodec { + + @SuppressWarnings("ThrowableInstanceNeverThrown") + private static final MalformedInputException NOT_MAP = new MalformedInputException("Data is not a Map."); + + @SuppressWarnings("ThrowableInstanceNeverThrown") + private static final MalformedInputException VALUE_NOT_BINARY = + new MalformedInputException("Value for a map entry is not binary."); + + static { + NOT_MAP.setStackTrace(EMPTY_STACK); + VALUE_NOT_BINARY.setStackTrace(EMPTY_STACK); + } + + private CborMapCodec() { + } + + public static void encode(IndexedUnsafeBuffer dst, CBORMap cborMap) { + cborMap.encodeTo(dst); + } + + public static void encode(IndexedUnsafeBuffer dst, Map toEncode) { + final int size = toEncode.size(); + CborHeader.forLengthToEncode(size).encode(dst, MAP, size); + for (Entry entry : toEncode.entrySet()) { + CborUtf8StringCodec.encode(dst, entry.getKey()); + + CborBinaryStringCodec.encode(dst, entry.getValue()); + } + } + + public static CBORMap decode(IndexedUnsafeBuffer src, CborMapFactory mapFactory) { + long mapSize = parseDataLengthOrDie(src, MAP, NOT_MAP); + final CBORMap dst = mapFactory.newMap(src.getBackingBuffer(), src.getBackingBufferOffset(), + mapSize == CborHeader.INDEFINITE.getCode() ? 16 : (int) mapSize); + _decode(src, mapSize, dst); + return dst; + } + + public static void decode(IndexedUnsafeBuffer src, CBORMap dst) { + long mapSize = parseDataLengthOrDie(src, MAP, NOT_MAP); + _decode(src, mapSize, dst); + } + + private static void _decode(IndexedUnsafeBuffer src, long mapSize, CBORMap dst) { + boolean isIndefiniteMap = mapSize == CborHeader.INDEFINITE.getCode(); + int i = 0; + while (true) { + String key = CborUtf8StringCodec.decode(src, isIndefiniteMap); + if (null == key) { + break; + } + + int valLength = (int) parseDataLengthOrDie(src, ByteString, VALUE_NOT_BINARY); + int valueOffset = src.getReaderIndex(); + if (valLength < 0) { + throw VALUE_NOT_BINARY; + } + + if (valLength == CborHeader.INDEFINITE.getCode()) { + throw CborBinaryStringCodec.INDEFINITE_LENGTH_NOT_SUPPORTED; + } + dst.putValueOffset(key, valueOffset, valLength); + src.incrementReaderIndex(valLength); + + if (!isIndefiniteMap && ++i >= mapSize) { + break; + } + } + } + + public interface CborMapFactory { + + CBORMap newMap(DirectBuffer backingBuffer, int offset, int initialCapacity); + + } + +} diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborUtf8StringCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborUtf8StringCodec.java new file mode 100644 index 000000000..403c8994f --- /dev/null +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborUtf8StringCodec.java @@ -0,0 +1,89 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal.cbor; + +import io.reactivesocket.mimetypes.internal.MalformedInputException; + +import java.nio.charset.StandardCharsets; + +import static io.reactivesocket.mimetypes.internal.cbor.CBORUtils.*; +import static io.reactivesocket.mimetypes.internal.cbor.CborMajorType.*; + +final class CborUtf8StringCodec { + + @SuppressWarnings("ThrowableInstanceNeverThrown") + static final MalformedInputException NOT_UTF8_STRING = + new MalformedInputException("Data is not a definite length UTF-8 encoded string."); + + @SuppressWarnings("ThrowableInstanceNeverThrown") + static final MalformedInputException BREAK_NOT_FOUND_FOR_INDEFINITE_LENGTH = + new MalformedInputException("End of string not found for indefinite length string."); + + static { + NOT_UTF8_STRING.setStackTrace(EMPTY_STACK); + BREAK_NOT_FOUND_FOR_INDEFINITE_LENGTH.setStackTrace(EMPTY_STACK); + } + + private CborUtf8StringCodec() { + } + + public static void encode(IndexedUnsafeBuffer dst, String utf8String) { + byte[] bytes = utf8String.getBytes(StandardCharsets.UTF_8); + encodeTypeHeader(dst, Utf8String, bytes.length); + dst.writeBytes(bytes, 0, bytes.length); + } + + public static String decode(IndexedUnsafeBuffer src) { + return decode(src, false); + } + + public static String decode(IndexedUnsafeBuffer src, boolean returnNullIfBreak) { + int length = (int) parseDataLengthOrDie(src, Utf8String, NOT_UTF8_STRING); + if (length < 0) { + if (returnNullIfBreak) { + return null; + } else { + throw NOT_UTF8_STRING; + } + } + + if (length == CborHeader.INDEFINITE.getCode()) { + String chunk = null; + while (true) { + byte aByte = src.getBackingBuffer().getByte(src.getReaderIndex()); + if (aByte == CBOR_BREAK) { + break; + } + + int chunkLength = (int) parseDataLengthOrDie(src, Utf8String, NOT_UTF8_STRING); + String thisChunk = readIntoString(src, chunkLength); + chunk = null == chunk ? thisChunk : chunk + thisChunk; + } + + return chunk; + } else { + return readIntoString(src, length); + } + } + + private static String readIntoString(IndexedUnsafeBuffer src, int chunkLength) { + byte[] keyBytes = new byte[chunkLength]; + src.readBytes(keyBytes, 0, keyBytes.length); + return new String(keyBytes, StandardCharsets.UTF_8); + } +} diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/IndexedUnsafeBuffer.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/IndexedUnsafeBuffer.java new file mode 100644 index 000000000..0f1b0a873 --- /dev/null +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/IndexedUnsafeBuffer.java @@ -0,0 +1,201 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal.cbor; + +import org.agrona.BitUtil; +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.function.Function; + +public class IndexedUnsafeBuffer { + + public static final byte[] EMPTY_ARRAY = new byte[0]; + + private int readerIndex; + private int writerIndex; + private int backingBufferOffset; + private final UnsafeBuffer delegate; + private final ByteOrder byteOrder; + + public IndexedUnsafeBuffer(ByteOrder byteOrder) { + this.byteOrder = byteOrder; + delegate = new UnsafeBuffer(EMPTY_ARRAY); + } + + public void wrap(ByteBuffer buffer) { + wrap(buffer, 0, buffer.capacity()); + } + + public void wrap(ByteBuffer buffer, int offset, int length) { + delegate.wrap(buffer, offset, length); + readerIndex = 0; + writerIndex = 0; + backingBufferOffset = offset; + } + + public void wrap(DirectBuffer buffer) { + wrap(buffer, 0, buffer.capacity()); + } + + public void wrap(DirectBuffer buffer, int offset, int length) { + delegate.wrap(buffer, offset, length); + readerIndex = 0; + writerIndex = 0; + backingBufferOffset = offset; + } + + public short readUnsignedByte() { + return (short) (delegate.getByte(readerIndex++) & 0xff); + } + + public byte readByte() { + return delegate.getByte(readerIndex++); + } + + public int readShort() { + short s = delegate.getShort(readerIndex, byteOrder); + readerIndex += BitUtil.SIZE_OF_SHORT; + return s; + } + + public int readInt() { + int i = delegate.getInt(readerIndex, byteOrder); + readerIndex += BitUtil.SIZE_OF_INT; + return i; + } + + public long readLong() { + long l = delegate.getLong(readerIndex, byteOrder); + readerIndex += BitUtil.SIZE_OF_LONG; + return l; + } + + public void readBytes(byte[] dst, int dstOffset, int length) { + delegate.getBytes(readerIndex, dst, dstOffset, length); + readerIndex += length - dstOffset; + } + + public void readBytes(MutableDirectBuffer dst, int offset, int length) { + delegate.getBytes(readerIndex, dst, offset, length); + readerIndex += length; + } + + public void readBytes(IndexedUnsafeBuffer dst, int length) { + delegate.getBytes(readerIndex, dst.getBackingBuffer(), dst.getWriterIndex(), length); + readerIndex += length; + } + + public void writeByte(byte toWrite) { + delegate.putByte(writerIndex++, toWrite); + } + + public void writeShort(short toWrite) { + delegate.putShort(writerIndex, toWrite, byteOrder); + writerIndex += BitUtil.SIZE_OF_SHORT; + } + + public void writeInt(int toWrite) { + delegate.putInt(writerIndex, toWrite, byteOrder); + writerIndex += BitUtil.SIZE_OF_INT; + } + + public void writeLong(long toWrite) { + delegate.putLong(writerIndex, toWrite, byteOrder); + writerIndex += BitUtil.SIZE_OF_LONG; + } + + public void writeBytes(byte[] src, int offset, int length) { + delegate.putBytes(writerIndex, src, offset, length); + writerIndex += length; + } + + public void writeBytes(ByteBuffer src, int length) { + delegate.putBytes(writerIndex, src, src.position(), length); + writerIndex += length; + } + + public void writeBytes(DirectBuffer src, int offset, int length) { + delegate.putBytes(writerIndex, src, offset, length); + writerIndex += length; + } + + public int getReaderIndex() { + return readerIndex; + } + + public int getWriterIndex() { + return writerIndex; + } + + public UnsafeBuffer getBackingBuffer() { + return delegate; + } + + public int getBackingBufferOffset() { + return backingBufferOffset; + } + + public void incrementReaderIndex(int increment) { + readerIndex += increment; + } + + public void setReaderIndex(int readerIndex) { + if (readerIndex >= delegate.capacity()) { + throw new IllegalArgumentException( + String.format("Reader Index should be less than capacity. Reader Index: %d, Capacity: %d", + readerIndex, delegate.capacity())); + } + this.readerIndex = readerIndex; + } + + public void setWriterIndex(int writerIndex) { + if (writerIndex >= delegate.capacity()) { + throw new IllegalArgumentException( + String.format("Writer Index should be less than capacity. Writer Index: %d, Capacity: %d", + writerIndex, delegate.capacity())); + } + this.writerIndex = writerIndex; + } + + public void incrementWriterIndex(int increment) { + writerIndex += increment; + } + + /** + * Scans this buffer and invokes the passed {@code scanner} for every byte. The scan stops if it has reached the end + * of buffer or the {@code scanner} returns {@code false}. This method does not move the {@code readerIndex} for + * this buffer. + * + * @param scanner Scanner that determines to scan further for every byte. + * + * @return Index in the buffer at which this scan stopped. + */ + public int forEachByte(Function scanner) { + int i; + for (i = readerIndex; i < delegate.capacity(); i++) { + if (!scanner.apply(delegate.getByte(i))) { + break; + } + } + return i; + } +} diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/MetadataCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/MetadataCodec.java new file mode 100644 index 000000000..1e4ae7405 --- /dev/null +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/MetadataCodec.java @@ -0,0 +1,158 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal.cbor; + +import io.reactivesocket.mimetypes.KVMetadata; +import io.reactivesocket.mimetypes.internal.Codec; +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Map.Entry; + +import static io.reactivesocket.mimetypes.internal.cbor.CBORUtils.*; + +/** + * This is a custom codec for default {@link KVMetadata} as defined by + * the spec. Since, the format + * is simple, it does not use a third-party library to do the encoding, but the logic is contained in this class. + */ +public class MetadataCodec implements Codec { + + public static final MetadataCodec INSTANCE = new MetadataCodec(); + + private static final ThreadLocal indexedBuffers = + ThreadLocal.withInitial(() -> new IndexedUnsafeBuffer(ByteOrder.BIG_ENDIAN)); + + protected MetadataCodec() { + } + + @Override + public T decode(ByteBuffer buffer, Class tClass) { + isValidType(tClass); + + IndexedUnsafeBuffer tmp = indexedBuffers.get(); + tmp.wrap(buffer); + + return _decode(tmp); + } + + @Override + public T decode(DirectBuffer buffer, int offset, Class tClass) { + isValidType(tClass); + + IndexedUnsafeBuffer tmp = indexedBuffers.get(); + tmp.wrap(buffer); + + return _decode(tmp); + } + + @Override + public ByteBuffer encode(T toEncode) { + isValidType(toEncode.getClass()); + + if (toEncode instanceof SlicedBufferKVMetadata) { + return ((SlicedBufferKVMetadata) toEncode).getBackingBuffer().byteBuffer(); + } + + ByteBuffer dst = ByteBuffer.allocate(getSizeAsBytes((KVMetadata) toEncode)); + encodeTo(dst, toEncode); + return dst; + } + + @Override + public DirectBuffer encodeDirect(T toEncode) { + isValidType(toEncode.getClass()); + + final KVMetadata input = (KVMetadata) toEncode; + + if (toEncode instanceof SlicedBufferKVMetadata) { + return ((SlicedBufferKVMetadata) toEncode).getBackingBuffer(); + } + + MutableDirectBuffer toReturn = new UnsafeBuffer(ByteBuffer.allocate(getSizeAsBytes(input))); + encodeTo(toReturn, toEncode, 0); + return toReturn; + } + + @Override + public void encodeTo(ByteBuffer buffer, T toEncode) { + isValidType(toEncode.getClass()); + + final KVMetadata input = (KVMetadata) toEncode; + + IndexedUnsafeBuffer tmp = indexedBuffers.get(); + tmp.wrap(buffer); + + _encodeTo(tmp, input); + } + + @Override + public void encodeTo(MutableDirectBuffer buffer, T toEncode, int offset) { + isValidType(toEncode.getClass()); + + final KVMetadata input = (KVMetadata) toEncode; + + IndexedUnsafeBuffer tmp = indexedBuffers.get(); + tmp.wrap(buffer, offset, buffer.capacity()); + + _encodeTo(tmp, input); + } + + private static void _encodeTo(IndexedUnsafeBuffer buffer, KVMetadata toEncode) { + if (toEncode instanceof SlicedBufferKVMetadata) { + SlicedBufferKVMetadata s = (SlicedBufferKVMetadata) toEncode; + DirectBuffer backingBuffer = s.getBackingBuffer(); + backingBuffer.getBytes(0, buffer.getBackingBuffer(), buffer.getWriterIndex(), backingBuffer.capacity()); + return; + } + + CborMapCodec.encode(buffer, toEncode); + } + + private static T _decode(IndexedUnsafeBuffer src) { + CBORMap m = CborMapCodec.decode(src, + (backingBuffer, offset, initialCapacity) -> new SlicedBufferKVMetadata( + backingBuffer, offset, initialCapacity)); + @SuppressWarnings("unchecked") + T t = (T) m; + return t; + } + + private static int getSizeAsBytes(KVMetadata toEncode) { + int toReturn = 1 + (int) getEncodeLength(toEncode.size()); // Map Starting + break + for (Entry entry : toEncode.entrySet()) { + toReturn += getEncodeLength(entry.getKey().length()); + toReturn += entry.getKey().length(); + + int valueLength = entry.getValue().remaining(); + toReturn += getEncodeLength(valueLength); + toReturn += valueLength; + } + return toReturn; + } + + private static void isValidType(Class tClass) { + if (!KVMetadata.class.isAssignableFrom(tClass)) { + throw new IllegalArgumentException("Metadata codec only supports encoding/decoding of: " + + KVMetadata.class.getName()); + } + } +} diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/ReactiveSocketDefaultMetadataCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/ReactiveSocketDefaultMetadataCodec.java new file mode 100644 index 000000000..8d15c1cf0 --- /dev/null +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/ReactiveSocketDefaultMetadataCodec.java @@ -0,0 +1,104 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal.cbor; + +import io.reactivesocket.mimetypes.KVMetadata; +import io.reactivesocket.mimetypes.internal.Codec; +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; + +import java.nio.ByteBuffer; + +public class ReactiveSocketDefaultMetadataCodec implements Codec { + + private final CborCodec cborCodec; + + private ReactiveSocketDefaultMetadataCodec(CborCodec cborCodec) { + this.cborCodec = cborCodec; + } + + public KVMetadata decodeDefault(ByteBuffer buffer) { + return MetadataCodec.INSTANCE.decode(buffer, SlicedBufferKVMetadata.class); + } + + public KVMetadata decodeDefault(DirectBuffer buffer, int offset) { + return MetadataCodec.INSTANCE.decode(buffer, offset, SlicedBufferKVMetadata.class); + } + + @Override + public T decode(ByteBuffer buffer, Class tClass) { + if (KVMetadata.class.isAssignableFrom(tClass)) { + @SuppressWarnings("unchecked") + T t = (T) decodeDefault(buffer); + return t; + } + return cborCodec.decode(buffer, tClass); + } + + @Override + public T decode(DirectBuffer buffer, int offset, Class tClass) { + if (KVMetadata.class.isAssignableFrom(tClass)) { + @SuppressWarnings("unchecked") + T t = (T) decodeDefault(buffer, offset); + return t; + } + return cborCodec.decode(buffer, offset, tClass); + } + + @Override + public ByteBuffer encode(T toEncode) { + if (KVMetadata.class.isAssignableFrom(toEncode.getClass())) { + return MetadataCodec.INSTANCE.encode(toEncode); + } + return cborCodec.encode(toEncode); + } + + @Override + public DirectBuffer encodeDirect(T toEncode) { + if (KVMetadata.class.isAssignableFrom(toEncode.getClass())) { + return MetadataCodec.INSTANCE.encodeDirect(toEncode); + } + return cborCodec.encodeDirect(toEncode); + } + + @Override + public void encodeTo(ByteBuffer buffer, T toEncode) { + if (KVMetadata.class.isAssignableFrom(toEncode.getClass())) { + MetadataCodec.INSTANCE.encodeTo(buffer, toEncode); + } else { + cborCodec.encodeTo(buffer, toEncode); + } + } + + @Override + public void encodeTo(MutableDirectBuffer buffer, T toEncode, int offset) { + if (KVMetadata.class.isAssignableFrom(toEncode.getClass())) { + MetadataCodec.INSTANCE.encodeTo(buffer, toEncode, offset); + } else { + cborCodec.encodeTo(buffer, toEncode, offset); + } + } + + public static ReactiveSocketDefaultMetadataCodec create() { + return new ReactiveSocketDefaultMetadataCodec(CborCodec.create()); + } + + public static ReactiveSocketDefaultMetadataCodec create(CborCodec cborCodec) { + return new ReactiveSocketDefaultMetadataCodec(cborCodec); + } +} diff --git a/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/SlicedBufferKVMetadata.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/SlicedBufferKVMetadata.java new file mode 100644 index 000000000..c6ab714d3 --- /dev/null +++ b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/SlicedBufferKVMetadata.java @@ -0,0 +1,84 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal.cbor; + +import io.reactivesocket.mimetypes.KVMetadata; +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +/** + * An implementation of {@link KVMetadata} that does not allocate buffers for values of metadata, instead it keeps a + * view of the original buffer with offsets to the values. See {@link CBORMap} to learn more about the structure and + * cases when this creates allocations. + * + *

Lifecycle

+ * + * This assumes exclusive access to the underlying buffer. If that is not the case, then {@link #duplicate(Function)} + * must be used to create a copy of the underlying buffer. + * + * @see CBORMap + */ +public class SlicedBufferKVMetadata extends CBORMap implements KVMetadata { + + public SlicedBufferKVMetadata(DirectBuffer backingBuffer, int offset, int initialCapacity) { + super(backingBuffer, offset, initialCapacity); + } + + public SlicedBufferKVMetadata(DirectBuffer backingBuffer, int offset) { + super(backingBuffer, offset); + } + + protected SlicedBufferKVMetadata(Map storeWhenModified) { + super(storeWhenModified); + } + + protected SlicedBufferKVMetadata(DirectBuffer backingBuffer, int offset, Map keysVsOffsets) { + super(backingBuffer, offset, keysVsOffsets); + } + + @Override + public String getAsString(String key, Charset valueEncoding) { + ByteBuffer v = get(key); + byte[] vBytes; + if (v.hasArray()) { + return new String(v.array(), v.arrayOffset(), v.limit(), valueEncoding); + } else { + vBytes = new byte[v.remaining()]; + v.get(vBytes); + return new String(vBytes, valueEncoding); + } + } + + @Override + public KVMetadata duplicate(Function newBufferFactory) { + if (null == storeWhenModified) { + int newCap = backingBuffer.capacity(); + MutableDirectBuffer newBuffer = newBufferFactory.apply(newCap); + backingBuffer.getBytes(0, newBuffer, 0, newCap); + return new SlicedBufferKVMetadata(newBuffer, 0, new HashMap<>(keysVsOffsets)); + } else { + return new SlicedBufferKVMetadata(storeWhenModified); + } + } +} diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/MimeTypeFactoryTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/MimeTypeFactoryTest.java index cdee4bbeb..19771642d 100644 --- a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/MimeTypeFactoryTest.java +++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/MimeTypeFactoryTest.java @@ -23,8 +23,8 @@ import io.reactivesocket.mimetypes.internal.CustomObjectRule; import io.reactivesocket.mimetypes.internal.KVMetadataImpl; import io.reactivesocket.mimetypes.internal.MetadataRule; -import io.reactivesocket.mimetypes.internal.ReactiveSocketDefaultMetadataCodec; import io.reactivesocket.mimetypes.internal.cbor.CborCodec; +import io.reactivesocket.mimetypes.internal.cbor.ReactiveSocketDefaultMetadataCodec; import org.agrona.DirectBuffer; import org.agrona.MutableDirectBuffer; import org.agrona.concurrent.UnsafeBuffer; @@ -32,13 +32,11 @@ import org.hamcrest.Matchers; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExternalResource; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; import java.nio.ByteBuffer; import static io.reactivesocket.mimetypes.SupportedMimeTypes.*; +import static io.reactivesocket.mimetypes.internal.cbor.ByteBufferMapMatcher.*; public class MimeTypeFactoryTest { @@ -82,8 +80,8 @@ public void testDifferentMimetypes() throws Exception { MimeType mimeType = MimeTypeFactory.from(ReactiveSocketDefaultMetadata, CBOR); - testMetadataCodec(mimeType, CborCodec.create()); - testDataCodec(mimeType, CborCodec.create()); + testMetadataCodec(mimeType, ReactiveSocketDefaultMetadataCodec.create()); + testDataCodec(mimeType, ReactiveSocketDefaultMetadataCodec.create()); } private void testMetadataCodec(MimeType mimeType, Codec expectedCodec) { @@ -92,7 +90,7 @@ private void testMetadataCodec(MimeType mimeType, Codec expectedCodec) { MatcherAssert.assertThat("Unexpected encode from mime type.", encode, Matchers.equalTo(encode1)); MatcherAssert.assertThat("Unexpected decode from encode.", metadataRule.getKvMetadata(), - Matchers.equalTo(mimeType.decodeMetadata(encode, KVMetadataImpl.class))); + mapEqualTo(mimeType.decodeMetadata(encode, KVMetadataImpl.class))); DirectBuffer dencode = mimeType.encodeMetadataDirect(metadataRule.getKvMetadata()); @@ -100,7 +98,7 @@ private void testMetadataCodec(MimeType mimeType, Codec expectedCodec) { MatcherAssert.assertThat("Unexpected direct buffer encode from mime type.", dencode, Matchers.equalTo(dencode1)); MatcherAssert.assertThat("Unexpected decode from direct encode.", metadataRule.getKvMetadata(), - Matchers.equalTo(mimeType.decodeMetadata(dencode, KVMetadataImpl.class))); + mapEqualTo(mimeType.decodeMetadata(dencode, KVMetadataImpl.class, 0))); ByteBuffer dst = ByteBuffer.allocate(100); ByteBuffer dst1 = ByteBuffer.allocate(100); @@ -112,17 +110,17 @@ private void testMetadataCodec(MimeType mimeType, Codec expectedCodec) { MatcherAssert.assertThat("Unexpected encodeTo buffer encode from mime type.", dst, Matchers.equalTo(dst1)); MatcherAssert.assertThat("Unexpected decode from encodeTo encode.", metadataRule.getKvMetadata(), - Matchers.equalTo(mimeType.decodeMetadata(dst, KVMetadataImpl.class))); + mapEqualTo(mimeType.decodeMetadata(dst, KVMetadataImpl.class))); MutableDirectBuffer mdst = new UnsafeBuffer(new byte[100]); MutableDirectBuffer mdst1 = new UnsafeBuffer(new byte[100]); - mimeType.encodeMetadataTo(mdst, metadataRule.getKvMetadata()); - expectedCodec.encodeTo(mdst1, metadataRule.getKvMetadata()); + mimeType.encodeMetadataTo(mdst, metadataRule.getKvMetadata(), 0); + expectedCodec.encodeTo(mdst1, metadataRule.getKvMetadata(), 0); MatcherAssert.assertThat("Unexpected encodeTo buffer encode from mime type.", mdst, Matchers.equalTo(mdst1)); MatcherAssert.assertThat("Unexpected decode from encodeTo encode.", metadataRule.getKvMetadata(), - Matchers.equalTo(mimeType.decodeMetadata(mdst, KVMetadataImpl.class))); + mapEqualTo(mimeType.decodeMetadata(mdst, KVMetadataImpl.class, 0))); } private void testDataCodec(MimeType mimeType, Codec expectedCodec) { @@ -139,7 +137,7 @@ private void testDataCodec(MimeType mimeType, Codec expectedCodec) { MatcherAssert.assertThat("Unexpected direct buffer encode from mime type.", dencode, Matchers.equalTo(dencode1)); MatcherAssert.assertThat("Unexpected decode from direct encode.", objectRule.getData(), - Matchers.equalTo(mimeType.decodeData(dencode, CustomObject.class))); + Matchers.equalTo(mimeType.decodeData(dencode, CustomObject.class, 0))); ByteBuffer dst = ByteBuffer.allocate(100); ByteBuffer dst1 = ByteBuffer.allocate(100); @@ -156,17 +154,17 @@ private void testDataCodec(MimeType mimeType, Codec expectedCodec) { MutableDirectBuffer mdst = new UnsafeBuffer(new byte[100]); MutableDirectBuffer mdst1 = new UnsafeBuffer(new byte[100]); - mimeType.encodeDataTo(mdst, objectRule.getData()); - expectedCodec.encodeTo(mdst1, objectRule.getData()); + mimeType.encodeDataTo(mdst, objectRule.getData(), 0); + expectedCodec.encodeTo(mdst1, objectRule.getData(), 0); MatcherAssert.assertThat("Unexpected encodeTo buffer encode from mime type.", mdst, Matchers.equalTo(mdst1)); MatcherAssert.assertThat("Unexpected decode from encodeTo encode.", objectRule.getData(), - Matchers.equalTo(mimeType.decodeData(mdst, CustomObject.class))); + Matchers.equalTo(mimeType.decodeData(mdst, CustomObject.class, 0))); } private static MimeType getMimeTypeFromSetup(SupportedMimeTypes metaMime, SupportedMimeTypes dataMime) { - ConnectionSetupPayload setup = new ConnectionSetupPayloadImpl(metaMime.getMimeTypes().get(0), - dataMime.getMimeTypes().get(0)); + ConnectionSetupPayload setup = new ConnectionSetupPayloadImpl(dataMime.getMimeTypes().get(0), + metaMime.getMimeTypes().get(0)); return MimeTypeFactory.from(setup); } diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodecTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodecTest.java index b211225a0..c1dd271e7 100644 --- a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodecTest.java +++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodecTest.java @@ -48,7 +48,7 @@ public void encodeDecodeDirect() throws Exception { customObjectRule.populateDefaultData(); DirectBuffer encode = getCodecRule().getCodec().encodeDirect(customObjectRule.getData()); - CustomObject decode = getCodecRule().getCodec().decode(encode, CustomObject.class); + CustomObject decode = getCodecRule().getCodec().decode(encode, 0, CustomObject.class); MatcherAssert.assertThat("Unexpected decode.", decode, Matchers.equalTo(customObjectRule.getData())); } @@ -70,9 +70,9 @@ public void encodeToDirect() throws Exception { customObjectRule.populateDefaultData(); byte[] destArr = new byte[1000]; MutableDirectBuffer encodeDest = new UnsafeBuffer(destArr); - getCodecRule().getCodec().encodeTo(encodeDest, customObjectRule.getData()); + getCodecRule().getCodec().encodeTo(encodeDest, customObjectRule.getData(), 0); - CustomObject decode = getCodecRule().getCodec().decode(encodeDest, CustomObject.class); + CustomObject decode = getCodecRule().getCodec().decode(encodeDest, 0, CustomObject.class); MatcherAssert.assertThat("Unexpected decode.", decode, Matchers.equalTo(customObjectRule.getData())); } diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/ReactiveSocketDefaultMetadataCodecTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/ReactiveSocketDefaultMetadataCodecTest.java index c0c8617da..25bf65383 100644 --- a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/ReactiveSocketDefaultMetadataCodecTest.java +++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/ReactiveSocketDefaultMetadataCodecTest.java @@ -18,6 +18,7 @@ package io.reactivesocket.mimetypes.internal; import io.reactivesocket.mimetypes.KVMetadata; +import io.reactivesocket.mimetypes.internal.cbor.ReactiveSocketDefaultMetadataCodec; import org.agrona.DirectBuffer; import org.hamcrest.MatcherAssert; import org.junit.Rule; @@ -25,7 +26,7 @@ import java.nio.ByteBuffer; -import static org.hamcrest.Matchers.*; +import static io.reactivesocket.mimetypes.internal.cbor.ByteBufferMapMatcher.*; public class ReactiveSocketDefaultMetadataCodecTest { @@ -43,7 +44,7 @@ public void testDecodeDefault() throws Exception { ByteBuffer encode = codecRule.getCodec().encode(metadataRule.getKvMetadata()); KVMetadata kvMetadata = codecRule.getCodec().decodeDefault(encode); - MatcherAssert.assertThat("Unexpected decoded metadata.", kvMetadata, equalTo(metadataRule.getKvMetadata())); + MatcherAssert.assertThat("Unexpected decoded metadata.", kvMetadata, mapEqualTo(metadataRule.getKvMetadata())); } @Test(timeout = 60000) @@ -52,9 +53,9 @@ public void testDecodeDefaultDirect() throws Exception { metadataRule.populateDefaultMetadataData(); DirectBuffer encode = codecRule.getCodec().encodeDirect(metadataRule.getKvMetadata()); - KVMetadata kvMetadata = codecRule.getCodec().decodeDefault(encode); + KVMetadata kvMetadata = codecRule.getCodec().decodeDefault(encode, 0); - MatcherAssert.assertThat("Unexpected decoded metadata.", kvMetadata, equalTo(metadataRule.getKvMetadata())); + MatcherAssert.assertThat("Unexpected decoded metadata.", kvMetadata, mapEqualTo(metadataRule.getKvMetadata())); } } \ No newline at end of file diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/AbstractCborMapRule.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/AbstractCborMapRule.java new file mode 100644 index 000000000..69bc95b5a --- /dev/null +++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/AbstractCborMapRule.java @@ -0,0 +1,87 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal.cbor; + +import org.junit.rules.ExternalResource; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; + +public abstract class AbstractCborMapRule extends ExternalResource { + + protected T map; + protected ByteBuffer valueBuffer; + protected IndexedUnsafeBuffer indexed; + + @Override + public Statement apply(final Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + init(); + base.evaluate(); + } + }; + } + + protected abstract void init(); + + public void addMockEntries(int count) { + for (int i =0; i < count; i++) { + addEntry(getMockKey(i), getMockValue(i)); + } + } + + public void addEntry(String utf8Key, String value) { + ByteBuffer vBuf = toValueBuffer(value); + int valueLength = vBuf.remaining(); + int offset = indexed.getWriterIndex(); + indexed.writeBytes(vBuf, valueLength); + map.putValueOffset(utf8Key, offset, valueLength); + } + + public String getMockKey(int index) { + return "Key" + index; + } + + public String getMockValue(int index) { + return "Value" + (index + 10); + } + + public ByteBuffer getMockValueAsBuffer(int index) { + String mockValue = getMockValue(index); + return toValueBuffer(mockValue); + } + + public ByteBuffer toValueBuffer(String value) { + byte[] vBytes = value.getBytes(StandardCharsets.UTF_8); + return ByteBuffer.wrap(vBytes); + } + + public void assertValueForKey(int index) { + String k = getMockKey(index); + ByteBuffer vBuf = map.get(k); + + assertThat("Unexpected lookup value for key: " + k, vBuf, equalTo(getMockValueAsBuffer(index))); + } +} diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/ByteBufferMapMatcher.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/ByteBufferMapMatcher.java new file mode 100644 index 000000000..967992b02 --- /dev/null +++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/ByteBufferMapMatcher.java @@ -0,0 +1,61 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal.cbor; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.mockito.ArgumentMatcher; + +import java.nio.ByteBuffer; +import java.util.Map; +import java.util.Map.Entry; + +public final class ByteBufferMapMatcher { + + private ByteBufferMapMatcher() { + } + + public static Matcher> mapEqualTo(Map toCheck) { + return new ArgumentMatcher>() { + + @Override + public boolean matches(Object argument) { + if (argument instanceof Map) { + @SuppressWarnings("unchecked") + Map arg = (Map) argument; + if (arg.size() == toCheck.size()) { + for (Entry e : arg.entrySet()) { + ByteBuffer v = toCheck.get(e.getKey()); + v.rewind(); + if (null == v || !e.getValue().equals(v)) { + return false; + } + } + return true; + } + } + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText("Map Equals " + toCheck); + } + }; + } +} diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORMapTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORMapTest.java new file mode 100644 index 000000000..5aef94e37 --- /dev/null +++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORMapTest.java @@ -0,0 +1,168 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal.cbor; + +import org.agrona.concurrent.UnsafeBuffer; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.Collection; + +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; + +@RunWith(Parameterized.class) +public class CBORMapTest { + + @Parameters + public static Collection data() { + return Arrays.asList(new Integer[][] { + {100, 0, 100, 5}, {400, 20, 200, 20}, {400, 20, 140, 20} + }); + } + + @Parameter + public int bufferSize; + @Parameter(1) + public int bufferOffset; + @Parameter(2) + public int bufferLength; + @Parameter(3) + public int entrySize; + + @Rule + public final CborMapRule mapRule = new CborMapRule(); + + @Test(timeout = 60000) + public void testGet() throws Exception { + mapRule.addMockEntries(entrySize); + for (int i = 0; i < entrySize; i++) { + mapRule.assertValueForKey(i); + } + } + + @Test(timeout = 60000) + public void testGetWithArrayWrappedBuffer() throws Exception { + mapRule.initWithArray(); + mapRule.addMockEntries(entrySize); + for (int i = 0; i < entrySize; i++) { + mapRule.assertValueForKey(i); + } + } + + @Test(timeout = 60000) + public void testContainsKey() throws Exception { + mapRule.addMockEntries(entrySize); + for (int i = 0; i < entrySize; i++) { + String k = mapRule.getMockKey(i); + assertThat("Key: " + k + " not found.", mapRule.map.containsKey(k), is(true)); + } + } + + @Test(timeout = 60000) + public void testNonExistentContainsKey() throws Exception { + mapRule.addMockEntries(entrySize); + String k = "dummy"; + assertThat("Key: " + k + " not found.", mapRule.map.containsKey(k), is(false)); + } + + @Test(timeout = 60000) + public void testSize() throws Exception { + mapRule.addMockEntries(entrySize); + assertThat("Unexpected size.", mapRule.map.size(), is(entrySize)); + } + + @Test(timeout = 60000) + public void testIsEmpty() throws Exception { + mapRule.addMockEntries(entrySize); + assertThat("isEmpty?.", mapRule.map.isEmpty(), is(false)); + } + + @Test(timeout = 60000) + public void testIsEmptyWithEmpty() throws Exception { + assertThat("isEmpty?.", mapRule.map.isEmpty(), is(true)); + } + + @Test(timeout = 60000) + public void testContainsValue() throws Exception { + mapRule.addMockEntries(entrySize); + for (int i = 0; i < entrySize; i++) { + String v = mapRule.getMockValue(i); + ByteBuffer vBuf = mapRule.getMockValueAsBuffer(i); + assertThat("Value: " + v + " not found.", mapRule.map.containsValue(vBuf), is(true)); + } + } + + @Test(timeout = 60000) + public void testNonExistentValue() throws Exception { + mapRule.addMockEntries(entrySize); + ByteBuffer vBuf = mapRule.toValueBuffer("dummy"); + assertThat("Unexpected value found.", mapRule.map.containsValue(vBuf), is(false)); + } + + @Test(timeout = 60000) + public void testPut() throws Exception { + mapRule.addMockEntries(entrySize); + String addnKey1 = "AddnKey1"; + ByteBuffer addnValue1 = mapRule.toValueBuffer("AddnValue1"); + mapRule.map.put(addnKey1, addnValue1); + for (int i = 0; i < entrySize; i++) { + mapRule.assertValueForKey(i); + } + ByteBuffer vBuf = mapRule.map.get(addnKey1); + + assertThat("Unexpected lookup value for key: " + addnKey1, vBuf, equalTo(addnValue1)); + } + + @Test(timeout = 60000) + public void testRemove() throws Exception { + mapRule.addMockEntries(entrySize); + int indexToRemove = 0 == entrySize? 0 : entrySize - 1; + String keyToRemove = mapRule.getMockKey(indexToRemove); + ByteBuffer removed = mapRule.map.remove(keyToRemove); + + assertThat("Unexpected value removed", removed, equalTo(mapRule.getMockValueAsBuffer(indexToRemove))); + assertThat("Value not removed from map.", mapRule.map.get(keyToRemove), is(nullValue())); + } + + public class CborMapRule extends AbstractCborMapRule { + + @Override + protected void init() { + valueBuffer = ByteBuffer.allocate(bufferSize); + indexed = new IndexedUnsafeBuffer(ByteOrder.BIG_ENDIAN); + indexed.wrap(valueBuffer, bufferOffset, bufferLength); + map = new CBORMap(indexed.getBackingBuffer(), bufferOffset); + } + + protected void initWithArray() { + byte[] src = new byte[bufferSize]; + UnsafeBuffer unsafeBuffer = new UnsafeBuffer(src, bufferOffset, bufferLength); + indexed = new IndexedUnsafeBuffer(ByteOrder.BIG_ENDIAN); + indexed.wrap(unsafeBuffer); + map = new CBORMap(indexed.getBackingBuffer(), bufferOffset); + } + } +} \ No newline at end of file diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORMapValueMaskTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORMapValueMaskTest.java new file mode 100644 index 000000000..daf65c162 --- /dev/null +++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORMapValueMaskTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal.cbor; + +import org.hamcrest.MatcherAssert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import java.util.Arrays; +import java.util.Collection; + +import static org.hamcrest.Matchers.*; + +@RunWith(Parameterized.class) +public class CBORMapValueMaskTest { + + @Parameters + public static Collection data() { + return Arrays.asList(new Integer[][] { + {0, 0}, {20, 6}, {100, Integer.MAX_VALUE} + }); + } + + @Parameter + public int offset; + @Parameter(1) + public int length; + + @Test(timeout = 60000) + public void testMask() throws Exception { + long mask = CBORMap.encodeValueMask(offset, length); + int offset = CBORMap.decodeOffsetFromMask(mask); + int length = CBORMap.decodeLengthFromMask(mask); + + MatcherAssert.assertThat("Unexpected offset post decode.", offset, is(this.offset)); + MatcherAssert.assertThat("Unexpected length post decode.", length, is(this.length)); + } +} \ No newline at end of file diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORUtilsTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORUtilsTest.java new file mode 100644 index 000000000..fa00affd1 --- /dev/null +++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORUtilsTest.java @@ -0,0 +1,154 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal.cbor; + +import org.agrona.BitUtil; +import org.hamcrest.MatcherAssert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.Collection; + +import static org.hamcrest.Matchers.*; + +@RunWith(Parameterized.class) +public class CBORUtilsTest { + + @Parameters + public static Collection data() { + return Arrays.asList(new Object[][] { + {CborMajorType.UnsignedInteger, 22}, + {CborMajorType.UnsignedInteger, -1}, + {CborMajorType.UnsignedInteger, 0}, + {CborMajorType.UnsignedInteger, Byte.MAX_VALUE}, + {CborMajorType.UnsignedInteger, Short.MAX_VALUE}, + {CborMajorType.UnsignedInteger, Integer.MAX_VALUE}, + + {CborMajorType.NegativeInteger, 2}, + {CborMajorType.NegativeInteger, -1}, + {CborMajorType.NegativeInteger, 0}, + {CborMajorType.NegativeInteger, Byte.MAX_VALUE}, + {CborMajorType.NegativeInteger, Short.MAX_VALUE}, + {CborMajorType.NegativeInteger, Integer.MAX_VALUE}, + + {CborMajorType.Utf8String, 2}, + {CborMajorType.Utf8String, -1}, + {CborMajorType.Utf8String, 0}, + {CborMajorType.Utf8String, Byte.MAX_VALUE}, + {CborMajorType.Utf8String, Short.MAX_VALUE}, + {CborMajorType.Utf8String, Integer.MAX_VALUE}, + {CborMajorType.Utf8String, Long.MAX_VALUE}, + + {CborMajorType.ByteString, 2}, + {CborMajorType.ByteString, -1}, + {CborMajorType.ByteString, 0}, + {CborMajorType.ByteString, Byte.MAX_VALUE}, + {CborMajorType.ByteString, Short.MAX_VALUE}, + {CborMajorType.ByteString, Integer.MAX_VALUE}, + {CborMajorType.ByteString, Long.MAX_VALUE}, + + {CborMajorType.MAP, 2}, + {CborMajorType.MAP, -1}, + {CborMajorType.MAP, 0}, + {CborMajorType.MAP, Byte.MAX_VALUE}, + {CborMajorType.MAP, Short.MAX_VALUE}, + {CborMajorType.MAP, Integer.MAX_VALUE}, + {CborMajorType.MAP, Long.MAX_VALUE}, + + {CborMajorType.ARRAY, 2}, + {CborMajorType.ARRAY, -1}, + {CborMajorType.ARRAY, 0}, + {CborMajorType.ARRAY, Byte.MAX_VALUE}, + {CborMajorType.ARRAY, Short.MAX_VALUE}, + {CborMajorType.ARRAY, Integer.MAX_VALUE}, + {CborMajorType.ARRAY, Long.MAX_VALUE}, + }); + } + + @Parameter + public CborMajorType type; + @Parameter(1) + public long length; + + @Test(timeout = 60000) + public void parseDataLengthOrDie() throws Exception { + IndexedUnsafeBuffer ib = newBufferWithHeader(); + long length = CBORUtils.parseDataLengthOrDie(ib, type, new NullPointerException()); + + MatcherAssert.assertThat("Unexpected length post decode.", length, is(normalizeLength(this.length))); + } + + @Test(timeout = 60000, expected = RuntimeException.class) + public void parseDataLengthOrDieWrongType() throws Exception { + IndexedUnsafeBuffer ib = newBufferWithHeader(); + CBORUtils.parseDataLengthOrDie(ib, CborMajorType.Unknown, new NullPointerException()); + } + + @Test(timeout = 60000) + public void getEncodeLength() throws Exception { + long encodeLength = CBORUtils.getEncodeLength(length); + long expectedLength = length; + if (length < 24 || expectedLength == 31) { + expectedLength++; + } else if (length <= Byte.MAX_VALUE) { + expectedLength++; + expectedLength += BitUtil.SIZE_OF_BYTE; + } else if (length <= Short.MAX_VALUE) { + expectedLength++; + expectedLength += BitUtil.SIZE_OF_SHORT; + } else if (length <= Integer.MAX_VALUE) { + expectedLength++; + expectedLength += BitUtil.SIZE_OF_INT; + } else if (length <= Long.MAX_VALUE) { + expectedLength++; + expectedLength += BitUtil.SIZE_OF_LONG; + } + + MatcherAssert.assertThat("Unexpected encoded length.", encodeLength, is(expectedLength)); + } + + @Test(timeout = 60000) + public void encodeTypeHeader() throws Exception { + ByteBuffer allocate = ByteBuffer.allocate(100); + IndexedUnsafeBuffer iub = new IndexedUnsafeBuffer(ByteOrder.BIG_ENDIAN); + iub.wrap(allocate); + int expected = CborHeader.forLengthToEncode(length).getSizeInBytes(); + int encodedLength = CBORUtils.encodeTypeHeader(iub, type, length); + + MatcherAssert.assertThat("Unexpected number of bytes written.", encodedLength, is(expected)); + } + + private IndexedUnsafeBuffer newBufferWithHeader() { + ByteBuffer src = ByteBuffer.allocate(100); + IndexedUnsafeBuffer ib = new IndexedUnsafeBuffer(ByteOrder.BIG_ENDIAN); + ib.wrap(src); + CborHeader cborHeader = CborHeader.forLengthToEncode(length); + cborHeader.encode(ib, type, normalizeLength(length)); + return ib; + } + + private long normalizeLength(long length) { + return -1 == length ? CborHeader.INDEFINITE.getCode() : this.length; + } +} \ No newline at end of file diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborBinaryStringCodecTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborBinaryStringCodecTest.java new file mode 100644 index 000000000..dee99db8b --- /dev/null +++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborBinaryStringCodecTest.java @@ -0,0 +1,131 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal.cbor; + +import io.reactivesocket.mimetypes.internal.CodecRule; +import org.hamcrest.MatcherAssert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.Collection; + +import static org.hamcrest.Matchers.*; + +@RunWith(Parameterized.class) +public class CborBinaryStringCodecTest { + + @Parameters + public static Collection data() { + return Arrays.asList(new Integer[][] { + { 0 }, + { Integer.valueOf(Byte.MAX_VALUE) }, + { Integer.valueOf(Short.MAX_VALUE) }, + }); + } + + @Parameter + public int bufLength; + + @Rule + public final CodecRule cborCodecRule = new CodecRule<>(CborCodec::create); + + @Test(timeout = 60000) + public void testInfiniteDecode() throws Exception { + ByteBuffer toEncode = newBuffer(bufLength); + ByteBuffer encode = encodeChunked(toEncode); + + testDecode(toEncode, encode); + } + + @Test(timeout = 60000) + public void testEncodeWithJacksonAndDecode() throws Exception { + ByteBuffer toEncode = newBuffer(bufLength); + ByteBuffer encode = cborCodecRule.getCodec().encode(toEncode); + testDecode(toEncode, encode); + } + + @Test(timeout = 60000) + public void testEncodeAndDecodeWithJackson() throws Exception { + ByteBuffer toEncode = newBuffer(bufLength); + ByteBuffer dst = ByteBuffer.allocate(bufLength + 10); + IndexedUnsafeBuffer iub = new IndexedUnsafeBuffer(ByteOrder.BIG_ENDIAN); + iub.wrap(dst); + CborBinaryStringCodec.encode(iub, toEncode); + dst.rewind(); + + ByteBuffer decode = cborCodecRule.getCodec().decode(dst, ByteBuffer.class); + + toEncode.rewind(); + MatcherAssert.assertThat("Unexpected decode.", decode, equalTo(toEncode)); + } + + private static ByteBuffer newBuffer(int len) { + byte[] b = new byte[len]; + Arrays.fill(b, (byte) 'a'); + return ByteBuffer.wrap(b); + } + + private static void testDecode(ByteBuffer toEncode, ByteBuffer encode) { + IndexedUnsafeBuffer iub = new IndexedUnsafeBuffer(ByteOrder.BIG_ENDIAN); + iub.wrap(encode); + + ByteBuffer dst = ByteBuffer.allocate(toEncode.remaining()); + IndexedUnsafeBuffer idst = new IndexedUnsafeBuffer(ByteOrder.BIG_ENDIAN); + idst.wrap(dst); + CborBinaryStringCodec.decode(iub, idst); + + MatcherAssert.assertThat("Unexpected decode.", dst, equalTo(toEncode)); + } + + private ByteBuffer encodeChunked(ByteBuffer toEncode) { + int chunkCount = 5; + int chunkSize = bufLength / chunkCount; + CborHeader chunkHeader = CborHeader.forLengthToEncode(chunkSize); + IndexedUnsafeBuffer iub = new IndexedUnsafeBuffer(ByteOrder.BIG_ENDIAN); + iub.wrap(toEncode); + + ByteBuffer encode = ByteBuffer.allocate(bufLength + 100); + IndexedUnsafeBuffer encodeB = new IndexedUnsafeBuffer(ByteOrder.BIG_ENDIAN); + encodeB.wrap(encode); + + int offset = 0; + CborHeader.INDEFINITE.encodeIndefiniteLength(encodeB, CborMajorType.ByteString); + + int remaining = bufLength - offset; + + while (remaining > 0) { + int thisChunkSize = Math.min(chunkSize, remaining); + chunkHeader.encode(encodeB, CborMajorType.ByteString, thisChunkSize); + iub.readBytes(encodeB, thisChunkSize); + encodeB.incrementWriterIndex(thisChunkSize); + offset += thisChunkSize; + remaining = bufLength - offset; + } + + CborHeader.SMALL.encode(encodeB, CborMajorType.Break, 31); + + return encode; + } +} \ No newline at end of file diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborHeaderTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborHeaderTest.java new file mode 100644 index 000000000..d128a30ef --- /dev/null +++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborHeaderTest.java @@ -0,0 +1,131 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal.cbor; + +import org.hamcrest.MatcherAssert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.Collection; + +import static org.hamcrest.Matchers.*; + +@RunWith(Parameterized.class) +public class CborHeaderTest { + + @Parameters + public static Collection data() { + return Arrays.asList(new Object[][] { + {CborMajorType.UnsignedInteger, 22}, + {CborMajorType.UnsignedInteger, -1}, + {CborMajorType.UnsignedInteger, 0}, + {CborMajorType.UnsignedInteger, Byte.MAX_VALUE}, + {CborMajorType.UnsignedInteger, Short.MAX_VALUE}, + {CborMajorType.UnsignedInteger, Integer.MAX_VALUE}, + + {CborMajorType.NegativeInteger, 2}, + {CborMajorType.NegativeInteger, -1}, + {CborMajorType.NegativeInteger, 0}, + {CborMajorType.NegativeInteger, Byte.MAX_VALUE}, + {CborMajorType.NegativeInteger, Short.MAX_VALUE}, + {CborMajorType.NegativeInteger, Integer.MAX_VALUE}, + + + {CborMajorType.Utf8String, 2}, + {CborMajorType.Utf8String, -1}, + {CborMajorType.Utf8String, 0}, + {CborMajorType.Utf8String, Byte.MAX_VALUE}, + {CborMajorType.Utf8String, Short.MAX_VALUE}, + {CborMajorType.Utf8String, Integer.MAX_VALUE}, + {CborMajorType.Utf8String, Long.MAX_VALUE}, + + {CborMajorType.ByteString, 2}, + {CborMajorType.ByteString, -1}, + {CborMajorType.ByteString, 0}, + {CborMajorType.ByteString, Byte.MAX_VALUE}, + {CborMajorType.ByteString, Short.MAX_VALUE}, + {CborMajorType.ByteString, Integer.MAX_VALUE}, + {CborMajorType.ByteString, Long.MAX_VALUE}, + + {CborMajorType.MAP, 2}, + {CborMajorType.MAP, -1}, + {CborMajorType.MAP, 0}, + {CborMajorType.MAP, Byte.MAX_VALUE}, + {CborMajorType.MAP, Short.MAX_VALUE}, + {CborMajorType.MAP, Integer.MAX_VALUE}, + {CborMajorType.MAP, Long.MAX_VALUE}, + + {CborMajorType.ARRAY, 2}, + {CborMajorType.ARRAY, -1}, + {CborMajorType.ARRAY, 0}, + {CborMajorType.ARRAY, Byte.MAX_VALUE}, + {CborMajorType.ARRAY, Short.MAX_VALUE}, + {CborMajorType.ARRAY, Integer.MAX_VALUE}, + {CborMajorType.ARRAY, Long.MAX_VALUE}, + }); + } + + @Parameter + public CborMajorType type; + @Parameter(1) + public long length; + + @Test(timeout = 60000) + public void testEncodeDecode() throws Exception { + CborHeader cborHeader = CborHeader.forLengthToEncode(length); + CborHeader expected = null; + if (length == -1) { + expected = CborHeader.INDEFINITE; + } else if (length < 24) { + expected = CborHeader.SMALL; + } else if (length <= Byte.MAX_VALUE) { + expected = CborHeader.BYTE; + } else if (length <= Short.MAX_VALUE) { + expected = CborHeader.SHORT; + } else if (length <= Integer.MAX_VALUE) { + expected = CborHeader.INT; + } else if (length <= Long.MAX_VALUE) { + expected = CborHeader.LONG; + } + + MatcherAssert.assertThat("Unexpected CBOR header type for length: " + length, cborHeader, is(expected)); + + if (length < 0) { + return; + } + + ByteBuffer allocate = ByteBuffer.allocate(CborHeader.LONG.getSizeInBytes()); + IndexedUnsafeBuffer iub = new IndexedUnsafeBuffer(ByteOrder.BIG_ENDIAN); + iub.wrap(allocate); + + cborHeader.encode(iub, type, length); + + MatcherAssert.assertThat("Unxexpected bytes written for type: " + type + " and length: " + length, + (short) iub.getWriterIndex(), equalTo(cborHeader.getSizeInBytes())); + + iub.setReaderIndex(0); + long l = CborHeader.readDataLength(iub, iub.readUnsignedByte()); + MatcherAssert.assertThat("Unexpected data length read from encode.", l, equalTo(length)); + } +} \ No newline at end of file diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborMapCodecTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborMapCodecTest.java new file mode 100644 index 000000000..2798dafc5 --- /dev/null +++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborMapCodecTest.java @@ -0,0 +1,125 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal.cbor; + +import io.reactivesocket.mimetypes.internal.CodecRule; +import io.reactivesocket.mimetypes.internal.KVMetadataImpl; +import org.hamcrest.MatcherAssert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import static io.reactivesocket.mimetypes.internal.cbor.ByteBufferMapMatcher.*; + +@RunWith(Parameterized.class) +public class CborMapCodecTest { + + @Parameters + public static Collection data() { + return Arrays.asList(new Integer[][] { + { 0 }, + { 1 }, + { Integer.valueOf(Byte.MAX_VALUE) }, + { Integer.valueOf(Short.MAX_VALUE) }, + }); + } + + @Parameter + public int mapSize; + + @Rule + public final CborMapRule mapRule = new CborMapRule(); + + @Rule + public final CodecRule cborCodecRule = new CodecRule<>(CborCodec::create); + + @Test(timeout = 60000) + public void testEncodeWithJacksonAndDecode() throws Exception { + Map map = mapRule.newMap(mapSize); + ByteBuffer encode = cborCodecRule.getCodec().encode(map); + IndexedUnsafeBuffer iub = new IndexedUnsafeBuffer(ByteOrder.BIG_ENDIAN); + iub.wrap(encode); + + CBORMap decode = CborMapCodec.decode(iub, (b, o, i) -> new CBORMap(b, o, i)); + + MatcherAssert.assertThat("Unexpected decode.", decode, mapEqualTo(map)); + } + + @Test(timeout = 60000) + public void testEncodeAndDecodeWithJackson() throws Exception { + Map map = mapRule.newMap(mapSize); + ByteBuffer encode = ByteBuffer.allocate(mapSize == 0 ? 20 : mapSize * 100); + IndexedUnsafeBuffer iencode = new IndexedUnsafeBuffer(ByteOrder.BIG_ENDIAN); + iencode.wrap(encode); + + CborMapCodec.encode(iencode, map); + + @SuppressWarnings("unchecked") + Map decode = cborCodecRule.getCodec().decode(encode, KVMetadataImpl.class); + + MatcherAssert.assertThat("Unexpected decode.", decode, mapEqualTo(map)); + } + + @Test(timeout = 60000) + public void testEncodeCborMapAsIs() throws Exception { + mapRule.addMockEntries(mapSize); + ByteBuffer encode = ByteBuffer.allocate(mapRule.map.getBackingBuffer().capacity() + 100); + IndexedUnsafeBuffer iub = new IndexedUnsafeBuffer(ByteOrder.BIG_ENDIAN); + iub.wrap(encode); + + CborMapCodec.encode(iub, mapRule.map); + + @SuppressWarnings("unchecked") + Map decode = cborCodecRule.getCodec().decode(encode, KVMetadataImpl.class); + + MatcherAssert.assertThat("Unexpected decode.", decode, mapEqualTo(mapRule.map)); + } + + public class CborMapRule extends AbstractCborMapRule { + + @Override + protected void init() { + valueBuffer = ByteBuffer.allocate(mapSize == 0 ? 20 : mapSize * 100); + indexed = new IndexedUnsafeBuffer(ByteOrder.BIG_ENDIAN); + indexed.wrap(valueBuffer); + map = new CBORMap(indexed.getBackingBuffer(), 0); + } + + private Map newMap(int mapSize) { + Map map = new HashMap<>(mapSize); + for (int i = 0; i < mapSize; i++) { + String key = "Key" + i; + byte[] val = ("Value" + i).getBytes(StandardCharsets.UTF_8); + ByteBuffer v = ByteBuffer.wrap(val); + map.put(key, v); + } + return map; + } + } +} \ No newline at end of file diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborUtf8StringCodecTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborUtf8StringCodecTest.java new file mode 100644 index 000000000..0e7623760 --- /dev/null +++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborUtf8StringCodecTest.java @@ -0,0 +1,85 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal.cbor; + +import io.reactivesocket.mimetypes.internal.CodecRule; +import org.hamcrest.MatcherAssert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.Collection; + +import static org.hamcrest.Matchers.*; + +@RunWith(Parameterized.class) +public class CborUtf8StringCodecTest { + + @Parameters + public static Collection data() { + return Arrays.asList(new Integer[][] { + { 0 }, + { Integer.valueOf(Byte.MAX_VALUE) }, + { Integer.valueOf(Short.MAX_VALUE) }, + }); + } + + @Parameter + public int stringLength; + + @Rule + public final CodecRule cborCodecRule = new CodecRule<>(CborCodec::create); + + @Test(timeout = 60000) + public void testEncodeWithJacksonAndDecode() throws Exception { + String toEncode = newString(stringLength); + ByteBuffer encode = cborCodecRule.getCodec().encode(toEncode); + IndexedUnsafeBuffer iub = new IndexedUnsafeBuffer(ByteOrder.BIG_ENDIAN); + iub.wrap(encode); + + String decode = CborUtf8StringCodec.decode(iub); + + MatcherAssert.assertThat("Unexpected decode.", decode, equalTo(toEncode)); + } + + @Test(timeout = 60000) + public void testEncodeWithDecodeWithJackson() throws Exception { + String toEncode = newString(stringLength); + ByteBuffer dst = ByteBuffer.allocate(stringLength + 10); + IndexedUnsafeBuffer iub = new IndexedUnsafeBuffer(ByteOrder.BIG_ENDIAN); + iub.wrap(dst); + CborUtf8StringCodec.encode(iub, toEncode); + dst.rewind(); + + String decode = cborCodecRule.getCodec().decode(dst, String.class); + + MatcherAssert.assertThat("Unexpected decode.", decode, equalTo(toEncode)); + } + + private static String newString(int stringLength) { + byte[] b = new byte[stringLength]; + Arrays.fill(b, (byte) 'a'); + return new String(b); + } +} \ No newline at end of file diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/IndexedUnsafeBufferTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/IndexedUnsafeBufferTest.java new file mode 100644 index 000000000..38596b01d --- /dev/null +++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/IndexedUnsafeBufferTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal.cbor; + +import org.hamcrest.MatcherAssert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExternalResource; +import org.junit.runner.Description; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; +import org.junit.runners.model.Statement; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.Collection; + +import static org.hamcrest.Matchers.*; + +@RunWith(Parameterized.class) +public class IndexedUnsafeBufferTest { + + @Parameters + public static Collection data() { + return Arrays.asList(new Integer[][] { + {0, 0, 0}, + {10, 0, 10}, + {100, 0, 100}, + {500, 0, 500}, + {500, 10, 400}, + {500, 10, 490}, + }); + } + + @Parameter + public int bufferSize; + @Parameter(1) + public int bufferOffset; + @Parameter(2) + public int bufferLength; + + @Rule + public final BufferRule bufferRule = new BufferRule(); + + @Test(timeout = 60000) + public void testForEachByteFound() throws Exception { + testScanForBreak(bufferLength / 2); + } + + @Test(timeout = 60000) + public void testForEachByteNotFound() throws Exception { + bufferRule.initBuffer(bufferSize, bufferOffset, bufferLength); + int i = bufferRule.buffer.forEachByte(CBORUtils.BREAK_SCANNER); + MatcherAssert.assertThat("Unexpected index.", i, is(bufferRule.buffer.getBackingBuffer().capacity())); + } + + @Test(timeout = 60000) + public void testForEachByteLastByte() throws Exception { + testScanForBreak(bufferLength - bufferOffset - 1); + } + + private void testScanForBreak(int indexForBreak) { + bufferRule.initBuffer(bufferSize, bufferOffset, bufferLength); + if (bufferSize > 0) { + bufferRule.buffer.getBackingBuffer().putByte(indexForBreak, CborMajorType.CBOR_BREAK); + } + + int i = bufferRule.buffer.forEachByte(CBORUtils.BREAK_SCANNER); + MatcherAssert.assertThat("Unexpected index.", i, is(Math.max(0, indexForBreak))); + } + + public static class BufferRule extends ExternalResource { + + private IndexedUnsafeBuffer buffer; + + @Override + public Statement apply(final Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + initBuffer(0, 0, 0); + base.evaluate(); + } + }; + } + + public void initBuffer(int size, int offset, int length) { + ByteBuffer b = ByteBuffer.allocate(size); + buffer = new IndexedUnsafeBuffer(ByteOrder.BIG_ENDIAN); + buffer.wrap(b, 0, length - offset); + if (length != 0) { + buffer.setReaderIndex(offset); + buffer.setWriterIndex(offset); + } + } + } +} \ No newline at end of file diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/MetadataCodecTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/MetadataCodecTest.java new file mode 100644 index 000000000..2640caf69 --- /dev/null +++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/MetadataCodecTest.java @@ -0,0 +1,148 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal.cbor; + +import io.reactivesocket.mimetypes.KVMetadata; +import io.reactivesocket.mimetypes.internal.KVMetadataImpl; +import org.agrona.DirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExternalResource; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + +import static io.reactivesocket.mimetypes.internal.cbor.ByteBufferMapMatcher.*; +import static org.hamcrest.MatcherAssert.*; + +public class MetadataCodecTest { + + @Rule + public final CodecRule metaCodecRule = new CodecRule(); + @Rule + public final io.reactivesocket.mimetypes.internal.CodecRule cborCodecRule = + new io.reactivesocket.mimetypes.internal.CodecRule<>(CborCodec::create); + + @Test(timeout = 60000) + public void testDecode() throws Exception { + metaCodecRule.addTestData("Key1", "Value1"); + metaCodecRule.addTestData("Key2", "Value2"); + metaCodecRule.addTestData("Key3", "Value3"); + + ByteBuffer encode = cborCodecRule.getCodec().encode(metaCodecRule.testDataHolder); + + KVMetadata decode = metaCodecRule.codec.decode(encode, KVMetadata.class); + + assertThat("Unexpected decode.", decode, mapEqualTo(metaCodecRule.testDataHolder)); + } + + @Test(timeout = 60000) + public void testDecodeDirect() throws Exception { + metaCodecRule.addTestData("Key1", "Value1"); + metaCodecRule.addTestData("Key2", "Value2"); + metaCodecRule.addTestData("Key3", "Value3"); + + ByteBuffer encode = cborCodecRule.getCodec().encode(metaCodecRule.testDataHolder); + UnsafeBuffer ub = new UnsafeBuffer(encode); + KVMetadata decode = metaCodecRule.codec.decode(ub, 0, KVMetadata.class); + + assertThat("Unexpected decode.", decode, mapEqualTo(metaCodecRule.testDataHolder)); + } + + @Test(timeout = 60000) + public void encode() throws Exception { + metaCodecRule.addTestData("Key1", "Value1"); + metaCodecRule.addTestData("Key2", "Value2"); + metaCodecRule.addTestData("Key3", "Value3"); + + ByteBuffer encode = metaCodecRule.codec.encode(metaCodecRule.testData); + KVMetadata decode = cborCodecRule.getCodec().decode(encode, KVMetadataImpl.class); + + assertThat("Unexpected decode.", decode, mapEqualTo(metaCodecRule.testData)); + } + + @Test(timeout = 60000) + public void encodeDirect() throws Exception { + metaCodecRule.addTestData("Key1", "Value1"); + metaCodecRule.addTestData("Key2", "Value2"); + metaCodecRule.addTestData("Key3", "Value3"); + + DirectBuffer encode = metaCodecRule.codec.encodeDirect(metaCodecRule.testData); + KVMetadata decode = cborCodecRule.getCodec().decode(encode.byteBuffer(), KVMetadataImpl.class); + + assertThat("Unexpected decode.", decode, mapEqualTo(metaCodecRule.testData)); + } + + @Test(timeout = 60000) + public void encodeTo() throws Exception { + metaCodecRule.addTestData("Key1", "Value1"); + metaCodecRule.addTestData("Key2", "Value2"); + metaCodecRule.addTestData("Key3", "Value3"); + + ByteBuffer dst = ByteBuffer.allocate(500); + metaCodecRule.codec.encodeTo(dst, metaCodecRule.testData); + KVMetadata decode = cborCodecRule.getCodec().decode(dst, KVMetadataImpl.class); + + assertThat("Unexpected decode.", decode, mapEqualTo(metaCodecRule.testData)); + } + + @Test(timeout = 60000) + public void encodeToDirect() throws Exception { + metaCodecRule.addTestData("Key1", "Value1"); + metaCodecRule.addTestData("Key2", "Value2"); + metaCodecRule.addTestData("Key3", "Value3"); + + ByteBuffer dst = ByteBuffer.allocate(500); + UnsafeBuffer ub = new UnsafeBuffer(dst); + metaCodecRule.codec.encodeTo(ub, metaCodecRule.testData, 0); + KVMetadata decode = cborCodecRule.getCodec().decode(dst, KVMetadataImpl.class); + + assertThat("Unexpected decode.", decode, mapEqualTo(metaCodecRule.testData)); + } + + public static class CodecRule extends ExternalResource { + + private MetadataCodec codec; + private Map testDataHolder; + private KVMetadata testData; + + @Override + public Statement apply(final Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + codec = MetadataCodec.INSTANCE; + testDataHolder = new HashMap<>(); + testData = new KVMetadataImpl(testDataHolder); + base.evaluate(); + } + }; + } + + public void addTestData(String key, String value) { + ByteBuffer vBuff = ByteBuffer.allocate(value.length()).put(value.getBytes()); + vBuff.flip(); + testDataHolder.put(key, vBuff); + } + } + +} \ No newline at end of file diff --git a/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/SlicedBufferKVMetadataTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/SlicedBufferKVMetadataTest.java new file mode 100644 index 000000000..a28450838 --- /dev/null +++ b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/SlicedBufferKVMetadataTest.java @@ -0,0 +1,103 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.reactivesocket.mimetypes.internal.cbor; + +import io.reactivesocket.mimetypes.KVMetadata; +import org.agrona.concurrent.UnsafeBuffer; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collection; + +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; + +@RunWith(Parameterized.class) +public class SlicedBufferKVMetadataTest { + + @Parameters + public static Collection data() { + return Arrays.asList(new Integer[][] { + {100, 0, 100, 5}, {400, 20, 200, 20}, {400, 20, 140, 20} + }); + } + + @Parameter + public int bufferSize; + @Parameter(1) + public int bufferOffset; + @Parameter(2) + public int bufferLength; + @Parameter(3) + public int entrySize; + + @Rule + public final SlicedBufferKVMetadataRule mapRule = new SlicedBufferKVMetadataRule(); + + @Test(timeout = 60000) + public void getAsString() throws Exception { + mapRule.addMockEntries(entrySize); + for (int i = 0; i < entrySize; i++) { + mapRule.assertValueAsStringForKey(i); + } + } + + @Test(timeout = 60000) + public void duplicate() throws Exception { + mapRule.addMockEntries(entrySize); + KVMetadata duplicate = mapRule.map.duplicate(capacity -> new UnsafeBuffer(ByteBuffer.allocate(capacity))); + + assertThat("Unexpected type of duplicate.", duplicate, instanceOf(SlicedBufferKVMetadata.class)); + + SlicedBufferKVMetadata dup = (SlicedBufferKVMetadata) duplicate; + + for (int i = 0; i < entrySize; i++) { + mapRule.assertValueAsStringForKey(i, dup); + } + } + + public class SlicedBufferKVMetadataRule extends AbstractCborMapRule { + + @Override + protected void init() { + valueBuffer = ByteBuffer.allocate(bufferSize); + indexed = new IndexedUnsafeBuffer(ByteOrder.BIG_ENDIAN); + indexed.wrap(valueBuffer, bufferOffset, bufferLength); + map = new SlicedBufferKVMetadata(indexed.getBackingBuffer(), bufferOffset); + } + + public void assertValueAsStringForKey(int index) { + assertValueAsStringForKey(index, map); + } + + public void assertValueAsStringForKey(int index, SlicedBufferKVMetadata map) { + String k = getMockKey(index); + assertThat("Unexpected lookup value for key: " + k, map.getAsString(k, StandardCharsets.UTF_8), + equalTo(getMockValue(index))); + } + } + +} \ No newline at end of file diff --git a/reactivesocket-mime-types/src/test/resources/log4j.properties b/reactivesocket-mime-types/src/test/resources/log4j.properties new file mode 100644 index 000000000..70bc4badb --- /dev/null +++ b/reactivesocket-mime-types/src/test/resources/log4j.properties @@ -0,0 +1,21 @@ +# +# Copyright 2015 Netflix, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# +log4j.rootLogger=DEBUG, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{dd MMM yyyy HH:mm:ss,SSS} %5p [%t] (%F:%L) - %m%n \ No newline at end of file From 179563632eb3e14be1708ac615f937753b944e07 Mon Sep 17 00:00:00 2001 From: Steve Gury Date: Wed, 1 Jun 2016 14:00:24 -0700 Subject: [PATCH 101/105] Propagate cancellation when a TransportException occurs Problem When a TransportException happens on the transport, it is propagated up in the chain but the Susbscription is not cancelled. Then, any source associated with the chain continue to emit events. One example of that is the Keep-Alive Observable, which regularly emit keep-alive messages at fixed interval, when the connection is closed, the observable has to be cancelled. Solution In all DuplexConnection implementation, cancel the Subscription when we saw a TransportException. --- .../aeron/client/AeronClientDuplexConnection.java | 9 +++++---- .../netty/tcp/client/ClientTcpDuplexConnection.java | 6 ++++++ .../client/ClientWebSocketDuplexConnection.java | 6 ++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java index 27736b6b6..5714f42dc 100644 --- a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java @@ -79,11 +79,11 @@ public void dispose() { public void addOutput(Publisher o, Completable callback) { o .subscribe(new Subscriber() { - private Subscription s; + private Subscription subscription; @Override public void onSubscribe(Subscription s) { - this.s = s; + this.subscription = s; s.request(128); } @@ -91,10 +91,10 @@ public void onSubscribe(Subscription s) { @Override public void onNext(Frame frame) { if (isTraceEnabled()) { - trace("onNext subscription => {} and frame => {}", s.toString(), frame.toString()); + trace("onNext subscription => {} and frame => {}", subscription.toString(), frame.toString()); } - final FrameHolder fh = FrameHolder.get(frame, publication, s); + final FrameHolder fh = FrameHolder.get(frame, publication, subscription); boolean offer; do { offer = frameSendQueue.offer(fh); @@ -105,6 +105,7 @@ public void onNext(Frame frame) { public void onError(Throwable t) { if (t instanceof NotConnectedException) { callback.error(new TransportException(t)); + subscription.cancel(); } else { callback.error(t); } diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java index 657fc2f89..54fbcb4d1 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java @@ -98,8 +98,11 @@ public final Observable getInput() { @Override public void addOutput(Publisher o, Completable callback) { o.subscribe(new Subscriber() { + private Subscription subscription; + @Override public void onSubscribe(Subscription s) { + subscription = s; s.request(Long.MAX_VALUE); } @@ -126,6 +129,9 @@ public void onNext(Frame frame) { @Override public void onError(Throwable t) { callback.error(t); + if (t instanceof TransportException) { + subscription.cancel(); + } } @Override diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java index 1eef1fa28..00e42dfa0 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java @@ -119,8 +119,11 @@ public final Observable getInput() { @Override public void addOutput(Publisher o, Completable callback) { o.subscribe(new Subscriber() { + private Subscription subscription; + @Override public void onSubscribe(Subscription s) { + subscription = s; s.request(Long.MAX_VALUE); } @@ -148,6 +151,9 @@ public void onNext(Frame frame) { @Override public void onError(Throwable t) { callback.error(t); + if (t instanceof TransportException) { + subscription.cancel(); + } } @Override From b1bd5f399d0abbcdc37dda7cec2c94023bfd233e Mon Sep 17 00:00:00 2001 From: Steve Gury Date: Tue, 7 Jun 2016 15:57:42 -0700 Subject: [PATCH 102/105] Refactor Factory to Connector + Implement availability (#15) * Refactor Factory to Connector + Implement availability Problem We need to comply with the recent refactoring "Factory to Connector" introduced in reactivesocket-java. Also, implementation of DuplexConnection have to return an availability. Solution The Factory to Connector refactoring is pretty straight forward. All implementations of DuplexConnection return an availability which directly map the state of the underlying resource (0.0 if the resource is closed, 1.0 otherwise). This will greatly help the load-balancer to select a valid connection. I removed the blocking method from reactivesocket-java, so I moved some utility blocking code inside the TestUtil class. Clean-up of the Netty implementation, remove unused args, explicitey specify tcp socket configuration. Shortened the toString name to ease log-reading. Bug All DuplexConnection implementations now cancel the subscription when an exception occurs. This fix the problem, where the Keep-Alive Observable kept trying to send a keep-alive on a closed connection. * Refactor the TestUtil helper to return CompletableFuture * rs version * Restore rs dependency to latest.release * Remove mavenLocal in gradle build file * Propagate cancellation in all cases * Use helper method from ReactiveSocket Unsafe --- .../client/AeronClientDuplexConnection.java | 6 +- ...java => AeronReactiveSocketConnector.java} | 15 ++- .../server/AeronServerDuplexConnection.java | 5 + .../websocket/WebSocketDuplexConnection.java | 5 + ... => WebSocketReactiveSocketConnector.java} | 13 +-- .../local/LocalClientDuplexConnection.java | 5 + ...> LocalClientReactiveSocketConnector.java} | 13 +-- .../local/LocalServerDuplexConection.java | 5 + ...> LocalServerReactiveSocketConnector.java} | 13 +-- .../local/ClientServerTest.java | 11 ++- .../tcp/client/ClientTcpDuplexConnection.java | 44 ++++----- .../client/TcpReactiveSocketConnector.java | 80 +++++++++++++++ .../tcp/client/TcpReactiveSocketFactory.java | 98 ------------------- .../tcp/server/ServerTcpDuplexConnection.java | 5 + .../ClientWebSocketDuplexConnection.java | 29 +++--- ... => WebSocketReactiveSocketConnector.java} | 13 +-- .../ServerWebSocketDuplexConnection.java | 5 + .../java/io/reactivesocket/test/TestUtil.java | 7 ++ 18 files changed, 192 insertions(+), 180 deletions(-) rename reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/{AeronReactiveSocketFactory.java => AeronReactiveSocketConnector.java} (86%) rename reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/{WebSocketReactiveSocketFactory.java => WebSocketReactiveSocketConnector.java} (84%) rename reactivesocket-local/src/main/java/io/reactivesocket/local/{LocalClientReactiveSocketFactory.java => LocalClientReactiveSocketConnector.java} (79%) rename reactivesocket-local/src/main/java/io/reactivesocket/local/{LocalServerReactiveSocketFactory.java => LocalServerReactiveSocketConnector.java} (78%) create mode 100644 reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketConnector.java delete mode 100644 reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketFactory.java rename reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/{WebSocketReactiveSocketFactory.java => WebSocketReactiveSocketConnector.java} (85%) diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java index 5714f42dc..795b39f4b 100644 --- a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java @@ -118,6 +118,11 @@ public void onComplete() { }); } + @Override + public double availability() { + return publication.isClosed() ? 0.0 : 1.0; + } + @Override public void close() throws IOException { onClose.accept(publication); @@ -136,6 +141,5 @@ public String toString() { "channel=" + publication.channel() + "," + "streamId=" + publication.streamId() + "," + "sessionId=" + publication.sessionId() + "]"; - } } diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketFactory.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketConnector.java similarity index 86% rename from reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketFactory.java rename to reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketConnector.java index 475a54688..a96af7f93 100644 --- a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketFactory.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketConnector.java @@ -15,10 +15,7 @@ */ package io.reactivesocket.aeron.client; -import io.reactivesocket.ConnectionSetupPayload; -import io.reactivesocket.DefaultReactiveSocket; -import io.reactivesocket.ReactiveSocket; -import io.reactivesocket.ReactiveSocketFactory; +import io.reactivesocket.*; import io.reactivesocket.rx.Completable; import org.agrona.LangUtil; import org.reactivestreams.Publisher; @@ -40,17 +37,17 @@ /** * An implementation of {@link ReactiveSocketFactory} that creates Aeron ReactiveSockets. */ -public class AeronReactiveSocketFactory implements ReactiveSocketFactory { - private static final Logger logger = LoggerFactory.getLogger(AeronReactiveSocketFactory.class); +public class AeronReactiveSocketConnector implements ReactiveSocketConnector { + private static final Logger logger = LoggerFactory.getLogger(AeronReactiveSocketConnector.class); private final ConnectionSetupPayload connectionSetupPayload; private final Consumer errorStream; - public AeronReactiveSocketFactory(ConnectionSetupPayload connectionSetupPayload, Consumer errorStream) { + public AeronReactiveSocketConnector(ConnectionSetupPayload connectionSetupPayload, Consumer errorStream) { this(getIPv4InetAddress().getHostAddress(), 39790, connectionSetupPayload, errorStream); } - public AeronReactiveSocketFactory(String host, int port, ConnectionSetupPayload connectionSetupPayload, Consumer errorStream) { + public AeronReactiveSocketConnector(String host, int port, ConnectionSetupPayload connectionSetupPayload, Consumer errorStream) { this.connectionSetupPayload = connectionSetupPayload; this.errorStream = errorStream; @@ -65,7 +62,7 @@ public AeronReactiveSocketFactory(String host, int port, ConnectionSetupPayload } @Override - public Publisher call(SocketAddress address) { + public Publisher connect(SocketAddress address) { Publisher connection = AeronClientDuplexConnectionFactory.getInstance().createAeronClientDuplexConnection(address); diff --git a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java index 0940c89d2..5b68a0f9a 100644 --- a/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java +++ b/reactivesocket-aeron/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java @@ -78,6 +78,11 @@ public void addOutput(Publisher o, Completable callback) { o.subscribe(new ServerSubscription(publication, callback)); } + @Override + public double availability() { + return isClosed ? 0.0 : 1.0; + } + // TODO - this is bad - I need to queue this up somewhere and process this on the polling thread so it doesn't just block everything void ackEstablishConnection(int ackSessionId) { debug("Acking establish connection for session id => {}", ackSessionId); diff --git a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/WebSocketDuplexConnection.java b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/WebSocketDuplexConnection.java index bd14568df..04c978912 100644 --- a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/WebSocketDuplexConnection.java +++ b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/WebSocketDuplexConnection.java @@ -79,6 +79,11 @@ public void onSubscribe(Subscription s) { }); } + @Override + public double availability() { + return session.isOpen() ? 1.0 : 0.0; + } + @Override public void close() throws IOException { session.close(); diff --git a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketFactory.java b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketConnector.java similarity index 84% rename from reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketFactory.java rename to reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketConnector.java index 581a25302..01a6269dd 100644 --- a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketFactory.java +++ b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketConnector.java @@ -15,10 +15,7 @@ */ package io.reactivesocket.javax.websocket.client; -import io.reactivesocket.ConnectionSetupPayload; -import io.reactivesocket.DefaultReactiveSocket; -import io.reactivesocket.ReactiveSocket; -import io.reactivesocket.ReactiveSocketFactory; +import io.reactivesocket.*; import io.reactivesocket.javax.websocket.WebSocketDuplexConnection; import io.reactivesocket.rx.Completable; import org.glassfish.tyrus.client.ClientManager; @@ -36,15 +33,15 @@ /** * An implementation of {@link ReactiveSocketFactory} that creates JSR-356 WebSocket ReactiveSockets. */ -public class WebSocketReactiveSocketFactory implements ReactiveSocketFactory { - private static final Logger logger = LoggerFactory.getLogger(WebSocketReactiveSocketFactory.class); +public class WebSocketReactiveSocketConnector implements ReactiveSocketConnector { + private static final Logger logger = LoggerFactory.getLogger(WebSocketReactiveSocketConnector.class); private final ConnectionSetupPayload connectionSetupPayload; private final Consumer errorStream; private final String path; private final ClientManager clientManager; - public WebSocketReactiveSocketFactory(String path, ClientManager clientManager, ConnectionSetupPayload connectionSetupPayload, Consumer errorStream) { + public WebSocketReactiveSocketConnector(String path, ClientManager clientManager, ConnectionSetupPayload connectionSetupPayload, Consumer errorStream) { this.connectionSetupPayload = connectionSetupPayload; this.errorStream = errorStream; this.path = path; @@ -52,7 +49,7 @@ public WebSocketReactiveSocketFactory(String path, ClientManager clientManager, } @Override - public Publisher call(SocketAddress address) { + public Publisher connect(SocketAddress address) { Publisher connection = ReactiveSocketWebSocketClient.create(address, path, clientManager); diff --git a/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientDuplexConnection.java b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientDuplexConnection.java index e4d82aa8a..77d330f92 100644 --- a/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientDuplexConnection.java +++ b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientDuplexConnection.java @@ -80,6 +80,11 @@ public void onComplete() { }); } + @Override + public double availability() { + return 1.0; + } + void write(Frame frame) { subjects .forEach(o -> o.onNext(frame)); diff --git a/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientReactiveSocketFactory.java b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientReactiveSocketConnector.java similarity index 79% rename from reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientReactiveSocketFactory.java rename to reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientReactiveSocketConnector.java index e7fe1ccae..43740f4b1 100644 --- a/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientReactiveSocketFactory.java +++ b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalClientReactiveSocketConnector.java @@ -15,20 +15,17 @@ */ package io.reactivesocket.local; -import io.reactivesocket.ConnectionSetupPayload; -import io.reactivesocket.DefaultReactiveSocket; -import io.reactivesocket.ReactiveSocket; -import io.reactivesocket.ReactiveSocketFactory; +import io.reactivesocket.*; import io.reactivesocket.internal.rx.EmptySubscription; import org.reactivestreams.Publisher; -public class LocalClientReactiveSocketFactory implements ReactiveSocketFactory { - public static final LocalClientReactiveSocketFactory INSTANCE = new LocalClientReactiveSocketFactory(); +public class LocalClientReactiveSocketConnector implements ReactiveSocketConnector { + public static final LocalClientReactiveSocketConnector INSTANCE = new LocalClientReactiveSocketConnector(); - private LocalClientReactiveSocketFactory() {} + private LocalClientReactiveSocketConnector() {} @Override - public Publisher call(Config config) { + public Publisher connect(Config config) { return s -> { try { s.onSubscribe(EmptySubscription.INSTANCE); diff --git a/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerDuplexConection.java b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerDuplexConection.java index 2ecc5cad3..baaf3800d 100644 --- a/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerDuplexConection.java +++ b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerDuplexConection.java @@ -79,6 +79,11 @@ public void onComplete() { }); } + @Override + public double availability() { + return 1.0; + } + void write(Frame frame) { subjects .forEach(o -> o.onNext(frame)); diff --git a/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerReactiveSocketFactory.java b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerReactiveSocketConnector.java similarity index 78% rename from reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerReactiveSocketFactory.java rename to reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerReactiveSocketConnector.java index 03faaf522..58ad1d62b 100644 --- a/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerReactiveSocketFactory.java +++ b/reactivesocket-local/src/main/java/io/reactivesocket/local/LocalServerReactiveSocketConnector.java @@ -15,20 +15,17 @@ */ package io.reactivesocket.local; -import io.reactivesocket.ConnectionSetupHandler; -import io.reactivesocket.DefaultReactiveSocket; -import io.reactivesocket.ReactiveSocket; -import io.reactivesocket.ReactiveSocketFactory; +import io.reactivesocket.*; import io.reactivesocket.internal.rx.EmptySubscription; import org.reactivestreams.Publisher; -public class LocalServerReactiveSocketFactory implements ReactiveSocketFactory { - public static final LocalServerReactiveSocketFactory INSTANCE = new LocalServerReactiveSocketFactory(); +public class LocalServerReactiveSocketConnector implements ReactiveSocketConnector { + public static final LocalServerReactiveSocketConnector INSTANCE = new LocalServerReactiveSocketConnector(); - private LocalServerReactiveSocketFactory() {} + private LocalServerReactiveSocketConnector() {} @Override - public Publisher call(Config config) { + public Publisher connect(Config config) { return s -> { try { s.onSubscribe(EmptySubscription.INSTANCE); diff --git a/reactivesocket-local/src/test/java/io/reactivesocket/local/ClientServerTest.java b/reactivesocket-local/src/test/java/io/reactivesocket/local/ClientServerTest.java index 30ca11a28..a62871e8f 100644 --- a/reactivesocket-local/src/test/java/io/reactivesocket/local/ClientServerTest.java +++ b/reactivesocket-local/src/test/java/io/reactivesocket/local/ClientServerTest.java @@ -32,6 +32,8 @@ import java.util.concurrent.TimeUnit; +import static io.reactivesocket.util.Unsafe.toSingleFuture; + public class ClientServerTest { static ReactiveSocket client; @@ -40,14 +42,13 @@ public class ClientServerTest { @BeforeClass public static void setup() throws Exception { - server = LocalServerReactiveSocketFactory.INSTANCE.callAndWait(new LocalServerReactiveSocketFactory.Config("test", new ConnectionSetupHandler() { + LocalServerReactiveSocketConnector.Config serverConfig = new LocalServerReactiveSocketConnector.Config("test", new ConnectionSetupHandler() { @Override public RequestHandler apply(ConnectionSetupPayload setupPayload, ReactiveSocket rs) throws SetupException { return new RequestHandler() { @Override public Publisher handleRequestResponse(Payload payload) { return s -> { - //System.out.println("Handling request/response payload => " + s.toString()); Payload response = TestUtil.utf8EncodedPayload("hello world", "metadata"); s.onNext(response); s.onComplete(); @@ -91,10 +92,12 @@ public Publisher handleMetadataPush(Payload payload) { } }; } - })); + }); - client = LocalClientReactiveSocketFactory.INSTANCE.callAndWait(new LocalClientReactiveSocketFactory.Config("test", "text", "text")); + server = toSingleFuture(LocalServerReactiveSocketConnector.INSTANCE.connect(serverConfig)).get(5, TimeUnit.SECONDS); + LocalClientReactiveSocketConnector.Config clientConfig = new LocalClientReactiveSocketConnector.Config("test", "text", "text"); + client = toSingleFuture(LocalClientReactiveSocketConnector.INSTANCE.connect(clientConfig)).get(5, TimeUnit.SECONDS);; } @Test diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java index 54fbcb4d1..4c1e05bfc 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java @@ -18,11 +18,7 @@ import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelPipeline; -import io.netty.channel.EventLoopGroup; +import io.netty.channel.*; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; @@ -38,25 +34,20 @@ import org.reactivestreams.Subscription; import java.io.IOException; -import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.channels.ClosedChannelException; import java.util.concurrent.CopyOnWriteArrayList; public class ClientTcpDuplexConnection implements DuplexConnection { - private Channel channel; - - private Bootstrap bootstrap; - + private final Channel channel; private final CopyOnWriteArrayList> subjects; - private ClientTcpDuplexConnection(Channel channel, Bootstrap bootstrap, CopyOnWriteArrayList> subjects) { + private ClientTcpDuplexConnection(Channel channel, CopyOnWriteArrayList> subjects) { this.subjects = subjects; this.channel = channel; - this.bootstrap = bootstrap; } - public static Publisher create(InetSocketAddress address, EventLoopGroup eventLoopGroup) { + public static Publisher create(SocketAddress address, EventLoopGroup eventLoopGroup) { return s -> { CopyOnWriteArrayList> subjects = new CopyOnWriteArrayList<>(); ReactiveSocketClientHandler clientHandler = new ReactiveSocketClientHandler(subjects); @@ -64,6 +55,10 @@ public static Publisher create(InetSocketAddress addr ChannelFuture connect = bootstrap .group(eventLoopGroup) .channel(NioSocketChannel.class) + .option(ChannelOption.TCP_NODELAY, true) + .option(ChannelOption.SO_REUSEADDR, true) + .option(ChannelOption.AUTO_READ, true) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000) .handler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) throws Exception { @@ -73,12 +68,12 @@ protected void initChannel(SocketChannel ch) throws Exception { clientHandler ); } - }).connect(address.getHostName(), address.getPort()); + }).connect(address); connect.addListener(connectFuture -> { if (connectFuture.isSuccess()) { - final Channel ch = connect.channel(); - s.onNext(new ClientTcpDuplexConnection(ch, bootstrap, subjects)); + Channel ch = connect.channel(); + s.onNext(new ClientTcpDuplexConnection(ch, subjects)); s.onComplete(); } else { s.onError(connectFuture.cause()); @@ -103,6 +98,7 @@ public void addOutput(Publisher o, Completable callback) { @Override public void onSubscribe(Subscription s) { subscription = s; + // TODO: wire back pressure s.request(Long.MAX_VALUE); } @@ -129,18 +125,22 @@ public void onNext(Frame frame) { @Override public void onError(Throwable t) { callback.error(t); - if (t instanceof TransportException) { - subscription.cancel(); - } + subscription.cancel(); } @Override public void onComplete() { callback.success(); + subscription.cancel(); } }); } + @Override + public double availability() { + return channel.isOpen() ? 1.0 : 0.0; + } + @Override public void close() throws IOException { channel.close(); @@ -148,17 +148,17 @@ public void close() throws IOException { public String toString() { if (channel == null) { - return getClass().getName() + ":channel=null"; + return "ClientTcpDuplexConnection(channel=null)"; } - return getClass().getName() + ":channel=[" + + return "ClientTcpDuplexConnection(channel=[" + "remoteAddress=" + channel.remoteAddress() + "," + "isActive=" + channel.isActive() + "," + "isOpen=" + channel.isOpen() + "," + "isRegistered=" + channel.isRegistered() + "," + "isWritable=" + channel.isWritable() + "," + "channelId=" + channel.id().asLongText() + - "]"; + "])"; } } diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketConnector.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketConnector.java new file mode 100644 index 000000000..4d5d38e42 --- /dev/null +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketConnector.java @@ -0,0 +1,80 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.reactivesocket.netty.tcp.client; + +import io.netty.channel.EventLoopGroup; +import io.reactivesocket.*; +import io.reactivesocket.rx.Completable; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import java.net.SocketAddress; +import java.util.function.Consumer; + +/** + * An implementation of {@link ReactiveSocketFactory} that creates Netty WebSocket ReactiveSockets. + */ +public class TcpReactiveSocketConnector implements ReactiveSocketConnector { + private final ConnectionSetupPayload connectionSetupPayload; + private final Consumer errorStream; + private final EventLoopGroup eventLoopGroup; + + public TcpReactiveSocketConnector(EventLoopGroup eventLoopGroup, ConnectionSetupPayload connectionSetupPayload, Consumer errorStream) { + this.connectionSetupPayload = connectionSetupPayload; + this.errorStream = errorStream; + this.eventLoopGroup = eventLoopGroup; + } + + @Override + public Publisher connect(SocketAddress address) { + Publisher connection + = ClientTcpDuplexConnection.create(address, eventLoopGroup); + + return s -> connection.subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(1); + } + + @Override + public void onNext(ClientTcpDuplexConnection connection) { + ReactiveSocket reactiveSocket = DefaultReactiveSocket.fromClientConnection( + connection, connectionSetupPayload, errorStream); + reactiveSocket.start(new Completable() { + @Override + public void success() { + s.onNext(reactiveSocket); + s.onComplete(); + } + + @Override + public void error(Throwable e) { + s.onError(e); + } + }); + } + + @Override + public void onError(Throwable t) { + s.onError(t); + } + + @Override + public void onComplete() {} + }); + } +} diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketFactory.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketFactory.java deleted file mode 100644 index 67aebb07a..000000000 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketFactory.java +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Copyright 2016 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.reactivesocket.netty.tcp.client; - -import io.netty.channel.EventLoopGroup; -import io.reactivesocket.ConnectionSetupPayload; -import io.reactivesocket.DefaultReactiveSocket; -import io.reactivesocket.ReactiveSocket; -import io.reactivesocket.ReactiveSocketFactory; -import io.reactivesocket.rx.Completable; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import rx.Observable; -import rx.RxReactiveStreams; - -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.util.function.Consumer; - -/** - * An implementation of {@link ReactiveSocketFactory} that creates Netty WebSocket ReactiveSockets. - */ -public class TcpReactiveSocketFactory implements ReactiveSocketFactory { - private static final Logger logger = LoggerFactory.getLogger(TcpReactiveSocketFactory.class); - - private final ConnectionSetupPayload connectionSetupPayload; - private final Consumer errorStream; - private final EventLoopGroup eventLoopGroup; - - public TcpReactiveSocketFactory(EventLoopGroup eventLoopGroup, ConnectionSetupPayload connectionSetupPayload, Consumer errorStream) { - this.connectionSetupPayload = connectionSetupPayload; - this.errorStream = errorStream; - this.eventLoopGroup = eventLoopGroup; - } - - @Override - public Publisher call(SocketAddress address) { - if (address instanceof InetSocketAddress) { - Publisher connection - = ClientTcpDuplexConnection.create((InetSocketAddress)address, eventLoopGroup); - - Observable result = Observable.create(s -> - connection.subscribe(new Subscriber() { - @Override - public void onSubscribe(Subscription s) { - s.request(1); - } - - @Override - public void onNext(ClientTcpDuplexConnection connection) { - ReactiveSocket reactiveSocket = DefaultReactiveSocket.fromClientConnection(connection, connectionSetupPayload, errorStream); - reactiveSocket.start(new Completable() { - @Override - public void success() { - s.onNext(reactiveSocket); - s.onCompleted(); - } - - @Override - public void error(Throwable e) { - s.onError(e); - } - }); - } - - @Override - public void onError(Throwable t) { - s.onError(t); - } - - @Override - public void onComplete() { - } - }) - ); - - return RxReactiveStreams.toPublisher(result); - } else { - throw new IllegalArgumentException("unknown socket address type => " + address.getClass()); - } - } -} diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/server/ServerTcpDuplexConnection.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/server/ServerTcpDuplexConnection.java index 7a99b223f..c4a9ee952 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/server/ServerTcpDuplexConnection.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/server/ServerTcpDuplexConnection.java @@ -94,6 +94,11 @@ public void onComplete() { }); } + @Override + public double availability() { + return ctx.channel().isOpen() ? 1.0 : 0.0; + } + @Override public void close() throws IOException { diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java index 00e42dfa0..d26c25697 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java @@ -46,14 +46,11 @@ public class ClientWebSocketDuplexConnection implements DuplexConnection { private Channel channel; - private Bootstrap bootstrap; - private final CopyOnWriteArrayList> subjects; - private ClientWebSocketDuplexConnection(Channel channel, Bootstrap bootstrap, CopyOnWriteArrayList> subjects) { + private ClientWebSocketDuplexConnection(Channel channel, CopyOnWriteArrayList> subjects) { this.subjects = subjects; this.channel = channel; - this.bootstrap = bootstrap; } public static Publisher create(InetSocketAddress address, String path, EventLoopGroup eventLoopGroup) { @@ -95,7 +92,7 @@ protected void initChannel(SocketChannel ch) throws Exception { .getHandshakePromise() .addListener(handshakeFuture -> { if (handshakeFuture.isSuccess()) { - s.onNext(new ClientWebSocketDuplexConnection(ch, bootstrap, subjects)); + s.onNext(new ClientWebSocketDuplexConnection(ch, subjects)); s.onComplete(); } else { s.onError(handshakeFuture.cause()); @@ -163,6 +160,11 @@ public void onComplete() { }); } + @Override + public double availability() { + return channel.isOpen() ? 1.0 : 0.0; + } + @Override public void close() throws IOException { channel.close(); @@ -170,17 +172,16 @@ public void close() throws IOException { public String toString() { if (channel == null) { - return getClass().getName() + ":channel=null"; + return "ClientWebSocketDuplexConnection(channel=null)"; } - return getClass().getName() + ":channel=[" + - "remoteAddress=" + channel.remoteAddress() + "," + - "isActive=" + channel.isActive() + "," + - "isOpen=" + channel.isOpen() + "," + - "isRegistered=" + channel.isRegistered() + "," + - "isWritable=" + channel.isWritable() + "," + - "channelId=" + channel.id().asLongText() + - "]"; + return "ClientWebSocketDuplexConnection(channel=[" + + "remoteAddress=" + channel.remoteAddress() + + ", isActive=" + channel.isActive() + + ", isOpen=" + channel.isOpen() + + ", isRegistered=" + channel.isRegistered() + + ", channelId=" + channel.id().asLongText() + + "])"; } } diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketFactory.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketConnector.java similarity index 85% rename from reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketFactory.java rename to reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketConnector.java index f8c73bc4d..34458bd52 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketFactory.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketConnector.java @@ -16,10 +16,7 @@ package io.reactivesocket.netty.websocket.client; import io.netty.channel.EventLoopGroup; -import io.reactivesocket.ConnectionSetupPayload; -import io.reactivesocket.DefaultReactiveSocket; -import io.reactivesocket.ReactiveSocket; -import io.reactivesocket.ReactiveSocketFactory; +import io.reactivesocket.*; import io.reactivesocket.rx.Completable; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; @@ -36,15 +33,15 @@ /** * An implementation of {@link ReactiveSocketFactory} that creates Netty WebSocket ReactiveSockets. */ -public class WebSocketReactiveSocketFactory implements ReactiveSocketFactory { - private static final Logger logger = LoggerFactory.getLogger(WebSocketReactiveSocketFactory.class); +public class WebSocketReactiveSocketConnector implements ReactiveSocketConnector { + private static final Logger logger = LoggerFactory.getLogger(WebSocketReactiveSocketConnector.class); private final ConnectionSetupPayload connectionSetupPayload; private final Consumer errorStream; private final String path; private final EventLoopGroup eventLoopGroup; - public WebSocketReactiveSocketFactory(String path, EventLoopGroup eventLoopGroup, ConnectionSetupPayload connectionSetupPayload, Consumer errorStream) { + public WebSocketReactiveSocketConnector(String path, EventLoopGroup eventLoopGroup, ConnectionSetupPayload connectionSetupPayload, Consumer errorStream) { this.connectionSetupPayload = connectionSetupPayload; this.errorStream = errorStream; this.path = path; @@ -52,7 +49,7 @@ public WebSocketReactiveSocketFactory(String path, EventLoopGroup eventLoopGroup } @Override - public Publisher call(SocketAddress address) { + public Publisher connect(SocketAddress address) { if (address instanceof InetSocketAddress) { Publisher connection = ClientWebSocketDuplexConnection.create((InetSocketAddress)address, path, eventLoopGroup); diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ServerWebSocketDuplexConnection.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ServerWebSocketDuplexConnection.java index 8e4f8b1d4..a1ae2c2f5 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ServerWebSocketDuplexConnection.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/server/ServerWebSocketDuplexConnection.java @@ -96,6 +96,11 @@ public void onComplete() { }); } + @Override + public double availability() { + return ctx.channel().isOpen() ? 1.0 : 0.0; + } + @Override public void close() throws IOException { diff --git a/reactivesocket-test/src/main/java/io/reactivesocket/test/TestUtil.java b/reactivesocket-test/src/main/java/io/reactivesocket/test/TestUtil.java index f2713e14f..6100102af 100644 --- a/reactivesocket-test/src/main/java/io/reactivesocket/test/TestUtil.java +++ b/reactivesocket-test/src/main/java/io/reactivesocket/test/TestUtil.java @@ -19,9 +19,16 @@ import io.reactivesocket.FrameType; import io.reactivesocket.Payload; import org.agrona.MutableDirectBuffer; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; public class TestUtil { From 8bb71c00a7686f67c250e6d8e2541edec0d94a3b Mon Sep 17 00:00:00 2001 From: Steve Gury Date: Wed, 8 Jun 2016 09:47:59 -0700 Subject: [PATCH 103/105] Fix Javadoc for implementations of ReactiveSocketConnector --- .../websocket/client/WebSocketReactiveSocketConnector.java | 2 +- .../netty/tcp/client/TcpReactiveSocketConnector.java | 2 +- .../websocket/client/WebSocketReactiveSocketConnector.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketConnector.java b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketConnector.java index 01a6269dd..51ce9120d 100644 --- a/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketConnector.java +++ b/reactivesocket-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketConnector.java @@ -31,7 +31,7 @@ import java.util.function.Consumer; /** - * An implementation of {@link ReactiveSocketFactory} that creates JSR-356 WebSocket ReactiveSockets. + * An implementation of {@link ReactiveSocketConnector} that creates JSR-356 WebSocket ReactiveSockets. */ public class WebSocketReactiveSocketConnector implements ReactiveSocketConnector { private static final Logger logger = LoggerFactory.getLogger(WebSocketReactiveSocketConnector.class); diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketConnector.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketConnector.java index 4d5d38e42..2bcc3a197 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketConnector.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketConnector.java @@ -26,7 +26,7 @@ import java.util.function.Consumer; /** - * An implementation of {@link ReactiveSocketFactory} that creates Netty WebSocket ReactiveSockets. + * An implementation of {@link ReactiveSocketConnecot} that creates Netty TCP ReactiveSockets. */ public class TcpReactiveSocketConnector implements ReactiveSocketConnector { private final ConnectionSetupPayload connectionSetupPayload; diff --git a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketConnector.java b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketConnector.java index 34458bd52..d14c540fc 100644 --- a/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketConnector.java +++ b/reactivesocket-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketConnector.java @@ -31,7 +31,7 @@ import java.util.function.Consumer; /** - * An implementation of {@link ReactiveSocketFactory} that creates Netty WebSocket ReactiveSockets. + * An implementation of {@link ReactiveSocketConnector} that creates Netty WebSocket ReactiveSockets. */ public class WebSocketReactiveSocketConnector implements ReactiveSocketConnector { private static final Logger logger = LoggerFactory.getLogger(WebSocketReactiveSocketConnector.class); From 9a300c756ad80ffa5d03654cde2b9fee3a05d3f2 Mon Sep 17 00:00:00 2001 From: Steve Gury Date: Wed, 8 Jun 2016 10:01:09 -0700 Subject: [PATCH 104/105] Migrate root project into a subproject --- build.gradle | 50 ++++++++++--------- core/build.gradle | 0 .../ConnectionSetupHandler.java | 0 .../ConnectionSetupPayload.java | 0 .../reactivesocket/DefaultReactiveSocket.java | 0 .../io/reactivesocket/DuplexConnection.java | 0 .../main/java/io/reactivesocket/Frame.java | 0 .../java/io/reactivesocket/FrameType.java | 0 .../java/io/reactivesocket/LeaseGovernor.java | 0 .../main/java/io/reactivesocket/Payload.java | 0 .../io/reactivesocket/ReactiveSocket.java | 0 .../ReactiveSocketConnector.java | 0 .../reactivesocket/ReactiveSocketFactory.java | 0 .../io/reactivesocket/RequestHandler.java | 0 .../exceptions/ApplicationException.java | 0 .../exceptions/CancelException.java | 0 .../exceptions/ConnectionException.java | 0 .../reactivesocket/exceptions/Exceptions.java | 0 .../exceptions/InvalidRequestException.java | 0 .../exceptions/InvalidSetupException.java | 0 .../exceptions/RejectedException.java | 0 .../exceptions/RejectedSetupException.java | 0 .../reactivesocket/exceptions/Retryable.java | 0 .../exceptions/SetupException.java | 0 .../exceptions/TransportException.java | 0 .../exceptions/UnsupportedSetupException.java | 0 .../internal/FragmentedPublisher.java | 0 .../internal/PublisherUtils.java | 0 .../io/reactivesocket/internal/Requester.java | 0 .../io/reactivesocket/internal/Responder.java | 0 .../internal/UnicastSubject.java | 0 .../internal/frame/ByteBufferUtil.java | 0 .../internal/frame/ErrorFrameFlyweight.java | 0 .../internal/frame/FrameHeaderFlyweight.java | 0 .../internal/frame/FramePool.java | 0 .../frame/KeepaliveFrameFlyweight.java | 0 .../internal/frame/LeaseFrameFlyweight.java | 0 .../internal/frame/PayloadBuilder.java | 0 .../internal/frame/PayloadFragmenter.java | 0 .../internal/frame/PayloadReassembler.java | 0 .../internal/frame/RequestFrameFlyweight.java | 0 .../frame/RequestNFrameFlyweight.java | 0 .../internal/frame/SetupFrameFlyweight.java | 0 .../internal/frame/ThreadLocalFramePool.java | 0 .../internal/frame/ThreadSafeFramePool.java | 0 .../internal/frame/UnpooledFrame.java | 0 .../rx/AppendOnlyLinkedArrayList.java | 0 .../internal/rx/BackpressureHelper.java | 0 .../internal/rx/BackpressureUtils.java | 0 .../internal/rx/BaseArrayQueue.java | 0 .../internal/rx/BaseLinkedQueue.java | 0 .../internal/rx/BooleanDisposable.java | 0 .../internal/rx/CompositeCompletable.java | 0 .../internal/rx/CompositeDisposable.java | 0 .../internal/rx/EmptyDisposable.java | 0 .../internal/rx/EmptySubscription.java | 0 .../internal/rx/LinkedQueueNode.java | 0 .../internal/rx/MpscLinkedQueue.java | 0 .../internal/rx/NotificationLite.java | 0 .../internal/rx/OperatorConcatMap.java | 0 .../io/reactivesocket/internal/rx/Pow2.java | 0 .../internal/rx/QueueDrainHelper.java | 0 .../io/reactivesocket/internal/rx/README.md | 0 .../internal/rx/SerializedSubscriber.java | 0 .../internal/rx/SpscArrayQueue.java | 0 .../internal/rx/SpscExactArrayQueue.java | 0 .../internal/rx/SubscriptionArbiter.java | 0 .../internal/rx/SubscriptionHelper.java | 0 .../lease/FairLeaseGovernor.java | 0 .../lease/NullLeaseGovernor.java | 0 .../lease/UnlimitedLeaseGovernor.java | 0 .../io/reactivesocket/rx/Completable.java | 0 .../java/io/reactivesocket/rx/Disposable.java | 0 .../java/io/reactivesocket/rx/Observable.java | 0 .../java/io/reactivesocket/rx/Observer.java | 0 .../main/java/io/reactivesocket/rx/README.md | 0 .../util/ReactiveSocketProxy.java | 0 .../java/io/reactivesocket/util/Unsafe.java | 0 .../java/io/reactivesocket/FramePerf.java | 0 .../perf/java/io/reactivesocket/README.md | 0 .../io/reactivesocket/ReactiveSocketPerf.java | 0 .../perfutil/PerfTestConnection.java | 0 .../PerfUnicastSubjectNoBackpressure.java | 0 .../java/io/reactivesocket/FrameTest.java | 0 .../io/reactivesocket/LatchedCompletable.java | 0 .../java/io/reactivesocket/LeaseTest.java | 0 .../io/reactivesocket/ReactiveSocketTest.java | 0 .../io/reactivesocket/SerializedEventBus.java | 0 .../io/reactivesocket/TestConnection.java | 0 .../TestConnectionWithControlledRequestN.java | 0 .../TestFlowControlRequestN.java | 0 .../reactivesocket/TestTransportRequestN.java | 0 .../test/java/io/reactivesocket/TestUtil.java | 0 .../internal/FragmenterTest.java | 0 .../internal/ReassemblerTest.java | 0 .../internal/RequesterTest.java | 0 .../internal/ResponderTest.java | 0 .../internal/UnicastSubjectTest.java | 0 settings.gradle | 1 + 99 files changed, 27 insertions(+), 24 deletions(-) create mode 100644 core/build.gradle rename {src => core/src}/main/java/io/reactivesocket/ConnectionSetupHandler.java (100%) rename {src => core/src}/main/java/io/reactivesocket/ConnectionSetupPayload.java (100%) rename {src => core/src}/main/java/io/reactivesocket/DefaultReactiveSocket.java (100%) rename {src => core/src}/main/java/io/reactivesocket/DuplexConnection.java (100%) rename {src => core/src}/main/java/io/reactivesocket/Frame.java (100%) rename {src => core/src}/main/java/io/reactivesocket/FrameType.java (100%) rename {src => core/src}/main/java/io/reactivesocket/LeaseGovernor.java (100%) rename {src => core/src}/main/java/io/reactivesocket/Payload.java (100%) rename {src => core/src}/main/java/io/reactivesocket/ReactiveSocket.java (100%) rename {src => core/src}/main/java/io/reactivesocket/ReactiveSocketConnector.java (100%) rename {src => core/src}/main/java/io/reactivesocket/ReactiveSocketFactory.java (100%) rename {src => core/src}/main/java/io/reactivesocket/RequestHandler.java (100%) rename {src => core/src}/main/java/io/reactivesocket/exceptions/ApplicationException.java (100%) rename {src => core/src}/main/java/io/reactivesocket/exceptions/CancelException.java (100%) rename {src => core/src}/main/java/io/reactivesocket/exceptions/ConnectionException.java (100%) rename {src => core/src}/main/java/io/reactivesocket/exceptions/Exceptions.java (100%) rename {src => core/src}/main/java/io/reactivesocket/exceptions/InvalidRequestException.java (100%) rename {src => core/src}/main/java/io/reactivesocket/exceptions/InvalidSetupException.java (100%) rename {src => core/src}/main/java/io/reactivesocket/exceptions/RejectedException.java (100%) rename {src => core/src}/main/java/io/reactivesocket/exceptions/RejectedSetupException.java (100%) rename {src => core/src}/main/java/io/reactivesocket/exceptions/Retryable.java (100%) rename {src => core/src}/main/java/io/reactivesocket/exceptions/SetupException.java (100%) rename {src => core/src}/main/java/io/reactivesocket/exceptions/TransportException.java (100%) rename {src => core/src}/main/java/io/reactivesocket/exceptions/UnsupportedSetupException.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/FragmentedPublisher.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/PublisherUtils.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/Requester.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/Responder.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/UnicastSubject.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/frame/ByteBufferUtil.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/frame/ErrorFrameFlyweight.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/frame/FrameHeaderFlyweight.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/frame/FramePool.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/frame/KeepaliveFrameFlyweight.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/frame/LeaseFrameFlyweight.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/frame/PayloadBuilder.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/frame/PayloadFragmenter.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/frame/PayloadReassembler.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/frame/RequestFrameFlyweight.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/frame/RequestNFrameFlyweight.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/frame/SetupFrameFlyweight.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/frame/ThreadLocalFramePool.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/frame/ThreadSafeFramePool.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/frame/UnpooledFrame.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/rx/AppendOnlyLinkedArrayList.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/rx/BackpressureHelper.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/rx/BackpressureUtils.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/rx/BaseArrayQueue.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/rx/BaseLinkedQueue.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/rx/BooleanDisposable.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/rx/CompositeCompletable.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/rx/CompositeDisposable.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/rx/EmptyDisposable.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/rx/EmptySubscription.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/rx/LinkedQueueNode.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/rx/MpscLinkedQueue.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/rx/NotificationLite.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/rx/OperatorConcatMap.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/rx/Pow2.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/rx/QueueDrainHelper.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/rx/README.md (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/rx/SerializedSubscriber.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/rx/SpscArrayQueue.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/rx/SpscExactArrayQueue.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/rx/SubscriptionArbiter.java (100%) rename {src => core/src}/main/java/io/reactivesocket/internal/rx/SubscriptionHelper.java (100%) rename {src => core/src}/main/java/io/reactivesocket/lease/FairLeaseGovernor.java (100%) rename {src => core/src}/main/java/io/reactivesocket/lease/NullLeaseGovernor.java (100%) rename {src => core/src}/main/java/io/reactivesocket/lease/UnlimitedLeaseGovernor.java (100%) rename {src => core/src}/main/java/io/reactivesocket/rx/Completable.java (100%) rename {src => core/src}/main/java/io/reactivesocket/rx/Disposable.java (100%) rename {src => core/src}/main/java/io/reactivesocket/rx/Observable.java (100%) rename {src => core/src}/main/java/io/reactivesocket/rx/Observer.java (100%) rename {src => core/src}/main/java/io/reactivesocket/rx/README.md (100%) rename {src => core/src}/main/java/io/reactivesocket/util/ReactiveSocketProxy.java (100%) rename {src => core/src}/main/java/io/reactivesocket/util/Unsafe.java (100%) rename {src => core/src}/perf/java/io/reactivesocket/FramePerf.java (100%) rename {src => core/src}/perf/java/io/reactivesocket/README.md (100%) rename {src => core/src}/perf/java/io/reactivesocket/ReactiveSocketPerf.java (100%) rename {src => core/src}/perf/java/io/reactivesocket/perfutil/PerfTestConnection.java (100%) rename {src => core/src}/perf/java/io/reactivesocket/perfutil/PerfUnicastSubjectNoBackpressure.java (100%) rename {src => core/src}/test/java/io/reactivesocket/FrameTest.java (100%) rename {src => core/src}/test/java/io/reactivesocket/LatchedCompletable.java (100%) rename {src => core/src}/test/java/io/reactivesocket/LeaseTest.java (100%) rename {src => core/src}/test/java/io/reactivesocket/ReactiveSocketTest.java (100%) rename {src => core/src}/test/java/io/reactivesocket/SerializedEventBus.java (100%) rename {src => core/src}/test/java/io/reactivesocket/TestConnection.java (100%) rename {src => core/src}/test/java/io/reactivesocket/TestConnectionWithControlledRequestN.java (100%) rename {src => core/src}/test/java/io/reactivesocket/TestFlowControlRequestN.java (100%) rename {src => core/src}/test/java/io/reactivesocket/TestTransportRequestN.java (100%) rename {src => core/src}/test/java/io/reactivesocket/TestUtil.java (100%) rename {src => core/src}/test/java/io/reactivesocket/internal/FragmenterTest.java (100%) rename {src => core/src}/test/java/io/reactivesocket/internal/ReassemblerTest.java (100%) rename {src => core/src}/test/java/io/reactivesocket/internal/RequesterTest.java (100%) rename {src => core/src}/test/java/io/reactivesocket/internal/ResponderTest.java (100%) rename {src => core/src}/test/java/io/reactivesocket/internal/UnicastSubjectTest.java (100%) diff --git a/build.gradle b/build.gradle index 8fbcaa6c4..9f7bac4dc 100644 --- a/build.gradle +++ b/build.gradle @@ -1,27 +1,38 @@ buildscript { - repositories { - jcenter() - } - - dependencies { classpath 'io.reactivesocket:gradle-nebula-plugin-reactivesocket:1.0.5' } + repositories { jcenter() } + dependencies { classpath 'io.reactivesocket:gradle-nebula-plugin-reactivesocket:1.0.6' } } description = 'ReactiveSocket: stream oriented messaging passing with Reactive Stream semantics.' apply plugin: 'reactivesocket-project' -apply plugin: 'java' -repositories { - maven { url 'https://oss.jfrog.org/libs-snapshot' } -} +subprojects { + apply plugin: 'reactivesocket-project' + apply plugin: 'java' + + compileJava { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } + + repositories { + jcenter() + maven { url 'https://oss.jfrog.org/libs-snapshot' } + } + + dependencies { + compile 'org.reactivestreams:reactive-streams:1.0.0.final' + compile 'org.agrona:Agrona:0.4.13' -dependencies { - compile 'org.reactivestreams:reactive-streams:1.0.0.final' - compile 'org.agrona:Agrona:0.4.13' + testCompile 'io.reactivex:rxjava:2.0.0-DP0-20151003.214425-143' + testCompile 'junit:junit:4.12' + testCompile 'org.mockito:mockito-core:1.10.19' + } - testCompile 'io.reactivex:rxjava:2.0.0-DP0-20151003.214425-143' - testCompile 'junit:junit:4.12' - testCompile 'org.mockito:mockito-core:1.10.19' + test { + testLogging.showStandardStreams = true + } } // support for snapshot/final releases via versioned branch names like 1.x @@ -33,12 +44,3 @@ nebulaRelease { if (project.hasProperty('release.useLastTag')) { tasks.prepare.enabled = false } - -test { - testLogging.showStandardStreams = true -} - -compileJava { - sourceCompatibility = 1.8 - targetCompatibility = 1.8 -} \ No newline at end of file diff --git a/core/build.gradle b/core/build.gradle new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/java/io/reactivesocket/ConnectionSetupHandler.java b/core/src/main/java/io/reactivesocket/ConnectionSetupHandler.java similarity index 100% rename from src/main/java/io/reactivesocket/ConnectionSetupHandler.java rename to core/src/main/java/io/reactivesocket/ConnectionSetupHandler.java diff --git a/src/main/java/io/reactivesocket/ConnectionSetupPayload.java b/core/src/main/java/io/reactivesocket/ConnectionSetupPayload.java similarity index 100% rename from src/main/java/io/reactivesocket/ConnectionSetupPayload.java rename to core/src/main/java/io/reactivesocket/ConnectionSetupPayload.java diff --git a/src/main/java/io/reactivesocket/DefaultReactiveSocket.java b/core/src/main/java/io/reactivesocket/DefaultReactiveSocket.java similarity index 100% rename from src/main/java/io/reactivesocket/DefaultReactiveSocket.java rename to core/src/main/java/io/reactivesocket/DefaultReactiveSocket.java diff --git a/src/main/java/io/reactivesocket/DuplexConnection.java b/core/src/main/java/io/reactivesocket/DuplexConnection.java similarity index 100% rename from src/main/java/io/reactivesocket/DuplexConnection.java rename to core/src/main/java/io/reactivesocket/DuplexConnection.java diff --git a/src/main/java/io/reactivesocket/Frame.java b/core/src/main/java/io/reactivesocket/Frame.java similarity index 100% rename from src/main/java/io/reactivesocket/Frame.java rename to core/src/main/java/io/reactivesocket/Frame.java diff --git a/src/main/java/io/reactivesocket/FrameType.java b/core/src/main/java/io/reactivesocket/FrameType.java similarity index 100% rename from src/main/java/io/reactivesocket/FrameType.java rename to core/src/main/java/io/reactivesocket/FrameType.java diff --git a/src/main/java/io/reactivesocket/LeaseGovernor.java b/core/src/main/java/io/reactivesocket/LeaseGovernor.java similarity index 100% rename from src/main/java/io/reactivesocket/LeaseGovernor.java rename to core/src/main/java/io/reactivesocket/LeaseGovernor.java diff --git a/src/main/java/io/reactivesocket/Payload.java b/core/src/main/java/io/reactivesocket/Payload.java similarity index 100% rename from src/main/java/io/reactivesocket/Payload.java rename to core/src/main/java/io/reactivesocket/Payload.java diff --git a/src/main/java/io/reactivesocket/ReactiveSocket.java b/core/src/main/java/io/reactivesocket/ReactiveSocket.java similarity index 100% rename from src/main/java/io/reactivesocket/ReactiveSocket.java rename to core/src/main/java/io/reactivesocket/ReactiveSocket.java diff --git a/src/main/java/io/reactivesocket/ReactiveSocketConnector.java b/core/src/main/java/io/reactivesocket/ReactiveSocketConnector.java similarity index 100% rename from src/main/java/io/reactivesocket/ReactiveSocketConnector.java rename to core/src/main/java/io/reactivesocket/ReactiveSocketConnector.java diff --git a/src/main/java/io/reactivesocket/ReactiveSocketFactory.java b/core/src/main/java/io/reactivesocket/ReactiveSocketFactory.java similarity index 100% rename from src/main/java/io/reactivesocket/ReactiveSocketFactory.java rename to core/src/main/java/io/reactivesocket/ReactiveSocketFactory.java diff --git a/src/main/java/io/reactivesocket/RequestHandler.java b/core/src/main/java/io/reactivesocket/RequestHandler.java similarity index 100% rename from src/main/java/io/reactivesocket/RequestHandler.java rename to core/src/main/java/io/reactivesocket/RequestHandler.java diff --git a/src/main/java/io/reactivesocket/exceptions/ApplicationException.java b/core/src/main/java/io/reactivesocket/exceptions/ApplicationException.java similarity index 100% rename from src/main/java/io/reactivesocket/exceptions/ApplicationException.java rename to core/src/main/java/io/reactivesocket/exceptions/ApplicationException.java diff --git a/src/main/java/io/reactivesocket/exceptions/CancelException.java b/core/src/main/java/io/reactivesocket/exceptions/CancelException.java similarity index 100% rename from src/main/java/io/reactivesocket/exceptions/CancelException.java rename to core/src/main/java/io/reactivesocket/exceptions/CancelException.java diff --git a/src/main/java/io/reactivesocket/exceptions/ConnectionException.java b/core/src/main/java/io/reactivesocket/exceptions/ConnectionException.java similarity index 100% rename from src/main/java/io/reactivesocket/exceptions/ConnectionException.java rename to core/src/main/java/io/reactivesocket/exceptions/ConnectionException.java diff --git a/src/main/java/io/reactivesocket/exceptions/Exceptions.java b/core/src/main/java/io/reactivesocket/exceptions/Exceptions.java similarity index 100% rename from src/main/java/io/reactivesocket/exceptions/Exceptions.java rename to core/src/main/java/io/reactivesocket/exceptions/Exceptions.java diff --git a/src/main/java/io/reactivesocket/exceptions/InvalidRequestException.java b/core/src/main/java/io/reactivesocket/exceptions/InvalidRequestException.java similarity index 100% rename from src/main/java/io/reactivesocket/exceptions/InvalidRequestException.java rename to core/src/main/java/io/reactivesocket/exceptions/InvalidRequestException.java diff --git a/src/main/java/io/reactivesocket/exceptions/InvalidSetupException.java b/core/src/main/java/io/reactivesocket/exceptions/InvalidSetupException.java similarity index 100% rename from src/main/java/io/reactivesocket/exceptions/InvalidSetupException.java rename to core/src/main/java/io/reactivesocket/exceptions/InvalidSetupException.java diff --git a/src/main/java/io/reactivesocket/exceptions/RejectedException.java b/core/src/main/java/io/reactivesocket/exceptions/RejectedException.java similarity index 100% rename from src/main/java/io/reactivesocket/exceptions/RejectedException.java rename to core/src/main/java/io/reactivesocket/exceptions/RejectedException.java diff --git a/src/main/java/io/reactivesocket/exceptions/RejectedSetupException.java b/core/src/main/java/io/reactivesocket/exceptions/RejectedSetupException.java similarity index 100% rename from src/main/java/io/reactivesocket/exceptions/RejectedSetupException.java rename to core/src/main/java/io/reactivesocket/exceptions/RejectedSetupException.java diff --git a/src/main/java/io/reactivesocket/exceptions/Retryable.java b/core/src/main/java/io/reactivesocket/exceptions/Retryable.java similarity index 100% rename from src/main/java/io/reactivesocket/exceptions/Retryable.java rename to core/src/main/java/io/reactivesocket/exceptions/Retryable.java diff --git a/src/main/java/io/reactivesocket/exceptions/SetupException.java b/core/src/main/java/io/reactivesocket/exceptions/SetupException.java similarity index 100% rename from src/main/java/io/reactivesocket/exceptions/SetupException.java rename to core/src/main/java/io/reactivesocket/exceptions/SetupException.java diff --git a/src/main/java/io/reactivesocket/exceptions/TransportException.java b/core/src/main/java/io/reactivesocket/exceptions/TransportException.java similarity index 100% rename from src/main/java/io/reactivesocket/exceptions/TransportException.java rename to core/src/main/java/io/reactivesocket/exceptions/TransportException.java diff --git a/src/main/java/io/reactivesocket/exceptions/UnsupportedSetupException.java b/core/src/main/java/io/reactivesocket/exceptions/UnsupportedSetupException.java similarity index 100% rename from src/main/java/io/reactivesocket/exceptions/UnsupportedSetupException.java rename to core/src/main/java/io/reactivesocket/exceptions/UnsupportedSetupException.java diff --git a/src/main/java/io/reactivesocket/internal/FragmentedPublisher.java b/core/src/main/java/io/reactivesocket/internal/FragmentedPublisher.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/FragmentedPublisher.java rename to core/src/main/java/io/reactivesocket/internal/FragmentedPublisher.java diff --git a/src/main/java/io/reactivesocket/internal/PublisherUtils.java b/core/src/main/java/io/reactivesocket/internal/PublisherUtils.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/PublisherUtils.java rename to core/src/main/java/io/reactivesocket/internal/PublisherUtils.java diff --git a/src/main/java/io/reactivesocket/internal/Requester.java b/core/src/main/java/io/reactivesocket/internal/Requester.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/Requester.java rename to core/src/main/java/io/reactivesocket/internal/Requester.java diff --git a/src/main/java/io/reactivesocket/internal/Responder.java b/core/src/main/java/io/reactivesocket/internal/Responder.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/Responder.java rename to core/src/main/java/io/reactivesocket/internal/Responder.java diff --git a/src/main/java/io/reactivesocket/internal/UnicastSubject.java b/core/src/main/java/io/reactivesocket/internal/UnicastSubject.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/UnicastSubject.java rename to core/src/main/java/io/reactivesocket/internal/UnicastSubject.java diff --git a/src/main/java/io/reactivesocket/internal/frame/ByteBufferUtil.java b/core/src/main/java/io/reactivesocket/internal/frame/ByteBufferUtil.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/frame/ByteBufferUtil.java rename to core/src/main/java/io/reactivesocket/internal/frame/ByteBufferUtil.java diff --git a/src/main/java/io/reactivesocket/internal/frame/ErrorFrameFlyweight.java b/core/src/main/java/io/reactivesocket/internal/frame/ErrorFrameFlyweight.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/frame/ErrorFrameFlyweight.java rename to core/src/main/java/io/reactivesocket/internal/frame/ErrorFrameFlyweight.java diff --git a/src/main/java/io/reactivesocket/internal/frame/FrameHeaderFlyweight.java b/core/src/main/java/io/reactivesocket/internal/frame/FrameHeaderFlyweight.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/frame/FrameHeaderFlyweight.java rename to core/src/main/java/io/reactivesocket/internal/frame/FrameHeaderFlyweight.java diff --git a/src/main/java/io/reactivesocket/internal/frame/FramePool.java b/core/src/main/java/io/reactivesocket/internal/frame/FramePool.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/frame/FramePool.java rename to core/src/main/java/io/reactivesocket/internal/frame/FramePool.java diff --git a/src/main/java/io/reactivesocket/internal/frame/KeepaliveFrameFlyweight.java b/core/src/main/java/io/reactivesocket/internal/frame/KeepaliveFrameFlyweight.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/frame/KeepaliveFrameFlyweight.java rename to core/src/main/java/io/reactivesocket/internal/frame/KeepaliveFrameFlyweight.java diff --git a/src/main/java/io/reactivesocket/internal/frame/LeaseFrameFlyweight.java b/core/src/main/java/io/reactivesocket/internal/frame/LeaseFrameFlyweight.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/frame/LeaseFrameFlyweight.java rename to core/src/main/java/io/reactivesocket/internal/frame/LeaseFrameFlyweight.java diff --git a/src/main/java/io/reactivesocket/internal/frame/PayloadBuilder.java b/core/src/main/java/io/reactivesocket/internal/frame/PayloadBuilder.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/frame/PayloadBuilder.java rename to core/src/main/java/io/reactivesocket/internal/frame/PayloadBuilder.java diff --git a/src/main/java/io/reactivesocket/internal/frame/PayloadFragmenter.java b/core/src/main/java/io/reactivesocket/internal/frame/PayloadFragmenter.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/frame/PayloadFragmenter.java rename to core/src/main/java/io/reactivesocket/internal/frame/PayloadFragmenter.java diff --git a/src/main/java/io/reactivesocket/internal/frame/PayloadReassembler.java b/core/src/main/java/io/reactivesocket/internal/frame/PayloadReassembler.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/frame/PayloadReassembler.java rename to core/src/main/java/io/reactivesocket/internal/frame/PayloadReassembler.java diff --git a/src/main/java/io/reactivesocket/internal/frame/RequestFrameFlyweight.java b/core/src/main/java/io/reactivesocket/internal/frame/RequestFrameFlyweight.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/frame/RequestFrameFlyweight.java rename to core/src/main/java/io/reactivesocket/internal/frame/RequestFrameFlyweight.java diff --git a/src/main/java/io/reactivesocket/internal/frame/RequestNFrameFlyweight.java b/core/src/main/java/io/reactivesocket/internal/frame/RequestNFrameFlyweight.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/frame/RequestNFrameFlyweight.java rename to core/src/main/java/io/reactivesocket/internal/frame/RequestNFrameFlyweight.java diff --git a/src/main/java/io/reactivesocket/internal/frame/SetupFrameFlyweight.java b/core/src/main/java/io/reactivesocket/internal/frame/SetupFrameFlyweight.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/frame/SetupFrameFlyweight.java rename to core/src/main/java/io/reactivesocket/internal/frame/SetupFrameFlyweight.java diff --git a/src/main/java/io/reactivesocket/internal/frame/ThreadLocalFramePool.java b/core/src/main/java/io/reactivesocket/internal/frame/ThreadLocalFramePool.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/frame/ThreadLocalFramePool.java rename to core/src/main/java/io/reactivesocket/internal/frame/ThreadLocalFramePool.java diff --git a/src/main/java/io/reactivesocket/internal/frame/ThreadSafeFramePool.java b/core/src/main/java/io/reactivesocket/internal/frame/ThreadSafeFramePool.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/frame/ThreadSafeFramePool.java rename to core/src/main/java/io/reactivesocket/internal/frame/ThreadSafeFramePool.java diff --git a/src/main/java/io/reactivesocket/internal/frame/UnpooledFrame.java b/core/src/main/java/io/reactivesocket/internal/frame/UnpooledFrame.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/frame/UnpooledFrame.java rename to core/src/main/java/io/reactivesocket/internal/frame/UnpooledFrame.java diff --git a/src/main/java/io/reactivesocket/internal/rx/AppendOnlyLinkedArrayList.java b/core/src/main/java/io/reactivesocket/internal/rx/AppendOnlyLinkedArrayList.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/rx/AppendOnlyLinkedArrayList.java rename to core/src/main/java/io/reactivesocket/internal/rx/AppendOnlyLinkedArrayList.java diff --git a/src/main/java/io/reactivesocket/internal/rx/BackpressureHelper.java b/core/src/main/java/io/reactivesocket/internal/rx/BackpressureHelper.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/rx/BackpressureHelper.java rename to core/src/main/java/io/reactivesocket/internal/rx/BackpressureHelper.java diff --git a/src/main/java/io/reactivesocket/internal/rx/BackpressureUtils.java b/core/src/main/java/io/reactivesocket/internal/rx/BackpressureUtils.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/rx/BackpressureUtils.java rename to core/src/main/java/io/reactivesocket/internal/rx/BackpressureUtils.java diff --git a/src/main/java/io/reactivesocket/internal/rx/BaseArrayQueue.java b/core/src/main/java/io/reactivesocket/internal/rx/BaseArrayQueue.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/rx/BaseArrayQueue.java rename to core/src/main/java/io/reactivesocket/internal/rx/BaseArrayQueue.java diff --git a/src/main/java/io/reactivesocket/internal/rx/BaseLinkedQueue.java b/core/src/main/java/io/reactivesocket/internal/rx/BaseLinkedQueue.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/rx/BaseLinkedQueue.java rename to core/src/main/java/io/reactivesocket/internal/rx/BaseLinkedQueue.java diff --git a/src/main/java/io/reactivesocket/internal/rx/BooleanDisposable.java b/core/src/main/java/io/reactivesocket/internal/rx/BooleanDisposable.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/rx/BooleanDisposable.java rename to core/src/main/java/io/reactivesocket/internal/rx/BooleanDisposable.java diff --git a/src/main/java/io/reactivesocket/internal/rx/CompositeCompletable.java b/core/src/main/java/io/reactivesocket/internal/rx/CompositeCompletable.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/rx/CompositeCompletable.java rename to core/src/main/java/io/reactivesocket/internal/rx/CompositeCompletable.java diff --git a/src/main/java/io/reactivesocket/internal/rx/CompositeDisposable.java b/core/src/main/java/io/reactivesocket/internal/rx/CompositeDisposable.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/rx/CompositeDisposable.java rename to core/src/main/java/io/reactivesocket/internal/rx/CompositeDisposable.java diff --git a/src/main/java/io/reactivesocket/internal/rx/EmptyDisposable.java b/core/src/main/java/io/reactivesocket/internal/rx/EmptyDisposable.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/rx/EmptyDisposable.java rename to core/src/main/java/io/reactivesocket/internal/rx/EmptyDisposable.java diff --git a/src/main/java/io/reactivesocket/internal/rx/EmptySubscription.java b/core/src/main/java/io/reactivesocket/internal/rx/EmptySubscription.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/rx/EmptySubscription.java rename to core/src/main/java/io/reactivesocket/internal/rx/EmptySubscription.java diff --git a/src/main/java/io/reactivesocket/internal/rx/LinkedQueueNode.java b/core/src/main/java/io/reactivesocket/internal/rx/LinkedQueueNode.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/rx/LinkedQueueNode.java rename to core/src/main/java/io/reactivesocket/internal/rx/LinkedQueueNode.java diff --git a/src/main/java/io/reactivesocket/internal/rx/MpscLinkedQueue.java b/core/src/main/java/io/reactivesocket/internal/rx/MpscLinkedQueue.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/rx/MpscLinkedQueue.java rename to core/src/main/java/io/reactivesocket/internal/rx/MpscLinkedQueue.java diff --git a/src/main/java/io/reactivesocket/internal/rx/NotificationLite.java b/core/src/main/java/io/reactivesocket/internal/rx/NotificationLite.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/rx/NotificationLite.java rename to core/src/main/java/io/reactivesocket/internal/rx/NotificationLite.java diff --git a/src/main/java/io/reactivesocket/internal/rx/OperatorConcatMap.java b/core/src/main/java/io/reactivesocket/internal/rx/OperatorConcatMap.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/rx/OperatorConcatMap.java rename to core/src/main/java/io/reactivesocket/internal/rx/OperatorConcatMap.java diff --git a/src/main/java/io/reactivesocket/internal/rx/Pow2.java b/core/src/main/java/io/reactivesocket/internal/rx/Pow2.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/rx/Pow2.java rename to core/src/main/java/io/reactivesocket/internal/rx/Pow2.java diff --git a/src/main/java/io/reactivesocket/internal/rx/QueueDrainHelper.java b/core/src/main/java/io/reactivesocket/internal/rx/QueueDrainHelper.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/rx/QueueDrainHelper.java rename to core/src/main/java/io/reactivesocket/internal/rx/QueueDrainHelper.java diff --git a/src/main/java/io/reactivesocket/internal/rx/README.md b/core/src/main/java/io/reactivesocket/internal/rx/README.md similarity index 100% rename from src/main/java/io/reactivesocket/internal/rx/README.md rename to core/src/main/java/io/reactivesocket/internal/rx/README.md diff --git a/src/main/java/io/reactivesocket/internal/rx/SerializedSubscriber.java b/core/src/main/java/io/reactivesocket/internal/rx/SerializedSubscriber.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/rx/SerializedSubscriber.java rename to core/src/main/java/io/reactivesocket/internal/rx/SerializedSubscriber.java diff --git a/src/main/java/io/reactivesocket/internal/rx/SpscArrayQueue.java b/core/src/main/java/io/reactivesocket/internal/rx/SpscArrayQueue.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/rx/SpscArrayQueue.java rename to core/src/main/java/io/reactivesocket/internal/rx/SpscArrayQueue.java diff --git a/src/main/java/io/reactivesocket/internal/rx/SpscExactArrayQueue.java b/core/src/main/java/io/reactivesocket/internal/rx/SpscExactArrayQueue.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/rx/SpscExactArrayQueue.java rename to core/src/main/java/io/reactivesocket/internal/rx/SpscExactArrayQueue.java diff --git a/src/main/java/io/reactivesocket/internal/rx/SubscriptionArbiter.java b/core/src/main/java/io/reactivesocket/internal/rx/SubscriptionArbiter.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/rx/SubscriptionArbiter.java rename to core/src/main/java/io/reactivesocket/internal/rx/SubscriptionArbiter.java diff --git a/src/main/java/io/reactivesocket/internal/rx/SubscriptionHelper.java b/core/src/main/java/io/reactivesocket/internal/rx/SubscriptionHelper.java similarity index 100% rename from src/main/java/io/reactivesocket/internal/rx/SubscriptionHelper.java rename to core/src/main/java/io/reactivesocket/internal/rx/SubscriptionHelper.java diff --git a/src/main/java/io/reactivesocket/lease/FairLeaseGovernor.java b/core/src/main/java/io/reactivesocket/lease/FairLeaseGovernor.java similarity index 100% rename from src/main/java/io/reactivesocket/lease/FairLeaseGovernor.java rename to core/src/main/java/io/reactivesocket/lease/FairLeaseGovernor.java diff --git a/src/main/java/io/reactivesocket/lease/NullLeaseGovernor.java b/core/src/main/java/io/reactivesocket/lease/NullLeaseGovernor.java similarity index 100% rename from src/main/java/io/reactivesocket/lease/NullLeaseGovernor.java rename to core/src/main/java/io/reactivesocket/lease/NullLeaseGovernor.java diff --git a/src/main/java/io/reactivesocket/lease/UnlimitedLeaseGovernor.java b/core/src/main/java/io/reactivesocket/lease/UnlimitedLeaseGovernor.java similarity index 100% rename from src/main/java/io/reactivesocket/lease/UnlimitedLeaseGovernor.java rename to core/src/main/java/io/reactivesocket/lease/UnlimitedLeaseGovernor.java diff --git a/src/main/java/io/reactivesocket/rx/Completable.java b/core/src/main/java/io/reactivesocket/rx/Completable.java similarity index 100% rename from src/main/java/io/reactivesocket/rx/Completable.java rename to core/src/main/java/io/reactivesocket/rx/Completable.java diff --git a/src/main/java/io/reactivesocket/rx/Disposable.java b/core/src/main/java/io/reactivesocket/rx/Disposable.java similarity index 100% rename from src/main/java/io/reactivesocket/rx/Disposable.java rename to core/src/main/java/io/reactivesocket/rx/Disposable.java diff --git a/src/main/java/io/reactivesocket/rx/Observable.java b/core/src/main/java/io/reactivesocket/rx/Observable.java similarity index 100% rename from src/main/java/io/reactivesocket/rx/Observable.java rename to core/src/main/java/io/reactivesocket/rx/Observable.java diff --git a/src/main/java/io/reactivesocket/rx/Observer.java b/core/src/main/java/io/reactivesocket/rx/Observer.java similarity index 100% rename from src/main/java/io/reactivesocket/rx/Observer.java rename to core/src/main/java/io/reactivesocket/rx/Observer.java diff --git a/src/main/java/io/reactivesocket/rx/README.md b/core/src/main/java/io/reactivesocket/rx/README.md similarity index 100% rename from src/main/java/io/reactivesocket/rx/README.md rename to core/src/main/java/io/reactivesocket/rx/README.md diff --git a/src/main/java/io/reactivesocket/util/ReactiveSocketProxy.java b/core/src/main/java/io/reactivesocket/util/ReactiveSocketProxy.java similarity index 100% rename from src/main/java/io/reactivesocket/util/ReactiveSocketProxy.java rename to core/src/main/java/io/reactivesocket/util/ReactiveSocketProxy.java diff --git a/src/main/java/io/reactivesocket/util/Unsafe.java b/core/src/main/java/io/reactivesocket/util/Unsafe.java similarity index 100% rename from src/main/java/io/reactivesocket/util/Unsafe.java rename to core/src/main/java/io/reactivesocket/util/Unsafe.java diff --git a/src/perf/java/io/reactivesocket/FramePerf.java b/core/src/perf/java/io/reactivesocket/FramePerf.java similarity index 100% rename from src/perf/java/io/reactivesocket/FramePerf.java rename to core/src/perf/java/io/reactivesocket/FramePerf.java diff --git a/src/perf/java/io/reactivesocket/README.md b/core/src/perf/java/io/reactivesocket/README.md similarity index 100% rename from src/perf/java/io/reactivesocket/README.md rename to core/src/perf/java/io/reactivesocket/README.md diff --git a/src/perf/java/io/reactivesocket/ReactiveSocketPerf.java b/core/src/perf/java/io/reactivesocket/ReactiveSocketPerf.java similarity index 100% rename from src/perf/java/io/reactivesocket/ReactiveSocketPerf.java rename to core/src/perf/java/io/reactivesocket/ReactiveSocketPerf.java diff --git a/src/perf/java/io/reactivesocket/perfutil/PerfTestConnection.java b/core/src/perf/java/io/reactivesocket/perfutil/PerfTestConnection.java similarity index 100% rename from src/perf/java/io/reactivesocket/perfutil/PerfTestConnection.java rename to core/src/perf/java/io/reactivesocket/perfutil/PerfTestConnection.java diff --git a/src/perf/java/io/reactivesocket/perfutil/PerfUnicastSubjectNoBackpressure.java b/core/src/perf/java/io/reactivesocket/perfutil/PerfUnicastSubjectNoBackpressure.java similarity index 100% rename from src/perf/java/io/reactivesocket/perfutil/PerfUnicastSubjectNoBackpressure.java rename to core/src/perf/java/io/reactivesocket/perfutil/PerfUnicastSubjectNoBackpressure.java diff --git a/src/test/java/io/reactivesocket/FrameTest.java b/core/src/test/java/io/reactivesocket/FrameTest.java similarity index 100% rename from src/test/java/io/reactivesocket/FrameTest.java rename to core/src/test/java/io/reactivesocket/FrameTest.java diff --git a/src/test/java/io/reactivesocket/LatchedCompletable.java b/core/src/test/java/io/reactivesocket/LatchedCompletable.java similarity index 100% rename from src/test/java/io/reactivesocket/LatchedCompletable.java rename to core/src/test/java/io/reactivesocket/LatchedCompletable.java diff --git a/src/test/java/io/reactivesocket/LeaseTest.java b/core/src/test/java/io/reactivesocket/LeaseTest.java similarity index 100% rename from src/test/java/io/reactivesocket/LeaseTest.java rename to core/src/test/java/io/reactivesocket/LeaseTest.java diff --git a/src/test/java/io/reactivesocket/ReactiveSocketTest.java b/core/src/test/java/io/reactivesocket/ReactiveSocketTest.java similarity index 100% rename from src/test/java/io/reactivesocket/ReactiveSocketTest.java rename to core/src/test/java/io/reactivesocket/ReactiveSocketTest.java diff --git a/src/test/java/io/reactivesocket/SerializedEventBus.java b/core/src/test/java/io/reactivesocket/SerializedEventBus.java similarity index 100% rename from src/test/java/io/reactivesocket/SerializedEventBus.java rename to core/src/test/java/io/reactivesocket/SerializedEventBus.java diff --git a/src/test/java/io/reactivesocket/TestConnection.java b/core/src/test/java/io/reactivesocket/TestConnection.java similarity index 100% rename from src/test/java/io/reactivesocket/TestConnection.java rename to core/src/test/java/io/reactivesocket/TestConnection.java diff --git a/src/test/java/io/reactivesocket/TestConnectionWithControlledRequestN.java b/core/src/test/java/io/reactivesocket/TestConnectionWithControlledRequestN.java similarity index 100% rename from src/test/java/io/reactivesocket/TestConnectionWithControlledRequestN.java rename to core/src/test/java/io/reactivesocket/TestConnectionWithControlledRequestN.java diff --git a/src/test/java/io/reactivesocket/TestFlowControlRequestN.java b/core/src/test/java/io/reactivesocket/TestFlowControlRequestN.java similarity index 100% rename from src/test/java/io/reactivesocket/TestFlowControlRequestN.java rename to core/src/test/java/io/reactivesocket/TestFlowControlRequestN.java diff --git a/src/test/java/io/reactivesocket/TestTransportRequestN.java b/core/src/test/java/io/reactivesocket/TestTransportRequestN.java similarity index 100% rename from src/test/java/io/reactivesocket/TestTransportRequestN.java rename to core/src/test/java/io/reactivesocket/TestTransportRequestN.java diff --git a/src/test/java/io/reactivesocket/TestUtil.java b/core/src/test/java/io/reactivesocket/TestUtil.java similarity index 100% rename from src/test/java/io/reactivesocket/TestUtil.java rename to core/src/test/java/io/reactivesocket/TestUtil.java diff --git a/src/test/java/io/reactivesocket/internal/FragmenterTest.java b/core/src/test/java/io/reactivesocket/internal/FragmenterTest.java similarity index 100% rename from src/test/java/io/reactivesocket/internal/FragmenterTest.java rename to core/src/test/java/io/reactivesocket/internal/FragmenterTest.java diff --git a/src/test/java/io/reactivesocket/internal/ReassemblerTest.java b/core/src/test/java/io/reactivesocket/internal/ReassemblerTest.java similarity index 100% rename from src/test/java/io/reactivesocket/internal/ReassemblerTest.java rename to core/src/test/java/io/reactivesocket/internal/ReassemblerTest.java diff --git a/src/test/java/io/reactivesocket/internal/RequesterTest.java b/core/src/test/java/io/reactivesocket/internal/RequesterTest.java similarity index 100% rename from src/test/java/io/reactivesocket/internal/RequesterTest.java rename to core/src/test/java/io/reactivesocket/internal/RequesterTest.java diff --git a/src/test/java/io/reactivesocket/internal/ResponderTest.java b/core/src/test/java/io/reactivesocket/internal/ResponderTest.java similarity index 100% rename from src/test/java/io/reactivesocket/internal/ResponderTest.java rename to core/src/test/java/io/reactivesocket/internal/ResponderTest.java diff --git a/src/test/java/io/reactivesocket/internal/UnicastSubjectTest.java b/core/src/test/java/io/reactivesocket/internal/UnicastSubjectTest.java similarity index 100% rename from src/test/java/io/reactivesocket/internal/UnicastSubjectTest.java rename to core/src/test/java/io/reactivesocket/internal/UnicastSubjectTest.java diff --git a/settings.gradle b/settings.gradle index 5aea80f18..467842f00 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,2 @@ rootProject.name='reactivesocket' +include 'core' \ No newline at end of file From e539270f012cec88c13b2672fb2998c47467eaaa Mon Sep 17 00:00:00 2001 From: Steve Gury Date: Thu, 9 Jun 2016 14:56:20 -0700 Subject: [PATCH 105/105] Restore 'reactivesocket' artifcat prefix. In order to keep the reactivesocket name and simplify searching in maven central, rename all the 'rs-*' project into 'reactivesocket-*'. --- {rs-core => reactivesocket-core}/build.gradle | 0 .../io/reactivesocket/ConnectionSetupHandler.java | 0 .../io/reactivesocket/ConnectionSetupPayload.java | 0 .../io/reactivesocket/DefaultReactiveSocket.java | 0 .../java/io/reactivesocket/DuplexConnection.java | 0 .../src/main/java/io/reactivesocket/Frame.java | 0 .../src/main/java/io/reactivesocket/FrameType.java | 0 .../main/java/io/reactivesocket/LeaseGovernor.java | 0 .../src/main/java/io/reactivesocket/Payload.java | 0 .../java/io/reactivesocket/ReactiveSocket.java | 0 .../io/reactivesocket/ReactiveSocketConnector.java | 0 .../io/reactivesocket/ReactiveSocketFactory.java | 0 .../java/io/reactivesocket/RequestHandler.java | 0 .../exceptions/ApplicationException.java | 0 .../reactivesocket/exceptions/CancelException.java | 0 .../exceptions/ConnectionException.java | 0 .../io/reactivesocket/exceptions/Exceptions.java | 0 .../exceptions/InvalidRequestException.java | 0 .../exceptions/InvalidSetupException.java | 0 .../exceptions/RejectedException.java | 0 .../exceptions/RejectedSetupException.java | 0 .../io/reactivesocket/exceptions/Retryable.java | 0 .../reactivesocket/exceptions/SetupException.java | 0 .../exceptions/TransportException.java | 0 .../exceptions/UnsupportedSetupException.java | 0 .../internal/FragmentedPublisher.java | 0 .../io/reactivesocket/internal/PublisherUtils.java | 0 .../java/io/reactivesocket/internal/Requester.java | 0 .../java/io/reactivesocket/internal/Responder.java | 0 .../io/reactivesocket/internal/UnicastSubject.java | 0 .../internal/frame/ByteBufferUtil.java | 0 .../internal/frame/ErrorFrameFlyweight.java | 0 .../internal/frame/FrameHeaderFlyweight.java | 0 .../reactivesocket/internal/frame/FramePool.java | 0 .../internal/frame/KeepaliveFrameFlyweight.java | 0 .../internal/frame/LeaseFrameFlyweight.java | 0 .../internal/frame/PayloadBuilder.java | 0 .../internal/frame/PayloadFragmenter.java | 0 .../internal/frame/PayloadReassembler.java | 0 .../internal/frame/RequestFrameFlyweight.java | 0 .../internal/frame/RequestNFrameFlyweight.java | 0 .../internal/frame/SetupFrameFlyweight.java | 0 .../internal/frame/ThreadLocalFramePool.java | 0 .../internal/frame/ThreadSafeFramePool.java | 0 .../internal/frame/UnpooledFrame.java | 0 .../internal/rx/AppendOnlyLinkedArrayList.java | 0 .../internal/rx/BackpressureHelper.java | 0 .../internal/rx/BackpressureUtils.java | 0 .../reactivesocket/internal/rx/BaseArrayQueue.java | 0 .../internal/rx/BaseLinkedQueue.java | 0 .../internal/rx/BooleanDisposable.java | 0 .../internal/rx/CompositeCompletable.java | 0 .../internal/rx/CompositeDisposable.java | 0 .../internal/rx/EmptyDisposable.java | 0 .../internal/rx/EmptySubscription.java | 0 .../internal/rx/LinkedQueueNode.java | 0 .../internal/rx/MpscLinkedQueue.java | 0 .../internal/rx/NotificationLite.java | 0 .../internal/rx/OperatorConcatMap.java | 0 .../java/io/reactivesocket/internal/rx/Pow2.java | 0 .../internal/rx/QueueDrainHelper.java | 0 .../java/io/reactivesocket/internal/rx/README.md | 0 .../internal/rx/SerializedSubscriber.java | 0 .../reactivesocket/internal/rx/SpscArrayQueue.java | 0 .../internal/rx/SpscExactArrayQueue.java | 0 .../internal/rx/SubscriptionArbiter.java | 0 .../internal/rx/SubscriptionHelper.java | 0 .../io/reactivesocket/lease/FairLeaseGovernor.java | 0 .../io/reactivesocket/lease/NullLeaseGovernor.java | 0 .../lease/UnlimitedLeaseGovernor.java | 0 .../java/io/reactivesocket/rx/Completable.java | 0 .../main/java/io/reactivesocket/rx/Disposable.java | 0 .../main/java/io/reactivesocket/rx/Observable.java | 0 .../main/java/io/reactivesocket/rx/Observer.java | 0 .../src/main/java/io/reactivesocket/rx/README.md | 0 .../reactivesocket/util/ReactiveSocketProxy.java | 0 .../main/java/io/reactivesocket/util/Unsafe.java | 0 .../src/perf/java/io/reactivesocket/FramePerf.java | 0 .../src/perf/java/io/reactivesocket/README.md | 0 .../java/io/reactivesocket/ReactiveSocketPerf.java | 0 .../perfutil/PerfTestConnection.java | 0 .../perfutil/PerfUnicastSubjectNoBackpressure.java | 0 .../src/test/java/io/reactivesocket/FrameTest.java | 0 .../java/io/reactivesocket/LatchedCompletable.java | 0 .../src/test/java/io/reactivesocket/LeaseTest.java | 0 .../java/io/reactivesocket/ReactiveSocketTest.java | 0 .../java/io/reactivesocket/SerializedEventBus.java | 0 .../java/io/reactivesocket/TestConnection.java | 0 .../TestConnectionWithControlledRequestN.java | 0 .../io/reactivesocket/TestFlowControlRequestN.java | 0 .../io/reactivesocket/TestTransportRequestN.java | 0 .../src/test/java/io/reactivesocket/TestUtil.java | 0 .../io/reactivesocket/internal/FragmenterTest.java | 0 .../reactivesocket/internal/ReassemblerTest.java | 0 .../io/reactivesocket/internal/RequesterTest.java | 0 .../io/reactivesocket/internal/ResponderTest.java | 0 .../internal/UnicastSubjectTest.java | 0 .../README.md | 0 .../build.gradle | 2 +- .../io/reactivesocket/mimetypes/KVMetadata.java | 0 .../java/io/reactivesocket/mimetypes/MimeType.java | 0 .../reactivesocket/mimetypes/MimeTypeFactory.java | 0 .../mimetypes/SupportedMimeTypes.java | 0 .../mimetypes/internal/AbstractJacksonCodec.java | 0 .../mimetypes/internal/ByteBufferInputStream.java | 0 .../mimetypes/internal/ByteBufferOutputStream.java | 0 .../reactivesocket/mimetypes/internal/Codec.java | 0 .../mimetypes/internal/KVMetadataImpl.java | 0 .../internal/MalformedInputException.java | 0 .../mimetypes/internal/cbor/CBORMap.java | 0 .../mimetypes/internal/cbor/CBORUtils.java | 0 .../internal/cbor/CborBinaryStringCodec.java | 0 .../mimetypes/internal/cbor/CborCodec.java | 0 .../mimetypes/internal/cbor/CborHeader.java | 0 .../mimetypes/internal/cbor/CborMajorType.java | 0 .../mimetypes/internal/cbor/CborMapCodec.java | 0 .../internal/cbor/CborUtf8StringCodec.java | 0 .../internal/cbor/IndexedUnsafeBuffer.java | 0 .../mimetypes/internal/cbor/MetadataCodec.java | 0 .../cbor/ReactiveSocketDefaultMetadataCodec.java | 0 .../internal/cbor/SlicedBufferKVMetadata.java | 0 .../mimetypes/internal/json/JsonCodec.java | 0 .../mimetypes/MimeTypeFactoryTest.java | 0 .../internal/AbstractJacksonCodecTest.java | 0 .../mimetypes/internal/CodecRule.java | 0 .../mimetypes/internal/CustomObject.java | 0 .../mimetypes/internal/CustomObjectRule.java | 0 .../mimetypes/internal/KVMetadataImplTest.java | 0 .../mimetypes/internal/MetadataRule.java | 0 .../ReactiveSocketDefaultMetadataCodecTest.java | 0 .../internal/cbor/AbstractCborMapRule.java | 0 .../internal/cbor/ByteBufferMapMatcher.java | 0 .../mimetypes/internal/cbor/CBORMapTest.java | 0 .../internal/cbor/CBORMapValueMaskTest.java | 0 .../mimetypes/internal/cbor/CBORUtilsTest.java | 0 .../internal/cbor/CborBinaryStringCodecTest.java | 0 .../mimetypes/internal/cbor/CborCodecTest.java | 0 .../mimetypes/internal/cbor/CborHeaderTest.java | 0 .../mimetypes/internal/cbor/CborMapCodecTest.java | 0 .../internal/cbor/CborUtf8StringCodecTest.java | 0 .../internal/cbor/IndexedUnsafeBufferTest.java | 0 .../mimetypes/internal/cbor/MetadataCodecTest.java | 0 .../internal/cbor/SlicedBufferKVMetadataTest.java | 0 .../mimetypes/internal/json/JsonCodecTest.java | 0 .../src/test/resources/log4j.properties | 0 reactivesocket-test/build.gradle | 3 +++ .../main/java/io/reactivesocket/test/TestUtil.java | 0 reactivesocket-transport-aeron/build.gradle | 5 +++++ .../reactivesocket/aeron/example/MediaDriver.java | 0 .../aeron/example/fireandforget/Fire.java | 0 .../aeron/example/fireandforget/Forget.java | 0 .../aeron/example/requestreply/Ping.java | 0 .../aeron/example/requestreply/Pong.java | 0 .../src/examples/resources/simplelogger.properties | 0 .../aeron/client/AeronClientDuplexConnection.java | 0 .../client/AeronClientDuplexConnectionFactory.java | 0 .../aeron/client/AeronReactiveSocketConnector.java | 0 .../aeron/client/ClientAeronManager.java | 0 .../reactivesocket/aeron/client/FrameHolder.java | 0 .../reactivesocket/aeron/client/PollingAction.java | 0 .../reactivesocket/aeron/internal/AeronUtil.java | 0 .../reactivesocket/aeron/internal/Constants.java | 0 .../io/reactivesocket/aeron/internal/Loggable.java | 0 .../reactivesocket/aeron/internal/MessageType.java | 0 .../aeron/internal/NotConnectedException.java | 0 .../aeron/internal/TimedOutException.java | 0 .../aeron/server/AeronServerDuplexConnection.java | 0 .../aeron/server/ReactiveSocketAeronServer.java | 0 .../aeron/server/ServerAeronManager.java | 0 .../aeron/server/ServerSubscription.java | 0 .../aeron/server/TimerWheelFairLeaseGovernor.java | 0 .../server/rx/ReactiveSocketAeronScheduler.java | 0 .../src/perf/java/io/aeron/DummySubscription.java | 0 .../aeron/client/PollingActionPerf.java | 0 .../aeron/jmh/InputWithIncrementingInteger.java | 0 .../reactivesocket/aeron/jmh/LatchedObserver.java | 0 .../aeron/client/ReactiveSocketAeronTest.java | 0 .../aeron/internal/AeronUtilTest.java | 0 .../aeron/server/ServerAeronManagerTest.java | 0 .../rx/ReactiveSocketAeronSchedulerTest.java | 0 .../src/test/resources/simplelogger.properties | 0 .../build.gradle | 4 ++-- .../javax/websocket/WebSocketDuplexConnection.java | 0 .../client/ReactiveSocketWebSocketClient.java | 0 .../client/WebSocketReactiveSocketConnector.java | 0 .../server/ReactiveSocketWebSocketServer.java | 0 .../javax/websocket/ClientServerEndpoint.java | 0 .../javax/websocket/ClientServerTest.java | 0 .../io/reactivesocket/javax/websocket/Ping.java | 0 .../io/reactivesocket/javax/websocket/Pong.java | 0 .../javax/websocket/PongEndpoint.java | 0 .../src/test/resources/simplelogger.properties | 0 reactivesocket-transport-local/build.gradle | 4 ++++ .../local/LocalClientDuplexConnection.java | 0 .../local/LocalClientReactiveSocketConnector.java | 0 .../local/LocalReactiveSocketManager.java | 0 .../local/LocalServerDuplexConection.java | 0 .../local/LocalServerReactiveSocketConnector.java | 0 .../io/reactivesocket/local/ClientServerTest.java | 0 .../build.gradle | 4 ++-- .../java/io/reactivesocket/netty/EchoServer.java | 0 .../io/reactivesocket/netty/EchoServerHandler.java | 0 .../io/reactivesocket/netty/HttpServerHandler.java | 0 .../reactivesocket/netty/MutableDirectByteBuf.java | 0 .../tcp/client/ClientTcpDuplexConnection.java | 0 .../tcp/client/ReactiveSocketClientHandler.java | 0 .../tcp/client/TcpReactiveSocketConnector.java | 0 .../tcp/server/ReactiveSocketServerHandler.java | 0 .../tcp/server/ServerTcpDuplexConnection.java | 0 .../client/ClientWebSocketDuplexConnection.java | 0 .../client/ReactiveSocketClientHandler.java | 0 .../client/WebSocketReactiveSocketConnector.java | 0 .../server/ReactiveSocketServerHandler.java | 0 .../server/ServerWebSocketDuplexConnection.java | 0 .../reactivesocket/netty/tcp/ClientServerTest.java | 0 .../java/io/reactivesocket/netty/tcp/Ping.java | 0 .../java/io/reactivesocket/netty/tcp/Pong.java | 0 .../netty/websocket/ClientServerTest.java | 0 .../io/reactivesocket/netty/websocket/Ping.java | 0 .../io/reactivesocket/netty/websocket/Pong.java | 0 .../src/test/resources/simplelogger.properties | 0 rs-test/build.gradle | 3 --- rs-transport-aeron/build.gradle | 5 ----- rs-transport-local/build.gradle | 4 ---- settings.gradle | 14 +++++++------- 225 files changed, 24 insertions(+), 24 deletions(-) rename {rs-core => reactivesocket-core}/build.gradle (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/ConnectionSetupHandler.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/ConnectionSetupPayload.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/DefaultReactiveSocket.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/DuplexConnection.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/Frame.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/FrameType.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/LeaseGovernor.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/Payload.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/ReactiveSocket.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/ReactiveSocketConnector.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/ReactiveSocketFactory.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/RequestHandler.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/exceptions/ApplicationException.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/exceptions/CancelException.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/exceptions/ConnectionException.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/exceptions/Exceptions.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/exceptions/InvalidRequestException.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/exceptions/InvalidSetupException.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/exceptions/RejectedException.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/exceptions/RejectedSetupException.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/exceptions/Retryable.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/exceptions/SetupException.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/exceptions/TransportException.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/exceptions/UnsupportedSetupException.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/FragmentedPublisher.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/PublisherUtils.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/Requester.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/Responder.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/UnicastSubject.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/frame/ByteBufferUtil.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/frame/ErrorFrameFlyweight.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/frame/FrameHeaderFlyweight.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/frame/FramePool.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/frame/KeepaliveFrameFlyweight.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/frame/LeaseFrameFlyweight.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/frame/PayloadBuilder.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/frame/PayloadFragmenter.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/frame/PayloadReassembler.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/frame/RequestFrameFlyweight.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/frame/RequestNFrameFlyweight.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/frame/SetupFrameFlyweight.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/frame/ThreadLocalFramePool.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/frame/ThreadSafeFramePool.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/frame/UnpooledFrame.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/rx/AppendOnlyLinkedArrayList.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/rx/BackpressureHelper.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/rx/BackpressureUtils.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/rx/BaseArrayQueue.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/rx/BaseLinkedQueue.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/rx/BooleanDisposable.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/rx/CompositeCompletable.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/rx/CompositeDisposable.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/rx/EmptyDisposable.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/rx/EmptySubscription.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/rx/LinkedQueueNode.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/rx/MpscLinkedQueue.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/rx/NotificationLite.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/rx/OperatorConcatMap.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/rx/Pow2.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/rx/QueueDrainHelper.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/rx/README.md (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/rx/SerializedSubscriber.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/rx/SpscArrayQueue.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/rx/SpscExactArrayQueue.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/rx/SubscriptionArbiter.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/internal/rx/SubscriptionHelper.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/lease/FairLeaseGovernor.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/lease/NullLeaseGovernor.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/lease/UnlimitedLeaseGovernor.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/rx/Completable.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/rx/Disposable.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/rx/Observable.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/rx/Observer.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/rx/README.md (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/util/ReactiveSocketProxy.java (100%) rename {rs-core => reactivesocket-core}/src/main/java/io/reactivesocket/util/Unsafe.java (100%) rename {rs-core => reactivesocket-core}/src/perf/java/io/reactivesocket/FramePerf.java (100%) rename {rs-core => reactivesocket-core}/src/perf/java/io/reactivesocket/README.md (100%) rename {rs-core => reactivesocket-core}/src/perf/java/io/reactivesocket/ReactiveSocketPerf.java (100%) rename {rs-core => reactivesocket-core}/src/perf/java/io/reactivesocket/perfutil/PerfTestConnection.java (100%) rename {rs-core => reactivesocket-core}/src/perf/java/io/reactivesocket/perfutil/PerfUnicastSubjectNoBackpressure.java (100%) rename {rs-core => reactivesocket-core}/src/test/java/io/reactivesocket/FrameTest.java (100%) rename {rs-core => reactivesocket-core}/src/test/java/io/reactivesocket/LatchedCompletable.java (100%) rename {rs-core => reactivesocket-core}/src/test/java/io/reactivesocket/LeaseTest.java (100%) rename {rs-core => reactivesocket-core}/src/test/java/io/reactivesocket/ReactiveSocketTest.java (100%) rename {rs-core => reactivesocket-core}/src/test/java/io/reactivesocket/SerializedEventBus.java (100%) rename {rs-core => reactivesocket-core}/src/test/java/io/reactivesocket/TestConnection.java (100%) rename {rs-core => reactivesocket-core}/src/test/java/io/reactivesocket/TestConnectionWithControlledRequestN.java (100%) rename {rs-core => reactivesocket-core}/src/test/java/io/reactivesocket/TestFlowControlRequestN.java (100%) rename {rs-core => reactivesocket-core}/src/test/java/io/reactivesocket/TestTransportRequestN.java (100%) rename {rs-core => reactivesocket-core}/src/test/java/io/reactivesocket/TestUtil.java (100%) rename {rs-core => reactivesocket-core}/src/test/java/io/reactivesocket/internal/FragmenterTest.java (100%) rename {rs-core => reactivesocket-core}/src/test/java/io/reactivesocket/internal/ReassemblerTest.java (100%) rename {rs-core => reactivesocket-core}/src/test/java/io/reactivesocket/internal/RequesterTest.java (100%) rename {rs-core => reactivesocket-core}/src/test/java/io/reactivesocket/internal/ResponderTest.java (100%) rename {rs-core => reactivesocket-core}/src/test/java/io/reactivesocket/internal/UnicastSubjectTest.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/README.md (100%) rename {rs-mime-types => reactivesocket-mime-types}/build.gradle (95%) rename {rs-mime-types => reactivesocket-mime-types}/src/main/java/io/reactivesocket/mimetypes/KVMetadata.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/main/java/io/reactivesocket/mimetypes/MimeType.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/main/java/io/reactivesocket/mimetypes/MimeTypeFactory.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/main/java/io/reactivesocket/mimetypes/SupportedMimeTypes.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/main/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodec.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/main/java/io/reactivesocket/mimetypes/internal/ByteBufferInputStream.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/main/java/io/reactivesocket/mimetypes/internal/ByteBufferOutputStream.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/main/java/io/reactivesocket/mimetypes/internal/Codec.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/main/java/io/reactivesocket/mimetypes/internal/KVMetadataImpl.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/main/java/io/reactivesocket/mimetypes/internal/MalformedInputException.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CBORMap.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CBORUtils.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborBinaryStringCodec.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborCodec.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborHeader.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborMajorType.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborMapCodec.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborUtf8StringCodec.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/main/java/io/reactivesocket/mimetypes/internal/cbor/IndexedUnsafeBuffer.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/main/java/io/reactivesocket/mimetypes/internal/cbor/MetadataCodec.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/main/java/io/reactivesocket/mimetypes/internal/cbor/ReactiveSocketDefaultMetadataCodec.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/main/java/io/reactivesocket/mimetypes/internal/cbor/SlicedBufferKVMetadata.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/main/java/io/reactivesocket/mimetypes/internal/json/JsonCodec.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/test/java/io/reactivesocket/mimetypes/MimeTypeFactoryTest.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/test/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodecTest.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/test/java/io/reactivesocket/mimetypes/internal/CodecRule.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/test/java/io/reactivesocket/mimetypes/internal/CustomObject.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/test/java/io/reactivesocket/mimetypes/internal/CustomObjectRule.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/test/java/io/reactivesocket/mimetypes/internal/KVMetadataImplTest.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/test/java/io/reactivesocket/mimetypes/internal/MetadataRule.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/test/java/io/reactivesocket/mimetypes/internal/ReactiveSocketDefaultMetadataCodecTest.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/test/java/io/reactivesocket/mimetypes/internal/cbor/AbstractCborMapRule.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/test/java/io/reactivesocket/mimetypes/internal/cbor/ByteBufferMapMatcher.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORMapTest.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORMapValueMaskTest.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORUtilsTest.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborBinaryStringCodecTest.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborCodecTest.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborHeaderTest.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborMapCodecTest.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborUtf8StringCodecTest.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/test/java/io/reactivesocket/mimetypes/internal/cbor/IndexedUnsafeBufferTest.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/test/java/io/reactivesocket/mimetypes/internal/cbor/MetadataCodecTest.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/test/java/io/reactivesocket/mimetypes/internal/cbor/SlicedBufferKVMetadataTest.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/test/java/io/reactivesocket/mimetypes/internal/json/JsonCodecTest.java (100%) rename {rs-mime-types => reactivesocket-mime-types}/src/test/resources/log4j.properties (100%) create mode 100644 reactivesocket-test/build.gradle rename {rs-test => reactivesocket-test}/src/main/java/io/reactivesocket/test/TestUtil.java (100%) create mode 100644 reactivesocket-transport-aeron/build.gradle rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/examples/java/io/reactivesocket/aeron/example/MediaDriver.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/examples/java/io/reactivesocket/aeron/example/fireandforget/Fire.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/examples/java/io/reactivesocket/aeron/example/fireandforget/Forget.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/examples/java/io/reactivesocket/aeron/example/requestreply/Ping.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/examples/java/io/reactivesocket/aeron/example/requestreply/Pong.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/examples/resources/simplelogger.properties (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketConnector.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/main/java/io/reactivesocket/aeron/client/PollingAction.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/main/java/io/reactivesocket/aeron/internal/Constants.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/main/java/io/reactivesocket/aeron/internal/Loggable.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/main/java/io/reactivesocket/aeron/internal/MessageType.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/main/java/io/reactivesocket/aeron/internal/TimedOutException.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/main/java/io/reactivesocket/aeron/server/TimerWheelFairLeaseGovernor.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/main/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronScheduler.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/perf/java/io/aeron/DummySubscription.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/perf/java/io/reactivesocket/aeron/client/PollingActionPerf.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/perf/java/io/reactivesocket/aeron/jmh/InputWithIncrementingInteger.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/perf/java/io/reactivesocket/aeron/jmh/LatchedObserver.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/test/java/io/reactivesocket/aeron/internal/AeronUtilTest.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/test/java/io/reactivesocket/aeron/server/ServerAeronManagerTest.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/test/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronSchedulerTest.java (100%) rename {rs-transport-aeron => reactivesocket-transport-aeron}/src/test/resources/simplelogger.properties (100%) rename {rs-transport-jsr-356 => reactivesocket-transport-jsr-356}/build.gradle (68%) rename {rs-transport-jsr-356 => reactivesocket-transport-jsr-356}/src/main/java/io/reactivesocket/javax/websocket/WebSocketDuplexConnection.java (100%) rename {rs-transport-jsr-356 => reactivesocket-transport-jsr-356}/src/main/java/io/reactivesocket/javax/websocket/client/ReactiveSocketWebSocketClient.java (100%) rename {rs-transport-jsr-356 => reactivesocket-transport-jsr-356}/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketConnector.java (100%) rename {rs-transport-jsr-356 => reactivesocket-transport-jsr-356}/src/main/java/io/reactivesocket/javax/websocket/server/ReactiveSocketWebSocketServer.java (100%) rename {rs-transport-jsr-356 => reactivesocket-transport-jsr-356}/src/test/java/io/reactivesocket/javax/websocket/ClientServerEndpoint.java (100%) rename {rs-transport-jsr-356 => reactivesocket-transport-jsr-356}/src/test/java/io/reactivesocket/javax/websocket/ClientServerTest.java (100%) rename {rs-transport-jsr-356 => reactivesocket-transport-jsr-356}/src/test/java/io/reactivesocket/javax/websocket/Ping.java (100%) rename {rs-transport-jsr-356 => reactivesocket-transport-jsr-356}/src/test/java/io/reactivesocket/javax/websocket/Pong.java (100%) rename {rs-transport-jsr-356 => reactivesocket-transport-jsr-356}/src/test/java/io/reactivesocket/javax/websocket/PongEndpoint.java (100%) rename {rs-transport-jsr-356 => reactivesocket-transport-jsr-356}/src/test/resources/simplelogger.properties (100%) create mode 100644 reactivesocket-transport-local/build.gradle rename {rs-transport-local => reactivesocket-transport-local}/src/main/java/io/reactivesocket/local/LocalClientDuplexConnection.java (100%) rename {rs-transport-local => reactivesocket-transport-local}/src/main/java/io/reactivesocket/local/LocalClientReactiveSocketConnector.java (100%) rename {rs-transport-local => reactivesocket-transport-local}/src/main/java/io/reactivesocket/local/LocalReactiveSocketManager.java (100%) rename {rs-transport-local => reactivesocket-transport-local}/src/main/java/io/reactivesocket/local/LocalServerDuplexConection.java (100%) rename {rs-transport-local => reactivesocket-transport-local}/src/main/java/io/reactivesocket/local/LocalServerReactiveSocketConnector.java (100%) rename {rs-transport-local => reactivesocket-transport-local}/src/test/java/io/reactivesocket/local/ClientServerTest.java (100%) rename {rs-transport-netty => reactivesocket-transport-netty}/build.gradle (73%) rename {rs-transport-netty => reactivesocket-transport-netty}/src/examples/java/io/reactivesocket/netty/EchoServer.java (100%) rename {rs-transport-netty => reactivesocket-transport-netty}/src/examples/java/io/reactivesocket/netty/EchoServerHandler.java (100%) rename {rs-transport-netty => reactivesocket-transport-netty}/src/examples/java/io/reactivesocket/netty/HttpServerHandler.java (100%) rename {rs-transport-netty => reactivesocket-transport-netty}/src/main/java/io/reactivesocket/netty/MutableDirectByteBuf.java (100%) rename {rs-transport-netty => reactivesocket-transport-netty}/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java (100%) rename {rs-transport-netty => reactivesocket-transport-netty}/src/main/java/io/reactivesocket/netty/tcp/client/ReactiveSocketClientHandler.java (100%) rename {rs-transport-netty => reactivesocket-transport-netty}/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketConnector.java (100%) rename {rs-transport-netty => reactivesocket-transport-netty}/src/main/java/io/reactivesocket/netty/tcp/server/ReactiveSocketServerHandler.java (100%) rename {rs-transport-netty => reactivesocket-transport-netty}/src/main/java/io/reactivesocket/netty/tcp/server/ServerTcpDuplexConnection.java (100%) rename {rs-transport-netty => reactivesocket-transport-netty}/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java (100%) rename {rs-transport-netty => reactivesocket-transport-netty}/src/main/java/io/reactivesocket/netty/websocket/client/ReactiveSocketClientHandler.java (100%) rename {rs-transport-netty => reactivesocket-transport-netty}/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketConnector.java (100%) rename {rs-transport-netty => reactivesocket-transport-netty}/src/main/java/io/reactivesocket/netty/websocket/server/ReactiveSocketServerHandler.java (100%) rename {rs-transport-netty => reactivesocket-transport-netty}/src/main/java/io/reactivesocket/netty/websocket/server/ServerWebSocketDuplexConnection.java (100%) rename {rs-transport-netty => reactivesocket-transport-netty}/src/test/java/io/reactivesocket/netty/tcp/ClientServerTest.java (100%) rename {rs-transport-netty => reactivesocket-transport-netty}/src/test/java/io/reactivesocket/netty/tcp/Ping.java (100%) rename {rs-transport-netty => reactivesocket-transport-netty}/src/test/java/io/reactivesocket/netty/tcp/Pong.java (100%) rename {rs-transport-netty => reactivesocket-transport-netty}/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java (100%) rename {rs-transport-netty => reactivesocket-transport-netty}/src/test/java/io/reactivesocket/netty/websocket/Ping.java (100%) rename {rs-transport-netty => reactivesocket-transport-netty}/src/test/java/io/reactivesocket/netty/websocket/Pong.java (100%) rename {rs-transport-netty => reactivesocket-transport-netty}/src/test/resources/simplelogger.properties (100%) delete mode 100644 rs-test/build.gradle delete mode 100644 rs-transport-aeron/build.gradle delete mode 100644 rs-transport-local/build.gradle diff --git a/rs-core/build.gradle b/reactivesocket-core/build.gradle similarity index 100% rename from rs-core/build.gradle rename to reactivesocket-core/build.gradle diff --git a/rs-core/src/main/java/io/reactivesocket/ConnectionSetupHandler.java b/reactivesocket-core/src/main/java/io/reactivesocket/ConnectionSetupHandler.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/ConnectionSetupHandler.java rename to reactivesocket-core/src/main/java/io/reactivesocket/ConnectionSetupHandler.java diff --git a/rs-core/src/main/java/io/reactivesocket/ConnectionSetupPayload.java b/reactivesocket-core/src/main/java/io/reactivesocket/ConnectionSetupPayload.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/ConnectionSetupPayload.java rename to reactivesocket-core/src/main/java/io/reactivesocket/ConnectionSetupPayload.java diff --git a/rs-core/src/main/java/io/reactivesocket/DefaultReactiveSocket.java b/reactivesocket-core/src/main/java/io/reactivesocket/DefaultReactiveSocket.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/DefaultReactiveSocket.java rename to reactivesocket-core/src/main/java/io/reactivesocket/DefaultReactiveSocket.java diff --git a/rs-core/src/main/java/io/reactivesocket/DuplexConnection.java b/reactivesocket-core/src/main/java/io/reactivesocket/DuplexConnection.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/DuplexConnection.java rename to reactivesocket-core/src/main/java/io/reactivesocket/DuplexConnection.java diff --git a/rs-core/src/main/java/io/reactivesocket/Frame.java b/reactivesocket-core/src/main/java/io/reactivesocket/Frame.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/Frame.java rename to reactivesocket-core/src/main/java/io/reactivesocket/Frame.java diff --git a/rs-core/src/main/java/io/reactivesocket/FrameType.java b/reactivesocket-core/src/main/java/io/reactivesocket/FrameType.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/FrameType.java rename to reactivesocket-core/src/main/java/io/reactivesocket/FrameType.java diff --git a/rs-core/src/main/java/io/reactivesocket/LeaseGovernor.java b/reactivesocket-core/src/main/java/io/reactivesocket/LeaseGovernor.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/LeaseGovernor.java rename to reactivesocket-core/src/main/java/io/reactivesocket/LeaseGovernor.java diff --git a/rs-core/src/main/java/io/reactivesocket/Payload.java b/reactivesocket-core/src/main/java/io/reactivesocket/Payload.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/Payload.java rename to reactivesocket-core/src/main/java/io/reactivesocket/Payload.java diff --git a/rs-core/src/main/java/io/reactivesocket/ReactiveSocket.java b/reactivesocket-core/src/main/java/io/reactivesocket/ReactiveSocket.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/ReactiveSocket.java rename to reactivesocket-core/src/main/java/io/reactivesocket/ReactiveSocket.java diff --git a/rs-core/src/main/java/io/reactivesocket/ReactiveSocketConnector.java b/reactivesocket-core/src/main/java/io/reactivesocket/ReactiveSocketConnector.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/ReactiveSocketConnector.java rename to reactivesocket-core/src/main/java/io/reactivesocket/ReactiveSocketConnector.java diff --git a/rs-core/src/main/java/io/reactivesocket/ReactiveSocketFactory.java b/reactivesocket-core/src/main/java/io/reactivesocket/ReactiveSocketFactory.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/ReactiveSocketFactory.java rename to reactivesocket-core/src/main/java/io/reactivesocket/ReactiveSocketFactory.java diff --git a/rs-core/src/main/java/io/reactivesocket/RequestHandler.java b/reactivesocket-core/src/main/java/io/reactivesocket/RequestHandler.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/RequestHandler.java rename to reactivesocket-core/src/main/java/io/reactivesocket/RequestHandler.java diff --git a/rs-core/src/main/java/io/reactivesocket/exceptions/ApplicationException.java b/reactivesocket-core/src/main/java/io/reactivesocket/exceptions/ApplicationException.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/exceptions/ApplicationException.java rename to reactivesocket-core/src/main/java/io/reactivesocket/exceptions/ApplicationException.java diff --git a/rs-core/src/main/java/io/reactivesocket/exceptions/CancelException.java b/reactivesocket-core/src/main/java/io/reactivesocket/exceptions/CancelException.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/exceptions/CancelException.java rename to reactivesocket-core/src/main/java/io/reactivesocket/exceptions/CancelException.java diff --git a/rs-core/src/main/java/io/reactivesocket/exceptions/ConnectionException.java b/reactivesocket-core/src/main/java/io/reactivesocket/exceptions/ConnectionException.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/exceptions/ConnectionException.java rename to reactivesocket-core/src/main/java/io/reactivesocket/exceptions/ConnectionException.java diff --git a/rs-core/src/main/java/io/reactivesocket/exceptions/Exceptions.java b/reactivesocket-core/src/main/java/io/reactivesocket/exceptions/Exceptions.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/exceptions/Exceptions.java rename to reactivesocket-core/src/main/java/io/reactivesocket/exceptions/Exceptions.java diff --git a/rs-core/src/main/java/io/reactivesocket/exceptions/InvalidRequestException.java b/reactivesocket-core/src/main/java/io/reactivesocket/exceptions/InvalidRequestException.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/exceptions/InvalidRequestException.java rename to reactivesocket-core/src/main/java/io/reactivesocket/exceptions/InvalidRequestException.java diff --git a/rs-core/src/main/java/io/reactivesocket/exceptions/InvalidSetupException.java b/reactivesocket-core/src/main/java/io/reactivesocket/exceptions/InvalidSetupException.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/exceptions/InvalidSetupException.java rename to reactivesocket-core/src/main/java/io/reactivesocket/exceptions/InvalidSetupException.java diff --git a/rs-core/src/main/java/io/reactivesocket/exceptions/RejectedException.java b/reactivesocket-core/src/main/java/io/reactivesocket/exceptions/RejectedException.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/exceptions/RejectedException.java rename to reactivesocket-core/src/main/java/io/reactivesocket/exceptions/RejectedException.java diff --git a/rs-core/src/main/java/io/reactivesocket/exceptions/RejectedSetupException.java b/reactivesocket-core/src/main/java/io/reactivesocket/exceptions/RejectedSetupException.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/exceptions/RejectedSetupException.java rename to reactivesocket-core/src/main/java/io/reactivesocket/exceptions/RejectedSetupException.java diff --git a/rs-core/src/main/java/io/reactivesocket/exceptions/Retryable.java b/reactivesocket-core/src/main/java/io/reactivesocket/exceptions/Retryable.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/exceptions/Retryable.java rename to reactivesocket-core/src/main/java/io/reactivesocket/exceptions/Retryable.java diff --git a/rs-core/src/main/java/io/reactivesocket/exceptions/SetupException.java b/reactivesocket-core/src/main/java/io/reactivesocket/exceptions/SetupException.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/exceptions/SetupException.java rename to reactivesocket-core/src/main/java/io/reactivesocket/exceptions/SetupException.java diff --git a/rs-core/src/main/java/io/reactivesocket/exceptions/TransportException.java b/reactivesocket-core/src/main/java/io/reactivesocket/exceptions/TransportException.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/exceptions/TransportException.java rename to reactivesocket-core/src/main/java/io/reactivesocket/exceptions/TransportException.java diff --git a/rs-core/src/main/java/io/reactivesocket/exceptions/UnsupportedSetupException.java b/reactivesocket-core/src/main/java/io/reactivesocket/exceptions/UnsupportedSetupException.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/exceptions/UnsupportedSetupException.java rename to reactivesocket-core/src/main/java/io/reactivesocket/exceptions/UnsupportedSetupException.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/FragmentedPublisher.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/FragmentedPublisher.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/FragmentedPublisher.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/FragmentedPublisher.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/PublisherUtils.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/PublisherUtils.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/PublisherUtils.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/PublisherUtils.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/Requester.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/Requester.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/Requester.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/Requester.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/Responder.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/Responder.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/Responder.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/Responder.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/UnicastSubject.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/UnicastSubject.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/UnicastSubject.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/UnicastSubject.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/frame/ByteBufferUtil.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/ByteBufferUtil.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/frame/ByteBufferUtil.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/ByteBufferUtil.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/frame/ErrorFrameFlyweight.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/ErrorFrameFlyweight.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/frame/ErrorFrameFlyweight.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/ErrorFrameFlyweight.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/frame/FrameHeaderFlyweight.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/FrameHeaderFlyweight.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/frame/FrameHeaderFlyweight.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/FrameHeaderFlyweight.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/frame/FramePool.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/FramePool.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/frame/FramePool.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/FramePool.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/frame/KeepaliveFrameFlyweight.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/KeepaliveFrameFlyweight.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/frame/KeepaliveFrameFlyweight.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/KeepaliveFrameFlyweight.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/frame/LeaseFrameFlyweight.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/LeaseFrameFlyweight.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/frame/LeaseFrameFlyweight.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/LeaseFrameFlyweight.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/frame/PayloadBuilder.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/PayloadBuilder.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/frame/PayloadBuilder.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/PayloadBuilder.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/frame/PayloadFragmenter.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/PayloadFragmenter.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/frame/PayloadFragmenter.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/PayloadFragmenter.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/frame/PayloadReassembler.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/PayloadReassembler.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/frame/PayloadReassembler.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/PayloadReassembler.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/frame/RequestFrameFlyweight.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/RequestFrameFlyweight.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/frame/RequestFrameFlyweight.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/RequestFrameFlyweight.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/frame/RequestNFrameFlyweight.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/RequestNFrameFlyweight.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/frame/RequestNFrameFlyweight.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/RequestNFrameFlyweight.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/frame/SetupFrameFlyweight.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/SetupFrameFlyweight.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/frame/SetupFrameFlyweight.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/SetupFrameFlyweight.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/frame/ThreadLocalFramePool.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/ThreadLocalFramePool.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/frame/ThreadLocalFramePool.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/ThreadLocalFramePool.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/frame/ThreadSafeFramePool.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/ThreadSafeFramePool.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/frame/ThreadSafeFramePool.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/ThreadSafeFramePool.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/frame/UnpooledFrame.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/UnpooledFrame.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/frame/UnpooledFrame.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/frame/UnpooledFrame.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/rx/AppendOnlyLinkedArrayList.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/AppendOnlyLinkedArrayList.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/rx/AppendOnlyLinkedArrayList.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/AppendOnlyLinkedArrayList.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/rx/BackpressureHelper.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/BackpressureHelper.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/rx/BackpressureHelper.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/BackpressureHelper.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/rx/BackpressureUtils.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/BackpressureUtils.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/rx/BackpressureUtils.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/BackpressureUtils.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/rx/BaseArrayQueue.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/BaseArrayQueue.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/rx/BaseArrayQueue.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/BaseArrayQueue.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/rx/BaseLinkedQueue.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/BaseLinkedQueue.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/rx/BaseLinkedQueue.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/BaseLinkedQueue.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/rx/BooleanDisposable.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/BooleanDisposable.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/rx/BooleanDisposable.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/BooleanDisposable.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/rx/CompositeCompletable.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/CompositeCompletable.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/rx/CompositeCompletable.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/CompositeCompletable.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/rx/CompositeDisposable.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/CompositeDisposable.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/rx/CompositeDisposable.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/CompositeDisposable.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/rx/EmptyDisposable.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/EmptyDisposable.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/rx/EmptyDisposable.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/EmptyDisposable.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/rx/EmptySubscription.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/EmptySubscription.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/rx/EmptySubscription.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/EmptySubscription.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/rx/LinkedQueueNode.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/LinkedQueueNode.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/rx/LinkedQueueNode.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/LinkedQueueNode.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/rx/MpscLinkedQueue.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/MpscLinkedQueue.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/rx/MpscLinkedQueue.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/MpscLinkedQueue.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/rx/NotificationLite.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/NotificationLite.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/rx/NotificationLite.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/NotificationLite.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/rx/OperatorConcatMap.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/OperatorConcatMap.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/rx/OperatorConcatMap.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/OperatorConcatMap.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/rx/Pow2.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/Pow2.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/rx/Pow2.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/Pow2.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/rx/QueueDrainHelper.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/QueueDrainHelper.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/rx/QueueDrainHelper.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/QueueDrainHelper.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/rx/README.md b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/README.md similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/rx/README.md rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/README.md diff --git a/rs-core/src/main/java/io/reactivesocket/internal/rx/SerializedSubscriber.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/SerializedSubscriber.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/rx/SerializedSubscriber.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/SerializedSubscriber.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/rx/SpscArrayQueue.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/SpscArrayQueue.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/rx/SpscArrayQueue.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/SpscArrayQueue.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/rx/SpscExactArrayQueue.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/SpscExactArrayQueue.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/rx/SpscExactArrayQueue.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/SpscExactArrayQueue.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/rx/SubscriptionArbiter.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/SubscriptionArbiter.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/rx/SubscriptionArbiter.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/SubscriptionArbiter.java diff --git a/rs-core/src/main/java/io/reactivesocket/internal/rx/SubscriptionHelper.java b/reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/SubscriptionHelper.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/internal/rx/SubscriptionHelper.java rename to reactivesocket-core/src/main/java/io/reactivesocket/internal/rx/SubscriptionHelper.java diff --git a/rs-core/src/main/java/io/reactivesocket/lease/FairLeaseGovernor.java b/reactivesocket-core/src/main/java/io/reactivesocket/lease/FairLeaseGovernor.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/lease/FairLeaseGovernor.java rename to reactivesocket-core/src/main/java/io/reactivesocket/lease/FairLeaseGovernor.java diff --git a/rs-core/src/main/java/io/reactivesocket/lease/NullLeaseGovernor.java b/reactivesocket-core/src/main/java/io/reactivesocket/lease/NullLeaseGovernor.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/lease/NullLeaseGovernor.java rename to reactivesocket-core/src/main/java/io/reactivesocket/lease/NullLeaseGovernor.java diff --git a/rs-core/src/main/java/io/reactivesocket/lease/UnlimitedLeaseGovernor.java b/reactivesocket-core/src/main/java/io/reactivesocket/lease/UnlimitedLeaseGovernor.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/lease/UnlimitedLeaseGovernor.java rename to reactivesocket-core/src/main/java/io/reactivesocket/lease/UnlimitedLeaseGovernor.java diff --git a/rs-core/src/main/java/io/reactivesocket/rx/Completable.java b/reactivesocket-core/src/main/java/io/reactivesocket/rx/Completable.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/rx/Completable.java rename to reactivesocket-core/src/main/java/io/reactivesocket/rx/Completable.java diff --git a/rs-core/src/main/java/io/reactivesocket/rx/Disposable.java b/reactivesocket-core/src/main/java/io/reactivesocket/rx/Disposable.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/rx/Disposable.java rename to reactivesocket-core/src/main/java/io/reactivesocket/rx/Disposable.java diff --git a/rs-core/src/main/java/io/reactivesocket/rx/Observable.java b/reactivesocket-core/src/main/java/io/reactivesocket/rx/Observable.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/rx/Observable.java rename to reactivesocket-core/src/main/java/io/reactivesocket/rx/Observable.java diff --git a/rs-core/src/main/java/io/reactivesocket/rx/Observer.java b/reactivesocket-core/src/main/java/io/reactivesocket/rx/Observer.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/rx/Observer.java rename to reactivesocket-core/src/main/java/io/reactivesocket/rx/Observer.java diff --git a/rs-core/src/main/java/io/reactivesocket/rx/README.md b/reactivesocket-core/src/main/java/io/reactivesocket/rx/README.md similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/rx/README.md rename to reactivesocket-core/src/main/java/io/reactivesocket/rx/README.md diff --git a/rs-core/src/main/java/io/reactivesocket/util/ReactiveSocketProxy.java b/reactivesocket-core/src/main/java/io/reactivesocket/util/ReactiveSocketProxy.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/util/ReactiveSocketProxy.java rename to reactivesocket-core/src/main/java/io/reactivesocket/util/ReactiveSocketProxy.java diff --git a/rs-core/src/main/java/io/reactivesocket/util/Unsafe.java b/reactivesocket-core/src/main/java/io/reactivesocket/util/Unsafe.java similarity index 100% rename from rs-core/src/main/java/io/reactivesocket/util/Unsafe.java rename to reactivesocket-core/src/main/java/io/reactivesocket/util/Unsafe.java diff --git a/rs-core/src/perf/java/io/reactivesocket/FramePerf.java b/reactivesocket-core/src/perf/java/io/reactivesocket/FramePerf.java similarity index 100% rename from rs-core/src/perf/java/io/reactivesocket/FramePerf.java rename to reactivesocket-core/src/perf/java/io/reactivesocket/FramePerf.java diff --git a/rs-core/src/perf/java/io/reactivesocket/README.md b/reactivesocket-core/src/perf/java/io/reactivesocket/README.md similarity index 100% rename from rs-core/src/perf/java/io/reactivesocket/README.md rename to reactivesocket-core/src/perf/java/io/reactivesocket/README.md diff --git a/rs-core/src/perf/java/io/reactivesocket/ReactiveSocketPerf.java b/reactivesocket-core/src/perf/java/io/reactivesocket/ReactiveSocketPerf.java similarity index 100% rename from rs-core/src/perf/java/io/reactivesocket/ReactiveSocketPerf.java rename to reactivesocket-core/src/perf/java/io/reactivesocket/ReactiveSocketPerf.java diff --git a/rs-core/src/perf/java/io/reactivesocket/perfutil/PerfTestConnection.java b/reactivesocket-core/src/perf/java/io/reactivesocket/perfutil/PerfTestConnection.java similarity index 100% rename from rs-core/src/perf/java/io/reactivesocket/perfutil/PerfTestConnection.java rename to reactivesocket-core/src/perf/java/io/reactivesocket/perfutil/PerfTestConnection.java diff --git a/rs-core/src/perf/java/io/reactivesocket/perfutil/PerfUnicastSubjectNoBackpressure.java b/reactivesocket-core/src/perf/java/io/reactivesocket/perfutil/PerfUnicastSubjectNoBackpressure.java similarity index 100% rename from rs-core/src/perf/java/io/reactivesocket/perfutil/PerfUnicastSubjectNoBackpressure.java rename to reactivesocket-core/src/perf/java/io/reactivesocket/perfutil/PerfUnicastSubjectNoBackpressure.java diff --git a/rs-core/src/test/java/io/reactivesocket/FrameTest.java b/reactivesocket-core/src/test/java/io/reactivesocket/FrameTest.java similarity index 100% rename from rs-core/src/test/java/io/reactivesocket/FrameTest.java rename to reactivesocket-core/src/test/java/io/reactivesocket/FrameTest.java diff --git a/rs-core/src/test/java/io/reactivesocket/LatchedCompletable.java b/reactivesocket-core/src/test/java/io/reactivesocket/LatchedCompletable.java similarity index 100% rename from rs-core/src/test/java/io/reactivesocket/LatchedCompletable.java rename to reactivesocket-core/src/test/java/io/reactivesocket/LatchedCompletable.java diff --git a/rs-core/src/test/java/io/reactivesocket/LeaseTest.java b/reactivesocket-core/src/test/java/io/reactivesocket/LeaseTest.java similarity index 100% rename from rs-core/src/test/java/io/reactivesocket/LeaseTest.java rename to reactivesocket-core/src/test/java/io/reactivesocket/LeaseTest.java diff --git a/rs-core/src/test/java/io/reactivesocket/ReactiveSocketTest.java b/reactivesocket-core/src/test/java/io/reactivesocket/ReactiveSocketTest.java similarity index 100% rename from rs-core/src/test/java/io/reactivesocket/ReactiveSocketTest.java rename to reactivesocket-core/src/test/java/io/reactivesocket/ReactiveSocketTest.java diff --git a/rs-core/src/test/java/io/reactivesocket/SerializedEventBus.java b/reactivesocket-core/src/test/java/io/reactivesocket/SerializedEventBus.java similarity index 100% rename from rs-core/src/test/java/io/reactivesocket/SerializedEventBus.java rename to reactivesocket-core/src/test/java/io/reactivesocket/SerializedEventBus.java diff --git a/rs-core/src/test/java/io/reactivesocket/TestConnection.java b/reactivesocket-core/src/test/java/io/reactivesocket/TestConnection.java similarity index 100% rename from rs-core/src/test/java/io/reactivesocket/TestConnection.java rename to reactivesocket-core/src/test/java/io/reactivesocket/TestConnection.java diff --git a/rs-core/src/test/java/io/reactivesocket/TestConnectionWithControlledRequestN.java b/reactivesocket-core/src/test/java/io/reactivesocket/TestConnectionWithControlledRequestN.java similarity index 100% rename from rs-core/src/test/java/io/reactivesocket/TestConnectionWithControlledRequestN.java rename to reactivesocket-core/src/test/java/io/reactivesocket/TestConnectionWithControlledRequestN.java diff --git a/rs-core/src/test/java/io/reactivesocket/TestFlowControlRequestN.java b/reactivesocket-core/src/test/java/io/reactivesocket/TestFlowControlRequestN.java similarity index 100% rename from rs-core/src/test/java/io/reactivesocket/TestFlowControlRequestN.java rename to reactivesocket-core/src/test/java/io/reactivesocket/TestFlowControlRequestN.java diff --git a/rs-core/src/test/java/io/reactivesocket/TestTransportRequestN.java b/reactivesocket-core/src/test/java/io/reactivesocket/TestTransportRequestN.java similarity index 100% rename from rs-core/src/test/java/io/reactivesocket/TestTransportRequestN.java rename to reactivesocket-core/src/test/java/io/reactivesocket/TestTransportRequestN.java diff --git a/rs-core/src/test/java/io/reactivesocket/TestUtil.java b/reactivesocket-core/src/test/java/io/reactivesocket/TestUtil.java similarity index 100% rename from rs-core/src/test/java/io/reactivesocket/TestUtil.java rename to reactivesocket-core/src/test/java/io/reactivesocket/TestUtil.java diff --git a/rs-core/src/test/java/io/reactivesocket/internal/FragmenterTest.java b/reactivesocket-core/src/test/java/io/reactivesocket/internal/FragmenterTest.java similarity index 100% rename from rs-core/src/test/java/io/reactivesocket/internal/FragmenterTest.java rename to reactivesocket-core/src/test/java/io/reactivesocket/internal/FragmenterTest.java diff --git a/rs-core/src/test/java/io/reactivesocket/internal/ReassemblerTest.java b/reactivesocket-core/src/test/java/io/reactivesocket/internal/ReassemblerTest.java similarity index 100% rename from rs-core/src/test/java/io/reactivesocket/internal/ReassemblerTest.java rename to reactivesocket-core/src/test/java/io/reactivesocket/internal/ReassemblerTest.java diff --git a/rs-core/src/test/java/io/reactivesocket/internal/RequesterTest.java b/reactivesocket-core/src/test/java/io/reactivesocket/internal/RequesterTest.java similarity index 100% rename from rs-core/src/test/java/io/reactivesocket/internal/RequesterTest.java rename to reactivesocket-core/src/test/java/io/reactivesocket/internal/RequesterTest.java diff --git a/rs-core/src/test/java/io/reactivesocket/internal/ResponderTest.java b/reactivesocket-core/src/test/java/io/reactivesocket/internal/ResponderTest.java similarity index 100% rename from rs-core/src/test/java/io/reactivesocket/internal/ResponderTest.java rename to reactivesocket-core/src/test/java/io/reactivesocket/internal/ResponderTest.java diff --git a/rs-core/src/test/java/io/reactivesocket/internal/UnicastSubjectTest.java b/reactivesocket-core/src/test/java/io/reactivesocket/internal/UnicastSubjectTest.java similarity index 100% rename from rs-core/src/test/java/io/reactivesocket/internal/UnicastSubjectTest.java rename to reactivesocket-core/src/test/java/io/reactivesocket/internal/UnicastSubjectTest.java diff --git a/rs-mime-types/README.md b/reactivesocket-mime-types/README.md similarity index 100% rename from rs-mime-types/README.md rename to reactivesocket-mime-types/README.md diff --git a/rs-mime-types/build.gradle b/reactivesocket-mime-types/build.gradle similarity index 95% rename from rs-mime-types/build.gradle rename to reactivesocket-mime-types/build.gradle index ffe865242..869c17c79 100644 --- a/rs-mime-types/build.gradle +++ b/reactivesocket-mime-types/build.gradle @@ -16,7 +16,7 @@ */ dependencies { - compile project(':rs-core') + compile project(':reactivesocket-core') compile 'com.fasterxml.jackson.core:jackson-core:latest.release' compile 'com.fasterxml.jackson.core:jackson-databind:latest.release' compile 'com.fasterxml.jackson.module:jackson-module-afterburner:latest.release' diff --git a/rs-mime-types/src/main/java/io/reactivesocket/mimetypes/KVMetadata.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/KVMetadata.java similarity index 100% rename from rs-mime-types/src/main/java/io/reactivesocket/mimetypes/KVMetadata.java rename to reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/KVMetadata.java diff --git a/rs-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeType.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeType.java similarity index 100% rename from rs-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeType.java rename to reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeType.java diff --git a/rs-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeTypeFactory.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeTypeFactory.java similarity index 100% rename from rs-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeTypeFactory.java rename to reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/MimeTypeFactory.java diff --git a/rs-mime-types/src/main/java/io/reactivesocket/mimetypes/SupportedMimeTypes.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/SupportedMimeTypes.java similarity index 100% rename from rs-mime-types/src/main/java/io/reactivesocket/mimetypes/SupportedMimeTypes.java rename to reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/SupportedMimeTypes.java diff --git a/rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodec.java similarity index 100% rename from rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodec.java rename to reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodec.java diff --git a/rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ByteBufferInputStream.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ByteBufferInputStream.java similarity index 100% rename from rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ByteBufferInputStream.java rename to reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ByteBufferInputStream.java diff --git a/rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ByteBufferOutputStream.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ByteBufferOutputStream.java similarity index 100% rename from rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ByteBufferOutputStream.java rename to reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/ByteBufferOutputStream.java diff --git a/rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/Codec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/Codec.java similarity index 100% rename from rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/Codec.java rename to reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/Codec.java diff --git a/rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/KVMetadataImpl.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/KVMetadataImpl.java similarity index 100% rename from rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/KVMetadataImpl.java rename to reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/KVMetadataImpl.java diff --git a/rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/MalformedInputException.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/MalformedInputException.java similarity index 100% rename from rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/MalformedInputException.java rename to reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/MalformedInputException.java diff --git a/rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CBORMap.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CBORMap.java similarity index 100% rename from rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CBORMap.java rename to reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CBORMap.java diff --git a/rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CBORUtils.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CBORUtils.java similarity index 100% rename from rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CBORUtils.java rename to reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CBORUtils.java diff --git a/rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborBinaryStringCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborBinaryStringCodec.java similarity index 100% rename from rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborBinaryStringCodec.java rename to reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborBinaryStringCodec.java diff --git a/rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborCodec.java similarity index 100% rename from rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborCodec.java rename to reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborCodec.java diff --git a/rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborHeader.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborHeader.java similarity index 100% rename from rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborHeader.java rename to reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborHeader.java diff --git a/rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborMajorType.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborMajorType.java similarity index 100% rename from rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborMajorType.java rename to reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborMajorType.java diff --git a/rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborMapCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborMapCodec.java similarity index 100% rename from rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborMapCodec.java rename to reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborMapCodec.java diff --git a/rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborUtf8StringCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborUtf8StringCodec.java similarity index 100% rename from rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborUtf8StringCodec.java rename to reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/CborUtf8StringCodec.java diff --git a/rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/IndexedUnsafeBuffer.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/IndexedUnsafeBuffer.java similarity index 100% rename from rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/IndexedUnsafeBuffer.java rename to reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/IndexedUnsafeBuffer.java diff --git a/rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/MetadataCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/MetadataCodec.java similarity index 100% rename from rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/MetadataCodec.java rename to reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/MetadataCodec.java diff --git a/rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/ReactiveSocketDefaultMetadataCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/ReactiveSocketDefaultMetadataCodec.java similarity index 100% rename from rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/ReactiveSocketDefaultMetadataCodec.java rename to reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/ReactiveSocketDefaultMetadataCodec.java diff --git a/rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/SlicedBufferKVMetadata.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/SlicedBufferKVMetadata.java similarity index 100% rename from rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/SlicedBufferKVMetadata.java rename to reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/cbor/SlicedBufferKVMetadata.java diff --git a/rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/json/JsonCodec.java b/reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/json/JsonCodec.java similarity index 100% rename from rs-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/json/JsonCodec.java rename to reactivesocket-mime-types/src/main/java/io/reactivesocket/mimetypes/internal/json/JsonCodec.java diff --git a/rs-mime-types/src/test/java/io/reactivesocket/mimetypes/MimeTypeFactoryTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/MimeTypeFactoryTest.java similarity index 100% rename from rs-mime-types/src/test/java/io/reactivesocket/mimetypes/MimeTypeFactoryTest.java rename to reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/MimeTypeFactoryTest.java diff --git a/rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodecTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodecTest.java similarity index 100% rename from rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodecTest.java rename to reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/AbstractJacksonCodecTest.java diff --git a/rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CodecRule.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CodecRule.java similarity index 100% rename from rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CodecRule.java rename to reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CodecRule.java diff --git a/rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CustomObject.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CustomObject.java similarity index 100% rename from rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CustomObject.java rename to reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CustomObject.java diff --git a/rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CustomObjectRule.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CustomObjectRule.java similarity index 100% rename from rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CustomObjectRule.java rename to reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/CustomObjectRule.java diff --git a/rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/KVMetadataImplTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/KVMetadataImplTest.java similarity index 100% rename from rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/KVMetadataImplTest.java rename to reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/KVMetadataImplTest.java diff --git a/rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/MetadataRule.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/MetadataRule.java similarity index 100% rename from rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/MetadataRule.java rename to reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/MetadataRule.java diff --git a/rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/ReactiveSocketDefaultMetadataCodecTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/ReactiveSocketDefaultMetadataCodecTest.java similarity index 100% rename from rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/ReactiveSocketDefaultMetadataCodecTest.java rename to reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/ReactiveSocketDefaultMetadataCodecTest.java diff --git a/rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/AbstractCborMapRule.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/AbstractCborMapRule.java similarity index 100% rename from rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/AbstractCborMapRule.java rename to reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/AbstractCborMapRule.java diff --git a/rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/ByteBufferMapMatcher.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/ByteBufferMapMatcher.java similarity index 100% rename from rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/ByteBufferMapMatcher.java rename to reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/ByteBufferMapMatcher.java diff --git a/rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORMapTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORMapTest.java similarity index 100% rename from rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORMapTest.java rename to reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORMapTest.java diff --git a/rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORMapValueMaskTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORMapValueMaskTest.java similarity index 100% rename from rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORMapValueMaskTest.java rename to reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORMapValueMaskTest.java diff --git a/rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORUtilsTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORUtilsTest.java similarity index 100% rename from rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORUtilsTest.java rename to reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CBORUtilsTest.java diff --git a/rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborBinaryStringCodecTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborBinaryStringCodecTest.java similarity index 100% rename from rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborBinaryStringCodecTest.java rename to reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborBinaryStringCodecTest.java diff --git a/rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborCodecTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborCodecTest.java similarity index 100% rename from rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborCodecTest.java rename to reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborCodecTest.java diff --git a/rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborHeaderTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborHeaderTest.java similarity index 100% rename from rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborHeaderTest.java rename to reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborHeaderTest.java diff --git a/rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborMapCodecTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborMapCodecTest.java similarity index 100% rename from rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborMapCodecTest.java rename to reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborMapCodecTest.java diff --git a/rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborUtf8StringCodecTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborUtf8StringCodecTest.java similarity index 100% rename from rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborUtf8StringCodecTest.java rename to reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/CborUtf8StringCodecTest.java diff --git a/rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/IndexedUnsafeBufferTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/IndexedUnsafeBufferTest.java similarity index 100% rename from rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/IndexedUnsafeBufferTest.java rename to reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/IndexedUnsafeBufferTest.java diff --git a/rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/MetadataCodecTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/MetadataCodecTest.java similarity index 100% rename from rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/MetadataCodecTest.java rename to reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/MetadataCodecTest.java diff --git a/rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/SlicedBufferKVMetadataTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/SlicedBufferKVMetadataTest.java similarity index 100% rename from rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/SlicedBufferKVMetadataTest.java rename to reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/cbor/SlicedBufferKVMetadataTest.java diff --git a/rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/json/JsonCodecTest.java b/reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/json/JsonCodecTest.java similarity index 100% rename from rs-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/json/JsonCodecTest.java rename to reactivesocket-mime-types/src/test/java/io/reactivesocket/mimetypes/internal/json/JsonCodecTest.java diff --git a/rs-mime-types/src/test/resources/log4j.properties b/reactivesocket-mime-types/src/test/resources/log4j.properties similarity index 100% rename from rs-mime-types/src/test/resources/log4j.properties rename to reactivesocket-mime-types/src/test/resources/log4j.properties diff --git a/reactivesocket-test/build.gradle b/reactivesocket-test/build.gradle new file mode 100644 index 000000000..5eff68dbc --- /dev/null +++ b/reactivesocket-test/build.gradle @@ -0,0 +1,3 @@ +dependencies { + compile project(':reactivesocket-core') +} diff --git a/rs-test/src/main/java/io/reactivesocket/test/TestUtil.java b/reactivesocket-test/src/main/java/io/reactivesocket/test/TestUtil.java similarity index 100% rename from rs-test/src/main/java/io/reactivesocket/test/TestUtil.java rename to reactivesocket-test/src/main/java/io/reactivesocket/test/TestUtil.java diff --git a/reactivesocket-transport-aeron/build.gradle b/reactivesocket-transport-aeron/build.gradle new file mode 100644 index 000000000..ce9b7eb0f --- /dev/null +++ b/reactivesocket-transport-aeron/build.gradle @@ -0,0 +1,5 @@ +dependencies { + compile project(':reactivesocket-core') + compile project(':reactivesocket-test') + compile 'io.aeron:aeron-all:0.9.5' +} \ No newline at end of file diff --git a/rs-transport-aeron/src/examples/java/io/reactivesocket/aeron/example/MediaDriver.java b/reactivesocket-transport-aeron/src/examples/java/io/reactivesocket/aeron/example/MediaDriver.java similarity index 100% rename from rs-transport-aeron/src/examples/java/io/reactivesocket/aeron/example/MediaDriver.java rename to reactivesocket-transport-aeron/src/examples/java/io/reactivesocket/aeron/example/MediaDriver.java diff --git a/rs-transport-aeron/src/examples/java/io/reactivesocket/aeron/example/fireandforget/Fire.java b/reactivesocket-transport-aeron/src/examples/java/io/reactivesocket/aeron/example/fireandforget/Fire.java similarity index 100% rename from rs-transport-aeron/src/examples/java/io/reactivesocket/aeron/example/fireandforget/Fire.java rename to reactivesocket-transport-aeron/src/examples/java/io/reactivesocket/aeron/example/fireandforget/Fire.java diff --git a/rs-transport-aeron/src/examples/java/io/reactivesocket/aeron/example/fireandforget/Forget.java b/reactivesocket-transport-aeron/src/examples/java/io/reactivesocket/aeron/example/fireandforget/Forget.java similarity index 100% rename from rs-transport-aeron/src/examples/java/io/reactivesocket/aeron/example/fireandforget/Forget.java rename to reactivesocket-transport-aeron/src/examples/java/io/reactivesocket/aeron/example/fireandforget/Forget.java diff --git a/rs-transport-aeron/src/examples/java/io/reactivesocket/aeron/example/requestreply/Ping.java b/reactivesocket-transport-aeron/src/examples/java/io/reactivesocket/aeron/example/requestreply/Ping.java similarity index 100% rename from rs-transport-aeron/src/examples/java/io/reactivesocket/aeron/example/requestreply/Ping.java rename to reactivesocket-transport-aeron/src/examples/java/io/reactivesocket/aeron/example/requestreply/Ping.java diff --git a/rs-transport-aeron/src/examples/java/io/reactivesocket/aeron/example/requestreply/Pong.java b/reactivesocket-transport-aeron/src/examples/java/io/reactivesocket/aeron/example/requestreply/Pong.java similarity index 100% rename from rs-transport-aeron/src/examples/java/io/reactivesocket/aeron/example/requestreply/Pong.java rename to reactivesocket-transport-aeron/src/examples/java/io/reactivesocket/aeron/example/requestreply/Pong.java diff --git a/rs-transport-aeron/src/examples/resources/simplelogger.properties b/reactivesocket-transport-aeron/src/examples/resources/simplelogger.properties similarity index 100% rename from rs-transport-aeron/src/examples/resources/simplelogger.properties rename to reactivesocket-transport-aeron/src/examples/resources/simplelogger.properties diff --git a/rs-transport-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java b/reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java similarity index 100% rename from rs-transport-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java rename to reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnection.java diff --git a/rs-transport-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java b/reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java similarity index 100% rename from rs-transport-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java rename to reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/client/AeronClientDuplexConnectionFactory.java diff --git a/rs-transport-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketConnector.java b/reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketConnector.java similarity index 100% rename from rs-transport-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketConnector.java rename to reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/client/AeronReactiveSocketConnector.java diff --git a/rs-transport-aeron/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java b/reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java similarity index 100% rename from rs-transport-aeron/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java rename to reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/client/ClientAeronManager.java diff --git a/rs-transport-aeron/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java b/reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java similarity index 100% rename from rs-transport-aeron/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java rename to reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/client/FrameHolder.java diff --git a/rs-transport-aeron/src/main/java/io/reactivesocket/aeron/client/PollingAction.java b/reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/client/PollingAction.java similarity index 100% rename from rs-transport-aeron/src/main/java/io/reactivesocket/aeron/client/PollingAction.java rename to reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/client/PollingAction.java diff --git a/rs-transport-aeron/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java b/reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java similarity index 100% rename from rs-transport-aeron/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java rename to reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/internal/AeronUtil.java diff --git a/rs-transport-aeron/src/main/java/io/reactivesocket/aeron/internal/Constants.java b/reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/internal/Constants.java similarity index 100% rename from rs-transport-aeron/src/main/java/io/reactivesocket/aeron/internal/Constants.java rename to reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/internal/Constants.java diff --git a/rs-transport-aeron/src/main/java/io/reactivesocket/aeron/internal/Loggable.java b/reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/internal/Loggable.java similarity index 100% rename from rs-transport-aeron/src/main/java/io/reactivesocket/aeron/internal/Loggable.java rename to reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/internal/Loggable.java diff --git a/rs-transport-aeron/src/main/java/io/reactivesocket/aeron/internal/MessageType.java b/reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/internal/MessageType.java similarity index 100% rename from rs-transport-aeron/src/main/java/io/reactivesocket/aeron/internal/MessageType.java rename to reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/internal/MessageType.java diff --git a/rs-transport-aeron/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java b/reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java similarity index 100% rename from rs-transport-aeron/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java rename to reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/internal/NotConnectedException.java diff --git a/rs-transport-aeron/src/main/java/io/reactivesocket/aeron/internal/TimedOutException.java b/reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/internal/TimedOutException.java similarity index 100% rename from rs-transport-aeron/src/main/java/io/reactivesocket/aeron/internal/TimedOutException.java rename to reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/internal/TimedOutException.java diff --git a/rs-transport-aeron/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java b/reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java similarity index 100% rename from rs-transport-aeron/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java rename to reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/server/AeronServerDuplexConnection.java diff --git a/rs-transport-aeron/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java b/reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java similarity index 100% rename from rs-transport-aeron/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java rename to reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/server/ReactiveSocketAeronServer.java diff --git a/rs-transport-aeron/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java b/reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java similarity index 100% rename from rs-transport-aeron/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java rename to reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/server/ServerAeronManager.java diff --git a/rs-transport-aeron/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java b/reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java similarity index 100% rename from rs-transport-aeron/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java rename to reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/server/ServerSubscription.java diff --git a/rs-transport-aeron/src/main/java/io/reactivesocket/aeron/server/TimerWheelFairLeaseGovernor.java b/reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/server/TimerWheelFairLeaseGovernor.java similarity index 100% rename from rs-transport-aeron/src/main/java/io/reactivesocket/aeron/server/TimerWheelFairLeaseGovernor.java rename to reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/server/TimerWheelFairLeaseGovernor.java diff --git a/rs-transport-aeron/src/main/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronScheduler.java b/reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronScheduler.java similarity index 100% rename from rs-transport-aeron/src/main/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronScheduler.java rename to reactivesocket-transport-aeron/src/main/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronScheduler.java diff --git a/rs-transport-aeron/src/perf/java/io/aeron/DummySubscription.java b/reactivesocket-transport-aeron/src/perf/java/io/aeron/DummySubscription.java similarity index 100% rename from rs-transport-aeron/src/perf/java/io/aeron/DummySubscription.java rename to reactivesocket-transport-aeron/src/perf/java/io/aeron/DummySubscription.java diff --git a/rs-transport-aeron/src/perf/java/io/reactivesocket/aeron/client/PollingActionPerf.java b/reactivesocket-transport-aeron/src/perf/java/io/reactivesocket/aeron/client/PollingActionPerf.java similarity index 100% rename from rs-transport-aeron/src/perf/java/io/reactivesocket/aeron/client/PollingActionPerf.java rename to reactivesocket-transport-aeron/src/perf/java/io/reactivesocket/aeron/client/PollingActionPerf.java diff --git a/rs-transport-aeron/src/perf/java/io/reactivesocket/aeron/jmh/InputWithIncrementingInteger.java b/reactivesocket-transport-aeron/src/perf/java/io/reactivesocket/aeron/jmh/InputWithIncrementingInteger.java similarity index 100% rename from rs-transport-aeron/src/perf/java/io/reactivesocket/aeron/jmh/InputWithIncrementingInteger.java rename to reactivesocket-transport-aeron/src/perf/java/io/reactivesocket/aeron/jmh/InputWithIncrementingInteger.java diff --git a/rs-transport-aeron/src/perf/java/io/reactivesocket/aeron/jmh/LatchedObserver.java b/reactivesocket-transport-aeron/src/perf/java/io/reactivesocket/aeron/jmh/LatchedObserver.java similarity index 100% rename from rs-transport-aeron/src/perf/java/io/reactivesocket/aeron/jmh/LatchedObserver.java rename to reactivesocket-transport-aeron/src/perf/java/io/reactivesocket/aeron/jmh/LatchedObserver.java diff --git a/rs-transport-aeron/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java b/reactivesocket-transport-aeron/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java similarity index 100% rename from rs-transport-aeron/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java rename to reactivesocket-transport-aeron/src/test/java/io/reactivesocket/aeron/client/ReactiveSocketAeronTest.java diff --git a/rs-transport-aeron/src/test/java/io/reactivesocket/aeron/internal/AeronUtilTest.java b/reactivesocket-transport-aeron/src/test/java/io/reactivesocket/aeron/internal/AeronUtilTest.java similarity index 100% rename from rs-transport-aeron/src/test/java/io/reactivesocket/aeron/internal/AeronUtilTest.java rename to reactivesocket-transport-aeron/src/test/java/io/reactivesocket/aeron/internal/AeronUtilTest.java diff --git a/rs-transport-aeron/src/test/java/io/reactivesocket/aeron/server/ServerAeronManagerTest.java b/reactivesocket-transport-aeron/src/test/java/io/reactivesocket/aeron/server/ServerAeronManagerTest.java similarity index 100% rename from rs-transport-aeron/src/test/java/io/reactivesocket/aeron/server/ServerAeronManagerTest.java rename to reactivesocket-transport-aeron/src/test/java/io/reactivesocket/aeron/server/ServerAeronManagerTest.java diff --git a/rs-transport-aeron/src/test/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronSchedulerTest.java b/reactivesocket-transport-aeron/src/test/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronSchedulerTest.java similarity index 100% rename from rs-transport-aeron/src/test/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronSchedulerTest.java rename to reactivesocket-transport-aeron/src/test/java/io/reactivesocket/aeron/server/rx/ReactiveSocketAeronSchedulerTest.java diff --git a/rs-transport-aeron/src/test/resources/simplelogger.properties b/reactivesocket-transport-aeron/src/test/resources/simplelogger.properties similarity index 100% rename from rs-transport-aeron/src/test/resources/simplelogger.properties rename to reactivesocket-transport-aeron/src/test/resources/simplelogger.properties diff --git a/rs-transport-jsr-356/build.gradle b/reactivesocket-transport-jsr-356/build.gradle similarity index 68% rename from rs-transport-jsr-356/build.gradle rename to reactivesocket-transport-jsr-356/build.gradle index 27a0fdabc..d78cacae5 100644 --- a/rs-transport-jsr-356/build.gradle +++ b/reactivesocket-transport-jsr-356/build.gradle @@ -1,8 +1,8 @@ dependencies { - compile project(':rs-core') + compile project(':reactivesocket-core') compile 'org.glassfish.tyrus:tyrus-client:1.12' testCompile 'org.glassfish.tyrus:tyrus-server:1.12' testCompile 'org.glassfish.tyrus:tyrus-container-grizzly-server:1.12' - testCompile project(':rs-test') + testCompile project(':reactivesocket-test') } \ No newline at end of file diff --git a/rs-transport-jsr-356/src/main/java/io/reactivesocket/javax/websocket/WebSocketDuplexConnection.java b/reactivesocket-transport-jsr-356/src/main/java/io/reactivesocket/javax/websocket/WebSocketDuplexConnection.java similarity index 100% rename from rs-transport-jsr-356/src/main/java/io/reactivesocket/javax/websocket/WebSocketDuplexConnection.java rename to reactivesocket-transport-jsr-356/src/main/java/io/reactivesocket/javax/websocket/WebSocketDuplexConnection.java diff --git a/rs-transport-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/ReactiveSocketWebSocketClient.java b/reactivesocket-transport-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/ReactiveSocketWebSocketClient.java similarity index 100% rename from rs-transport-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/ReactiveSocketWebSocketClient.java rename to reactivesocket-transport-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/ReactiveSocketWebSocketClient.java diff --git a/rs-transport-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketConnector.java b/reactivesocket-transport-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketConnector.java similarity index 100% rename from rs-transport-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketConnector.java rename to reactivesocket-transport-jsr-356/src/main/java/io/reactivesocket/javax/websocket/client/WebSocketReactiveSocketConnector.java diff --git a/rs-transport-jsr-356/src/main/java/io/reactivesocket/javax/websocket/server/ReactiveSocketWebSocketServer.java b/reactivesocket-transport-jsr-356/src/main/java/io/reactivesocket/javax/websocket/server/ReactiveSocketWebSocketServer.java similarity index 100% rename from rs-transport-jsr-356/src/main/java/io/reactivesocket/javax/websocket/server/ReactiveSocketWebSocketServer.java rename to reactivesocket-transport-jsr-356/src/main/java/io/reactivesocket/javax/websocket/server/ReactiveSocketWebSocketServer.java diff --git a/rs-transport-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerEndpoint.java b/reactivesocket-transport-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerEndpoint.java similarity index 100% rename from rs-transport-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerEndpoint.java rename to reactivesocket-transport-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerEndpoint.java diff --git a/rs-transport-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerTest.java b/reactivesocket-transport-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerTest.java similarity index 100% rename from rs-transport-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerTest.java rename to reactivesocket-transport-jsr-356/src/test/java/io/reactivesocket/javax/websocket/ClientServerTest.java diff --git a/rs-transport-jsr-356/src/test/java/io/reactivesocket/javax/websocket/Ping.java b/reactivesocket-transport-jsr-356/src/test/java/io/reactivesocket/javax/websocket/Ping.java similarity index 100% rename from rs-transport-jsr-356/src/test/java/io/reactivesocket/javax/websocket/Ping.java rename to reactivesocket-transport-jsr-356/src/test/java/io/reactivesocket/javax/websocket/Ping.java diff --git a/rs-transport-jsr-356/src/test/java/io/reactivesocket/javax/websocket/Pong.java b/reactivesocket-transport-jsr-356/src/test/java/io/reactivesocket/javax/websocket/Pong.java similarity index 100% rename from rs-transport-jsr-356/src/test/java/io/reactivesocket/javax/websocket/Pong.java rename to reactivesocket-transport-jsr-356/src/test/java/io/reactivesocket/javax/websocket/Pong.java diff --git a/rs-transport-jsr-356/src/test/java/io/reactivesocket/javax/websocket/PongEndpoint.java b/reactivesocket-transport-jsr-356/src/test/java/io/reactivesocket/javax/websocket/PongEndpoint.java similarity index 100% rename from rs-transport-jsr-356/src/test/java/io/reactivesocket/javax/websocket/PongEndpoint.java rename to reactivesocket-transport-jsr-356/src/test/java/io/reactivesocket/javax/websocket/PongEndpoint.java diff --git a/rs-transport-jsr-356/src/test/resources/simplelogger.properties b/reactivesocket-transport-jsr-356/src/test/resources/simplelogger.properties similarity index 100% rename from rs-transport-jsr-356/src/test/resources/simplelogger.properties rename to reactivesocket-transport-jsr-356/src/test/resources/simplelogger.properties diff --git a/reactivesocket-transport-local/build.gradle b/reactivesocket-transport-local/build.gradle new file mode 100644 index 000000000..887584cdc --- /dev/null +++ b/reactivesocket-transport-local/build.gradle @@ -0,0 +1,4 @@ +dependencies { + compile project(':reactivesocket-core') + testCompile project(':reactivesocket-test') +} diff --git a/rs-transport-local/src/main/java/io/reactivesocket/local/LocalClientDuplexConnection.java b/reactivesocket-transport-local/src/main/java/io/reactivesocket/local/LocalClientDuplexConnection.java similarity index 100% rename from rs-transport-local/src/main/java/io/reactivesocket/local/LocalClientDuplexConnection.java rename to reactivesocket-transport-local/src/main/java/io/reactivesocket/local/LocalClientDuplexConnection.java diff --git a/rs-transport-local/src/main/java/io/reactivesocket/local/LocalClientReactiveSocketConnector.java b/reactivesocket-transport-local/src/main/java/io/reactivesocket/local/LocalClientReactiveSocketConnector.java similarity index 100% rename from rs-transport-local/src/main/java/io/reactivesocket/local/LocalClientReactiveSocketConnector.java rename to reactivesocket-transport-local/src/main/java/io/reactivesocket/local/LocalClientReactiveSocketConnector.java diff --git a/rs-transport-local/src/main/java/io/reactivesocket/local/LocalReactiveSocketManager.java b/reactivesocket-transport-local/src/main/java/io/reactivesocket/local/LocalReactiveSocketManager.java similarity index 100% rename from rs-transport-local/src/main/java/io/reactivesocket/local/LocalReactiveSocketManager.java rename to reactivesocket-transport-local/src/main/java/io/reactivesocket/local/LocalReactiveSocketManager.java diff --git a/rs-transport-local/src/main/java/io/reactivesocket/local/LocalServerDuplexConection.java b/reactivesocket-transport-local/src/main/java/io/reactivesocket/local/LocalServerDuplexConection.java similarity index 100% rename from rs-transport-local/src/main/java/io/reactivesocket/local/LocalServerDuplexConection.java rename to reactivesocket-transport-local/src/main/java/io/reactivesocket/local/LocalServerDuplexConection.java diff --git a/rs-transport-local/src/main/java/io/reactivesocket/local/LocalServerReactiveSocketConnector.java b/reactivesocket-transport-local/src/main/java/io/reactivesocket/local/LocalServerReactiveSocketConnector.java similarity index 100% rename from rs-transport-local/src/main/java/io/reactivesocket/local/LocalServerReactiveSocketConnector.java rename to reactivesocket-transport-local/src/main/java/io/reactivesocket/local/LocalServerReactiveSocketConnector.java diff --git a/rs-transport-local/src/test/java/io/reactivesocket/local/ClientServerTest.java b/reactivesocket-transport-local/src/test/java/io/reactivesocket/local/ClientServerTest.java similarity index 100% rename from rs-transport-local/src/test/java/io/reactivesocket/local/ClientServerTest.java rename to reactivesocket-transport-local/src/test/java/io/reactivesocket/local/ClientServerTest.java diff --git a/rs-transport-netty/build.gradle b/reactivesocket-transport-netty/build.gradle similarity index 73% rename from rs-transport-netty/build.gradle rename to reactivesocket-transport-netty/build.gradle index 73dc23adf..5bea1d904 100644 --- a/rs-transport-netty/build.gradle +++ b/reactivesocket-transport-netty/build.gradle @@ -1,9 +1,9 @@ dependencies { - compile project(':rs-core') + compile project(':reactivesocket-core') compile 'io.netty:netty-handler:4.1.0.CR7' compile 'io.netty:netty-codec-http:4.1.0.CR7' - testCompile project(':rs-test') + testCompile project(':reactivesocket-test') } task echoServer(type: JavaExec) { diff --git a/rs-transport-netty/src/examples/java/io/reactivesocket/netty/EchoServer.java b/reactivesocket-transport-netty/src/examples/java/io/reactivesocket/netty/EchoServer.java similarity index 100% rename from rs-transport-netty/src/examples/java/io/reactivesocket/netty/EchoServer.java rename to reactivesocket-transport-netty/src/examples/java/io/reactivesocket/netty/EchoServer.java diff --git a/rs-transport-netty/src/examples/java/io/reactivesocket/netty/EchoServerHandler.java b/reactivesocket-transport-netty/src/examples/java/io/reactivesocket/netty/EchoServerHandler.java similarity index 100% rename from rs-transport-netty/src/examples/java/io/reactivesocket/netty/EchoServerHandler.java rename to reactivesocket-transport-netty/src/examples/java/io/reactivesocket/netty/EchoServerHandler.java diff --git a/rs-transport-netty/src/examples/java/io/reactivesocket/netty/HttpServerHandler.java b/reactivesocket-transport-netty/src/examples/java/io/reactivesocket/netty/HttpServerHandler.java similarity index 100% rename from rs-transport-netty/src/examples/java/io/reactivesocket/netty/HttpServerHandler.java rename to reactivesocket-transport-netty/src/examples/java/io/reactivesocket/netty/HttpServerHandler.java diff --git a/rs-transport-netty/src/main/java/io/reactivesocket/netty/MutableDirectByteBuf.java b/reactivesocket-transport-netty/src/main/java/io/reactivesocket/netty/MutableDirectByteBuf.java similarity index 100% rename from rs-transport-netty/src/main/java/io/reactivesocket/netty/MutableDirectByteBuf.java rename to reactivesocket-transport-netty/src/main/java/io/reactivesocket/netty/MutableDirectByteBuf.java diff --git a/rs-transport-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java b/reactivesocket-transport-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java similarity index 100% rename from rs-transport-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java rename to reactivesocket-transport-netty/src/main/java/io/reactivesocket/netty/tcp/client/ClientTcpDuplexConnection.java diff --git a/rs-transport-netty/src/main/java/io/reactivesocket/netty/tcp/client/ReactiveSocketClientHandler.java b/reactivesocket-transport-netty/src/main/java/io/reactivesocket/netty/tcp/client/ReactiveSocketClientHandler.java similarity index 100% rename from rs-transport-netty/src/main/java/io/reactivesocket/netty/tcp/client/ReactiveSocketClientHandler.java rename to reactivesocket-transport-netty/src/main/java/io/reactivesocket/netty/tcp/client/ReactiveSocketClientHandler.java diff --git a/rs-transport-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketConnector.java b/reactivesocket-transport-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketConnector.java similarity index 100% rename from rs-transport-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketConnector.java rename to reactivesocket-transport-netty/src/main/java/io/reactivesocket/netty/tcp/client/TcpReactiveSocketConnector.java diff --git a/rs-transport-netty/src/main/java/io/reactivesocket/netty/tcp/server/ReactiveSocketServerHandler.java b/reactivesocket-transport-netty/src/main/java/io/reactivesocket/netty/tcp/server/ReactiveSocketServerHandler.java similarity index 100% rename from rs-transport-netty/src/main/java/io/reactivesocket/netty/tcp/server/ReactiveSocketServerHandler.java rename to reactivesocket-transport-netty/src/main/java/io/reactivesocket/netty/tcp/server/ReactiveSocketServerHandler.java diff --git a/rs-transport-netty/src/main/java/io/reactivesocket/netty/tcp/server/ServerTcpDuplexConnection.java b/reactivesocket-transport-netty/src/main/java/io/reactivesocket/netty/tcp/server/ServerTcpDuplexConnection.java similarity index 100% rename from rs-transport-netty/src/main/java/io/reactivesocket/netty/tcp/server/ServerTcpDuplexConnection.java rename to reactivesocket-transport-netty/src/main/java/io/reactivesocket/netty/tcp/server/ServerTcpDuplexConnection.java diff --git a/rs-transport-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java b/reactivesocket-transport-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java similarity index 100% rename from rs-transport-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java rename to reactivesocket-transport-netty/src/main/java/io/reactivesocket/netty/websocket/client/ClientWebSocketDuplexConnection.java diff --git a/rs-transport-netty/src/main/java/io/reactivesocket/netty/websocket/client/ReactiveSocketClientHandler.java b/reactivesocket-transport-netty/src/main/java/io/reactivesocket/netty/websocket/client/ReactiveSocketClientHandler.java similarity index 100% rename from rs-transport-netty/src/main/java/io/reactivesocket/netty/websocket/client/ReactiveSocketClientHandler.java rename to reactivesocket-transport-netty/src/main/java/io/reactivesocket/netty/websocket/client/ReactiveSocketClientHandler.java diff --git a/rs-transport-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketConnector.java b/reactivesocket-transport-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketConnector.java similarity index 100% rename from rs-transport-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketConnector.java rename to reactivesocket-transport-netty/src/main/java/io/reactivesocket/netty/websocket/client/WebSocketReactiveSocketConnector.java diff --git a/rs-transport-netty/src/main/java/io/reactivesocket/netty/websocket/server/ReactiveSocketServerHandler.java b/reactivesocket-transport-netty/src/main/java/io/reactivesocket/netty/websocket/server/ReactiveSocketServerHandler.java similarity index 100% rename from rs-transport-netty/src/main/java/io/reactivesocket/netty/websocket/server/ReactiveSocketServerHandler.java rename to reactivesocket-transport-netty/src/main/java/io/reactivesocket/netty/websocket/server/ReactiveSocketServerHandler.java diff --git a/rs-transport-netty/src/main/java/io/reactivesocket/netty/websocket/server/ServerWebSocketDuplexConnection.java b/reactivesocket-transport-netty/src/main/java/io/reactivesocket/netty/websocket/server/ServerWebSocketDuplexConnection.java similarity index 100% rename from rs-transport-netty/src/main/java/io/reactivesocket/netty/websocket/server/ServerWebSocketDuplexConnection.java rename to reactivesocket-transport-netty/src/main/java/io/reactivesocket/netty/websocket/server/ServerWebSocketDuplexConnection.java diff --git a/rs-transport-netty/src/test/java/io/reactivesocket/netty/tcp/ClientServerTest.java b/reactivesocket-transport-netty/src/test/java/io/reactivesocket/netty/tcp/ClientServerTest.java similarity index 100% rename from rs-transport-netty/src/test/java/io/reactivesocket/netty/tcp/ClientServerTest.java rename to reactivesocket-transport-netty/src/test/java/io/reactivesocket/netty/tcp/ClientServerTest.java diff --git a/rs-transport-netty/src/test/java/io/reactivesocket/netty/tcp/Ping.java b/reactivesocket-transport-netty/src/test/java/io/reactivesocket/netty/tcp/Ping.java similarity index 100% rename from rs-transport-netty/src/test/java/io/reactivesocket/netty/tcp/Ping.java rename to reactivesocket-transport-netty/src/test/java/io/reactivesocket/netty/tcp/Ping.java diff --git a/rs-transport-netty/src/test/java/io/reactivesocket/netty/tcp/Pong.java b/reactivesocket-transport-netty/src/test/java/io/reactivesocket/netty/tcp/Pong.java similarity index 100% rename from rs-transport-netty/src/test/java/io/reactivesocket/netty/tcp/Pong.java rename to reactivesocket-transport-netty/src/test/java/io/reactivesocket/netty/tcp/Pong.java diff --git a/rs-transport-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java b/reactivesocket-transport-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java similarity index 100% rename from rs-transport-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java rename to reactivesocket-transport-netty/src/test/java/io/reactivesocket/netty/websocket/ClientServerTest.java diff --git a/rs-transport-netty/src/test/java/io/reactivesocket/netty/websocket/Ping.java b/reactivesocket-transport-netty/src/test/java/io/reactivesocket/netty/websocket/Ping.java similarity index 100% rename from rs-transport-netty/src/test/java/io/reactivesocket/netty/websocket/Ping.java rename to reactivesocket-transport-netty/src/test/java/io/reactivesocket/netty/websocket/Ping.java diff --git a/rs-transport-netty/src/test/java/io/reactivesocket/netty/websocket/Pong.java b/reactivesocket-transport-netty/src/test/java/io/reactivesocket/netty/websocket/Pong.java similarity index 100% rename from rs-transport-netty/src/test/java/io/reactivesocket/netty/websocket/Pong.java rename to reactivesocket-transport-netty/src/test/java/io/reactivesocket/netty/websocket/Pong.java diff --git a/rs-transport-netty/src/test/resources/simplelogger.properties b/reactivesocket-transport-netty/src/test/resources/simplelogger.properties similarity index 100% rename from rs-transport-netty/src/test/resources/simplelogger.properties rename to reactivesocket-transport-netty/src/test/resources/simplelogger.properties diff --git a/rs-test/build.gradle b/rs-test/build.gradle deleted file mode 100644 index 92e91e568..000000000 --- a/rs-test/build.gradle +++ /dev/null @@ -1,3 +0,0 @@ -dependencies { - compile project(':rs-core') -} diff --git a/rs-transport-aeron/build.gradle b/rs-transport-aeron/build.gradle deleted file mode 100644 index 53433dbfc..000000000 --- a/rs-transport-aeron/build.gradle +++ /dev/null @@ -1,5 +0,0 @@ -dependencies { - compile project(':rs-core') - compile project(':rs-test') - compile 'io.aeron:aeron-all:0.9.5' -} \ No newline at end of file diff --git a/rs-transport-local/build.gradle b/rs-transport-local/build.gradle deleted file mode 100644 index 7d43bf0cf..000000000 --- a/rs-transport-local/build.gradle +++ /dev/null @@ -1,4 +0,0 @@ -dependencies { - compile project(':rs-core') - testCompile project(':rs-test') -} diff --git a/settings.gradle b/settings.gradle index c635afeb4..47d88f0fe 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,8 +1,8 @@ rootProject.name='reactivesocket' -include 'rs-core' -include 'rs-transport-aeron' -include 'rs-transport-jsr-356' -include 'rs-transport-netty' -include 'rs-transport-local' -include 'rs-mime-types' -include 'rs-test' +include 'reactivesocket-core' +include 'reactivesocket-transport-aeron' +include 'reactivesocket-transport-jsr-356' +include 'reactivesocket-transport-netty' +include 'reactivesocket-transport-local' +include 'reactivesocket-mime-types' +include 'reactivesocket-test'