├── .gitattributes ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── RELEASE_NOTES.md ├── Relicensing.txt ├── build.cmd ├── build.fsx ├── build.ps1 ├── build.sh └── src ├── .nuget ├── NuGet.Config ├── NuGet.Dev.Config ├── NuGet.exe └── NuGet.targets ├── Reactive.Streams.sln ├── SharedAssemblyInfo.cs ├── api └── Reactive.Streams │ ├── IProcessor.cs │ ├── IPublisher.cs │ ├── ISubscriber.cs │ ├── ISubscription.cs │ └── Reactive.Streams.csproj ├── examples ├── Reactive.Streams.Example.Unicast.Tests │ ├── AsyncSubscriberTest.cs │ ├── IterablePublisherTest.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Reactive.Streams.Example.Unicast.Tests.csproj │ ├── SyncSubscriberTest.cs │ ├── SyncSubscriberWhiteboxTest.cs │ └── UnboundedIntegerIncrementPublisherTest.cs └── Reactive.Streams.Example.Unicast │ ├── AsyncIterablePublisher.cs │ ├── AsyncSubscriber.cs │ ├── AtomicBoolean.cs │ ├── IllegalStateException.cs │ ├── InfiniteIncrementNumberPublisher.cs │ ├── NumberIterablePublisher.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── Reactive.Streams.Example.Unicast.csproj │ └── SyncSubscriber.cs └── tck ├── README.md ├── Reactive.Streams.TCK.Tests ├── EmptyLazyPublisherTest.cs ├── IdentityProcessorVerificationDelegationTest.cs ├── IdentityProcessorVerificationTest.cs ├── Properties │ └── AssemblyInfo.cs ├── PublisherVerificationTest.cs ├── RangePublisherTest.cs ├── Reactive.Streams.TCK.Tests.csproj ├── SingleElementPublisherTest.cs ├── SubscriberBlackboxVerificationTest.cs ├── SubscriberWhiteboxVerificationTest.cs ├── Support │ ├── LamdaPublisher.cs │ ├── LamdaSubscriber.cs │ ├── LamdaSubscription.cs │ ├── SyncTriggeredDemandSubscriber.cs │ └── TCKVerificationSupport.cs ├── SyncTriggeredDemandSubscriberTest.cs └── SyncTriggeredDemandSubscriberWhiteboxTest.cs └── Reactive.Streams.TCK ├── IdentityProcessorVerification.cs ├── Properties └── AssemblyInfo.cs ├── PublisherVerification.cs ├── Reactive.Streams.TCK.csproj ├── Reactive.Streams.TCK.nuspec ├── SubscriberBlackboxVerification.cs ├── SubscriberWhiteboxVerification.cs ├── Support ├── AtomicBoolean.cs ├── AtomicCounter.cs ├── AtomicCounterLong.cs ├── AtomicReference.cs ├── HelperPublisher.cs ├── IPublisherVerificationRules .cs ├── ISubscriberBlackboxVerificationRules.cs ├── ISubscriberWhiteboxVerificationRules.cs ├── IllegalStateException.cs ├── InfiniteHelperPublisher.cs ├── Option.cs ├── SubscriberBufferOverflowException.cs └── TestException.cs ├── TestEnvironment.cs └── WithHelperPublisher.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | 5 | # Custom for Visual Studio 6 | *.cs diff=csharp 7 | *.sln merge=union 8 | *.csproj merge=union 9 | *.vbproj merge=union 10 | *.fsproj merge=union 11 | *.dbproj merge=union 12 | 13 | # Standard to msysgit 14 | *.doc diff=astextplain 15 | *.DOC diff=astextplain 16 | *.docx diff=astextplain 17 | *.DOCX diff=astextplain 18 | *.dot diff=astextplain 19 | *.DOT diff=astextplain 20 | *.pdf diff=astextplain 21 | *.PDF diff=astextplain 22 | *.rtf diff=astextplain 23 | *.RTF diff=astextplain 24 | 25 | # Needed for Mono build shell script 26 | *.sh -text eol=lf 27 | 28 | # Needed for API Approvals 29 | *.txt text eol=crlf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | #GitExtensions 3 | us.stackdump 4 | 5 | #KDiff3 and other git merge tools 6 | *.orig 7 | 8 | #------------------------------------------------------------------------------- 9 | #Based on https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 10 | 11 | ## Ignore Visual Studio temporary files, build results, and 12 | ## files generated by popular Visual Studio add-ons. 13 | 14 | # User-specific files 15 | *.suo 16 | *.user 17 | *.sln.docstates 18 | 19 | #MonoDevelop 20 | *.userprefs 21 | 22 | # Build results 23 | [Dd]ebug/ 24 | [Dd]ebugPublic/ 25 | [Rr]elease/ 26 | [Rr]eleases/ 27 | x64/ 28 | x86/ 29 | build/ 30 | bld/ 31 | [Bb]in/ 32 | [Oo]bj/ 33 | 34 | # Roslyn cache directories 35 | *.ide/ 36 | 37 | # MSTest test Results 38 | [Tt]est[Rr]esult*/ 39 | [Bb]uild[Ll]og.* 40 | 41 | # NBench results 42 | [Pp]erf[Rr]esult*/ 43 | 44 | #NUNIT 45 | *.VisualState.xml 46 | TestResult.xml 47 | 48 | # Build Results of an ATL Project 49 | [Dd]ebugPS/ 50 | [Rr]eleasePS/ 51 | dlldata.c 52 | 53 | *_i.c 54 | *_p.c 55 | *_i.h 56 | *.ilk 57 | *.meta 58 | *.obj 59 | *.pch 60 | *.pdb 61 | *.pgc 62 | *.pgd 63 | *.rsp 64 | *.sbr 65 | *.tlb 66 | *.tli 67 | *.tlh 68 | *.tmp 69 | *.tmp_proj 70 | *.log 71 | *.vspscc 72 | *.vssscc 73 | .builds 74 | *.pidb 75 | *.svclog 76 | *.scc 77 | 78 | # Chutzpah Test files 79 | _Chutzpah* 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | 94 | # TFS 2012 Local Workspace 95 | $tf/ 96 | 97 | # Guidance Automation Toolkit 98 | *.gpState 99 | 100 | # ReSharper is a .NET coding add-in 101 | _ReSharper*/ 102 | *.[Rr]e[Ss]harper 103 | *.DotSettings.user 104 | 105 | # JustCode is a .NET coding addin-in 106 | .JustCode 107 | 108 | # TeamCity is a build add-in 109 | _TeamCity* 110 | 111 | # DotCover is a Code Coverage Tool 112 | *.dotCover 113 | 114 | # NCrunch 115 | _NCrunch_* 116 | .*crunch*.local.xml 117 | *.ncrunchsolution 118 | *.v2.ncrunchproject 119 | 120 | 121 | # MightyMoose 122 | *.mm.* 123 | AutoTest.Net/ 124 | 125 | # Web workbench (sass) 126 | .sass-cache/ 127 | 128 | # Installshield output folder 129 | [Ee]xpress/ 130 | 131 | # DocProject is a documentation generator add-in 132 | DocProject/buildhelp/ 133 | DocProject/Help/*.HxT 134 | DocProject/Help/*.HxC 135 | DocProject/Help/*.hhc 136 | DocProject/Help/*.hhk 137 | DocProject/Help/*.hhp 138 | DocProject/Help/Html2 139 | DocProject/Help/html 140 | 141 | # Click-Once directory 142 | publish/ 143 | 144 | # Publish Web Output 145 | *.[Pp]ublish.xml 146 | *.azurePubxml 147 | # TODO: Comment out the next line if you want to keep your passwords hidden 148 | *.pubxml 149 | 150 | # NuGet Packages 151 | *.nupkg 152 | # The packages folder can be ignored because of Package Restore 153 | **/packages/* 154 | # except build/, which is used as an MSBuild target. 155 | !**/packages/build/ 156 | # If using the old MSBuild-Integrated Package Restore, uncomment this: 157 | !**/packages/repositories.config 158 | 159 | # Windows Azure Build Output 160 | csx/ 161 | *.build.csdef 162 | 163 | # Windows Store app package directory 164 | AppPackages/ 165 | 166 | # Others 167 | sql/ 168 | *.Cache 169 | ClientBin/ 170 | [Ss]tyle[Cc]op.* 171 | ~$* 172 | *~ 173 | *.dbmdl 174 | *.dbproj.schemaview 175 | *.pfx 176 | *.publishsettings 177 | node_modules/ 178 | 179 | # RIA/Silverlight projects 180 | Generated_Code/ 181 | 182 | # Backup & report files from converting an old project file 183 | # to a newer Visual Studio version. Backup files are not needed, 184 | # because we have git ;-) 185 | _UpgradeReport_Files/ 186 | Backup*/ 187 | UpgradeLog*.XML 188 | UpgradeLog*.htm 189 | 190 | # SQL Server files 191 | *.mdf 192 | *.ldf 193 | 194 | # make exception for Akka.Persistence.SqlServer database file 195 | !AkkaPersistenceSqlServerSpecDb.mdf 196 | !AkkaPersistenceSqlServerSpecDb_log.ldf 197 | 198 | # Business Intelligence projects 199 | *.rdl.data 200 | *.bim.layout 201 | *.bim_*.settings 202 | 203 | # FAKE 204 | .fake/ 205 | 206 | # .Net Core 207 | .dotnet/ 208 | 209 | # Tools 210 | tools/ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the Reactive Streams Project 2 | 3 | The Reactive Streams project welcomes contributions from anybody who wants to participate in moving this initiative forward. All code or documentation that is contributed will have to be covered by the **MIT No Attribution** (SPDX: MIT-0) license, the rationale for this is that the APIs defined by this project shall be freely implementable and usable by everyone. For more detail, see [LICENSE](https://github.com/reactive-streams/reactive-streams-jvm/blob/master/LICENSE). 4 | 5 | ## Gatekeepers 6 | 7 | To ensure consistent development of Reactive Streams towards their goal, a group of gatekeepers is defined: 8 | 9 | * Kaazing Corp., currently represented by Todd Montgomery (@tmontgomery) 10 | * Netflix Inc., currently represented by Ben Christensen (@benjchristensen) 11 | * Pivotal Software Inc., currently represented by Jon Brisbin (@jbrisbin) and Stéphane Maldini (@smaldini) 12 | * Red Hat Inc., currently represented by Tim Fox (@purplefox) and Norman Maurer (@normanmaurer) 13 | * Twitter Inc., currently represented by Marius Eriksen (@mariusaeriksen) 14 | * Typesafe Inc., currently represented by Viktor Klang (@viktorklang) and Roland Kuhn (@rkuhn) 15 | 16 | The role of this group is detailed in the following, additions to this list are made by pull request as defined below, removals require the consent of the entity to be removed or unanimous consent of all other Gatekeepers. Changing a representative of one of the gatekeeper entities can be done by a member of that entity without requiring consent from the other Gatekeepers. 17 | 18 | Gatekeepers commit to the following: 19 | 20 | 1. 1-week SLA on :+1: or :-1: Pull Requests 21 | * If a Gatekeeper will be unavailable for a period of time, notify @reactive-streams/contributors and appoint who will vote in his/her place in the mean time 22 | 2. tag @reactive-streams/contributors with a deadline when there needs to be a vote on an Issue, 23 | with at least 1 week of notice (see rule 1 above) 24 | 25 | ## General Workflow 26 | 27 | 1. Before starting to work on a change, make sure that: 28 | 1. There is a ticket for your work in the project's issue tracker. If not, create it first. It can help accelerating the pull request acceptance process if the change is agreed upon beforehand within the ticket, but in some cases it may be preferable to discuss the necessity of the change in consideration of a concrete proposal. 29 | 2. The ticket has been scheduled for the current milestone. 30 | 2. You should always perform your work in a Git feature branch within your own fork of the repository your are targeting (even if you should have push rights to the target repository). 31 | 3. When the change is completed you should open a [Pull Request](https://help.github.com/articles/using-pull-requests) on GitHub. 32 | 4. Anyone can comment on the pull request while it is open, and you are expected to answer questions or incorporate feedback. 33 | 5. Once at least two thirds of the gatekeepers have signaled their consent, the pull request is merged by one of the gatekeepers into the branch and repository it is targeting. Consent is signaled by commenting on the pull request with the text “LGTM”, and it suffices for one representative of a gatekeeper to signal consent for that gatekeeper to be counted towards the two thirds quorum. 34 | 6. It is not allowed to force-push to the branch on which the pull request is based. Replacing or adding to the commits on that branch will invalidate all previous consenting comments and consent needs to be re-established. 35 | 7. Before merging a branch that contains multiple commits, it is recommended that these are squashed into a single commit before performing the merge. To aid in verification that no new changes are introduced, a new pull request should be opened in this case, targeting the same branch and repository and containing just one commit which encompasses the full change set of the original pull request. 36 | 37 | ## Pull Request Requirements 38 | 39 | For a Pull Request to be considered at all it has to meet these requirements: 40 | 41 | 1. If applicable, the new or fixed features must be accompanied by comprehensive tests. 42 | 2. If applicable, the pull request must contain all necessary documentation updates required by the changes introduced. 43 | 3. The pull request must not contain changes that are unrelated to the ticket that it corresponds to. One pull request is meant to introduce only one logically contiguous change. 44 | 45 | ## Creating Commits And Writing Commit Messages 46 | 47 | Follow these guidelines when creating public commits and writing commit messages. 48 | 49 | 1. If your work spans multiple local commits (for example; if you do safe point commits while working in a feature branch or work in a branch for long time doing merges/rebases etc.) then please do not commit it all but rewrite the history by squashing the commits into a single big commit which you write a good commit message for (like discussed in the following sections). For more info read this article: [Git Workflow](http://sandofsky.com/blog/git-workflow.html). Every commit should be able to be used in isolation, cherry picked etc. 50 | 51 | 2. First line should be a descriptive sentence what the commit is doing. It should be possible to fully understand what the commit does—but not necessarily how it does it—by just reading this single line. We follow the “imperative present tense” style for commit messages ([more info here](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)). 52 | 53 | It is **not ok** to only list the ticket number, type "minor fix" or similar. In order to help with automatic filtering of the commit history (generating ChangeLogs, writing the migration guide, code archaeology) we use the following encoding: 54 | 55 | 3. Following the single line description should be a blank line followed by an enumerated list with the details of the commit. For very simple commits this may be empty. 56 | 57 | 4. Add keywords for your commit: 58 | * ``Review by @gituser`` - if you want to notify someone specifically for review; this has no influence on the acceptance process described above 59 | 60 | Example: 61 | 62 | add CONTRIBUTING.md 63 | 64 | * clarify how pull requests should look like 65 | * describe the acceptance process 66 | 67 | ## Performing Official Releases 68 | 69 | Creating binary artifacts, uploading them to central repositories and declaring these to be an official release of the Reactive Streams project requires the consent of all gatekeepers. The process is initiated by creating a ticket in the `reactive-streams` repository for this purpose and consent is signaled in the same way as for pull requests. The actual work of updating version numbers and publishing the artifacts will typically involve pull requests targeting the affected repositories. 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT No Attribution 2 | 3 | Copyright 2014 Reactive Streams 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | #### 1.0.2 - 07.04.2017 2 | 3 | Reactive.Streams 4 | - Stable release 5 | - Support for .Net Standard 1.0 6 | 7 | Reactive.Streams.TCK 8 | - Stable release 9 | - Fix missing Cancel() in tests that don't consume the entire source #32 10 | - Skip §2.13 for value types #34 11 | - Fail NextN with the correct error message if timeout is reached #36 12 | 13 | #### 1.0.0-RC1 - 04.07.2016 14 | 15 | Reactive.Streams 16 | - Support for PCL Profile7 17 | - Removed non-generic IPublisher and ISubscriber interfaces. 18 | 19 | Reactive.Streams.TCK 20 | - Initial release 21 | -------------------------------------------------------------------------------- /Relicensing.txt: -------------------------------------------------------------------------------- 1 | The following copyright holders agree that all of their contributions originally submitted to this project under Creative Commons 2 | Zero 1.0 Universal (SPDX: CC0-1.0) license are hereby relicensed to MIT No Attribution (SPDX: MIT-0) license, and are submitted pursuant to the Developer Certificate of Origin, version 1.1: 3 | 4 | 5 | github name | Real Name, Email Address used for git commits, Company 6 | ---------------+---------------------------------------------------------------------------- 7 | viktorklang | Viktor Klang, viktor.klang@gmail.com, Lightbend Inc. 8 | akarnokd | David Karnok, akarnokd@gmail.com 9 | marcpiechura | Marc Piechura, piechura@gmx.net 10 | cconstantin | Chris Constantin, chris.constantin@aligned.io, Aligned Software Solutions Inc. 11 | alexvaluyskiy | Oleksii Valuiskyi, alex.valuiskyi@gmail.com 12 | -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | PowerShell.exe -file "build.ps1" %* -------------------------------------------------------------------------------- /build.fsx: -------------------------------------------------------------------------------- 1 | #I @"tools/FAKE/tools" 2 | #r "FakeLib.dll" 3 | 4 | open System 5 | open System.IO 6 | open System.Text 7 | 8 | open Fake 9 | open Fake.DotNetCli 10 | open Fake.Testing 11 | 12 | // Variables 13 | let configuration = "Release" 14 | 15 | // Directories 16 | let output = __SOURCE_DIRECTORY__ @@ "build" 17 | let outputTests = output @@ "TestResults" 18 | let outputBinaries = output @@ "binaries" 19 | let outputNuGet = output @@ "nuget" 20 | let outputBinariesNet45 = outputBinaries @@ "net45" 21 | let outputBinariesNetStandard = outputBinaries @@ "netstandard1.0" 22 | 23 | Target "Clean" (fun _ -> 24 | CleanDir output 25 | CleanDir outputTests 26 | CleanDir outputBinaries 27 | CleanDir outputNuGet 28 | CleanDir outputBinariesNet45 29 | CleanDir outputBinariesNetStandard 30 | 31 | CleanDirs !! "./**/bin" 32 | CleanDirs !! "./**/obj" 33 | ) 34 | 35 | Target "RestorePackages" (fun _ -> 36 | DotNetCli.Restore 37 | (fun p -> 38 | { p with 39 | Project = "./src/Reactive.Streams.sln" 40 | NoCache = false }) 41 | ) 42 | 43 | Target "Build" (fun _ -> 44 | if (isWindows) then 45 | let projects = !! "./**/*.csproj" 46 | 47 | let runSingleProject project = 48 | DotNetCli.Build 49 | (fun p -> 50 | { p with 51 | Project = project 52 | Configuration = configuration }) 53 | 54 | projects |> Seq.iter (runSingleProject) 55 | else 56 | DotNetCli.Build 57 | (fun p -> 58 | { p with 59 | Project = "./src/api/Reactive.Streams/Reactive.Streams.csproj" 60 | Framework = "netstandard1.0" 61 | Configuration = configuration }) 62 | ) 63 | 64 | Target "RunTests" (fun _ -> 65 | let projects = !! "./src/**/Reactive.Streams.Example.Unicast.Tests.csproj" 66 | ++ "./src/**/Reactive.Streams.TCK.Tests.csproj" 67 | 68 | let runSingleProject project = 69 | DotNetCli.Test 70 | (fun p -> 71 | { p with 72 | Project = project 73 | Configuration = configuration }) 74 | 75 | projects |> Seq.iter (runSingleProject) 76 | ) 77 | 78 | //-------------------------------------------------------------------------------- 79 | // Nuget targets 80 | //-------------------------------------------------------------------------------- 81 | 82 | Target "CreateNuget" (fun _ -> 83 | let versionSuffix = getBuildParamOrDefault "versionsuffix" "" 84 | 85 | DotNetCli.Pack 86 | (fun p -> 87 | { p with 88 | Project = "./src/api/Reactive.Streams/Reactive.Streams.csproj" 89 | Configuration = configuration 90 | AdditionalArgs = ["--include-symbols"] 91 | VersionSuffix = versionSuffix 92 | OutputPath = outputNuGet }) 93 | 94 | DotNetCli.Pack 95 | (fun p -> 96 | { p with 97 | Project = "./src/TCK/Reactive.Streams.TCK/Reactive.Streams.TCK.csproj" 98 | Configuration = configuration 99 | AdditionalArgs = ["--include-symbols /p:NuspecFile=Reactive.Streams.TCK.nuspec"] 100 | VersionSuffix = versionSuffix 101 | OutputPath = outputNuGet }) 102 | ) 103 | 104 | Target "PublishNuget" (fun _ -> 105 | let projects = !! "./build/nuget/*.nupkg" -- "./build/nuget/*.symbols.nupkg" 106 | let apiKey = getBuildParamOrDefault "nugetkey" "" 107 | let source = getBuildParamOrDefault "nugetpublishurl" "" 108 | let symbolSource = getBuildParamOrDefault "symbolspublishurl" "" 109 | 110 | let runSingleProject project = 111 | DotNetCli.RunCommand 112 | (fun p -> 113 | { p with 114 | TimeOut = TimeSpan.FromMinutes 10. }) 115 | (sprintf "nuget push %s --api-key %s --source %s --symbol-source %s" project apiKey source symbolSource) 116 | 117 | projects |> Seq.iter (runSingleProject) 118 | ) 119 | 120 | //-------------------------------------------------------------------------------- 121 | // Help 122 | //-------------------------------------------------------------------------------- 123 | 124 | Target "Help" <| fun _ -> 125 | List.iter printfn [ 126 | "usage:" 127 | "/build [target]" 128 | "" 129 | " Targets for building:" 130 | " * Build Builds" 131 | " * Nuget Create and optionally publish nugets packages" 132 | " * RunTests Runs tests" 133 | " * All Builds, run tests, creates and optionally publish nuget packages" 134 | "" 135 | " Other Targets" 136 | " * Help Display this help" 137 | ""] 138 | 139 | Target "HelpNuget" <| fun _ -> 140 | List.iter printfn [ 141 | "usage: " 142 | "build Nuget [nugetkey= [nugetpublishurl=]] " 143 | " [symbolspublishurl=] " 144 | "" 145 | "In order to publish a nuget package, keys must be specified." 146 | "If a key is not specified the nuget packages will only be created on disk" 147 | "After a build you can find them in build/nuget" 148 | "" 149 | "For pushing nuget packages to nuget.org and symbols to symbolsource.org" 150 | "you need to specify nugetkey=" 151 | " build Nuget nugetKey=" 152 | "" 153 | "For pushing the ordinary nuget packages to another place than nuget.org specify the url" 154 | " nugetkey= nugetpublishurl= " 155 | "" 156 | "For pushing symbols packages specify:" 157 | " symbolskey= symbolspublishurl= " 158 | "" 159 | "Examples:" 160 | " build Nuget Build nuget packages to the build/nuget folder" 161 | "" 162 | " build Nuget versionsuffix=beta1 Build nuget packages with the custom version suffix" 163 | "" 164 | " build Nuget nugetkey=123 Build and publish to nuget.org and symbolsource.org" 165 | "" 166 | " build Nuget nugetprerelease=dev nugetkey=123 nugetpublishurl=http://abcsymbolspublishurl=http://xyz" 167 | ""] 168 | 169 | //-------------------------------------------------------------------------------- 170 | // Target dependencies 171 | //-------------------------------------------------------------------------------- 172 | 173 | Target "BuildRelease" DoNothing 174 | Target "All" DoNothing 175 | Target "Nuget" DoNothing 176 | 177 | // build dependencies 178 | "Clean" ==> "RestorePackages" ==> "Build" ==> "BuildRelease" 179 | 180 | // tests dependencies 181 | "Clean" ==> "RestorePackages" ==> "Build" ==> "RunTests" 182 | 183 | // nuget dependencies 184 | "Clean" ==> "RestorePackages" ==> "Build" ==> "CreateNuget" 185 | "CreateNuget" ==> "PublishNuget" 186 | "PublishNuget" ==> "Nuget" 187 | 188 | // all 189 | "BuildRelease" ==> "All" 190 | "RunTests" ==> "All" 191 | 192 | RunTargetOrDefault "Help" -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This is a Powershell script to bootstrap a Fake build. 4 | .DESCRIPTION 5 | This Powershell script will download NuGet if missing, restore NuGet tools (including Fake) 6 | and execute your Fake build script with the parameters you provide. 7 | .PARAMETER Target 8 | The build script target to run. 9 | .PARAMETER Configuration 10 | The build configuration to use. 11 | .PARAMETER Verbosity 12 | Specifies the amount of information to be displayed. 13 | .PARAMETER WhatIf 14 | Performs a dry run of the build script. 15 | No tasks will be executed. 16 | .PARAMETER ScriptArgs 17 | Remaining arguments are added here. 18 | #> 19 | 20 | [CmdletBinding()] 21 | Param( 22 | [string]$Target = "Default", 23 | [ValidateSet("Release", "Debug")] 24 | [string]$Configuration = "Release", 25 | [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] 26 | [string]$Verbosity = "Verbose", 27 | [switch]$WhatIf, 28 | [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] 29 | [string[]]$ScriptArgs 30 | ) 31 | 32 | $FakeVersion = "4.57.4" 33 | $NUnitVersion = "3.6.0" 34 | $DotNetChannel = "preview"; 35 | $DotNetVersion = "1.0.0"; 36 | $DotNetInstallerUri = "https://raw.githubusercontent.com/dotnet/cli/rel/1.0.0/scripts/obtain/dotnet-install.ps1"; 37 | $NugetVersion = "4.1.0"; 38 | $NugetUrl = "https://dist.nuget.org/win-x86-commandline/v$NugetVersion/nuget.exe" 39 | 40 | # Make sure tools folder exists 41 | $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent 42 | $ToolPath = Join-Path $PSScriptRoot "tools" 43 | if (!(Test-Path $ToolPath)) { 44 | Write-Verbose "Creating tools directory..." 45 | New-Item -Path $ToolPath -Type directory | out-null 46 | } 47 | 48 | ########################################################################### 49 | # INSTALL .NET CORE CLI 50 | ########################################################################### 51 | 52 | Function Remove-PathVariable([string]$VariableToRemove) 53 | { 54 | $path = [Environment]::GetEnvironmentVariable("PATH", "User") 55 | if ($path -ne $null) 56 | { 57 | $newItems = $path.Split(';', [StringSplitOptions]::RemoveEmptyEntries) | Where-Object { "$($_)" -inotlike $VariableToRemove } 58 | [Environment]::SetEnvironmentVariable("PATH", [System.String]::Join(';', $newItems), "User") 59 | } 60 | 61 | $path = [Environment]::GetEnvironmentVariable("PATH", "Process") 62 | if ($path -ne $null) 63 | { 64 | $newItems = $path.Split(';', [StringSplitOptions]::RemoveEmptyEntries) | Where-Object { "$($_)" -inotlike $VariableToRemove } 65 | [Environment]::SetEnvironmentVariable("PATH", [System.String]::Join(';', $newItems), "Process") 66 | } 67 | } 68 | 69 | # Get .NET Core CLI path if installed. 70 | $FoundDotNetCliVersion = $null; 71 | if (Get-Command dotnet -ErrorAction SilentlyContinue) { 72 | $FoundDotNetCliVersion = dotnet --version; 73 | } 74 | 75 | if($FoundDotNetCliVersion -ne $DotNetVersion) { 76 | $InstallPath = Join-Path $PSScriptRoot ".dotnet" 77 | if (!(Test-Path $InstallPath)) { 78 | mkdir -Force $InstallPath | Out-Null; 79 | } 80 | (New-Object System.Net.WebClient).DownloadFile($DotNetInstallerUri, "$InstallPath\dotnet-install.ps1"); 81 | & $InstallPath\dotnet-install.ps1 -Channel $DotNetChannel -Version $DotNetVersion -InstallDir $InstallPath; 82 | 83 | Remove-PathVariable "$InstallPath" 84 | $env:PATH = "$InstallPath;$env:PATH" 85 | $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 86 | $env:DOTNET_CLI_TELEMETRY_OPTOUT=1 87 | } 88 | 89 | ########################################################################### 90 | # INSTALL NUGET 91 | ########################################################################### 92 | 93 | # Make sure nuget.exe exists. 94 | $NugetPath = Join-Path $ToolPath "nuget.exe" 95 | if (!(Test-Path $NugetPath)) { 96 | Write-Host "Downloading NuGet.exe..." 97 | (New-Object System.Net.WebClient).DownloadFile($NugetUrl, $NugetPath); 98 | } 99 | 100 | ########################################################################### 101 | # INSTALL FAKE 102 | ########################################################################### 103 | # Make sure Fake has been installed. 104 | 105 | $FakeExePath = Join-Path $ToolPath "FAKE/tools/FAKE.exe" 106 | if (!(Test-Path $FakeExePath)) { 107 | Write-Host "Installing Fake..." 108 | Invoke-Expression "&`"$NugetPath`" install Fake -ExcludeVersion -Version $FakeVersion -OutputDirectory `"$ToolPath`"" | Out-Null; 109 | if ($LASTEXITCODE -ne 0) { 110 | Throw "An error occured while restoring Fake from NuGet." 111 | } 112 | } 113 | 114 | ########################################################################### 115 | # INSTALL NUnit3 Test Runner 116 | ########################################################################### 117 | 118 | # Make sure NUnit3 Test Runner has been installed. 119 | $NUnitDllPath = Join-Path $ToolPath "NUnit.ConsoleRunner/tools/nunit3-console.exe" 120 | if (!(Test-Path $NUnitDllPath)) { 121 | Write-Host "Installing NUnit3 Runner..." 122 | Invoke-Expression "&`"$NugetPath`" install NUnit.ConsoleRunner -ExcludeVersion -Version $NUnitVersion -OutputDirectory `"$ToolPath`"" | Out-Null; 123 | if ($LASTEXITCODE -ne 0) { 124 | Throw "An error occured while restoring NUnit3 Test from NuGet." 125 | } 126 | } 127 | 128 | ########################################################################### 129 | # RUN BUILD SCRIPT 130 | ########################################################################### 131 | 132 | # Build the argument list. 133 | $Arguments = @{ 134 | target=$Target; 135 | configuration=$Configuration; 136 | verbosity=$Verbosity; 137 | dryrun=$WhatIf; 138 | }.GetEnumerator() | %{"--{0}=`"{1}`"" -f $_.key, $_.value }; 139 | 140 | # Start Fake 141 | Write-Host "Running build script..." 142 | Invoke-Expression "$FakeExePath `"build.fsx`" $ScriptArgs $Arguments" 143 | 144 | exit $LASTEXITCODE -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ########################################################################## 3 | # This is the Fake bootstrapper script for Linux and OS X. 4 | ########################################################################## 5 | 6 | # Define directories. 7 | SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 8 | TOOLS_DIR=$SCRIPT_DIR/tools 9 | NUGET_EXE=$TOOLS_DIR/nuget.exe 10 | NUGET_URL=https://dist.nuget.org/win-x86-commandline/v4.1.0/nuget.exe 11 | FAKE_VERSION=4.57.4 12 | FAKE_EXE=$TOOLS_DIR/FAKE/tools/FAKE.exe 13 | DOTNET_VERSION=1.0.0 14 | DOTNET_INSTALLER_URL=https://raw.githubusercontent.com/dotnet/cli/rel/1.0.0/scripts/obtain/dotnet-install.sh 15 | 16 | # Define default arguments. 17 | TARGET="Default" 18 | CONFIGURATION="Release" 19 | VERBOSITY="verbose" 20 | DRYRUN= 21 | SCRIPT_ARGUMENTS=() 22 | 23 | # Parse arguments. 24 | for i in "$@"; do 25 | case $1 in 26 | -t|--target) TARGET="$2"; shift ;; 27 | -c|--configuration) CONFIGURATION="$2"; shift ;; 28 | -v|--verbosity) VERBOSITY="$2"; shift ;; 29 | -d|--dryrun) DRYRUN="-dryrun" ;; 30 | --) shift; SCRIPT_ARGUMENTS+=("$@"); break ;; 31 | *) SCRIPT_ARGUMENTS+=("$1") ;; 32 | esac 33 | shift 34 | done 35 | 36 | # Make sure the tools folder exist. 37 | if [ ! -d "$TOOLS_DIR" ]; then 38 | mkdir "$TOOLS_DIR" 39 | fi 40 | 41 | ########################################################################### 42 | # INSTALL .NET CORE CLI 43 | ########################################################################### 44 | 45 | echo "Installing .NET CLI..." 46 | if [ ! -d "$SCRIPT_DIR/.dotnet" ]; then 47 | mkdir "$SCRIPT_DIR/.dotnet" 48 | fi 49 | curl -Lsfo "$SCRIPT_DIR/.dotnet/dotnet-install.sh" $DOTNET_INSTALLER_URL 50 | bash "$SCRIPT_DIR/.dotnet/dotnet-install.sh" --version $DOTNET_VERSION --install-dir .dotnet --no-path 51 | export PATH="$SCRIPT_DIR/.dotnet":$PATH 52 | export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 53 | export DOTNET_CLI_TELEMETRY_OPTOUT=1 54 | chmod -R 0755 ".dotnet" 55 | "$SCRIPT_DIR/.dotnet/dotnet" --info 56 | 57 | ########################################################################### 58 | # INSTALL NUGET 59 | ########################################################################### 60 | 61 | # Download NuGet if it does not exist. 62 | if [ ! -f "$NUGET_EXE" ]; then 63 | echo "Downloading NuGet..." 64 | curl -Lsfo "$NUGET_EXE" $NUGET_URL 65 | if [ $? -ne 0 ]; then 66 | echo "An error occured while downloading nuget.exe." 67 | exit 1 68 | fi 69 | fi 70 | 71 | ########################################################################### 72 | # INSTALL FAKE 73 | ########################################################################### 74 | 75 | if [ ! -f "$FAKE_EXE" ]; then 76 | mono "$NUGET_EXE" install Fake -ExcludeVersion -Version $FAKE_VERSION -OutputDirectory "$TOOLS_DIR" 77 | if [ $? -ne 0 ]; then 78 | echo "An error occured while installing Cake." 79 | exit 1 80 | fi 81 | fi 82 | 83 | # Make sure that Fake has been installed. 84 | if [ ! -f "$FAKE_EXE" ]; then 85 | echo "Could not find Fake.exe at '$FAKE_EXE'." 86 | exit 1 87 | fi 88 | 89 | ########################################################################### 90 | # RUN BUILD SCRIPT 91 | ########################################################################### 92 | 93 | # Start Fake 94 | exec mono "$FAKE_EXE" build.fsx "${SCRIPT_ARGUMENTS[@]}" --verbosity=$VERBOSITY --configuration=$CONFIGURATION --target=$TARGET $DRYRUN 95 | -------------------------------------------------------------------------------- /src/.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/.nuget/NuGet.Dev.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/.nuget/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reactive-streams/reactive-streams-dotnet/c4d6d54226f74fca3349d34e2f0e77e1a828630a/src/.nuget/NuGet.exe -------------------------------------------------------------------------------- /src/.nuget/NuGet.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildProjectDirectory)\..\ 5 | 6 | 7 | false 8 | 9 | 10 | false 11 | 12 | 13 | true 14 | 15 | 16 | false 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) 27 | 28 | 29 | 30 | 31 | $(SolutionDir).nuget 32 | 33 | 34 | 35 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config 36 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config 37 | 38 | 39 | 40 | $(MSBuildProjectDirectory)\packages.config 41 | $(PackagesProjectConfig) 42 | 43 | 44 | 45 | 46 | $(NuGetToolsPath)\NuGet.exe 47 | @(PackageSource) 48 | 49 | "$(NuGetExePath)" 50 | mono --runtime=v4.0.30319 "$(NuGetExePath)" 51 | 52 | $(TargetDir.Trim('\\')) 53 | 54 | -RequireConsent 55 | -NonInteractive 56 | 57 | "$(SolutionDir) " 58 | "$(SolutionDir)" 59 | 60 | 61 | $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) 62 | $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols 63 | 64 | 65 | 66 | RestorePackages; 67 | $(BuildDependsOn); 68 | 69 | 70 | 71 | 72 | $(BuildDependsOn); 73 | BuildPackage; 74 | 75 | 76 | 77 | 78 | 79 | 80 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | 102 | 104 | 105 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /src/Reactive.Streams.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25123.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reactive.Streams", "api\Reactive.Streams\Reactive.Streams.csproj", "{68FBB4DF-6D83-4CF1-8105-A1D41912852F}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{97BE0CEB-2816-47E9-ABC3-78905582A882}" 9 | ProjectSection(SolutionItems) = preProject 10 | .nuget\NuGet.Config = .nuget\NuGet.Config 11 | .nuget\NuGet.Dev.Config = .nuget\NuGet.Dev.Config 12 | .nuget\NuGet.exe = .nuget\NuGet.exe 13 | .nuget\NuGet.targets = .nuget\NuGet.targets 14 | EndProjectSection 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{D389959C-7C8C-4372-8AB2-049BAB2F95C3}" 17 | ProjectSection(SolutionItems) = preProject 18 | ..\build.cmd = ..\build.cmd 19 | ..\build.fsx = ..\build.fsx 20 | ..\build.sh = ..\build.sh 21 | EndProjectSection 22 | EndProject 23 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reactive.Streams.TCK", "tck\Reactive.Streams.TCK\Reactive.Streams.TCK.csproj", "{7DBE1261-04EB-4DA7-9953-C8E8CCC49010}" 24 | EndProject 25 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reactive.Streams.TCK.Tests", "tck\Reactive.Streams.TCK.Tests\Reactive.Streams.TCK.Tests.csproj", "{0F1C51EB-3554-487F-8CE4-75CAAD2887F8}" 26 | EndProject 27 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reactive.Streams.Example.Unicast", "examples\Reactive.Streams.Example.Unicast\Reactive.Streams.Example.Unicast.csproj", "{01737D0D-ED40-499B-A706-12BE9847491B}" 28 | EndProject 29 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reactive.Streams.Example.Unicast.Tests", "examples\Reactive.Streams.Example.Unicast.Tests\Reactive.Streams.Example.Unicast.Tests.csproj", "{A69C5115-5E39-47ED-B8EF-83A342968999}" 30 | EndProject 31 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "api", "api", "{47E4C672-A6E8-43FE-A03E-761BC4601F84}" 32 | EndProject 33 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tck", "tck", "{755FE80E-BE8C-4FC3-8ACD-CCB8338889D2}" 34 | EndProject 35 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{6CCDDC32-BD9D-4767-BF9A-64275D330558}" 36 | EndProject 37 | Global 38 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 39 | Debug|Any CPU = Debug|Any CPU 40 | Release|Any CPU = Release|Any CPU 41 | EndGlobalSection 42 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 43 | {68FBB4DF-6D83-4CF1-8105-A1D41912852F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {68FBB4DF-6D83-4CF1-8105-A1D41912852F}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {68FBB4DF-6D83-4CF1-8105-A1D41912852F}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {68FBB4DF-6D83-4CF1-8105-A1D41912852F}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {7DBE1261-04EB-4DA7-9953-C8E8CCC49010}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {7DBE1261-04EB-4DA7-9953-C8E8CCC49010}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {7DBE1261-04EB-4DA7-9953-C8E8CCC49010}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {7DBE1261-04EB-4DA7-9953-C8E8CCC49010}.Release|Any CPU.Build.0 = Release|Any CPU 51 | {0F1C51EB-3554-487F-8CE4-75CAAD2887F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 52 | {0F1C51EB-3554-487F-8CE4-75CAAD2887F8}.Debug|Any CPU.Build.0 = Debug|Any CPU 53 | {0F1C51EB-3554-487F-8CE4-75CAAD2887F8}.Release|Any CPU.ActiveCfg = Release|Any CPU 54 | {0F1C51EB-3554-487F-8CE4-75CAAD2887F8}.Release|Any CPU.Build.0 = Release|Any CPU 55 | {01737D0D-ED40-499B-A706-12BE9847491B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 56 | {01737D0D-ED40-499B-A706-12BE9847491B}.Debug|Any CPU.Build.0 = Debug|Any CPU 57 | {01737D0D-ED40-499B-A706-12BE9847491B}.Release|Any CPU.ActiveCfg = Release|Any CPU 58 | {01737D0D-ED40-499B-A706-12BE9847491B}.Release|Any CPU.Build.0 = Release|Any CPU 59 | {A69C5115-5E39-47ED-B8EF-83A342968999}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 60 | {A69C5115-5E39-47ED-B8EF-83A342968999}.Debug|Any CPU.Build.0 = Debug|Any CPU 61 | {A69C5115-5E39-47ED-B8EF-83A342968999}.Release|Any CPU.ActiveCfg = Release|Any CPU 62 | {A69C5115-5E39-47ED-B8EF-83A342968999}.Release|Any CPU.Build.0 = Release|Any CPU 63 | EndGlobalSection 64 | GlobalSection(SolutionProperties) = preSolution 65 | HideSolutionNode = FALSE 66 | EndGlobalSection 67 | GlobalSection(NestedProjects) = preSolution 68 | {68FBB4DF-6D83-4CF1-8105-A1D41912852F} = {47E4C672-A6E8-43FE-A03E-761BC4601F84} 69 | {7DBE1261-04EB-4DA7-9953-C8E8CCC49010} = {755FE80E-BE8C-4FC3-8ACD-CCB8338889D2} 70 | {0F1C51EB-3554-487F-8CE4-75CAAD2887F8} = {755FE80E-BE8C-4FC3-8ACD-CCB8338889D2} 71 | {01737D0D-ED40-499B-A706-12BE9847491B} = {6CCDDC32-BD9D-4767-BF9A-64275D330558} 72 | {A69C5115-5E39-47ED-B8EF-83A342968999} = {6CCDDC32-BD9D-4767-BF9A-64275D330558} 73 | EndGlobalSection 74 | EndGlobal 75 | -------------------------------------------------------------------------------- /src/SharedAssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System.Reflection; 3 | 4 | [assembly: AssemblyCompanyAttribute("Reactive Streams")] 5 | [assembly: AssemblyCopyrightAttribute("MIT-0")] 6 | [assembly: AssemblyTrademarkAttribute("")] 7 | [assembly: AssemblyVersionAttribute("1.0.0.0")] 8 | [assembly: AssemblyFileVersionAttribute("1.0.0.0")] 9 | -------------------------------------------------------------------------------- /src/api/Reactive.Streams/IProcessor.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | namespace Reactive.Streams 5 | { 6 | /// 7 | /// A Processor represents a processing stage—which is both a 8 | /// and a and obeys the contracts of both. 9 | /// 10 | /// The type of element signaled to the 11 | /// The type of element signaled to the 12 | public interface IProcessor : ISubscriber, IPublisher 13 | { 14 | } 15 | } -------------------------------------------------------------------------------- /src/api/Reactive.Streams/IPublisher.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | namespace Reactive.Streams 5 | { 6 | /// 7 | /// 8 | /// A is a provider of a potentially unbounded number of sequenced elements, 9 | /// publishing them according to the demand received from its . 10 | /// 11 | /// 12 | /// A can serve multiple s subscribed dynamically 13 | /// at various points in time. 14 | /// 15 | /// 16 | /// The type of element signaled. 17 | public interface IPublisher 18 | { 19 | /// 20 | /// 21 | /// Request to start streaming data. 22 | /// 23 | /// 24 | /// This is a "factory method" and can be called multiple times, each time starting a new 25 | /// . 26 | /// 27 | /// 28 | /// Each will work for only a single . 29 | /// 30 | /// 31 | /// A should only subscribe once to a single 32 | /// . 33 | /// 34 | /// 35 | /// If the rejects the subscription attempt or otherwise fails 36 | /// it will signal the error via . 37 | /// 38 | /// 39 | /// The that will consume signals 40 | /// from this 41 | void Subscribe(ISubscriber subscriber); 42 | } 43 | } -------------------------------------------------------------------------------- /src/api/Reactive.Streams/ISubscriber.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System; 5 | 6 | namespace Reactive.Streams 7 | { 8 | /// 9 | /// 10 | /// Will receive call to once after passing an instance of 11 | /// to . 12 | /// 13 | /// 14 | /// No further notifications will be received until is called. 15 | /// 16 | /// After signaling demand: 17 | /// 1. One or more invocations of up to the maximum number defined by 18 | /// 19 | /// 2. Single invocation of or 20 | /// which signals a terminal state after which no further 21 | /// events will be sent. 22 | /// 23 | /// Demand can be signaled via whenever the 24 | /// instance is capable of handling more. 25 | /// 26 | /// The type of element signaled. 27 | public interface ISubscriber 28 | { 29 | /// 30 | /// 31 | /// Invoked after calling . 32 | /// 33 | /// 34 | /// No data will start flowing until is invoked. 35 | /// 36 | /// 37 | /// It is the responsibility of this instance to call 38 | /// whenever more data is wanted. 39 | /// 40 | /// 41 | /// The will send notifications only in response to 42 | /// . 43 | /// 44 | /// 45 | /// that allows requesting data 46 | /// via 47 | void OnSubscribe(ISubscription subscription); 48 | 49 | /// 50 | /// Data notification sent by the in response to requests to 51 | /// . 52 | /// 53 | /// The element signaled 54 | void OnNext(T element); 55 | 56 | /// 57 | /// 58 | /// Failed terminal state. 59 | /// 60 | /// 61 | /// No further events will be sent even if is 62 | /// invoked again. 63 | /// 64 | /// 65 | /// The exception signaled 66 | void OnError(Exception cause); 67 | 68 | /// 69 | /// 70 | /// Successful terminal state. 71 | /// 72 | /// 73 | /// No further events will be sent even if is 74 | /// invoked again. 75 | /// 76 | /// 77 | void OnComplete(); 78 | } 79 | } -------------------------------------------------------------------------------- /src/api/Reactive.Streams/ISubscription.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | namespace Reactive.Streams 5 | { 6 | /// 7 | /// 8 | /// A represents a one-to-one lifecycle of a 9 | /// subscribing to a . 10 | /// 11 | /// 12 | /// It can only be used once by a single . 13 | /// 14 | /// 15 | /// It is used to both signal desire for data and cancel demand (and allow resource cleanup). 16 | /// 17 | /// 18 | public interface ISubscription 19 | { 20 | /// 21 | /// 22 | /// No events will be sent by a until demand is signaled via this method. 23 | /// 24 | /// 25 | /// It can be called however often and whenever needed—but the outstanding cumulative demand 26 | /// must never exceed . 27 | /// An outstanding cumulative demand of may be treated by the 28 | /// as "effectively unbounded". 29 | /// 30 | /// 31 | /// Whatever has been requested can be sent by the so only signal demand 32 | /// for what can be safely handled. 33 | /// 34 | /// 35 | /// A can send less than is requested if the stream ends but 36 | /// then must emit either or . 37 | /// 38 | /// 39 | /// The strictly positive number of elements to requests to the upstream 40 | /// 41 | void Request(long n); 42 | 43 | /// 44 | /// 45 | /// Request the to stop sending data and clean up resources. 46 | /// 47 | /// 48 | /// Data may still be sent to meet previously signalled demand after calling cancel. 49 | /// 50 | /// 51 | void Cancel(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/api/Reactive.Streams/Reactive.Streams.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Reactive.Streams 5 | Reactive Streams API 6 | MIT-0 7 | 1.0.3 8 | Reactive Streams 9 | netstandard1.0;net45 10 | reactive;stream 11 | https://github.com/reactive-streams/reactive-streams-dotnet 12 | http://creativecommons.org/publicdomain/zero/1.0/ 13 | 1.6.0 14 | true 15 | 16 | 17 | 18 | $(DefineConstants);RELEASE 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/examples/Reactive.Streams.Example.Unicast.Tests/AsyncSubscriberTest.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System; 5 | using System.Threading; 6 | using NUnit.Framework; 7 | using Reactive.Streams.TCK; 8 | using Reactive.Streams.TCK.Support; 9 | 10 | namespace Reactive.Streams.Example.Unicast.Tests 11 | { 12 | [TestFixture] 13 | public class AsyncSubscriberTest : SubscriberBlackboxVerification 14 | { 15 | public AsyncSubscriberTest() : base(new TestEnvironment()) 16 | { 17 | } 18 | 19 | public override int? CreateElement(int element) => element; 20 | 21 | public override ISubscriber CreateSubscriber() => new Suscriber(); 22 | 23 | private sealed class Suscriber : AsyncSubscriber 24 | { 25 | protected override bool WhenNext(int? element) => true; 26 | } 27 | 28 | [Test] 29 | public void TestAccumulation() 30 | { 31 | var i = new AtomicCounterLong(0); 32 | var latch = new CountdownEvent(1); 33 | var subscriber = new AccSubscriber(i, latch); 34 | new NumberIterablePublisher(0,10).Subscribe(subscriber); 35 | latch.Wait(TimeSpan.FromMilliseconds(Environment.DefaultTimeoutMilliseconds*10)); 36 | Assert.AreEqual(45, i.Current); 37 | } 38 | 39 | private sealed class AccSubscriber : AsyncSubscriber 40 | { 41 | private readonly AtomicCounterLong _counter; 42 | private readonly CountdownEvent _latch; 43 | private long _acc; 44 | 45 | public AccSubscriber(AtomicCounterLong counter, CountdownEvent latch) 46 | { 47 | _counter = counter; 48 | _latch = latch; 49 | } 50 | 51 | protected override bool WhenNext(int? element) 52 | { 53 | // no need for null check, OnNext handles this case 54 | _acc += element.Value; 55 | return true; 56 | } 57 | 58 | protected override void WhenComplete() 59 | { 60 | _counter.GetAndAdd(_acc); 61 | _latch.Signal(); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/examples/Reactive.Streams.Example.Unicast.Tests/IterablePublisherTest.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using NUnit.Framework; 8 | using Reactive.Streams.TCK; 9 | 10 | namespace Reactive.Streams.Example.Unicast.Tests 11 | { 12 | [TestFixture] 13 | public class IterablePublisherTest : PublisherVerification 14 | { 15 | public IterablePublisherTest() : base(new TestEnvironment()) 16 | { 17 | } 18 | 19 | public override IPublisher CreatePublisher(long elements) 20 | { 21 | Assert.LessOrEqual(elements, MaxElementsFromPublisher); 22 | return new NumberIterablePublisher(0, (int)elements); 23 | } 24 | 25 | public override IPublisher CreateFailedPublisher() => new FailedPublisher(); 26 | 27 | private sealed class FailedPublisher : AsyncIterablePublisher 28 | { 29 | public FailedPublisher() : base(new FailedEnumerable()) 30 | { 31 | } 32 | 33 | private sealed class FailedEnumerable : IEnumerable 34 | { 35 | public IEnumerator GetEnumerator() 36 | { 37 | throw new Exception("Error state signal!"); 38 | } 39 | 40 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 41 | } 42 | } 43 | 44 | public override long MaxElementsFromPublisher { get; } = int.MaxValue; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/examples/Reactive.Streams.Example.Unicast.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System.Reflection; 5 | using System.Runtime.InteropServices; 6 | 7 | // Allgemeine Informationen über eine Assembly werden über die folgenden 8 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, 9 | // die einer Assembly zugeordnet sind. 10 | 11 | // Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar 12 | // für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von 13 | // COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. 14 | [assembly: ComVisible(false)] 15 | 16 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird 17 | [assembly: Guid("a69c5115-5e39-47ed-b8ef-83a342968999")] 18 | 19 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: 20 | // 21 | // Hauptversion 22 | // Nebenversion 23 | // Buildnummer 24 | // Revision 25 | // 26 | // Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern 27 | // übernehmen, indem Sie "*" eingeben: 28 | // [assembly: AssemblyVersion("1.0.*")] 29 | -------------------------------------------------------------------------------- /src/examples/Reactive.Streams.Example.Unicast.Tests/Reactive.Streams.Example.Unicast.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Reactive.Streams.Example.Unicast.Tests 4 | net45 5 | win7-x64 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | $(DefineConstants);RELEASE 22 | 23 | -------------------------------------------------------------------------------- /src/examples/Reactive.Streams.Example.Unicast.Tests/SyncSubscriberTest.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System; 5 | using NUnit.Framework; 6 | using Reactive.Streams.TCK; 7 | 8 | namespace Reactive.Streams.Example.Unicast.Tests 9 | { 10 | [TestFixture] 11 | public class SyncSubscriberTest : SubscriberBlackboxVerification 12 | { 13 | public SyncSubscriberTest() : base(new TestEnvironment()) 14 | { 15 | } 16 | 17 | public override int? CreateElement(int element) => element; 18 | 19 | public override ISubscriber CreateSubscriber() => new Subscriber(); 20 | 21 | private sealed class Subscriber : SyncSubscriber 22 | { 23 | private long _acc; 24 | 25 | protected override bool WhenNext(int? element) 26 | { 27 | _acc += element.Value; 28 | return true; 29 | } 30 | 31 | public override void OnComplete() => Console.WriteLine("Accumulated: " + _acc); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/examples/Reactive.Streams.Example.Unicast.Tests/SyncSubscriberWhiteboxTest.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System; 5 | using NUnit.Framework; 6 | using Reactive.Streams.TCK; 7 | 8 | namespace Reactive.Streams.Example.Unicast.Tests 9 | { 10 | [TestFixture] 11 | public class ValueTypeSyncSubscriberWhiteboxTest : SubscriberWhiteboxVerification 12 | { 13 | public ValueTypeSyncSubscriberWhiteboxTest() : base(new TestEnvironment()) 14 | { 15 | } 16 | 17 | public override int CreateElement(int element) => element; 18 | 19 | public override ISubscriber CreateSubscriber(WhiteboxSubscriberProbe probe) => new Subscriber(probe); 20 | 21 | private sealed class Subscriber : SyncSubscriber 22 | { 23 | private readonly WhiteboxSubscriberProbe _probe; 24 | 25 | public Subscriber(WhiteboxSubscriberProbe probe) 26 | { 27 | _probe = probe; 28 | } 29 | 30 | public override void OnSubscribe(ISubscription subscription) 31 | { 32 | base.OnSubscribe(subscription); 33 | 34 | _probe.RegisterOnSubscribe(new SubscriberPuppet(subscription)); 35 | } 36 | 37 | private sealed class SubscriberPuppet : ISubscriberPuppet 38 | { 39 | private readonly ISubscription _subscription; 40 | 41 | public SubscriberPuppet(ISubscription subscription) 42 | { 43 | _subscription = subscription; 44 | } 45 | 46 | public void TriggerRequest(long elements) => _subscription.Request(elements); 47 | 48 | public void SignalCancel() => _subscription.Cancel(); 49 | } 50 | 51 | public override void OnNext(int element) 52 | { 53 | base.OnNext(element); 54 | _probe.RegisterOnNext(element); 55 | } 56 | 57 | protected override bool WhenNext(int element) => true; 58 | 59 | public override void OnError(Exception cause) 60 | { 61 | base.OnError(cause); 62 | _probe.RegisterOnError(cause); 63 | } 64 | 65 | public override void OnComplete() 66 | { 67 | base.OnComplete(); 68 | _probe.RegisterOnComplete(); 69 | } 70 | } 71 | } 72 | 73 | [TestFixture] 74 | public class NullableSyncSubscriberWhiteboxTest : SubscriberWhiteboxVerification 75 | { 76 | public NullableSyncSubscriberWhiteboxTest() : base(new TestEnvironment()) 77 | { 78 | } 79 | 80 | public override int? CreateElement(int element) => element; 81 | 82 | public override ISubscriber CreateSubscriber(WhiteboxSubscriberProbe probe) => new Subscriber(probe); 83 | 84 | private sealed class Subscriber : SyncSubscriber 85 | { 86 | private readonly WhiteboxSubscriberProbe _probe; 87 | 88 | public Subscriber(WhiteboxSubscriberProbe probe) 89 | { 90 | _probe = probe; 91 | } 92 | 93 | public override void OnSubscribe(ISubscription subscription) 94 | { 95 | base.OnSubscribe(subscription); 96 | 97 | _probe.RegisterOnSubscribe(new SubscriberPuppet(subscription)); 98 | } 99 | 100 | private sealed class SubscriberPuppet : ISubscriberPuppet 101 | { 102 | private readonly ISubscription _subscription; 103 | 104 | public SubscriberPuppet(ISubscription subscription) 105 | { 106 | _subscription = subscription; 107 | } 108 | 109 | public void TriggerRequest(long elements) => _subscription.Request(elements); 110 | 111 | public void SignalCancel() => _subscription.Cancel(); 112 | } 113 | 114 | public override void OnNext(int? element) 115 | { 116 | base.OnNext(element); 117 | _probe.RegisterOnNext(element); 118 | } 119 | 120 | protected override bool WhenNext(int? element) => true; 121 | 122 | public override void OnError(Exception cause) 123 | { 124 | base.OnError(cause); 125 | _probe.RegisterOnError(cause); 126 | } 127 | 128 | public override void OnComplete() 129 | { 130 | base.OnComplete(); 131 | _probe.RegisterOnComplete(); 132 | } 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/examples/Reactive.Streams.Example.Unicast.Tests/UnboundedIntegerIncrementPublisherTest.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using NUnit.Framework; 8 | using Reactive.Streams.TCK; 9 | 10 | namespace Reactive.Streams.Example.Unicast.Tests 11 | { 12 | [TestFixture] 13 | public class UnboundedIntegerIncrementPublisherTest : PublisherVerification 14 | { 15 | public UnboundedIntegerIncrementPublisherTest() : base(new TestEnvironment()) 16 | { 17 | } 18 | 19 | public override IPublisher CreatePublisher(long elements) => new InfiniteIncrementNumberPublisher(); 20 | 21 | 22 | public override IPublisher CreateFailedPublisher() => new FailedPublisher(); 23 | 24 | private sealed class FailedPublisher : AsyncIterablePublisher 25 | { 26 | public FailedPublisher() : base(new FailedEnumerable()) 27 | { 28 | } 29 | 30 | private sealed class FailedEnumerable : IEnumerable 31 | { 32 | public IEnumerator GetEnumerator() 33 | { 34 | throw new Exception("Error state signal!"); 35 | } 36 | 37 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 38 | } 39 | } 40 | 41 | public override long MaxElementsFromPublisher => PublisherUnableToSignalOnComplete; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/examples/Reactive.Streams.Example.Unicast/AsyncIterablePublisher.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System; 5 | using System.Collections.Concurrent; 6 | using System.Collections.Generic; 7 | using System.Threading.Tasks; 8 | 9 | namespace Reactive.Streams.Example.Unicast 10 | { 11 | public class AsyncIterablePublisher : IPublisher 12 | { 13 | // These represent the protocol of the `AsyncIterablePublishers` SubscriptionImpls 14 | private interface ISignal { } 15 | 16 | private class CancelSignal : ISignal 17 | { 18 | public static CancelSignal Instance { get; } = new CancelSignal(); 19 | 20 | private CancelSignal() 21 | { 22 | 23 | } 24 | } 25 | 26 | private class SubscribeSignal : ISignal 27 | { 28 | public static SubscribeSignal Instance { get; } = new SubscribeSignal(); 29 | 30 | private SubscribeSignal() 31 | { 32 | 33 | } 34 | } 35 | 36 | private class SendSignal : ISignal 37 | { 38 | public static SendSignal Instance { get; } = new SendSignal(); 39 | 40 | private SendSignal() 41 | { 42 | 43 | } 44 | } 45 | 46 | private class RequestSignal : ISignal 47 | { 48 | public RequestSignal(long n) 49 | { 50 | N = n; 51 | } 52 | 53 | public long N { get; } 54 | } 55 | 56 | private const int DefaultBatchsize = 1024; 57 | 58 | private readonly IEnumerable _elements; // This is our data source / generator 59 | private readonly int _batchSize; // In general one should be nice nad not hog a thread for too long, this is the cap for that, in elements 60 | 61 | public AsyncIterablePublisher(IEnumerable elements) : this(elements, DefaultBatchsize) 62 | { 63 | } 64 | 65 | private AsyncIterablePublisher(IEnumerable elements, int batchSize) 66 | { 67 | if(elements == null) 68 | throw new ArgumentNullException(nameof(elements)); 69 | if (batchSize < 1) 70 | throw new ArgumentNullException(nameof(batchSize), "batchSize must be greater than zero!"); 71 | 72 | _elements = elements; 73 | _batchSize = batchSize; 74 | } 75 | 76 | 77 | public void Subscribe(ISubscriber subscriber) 78 | { 79 | // As per rule 1.11, we have decided to support multiple subscribers in a unicast configuration 80 | // for this `Publisher` implementation. 81 | // As per 2.13, this method must return normally (i.e. not throw) 82 | new SubscriptionImplementation(_elements, _batchSize, subscriber).Init(); 83 | } 84 | 85 | // This is our implementation of the Reactive Streams `Subscription`, 86 | // which represents the association between a `Publisher` and a `Subscriber`. 87 | private class SubscriptionImplementation : ISubscription 88 | { 89 | private readonly IEnumerable _elements; 90 | private readonly int _batchSize; 91 | private readonly ISubscriber _subscriber; // We need a reference to the `Subscriber` so we can talk to it 92 | private bool _cancelled; // This flag will track whether this `Subscription` is to be considered cancelled or not 93 | private long _demand; // Here we track the current demand, i.e. what has been requested but not yet delivered 94 | private IEnumerator _enumerator; // This is our cursor into the data stream, which we will send to the `Subscriber` 95 | 96 | // This `ConcurrentQueue` will track signals that are sent to this `Subscription`, like `request` and `cancel` 97 | private readonly ConcurrentQueue _inboundSignals = new ConcurrentQueue(); 98 | 99 | // We are using this `AtomicBoolean` to make sure that this `Subscription` doesn't run concurrently with itself, 100 | // which would violate rule 1.3 among others (no concurrent notifications). 101 | private readonly AtomicBoolean _on = new AtomicBoolean(); 102 | 103 | // This is the main "event loop" if you so will 104 | private readonly Action _run; 105 | 106 | public SubscriptionImplementation(IEnumerable elements, int batchSize, ISubscriber subscriber) 107 | { 108 | // As per rule 1.09, we need to throw a `ArgumentNullException` if the `Subscriber` is `null` 109 | if (subscriber == null) 110 | throw new ArgumentNullException(nameof(subscriber)); 111 | 112 | _elements = elements; 113 | _batchSize = batchSize; 114 | _subscriber = subscriber; 115 | 116 | // This is the main "event loop" if you so will 117 | _run = () => 118 | { 119 | if (_on.Value) // establishes a happens-before relationship with the end of the previous run 120 | { 121 | try 122 | { 123 | ISignal signal; 124 | if (_inboundSignals.TryDequeue(out signal) && !_cancelled) // to make sure that we follow rule 1.8, 3.6 and 3.7 125 | { 126 | // Below we simply unpack the `Signal`s and invoke the corresponding method 127 | if(signal is RequestSignal) 128 | DoRequest(((RequestSignal)signal).N); 129 | else if (signal == SendSignal.Instance) 130 | DoSend(); 131 | else if(signal == CancelSignal.Instance) 132 | DoCancel(); 133 | else if(signal == SubscribeSignal.Instance) 134 | DoSubscribe(); 135 | } 136 | } 137 | finally 138 | { 139 | _on.Value = false; // establishes a happens-before relationship with the beginning of the next run 140 | if (!_inboundSignals.IsEmpty) // If we still have signals to process 141 | TryScheduleToExecute(); 142 | } 143 | } 144 | }; 145 | } 146 | 147 | private void DoRequest(long n) 148 | { 149 | if (n < 1) 150 | TerminateDueTo(new ArgumentException(_subscriber +" violated the Reactive Streams rule 3.9 by requesting a non-positive number of elements")); 151 | else if (_demand + n < 1) 152 | { 153 | // As governed by rule 3.17, when demand overflows `long.MaxValue` we treat the signalled demand as "effectively unbounded" 154 | _demand = long.MaxValue; 155 | // Here we protect from the overflow and treat it as "effectively unbounded" 156 | DoSend(); // Then we proceed with sending data downstream 157 | } 158 | else 159 | { 160 | _demand += n; // Here we record the downstream demand 161 | DoSend(); // Then we can proceed with sending data downstream 162 | } 163 | } 164 | 165 | // This handles cancellation requests, and is idempotent, thread-safe and not synchronously performing heavy computations as specified in rule 3.5 166 | private void DoCancel() => _cancelled = true; 167 | 168 | // Instead of executing `subscriber.onSubscribe` synchronously from within `Publisher.subscribe` 169 | // we execute it asynchronously, this is to avoid executing the user code on the calling thread. 170 | // It also makes it easier to follow rule 1.9 171 | private void DoSubscribe() 172 | { 173 | try 174 | { 175 | _enumerator = _elements.GetEnumerator(); 176 | } 177 | catch (Exception ex) 178 | { 179 | // We need to make sure we signal onSubscribe before onError, obeying rule 1.9 180 | _subscriber.OnSubscribe(new DummySubscription()); 181 | TerminateDueTo(ex); // Here we send onError, obeying rule 1.09 182 | } 183 | 184 | if (!_cancelled) 185 | { 186 | // Deal with setting up the subscription with the subscriber 187 | try 188 | { 189 | _subscriber.OnSubscribe(this); 190 | } 191 | catch (Exception ex) // Due diligence to obey 2.13 192 | { 193 | TerminateDueTo(new IllegalStateException(_subscriber + " violated the Reactive Streams rule 2.13 by throwing an exception from onSubscribe.", ex)); 194 | } 195 | 196 | // Deal with already complete iterators promptly 197 | bool hasElements; 198 | try 199 | { 200 | hasElements = _enumerator.MoveNext(); 201 | } 202 | catch (Exception ex) 203 | { 204 | TerminateDueTo(ex); // If MoveNext throws, there's something wrong and we need to signal onError as per 1.2, 1.4, 205 | throw; 206 | } 207 | 208 | // If we don't have anything to deliver, we're already done, so lets do the right thing and 209 | // not wait for demand to deliver `onComplete` as per rule 1.2 and 1.3 210 | if (!hasElements) 211 | { 212 | try 213 | { 214 | DoCancel(); // Rule 1.6 says we need to consider the `Subscription` cancelled when `onComplete` is signalled 215 | _subscriber.OnComplete(); 216 | } 217 | catch (Exception ex) 218 | { 219 | // As per rule 2.13, `onComplete` is not allowed to throw exceptions, so we do what we can, and log this. 220 | System.Diagnostics.Trace.TraceError( 221 | new IllegalStateException( 222 | _subscriber + 223 | " violated the Reactive Streams rule 2.13 by throwing an exception from onComplete.", 224 | ex).StackTrace); 225 | } 226 | } 227 | } 228 | } 229 | 230 | 231 | private class DummySubscription : ISubscription 232 | { 233 | public void Request(long n) { } 234 | 235 | public void Cancel() { } 236 | } 237 | 238 | 239 | // This is our behavior for producing elements downstream 240 | private void DoSend() 241 | { 242 | try 243 | { 244 | // In order to play nice we will only send at-most `batchSize` before 245 | // rescheduing ourselves and relinquishing the current thread. 246 | 247 | var leftInBatch = _batchSize; 248 | 249 | do 250 | { 251 | T next; 252 | bool hasNext; 253 | try 254 | { 255 | next = _enumerator.Current; 256 | // We have already checked `MoveNext` when subscribing, so we can fall back to testing -after- `Current` is called. 257 | hasNext = _enumerator.MoveNext(); // Need to keep track of End-of-Stream 258 | } 259 | catch (Exception ex) 260 | { 261 | TerminateDueTo(ex); 262 | // If `Current` or `MoveNext` throws (they can, since it is user-provided), we need to treat the stream as errored as per rule 1.4 263 | return; 264 | } 265 | 266 | _subscriber.OnNext(next); // Then we signal the next element downstream to the `Subscriber` 267 | if (!hasNext) // If we are at End-of-Stream 268 | { 269 | DoCancel(); // We need to consider this `Subscription` as cancelled as per rule 1.6 270 | _subscriber.OnComplete(); // Then we signal `onComplete` as per rule 1.2 and 1.5 271 | } 272 | } while (!_cancelled && --leftInBatch > 0 && --_demand > 0); 273 | 274 | if (!_cancelled && _demand > 0) 275 | // If the `Subscription` is still alive and well, and we have demand to satisfy, we signal ourselves to send more data 276 | Signal(SendSignal.Instance); 277 | } 278 | catch (Exception ex) 279 | { 280 | // We can only get here if `onNext` or `onComplete` threw, and they are not allowed to according to 2.13, so we can only cancel and log here. 281 | DoCancel(); 282 | System.Diagnostics.Trace.TraceError( 283 | new IllegalStateException( 284 | _subscriber + 285 | " violated the Reactive Streams rule 2.13 by throwing an exception from onNext or onComplete.", 286 | ex).StackTrace); 287 | } 288 | } 289 | 290 | // This is a helper method to ensure that we always `cancel` when we signal `onError` as per rule 1.6 291 | private void TerminateDueTo(Exception exception) 292 | { 293 | _cancelled = true; // When we signal onError, the subscription must be considered as cancelled, as per rule 1.6 294 | try 295 | { 296 | _subscriber.OnError(exception); // Then we signal the error downstream, to the `Subscriber` 297 | } 298 | catch (Exception ex) 299 | { 300 | // If `onError` throws an exception, this is a spec violation according to rule 1.9, and all we can do is to log it. 301 | System.Diagnostics.Trace.TraceError( 302 | new IllegalStateException( 303 | _subscriber + 304 | " violated the Reactive Streams rule 2.13 by throwing an exception from onError.", 305 | ex).StackTrace); 306 | } 307 | } 308 | 309 | // What `Signal` does is that it sends signals to the `Subscription` asynchronously 310 | private void Signal(ISignal signal) 311 | { 312 | _inboundSignals.Enqueue(signal); 313 | TryScheduleToExecute(); // Then we try to schedule it for execution, if it isn't already 314 | } 315 | 316 | // This method makes sure that this `Subscription` is only running on one Thread at a time, 317 | // this is important to make sure that we follow rule 1.3 318 | private void TryScheduleToExecute() 319 | { 320 | if (_on.CompareAndSet(false, true)) 321 | { 322 | try 323 | { 324 | Task.Run(_run); 325 | } 326 | catch (Exception ex) // If we can't run, we need to fail gracefully 327 | { 328 | if (!_cancelled) 329 | { 330 | DoCancel(); // First of all, this failure is not recoverable, so we need to follow rule 1.4 and 1.6 331 | try 332 | { 333 | TerminateDueTo(new IllegalStateException("Publisher terminated due to unavailable Executor.", ex)); 334 | } 335 | finally 336 | { 337 | ISignal tmp; 338 | while (!_inboundSignals.IsEmpty) 339 | _inboundSignals.TryDequeue(out tmp); // We're not going to need these anymore 340 | 341 | // This subscription is cancelled by now, but letting it become schedulable again means 342 | // that we can drain the inboundSignals queue if anything arrives after clearing 343 | _on.Value = false; 344 | } 345 | } 346 | } 347 | } 348 | } 349 | 350 | // Our implementation of `Subscription.cancel` sends a signal to the Subscription that the `Subscriber` is not interested in any more elements 351 | public void Cancel() => Signal(CancelSignal.Instance); 352 | 353 | // The reason for the `Init` method is that we want to ensure the `SubscriptionImpl` 354 | // is completely constructed before it is exposed to the thread pool, therefor this 355 | // method is only intended to be invoked once, and immediately after the constructor has 356 | // finished. 357 | public void Init() => Signal(SubscribeSignal.Instance); 358 | 359 | // Our implementation of `Subscription.request` sends a signal to the Subscription that more elements are in demand 360 | public void Request(long n) => Signal(new RequestSignal(n)); 361 | } 362 | } 363 | 364 | 365 | 366 | } 367 | -------------------------------------------------------------------------------- /src/examples/Reactive.Streams.Example.Unicast/AsyncSubscriber.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System; 5 | using System.Collections.Concurrent; 6 | using System.Threading.Tasks; 7 | 8 | namespace Reactive.Streams.Example.Unicast 9 | { 10 | /// 11 | /// AsyncSubscriber is an implementation of Reactive Streams `Subscriber`, 12 | /// it runs asynchronously, requests one element 13 | /// at a time, and invokes a user-defined method to process each element. 14 | /// 15 | /// NOTE: The code below uses a lot of try-catches to show the reader where exceptions can be expected, and where they are forbidden. 16 | /// 17 | public abstract class AsyncSubscriber : ISubscriber 18 | { 19 | // Signal represents the asynchronous protocol between the Publisher and Subscriber 20 | private interface ISignal { } 21 | 22 | private class OnCompleteSignal : ISignal 23 | { 24 | public static OnCompleteSignal Instance { get; } = new OnCompleteSignal(); 25 | 26 | private OnCompleteSignal() 27 | { 28 | 29 | } 30 | } 31 | 32 | private sealed class OnErrorSignal : ISignal 33 | { 34 | public OnErrorSignal(Exception cause) 35 | { 36 | Cause = cause; 37 | } 38 | 39 | public Exception Cause { get; } 40 | } 41 | 42 | private sealed class OnNextSignal : ISignal 43 | { 44 | public OnNextSignal(T next) 45 | { 46 | Next = next; 47 | } 48 | 49 | public T Next { get; } 50 | } 51 | 52 | private sealed class OnSubscribeSignal : ISignal 53 | { 54 | public OnSubscribeSignal(ISubscription subscription) 55 | { 56 | Subscription = subscription; 57 | } 58 | 59 | public ISubscription Subscription { get; } 60 | } 61 | 62 | 63 | private ISubscription _subscription; // Obeying rule 3.1, we make this private! 64 | private bool _done; // It's useful to keep track of whether this Subscriber is done or not 65 | 66 | protected AsyncSubscriber() 67 | { 68 | _run = () => 69 | { 70 | if (_on.Value) // establishes a happens-before relationship with the end of the previous run 71 | { 72 | try 73 | { 74 | ISignal signal; 75 | if (!_inboundSignals.TryDequeue(out signal)) // We take a signal off the queue 76 | return; 77 | 78 | if (!_done) // If we're done, we shouldn't process any more signals, obeying rule 2.8 79 | { 80 | // Below we simply unpack the `Signal`s and invoke the corresponding methods 81 | var next = signal as OnNextSignal; 82 | if (next != null) 83 | { 84 | HandleOnNext(next.Next); 85 | return; 86 | } 87 | 88 | var subscribe = signal as OnSubscribeSignal; 89 | if (subscribe != null) 90 | { 91 | HandleOnSubscribe(subscribe.Subscription); 92 | return; 93 | } 94 | 95 | var error = signal as OnErrorSignal; 96 | if (error != null) // We are always able to handle OnError, obeying rule 2.10 97 | { 98 | HandleOnError(error.Cause); 99 | return; 100 | } 101 | 102 | if(signal == OnCompleteSignal.Instance) // We are always able to handle OnComplete, obeying rule 2.9 103 | HandleOnComplete(); 104 | } 105 | } 106 | finally 107 | { 108 | _on.Value = false; // establishes a happens-before relationship with the beginning of the next run 109 | if (!_inboundSignals.IsEmpty) // If we still have signals to process 110 | TryScheduleToExecute(); // Then we try to schedule ourselves to execute again 111 | } 112 | } 113 | }; 114 | } 115 | 116 | /// 117 | /// Showcases a convenience method to idempotently marking the Subscriber as "done", so we don't want to process more elements 118 | /// herefor we also need to cancel our `Subscription`. 119 | /// 120 | private void Done() 121 | { 122 | //On this line we could add a guard against `!done`, but since rule 3.7 says that `Subscription.cancel()` is idempotent, we don't need to. 123 | _done = true; // If `whenNext` throws an exception, let's consider ourselves done (not accepting more elements) 124 | if (_subscription != null) // If we are bailing out before we got a `Subscription` there's little need for cancelling it. 125 | { 126 | try 127 | { 128 | _subscription.Cancel(); // Cancel the subscription 129 | } 130 | catch (Exception ex) 131 | { 132 | //Subscription.cancel is not allowed to throw an exception, according to rule 3.15 133 | System.Diagnostics.Trace.TraceError( 134 | new IllegalStateException( 135 | _subscription + 136 | " violated the Reactive Streams rule 3.15 by throwing an exception from cancel.", 137 | ex).StackTrace); 138 | } 139 | } 140 | } 141 | 142 | /// 143 | /// This method is invoked when the OnNext signals arrive 144 | /// Returns whether more elements are desired or not, and if no more elements are desired, for convenience. 145 | /// 146 | protected abstract bool WhenNext(T element); 147 | 148 | /// 149 | /// This method is invoked when the OnComplete signal arrives 150 | /// override this method to implement your own custom onComplete logic. 151 | /// 152 | protected virtual void WhenComplete() { } 153 | 154 | /// 155 | /// This method is invoked if the OnError signal arrives 156 | /// override this method to implement your own custom onError logic. 157 | /// 158 | protected virtual void WhenError(Exception cause) { } 159 | 160 | private void HandleOnSubscribe(ISubscription subscription) 161 | { 162 | if (subscription == null) 163 | { 164 | // Getting a null `Subscription` here is not valid so lets just ignore it. 165 | } 166 | else if (_subscription != null) 167 | // If someone has made a mistake and added this Subscriber multiple times, let's handle it gracefully 168 | { 169 | try 170 | { 171 | subscription.Cancel(); // Cancel the additional subscription to follow rule 2.5 172 | } 173 | catch (Exception ex) 174 | { 175 | //Subscription.cancel is not allowed to throw an exception, according to rule 3.15 176 | System.Diagnostics.Trace.TraceError( 177 | new IllegalStateException( 178 | _subscription + 179 | " violated the Reactive Streams rule 3.15 by throwing an exception from cancel.", 180 | ex).StackTrace); 181 | } 182 | } 183 | else 184 | { 185 | // We have to assign it locally before we use it, if we want to be a synchronous `Subscriber` 186 | // Because according to rule 3.10, the Subscription is allowed to call `onNext` synchronously from within `request` 187 | _subscription = subscription; 188 | 189 | try 190 | { 191 | // If we want elements, according to rule 2.1 we need to call `request` 192 | // And, according to rule 3.2 we are allowed to call this synchronously from within the `onSubscribe` method 193 | _subscription.Request(1); // Our Subscriber is unbuffered and modest, it requests one element at a time 194 | } 195 | catch (Exception ex) 196 | { 197 | // Subscription.request is not allowed to throw according to rule 3.16 198 | System.Diagnostics.Trace.TraceError( 199 | new IllegalStateException( 200 | _subscription + 201 | " violated the Reactive Streams rule 3.16 by throwing an exception from request.", 202 | ex).StackTrace); 203 | } 204 | } 205 | } 206 | 207 | private void HandleOnNext(T element) 208 | { 209 | if (!_done) // If we aren't already done 210 | { 211 | if (_subscription == null) // Technically this check is not needed, since we are expecting Publishers to conform to the spec 212 | { 213 | // Check for spec violation of 2.1 and 1.09 214 | System.Diagnostics.Trace.TraceError( 215 | new IllegalStateException( 216 | "Someone violated the Reactive Streams rule 1.09 and 2.1 by signalling OnNext before `Subscription.request`. (no Subscription)") 217 | .StackTrace); 218 | } 219 | else 220 | { 221 | try 222 | { 223 | if (WhenNext(element)) 224 | { 225 | try 226 | { 227 | _subscription.Request(1); // Our Subscriber is unbuffered and modest, it requests one element at a time 228 | } 229 | catch (Exception ex) 230 | { 231 | // Subscription.request is not allowed to throw according to rule 3.16 232 | System.Diagnostics.Trace.TraceError( 233 | new IllegalStateException( 234 | _subscription + 235 | " violated the Reactive Streams rule 3.16 by throwing an exception from request.", 236 | ex).StackTrace); 237 | } 238 | } 239 | else 240 | Done(); // This is legal according to rule 2.6 241 | } 242 | catch (Exception ex) 243 | { 244 | Done(); 245 | try 246 | { 247 | OnError(ex); 248 | } 249 | catch (Exception e) 250 | { 251 | //Subscriber.onError is not allowed to throw an exception, according to rule 2.13 252 | System.Diagnostics.Trace.TraceError( 253 | new IllegalStateException( 254 | this + 255 | " violated the Reactive Streams rule 2.13 by throwing an exception from onError.", 256 | e).StackTrace); 257 | } 258 | } 259 | } 260 | } 261 | } 262 | 263 | // Here it is important that we do not violate 2.2 and 2.3 by calling methods on the `Subscription` or `Publisher` 264 | private void HandleOnComplete() 265 | { 266 | if (_subscription == null) 267 | // Technically this check is not needed, since we are expecting Publishers to conform to the spec 268 | { 269 | // Publisher is not allowed to signal onComplete before onSubscribe according to rule 1.09 270 | System.Diagnostics.Trace.TraceError( 271 | new IllegalStateException( 272 | "Publisher violated the Reactive Streams rule 1.09 signalling onComplete prior to onSubscribe.") 273 | .StackTrace); 274 | } 275 | else 276 | { 277 | _done = true; // Obey rule 2.4 278 | WhenComplete(); 279 | } 280 | } 281 | 282 | // Here it is important that we do not violate 2.2 and 2.3 by calling methods on the `Subscription` or `Publisher` 283 | private void HandleOnError(Exception cause) 284 | { 285 | if (_subscription == null) 286 | // Technically this check is not needed, since we are expecting Publishers to conform to the spec 287 | { 288 | // Publisher is not allowed to signal onError before onSubscribe according to rule 1.09 289 | System.Diagnostics.Trace.TraceError( 290 | new IllegalStateException( 291 | "Publisher violated the Reactive Streams rule 1.09 signalling onError prior to onSubscribe.") 292 | .StackTrace); 293 | } 294 | else 295 | { 296 | _done = true; // Obey rule 2.4 297 | WhenError(cause); 298 | } 299 | } 300 | 301 | // We implement the OnX methods on `Subscriber` to send Signals that we will process asycnhronously, but only one at a time 302 | 303 | public void OnSubscribe(ISubscription subscription) 304 | { 305 | // As per rule 2.13, we need to throw a `ArgumentNullException` if the `Subscription` is `null` 306 | if (subscription == null) 307 | throw new ArgumentNullException(nameof(subscription)); 308 | 309 | Signal(new OnSubscribeSignal(subscription)); 310 | } 311 | 312 | public void OnNext(T element) 313 | { 314 | // As per rule 2.13, we need to throw a `ArgumentNullException` if the `element` is `null` 315 | if (element == null) 316 | throw new ArgumentNullException(nameof(element)); 317 | 318 | Signal(new OnNextSignal(element)); 319 | } 320 | 321 | public void OnError(Exception cause) 322 | { 323 | // As per rule 2.13, we need to throw a `ArgumentNullException` if the `element` is `null` 324 | if (cause == null) 325 | throw new ArgumentNullException(nameof(cause)); 326 | 327 | Signal(new OnErrorSignal(cause)); 328 | } 329 | 330 | public void OnComplete() => Signal(OnCompleteSignal.Instance); 331 | 332 | /// 333 | /// This `ConcurrentQueue` will track signals that are sent to this `Subscriber`, like `OnComplete` and `OnNext` , 334 | /// and obeying rule 2.11 335 | /// 336 | private readonly ConcurrentQueue _inboundSignals = new ConcurrentQueue(); 337 | 338 | /// 339 | /// We are using this `AtomicBoolean` to make sure that this `Subscriber` doesn't run concurrently with itself, 340 | /// obeying rule 2.7 and 2.11 341 | /// 342 | private readonly AtomicBoolean _on = new AtomicBoolean(); 343 | 344 | private readonly Action _run; 345 | 346 | // What `signal` does is that it sends signals to the `Subscription` asynchronously 347 | private void Signal(ISignal signal) 348 | { 349 | if(signal == null) 350 | throw new ArgumentNullException(nameof(signal)); 351 | 352 | _inboundSignals.Enqueue(signal); 353 | TryScheduleToExecute(); // Then we try to schedule it for execution, if it isn't already 354 | } 355 | 356 | // This method makes sure that this `Subscriber` is only executing on one Thread at a time 357 | private void TryScheduleToExecute() 358 | { 359 | if (_on.CompareAndSet(false, true)) 360 | { 361 | try 362 | { 363 | Task.Run(_run); 364 | } 365 | catch (Exception) // If we can't run , we need to fail gracefully and not violate rule 2.13 366 | { 367 | if (!_done) 368 | { 369 | try 370 | { 371 | Done(); // First of all, this failure is not recoverable, so we need to cancel our subscription 372 | } 373 | finally 374 | { 375 | ISignal tmp; 376 | while (!_inboundSignals.IsEmpty) // We're not going to need these anymore 377 | _inboundSignals.TryDequeue(out tmp); 378 | 379 | // This subscription is cancelled by now, but letting the Subscriber become schedulable again means 380 | // that we can drain the inboundSignals queue if anything arrives after clearing 381 | _on.Value = false; 382 | } 383 | } 384 | } 385 | } 386 | } 387 | } 388 | 389 | } 390 | -------------------------------------------------------------------------------- /src/examples/Reactive.Streams.Example.Unicast/AtomicBoolean.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System.Threading; 5 | 6 | namespace Reactive.Streams.Example.Unicast 7 | { 8 | /// 9 | /// Implementation of the java.concurrent.util.AtomicBoolean type. 10 | /// 11 | /// Uses internally to enforce ordering of writes 12 | /// without any explicit locking. .NET's strong memory on write guarantees might already enforce 13 | /// this ordering, but the addition of the MemoryBarrier guarantees it. 14 | /// 15 | public class AtomicBoolean 16 | { 17 | private const int FalseValue = 0; 18 | private const int TrueValue = 1; 19 | 20 | private int _value; 21 | /// 22 | /// Sets the initial value of this to . 23 | /// 24 | public AtomicBoolean(bool initialValue = false) 25 | { 26 | _value = initialValue ? TrueValue : FalseValue; 27 | } 28 | 29 | /// 30 | /// The current value of this 31 | /// 32 | public bool Value 33 | { 34 | get 35 | { 36 | Interlocked.MemoryBarrier(); 37 | return _value == TrueValue; 38 | } 39 | set 40 | { 41 | Interlocked.Exchange(ref _value, value ? TrueValue : FalseValue); 42 | } 43 | } 44 | 45 | /// 46 | /// If equals , then set the Value to 47 | /// . 48 | /// 49 | /// true if was set 50 | public bool CompareAndSet(bool expected, bool newValue) 51 | { 52 | var expectedInt = expected ? TrueValue : FalseValue; 53 | var newInt = newValue ? TrueValue : FalseValue; 54 | return Interlocked.CompareExchange(ref _value, newInt, expectedInt) == expectedInt; 55 | } 56 | 57 | #region Conversion operators 58 | 59 | /// 60 | /// Implicit conversion operator = automatically casts the to a 61 | /// 62 | public static implicit operator bool(AtomicBoolean boolean) => boolean.Value; 63 | 64 | /// 65 | /// Implicit conversion operator = allows us to cast any bool directly into a instance. 66 | /// 67 | /// 68 | /// 69 | public static implicit operator AtomicBoolean(bool value) => new AtomicBoolean(value); 70 | 71 | #endregion 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/examples/Reactive.Streams.Example.Unicast/IllegalStateException.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System; 5 | 6 | namespace Reactive.Streams.Example.Unicast 7 | { 8 | public class IllegalStateException : Exception 9 | { 10 | public IllegalStateException(string message, Exception innerException) : base(message, innerException) 11 | { 12 | 13 | } 14 | 15 | public IllegalStateException(string message): base(message) 16 | { 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/examples/Reactive.Streams.Example.Unicast/InfiniteIncrementNumberPublisher.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | namespace Reactive.Streams.Example.Unicast 8 | { 9 | public class InfiniteIncrementNumberPublisher : AsyncIterablePublisher 10 | { 11 | public InfiniteIncrementNumberPublisher() : base(new InfiniteEnumerable()) 12 | { 13 | 14 | } 15 | 16 | private sealed class InfiniteEnumerable : IEnumerable 17 | { 18 | public IEnumerator GetEnumerator() => new InfiniteEnumerator(); 19 | 20 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 21 | } 22 | 23 | private sealed class InfiniteEnumerator : IEnumerator 24 | { 25 | private int _at; 26 | 27 | public void Dispose() 28 | { 29 | } 30 | 31 | public bool MoveNext() 32 | { 33 | _at++; 34 | return true; 35 | } 36 | 37 | public void Reset() => _at = 0; 38 | 39 | public int? Current => _at; 40 | 41 | object IEnumerator.Current => Current; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/examples/Reactive.Streams.Example.Unicast/NumberIterablePublisher.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System.Linq; 5 | 6 | namespace Reactive.Streams.Example.Unicast 7 | { 8 | public class NumberIterablePublisher : AsyncIterablePublisher 9 | { 10 | public NumberIterablePublisher(int start, int count) : base(Enumerable.Range(start, count).Cast()) 11 | { 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/examples/Reactive.Streams.Example.Unicast/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System.Reflection; 5 | using System.Runtime.InteropServices; 6 | 7 | // Allgemeine Informationen über eine Assembly werden über die folgenden 8 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, 9 | // die einer Assembly zugeordnet sind. 10 | 11 | // Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar 12 | // für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von 13 | // COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. 14 | [assembly: ComVisible(false)] 15 | 16 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird 17 | [assembly: Guid("01737d0d-ed40-499b-a706-12be9847491b")] 18 | 19 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: 20 | // 21 | // Hauptversion 22 | // Nebenversion 23 | // Buildnummer 24 | // Revision 25 | // 26 | // Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern 27 | // übernehmen, indem Sie "*" eingeben: 28 | // [assembly: AssemblyVersion("1.0.*")] 29 | -------------------------------------------------------------------------------- /src/examples/Reactive.Streams.Example.Unicast/Reactive.Streams.Example.Unicast.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Reactive.Streams.Example.Unicast 5 | netstandard1.4;net45 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | $(DefineConstants);RELEASE 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/examples/Reactive.Streams.Example.Unicast/SyncSubscriber.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System; 5 | 6 | namespace Reactive.Streams.Example.Unicast 7 | { 8 | /// 9 | /// SyncSubscriber is an implementation of Reactive Streams `Subscriber`, 10 | /// it runs synchronously (on the Publisher's thread) and requests one element 11 | /// at a time and invokes a user-defined method to process each element. 12 | /// 13 | /// NOTE: The code below uses a lot of try-catches to show the reader where exceptions can be expected, and where they are forbidden. 14 | /// 15 | public abstract class SyncSubscriber : ISubscriber 16 | { 17 | private ISubscription _subscription; // Obeying rule 3.1, we make this private! 18 | private bool _done; 19 | 20 | 21 | public virtual void OnSubscribe(ISubscription subscription) 22 | { 23 | // As per rule 2.13, we need to throw a `ArgumentNullException` if the `Subscription` is `null` 24 | if (subscription == null) 25 | throw new ArgumentNullException(nameof(subscription)); 26 | 27 | if (_subscription != null) 28 | // If someone has made a mistake and added this Subscriber multiple times, let's handle it gracefully 29 | { 30 | try 31 | { 32 | subscription.Cancel(); 33 | } 34 | catch (Exception ex) 35 | { 36 | //Subscription.cancel is not allowed to throw an exception, according to rule 3.15 37 | System.Diagnostics.Trace.TraceError( 38 | new IllegalStateException( 39 | subscription + 40 | " violated the Reactive Streams rule 3.15 by throwing an exception from cancel.", 41 | ex).StackTrace); 42 | } 43 | } 44 | else 45 | { 46 | // We have to assign it locally before we use it, if we want to be a synchronous `Subscriber` 47 | // Because according to rule 3.10, the Subscription is allowed to call `onNext` synchronously from within `request` 48 | _subscription = subscription; 49 | try 50 | { 51 | // If we want elements, according to rule 2.1 we need to call `request` 52 | // And, according to rule 3.2 we are allowed to call this synchronously from within the `onSubscribe` method 53 | subscription.Request(1); // Our Subscriber is unbuffered and modest, it requests one element at a time 54 | } 55 | catch (Exception ex) 56 | { 57 | // Subscription.request is not allowed to throw according to rule 3.16 58 | System.Diagnostics.Trace.TraceError( 59 | new IllegalStateException( 60 | subscription + 61 | " violated the Reactive Streams rule 3.16 by throwing an exception from request.", 62 | ex).StackTrace); 63 | } 64 | } 65 | } 66 | 67 | public virtual void OnNext(T element) 68 | { 69 | if (_subscription == null) 70 | // Technically this check is not needed, since we are expecting Publishers to conform to the spec 71 | System.Diagnostics.Trace.TraceError( 72 | new IllegalStateException( 73 | "Publisher violated the Reactive Streams rule 1.09 signalling onNext prior to onSubscribe." 74 | ).StackTrace); 75 | else 76 | { 77 | // As per rule 2.13, we need to throw a `ArgumentNullException` if the `element` is `null` 78 | if (element == null) 79 | throw new ArgumentNullException(nameof(element)); 80 | 81 | if (!_done) 82 | { 83 | try 84 | { 85 | if (WhenNext(element)) 86 | { 87 | try 88 | { 89 | _subscription.Request(1); 90 | // Our Subscriber is unbuffered and modest, it requests one element at a time 91 | } 92 | catch (Exception ex) 93 | { 94 | // Subscription.request is not allowed to throw according to rule 3.16 95 | System.Diagnostics.Trace.TraceError( 96 | new IllegalStateException( 97 | _subscription + 98 | " violated the Reactive Streams rule 3.16 by throwing an exception from request.", 99 | ex).StackTrace); 100 | } 101 | } 102 | else 103 | Done(); 104 | } 105 | catch (Exception ex) 106 | { 107 | Done(); 108 | try 109 | { 110 | OnError(ex); 111 | } 112 | catch (Exception) 113 | { 114 | //Subscriber.onError is not allowed to throw an exception, according to rule 2.13 115 | System.Diagnostics.Trace.TraceError( 116 | new IllegalStateException( 117 | this + 118 | " violated the Reactive Streams rule 2.13 by throwing an exception from onError.", 119 | ex).StackTrace); 120 | } 121 | } 122 | } 123 | } 124 | } 125 | 126 | /// 127 | /// Showcases a convenience method to idempotently marking the Subscriber as "done", so we don't want to process more elements 128 | /// herefor we also need to cancel our `Subscription`. 129 | /// 130 | private void Done() 131 | { 132 | //On this line we could add a guard against `!done`, but since rule 3.7 says that `Subscription.cancel()` is idempotent, we don't need to. 133 | _done = true; 134 | try 135 | { 136 | _subscription.Cancel(); // Cancel the subscription 137 | } 138 | catch (Exception ex) 139 | { 140 | //Subscription.cancel is not allowed to throw an exception, according to rule 3.15 141 | System.Diagnostics.Trace.TraceError( 142 | new IllegalStateException( 143 | _subscription + 144 | " violated the Reactive Streams rule 3.15 by throwing an exception from cancel.", 145 | ex).StackTrace); 146 | } 147 | } 148 | 149 | /// 150 | /// This method is left as an exercise to the reader/extension point 151 | /// 152 | /// whether more elements are desired or not, and if no more elements are desired 153 | protected abstract bool WhenNext(T element); 154 | 155 | public virtual void OnComplete() 156 | { 157 | if (_subscription == null) // Technically this check is not needed, since we are expecting Publishers to conform to the spec 158 | System.Diagnostics.Trace.TraceError( 159 | new IllegalStateException( 160 | "Publisher violated the Reactive Streams rule 1.09 signalling onNext prior to onSubscribe." 161 | ).StackTrace); 162 | else 163 | { 164 | // Here we are not allowed to call any methods on the `Subscription` or the `Publisher`, as per rule 2.3 165 | // And anyway, the `Subscription` is considered to be cancelled if this method gets called, as per rule 2.4 166 | } 167 | } 168 | 169 | public virtual void OnError(Exception cause) 170 | { 171 | if (_subscription == null) // Technically this check is not needed, since we are expecting Publishers to conform to the spec 172 | System.Diagnostics.Trace.TraceError( 173 | new IllegalStateException( 174 | "Publisher violated the Reactive Streams rule 1.09 signalling onNext prior to onSubscribe." 175 | ).StackTrace); 176 | else 177 | { 178 | // As per rule 2.13, we need to throw a `ArgumentNullException` if the `Throwable` is `null` 179 | if (cause == null) 180 | throw new ArgumentNullException(nameof(cause)); 181 | 182 | // Here we are not allowed to call any methods on the `Subscription` or the `Publisher`, as per rule 2.3 183 | // And anyway, the `Subscription` is considered to be cancelled if this method gets called, as per rule 2.4 184 | } 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/tck/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reactive-streams/reactive-streams-dotnet/c4d6d54226f74fca3349d34e2f0e77e1a828630a/src/tck/README.md -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK.Tests/EmptyLazyPublisherTest.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System.Linq; 5 | using NUnit.Framework; 6 | using Reactive.Streams.Example.Unicast; 7 | 8 | namespace Reactive.Streams.TCK.Tests 9 | { 10 | [TestFixture] 11 | public class EmptyLazyPublisherTest : PublisherVerification 12 | { 13 | public EmptyLazyPublisherTest() : base(new TestEnvironment()) 14 | { 15 | 16 | } 17 | 18 | public override IPublisher CreatePublisher(long elements) 19 | => new AsyncIterablePublisher(Enumerable.Empty()); 20 | 21 | public override IPublisher CreateFailedPublisher() => null; 22 | 23 | public override long MaxElementsFromPublisher { get; } = 0; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK.Tests/IdentityProcessorVerificationDelegationTest.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Text; 9 | using NUnit.Framework; 10 | 11 | namespace Reactive.Streams.TCK.Tests 12 | { 13 | /// 14 | /// The must also run all tests from 15 | /// and . 16 | /// 17 | /// Since in .Net this can be only achieved by delegating, we need to make sure we delegate to each of the tests, 18 | /// so that if in the future we add more tests to these verifications we're sure to not forget to add the delegating methods. 19 | /// 20 | public class IdentityProcessorVerificationDelegationTest 21 | { 22 | [Test] 23 | public void ShouldIncludeAllTestsFromPublisherVerification() 24 | { 25 | var processeroTests = GetTestNames(typeof(IdentityProcessorVerification<>)).ToList(); 26 | var publisherTests = GetTestNames(typeof(PublisherVerification<>)).ToList(); 27 | AssertSuiteDelegatedAllTests(typeof(IdentityProcessorVerification<>), processeroTests, 28 | typeof(PublisherVerification<>), publisherTests); 29 | } 30 | 31 | [Test] 32 | public void ShouldIncludeAllTestsFromSubscriberVerification() 33 | { 34 | var processeroTests = GetTestNames(typeof(IdentityProcessorVerification<>)).ToList(); 35 | var subscriberTests = GetTestNames(typeof(SubscriberWhiteboxVerification<>)).ToList(); 36 | AssertSuiteDelegatedAllTests(typeof(IdentityProcessorVerification<>), processeroTests, 37 | typeof(SubscriberWhiteboxVerification<>), subscriberTests); 38 | } 39 | 40 | private static void AssertSuiteDelegatedAllTests(Type delegatingFrom, IList allTests, Type targetClass, 41 | IList delegatedToTests) 42 | { 43 | foreach (var targetTest in delegatedToTests) 44 | { 45 | var message = new StringBuilder(); 46 | message.AppendLine($"Test '{targetTest}' in '{targetClass}' has not been properly delegated to in aggregate '{delegatingFrom}'!"); 47 | message.AppendLine($"You must delegate to this test from {delegatingFrom}, like this:"); 48 | message.AppendLine("[Test]"); 49 | message.AppendLine($"public void {targetTest} () => delegate{targetClass.Name}.{targetTest}();"); 50 | 51 | Assert.True(TestsInclude(allTests, targetTest), message.ToString()); 52 | } 53 | } 54 | 55 | private static bool TestsInclude(IList processorTests, string targetTest) 56 | => processorTests.Contains(targetTest); 57 | 58 | private static IEnumerable GetTestNames(Type type) 59 | => type.GetMethods() 60 | .Where(m => m.GetCustomAttribute() != null) 61 | .Select(m => m.Name); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK.Tests/IdentityProcessorVerificationTest.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System; 5 | using NUnit.Framework; 6 | using Reactive.Streams.TCK.Tests.Support; 7 | 8 | namespace Reactive.Streams.TCK.Tests 9 | { 10 | public class IdentityProcessorVerificationTest : TCKVerificationSupport 11 | { 12 | private static readonly long DefaultTimeoutMilliseconds = 13 | TestEnvironment.EnvironmentDefaultTimeoutMilliseconds(); 14 | private static readonly long DefaultNoSignalsTimeoutMilliseconds = 15 | TestEnvironment.EnvironmentDefaultNoSignalsTimeoutMilliseconds(); 16 | 17 | [Test] 18 | public void Required_spec104_mustCallOnErrorOnAllItsSubscribersIfItEncountersANonRecoverableError_shouldBeIgnored() 19 | { 20 | RequireTestSkip(() => 21 | { 22 | new Spec104IgnoreVerification(NewTestEnvironment()) 23 | .Required_spec104_mustCallOnErrorOnAllItsSubscribersIfItEncountersANonRecoverableError(); 24 | }, "The Publisher under test only supports 1 subscribers, while this test requires at least 2 to run"); 25 | } 26 | 27 | [TestFixture(Ignore = "Helper verification for single test")] 28 | private sealed class Spec104WaitingVerification : IdentityProcessorVerification 29 | { 30 | /// 31 | /// We need this constructor for NUnit even if the fixture is ignored 32 | /// 33 | public Spec104WaitingVerification() : base(new TestEnvironment()) 34 | { 35 | 36 | } 37 | 38 | private sealed class Processor : IProcessor 39 | { 40 | private sealed class Subscription : ISubscription 41 | { 42 | private readonly ISubscriber _subscriber; 43 | 44 | public Subscription(ISubscriber subscriber) 45 | { 46 | _subscriber = subscriber; 47 | } 48 | 49 | public void Request(long n) => _subscriber.OnNext(0); 50 | 51 | public void Cancel() 52 | { 53 | } 54 | } 55 | 56 | public void OnNext(int element) 57 | { 58 | // noop 59 | } 60 | 61 | public void OnSubscribe(ISubscription subscription) => subscription.Request(1); 62 | 63 | public void OnError(Exception cause) 64 | { 65 | // noop 66 | } 67 | 68 | public void OnComplete() 69 | { 70 | // noop 71 | } 72 | 73 | public void Subscribe(ISubscriber subscriber) 74 | => subscriber.OnSubscribe(new Subscription(subscriber)); 75 | } 76 | 77 | private sealed class Publisher : IPublisher 78 | { 79 | public void Subscribe(ISubscriber subscriber) 80 | { 81 | subscriber.OnSubscribe(new LamdaSubscription(onRequest: _ => 82 | { 83 | for (var i = 0; i < 10; i++) 84 | subscriber.OnNext(i); 85 | })); 86 | } 87 | } 88 | 89 | public Spec104WaitingVerification(TestEnvironment environment, long publisherReferenceGcTimeoutMillis) 90 | : base(environment, publisherReferenceGcTimeoutMillis) 91 | { 92 | } 93 | 94 | 95 | public override int CreateElement(int element) => element; 96 | 97 | public override IProcessor CreateIdentityProcessor(int bufferSize) => new Processor(); 98 | 99 | public override IPublisher CreateHelperPublisher(long elements) => new Publisher(); 100 | 101 | public override IPublisher CreateFailedPublisher() => null; 102 | } 103 | 104 | [Test] 105 | public void Required_spec104_mustCallOnErrorOnAllItsSubscribersIfItEncountersANonRecoverableError_shouldFailWhileWaitingForOnError() 106 | { 107 | RequireTestFailure(() => 108 | { 109 | new Spec104WaitingVerification(NewTestEnvironment(), DefaultTimeoutMilliseconds) 110 | .Required_spec104_mustCallOnErrorOnAllItsSubscribersIfItEncountersANonRecoverableError(); 111 | }, "Did not receive expected error on downstream within " + DefaultTimeoutMilliseconds); 112 | } 113 | 114 | [TestFixture(Ignore = "Helper verification for single test")] 115 | private sealed class Spec104IgnoreVerification : IdentityProcessorVerification 116 | { 117 | /// 118 | /// We need this constructor for NUnit even if the fixture is ignored 119 | /// 120 | public Spec104IgnoreVerification() : base(new TestEnvironment()) 121 | { 122 | 123 | } 124 | 125 | public Spec104IgnoreVerification(TestEnvironment environment) : base(environment) 126 | { 127 | } 128 | 129 | public override int CreateElement(int element) => element; 130 | 131 | public override IProcessor CreateIdentityProcessor(int bufferSize) => new NoopProcessor(); 132 | 133 | public override IPublisher CreateFailedPublisher() => null; 134 | 135 | public override IPublisher CreateHelperPublisher(long elements) => null; 136 | 137 | // can only support 1 subscribe => unable to run this test 138 | public override long MaxSupportedSubscribers { get; } = 1; 139 | } 140 | 141 | private static TestEnvironment NewTestEnvironment() 142 | => new TestEnvironment(DefaultTimeoutMilliseconds, DefaultNoSignalsTimeoutMilliseconds); 143 | 144 | 145 | // FAILING IMPLEMENTATIONS // 146 | 147 | private sealed class NoopProcessor : IProcessor 148 | { 149 | public void OnNext(int element) 150 | { 151 | // noop 152 | } 153 | 154 | public void OnSubscribe(ISubscription subscription) 155 | { 156 | // noop 157 | } 158 | 159 | public void OnError(Exception cause) 160 | { 161 | // noop 162 | } 163 | 164 | public void OnComplete() 165 | { 166 | // noop 167 | } 168 | 169 | public void Subscribe(ISubscriber subscriber) 170 | { 171 | // noop 172 | } 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System.Reflection; 5 | using System.Runtime.InteropServices; 6 | 7 | // Allgemeine Informationen über eine Assembly werden über die folgenden 8 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, 9 | // die einer Assembly zugeordnet sind. 10 | 11 | // Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar 12 | // für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von 13 | // COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. 14 | [assembly: ComVisible(false)] 15 | 16 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird 17 | [assembly: Guid("0f1c51eb-3554-487f-8ce4-75caad2887f8")] 18 | 19 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: 20 | // 21 | // Hauptversion 22 | // Nebenversion 23 | // Buildnummer 24 | // Revision 25 | // 26 | // Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern 27 | // übernehmen, indem Sie "*" eingeben: 28 | // [assembly: AssemblyVersion("1.0.*")] 29 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK.Tests/RangePublisherTest.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using NUnit.Framework; 5 | using System; 6 | using System.Collections.Concurrent; 7 | using System.Collections.Generic; 8 | using System.Diagnostics; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading; 12 | using System.Threading.Tasks; 13 | 14 | namespace Reactive.Streams.TCK.Tests 15 | { 16 | [TestFixture] 17 | public class RangePublisherTest : PublisherVerification 18 | { 19 | static readonly ConcurrentDictionary stacks = new ConcurrentDictionary(); 20 | 21 | static readonly ConcurrentDictionary states = new ConcurrentDictionary(); 22 | 23 | static int id; 24 | 25 | [TearDown] 26 | public void AfterTest() 27 | { 28 | bool fail = false; 29 | StringBuilder b = new StringBuilder(); 30 | foreach (var t in states) 31 | { 32 | if (!t.Value) 33 | { 34 | b.Append("\r\n-------------------------------"); 35 | 36 | b.Append("\r\nat ").Append(stacks[t.Key]); 37 | 38 | fail = true; 39 | } 40 | } 41 | states.Clear(); 42 | stacks.Clear(); 43 | if (fail) 44 | { 45 | throw new InvalidOperationException("Cancellations were missing:" + b); 46 | } 47 | } 48 | 49 | public RangePublisherTest() : base(new TestEnvironment()) 50 | { 51 | } 52 | 53 | public override IPublisher CreatePublisher(long elements) 54 | { 55 | return new RangePublisher(1, elements); 56 | } 57 | 58 | public override IPublisher CreateFailedPublisher() 59 | { 60 | return null; 61 | } 62 | 63 | internal sealed class RangePublisher : IPublisher 64 | { 65 | 66 | readonly string stacktrace; 67 | 68 | readonly long start; 69 | 70 | readonly long count; 71 | 72 | internal RangePublisher(long start, long count) 73 | { 74 | this.stacktrace = Environment.StackTrace; 75 | this.start = start; 76 | this.count = count; 77 | } 78 | 79 | public void Subscribe(ISubscriber s) 80 | { 81 | if (s == null) 82 | { 83 | throw new ArgumentNullException(); 84 | } 85 | 86 | int ids = Interlocked.Increment(ref id); 87 | 88 | RangeSubscription parent = new RangeSubscription(s, ids, start, start + count); 89 | stacks.AddOrUpdate(ids, (a) => stacktrace, (a, b) => stacktrace); 90 | states.AddOrUpdate(ids, (a) => false, (a, b) => false); 91 | s.OnSubscribe(parent); 92 | } 93 | 94 | sealed class RangeSubscription : ISubscription 95 | { 96 | 97 | readonly ISubscriber actual; 98 | 99 | readonly int ids; 100 | 101 | readonly long end; 102 | 103 | long index; 104 | 105 | volatile bool cancelled; 106 | 107 | long requested; 108 | 109 | internal RangeSubscription(ISubscriber actual, int ids, long start, long end) 110 | { 111 | this.actual = actual; 112 | this.ids = ids; 113 | this.index = start; 114 | this.end = end; 115 | } 116 | 117 | 118 | public void Request(long n) 119 | { 120 | if (!cancelled) 121 | { 122 | if (n <= 0L) 123 | { 124 | cancelled = true; 125 | states[ids] = true; 126 | actual.OnError(new ArgumentException("§3.9 violated")); 127 | return; 128 | } 129 | 130 | for (;;) 131 | { 132 | long r = Volatile.Read(ref requested); 133 | long u = r + n; 134 | if (u < 0L) 135 | { 136 | u = long.MaxValue; 137 | } 138 | if (Interlocked.CompareExchange(ref requested, u, r) == r) 139 | { 140 | if (r == 0) 141 | { 142 | break; 143 | } 144 | return; 145 | } 146 | } 147 | 148 | long idx = index; 149 | long f = end; 150 | 151 | for (;;) 152 | { 153 | long e = 0; 154 | while (e != n && idx != f) 155 | { 156 | if (cancelled) 157 | { 158 | return; 159 | } 160 | 161 | actual.OnNext((int)idx); 162 | 163 | idx++; 164 | e++; 165 | } 166 | 167 | if (idx == f) 168 | { 169 | if (!cancelled) 170 | { 171 | states[ids] = true; 172 | actual.OnComplete(); 173 | } 174 | return; 175 | } 176 | 177 | index = idx; 178 | n = Interlocked.Add(ref requested, -n); 179 | if (n == 0) 180 | { 181 | break; 182 | } 183 | } 184 | } 185 | } 186 | 187 | public void Cancel() 188 | { 189 | cancelled = true; 190 | states[ids] = true; 191 | } 192 | } 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK.Tests/Reactive.Streams.TCK.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Reactive.Streams.TCK.Tests 4 | net45 5 | win7-x64 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | $(DefineConstants);RELEASE 22 | 23 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK.Tests/SingleElementPublisherTest.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System.Linq; 5 | using NUnit.Framework; 6 | using Reactive.Streams.Example.Unicast; 7 | 8 | namespace Reactive.Streams.TCK.Tests 9 | { 10 | [TestFixture] 11 | public class SingleElementPublisherTest : PublisherVerification 12 | { 13 | public SingleElementPublisherTest() : base(new TestEnvironment()) 14 | { 15 | 16 | } 17 | 18 | public override IPublisher CreatePublisher(long elements) 19 | => new AsyncIterablePublisher(Enumerable.Repeat(1, 1)); 20 | 21 | public override IPublisher CreateFailedPublisher() => null; 22 | 23 | public override long MaxElementsFromPublisher { get; } = 1; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK.Tests/SubscriberBlackboxVerificationTest.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System; 5 | using NUnit.Framework; 6 | using Reactive.Streams.TCK.Tests.Support; 7 | 8 | namespace Reactive.Streams.TCK.Tests 9 | { 10 | /// 11 | /// Validates that the TCK's fails with nice human readable errors. 12 | /// >Important: Please note that all Publishers implemented in this file are *wrong*! 13 | /// 14 | [TestFixture] 15 | public class SubscriberBlackboxVerificationTest : TCKVerificationSupport 16 | { 17 | [Test] 18 | public void Required_spec201_blackbox_mustSignalDemandViaSubscriptionRequest_shouldFailBy_notGettingRequestCall() 19 | => RequireTestFailure( 20 | () => NoopSubscriberVerification().Required_spec201_blackbox_mustSignalDemandViaSubscriptionRequest(), 21 | "Did not receive expected `Request` call within"); 22 | 23 | [Test] 24 | public void Required_spec201_blackbox_mustSignalDemandViaSubscriptionRequest_shouldPass() 25 | => SimpleSubscriberVerification().Required_spec201_blackbox_mustSignalDemandViaSubscriptionRequest(); 26 | 27 | [Test] 28 | public void Required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_shouldFail_dueToCallingRequest() 29 | { 30 | ISubscription subscription = null; 31 | var subscriber = new LamdaSubscriber(onSubscribe: sub => subscription = sub, 32 | onComplete: () => subscription.Request(1)); 33 | var verification = CustomSubscriberVerification(subscriber); 34 | RequireTestFailure(() => verification.Required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete(), 35 | "Subscription.Request MUST NOT be called from Subscriber.OnComplete (Rule 2.3)!"); 36 | } 37 | 38 | [Test] 39 | public void Required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_shouldFail_dueToCallingCancel() 40 | { 41 | ISubscription subscription = null; 42 | var subscriber = new LamdaSubscriber(onSubscribe: sub => subscription = sub, 43 | onComplete: () => subscription.Cancel()); 44 | var verification = CustomSubscriberVerification(subscriber); 45 | RequireTestFailure(() => verification.Required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete(), 46 | "Subscription.Cancel MUST NOT be called from Subscriber.OnComplete (Rule 2.3)!"); 47 | } 48 | 49 | [Test] 50 | public void Required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError_shouldFail_dueToCallingRequest() 51 | { 52 | ISubscription subscription = null; 53 | var subscriber = new LamdaSubscriber(onSubscribe: sub => subscription = sub, 54 | onError: _ => subscription.Request(1)); 55 | var verification = CustomSubscriberVerification(subscriber); 56 | RequireTestFailure(() => verification.Required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError(), 57 | "Subscription.Request MUST NOT be called from Subscriber.OnError (Rule 2.3)!"); 58 | } 59 | 60 | [Test] 61 | public void Required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError_shouldFail_dueToCallingCancel() 62 | { 63 | ISubscription subscription = null; 64 | var subscriber = new LamdaSubscriber(onSubscribe: sub => subscription = sub, 65 | onError: _ => subscription.Cancel()); 66 | var verification = CustomSubscriberVerification(subscriber); 67 | RequireTestFailure(() => verification.Required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError(), 68 | "Subscription.Cancel MUST NOT be called from Subscriber.OnError (Rule 2.3)!"); 69 | } 70 | 71 | [Test] 72 | public void Required_spec205_blackbox_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal_shouldFail() 73 | { 74 | ISubscription subscription = null; 75 | var subscriber = new LamdaSubscriber(onSubscribe: sub => 76 | { 77 | subscription = sub; 78 | sub.Request(1); // this is wrong, as one should always check if should accept or reject the subscription 79 | }); 80 | var verification = CustomSubscriberVerification(subscriber); 81 | RequireTestFailure(() => verification.Required_spec205_blackbox_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal(), 82 | "illegally called `Subscription.Request(1)`"); 83 | } 84 | 85 | [Test] 86 | public void Required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall_shouldFail() 87 | => RequireTestFailure( 88 | () => CustomSubscriberVerification(new LamdaSubscriber()).Required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall(), 89 | "did not call `RegisterOnComplete()`"); 90 | 91 | [Test] 92 | public void Required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall_shouldPass_withNoopSubscriber() 93 | => CustomSubscriberVerification(new LamdaSubscriber()) 94 | .Required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall(); 95 | 96 | [Test] 97 | public void Required_spec210_blackbox_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall_shouldFail() 98 | { 99 | var subscriber = new LamdaSubscriber(onError: cause => 100 | { 101 | // this is wrong in many ways (incl. spec violation), but aims to simulate user code which "blows up" when handling the onError signal 102 | throw new Exception("Wrong, don't do this!", cause); // don't do this 103 | }); 104 | var verification = CustomSubscriberVerification(subscriber); 105 | RequireTestFailure(() => verification.Required_spec210_blackbox_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall(), 106 | "Test Exception: Boom!"); // checks that the expected exception was delivered to onError, we don't expect anyone to implement onError so weirdly 107 | } 108 | 109 | [Test] 110 | public void Required_spec213_blackbox_mustThrowNullPointerExceptionWhenParametersAreNull_mustFailOnIgnoredNull_onSubscribe() 111 | => RequireTestFailure( 112 | () => CustomSubscriberVerification(new LamdaSubscriber()).Required_spec213_blackbox_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull(), 113 | "OnSubscribe(null) did not throw ArgumentNullException"); 114 | 115 | [Test] 116 | public void Required_spec213_blackbox_mustThrowNullPointerExceptionWhenParametersAreNull_mustFailOnIgnoredNull_onNext() 117 | => RequireTestFailure( 118 | () => CustomSubscriberVerification(new LamdaSubscriber()).Required_spec213_blackbox_onNext_mustThrowNullPointerExceptionWhenParametersAreNull(), 119 | "OnNext(null) did not throw ArgumentNullException"); 120 | 121 | [Test] 122 | public void Required_spec213_blackbox_mustThrowNullPointerExceptionWhenParametersAreNull_mustIgnoreSpecForValueType_onNext() 123 | => RequireTestSkip( 124 | () => SimpleSubscriberVerification().Required_spec213_blackbox_onNext_mustThrowNullPointerExceptionWhenParametersAreNull(), 125 | "Can't verify behavior for value types"); 126 | 127 | [Test] 128 | public void Required_spec213_blackbox_mustThrowNullPointerExceptionWhenParametersAreNull_mustFailOnIgnoredNull_onError() 129 | => RequireTestFailure( 130 | () => CustomSubscriberVerification(new LamdaSubscriber()).Required_spec213_blackbox_onError_mustThrowNullPointerExceptionWhenParametersAreNull(), 131 | "OnError(null) did not throw ArgumentNullException"); 132 | 133 | 134 | // FAILING IMPLEMENTATIONS // 135 | 136 | /// 137 | /// Verification using a Subscriber that doesn't do anything on any of the callbacks 138 | /// 139 | private SubscriberBlackboxVerification NoopSubscriberVerification() 140 | => new NoopBlackboxVerification(new TestEnvironment()); 141 | 142 | [TestFixture(Ignore = "Helper verification for single test")] 143 | private sealed class NoopBlackboxVerification : SubscriberBlackboxVerification 144 | { 145 | //Requirement for NUnit even if the tests are ignored 146 | public NoopBlackboxVerification() : base(new TestEnvironment()) 147 | { 148 | 149 | } 150 | 151 | public NoopBlackboxVerification(TestEnvironment environment) : base(environment) 152 | { 153 | } 154 | 155 | public override int CreateElement(int element) => element; 156 | 157 | public override ISubscriber CreateSubscriber() => new LamdaSubscriber(); 158 | } 159 | 160 | /// 161 | /// Verification using a Subscriber that only calls 'Requests(1)' on 'OnSubscribe' and 'OnNext' 162 | /// 163 | private SubscriberBlackboxVerification SimpleSubscriberVerification() 164 | => new SimpleBlackboxVerification(new TestEnvironment()); 165 | 166 | [TestFixture(Ignore = "Helper verification for single test")] 167 | private sealed class SimpleBlackboxVerification : SubscriberBlackboxVerification 168 | { 169 | //Requirement for NUnit even if the tests are ignored 170 | public SimpleBlackboxVerification() : base(new TestEnvironment()) 171 | { 172 | 173 | } 174 | 175 | public SimpleBlackboxVerification(TestEnvironment environment) : base(environment) 176 | { 177 | } 178 | 179 | public override int CreateElement(int element) => element; 180 | 181 | public override ISubscriber CreateSubscriber() 182 | { 183 | ISubscription sub = null; 184 | return new LamdaSubscriber( 185 | onSubscribe: subscription => 186 | { 187 | sub = subscription; 188 | sub.Request(1); 189 | }, 190 | onNext: _ => sub.Request(1)); 191 | } 192 | } 193 | 194 | /// 195 | /// Custom Verification using given Subscriber 196 | /// 197 | private SubscriberBlackboxVerification CustomSubscriberVerification(ISubscriber subscriber) 198 | => new CustomBlackboxVerification(new TestEnvironment(), subscriber); 199 | 200 | [TestFixture(Ignore = "Helper verification for single test")] 201 | private sealed class CustomBlackboxVerification : SubscriberBlackboxVerification 202 | { 203 | private readonly ISubscriber _subscriber; 204 | 205 | //Requirement for NUnit even if the tests are ignored 206 | public CustomBlackboxVerification() : base(new TestEnvironment()) 207 | { 208 | 209 | } 210 | 211 | public CustomBlackboxVerification(TestEnvironment environment, ISubscriber subscriber) : base(environment) 212 | { 213 | _subscriber = subscriber; 214 | } 215 | 216 | public override int? CreateElement(int element) => element; 217 | 218 | public override ISubscriber CreateSubscriber() => _subscriber; 219 | } 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK.Tests/Support/LamdaPublisher.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System; 5 | 6 | namespace Reactive.Streams.TCK.Tests.Support 7 | { 8 | internal sealed class LamdaPublisher : IPublisher 9 | { 10 | private readonly Action> _onSubscribe; 11 | 12 | public LamdaPublisher(Action> onSubscribe) 13 | { 14 | _onSubscribe = onSubscribe; 15 | } 16 | 17 | public void Subscribe(ISubscriber subscriber) => _onSubscribe(subscriber); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK.Tests/Support/LamdaSubscriber.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System; 5 | using System.Runtime.CompilerServices; 6 | 7 | namespace Reactive.Streams.TCK.Tests.Support 8 | { 9 | internal sealed class LamdaSubscriber : ISubscriber 10 | { 11 | private readonly Action _onNext; 12 | private readonly Action _onSubscribe; 13 | private readonly Action _onError; 14 | private readonly Action _onComplete; 15 | 16 | public LamdaSubscriber(Action onNext = null, Action onSubscribe = null, 17 | Action onError = null, Action onComplete = null) 18 | { 19 | _onNext = onNext ?? (_ => { }); 20 | _onSubscribe = onSubscribe ?? (_ => { }); 21 | _onError = onError ?? (_ => { }); 22 | _onComplete = onComplete ?? (() => { }); 23 | } 24 | 25 | public void OnNext(T element) => _onNext(element); 26 | 27 | public void OnSubscribe(ISubscription subscription) => _onSubscribe(subscription); 28 | 29 | // Make sure we see the method in the stack trace in release mode 30 | [MethodImpl(MethodImplOptions.NoInlining)] 31 | public void OnError(Exception cause) => _onError(cause); 32 | 33 | // Make sure we see the method in the stack trace in release mode 34 | [MethodImpl(MethodImplOptions.NoInlining)] 35 | public void OnComplete() => _onComplete(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK.Tests/Support/LamdaSubscription.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System; 5 | 6 | namespace Reactive.Streams.TCK.Tests.Support 7 | { 8 | internal sealed class LamdaSubscription : ISubscription 9 | { 10 | private readonly Action _onRequest; 11 | private readonly Action _onCancel; 12 | 13 | public LamdaSubscription(Action onRequest = null, Action onCancel = null) 14 | { 15 | _onRequest = onRequest; 16 | _onCancel = onCancel; 17 | } 18 | 19 | public void Request(long n) => _onRequest?.Invoke(n); 20 | 21 | public void Cancel() => _onCancel?.Invoke(); 22 | } 23 | } -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK.Tests/Support/SyncTriggeredDemandSubscriber.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System; 5 | using Reactive.Streams.TCK.Support; 6 | 7 | namespace Reactive.Streams.TCK.Tests.Support 8 | { 9 | /// 10 | /// SyncTriggeredDemandSubscriber is an implementation of Reactive Streams `Subscriber`, 11 | /// it runs synchronously (on the Publisher's thread) and requests demand triggered from 12 | /// "the outside" using its `triggerDemand` method and from "the inside" using the return 13 | /// value of its user-defined `whenNext` method which is invoked to process each element. 14 | /// 15 | /// NOTE: The code below uses a lot of try-catches to show the reader where exceptions can be expected, and where they are forbidden. 16 | /// 17 | public abstract class SyncTriggeredDemandSubscriber : ISubscriber 18 | { 19 | private ISubscription _subscription; // Obeying rule 3.1, we make this private! 20 | private bool _done; 21 | 22 | public virtual void OnSubscribe(ISubscription subscription) 23 | { 24 | // As per rule 2.13, we need to throw a `ArgumentNullException` if the `Subscription` is `null` 25 | if(subscription == null) 26 | throw new ArgumentNullException(nameof(subscription)); 27 | 28 | if (_subscription != null) 29 | // If someone has made a mistake and added this Subscriber multiple times, let's handle it gracefully 30 | { 31 | try 32 | { 33 | subscription.Cancel(); // Cancel the additional subscription 34 | } 35 | catch (Exception ex) 36 | { 37 | //Subscription.cancel is not allowed to throw an exception, according to rule 3.15 38 | System.Diagnostics.Trace.TraceError( 39 | new IllegalStateException( 40 | subscription + 41 | " violated the Reactive Streams rule 3.15 by throwing an exception from cancel.", ex) 42 | .StackTrace); 43 | } 44 | } 45 | else 46 | // We have to assign it locally before we use it, if we want to be a synchronous `Subscriber` 47 | // Because according to rule 3.10, the Subscription is allowed to call `onNext` synchronously from within `request` 48 | _subscription = subscription; 49 | } 50 | 51 | /// 52 | /// Requests the provided number of elements from the `Subscription` of this `Subscriber`. 53 | /// NOTE: This makes no attempt at thread safety so only invoke it once from the outside to initiate the demand. 54 | /// 55 | /// `true` if successful and `false` if not (either due to no `Subscription` or due to exceptions thrown) 56 | public virtual bool TriggerDemand(long n) 57 | { 58 | if (_subscription == null) 59 | return false; 60 | try 61 | { 62 | _subscription.Request(n); 63 | } 64 | catch (Exception ex) 65 | { 66 | // Subscription.request is not allowed to throw according to rule 3.16 67 | System.Diagnostics.Trace.TraceError( 68 | new IllegalStateException( 69 | _subscription + 70 | " violated the Reactive Streams rule 3.15 by throwing an exception from request.", ex) 71 | .StackTrace); 72 | return false; 73 | } 74 | 75 | return true; 76 | } 77 | 78 | public virtual void OnNext(T element) 79 | { 80 | if (_subscription == null) 81 | // Technically this check is not needed, since we are expecting Publishers to conform to the spec 82 | System.Diagnostics.Trace.TraceError(Environment.StackTrace, 83 | new IllegalStateException( 84 | "Publisher violated the Reactive Streams rule 1.09 signalling onNext prior to onSubscribe.")); 85 | else 86 | { 87 | // As per rule 2.13, we need to throw a `ArgumentNullException` if the `element` is `null` 88 | if(element == null) 89 | throw new ArgumentNullException(nameof(element)); 90 | 91 | if (!_done) // If we aren't already done 92 | { 93 | try 94 | { 95 | var need = Foreach(element); 96 | if (need > 0) 97 | TriggerDemand(need); 98 | else if (need == 0) 99 | { 100 | 101 | } 102 | else 103 | Done(); 104 | } 105 | catch (Exception ex) 106 | { 107 | Done(); 108 | try 109 | { 110 | OnError(ex); 111 | } 112 | catch (Exception e) 113 | { 114 | //Subscriber.onError is not allowed to throw an exception, according to rule 2.13 115 | System.Diagnostics.Trace.TraceError( 116 | new IllegalStateException( 117 | this + 118 | " violated the Reactive Streams rule 2.13 by throwing an exception from onError.", e) 119 | .StackTrace); 120 | } 121 | } 122 | } 123 | } 124 | } 125 | 126 | /// 127 | /// Showcases a convenience method to idempotently marking the Subscriber as "done", so we don't want to process more elements 128 | /// herefor we also need to cancel our `Subscription`. 129 | /// 130 | private void Done() 131 | { 132 | //On this line we could add a guard against `!done`, but since rule 3.7 says that `Subscription.cancel()` is idempotent, we don't need to. 133 | _done = true; // If we `whenNext` throws an exception, let's consider ourselves done (not accepting more elements) 134 | try 135 | { 136 | _subscription.Cancel(); // Cancel the subscription 137 | } 138 | catch (Exception ex) 139 | { 140 | //Subscription.cancel is not allowed to throw an exception, according to rule 3.15 141 | System.Diagnostics.Trace.TraceError( 142 | new IllegalStateException( 143 | _subscription + 144 | " violated the Reactive Streams rule 3.15 by throwing an exception from cancel.", ex) 145 | .StackTrace); 146 | } 147 | } 148 | 149 | /// 150 | /// This method is left as an exercise to the reader/extension point 151 | /// Don't forget to call `TriggerDemand` at the end if you are interested in more data, 152 | /// a return value of lower than 0 indicates that the subscription should be cancelled, 153 | /// a value of 0 indicates that there is no current need, 154 | /// a value of greater than 0 indicates the current need. 155 | /// 156 | protected abstract long Foreach(T element); 157 | 158 | public virtual void OnError(Exception cause) 159 | { 160 | if (_subscription == null) 161 | // Technically this check is not needed, since we are expecting Publishers to conform to the spec 162 | System.Diagnostics.Trace.TraceError(Environment.StackTrace, 163 | new IllegalStateException( 164 | "Publisher violated the Reactive Streams rule 1.09 signalling onError prior to onSubscribe.")); 165 | else 166 | { 167 | // As per rule 2.13, we need to throw a `ArgumentNullException` if the `Throwable` is `null` 168 | if(cause == null) 169 | throw new ArgumentNullException(nameof(cause)); 170 | 171 | // Here we are not allowed to call any methods on the `Subscription` or the `Publisher`, as per rule 2.3 172 | // And anyway, the `Subscription` is considered to be cancelled if this method gets called, as per rule 2.4 173 | } 174 | } 175 | 176 | public virtual void OnComplete() 177 | { 178 | if (_subscription == null) 179 | // Technically this check is not needed, since we are expecting Publishers to conform to the spec 180 | System.Diagnostics.Trace.TraceError(Environment.StackTrace, 181 | new IllegalStateException( 182 | "Publisher violated the Reactive Streams rule 1.09 signalling onError prior to onSubscribe.")); 183 | else 184 | { 185 | // Here we are not allowed to call any methods on the `Subscription` or the `Publisher`, as per rule 2.3 186 | // And anyway, the `Subscription` is considered to be cancelled if this method gets called, as per rule 2.4 187 | } 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK.Tests/Support/TCKVerificationSupport.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System; 5 | using NUnit.Framework; 6 | using Reactive.Streams.TCK.Support; 7 | 8 | namespace Reactive.Streams.TCK.Tests.Support 9 | { 10 | /// 11 | /// Provides assertions to validate the TCK tests themselves, 12 | /// with the goal of guaranteeing proper error messages when an implementation does not pass a given TCK test. 13 | /// 14 | /// "Quis custodiet ipsos custodes?" -- Iuvenalis 15 | /// 16 | // ReSharper disable once InconsistentNaming 17 | public class TCKVerificationSupport 18 | { 19 | // INTERNAL ASSERTION METHODS // 20 | 21 | /// 22 | /// Runs given code block and expects it to fail with an "Expected onError" failure. 23 | /// Use this method to validate that TCK tests fail with meaningful errors instead of NullPointerExceptions etc. 24 | /// 25 | /// encapsulates test case which we expect to fail 26 | /// the exception failing the test (inside the run parameter) must contain this message part in one of it's causes 27 | public void RequireTestFailure(Action throwingRun, string messagePart) 28 | { 29 | try 30 | { 31 | throwingRun(); 32 | } 33 | catch (Exception ex) 34 | { 35 | if (FindDeepErrorMessage(ex, messagePart)) 36 | return; 37 | 38 | throw new Exception($"Expected TCK to fail with '... {messagePart} ...', " + 39 | $"yet `{ex.GetType().Name}({ex.Message})` was thrown and test would fail with not useful error message!", ex); 40 | } 41 | 42 | throw new Exception($"Expected TCK to fail with '... {messagePart} ...', " + 43 | "yet no exception was thrown and test would pass unexpectedly!"); 44 | } 45 | 46 | /// 47 | /// Runs given code block and expects it fail with an 48 | /// 49 | /// encapsulates test case which we expect to be skipped 50 | /// the exception failing the test (inside the run parameter) must contain this message part in one of it's causes 51 | public void RequireTestSkip(Action throwingRun, string messagePart) 52 | { 53 | try 54 | { 55 | throwingRun(); 56 | } 57 | catch (IgnoreException ignore) 58 | { 59 | if(ignore.Message.Contains(messagePart)) 60 | return; 61 | 62 | throw new Exception($"Expected TCK to skip this test with '... {messagePart} ...', " + 63 | $"yet it skipped with ({ignore.Message}) instead!", ignore); 64 | } 65 | catch (Exception ex) 66 | { 67 | throw new Exception( 68 | $"Expected TCK to skip this test, yet it threw {ex.GetType().Name}({ex.Message}) instead!", ex); 69 | } 70 | 71 | throw new Exception($"Expected TCK to fail with '... {messagePart} ...', " + 72 | "yet no exception was thrown and test would pass unexpectedly!"); 73 | } 74 | 75 | /// 76 | /// This publisher does NOT fulfil all Publisher spec requirements. 77 | /// It's just the bare minimum to enable this test to fail the Subscriber tests. 78 | /// 79 | public IPublisher NewSimpleIntsPublisher(long maxElementsToEmit) 80 | => new SimpleIntsPublisher(maxElementsToEmit); 81 | 82 | private sealed class SimpleIntsPublisher : IPublisher 83 | { 84 | private sealed class SimpleIntsSubscribtion : ISubscription 85 | { 86 | private readonly SimpleIntsPublisher _publisher; 87 | private readonly AtomicCounterLong _nums = new AtomicCounterLong(); 88 | private readonly AtomicBoolean _active = new AtomicBoolean(true); 89 | 90 | public SimpleIntsSubscribtion(SimpleIntsPublisher publisher) 91 | { 92 | _publisher = publisher; 93 | } 94 | 95 | public void Request(long n) 96 | { 97 | var thisDemand = n; 98 | while (_active.Value && thisDemand > 0 && _nums.Current < _publisher._maxElementsToEmit) 99 | { 100 | _publisher._subscriber.OnNext((int)_nums.GetAndIncrement()); 101 | thisDemand--; 102 | } 103 | 104 | if (_nums.Current == _publisher._maxElementsToEmit) 105 | _publisher._subscriber.OnComplete(); 106 | } 107 | 108 | public void Cancel() => _active.Value = false; 109 | } 110 | 111 | private ISubscriber _subscriber; 112 | private readonly long _maxElementsToEmit; 113 | 114 | public SimpleIntsPublisher(long maxElementsToEmit) 115 | { 116 | _maxElementsToEmit = maxElementsToEmit; 117 | } 118 | 119 | public void Subscribe(ISubscriber subscriber) 120 | { 121 | _subscriber = subscriber; 122 | subscriber.OnSubscribe(new SimpleIntsSubscribtion(this)); 123 | } 124 | } 125 | 126 | /// 127 | /// Looks for expected error message prefix inside of causes of thrown throwable. 128 | /// 129 | /// true if one of the causes indeed contains expected error, false otherwise 130 | public bool FindDeepErrorMessage(Exception exception, string messagePart) 131 | => FindDeepErrorMessage(exception, messagePart, 5); 132 | 133 | private bool FindDeepErrorMessage(Exception exception, string messagePart, int depth) 134 | { 135 | if (exception is NullReferenceException) 136 | { 137 | Assert.Fail($"{typeof(NullReferenceException).Name} was thrown, definitely not a helpful error!", 138 | exception); 139 | } 140 | if (exception == null || depth == 0) 141 | return false; 142 | 143 | var message = exception.Message; 144 | return message.Contains(messagePart) || 145 | FindDeepErrorMessage(exception.InnerException, messagePart, depth - 1); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK.Tests/SyncTriggeredDemandSubscriberTest.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using NUnit.Framework; 5 | using Reactive.Streams.TCK.Tests.Support; 6 | 7 | namespace Reactive.Streams.TCK.Tests 8 | { 9 | [TestFixture] 10 | public class SyncTriggeredDemandSubscriberTest : SubscriberBlackboxVerification 11 | { 12 | public SyncTriggeredDemandSubscriberTest() : base(new TestEnvironment()) 13 | { 14 | } 15 | 16 | public override void TriggerRequest(ISubscriber subscriber) 17 | => ((SyncTriggeredDemandSubscriber) subscriber).TriggerDemand(1); 18 | 19 | 20 | public override ISubscriber CreateSubscriber() => new Subscriber(); 21 | 22 | private sealed class Subscriber : SyncTriggeredDemandSubscriber 23 | { 24 | private long _acc; 25 | 26 | protected override long Foreach(int? element) 27 | { 28 | _acc += element.Value; 29 | return 1; 30 | } 31 | 32 | public override void OnComplete() 33 | { 34 | } 35 | } 36 | 37 | public override int? CreateElement(int element) => element; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK.Tests/SyncTriggeredDemandSubscriberWhiteboxTest.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System; 5 | using NUnit.Framework; 6 | using Reactive.Streams.TCK.Tests.Support; 7 | 8 | namespace Reactive.Streams.TCK.Tests 9 | { 10 | [TestFixture] 11 | public class SyncTriggeredDemandSubscriberWhiteboxTest : SubscriberWhiteboxVerification 12 | { 13 | public SyncTriggeredDemandSubscriberWhiteboxTest() : base(new TestEnvironment()) 14 | { 15 | } 16 | 17 | public override int? CreateElement(int element) => element; 18 | 19 | public override ISubscriber CreateSubscriber(WhiteboxSubscriberProbe probe) => new Subscriber(probe); 20 | 21 | private sealed class Subscriber : SyncTriggeredDemandSubscriber 22 | { 23 | private readonly WhiteboxSubscriberProbe _probe; 24 | 25 | public Subscriber(WhiteboxSubscriberProbe probe) 26 | { 27 | _probe = probe; 28 | } 29 | 30 | public override void OnSubscribe(ISubscription subscription) 31 | { 32 | base.OnSubscribe(subscription); 33 | 34 | _probe.RegisterOnSubscribe(new Puppet(subscription)); 35 | } 36 | 37 | private sealed class Puppet : ISubscriberPuppet 38 | { 39 | private readonly ISubscription _subscription; 40 | 41 | public Puppet(ISubscription subscription) 42 | { 43 | _subscription = subscription; 44 | } 45 | 46 | public void TriggerRequest(long elements) => _subscription.Request(elements); 47 | 48 | public void SignalCancel() => _subscription.Cancel(); 49 | } 50 | 51 | public override void OnNext(int? element) 52 | { 53 | base.OnNext(element); 54 | _probe.RegisterOnNext(element); 55 | } 56 | 57 | public override void OnError(Exception cause) 58 | { 59 | base.OnError(cause); 60 | _probe.RegisterOnError(cause); 61 | } 62 | 63 | public override void OnComplete() 64 | { 65 | base.OnComplete(); 66 | _probe.RegisterOnComplete(); 67 | } 68 | 69 | protected override long Foreach(int? element) => 1; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System.Reflection; 5 | using System.Runtime.InteropServices; 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | 11 | // Setting ComVisible to false makes the types in this assembly not visible 12 | // to COM components. If you need to access a type in this assembly from 13 | // COM, set the ComVisible attribute to true on that type. 14 | [assembly: ComVisible(false)] 15 | 16 | // The following GUID is for the ID of the typelib if this project is exposed to COM 17 | [assembly: Guid("7dbe1261-04eb-4da7-9953-c8e8ccc49010")] -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK/Reactive.Streams.TCK.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Reactive.Streams.TCK 5 | Reactive Streams Technology Compatibility Kit 6 | MIT-0 7 | 1.0.3 8 | Reactive Streams 9 | net45 10 | reactive;stream 11 | https://github.com/reactive-streams/reactive-streams-dotnet 12 | http://creativecommons.org/publicdomain/zero/1.0/ 13 | true 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | $(DefineConstants);RELEASE 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK/Reactive.Streams.TCK.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Reactive.Streams.TCK 5 | 1.0.3 6 | Reactive Streams 7 | Reactive Streams 8 | false 9 | http://creativecommons.org/publicdomain/zero/1.0/ 10 | https://github.com/reactive-streams/reactive-streams-dotnet 11 | Reactive Streams Technology Compatibility Kit 12 | MIT-0 13 | reactive stream 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK/Support/AtomicBoolean.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System.Threading; 5 | 6 | namespace Reactive.Streams.TCK.Support 7 | { 8 | /// 9 | /// Implementation of the java.concurrent.util.AtomicBoolean type. 10 | /// 11 | /// Uses internally to enforce ordering of writes 12 | /// without any explicit locking. .NET's strong memory on write guarantees might already enforce 13 | /// this ordering, but the addition of the MemoryBarrier guarantees it. 14 | /// 15 | public class AtomicBoolean 16 | { 17 | private const int FalseValue = 0; 18 | private const int TrueValue = 1; 19 | 20 | private int _value; 21 | /// 22 | /// Sets the initial value of this to . 23 | /// 24 | public AtomicBoolean(bool initialValue = false) 25 | { 26 | _value = initialValue ? TrueValue : FalseValue; 27 | } 28 | 29 | /// 30 | /// The current value of this 31 | /// 32 | public bool Value 33 | { 34 | get 35 | { 36 | Interlocked.MemoryBarrier(); 37 | return _value == TrueValue; 38 | } 39 | set 40 | { 41 | Interlocked.Exchange(ref _value, value ? TrueValue : FalseValue); 42 | } 43 | } 44 | 45 | /// 46 | /// If equals , then set the Value to 47 | /// . 48 | /// 49 | /// true if was set 50 | public bool CompareAndSet(bool expected, bool newValue) 51 | { 52 | var expectedInt = expected ? TrueValue : FalseValue; 53 | var newInt = newValue ? TrueValue : FalseValue; 54 | return Interlocked.CompareExchange(ref _value, newInt, expectedInt) == expectedInt; 55 | } 56 | 57 | #region Conversion operators 58 | 59 | /// 60 | /// Implicit conversion operator = automatically casts the to a 61 | /// 62 | public static implicit operator bool(AtomicBoolean boolean) => boolean.Value; 63 | 64 | /// 65 | /// Implicit conversion operator = allows us to cast any bool directly into a instance. 66 | /// 67 | /// 68 | /// 69 | public static implicit operator AtomicBoolean(bool value) => new AtomicBoolean(value); 70 | 71 | #endregion 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK/Support/AtomicCounter.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System.Threading; 5 | 6 | namespace Reactive.Streams.TCK.Support 7 | { 8 | /// 9 | /// An atomic 32 bit integer counter. 10 | /// 11 | public class AtomicCounter 12 | { 13 | /// 14 | /// Creates an instance of an AtomicCounter. 15 | /// 16 | /// The initial value of this counter. 17 | public AtomicCounter(int initialValue) 18 | { 19 | _value = initialValue; 20 | } 21 | 22 | /// 23 | /// Creates an instance of an AtomicCounter with a starting value of -1. 24 | /// 25 | public AtomicCounter() 26 | { 27 | _value = -1; 28 | } 29 | 30 | /// 31 | /// The current value of the atomic counter. 32 | /// 33 | private int _value; 34 | 35 | /// 36 | /// Retrieves the current value of the counter 37 | /// 38 | public int Current => _value; 39 | 40 | /// 41 | /// Increments the counter and returns the next value 42 | /// 43 | public int Next() => Interlocked.Increment(ref _value); 44 | 45 | /// 46 | /// Decrements the counter and returns the next value 47 | /// 48 | public int Decrement() => Interlocked.Decrement(ref _value); 49 | 50 | /// 51 | /// Atomically increments the counter by one. 52 | /// 53 | /// The original value. 54 | public int GetAndIncrement() 55 | { 56 | var nextValue = Next(); 57 | return nextValue - 1; 58 | } 59 | 60 | /// 61 | /// Atomically increments the counter by one. 62 | /// 63 | /// The new value. 64 | public int IncrementAndGet() 65 | { 66 | var nextValue = Next(); 67 | return nextValue; 68 | } 69 | 70 | /// 71 | /// Atomically decrements the counter by one. 72 | /// 73 | /// The original value. 74 | public int GetAndDecrement() 75 | { 76 | var previousValue = Decrement(); 77 | return previousValue + 1; 78 | } 79 | 80 | /// 81 | /// Atomically decrements the counter by one. 82 | /// 83 | /// The new value. 84 | public int DecrementAndGet() => Decrement(); 85 | 86 | /// 87 | /// Returns the current value and adds the specified value to the counter. 88 | /// 89 | /// The amount to add to the counter. 90 | /// The original value before additions. 91 | public int GetAndAdd(int amount) 92 | { 93 | var newValue = Interlocked.Add(ref _value, amount); 94 | return newValue - amount; 95 | } 96 | 97 | 98 | /// 99 | /// Adds the specified value to the counter and returns the new value. 100 | /// 101 | /// The amount to add to the counter. 102 | /// The new value after additions. 103 | public int AddAndGet(int amount) 104 | { 105 | var newValue = Interlocked.Add(ref _value, amount); 106 | return newValue; 107 | } 108 | 109 | /// 110 | /// Resets the counter to zero. 111 | /// 112 | public void Reset() => Interlocked.Exchange(ref _value, 0); 113 | 114 | /// 115 | /// Returns current counter value and sets a new value on it's place in one operation. 116 | /// 117 | public int GetAndSet(int value) => Interlocked.Exchange(ref _value, value); 118 | 119 | /// 120 | /// Compares current counter value with provided value, 121 | /// and sets it to if compared values where equal. 122 | /// Returns true if replacement has succeed. 123 | /// 124 | public bool CompareAndSet(int expected, int newValue) 125 | => Interlocked.CompareExchange(ref _value, newValue, expected) != _value; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK/Support/AtomicCounterLong.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System.Threading; 5 | 6 | namespace Reactive.Streams.TCK.Support 7 | { 8 | /// 9 | /// An atomic 64 bit integer counter. 10 | /// 11 | public class AtomicCounterLong 12 | { 13 | /// 14 | /// Creates an instance of an AtomicCounterLong. 15 | /// 16 | /// The initial value of this counter. 17 | public AtomicCounterLong(long value) 18 | { 19 | _value = value; 20 | } 21 | 22 | /// 23 | /// Creates an instance of an AtomicCounterLong with a starting value of -1. 24 | /// 25 | public AtomicCounterLong() 26 | { 27 | _value = -1; 28 | } 29 | 30 | /// 31 | /// The current value for this counter. 32 | /// 33 | private long _value; 34 | 35 | /// 36 | /// Retrieves the current value of the counter 37 | /// 38 | public long Current => Interlocked.Read(ref _value); 39 | 40 | /// 41 | /// Increments the counter and returns the next value. 42 | /// 43 | public long Next() => Interlocked.Increment(ref _value); 44 | 45 | /// 46 | /// Atomically increments the counter by one. 47 | /// 48 | /// The original value. 49 | public long GetAndIncrement() 50 | { 51 | var nextValue = Next(); 52 | return nextValue - 1; 53 | } 54 | 55 | /// 56 | /// Atomically increments the counter by one. 57 | /// 58 | /// The new value. 59 | public long IncrementAndGet() 60 | { 61 | var nextValue = Next(); 62 | return nextValue; 63 | } 64 | 65 | /// 66 | /// Gets the current value of the counter and adds an amount to it. 67 | /// 68 | /// This uses a CAS loop as Interlocked.Increment is not atomic for longs on 32bit systems. 69 | /// The amount to add to the counter. 70 | /// The original value. 71 | public long GetAndAdd(long amount) 72 | { 73 | var newValue = Interlocked.Add(ref _value, amount); 74 | return newValue - amount; 75 | } 76 | 77 | /// 78 | /// Adds an amount to the counter and returns the new value. 79 | /// 80 | /// This uses a CAS loop as Interlocked.Increment is not atomic for longs on 32bit systems. 81 | /// The amount to add to the counter. 82 | /// The new counter value. 83 | public long AddAndGet(long amount) 84 | { 85 | var newValue = Interlocked.Add(ref _value, amount); 86 | return newValue; 87 | } 88 | 89 | /// 90 | /// Resets the counter to zero. 91 | /// 92 | public void Reset() => Interlocked.Exchange(ref _value, 0); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK/Support/AtomicReference.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System.Threading; 5 | 6 | namespace Reactive.Streams.TCK.Support 7 | { 8 | /// 9 | /// Implementation of the java.concurrent.util AtomicReference type. 10 | /// 11 | /// Uses internally to enforce ordering of writes 12 | /// without any explicit locking. .NET's strong memory on write guarantees might already enforce 13 | /// this ordering, but the addition of the Volatile guarantees it. 14 | /// 15 | public class AtomicReference 16 | where T : class 17 | { 18 | /// 19 | /// Sets the initial value of this to . 20 | /// 21 | public AtomicReference(T originalValue) 22 | { 23 | atomicValue = originalValue; 24 | } 25 | 26 | /// 27 | /// Default constructor 28 | /// 29 | public AtomicReference() 30 | { 31 | atomicValue = default(T); 32 | } 33 | 34 | // ReSharper disable once InconsistentNaming 35 | protected T atomicValue; 36 | 37 | /// 38 | /// The current value of this 39 | /// 40 | public T Value 41 | { 42 | get { return Volatile.Read(ref atomicValue); } 43 | set { Volatile.Write(ref atomicValue, value); } 44 | } 45 | 46 | /// 47 | /// If equals , then set the Value to 48 | /// . 49 | /// 50 | /// true if was set 51 | public bool CompareAndSet(T expected, T newValue) 52 | { 53 | var previous = Interlocked.CompareExchange(ref atomicValue, newValue, expected); 54 | return ReferenceEquals(previous, expected); 55 | } 56 | 57 | /// 58 | /// Atomically sets the to and returns the old . 59 | /// 60 | /// The new value 61 | /// The old value 62 | public T GetAndSet(T newValue) => Interlocked.Exchange(ref atomicValue, newValue); 63 | 64 | #region Conversion operators 65 | 66 | /// 67 | /// Implicit conversion operator = automatically casts the to an instance of . 68 | /// 69 | public static implicit operator T(AtomicReference aRef) => aRef.Value; 70 | 71 | /// 72 | /// Implicit conversion operator = allows us to cast any type directly into a instance. 73 | /// 74 | /// 75 | /// 76 | public static implicit operator AtomicReference(T newValue) => new AtomicReference(newValue); 77 | 78 | #endregion 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK/Support/HelperPublisher.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using Reactive.Streams.Example.Unicast; 8 | 9 | namespace Reactive.Streams.TCK.Support 10 | { 11 | public class HelperPublisher : AsyncIterablePublisher 12 | { 13 | public HelperPublisher(int from, int to, Func create) 14 | : base(new HelperPublisherEnumerable(from, to, create)) 15 | { 16 | } 17 | 18 | private sealed class HelperPublisherEnumerable : IEnumerable 19 | { 20 | private readonly HelperPublisherEnumerator _enumerator; 21 | 22 | public HelperPublisherEnumerable(int from, int to, Func create) 23 | { 24 | _enumerator = new HelperPublisherEnumerator(from, to, create); 25 | } 26 | 27 | public IEnumerator GetEnumerator() => _enumerator; 28 | 29 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 30 | } 31 | 32 | private sealed class HelperPublisherEnumerator : IEnumerator 33 | { 34 | private readonly int _to; 35 | private readonly Func _create; 36 | private int _at; 37 | 38 | public HelperPublisherEnumerator(int from, int to, Func create) 39 | { 40 | if(from > to) 41 | throw new ArgumentException("from must be equal or greater than to!"); 42 | 43 | _at = from; 44 | _to = to; 45 | _create = create; 46 | } 47 | 48 | public T Current { get; private set; } 49 | 50 | object IEnumerator.Current => Current; 51 | 52 | public void Dispose() 53 | { 54 | } 55 | 56 | public bool MoveNext() 57 | { 58 | try 59 | { 60 | if (_at >= _to) 61 | return false; 62 | 63 | Current = _create(_at++); 64 | return true; 65 | } 66 | catch (Exception ex) 67 | { 68 | throw new IllegalStateException($"Failed to create element for id {_at - 1}!", ex); 69 | } 70 | } 71 | 72 | public void Reset() 73 | { 74 | throw new NotSupportedException(); 75 | } 76 | 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK/Support/IPublisherVerificationRules .cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | namespace Reactive.Streams.TCK.Support 5 | { 6 | /// 7 | /// Internal TCK use only. 8 | /// Add / Remove tests for PublisherVerification here to make sure that they arre added/removed in the other places. 9 | /// 10 | internal interface IPublisherVerificationRules 11 | { 12 | void Required_validate_maxElementsFromPublisher(); 13 | void Required_validate_boundedDepthOfOnNextAndRequestRecursion(); 14 | void Required_createPublisher1MustProduceAStreamOfExactly1Element(); 15 | void Required_createPublisher3MustProduceAStreamOfExactly3Elements(); 16 | void Required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements(); 17 | void Required_spec102_maySignalLessThanRequestedAndTerminateSubscription(); 18 | void Stochastic_spec103_mustSignalOnMethodsSequentially(); 19 | void Optional_spec104_mustSignalOnErrorWhenFails(); 20 | void Required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates(); 21 | void Optional_spec105_emptyStreamMustTerminateBySignallingOnComplete(); 22 | void Untested_spec106_mustConsiderSubscriptionCancelledAfterOnErrorOrOnCompleteHasBeenCalled(); 23 | void Required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled(); 24 | void Untested_spec107_mustNotEmitFurtherSignalsOnceOnErrorHasBeenSignalled(); 25 | void Untested_spec108_possiblyCanceledSubscriptionShouldNotReceiveOnErrorOrOnCompleteSignals(); 26 | void Required_spec109_mustIssueOnSubscribeForNonNullSubscriber(); 27 | void Untested_spec109_subscribeShouldNotThrowNonFatalThrowable(); 28 | void Required_spec109_subscribeThrowNPEOnNullSubscriber(); 29 | void Required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe(); 30 | void Untested_spec110_rejectASubscriptionRequestIfTheSameSubscriberSubscribesTwice(); 31 | void Optional_spec111_maySupportMultiSubscribe(); 32 | void Optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingOneByOne(); 33 | void Optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfront(); 34 | void Optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfrontAndCompleteAsExpected(); 35 | void Required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe(); 36 | void Required_spec303_mustNotAllowUnboundedRecursion(); 37 | void Untested_spec304_requestShouldNotPerformHeavyComputations(); 38 | void Untested_spec305_cancelMustNotSynchronouslyPerformHeavyCompuatation(); 39 | void Required_spec306_afterSubscriptionIsCancelledRequestMustBeNops(); 40 | void Required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops(); 41 | void Required_spec309_requestZeroMustSignalIllegalArgumentException(); 42 | void Required_spec309_requestNegativeNumberMustSignalIllegalArgumentException(); 43 | void Required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling(); 44 | void Required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber(); 45 | void Required_spec317_mustSupportAPendingElementCountUpToLongMaxValue(); 46 | void Required_spec317_mustSupportACumulativePendingElementCountUpToLongMaxValue(); 47 | void Required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK/Support/ISubscriberBlackboxVerificationRules.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | namespace Reactive.Streams.TCK.Support 5 | { 6 | /// 7 | /// Internal TCK use only. 8 | /// Add / Remove tests for SubscriberBlackboxVerificationRules here to make sure that they arre added/removed in the other places. 9 | /// 10 | internal interface ISubscriberBlackboxVerificationRules 11 | { 12 | void Required_spec201_blackbox_mustSignalDemandViaSubscriptionRequest(); 13 | void Untested_spec202_blackbox_shouldAsynchronouslyDispatch(); 14 | void Required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete(); 15 | void Required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError(); 16 | void Untested_spec204_blackbox_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError(); 17 | void Required_spec205_blackbox_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal(); 18 | void Untested_spec206_blackbox_mustCallSubscriptionCancelIfItIsNoLongerValid(); 19 | void Untested_spec207_blackbox_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization(); 20 | void Untested_spec208_blackbox_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel(); 21 | void Required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall(); 22 | void Required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall(); 23 | void Required_spec210_blackbox_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall(); 24 | void Untested_spec211_blackbox_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents(); 25 | void Untested_spec212_blackbox_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality(); 26 | void Untested_spec213_blackbox_failingOnSignalInvocation(); 27 | void Required_spec213_blackbox_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull(); 28 | void Required_spec213_blackbox_onNext_mustThrowNullPointerExceptionWhenParametersAreNull(); 29 | void Required_spec213_blackbox_onError_mustThrowNullPointerExceptionWhenParametersAreNull(); 30 | void Untested_spec301_blackbox_mustNotBeCalledOutsideSubscriberContext(); 31 | void Untested_spec308_blackbox_requestMustRegisterGivenNumberElementsToBeProduced(); 32 | void Untested_spec310_blackbox_requestMaySynchronouslyCallOnNextOnSubscriber(); 33 | void Untested_spec311_blackbox_requestMaySynchronouslyCallOnCompleteOrOnError(); 34 | void Untested_spec314_blackbox_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists(); 35 | void Untested_spec315_blackbox_cancelMustNotThrowExceptionAndMustSignalOnError(); 36 | void Untested_spec316_blackbox_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK/Support/ISubscriberWhiteboxVerificationRules.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | namespace Reactive.Streams.TCK.Support 5 | { 6 | /// 7 | /// Internal TCK use only. 8 | /// Add / Remove tests for SubscriberWhiteboxVerificationRules here to make sure that they arre added/removed in the other places. 9 | /// 10 | internal interface ISubscriberWhiteboxVerificationRules 11 | { 12 | void Required_exerciseWhiteboxHappyPath(); 13 | void Required_spec201_mustSignalDemandViaSubscriptionRequest(); 14 | void Untested_spec202_shouldAsynchronouslyDispatch(); 15 | void Required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete(); 16 | void Required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError(); 17 | void Untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError(); 18 | void Required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal(); 19 | void Untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid(); 20 | void Untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization(); 21 | void Required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel(); 22 | void Required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall(); 23 | void Required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall(); 24 | void Required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall(); 25 | void Required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall(); 26 | void Untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents(); 27 | void Untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation(); 28 | void Untested_spec213_failingOnSignalInvocation(); 29 | void Required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull(); 30 | void Required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull(); 31 | void Required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull(); 32 | void Untested_spec301_mustNotBeCalledOutsideSubscriberContext(); 33 | void Required_spec308_requestMustRegisterGivenNumberElementsToBeProduced(); 34 | void Untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber(); 35 | void Untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError(); 36 | void Untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists(); 37 | void Untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError(); 38 | void Untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK/Support/IllegalStateException.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System; 5 | 6 | namespace Reactive.Streams.TCK.Support 7 | { 8 | public class IllegalStateException : Exception 9 | { 10 | public IllegalStateException(string message, Exception innerException) : base(message, innerException) 11 | { 12 | 13 | } 14 | 15 | public IllegalStateException(string message): base(message) 16 | { 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK/Support/InfiniteHelperPublisher.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using Reactive.Streams.Example.Unicast; 8 | 9 | namespace Reactive.Streams.TCK.Support 10 | { 11 | public class InfiniteHelperPublisher : AsyncIterablePublisher 12 | { 13 | public InfiniteHelperPublisher(Func create) : base(new InfiniteHelperEnumerable(create)) 14 | { 15 | } 16 | 17 | private sealed class InfiniteHelperEnumerable : IEnumerable 18 | { 19 | private readonly InfiniteHelperEnumerator _enumerator; 20 | 21 | public InfiniteHelperEnumerable(Func create) 22 | { 23 | _enumerator = new InfiniteHelperEnumerator(create); 24 | } 25 | 26 | public IEnumerator GetEnumerator() => _enumerator; 27 | 28 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 29 | } 30 | 31 | private sealed class InfiniteHelperEnumerator : IEnumerator 32 | { 33 | private readonly Func _create; 34 | private int _at; 35 | 36 | public InfiniteHelperEnumerator(Func create) 37 | { 38 | _create = create; 39 | } 40 | 41 | public T Current { get; private set; } 42 | 43 | object IEnumerator.Current => Current; 44 | 45 | public void Dispose() 46 | { 47 | } 48 | 49 | public bool MoveNext() 50 | { 51 | try 52 | { 53 | Current = _create(_at++); // Wraps around on overflow 54 | return true; 55 | } 56 | catch (Exception ex) 57 | { 58 | throw new IllegalStateException($"Failed to create element in {GetType().Name} for id {_at - 1}!", 59 | ex); 60 | } 61 | } 62 | 63 | public void Reset() 64 | { 65 | throw new NotSupportedException(); 66 | } 67 | 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK/Support/Option.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System.Collections.Generic; 5 | 6 | namespace Reactive.Streams.TCK.Support 7 | { 8 | /// 9 | /// Allows tracking of whether a value has be initialized (even with the default value) for both 10 | /// reference and value types. 11 | /// Useful where distinguishing between null (or zero, or false) and unitialized is significant. 12 | /// 13 | /// 14 | public struct Option 15 | { 16 | public static readonly Option None = new Option(); 17 | 18 | public Option(T value) 19 | { 20 | Value = value; 21 | HasValue = true; 22 | } 23 | 24 | public bool HasValue { get; } 25 | 26 | public T Value { get; } 27 | 28 | public static implicit operator Option(T value) => new Option(value); 29 | 30 | public bool Equals(Option other) 31 | => HasValue == other.HasValue && EqualityComparer.Default.Equals(Value, other.Value); 32 | 33 | public override bool Equals(object obj) 34 | { 35 | if (ReferenceEquals(null, obj)) 36 | return false; 37 | return obj is Option && Equals((Option)obj); 38 | } 39 | 40 | public override int GetHashCode() 41 | { 42 | unchecked 43 | { 44 | return (EqualityComparer.Default.GetHashCode(Value) * 397) ^ HasValue.GetHashCode(); 45 | } 46 | } 47 | 48 | public override string ToString() => HasValue ? $"Some<{Value}>" : "None"; 49 | } 50 | } -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK/Support/SubscriberBufferOverflowException.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System; 5 | 6 | namespace Reactive.Streams.TCK.Support 7 | { 8 | public class SubscriberBufferOverflowException : Exception 9 | { 10 | public SubscriberBufferOverflowException(string message) : base(message) 11 | { 12 | 13 | } 14 | 15 | public SubscriberBufferOverflowException(string message, Exception innerException) 16 | : base(message, innerException) 17 | { 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK/Support/TestException.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using System; 5 | 6 | namespace Reactive.Streams.TCK.Support 7 | { 8 | /// 9 | /// Exception used by the TCK to signal failures. 10 | /// May be thrown or signalled through 11 | /// 12 | public sealed class TestException : Exception 13 | { 14 | public TestException() : base("Test Exception: Boom!") 15 | { 16 | 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/tck/Reactive.Streams.TCK/WithHelperPublisher.cs: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | * Licensed under MIT No Attribution (SPDX: MIT-0) * 3 | ***************************************************/ 4 | using Reactive.Streams.TCK.Support; 5 | 6 | namespace Reactive.Streams.TCK 7 | { 8 | /// 9 | /// Type which is able to create elements based on a seed id value. 10 | /// 11 | /// Simplest implementations will simply return the incoming id as the element. 12 | /// 13 | /// type of element to be delivered to the Subscriber 14 | public abstract class WithHelperPublisher 15 | { 16 | /// 17 | /// Implement this method to match your expected element type. 18 | /// In case of implementing a simple Subscriber which is able to consume any kind of element simply return the 19 | /// incoming . 20 | /// 21 | /// Sometimes the Subscriber may be limited in what type of element it is able to consume, this you may have to implement 22 | /// this method such that the emitted element matches the Subscribers requirements. Simplest implementations would be 23 | /// to simply pass in the as payload of your custom element, such as appending it to a String or other identifier. 24 | /// 25 | /// Warning: This method may be called concurrently by the helper publisher, thus it should be implemented in a 26 | /// thread-safe manner. 27 | /// 28 | /// element of the matching type that will be delivered to the tested Subscriber 29 | public abstract T CreateElement(int element); 30 | 31 | /// 32 | /// Helper method required for creating the Publisher to which the tested Subscriber will be subscribed and tested against. 33 | /// 34 | /// By default an asynchronously signalling Publisher is provided, which will use 35 | /// to generate elements type your Subscriber is able to consume. 36 | /// 37 | /// Sometimes you may want to implement your own custom custom helper Publisher - to validate behaviour of a Subscriber 38 | /// when facing a synchronous Publisher for example.If you do, it MUST emit the exact number of elements asked for 39 | /// (via the parameter) and MUST also must treat the following numbers of elements in these specific ways: 40 | /// 41 | /// 42 | /// If is the produced stream must be infinite. 43 | /// 44 | /// 45 | /// 46 | /// If is 0 the should signal immediatly. 47 | /// In other words, it should represent a "completed stream". 48 | /// 49 | /// 50 | /// 51 | /// 52 | public virtual IPublisher CreateHelperPublisher(long elements) 53 | => elements > int.MaxValue 54 | ? (IPublisher) new InfiniteHelperPublisher(CreateElement) 55 | : new HelperPublisher(0, (int) elements, CreateElement); 56 | } 57 | } 58 | --------------------------------------------------------------------------------