├── .editorconfig ├── .github ├── maven-cd-settings.xml ├── maven-ci-settings.xml └── workflows │ ├── ci-4.x.yml │ ├── ci-5.x-stable.yml │ ├── ci-5.x.yml │ ├── ci-matrix-5.x.yml │ ├── ci.yml │ └── deploy.yml ├── .gitignore ├── LICENSE.txt ├── README.adoc ├── pom.xml └── src ├── main ├── asciidoc │ └── index.adoc └── java │ ├── examples │ └── ReactiveStreamsExamples.java │ ├── io │ └── vertx │ │ └── ext │ │ └── reactivestreams │ │ ├── ReactiveReadStream.java │ │ ├── ReactiveWriteStream.java │ │ ├── impl │ │ ├── ReactiveReadStreamImpl.java │ │ └── ReactiveWriteStreamImpl.java │ │ └── package-info.java │ └── module-info.java └── test └── java ├── io └── vertx │ └── ext │ └── reactivestreams │ ├── tck │ ├── FiniteReactiveWriteStream.java │ ├── PublisherVerificationTest.java │ ├── SubscriberBlackboxVerificationTest.java │ └── SubscriberWhiteboxVerificationTest.java │ └── test │ ├── ReactiveReadStreamTest.java │ ├── ReactiveStreamTestBase.java │ └── ReactiveWriteStreamTest.java └── module-info.java /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | trim_trailing_whitespace = true 8 | end_of_line = lf 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.github/maven-cd-settings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | false 20 | 21 | 22 | 23 | vertx-snapshots-repository 24 | ${env.VERTX_NEXUS_USERNAME} 25 | ${env.VERTX_NEXUS_PASSWORD} 26 | 27 | 28 | 29 | 30 | 31 | google-mirror 32 | 33 | true 34 | 35 | 36 | 37 | google-maven-central 38 | GCS Maven Central mirror EU 39 | https://maven-central.storage-download.googleapis.com/maven2/ 40 | 41 | true 42 | 43 | 44 | false 45 | 46 | 47 | 48 | 49 | 50 | google-maven-central 51 | GCS Maven Central mirror 52 | https://maven-central.storage-download.googleapis.com/maven2/ 53 | 54 | true 55 | 56 | 57 | false 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /.github/maven-ci-settings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | false 20 | 21 | 22 | 23 | google-mirror 24 | 25 | true 26 | 27 | 28 | 29 | google-maven-central 30 | GCS Maven Central mirror EU 31 | https://maven-central.storage-download.googleapis.com/maven2/ 32 | 33 | true 34 | 35 | 36 | false 37 | 38 | 39 | 40 | 41 | 42 | google-maven-central 43 | GCS Maven Central mirror 44 | https://maven-central.storage-download.googleapis.com/maven2/ 45 | 46 | true 47 | 48 | 49 | false 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /.github/workflows/ci-4.x.yml: -------------------------------------------------------------------------------- 1 | name: vertx-reactive-streams (4.x) 2 | on: 3 | schedule: 4 | - cron: '0 4 * * *' 5 | jobs: 6 | CI: 7 | strategy: 8 | matrix: 9 | include: 10 | - os: ubuntu-latest 11 | jdk: 8 12 | - os: ubuntu-latest 13 | jdk: 17 14 | uses: ./.github/workflows/ci.yml 15 | with: 16 | branch: 4.x 17 | jdk: ${{ matrix.jdk }} 18 | os: ${{ matrix.os }} 19 | secrets: inherit 20 | Deploy: 21 | if: ${{ github.repository_owner == 'vert-x3' && (github.event_name == 'push' || github.event_name == 'schedule') }} 22 | needs: CI 23 | uses: ./.github/workflows/deploy.yml 24 | with: 25 | branch: 4.x 26 | jdk: 8 27 | secrets: inherit 28 | -------------------------------------------------------------------------------- /.github/workflows/ci-5.x-stable.yml: -------------------------------------------------------------------------------- 1 | name: vertx-reactive-streams (5.x-stable) 2 | on: 3 | push: 4 | branches: 5 | - '5.[0-9]+' 6 | pull_request: 7 | branches: 8 | - '5.[0-9]+' 9 | schedule: 10 | - cron: '0 6 * * *' 11 | jobs: 12 | CI-CD: 13 | uses: ./.github/workflows/ci-matrix-5.x.yml 14 | secrets: inherit 15 | with: 16 | branch: ${{ github.event_name == 'schedule' && vars.VERTX_5_STABLE_BRANCH || github.event.pull_request.head.sha || github.ref_name }} 17 | -------------------------------------------------------------------------------- /.github/workflows/ci-5.x.yml: -------------------------------------------------------------------------------- 1 | name: vertx-reactive-streams (5.x) 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | schedule: 10 | - cron: '0 5 * * *' 11 | jobs: 12 | CI-CD: 13 | uses: ./.github/workflows/ci-matrix-5.x.yml 14 | secrets: inherit 15 | with: 16 | branch: ${{ github.event.pull_request.head.sha || github.ref_name }} 17 | -------------------------------------------------------------------------------- /.github/workflows/ci-matrix-5.x.yml: -------------------------------------------------------------------------------- 1 | name: CI matrix (5.x) 2 | on: 3 | workflow_call: 4 | inputs: 5 | branch: 6 | required: true 7 | type: string 8 | jobs: 9 | CI: 10 | strategy: 11 | matrix: 12 | include: 13 | - os: ubuntu-latest 14 | jdk: 11 15 | - os: ubuntu-latest 16 | jdk: 21 17 | uses: ./.github/workflows/ci.yml 18 | with: 19 | branch: ${{ inputs.branch }} 20 | jdk: ${{ matrix.jdk }} 21 | os: ${{ matrix.os }} 22 | secrets: inherit 23 | Deploy: 24 | if: ${{ github.repository_owner == 'vert-x3' && (github.event_name == 'push' || github.event_name == 'schedule') }} 25 | needs: CI 26 | uses: ./.github/workflows/deploy.yml 27 | with: 28 | branch: ${{ inputs.branch }} 29 | jdk: 11 30 | secrets: inherit 31 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | workflow_call: 4 | inputs: 5 | branch: 6 | required: true 7 | type: string 8 | jdk: 9 | default: 8 10 | type: string 11 | os: 12 | default: ubuntu-latest 13 | type: string 14 | jobs: 15 | Test: 16 | name: Run tests 17 | runs-on: ${{ inputs.os }} 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | with: 22 | ref: ${{ inputs.branch }} 23 | - name: Install JDK 24 | uses: actions/setup-java@v2 25 | with: 26 | java-version: ${{ inputs.jdk }} 27 | distribution: temurin 28 | - name: Run tests 29 | run: mvn -s .github/maven-ci-settings.xml -q clean verify -B 30 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | workflow_call: 4 | inputs: 5 | branch: 6 | required: true 7 | type: string 8 | jdk: 9 | default: 8 10 | type: string 11 | jobs: 12 | Deploy: 13 | name: Deploy to OSSRH 14 | runs-on: ubuntu-latest 15 | env: 16 | VERTX_NEXUS_USERNAME: ${{ secrets.VERTX_NEXUS_USERNAME }} 17 | VERTX_NEXUS_PASSWORD: ${{ secrets.VERTX_NEXUS_PASSWORD }} 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | with: 22 | ref: ${{ inputs.branch }} 23 | - name: Install JDK 24 | uses: actions/setup-java@v2 25 | with: 26 | java-version: ${{ inputs.jdk }} 27 | distribution: temurin 28 | - name: Get project version 29 | run: echo "PROJECT_VERSION=$(mvn org.apache.maven.plugins:maven-help-plugin:evaluate -Dexpression=project.version -q -DforceStdout | grep -v '\[')" >> $GITHUB_ENV 30 | - name: Maven deploy 31 | if: ${{ endsWith(env.PROJECT_VERSION, '-SNAPSHOT') }} 32 | run: mvn deploy -s .github/maven-cd-settings.xml -DskipTests -B 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .gradle 3 | .idea 4 | .classpath 5 | .project 6 | .settings 7 | .yardoc 8 | .yardopts 9 | build 10 | target 11 | out 12 | *.iml 13 | *.ipr 14 | *.iws 15 | test-output 16 | Scratch.java 17 | ScratchTest.java 18 | test-results 19 | test-tmp 20 | *.class 21 | ScratchPad.java 22 | src/main/resources/ext-js/*.js 23 | src/main/java/io/vertx/java/**/*.java 24 | src/main/groovy/io/vertx/groovy/**/*.groovy 25 | *.swp 26 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Reactive Streams for Vert.x 2 | 3 | image:https://github.com/vert-x3/vertx-reactive-streams/actions/workflows/ci-5.x.yml/badge.svg["Build Status (5.x)",link="https://github.com/vert-x3/vertx-reactive-streams/actions/workflows/ci-5.x.yml"] 4 | image:https://github.com/vert-x3/vertx-reactive-streams/actions/workflows/ci-4.x.yml/badge.svg["Build Status (4.x)",link="https://github.com/vert-x3/vertx-reactive-streams/actions/workflows/ci-4.x.yml"] 5 | 6 | "link:http://www.reactive-streams.org/[Reactive Streams] is an initiative to provide a standard for asynchronous stream 7 | processing with non-blocking back pressure on the JVM." 8 | 9 | Please see the main documentation on the web-site for a full description: 10 | 11 | * https://vertx.io/docs/vertx-reactive-streams/java/[Web-site documentation] 12 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 4.0.0 19 | 20 | 21 | io.vertx 22 | vertx5-parent 23 | 12 24 | 25 | 26 | vertx-reactive-streams 27 | 5.1.0-SNAPSHOT 28 | 29 | Vert.x - Reactive Streams 30 | 31 | 32 | scm:git:git@github.com:vert-x3/vertx-reactive-streams.git 33 | scm:git:git@github.com:vert-x3/vertx-reactive-streams.git 34 | git@github.com:vert-x3/vertx-reactive-streams.git 35 | 36 | 37 | 38 | 1.0.3 39 | 40 | 41 | 42 | 43 | 44 | io.vertx 45 | vertx-dependencies 46 | ${project.version} 47 | pom 48 | import 49 | 50 | 51 | 52 | 53 | 54 | 55 | io.vertx 56 | vertx-core 57 | 58 | 59 | io.vertx 60 | vertx-codegen-api 61 | true 62 | 63 | 64 | io.vertx 65 | vertx-docgen-api 66 | true 67 | 68 | 69 | org.reactivestreams 70 | reactive-streams 71 | ${reactive.streams.version} 72 | 73 | 74 | org.reactivestreams 75 | reactive-streams-tck 76 | ${reactive.streams.version} 77 | test 78 | 79 | 80 | io.vertx 81 | vertx-core 82 | test-jar 83 | test 84 | 85 | 86 | junit 87 | junit 88 | test 89 | 4.13.1 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | maven-compiler-plugin 98 | 99 | 100 | default-compile 101 | 102 | 103 | 104 | io.vertx 105 | vertx-codegen 106 | processor 107 | 108 | 109 | io.vertx 110 | vertx-docgen-processor 111 | processor 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | org.apache.maven.plugins 123 | maven-surefire-plugin 124 | 125 | 126 | default-test 127 | 128 | test 129 | 130 | 131 | 132 | **/tck/* 133 | 134 | none:none 135 | 136 | 137 | 138 | tck 139 | 140 | test 141 | 142 | 143 | 144 | **/test/* 145 | **/tck/SubscriberWhitebox* 146 | 147 | none:none 148 | 149 | 150 | 151 | 152 | 153 | maven-assembly-plugin 154 | 155 | 156 | package-docs 157 | 158 | single 159 | 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /src/main/asciidoc/index.adoc: -------------------------------------------------------------------------------- 1 | = Vert.x Reactive Streams Integration 2 | 3 | link:http://www.reactive-streams.org/[Reactive Streams] is an initiative to provide a standard for asynchronous stream 4 | processing with non-blocking back pressure on the JVM. 5 | 6 | This library provides an implementation of reactive streams for Vert.x. 7 | 8 | Vert.x provides its own mechanisms for handling streams of data and pumping them with back pressure from one to another 9 | using the `io.vertx.core.streams.ReadStream`, `io.vertx.core.streams.WriteStream` and `io.vertx.core.streams.Pump`. 10 | Please see the Vert.x core manual for more information on Vert.x streams. 11 | 12 | This library provides implementations of read stream and write stream which also act as reactive streams publishers 13 | and subscribers. This allows us to treat any reactive streams publisher or subscriber and deal with it like any other 14 | Vert.x read or write stream. 15 | 16 | == Using Vert.x Reactive Streams 17 | 18 | To use Vert.x Reactive Streams, add the following dependency to the _dependencies_ section of your build descriptor: 19 | 20 | * Maven (in your `pom.xml`): 21 | 22 | [source,xml,subs="+attributes"] 23 | ---- 24 | 25 | io.vertx 26 | vertx-reactive-streams 27 | ${maven.version} 28 | 29 | ---- 30 | 31 | * Gradle (in your `build.gradle` file): 32 | 33 | [source,groovy,subs="+attributes"] 34 | ---- 35 | compile io.vertx:vertx-reactive-streams:${maven.version} 36 | ---- 37 | 38 | == Reactive Read Stream 39 | 40 | We provide an implementation of the Vert.x `ReadStream` interface with {@link io.vertx.ext.reactivestreams.ReactiveReadStream} 41 | which also implements a reactive streams `Subscriber`. 42 | 43 | You can pass an instance of this to any reactive streams `Publisher` (e.g. a Publisher from Akka) and then you will be 44 | able to read from that just like any other Vert.x `ReadStream` (e.g. use a `Pump` to pump it to a `WriteStream`. 45 | 46 | Here's an example of taking a publisher from some other reactive streams implementation (e.g. Akka) and pumping that 47 | stream to the body of a server side HTTP response. This will handle back pressure automatically. 48 | 49 | [source,java] 50 | ---- 51 | {@link examples.ReactiveStreamsExamples#example1} 52 | ---- 53 | 54 | == Reactive Write Stream 55 | 56 | We also provide an implementation of the Vert.x `WriteStream` interface with {@link io.vertx.ext.reactivestreams.ReactiveWriteStream} 57 | which also implements a reactive streams `Publisher`. You can take any reactive streams `Subscriber` 58 | (e.g. a Subscriber from Akka) and then you will be able* to write to it like any other Vert.x `WriteStream`. 59 | (e.g. use a `Pump` to pump it from a `ReadStream`). 60 | 61 | You use `pause`, `resume`, and `writeQueueFull`, as you would with any Vert.x read stream to handle your back pressure. 62 | This is automatically translated internally into the reactive streams method of propagating back pressure 63 | (requesting more items). 64 | 65 | Here's an example of taking a subscriber from some other reactive streams implementation and pumping the body of 66 | a server side HTTP request to that subscriber. This will handle back pressure automatically. 67 | 68 | [source,java] 69 | ---- 70 | {@link examples.ReactiveStreamsExamples#example2} 71 | ---- 72 | -------------------------------------------------------------------------------- /src/main/java/examples/ReactiveStreamsExamples.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Red Hat, Inc. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | package examples; 18 | 19 | import io.vertx.core.Vertx; 20 | import io.vertx.core.buffer.Buffer; 21 | import io.vertx.core.http.HttpServerRequest; 22 | import io.vertx.core.http.HttpServerResponse; 23 | import io.vertx.docgen.Source; 24 | import io.vertx.ext.reactivestreams.ReactiveReadStream; 25 | import io.vertx.ext.reactivestreams.ReactiveWriteStream; 26 | import org.reactivestreams.Publisher; 27 | import org.reactivestreams.Subscriber; 28 | 29 | /** 30 | * @author Tim Fox 31 | */ 32 | @Source(translate = false) 33 | public class ReactiveStreamsExamples { 34 | 35 | public void example1(HttpServerResponse response, Publisher otherPublisher) { 36 | 37 | ReactiveReadStream rrs = ReactiveReadStream.readStream(); 38 | 39 | // Subscribe the read stream to the publisher 40 | otherPublisher.subscribe(rrs); 41 | 42 | // Pipe from the read stream to the http response 43 | rrs.pipeTo(response); 44 | 45 | } 46 | 47 | public void example2(Vertx vertx, HttpServerRequest request, Subscriber otherSubscriber) { 48 | 49 | ReactiveWriteStream rws = ReactiveWriteStream.writeStream(vertx); 50 | 51 | // Subscribe the other subscriber to the write stream 52 | rws.subscribe(otherSubscriber); 53 | 54 | // Pipe the http request to the write stream 55 | request.pipeTo(rws); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/ext/reactivestreams/ReactiveReadStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Red Hat, Inc. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | package io.vertx.ext.reactivestreams; 18 | 19 | import io.vertx.core.Handler; 20 | import io.vertx.core.streams.ReadStream; 21 | import io.vertx.ext.reactivestreams.impl.ReactiveReadStreamImpl; 22 | import org.reactivestreams.Subscriber; 23 | 24 | /** 25 | * A Vert.x read stream that also implements reactive streams subscriber interface. 26 | * 27 | * @author Tim Fox 28 | */ 29 | public interface ReactiveReadStream extends ReadStream, Subscriber { 30 | 31 | /** 32 | * Default batch size 33 | */ 34 | static final long DEFAULT_BATCH_SIZE = 4L; 35 | 36 | /** 37 | * Create a reactive read stream 38 | * 39 | * @return the stream 40 | */ 41 | static ReactiveReadStream readStream() { 42 | return readStream(DEFAULT_BATCH_SIZE); 43 | } 44 | 45 | /** 46 | * Create a reactive read stream specifying batch size 47 | * 48 | * @param batchSize the batch size 49 | * @return the stream 50 | */ 51 | static ReactiveReadStream readStream(long batchSize) { 52 | return new ReactiveReadStreamImpl<>(batchSize); 53 | } 54 | 55 | @Override 56 | ReactiveReadStream exceptionHandler(Handler handler); 57 | 58 | @Override 59 | ReactiveReadStream handler(Handler handler); 60 | 61 | @Override 62 | ReactiveReadStream pause(); 63 | 64 | @Override 65 | ReactiveReadStream resume(); 66 | 67 | @Override 68 | ReactiveReadStream endHandler(Handler endHandler); 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/ext/reactivestreams/ReactiveWriteStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Red Hat, Inc. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | package io.vertx.ext.reactivestreams; 18 | 19 | import io.vertx.core.AsyncResult; 20 | import io.vertx.core.Future; 21 | import io.vertx.core.Handler; 22 | import io.vertx.core.Vertx; 23 | import io.vertx.core.streams.WriteStream; 24 | import io.vertx.ext.reactivestreams.impl.ReactiveWriteStreamImpl; 25 | import org.reactivestreams.Publisher; 26 | 27 | /** 28 | * A Vert.x write stream that also implements reactive streams publisher interface. 29 | * 30 | * 31 | * @author Tim Fox 32 | */ 33 | public interface ReactiveWriteStream extends WriteStream, Publisher { 34 | 35 | /** 36 | * Default write queue max size 37 | */ 38 | static final int DEFAULT_WRITE_QUEUE_MAX_SIZE = 32; 39 | 40 | /** 41 | * Create a reactive write stream 42 | * 43 | * @param vertx the Vert.x instance 44 | * @return the stream 45 | */ 46 | static ReactiveWriteStream writeStream(Vertx vertx) { 47 | return new ReactiveWriteStreamImpl<>(vertx); 48 | } 49 | 50 | @Override 51 | ReactiveWriteStream exceptionHandler(Handler handler); 52 | 53 | @Override 54 | Future write(T data); 55 | 56 | @Override 57 | ReactiveWriteStream setWriteQueueMaxSize(int maxSize); 58 | 59 | @Override 60 | ReactiveWriteStream drainHandler(Handler handler); 61 | 62 | /** 63 | * Close the stream 64 | * 65 | * @return a reference to this for a fluent API 66 | */ 67 | ReactiveWriteStream close(); 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/ext/reactivestreams/impl/ReactiveReadStreamImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Red Hat, Inc. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | package io.vertx.ext.reactivestreams.impl; 18 | 19 | import io.vertx.core.Handler; 20 | import io.vertx.ext.reactivestreams.ReactiveReadStream; 21 | import org.reactivestreams.Subscription; 22 | 23 | import java.util.ArrayDeque; 24 | import java.util.Queue; 25 | 26 | /** 27 | * @author Nick Scavelli 28 | * @author Tim Fox 29 | */ 30 | public class ReactiveReadStreamImpl implements ReactiveReadStream { 31 | 32 | private final long batchSize; 33 | private Handler dataHandler; 34 | private Handler endHandler; 35 | private Handler exceptionHandler; 36 | 37 | private Subscription subscription; 38 | private final Queue pending = new ArrayDeque<>(); 39 | private long demand = Long.MAX_VALUE; 40 | private long tokens; 41 | 42 | public ReactiveReadStreamImpl(long batchSize) { 43 | this.batchSize = batchSize; 44 | } 45 | 46 | public synchronized ReactiveReadStream handler(Handler handler) { 47 | this.dataHandler = handler; 48 | if (dataHandler != null && demand > 0L) { 49 | checkRequestTokens(); 50 | } 51 | return this; 52 | } 53 | 54 | @Override 55 | public synchronized ReactiveReadStream pause() { 56 | this.demand = 0L; 57 | return this; 58 | } 59 | 60 | @Override 61 | public ReactiveReadStream fetch(long amount) { 62 | if (amount > 0L) { 63 | demand += amount; 64 | if (demand < 0L) { 65 | demand = Long.MAX_VALUE; 66 | } 67 | T data; 68 | while (demand > 0L && (data = pending.poll()) != null) { 69 | if (demand != Long.MAX_VALUE) { 70 | demand--; 71 | } 72 | handleData(data); 73 | } 74 | checkRequestTokens(); 75 | } 76 | return this; 77 | } 78 | 79 | @Override 80 | public synchronized ReactiveReadStream resume() { 81 | return fetch(Long.MAX_VALUE); 82 | } 83 | 84 | @Override 85 | public synchronized ReactiveReadStream endHandler(Handler endHandler) { 86 | this.endHandler = endHandler; 87 | return this; 88 | } 89 | 90 | @Override 91 | public synchronized ReactiveReadStream exceptionHandler(Handler handler) { 92 | this.exceptionHandler = handler; 93 | return this; 94 | } 95 | 96 | @Override 97 | public synchronized void onSubscribe(Subscription subscription) { 98 | if (subscription == null) { 99 | throw new NullPointerException("subscription"); 100 | } 101 | if (this.subscription != null) { 102 | subscription.cancel(); 103 | } else { 104 | this.subscription = subscription; 105 | } 106 | } 107 | 108 | @Override 109 | public synchronized void onNext(T data) { 110 | if (data == null) { 111 | throw new NullPointerException("data"); 112 | } 113 | checkUnsolicitedTokens(); 114 | if (demand > 0L) { 115 | if (demand != Long.MAX_VALUE) { 116 | demand--; 117 | } 118 | if (pending.size() > 0) { 119 | pending.add(data); 120 | data = pending.poll(); 121 | } 122 | handleData(data); 123 | } else { 124 | pending.add(data); 125 | } 126 | } 127 | 128 | @Override 129 | public synchronized void onError(Throwable throwable) { 130 | if (throwable == null) { 131 | throw new NullPointerException("throwable"); 132 | } 133 | if (exceptionHandler != null) { 134 | exceptionHandler.handle(throwable); 135 | } 136 | } 137 | 138 | @Override 139 | public synchronized void onComplete() { 140 | if (endHandler != null) { 141 | endHandler.handle(null); 142 | } 143 | } 144 | 145 | protected void checkUnsolicitedTokens() { 146 | if (tokens == 0) { 147 | throw new IllegalStateException("Data received but wasn't requested"); 148 | } 149 | } 150 | 151 | private synchronized void handleData(T data) { 152 | if (dataHandler != null) { 153 | dataHandler.handle(data); 154 | tokens--; 155 | checkRequestTokens(); 156 | } 157 | } 158 | 159 | private void checkRequestTokens() { 160 | if (demand > 0L && subscription != null && tokens == 0) { 161 | tokens = batchSize; 162 | subscription.request(batchSize); 163 | } 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/ext/reactivestreams/impl/ReactiveWriteStreamImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Red Hat, Inc. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | package io.vertx.ext.reactivestreams.impl; 18 | 19 | import io.vertx.core.*; 20 | import io.vertx.core.internal.ContextInternal; 21 | import io.vertx.core.internal.net.NetSocketInternal; 22 | import io.vertx.ext.reactivestreams.ReactiveWriteStream; 23 | import org.reactivestreams.Subscriber; 24 | import org.reactivestreams.Subscription; 25 | 26 | import java.util.ArrayDeque; 27 | import java.util.Objects; 28 | import java.util.Queue; 29 | import java.util.Set; 30 | import java.util.concurrent.ConcurrentHashMap; 31 | import java.util.concurrent.atomic.AtomicLong; 32 | 33 | /** 34 | * @author Tim Fox 35 | */ 36 | public class ReactiveWriteStreamImpl implements ReactiveWriteStream { 37 | 38 | private final Set subscriptions = ConcurrentHashMap.newKeySet(); 39 | private final Queue> pending = new ArrayDeque<>(); 40 | private Handler drainHandler; 41 | private int writeQueueMaxSize = DEFAULT_WRITE_QUEUE_MAX_SIZE; 42 | protected final ContextInternal ctx; 43 | private boolean closed; 44 | 45 | public ReactiveWriteStreamImpl(Vertx vertx) { 46 | ctx = (ContextInternal) vertx.getOrCreateContext(); 47 | } 48 | 49 | private void checkClosed() { 50 | if (closed) { 51 | throw new IllegalStateException("Closed"); 52 | } 53 | } 54 | 55 | @Override 56 | public synchronized void subscribe(Subscriber subscriber) { 57 | checkClosed(); 58 | Objects.requireNonNull(subscriber); 59 | 60 | SubscriptionImpl sub = new SubscriptionImpl(subscriber); 61 | if (subscriptions.add(sub)) { 62 | ctx.runOnContext(v -> { 63 | try { 64 | subscriber.onSubscribe(sub); 65 | } catch (Throwable t) { 66 | signalError(sub.subscriber, t); 67 | } 68 | }); 69 | } else { 70 | throw new IllegalStateException("1.10 Cannot subscribe multiple times with the same subscriber."); 71 | } 72 | } 73 | 74 | @Override 75 | public synchronized Future write(T data) { 76 | checkClosed(); 77 | Promise promise = ctx.promise(); 78 | pending.add(new Item<>(data, promise)); 79 | checkSend(); 80 | return promise.future(); 81 | } 82 | 83 | @Override 84 | public synchronized ReactiveWriteStream setWriteQueueMaxSize(int maxSize) { 85 | checkClosed(); 86 | if (writeQueueMaxSize < 1) { 87 | throw new IllegalArgumentException("writeQueueMaxSize must be >=1"); 88 | } 89 | this.writeQueueMaxSize = maxSize; 90 | return this; 91 | } 92 | 93 | @Override 94 | public synchronized boolean writeQueueFull() { 95 | checkClosed(); 96 | return pending.size() >= writeQueueMaxSize; 97 | } 98 | 99 | @Override 100 | public synchronized ReactiveWriteStream drainHandler(Handler handler) { 101 | checkClosed(); 102 | this.drainHandler = handler; 103 | return this; 104 | } 105 | 106 | @Override 107 | public synchronized ReactiveWriteStream exceptionHandler(Handler handler) { 108 | return this; 109 | } 110 | 111 | @Override 112 | public Future end() { 113 | close(); 114 | return ctx.succeededFuture(); 115 | } 116 | 117 | @Override 118 | public ReactiveWriteStream close() { 119 | synchronized (this) { 120 | if (closed) { 121 | return this; 122 | } 123 | closed = true; 124 | complete(); 125 | subscriptions.clear(); 126 | Future closedFut = Future.failedFuture(NetSocketInternal.CLOSED_EXCEPTION); 127 | for (Item item: pending) { 128 | Completable handler = item.handler; 129 | if (handler != null) { 130 | ctx.runOnContext(v -> { 131 | if (closedFut.succeeded()) { 132 | handler.succeed(closedFut.result()); 133 | } else { 134 | handler.fail(closedFut.cause()); 135 | } 136 | }); 137 | } 138 | } 139 | pending.clear(); 140 | } 141 | return this; 142 | } 143 | 144 | private synchronized void checkSend() { 145 | if (!subscriptions.isEmpty()) { 146 | long availableTokens = getAvailable(); 147 | long toSend = Math.min(availableTokens, pending.size()); 148 | takeTokens(toSend); 149 | for (long i = 0; i < toSend; i++) { 150 | sendToSubscribers(pending.poll()); 151 | } 152 | if (drainHandler != null && pending.size() < writeQueueMaxSize) { 153 | callDrainHandler(); 154 | } 155 | } 156 | } 157 | 158 | private void callDrainHandler() { 159 | Handler dh = drainHandler; 160 | ctx.runOnContext(v -> dh.handle(null)); 161 | } 162 | 163 | private long getAvailable() { 164 | long min = Long.MAX_VALUE; 165 | for (SubscriptionImpl subscription: subscriptions) { 166 | min = Math.min(subscription.tokens(), min); 167 | } 168 | return min; 169 | } 170 | 171 | private void takeTokens(long toSend) { 172 | for (SubscriptionImpl subscription: subscriptions) { 173 | subscription.takeTokens(toSend); 174 | } 175 | } 176 | 177 | private void complete() { 178 | for (SubscriptionImpl sub: subscriptions) { 179 | ctx.runOnContext(v -> sub.subscriber.onComplete()); 180 | } 181 | } 182 | 183 | private void sendToSubscribers(Item item) { 184 | for (SubscriptionImpl sub: subscriptions) { 185 | onNext(ctx, sub.subscriber, item.value); 186 | } 187 | if (item.handler != null) { 188 | item.handler.succeed(); 189 | } 190 | } 191 | 192 | protected void onNext(Context context, Subscriber subscriber, T data) { 193 | context.runOnContext(v -> { 194 | try { 195 | subscriber.onNext(data); 196 | } catch (Throwable t) { 197 | signalError(subscriber, t); 198 | } 199 | }); 200 | } 201 | 202 | public class SubscriptionImpl implements Subscription { 203 | 204 | private final Subscriber subscriber; 205 | // We start at Long.MIN_VALUE so we know when we've requested more then Long.MAX_VALUE. See 3.17 of spec 206 | private final AtomicLong tokens = new AtomicLong(Long.MIN_VALUE); 207 | 208 | private SubscriptionImpl(Subscriber subscriber) { 209 | this.subscriber = subscriber; 210 | } 211 | 212 | public long tokens() { 213 | return -(Long.MIN_VALUE - tokens.get()); 214 | } 215 | 216 | public void takeTokens(long amount) { 217 | tokens.addAndGet(-amount); 218 | } 219 | 220 | @Override 221 | public void request(long n) { 222 | if (n > 0) { 223 | // More then Long.MAX_VALUE pending 224 | if (tokens.addAndGet(n) > 0) { 225 | signalError(subscriber, new IllegalStateException("3.17 Subscriber has more then Long.MAX_VALUE (2^63-1) currently pending.")); 226 | } else { 227 | checkSend(); 228 | } 229 | } else { 230 | signalError(subscriber, new IllegalArgumentException("3.9 Subscriber cannot request less then 1 for the number of elements.")); 231 | } 232 | } 233 | 234 | @Override 235 | public void cancel() { 236 | subscriptions.remove(this); 237 | } 238 | 239 | @Override 240 | public boolean equals(Object o) { 241 | if (this == o) return true; 242 | if (o == null || getClass() != o.getClass()) return false; 243 | 244 | @SuppressWarnings("unchecked") 245 | SubscriptionImpl that = (SubscriptionImpl) o; 246 | 247 | return subscriber == that.subscriber; 248 | } 249 | 250 | @Override 251 | public int hashCode() { 252 | return subscriber.hashCode(); 253 | } 254 | } 255 | 256 | private void signalError(Subscriber subscriber, Throwable error) { 257 | subscriptions.removeIf(sub -> sub.subscriber == subscriber); 258 | subscriber.onError(error); 259 | } 260 | 261 | static class Item { 262 | final T value; 263 | final Completable handler; 264 | Item(T value, Completable handler) { 265 | this.value = value; 266 | this.handler = handler; 267 | } 268 | } 269 | 270 | } 271 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/ext/reactivestreams/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * = Vert.x Reactive Streams Integration 3 | * 4 | * link:http://www.reactive-streams.org/[Reactive Streams] is an initiative to provide a standard for asynchronous stream 5 | * processing with non-blocking back pressure on the JVM. 6 | * 7 | * This library provides an implementation of reactive streams for Vert.x. 8 | * 9 | * Vert.x provides its own mechanisms for handling streams of data and pumping them with back pressure from one to another 10 | * using the `io.vertx.core.streams.ReadStream`, `io.vertx.core.streams.WriteStream` and `io.vertx.core.streams.Pump`. 11 | * Please see the Vert.x core manual for more information on Vert.x streams. 12 | * 13 | * This library provides implementations of read stream and write stream which also act as reactive streams publishers 14 | * and subscribers. This allows us to treat any reactive streams publisher or subscriber and deal with it like any other 15 | * Vert.x read or write stream. 16 | * 17 | * == Using Vert.x Reactive Streams 18 | * 19 | * To use Vert.x Reactive Streams, add the following dependency to the _dependencies_ section of your build descriptor: 20 | * 21 | * * Maven (in your `pom.xml`): 22 | * 23 | * [source,xml,subs="+attributes"] 24 | * ---- 25 | * 26 | * ${maven.groupId} 27 | * ${maven.artifactId} 28 | * ${maven.version} 29 | * 30 | * ---- 31 | * 32 | * * Gradle (in your `build.gradle` file): 33 | * 34 | * [source,groovy,subs="+attributes"] 35 | * ---- 36 | * compile ${maven.groupId}:${maven.artifactId}:${maven.version} 37 | * ---- 38 | * 39 | * == Reactive Read Stream 40 | * 41 | * We provide an implementation of the Vert.x `ReadStream` interface with {@link io.vertx.ext.reactivestreams.ReactiveReadStream} 42 | * which also implements a reactive streams `Subscriber`. 43 | * 44 | * You can pass an instance of this to any reactive streams `Publisher` (e.g. a Publisher from Akka) and then you will be 45 | * able to read from that just like any other Vert.x `ReadStream` (e.g. use a `Pump` to pump it to a `WriteStream`. 46 | * 47 | * Here's an example of taking a publisher from some other reactive streams implementation (e.g. Akka) and pumping that 48 | * stream to the body of a server side HTTP response. This will handle back pressure automatically. 49 | * 50 | * [source,java] 51 | * ---- 52 | * {@link examples.ReactiveStreamsExamples#example1} 53 | * ---- 54 | * 55 | * == Reactive Write Stream 56 | * 57 | * We also provide an implementation of the Vert.x `WriteStream` interface with {@link io.vertx.ext.reactivestreams.ReactiveWriteStream} 58 | * which also implements a reactive streams `Publisher`. You can take any reactive streams `Subscriber` 59 | * (e.g. a Subscriber from Akka) and then you will be able* to write to it like any other Vert.x `WriteStream`. 60 | * (e.g. use a `Pump` to pump it from a `ReadStream`). 61 | * 62 | * You use `pause`, `resume`, and `writeQueueFull`, as you would with any Vert.x read stream to handle your back pressure. 63 | * This is automatically translated internally into the reactive streams method of propagating back pressure 64 | * (requesting more items). 65 | * 66 | * Here's an example of taking a subscriber from some other reactive streams implementation and pumping the body of 67 | * a server side HTTP request to that subscriber. This will handle back pressure automatically. 68 | * 69 | * [source,java] 70 | * ---- 71 | * {@link examples.ReactiveStreamsExamples#example2} 72 | * ---- 73 | * 74 | */ 75 | @Document(fileName = "index.adoc") 76 | package io.vertx.ext.reactivestreams; 77 | 78 | import io.vertx.docgen.Document; 79 | -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | open module io.vertx.reactivestreams { 2 | 3 | requires static io.vertx.docgen; 4 | 5 | requires io.vertx.core; 6 | requires org.reactivestreams; 7 | 8 | exports io.vertx.ext.reactivestreams; 9 | exports io.vertx.ext.reactivestreams.impl to io.vertx.reactivestreams.tests; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/ext/reactivestreams/tck/FiniteReactiveWriteStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2014 The original author or authors 3 | * ------------------------------------------------------ 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | package io.vertx.ext.reactivestreams.tck; 18 | 19 | import io.vertx.core.Context; 20 | import io.vertx.core.Vertx; 21 | import io.vertx.ext.reactivestreams.impl.ReactiveWriteStreamImpl; 22 | import org.reactivestreams.Subscriber; 23 | 24 | import java.util.Map; 25 | import java.util.WeakHashMap; 26 | import java.util.concurrent.atomic.AtomicLong; 27 | 28 | /** 29 | * @author Nick Scavelli 30 | */ 31 | public class FiniteReactiveWriteStream extends ReactiveWriteStreamImpl { 32 | 33 | private Map, AtomicLong> subs = new WeakHashMap<>(); 34 | private final long elements; 35 | 36 | public FiniteReactiveWriteStream(Vertx vertx, long elements) { 37 | super(vertx); 38 | this.elements = elements; 39 | } 40 | 41 | @Override 42 | public void subscribe(Subscriber subscriber) { 43 | subs.put(subscriber, new AtomicLong(elements)); 44 | super.subscribe(subscriber); 45 | } 46 | 47 | @Override 48 | protected void onNext(Context context, Subscriber subscriber, T data) { 49 | AtomicLong count = subs.get(subscriber); 50 | if (count == null) return; // Means we already completed it 51 | 52 | long remaining = count.decrementAndGet(); 53 | super.onNext(context, subscriber, data); 54 | if (remaining == 0) { 55 | close(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/ext/reactivestreams/tck/PublisherVerificationTest.java: -------------------------------------------------------------------------------- 1 | package io.vertx.ext.reactivestreams.tck; 2 | 3 | import io.vertx.core.Context; 4 | import io.vertx.core.Vertx; 5 | import io.vertx.core.buffer.Buffer; 6 | import io.vertx.ext.reactivestreams.ReactiveWriteStream; 7 | import io.vertx.ext.reactivestreams.impl.ReactiveWriteStreamImpl; 8 | import io.vertx.test.core.TestUtils; 9 | import org.reactivestreams.Publisher; 10 | import org.reactivestreams.Subscriber; 11 | import org.reactivestreams.tck.PublisherVerification; 12 | import org.reactivestreams.tck.TestEnvironment; 13 | 14 | 15 | /** 16 | * @author Nick Scavelli 17 | */ 18 | public class PublisherVerificationTest extends PublisherVerification { 19 | 20 | private static final long DEFAULT_TIMEOUT = 300L; 21 | private static final long DEFAULT_GC_TIMEOUT = 1000L; 22 | 23 | private Vertx vertx; 24 | 25 | public PublisherVerificationTest() { 26 | super(new TestEnvironment(DEFAULT_TIMEOUT), DEFAULT_GC_TIMEOUT); 27 | this.vertx = Vertx.vertx(); 28 | } 29 | 30 | @Override 31 | public Publisher createPublisher(long elements) { 32 | ReactiveWriteStream rws; 33 | rws = new FiniteReactiveWriteStream<>(vertx, elements); 34 | if (elements < Integer.MAX_VALUE) { 35 | for (long i = 0; i < elements; i++) { 36 | rws.write(TestUtils.randomBuffer(10)); 37 | } 38 | } 39 | return rws; 40 | } 41 | 42 | @Override 43 | public Publisher createFailedPublisher() { 44 | 45 | return new ReactiveWriteStreamImpl(vertx) { 46 | @Override 47 | public void subscribe(Subscriber subscriber) { 48 | super.subscribe(subscriber); 49 | ctx.runOnContext(v -> { 50 | // Now signal an error 51 | subscriber.onError(new RuntimeException("Can't subscribe subscriber: " + subscriber + ", because of reasons.")); 52 | }); 53 | } 54 | }; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/ext/reactivestreams/tck/SubscriberBlackboxVerificationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Red Hat, Inc. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | package io.vertx.ext.reactivestreams.tck; 18 | 19 | import io.vertx.core.buffer.Buffer; 20 | import io.vertx.ext.reactivestreams.ReactiveReadStream; 21 | import org.reactivestreams.Subscriber; 22 | import org.reactivestreams.tck.SubscriberBlackboxVerification; 23 | import org.reactivestreams.tck.TestEnvironment; 24 | 25 | /* 26 | * 27 | * @author Tim Fox 28 | */ 29 | public class SubscriberBlackboxVerificationTest extends SubscriberBlackboxVerification { 30 | 31 | public SubscriberBlackboxVerificationTest() { 32 | super(new TestEnvironment()); 33 | } 34 | 35 | @Override 36 | public Buffer createElement(int i) { 37 | return Buffer.buffer("element" + i); 38 | } 39 | 40 | @Override 41 | public Subscriber createSubscriber() { 42 | return ReactiveReadStream.readStream(); 43 | } 44 | 45 | @Override 46 | public void triggerRequest(Subscriber subscriber) { 47 | System.out.println("Calling triggerRequest on: " + subscriber); 48 | ReactiveReadStream rrs = (ReactiveReadStream)subscriber; 49 | rrs.handler(b -> {}); 50 | } 51 | 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/ext/reactivestreams/tck/SubscriberWhiteboxVerificationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Red Hat, Inc. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | package io.vertx.ext.reactivestreams.tck; 18 | 19 | import io.vertx.core.buffer.Buffer; 20 | import io.vertx.ext.reactivestreams.ReactiveReadStream; 21 | import io.vertx.ext.reactivestreams.impl.ReactiveReadStreamImpl; 22 | import org.junit.Ignore; 23 | import org.reactivestreams.Subscriber; 24 | import org.reactivestreams.Subscription; 25 | import org.reactivestreams.tck.SubscriberWhiteboxVerification; 26 | import org.reactivestreams.tck.TestEnvironment; 27 | 28 | /* 29 | * 30 | * @author Tim Fox 31 | */ 32 | @Ignore 33 | public class SubscriberWhiteboxVerificationTest extends SubscriberWhiteboxVerification { 34 | 35 | 36 | public SubscriberWhiteboxVerificationTest() { 37 | super(new TestEnvironment()); 38 | } 39 | 40 | @Override 41 | public Subscriber createSubscriber(WhiteboxSubscriberProbe probe) { 42 | 43 | // return YOUR subscriber under-test, with additional WhiteboxSubscriberProbe instrumentation 44 | return new ReactiveReadStreamImpl(ReactiveReadStream.DEFAULT_BATCH_SIZE) { 45 | 46 | @Override 47 | public void onSubscribe(final Subscription s) { 48 | 49 | super.onSubscribe(s); 50 | 51 | // in addition to normal Subscriber work that you're testing, 52 | // register a SubscriberPuppet, to give the TCK control over demand generation and cancelling 53 | probe.registerOnSubscribe(new SubscriberPuppet() { 54 | 55 | @Override 56 | public void triggerRequest(long n) { 57 | s.request(n); 58 | } 59 | 60 | @Override 61 | public void signalCancel() { 62 | s.cancel(); 63 | } 64 | }); 65 | 66 | 67 | } 68 | 69 | @Override 70 | public void onNext(Buffer value) { 71 | 72 | // in addition to normal Subscriber work that you're testing, register onNext with the probe 73 | probe.registerOnNext(value); 74 | super.onNext(value); 75 | } 76 | 77 | @Override 78 | public void onError(Throwable cause) { 79 | 80 | // in addition to normal Subscriber work that you're testing, register onError with the probe 81 | probe.registerOnError(cause); 82 | super.onError(cause); 83 | } 84 | 85 | @Override 86 | public void onComplete() { 87 | 88 | // in addition to normal Subscriber work that you're testing, register onComplete with the probe 89 | probe.registerOnComplete(); 90 | super.onComplete(); 91 | } 92 | 93 | // Need to override this to drop the tokens check or the test won't pass 94 | @Override 95 | protected void checkUnsolicitedTokens() { 96 | // NO OP 97 | } 98 | }; 99 | } 100 | 101 | @Override 102 | public Buffer createElement(int element) { 103 | return Buffer.buffer("element" + element); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/ext/reactivestreams/test/ReactiveReadStreamTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Red Hat, Inc. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | package io.vertx.ext.reactivestreams.test; 18 | 19 | import io.vertx.core.VertxException; 20 | import io.vertx.core.buffer.Buffer; 21 | import io.vertx.ext.reactivestreams.ReactiveReadStream; 22 | import org.junit.Test; 23 | import org.reactivestreams.Publisher; 24 | import org.reactivestreams.Subscriber; 25 | import org.reactivestreams.Subscription; 26 | 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | 30 | /** 31 | * @author Tim Fox 32 | */ 33 | public class ReactiveReadStreamTest extends ReactiveStreamTestBase { 34 | 35 | 36 | @Test 37 | public void testSubscribe() throws Exception { 38 | ReactiveReadStream rws = ReactiveReadStream.readStream(); 39 | MyPublisher publisher = new MyPublisher(); 40 | publisher.subscribe(rws); 41 | assertNotNull(publisher.subscription); 42 | assertEquals(0, publisher.subscription.requestedTimes); 43 | } 44 | 45 | @Test 46 | public void testDatahandler() throws Exception { 47 | ReactiveReadStream rws = ReactiveReadStream.readStream(); 48 | MyPublisher publisher = new MyPublisher(); 49 | publisher.subscribe(rws); 50 | assertNotNull(publisher.subscription); 51 | assertEquals(0, publisher.subscription.requestedTimes); 52 | List received = new ArrayList<>(); 53 | rws.handler(received::add); 54 | assertEquals(1, publisher.subscription.requestedTimes); 55 | assertEquals(ReactiveReadStream.DEFAULT_BATCH_SIZE, publisher.subscription.requested); 56 | int numBuffers = 4; 57 | List buffers = createRandomBuffers(numBuffers); 58 | for (Buffer buffer: buffers) { 59 | publisher.subscriber.onNext(buffer); 60 | } 61 | assertEquals(numBuffers, received.size()); 62 | for (int i = 0; i < numBuffers; i++) { 63 | assertEquals(buffers.get(i), received.get(i)); 64 | } 65 | assertEquals(2, publisher.subscription.requestedTimes); 66 | assertEquals(ReactiveReadStream.DEFAULT_BATCH_SIZE * 2, publisher.subscription.requested); 67 | } 68 | 69 | @Test 70 | public void testSetPausedDataHandler() throws Exception { 71 | ReactiveReadStream rws = ReactiveReadStream.readStream(); 72 | MyPublisher publisher = new MyPublisher(); 73 | publisher.subscribe(rws); 74 | assertNotNull(publisher.subscription); 75 | assertEquals(0, publisher.subscription.requestedTimes); 76 | List received = new ArrayList<>(); 77 | rws.pause(); 78 | rws.handler(received::add); 79 | assertEquals(0, publisher.subscription.requestedTimes); 80 | assertEquals(0, publisher.subscription.requested); 81 | 82 | assertEquals(0, received.size()); 83 | assertEquals(0, publisher.subscription.requestedTimes); 84 | assertEquals(0, publisher.subscription.requested); 85 | rws.resume(); 86 | assertEquals(1, publisher.subscription.requestedTimes); 87 | assertEquals(ReactiveReadStream.DEFAULT_BATCH_SIZE, publisher.subscription.requested); 88 | int numBuffers = 4; 89 | List buffers = createRandomBuffers(numBuffers); 90 | for (Buffer buffer: buffers) { 91 | publisher.subscriber.onNext(buffer); 92 | } 93 | assertEquals(numBuffers, received.size()); 94 | for (int i = 0; i < numBuffers; i++) { 95 | assertEquals(buffers.get(i), received.get(i)); 96 | } 97 | } 98 | 99 | @Test 100 | public void testPauseInHandler() throws Exception { 101 | ReactiveReadStream rws = ReactiveReadStream.readStream(); 102 | MyPublisher publisher = new MyPublisher(); 103 | publisher.subscribe(rws); 104 | assertNotNull(publisher.subscription); 105 | assertEquals(0, publisher.subscription.requestedTimes); 106 | List received = new ArrayList<>(); 107 | rws.handler(buff -> { 108 | received.add(buff); 109 | rws.pause(); 110 | }); 111 | assertEquals(1, publisher.subscription.requestedTimes); 112 | assertEquals(ReactiveReadStream.DEFAULT_BATCH_SIZE, publisher.subscription.requested); 113 | int numBuffers = 4; 114 | List buffers = createRandomBuffers(numBuffers); 115 | for (Buffer buffer: buffers) { 116 | publisher.subscriber.onNext(buffer); 117 | } 118 | assertEquals(1, received.size()); 119 | assertEquals(buffers.get(0), received.get(0)); 120 | 121 | assertEquals(1, publisher.subscription.requestedTimes); 122 | assertEquals(ReactiveReadStream.DEFAULT_BATCH_SIZE, publisher.subscription.requested); 123 | } 124 | 125 | @Test 126 | public void testPauseResume() throws Exception { 127 | ReactiveReadStream rws = ReactiveReadStream.readStream(); 128 | MyPublisher publisher = new MyPublisher(); 129 | publisher.subscribe(rws); 130 | assertNotNull(publisher.subscription); 131 | assertEquals(0, publisher.subscription.requestedTimes); 132 | List received = new ArrayList<>(); 133 | rws.handler(received::add); 134 | assertEquals(1, publisher.subscription.requestedTimes); 135 | assertEquals(ReactiveReadStream.DEFAULT_BATCH_SIZE, publisher.subscription.requested); 136 | int numBuffers = 4; 137 | List buffers = createRandomBuffers(numBuffers); 138 | for (Buffer buffer: buffers) { 139 | publisher.subscriber.onNext(buffer); 140 | } 141 | assertEquals(numBuffers, received.size()); 142 | for (int i = 0; i < numBuffers; i++) { 143 | assertEquals(buffers.get(i), received.get(i)); 144 | } 145 | rws.pause(); 146 | assertEquals(2, publisher.subscription.requestedTimes); 147 | assertEquals(ReactiveReadStream.DEFAULT_BATCH_SIZE * 2, publisher.subscription.requested); 148 | rws.resume(); 149 | assertEquals(2, publisher.subscription.requestedTimes); 150 | assertEquals(ReactiveReadStream.DEFAULT_BATCH_SIZE * 2, publisher.subscription.requested); 151 | buffers.clear(); 152 | received.clear(); 153 | buffers = createRandomBuffers(numBuffers); 154 | for (Buffer buffer: buffers) { 155 | publisher.subscriber.onNext(buffer); 156 | } 157 | assertEquals(numBuffers, received.size()); 158 | for (int i = 0; i < numBuffers; i++) { 159 | assertEquals(buffers.get(i), received.get(i)); 160 | } 161 | } 162 | 163 | @Test 164 | public void testPauseResumeInHandler() throws Exception { 165 | ReactiveReadStream rws = ReactiveReadStream.readStream(); 166 | MyPublisher publisher = new MyPublisher(); 167 | publisher.subscribe(rws); 168 | assertNotNull(publisher.subscription); 169 | assertEquals(0, publisher.subscription.requestedTimes); 170 | List received = new ArrayList<>(); 171 | rws.handler(buff -> { 172 | rws.pause(); 173 | rws.resume(); 174 | received.add(buff); 175 | }); 176 | assertEquals(1, publisher.subscription.requestedTimes); 177 | assertEquals(ReactiveReadStream.DEFAULT_BATCH_SIZE, publisher.subscription.requested); 178 | int numBuffers = 4; 179 | List buffers = createRandomBuffers(numBuffers); 180 | for (Buffer buffer: buffers) { 181 | publisher.subscriber.onNext(buffer); 182 | } 183 | assertEquals(numBuffers, received.size()); 184 | for (int i = 0; i < numBuffers; i++) { 185 | assertEquals(buffers.get(i), received.get(i)); 186 | } 187 | assertEquals(2, publisher.subscription.requestedTimes); 188 | assertEquals(ReactiveReadStream.DEFAULT_BATCH_SIZE * 2, publisher.subscription.requested); 189 | } 190 | 191 | @Test 192 | public void testFetch() throws Exception { 193 | ReactiveReadStream rws = ReactiveReadStream.readStream(); 194 | MyPublisher publisher = new MyPublisher(); 195 | publisher.subscribe(rws); 196 | assertNotNull(publisher.subscription); 197 | assertEquals(0, publisher.subscription.requestedTimes); 198 | List received = new ArrayList<>(); 199 | rws.handler(received::add); 200 | assertEquals(1, publisher.subscription.requestedTimes); 201 | assertEquals(ReactiveReadStream.DEFAULT_BATCH_SIZE, publisher.subscription.requested); 202 | int numBuffers = 4; 203 | List buffers = createRandomBuffers(numBuffers); 204 | for (Buffer buffer: buffers) { 205 | publisher.subscriber.onNext(buffer); 206 | } 207 | assertEquals(numBuffers, received.size()); 208 | for (int i = 0; i < numBuffers; i++) { 209 | assertEquals(buffers.get(i), received.get(i)); 210 | } 211 | rws.pause(); 212 | assertEquals(2, publisher.subscription.requestedTimes); 213 | assertEquals(ReactiveReadStream.DEFAULT_BATCH_SIZE * 2, publisher.subscription.requested); 214 | assertEquals(2, publisher.subscription.requestedTimes); 215 | assertEquals(ReactiveReadStream.DEFAULT_BATCH_SIZE * 2, publisher.subscription.requested); 216 | buffers.clear(); 217 | received.clear(); 218 | buffers = createRandomBuffers(numBuffers); 219 | for (Buffer buffer: buffers) { 220 | publisher.subscriber.onNext(buffer); 221 | } 222 | for (int i = 0;i < numBuffers;i++) { 223 | rws.fetch(1); 224 | assertEquals(1 + i, received.size()); 225 | } 226 | for (int i = 0; i < numBuffers; i++) { 227 | assertEquals(buffers.get(i), received.get(i)); 228 | } 229 | } 230 | 231 | @Test 232 | public void testOnError() throws Exception { 233 | ReactiveReadStream rws = ReactiveReadStream.readStream(); 234 | MyPublisher publisher = new MyPublisher(); 235 | publisher.subscribe(rws); 236 | rws.exceptionHandler(t -> { 237 | assertTrue(t instanceof VertxException); 238 | assertEquals("foo", t.getMessage()); 239 | testComplete(); 240 | }); 241 | publisher.subscriber.onError(new VertxException("foo")); 242 | await(); 243 | } 244 | 245 | @Test 246 | public void testOnComplete() throws Exception { 247 | ReactiveReadStream rws = ReactiveReadStream.readStream(); 248 | MyPublisher publisher = new MyPublisher(); 249 | publisher.subscribe(rws); 250 | rws.endHandler(v -> { 251 | testComplete(); 252 | }); 253 | publisher.subscriber.onComplete(); 254 | await(); 255 | } 256 | 257 | class MySubscription implements Subscription { 258 | 259 | int requested; 260 | int requestedTimes; 261 | 262 | @Override 263 | public void request(long n) { 264 | requestedTimes++; 265 | requested += n; 266 | } 267 | 268 | @Override 269 | public void cancel() { 270 | 271 | } 272 | } 273 | 274 | 275 | class MyPublisher implements Publisher { 276 | 277 | MySubscription subscription; 278 | Subscriber subscriber; 279 | 280 | @Override 281 | public void subscribe(Subscriber subscriber) { 282 | this.subscriber = subscriber; 283 | subscription = new MySubscription(); 284 | subscriber.onSubscribe(subscription); 285 | } 286 | } 287 | 288 | } 289 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/ext/reactivestreams/test/ReactiveStreamTestBase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Red Hat, Inc. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | package io.vertx.ext.reactivestreams.test; 18 | 19 | import io.vertx.core.buffer.Buffer; 20 | import io.vertx.test.core.TestUtils; 21 | import io.vertx.test.core.VertxTestBase; 22 | 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | 26 | /** 27 | * @author Tim Fox 28 | */ 29 | public class ReactiveStreamTestBase extends VertxTestBase { 30 | 31 | protected List createRandomBuffers(int number) { 32 | List buffers = new ArrayList<>(); 33 | for (int i = 0; i < number; i++) { 34 | buffers.add(TestUtils.randomBuffer(100)); 35 | } 36 | return buffers; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/ext/reactivestreams/test/ReactiveWriteStreamTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Red Hat, Inc. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | package io.vertx.ext.reactivestreams.test; 18 | 19 | import io.vertx.core.Future; 20 | import io.vertx.core.Promise; 21 | import io.vertx.core.buffer.Buffer; 22 | import io.vertx.ext.reactivestreams.ReactiveWriteStream; 23 | import io.vertx.test.core.TestUtils; 24 | import org.junit.Test; 25 | import org.reactivestreams.Subscriber; 26 | import org.reactivestreams.Subscription; 27 | 28 | import java.util.List; 29 | import java.util.concurrent.CopyOnWriteArrayList; 30 | import java.util.concurrent.atomic.AtomicBoolean; 31 | 32 | /** 33 | * @author Tim Fox 34 | */ 35 | public class ReactiveWriteStreamTest extends ReactiveStreamTestBase { 36 | 37 | @Test 38 | public void testWriteNoTokensInitially() throws Exception { 39 | ReactiveWriteStream rws = ReactiveWriteStream.writeStream(vertx); 40 | 41 | MySubscriber subscriber = new MySubscriber(); 42 | rws.subscribe(subscriber); 43 | 44 | assertWaitUntil(() -> subscriber.subscription != null); 45 | 46 | List buffers = createRandomBuffers(4); 47 | for (Buffer buffer: buffers) { 48 | rws.write(buffer); 49 | } 50 | 51 | assertTrue(subscriber.buffers.isEmpty()); 52 | 53 | subscriber.subscription.request(1); 54 | 55 | assertWaitUntil(() -> subscriber.buffers.size() == 1); 56 | assertEquals(1, subscriber.buffers.size()); 57 | assertSame(buffers.get(0), subscriber.buffers.get(0)); 58 | 59 | subscriber.subscription.request(2); 60 | assertWaitUntil(() -> subscriber.buffers.size() == 3); 61 | assertEquals(3, subscriber.buffers.size()); 62 | assertSame(buffers.get(1), subscriber.buffers.get(1)); 63 | assertSame(buffers.get(2), subscriber.buffers.get(2)); 64 | 65 | } 66 | 67 | @Test 68 | public void testWriteInitialTokens() throws Exception { 69 | ReactiveWriteStream rws = ReactiveWriteStream.writeStream(vertx); 70 | 71 | MySubscriber subscriber = new MySubscriber(); 72 | rws.subscribe(subscriber); 73 | 74 | assertWaitUntil(() -> subscriber.subscription != null); 75 | subscriber.subscription.request(3); 76 | 77 | List buffers = createRandomBuffers(4); 78 | for (Buffer buffer: buffers) { 79 | rws.write(buffer); 80 | } 81 | 82 | assertWaitUntil(() -> subscriber.buffers.size() == 3); 83 | assertEquals(3, subscriber.buffers.size()); 84 | assertSame(buffers.get(0), subscriber.buffers.get(0)); 85 | assertSame(buffers.get(1), subscriber.buffers.get(1)); 86 | assertSame(buffers.get(2), subscriber.buffers.get(2)); 87 | 88 | } 89 | 90 | // TODO test setters for max writestreamsize and buffer size and valid values 91 | 92 | // TODO test cancel subscription 93 | 94 | @Test 95 | public void testMultipleSubscribers() throws Exception { 96 | ReactiveWriteStream rws = ReactiveWriteStream.writeStream(vertx); 97 | 98 | MySubscriber subscriber1 = new MySubscriber(); 99 | rws.subscribe(subscriber1); 100 | MySubscriber subscriber2 = new MySubscriber(); 101 | rws.subscribe(subscriber2); 102 | MySubscriber subscriber3 = new MySubscriber(); 103 | rws.subscribe(subscriber3); 104 | 105 | assertWaitUntil(() -> subscriber1.subscription != null); 106 | assertWaitUntil(() -> subscriber2.subscription != null); 107 | assertWaitUntil(() -> subscriber3.subscription != null); 108 | 109 | List buffers = createRandomBuffers(10); 110 | for (Buffer buffer: buffers) { 111 | rws.write(buffer); 112 | } 113 | 114 | assertEquals(0, subscriber1.buffers.size()); 115 | assertEquals(0, subscriber2.buffers.size()); 116 | assertEquals(0, subscriber3.buffers.size()); 117 | 118 | // We go at the speed of the slowest consumer 119 | subscriber1.subscription.request(1); 120 | assertEquals(0, subscriber1.buffers.size()); 121 | assertEquals(0, subscriber2.buffers.size()); 122 | assertEquals(0, subscriber3.buffers.size()); 123 | 124 | subscriber2.subscription.request(1); 125 | assertEquals(0, subscriber1.buffers.size()); 126 | assertEquals(0, subscriber2.buffers.size()); 127 | assertEquals(0, subscriber3.buffers.size()); 128 | 129 | subscriber3.subscription.request(1); 130 | assertWaitUntil(() -> subscriber1.buffers.size() == 1); 131 | assertWaitUntil(() -> subscriber2.buffers.size() == 1); 132 | assertWaitUntil(() -> subscriber3.buffers.size() == 1); 133 | assertEquals(1, subscriber1.buffers.size()); 134 | assertEquals(1, subscriber2.buffers.size()); 135 | assertEquals(1, subscriber3.buffers.size()); 136 | assertEquals(buffers.get(0), subscriber1.buffers.get(0)); 137 | assertEquals(buffers.get(0), subscriber2.buffers.get(0)); 138 | assertEquals(buffers.get(0), subscriber3.buffers.get(0)); 139 | 140 | subscriber1.subscription.request(4); 141 | assertEquals(1, subscriber1.buffers.size()); 142 | assertEquals(1, subscriber2.buffers.size()); 143 | assertEquals(1, subscriber3.buffers.size()); 144 | subscriber2.subscription.request(3); 145 | assertEquals(1, subscriber1.buffers.size()); 146 | assertEquals(1, subscriber2.buffers.size()); 147 | assertEquals(1, subscriber3.buffers.size()); 148 | subscriber3.subscription.request(2); 149 | assertWaitUntil(() -> subscriber1.buffers.size() == 3); 150 | assertWaitUntil(() -> subscriber2.buffers.size() == 3); 151 | assertWaitUntil(() -> subscriber3.buffers.size() == 3); 152 | assertEquals(3, subscriber1.buffers.size()); 153 | assertEquals(3, subscriber2.buffers.size()); 154 | assertEquals(3, subscriber3.buffers.size()); 155 | assertEquals(buffers.get(0), subscriber1.buffers.get(0)); 156 | assertEquals(buffers.get(1), subscriber1.buffers.get(1)); 157 | assertEquals(buffers.get(2), subscriber1.buffers.get(2)); 158 | assertEquals(buffers.get(0), subscriber2.buffers.get(0)); 159 | assertEquals(buffers.get(1), subscriber2.buffers.get(1)); 160 | assertEquals(buffers.get(2), subscriber2.buffers.get(2)); 161 | assertEquals(buffers.get(0), subscriber3.buffers.get(0)); 162 | assertEquals(buffers.get(1), subscriber3.buffers.get(1)); 163 | assertEquals(buffers.get(2), subscriber3.buffers.get(2)); 164 | 165 | subscriber2.subscription.request(1); 166 | assertEquals(3, subscriber1.buffers.size()); 167 | assertEquals(3, subscriber2.buffers.size()); 168 | assertEquals(3, subscriber3.buffers.size()); 169 | subscriber3.subscription.request(2); 170 | assertWaitUntil(() -> subscriber1.buffers.size() == 5); 171 | assertWaitUntil(() -> subscriber2.buffers.size() == 5); 172 | assertWaitUntil(() -> subscriber3.buffers.size() == 5); 173 | assertEquals(5, subscriber1.buffers.size()); 174 | assertEquals(5, subscriber2.buffers.size()); 175 | assertEquals(5, subscriber3.buffers.size()); 176 | assertEquals(buffers.get(0), subscriber1.buffers.get(0)); 177 | assertEquals(buffers.get(1), subscriber1.buffers.get(1)); 178 | assertEquals(buffers.get(2), subscriber1.buffers.get(2)); 179 | assertEquals(buffers.get(3), subscriber1.buffers.get(3)); 180 | assertEquals(buffers.get(4), subscriber1.buffers.get(4)); 181 | assertEquals(buffers.get(0), subscriber2.buffers.get(0)); 182 | assertEquals(buffers.get(1), subscriber2.buffers.get(1)); 183 | assertEquals(buffers.get(2), subscriber2.buffers.get(2)); 184 | assertEquals(buffers.get(3), subscriber2.buffers.get(3)); 185 | assertEquals(buffers.get(4), subscriber2.buffers.get(4)); 186 | assertEquals(buffers.get(0), subscriber3.buffers.get(0)); 187 | assertEquals(buffers.get(1), subscriber3.buffers.get(1)); 188 | assertEquals(buffers.get(2), subscriber3.buffers.get(2)); 189 | assertEquals(buffers.get(3), subscriber3.buffers.get(3)); 190 | assertEquals(buffers.get(4), subscriber3.buffers.get(4)); 191 | } 192 | 193 | @Test 194 | public void testWriteQueueFullAndDrainDefaultQueueSize() throws Exception { 195 | ReactiveWriteStream rws = ReactiveWriteStream.writeStream(vertx); 196 | testWriteQueueFullAndDrain(rws, 10); 197 | } 198 | 199 | private void testWriteQueueFullAndDrain(ReactiveWriteStream rws, int writeQueueMaxSize) throws Exception { 200 | rws.setWriteQueueMaxSize(writeQueueMaxSize); 201 | MySubscriber subscriber = new MySubscriber(); 202 | rws.subscribe(subscriber); 203 | for (int i = 0; i < writeQueueMaxSize - 1; i++) { 204 | rws.write(TestUtils.randomBuffer(50)); 205 | } 206 | assertFalse(rws.writeQueueFull()); 207 | Buffer buff2 = TestUtils.randomBuffer(100); 208 | rws.write(buff2); 209 | assertTrue(rws.writeQueueFull()); 210 | rws.drainHandler(v -> { 211 | assertFalse(rws.writeQueueFull()); 212 | testComplete(); 213 | }); 214 | assertWaitUntil(() -> subscriber.subscription != null); 215 | subscriber.subscription.request(2); 216 | await(); 217 | } 218 | 219 | 220 | class MySubscriber implements Subscriber { 221 | 222 | final List buffers = new CopyOnWriteArrayList<>(); 223 | volatile Subscription subscription; 224 | 225 | @Override 226 | public void onSubscribe(Subscription subscription) { 227 | this.subscription = subscription; 228 | } 229 | 230 | @Override 231 | public void onNext(Buffer buffer) { 232 | buffers.add(buffer); 233 | } 234 | 235 | @Override 236 | public void onError(Throwable throwable) { 237 | 238 | } 239 | 240 | @Override 241 | public void onComplete() { 242 | 243 | } 244 | } 245 | 246 | @Test 247 | public void testCancelSubscriptionOnError1() { 248 | ReactiveWriteStream rws = ReactiveWriteStream.writeStream(vertx); 249 | AtomicBoolean failed = new AtomicBoolean(); 250 | MySubscriber subscriber1 = new MySubscriber() { 251 | @Override 252 | public void onSubscribe(Subscription subscription) { 253 | super.onSubscribe(subscription); 254 | throw new RuntimeException(); 255 | } 256 | @Override 257 | public void onNext(Buffer buffer) { 258 | fail(); 259 | } 260 | @Override 261 | public void onError(Throwable throwable) { 262 | failed.set(true); 263 | } 264 | }; 265 | rws.subscribe(subscriber1); 266 | MySubscriber subscriber2 = new MySubscriber() { 267 | @Override 268 | public void onSubscribe(Subscription subscription) { 269 | subscription.request(1); 270 | super.onSubscribe(subscription); 271 | } 272 | int count = 0; 273 | @Override 274 | public void onNext(Buffer buffer) { 275 | if (++count == 1) { 276 | testComplete(); 277 | } 278 | } 279 | }; 280 | rws.subscribe(subscriber2); 281 | assertWaitUntil(() -> subscriber1.subscription != null); 282 | assertWaitUntil(() -> subscriber2.subscription != null); 283 | assertWaitUntil(failed::get); 284 | rws.write(createRandomBuffers(1).get(0)); 285 | await(); 286 | } 287 | 288 | @Test 289 | public void testCancelSubscriptionOnError2() { 290 | ReactiveWriteStream rws = ReactiveWriteStream.writeStream(vertx); 291 | AtomicBoolean failed = new AtomicBoolean(); 292 | MySubscriber subscriber1 = new MySubscriber() { 293 | @Override 294 | public void onSubscribe(Subscription subscription) { 295 | subscription.request(2); 296 | super.onSubscribe(subscription); 297 | } 298 | @Override 299 | public void onNext(Buffer buffer) { 300 | if (!failed.get()) { 301 | throw new RuntimeException(); 302 | } else { 303 | fail(); 304 | } 305 | } 306 | @Override 307 | public void onError(Throwable throwable) { 308 | failed.set(true); 309 | } 310 | }; 311 | rws.subscribe(subscriber1); 312 | MySubscriber subscriber2 = new MySubscriber() { 313 | @Override 314 | public void onSubscribe(Subscription subscription) { 315 | subscription.request(3); 316 | super.onSubscribe(subscription); 317 | } 318 | int count = 0; 319 | @Override 320 | public void onNext(Buffer buffer) { 321 | if (++count == 3) { 322 | testComplete(); 323 | } 324 | } 325 | }; 326 | rws.subscribe(subscriber2); 327 | assertWaitUntil(() -> subscriber1.subscription != null); 328 | assertWaitUntil(() -> subscriber2.subscription != null); 329 | rws.write(createRandomBuffers(1).get(0)); 330 | assertWaitUntil(failed::get); 331 | rws.write(createRandomBuffers(1).get(0)); 332 | rws.write(createRandomBuffers(1).get(0)); 333 | await(); 334 | } 335 | 336 | @Test 337 | public void testWriteHandler() { 338 | ReactiveWriteStream rws = ReactiveWriteStream.writeStream(vertx); 339 | Promise f1 = Promise.promise(); 340 | rws.write(createRandomBuffers(1).get(0)).onComplete(f1); 341 | assertFalse(f1.future().isComplete()); 342 | rws.subscribe(new MySubscriber() { 343 | @Override 344 | public void onSubscribe(Subscription subscription) { 345 | subscription.request(1); 346 | } 347 | }); 348 | waitUntil(f1.future()::succeeded); 349 | } 350 | 351 | @Test 352 | public void testWriteHandlerFailure() { 353 | ReactiveWriteStream rws = ReactiveWriteStream.writeStream(vertx); 354 | Promise f1 = Promise.promise(); 355 | rws.write(createRandomBuffers(1).get(0)).onComplete(f1); 356 | assertFalse(f1.future().isComplete()); 357 | Promise f2 = Promise.promise(); 358 | rws.end().onComplete(f2); 359 | waitUntil(f1.future()::failed); 360 | waitUntil(f2.future()::succeeded); 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /src/test/java/module-info.java: -------------------------------------------------------------------------------- 1 | module io.vertx.reactivestreams.tests { 2 | requires io.vertx.core; 3 | requires io.vertx.core.tests; 4 | requires io.vertx.reactivestreams; 5 | requires junit; 6 | requires org.reactivestreams; 7 | requires org.reactivestreams.tck; 8 | 9 | exports io.vertx.ext.reactivestreams.test; 10 | exports io.vertx.ext.reactivestreams.tck; 11 | } 12 | --------------------------------------------------------------------------------