Repository navigation
Conversation
samber
commented
Oct 4, 2026
samber
left a comment
Owner
Author
There was a problem hiding this comment.
- ✅ 4 commits: counter +
make fuzz+ CI, core fuzz targets, plugin fuzz targets, docs ⚠️ 60 of 99 new targets are skipped on purpose: each proves a bug onmain- ℹ️ Fix PRs follow, one per family, each removing its skips
| // a synchronous source emits inside Subscribe (teardown is registered only after it returns), | ||
| // while an asynchronous source emits from its own goroutine (Next races Unsubscribe and Complete). | ||
| // Derive async from a bit of the fuzz input so the corpus covers both. | ||
| func fuzzSource(seed int64, n int, async bool) Observable[int] { |
Owner
Author
There was a problem hiding this comment.
✅ Every target must use both kinds. A synchronous source emits before the teardown exists, an asynchronous one races Unsubscribe.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #440 +/- ##
==========================================
+ Coverage 68.05% 68.61% +0.56%
==========================================
Files 96 98 +2
Lines 8207 8279 +72
==========================================
+ Hits 5585 5681 +96
+ Misses 2622 2598 -24
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
samber
marked this pull request as ready for review
October 4, 2026 18:07
Every fuzz target seeds itself through internal/xtest, so one variable, RO_FUZZ_ITERATIONS, controls the number of interleavings explored by go test, make fuzz and CI. CI runs make fuzz on the stable Go version.
Targets cover subjects, connectable and share, higher-order operators, buffer and window operators, Zip and CombineLatest ordering, time-based operators and short-circuit operators, each with synchronous and asynchronous sources. Targets that prove a bug on main are skipped with the bug id, so each fix PR removes its own skip. The four hand-rolled round loops become native fuzz targets on the shared counter.
…et and cron Plugins are separate modules and cannot import internal/xtest, so each carries a small helper that reads the same RO_FUZZ_ITERATIONS variable. Targets that prove a bug are skipped with the bug id.
List the 15 race patterns found in the codebase, with how to test each one. New and changed operators must ship a native fuzz target that covers synchronous and asynchronous sources, and tests must always run with -race.
Core fuzz targets live in the fuzz package, one file per source file (fuzz/operator_filter_fuzz_test.go, ...), like bench/. Plugin fuzz targets sit next to the code they test, named after the source file. The shared iteration helpers move from internal/xtest to internal/xfuzz and plugins import them directly. The fuzz make target is phony, because a fuzz directory now exists, and a comment records that the test target can skip fuzz targets once Go 1.20 is the minimum version.
…ples Each of the 15 patterns gets its own section with a generic bad and good example, how to test it, and the library facts behind it. The section no longer references pull requests or specific operators, and documents where fuzz targets and helpers live.
Docusaurus fails the build on broken anchors. Link to the page and name the section in the text instead.
Wait helpers, panic recovery, leak assertion, standard seeds and the failing writer were duplicated across plugin fuzz tests. They now live in internal/xfuzz with their own unit tests. The remaining per-plugin helper files are named fuzz_test.go.
Helpers and targets carried cryptic family prefixes. They now describe what they do, for example runWithRawSink, rawNotificationSink and predicateCallCounter, and targets are named after the operator under test. Duplicate min helpers are merged and stale file references are removed from skip comments.
…e inputs Targets take typed, named inputs and carry a doc comment with the scenario and the invariant, instead of decoding a seed and a bit mask. Generic building blocks (sources, recorder, guards, waits, stop helpers) live in small purpose-named files. The conventions are written in fuzz/doc.go.
Each target takes typed, named inputs and documents its scenario and invariant. Targets whose scenarios read differently are split, for example the self and outside unsubscribe variants of the broadcast target.
…e inputs Targets that mixed several scenarios in one input are split by scenario, for example plain, take and unsubscribe variants of the higher-order operators. Boundary, ordering and higher-order checks are written as named expectations instead of mode switches.
…ld helpers Sink, creation, utility, error handling and upstream targets take typed named inputs and use the shared fuzz primitives. The old enum-driven helper file is removed and the contributor docs describe the new primitives.
The ./bench directory shares the target name, so make reported it as up to date.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds native Go fuzz targets that hunt races between subscribe, unsubscribe, in-flight messages, multiple subscriptions and sync/async sources, across the core package and the plugins with custom subscribe logic.
fuzz/, one file per source file (fuzz/operator_filter_fuzz_test.go, ...), likebench/. Plugin targets sit next to the code they test, named after the source file (plugins/cron/source_fuzz_test.go, ...).items uint8,asyncSource bool,unsubscribeAfter uint8...) and keeps its scenario inline, with a doc comment stating the invariant and the seeds. Shared building blocks are small purpose-named files infuzz/(source_test.go,recorder_test.go,guards_test.go,waits_test.go...), indexed infuzz/doc.go. No bitmask decoding, no enum scenarios.fuzz/main_test.gorunsgoleakfor the whole package.internal/xfuzzreadsRO_FUZZ_ITERATIONS(default 100, 10 under-short). Every target seeds itself with it, sogo test -race,make fuzzand CI use the same knob. The 4 hand-rolled round loops (Connectable,Zip,ToChannel,GroupBy) are now nativeFuzzXxxtargets on it.make fuzzruns every target (RO_FUZZ_ITERATIONS=1000by default). CI runs it on thestableGo version only.make teststill runs each target on its seeds; a comment in the Makefile says to use-skip '^Fuzz'once Go < 1.20 is dropped.contributing.mdgets a "Race condition patterns" section: 15 generic patterns, each with a bad and a good example and how to test it. The rules: a native fuzz target per applicable pattern, sync and async sources, always-race.hacking.mdandCLAUDE.mdlink to it.Bugs proven on
main155 targets are added. 68 of them reproduce a bug on
mainand are skipped withf.Skip("race: <id>; remove when fixed"), so CI stays green and each fix PR removes its own skip. The other 87 pass at 2000 iterations and show the race is absent for that operator.Families with at least one reproduced bug:
IsClosed/Next/Subscribedeadlock,UnicastSubjectclosed-subscriber and replay deadlock, long lock while broadcasting.Connectdeadlock,ShareReplaynever releases its source.Catch/StartWithoverMerge(overlappingNext),Racewinner leak,Retry/While/DoWhile/RepeatWith/OnErrorResumeNextWithignore a closed downstream,FlatMapblocks its producer.BufferWithCountdata race,BufferWhenandBufferWithTimelost buffers,WindowWhenlost items and open windows,Zipout of order,CombineLateststale or duplicated tuples.ObserveOnsend on closed channel,SubscribeOninfinite hang,Delaydeadlock,FromChannellost items,Futurepanic.Contains/Find/Firstkeep calling the predicate,Averageon an empty source emits twice.iter(ToSeq),stdioandcsvreaders and writers,http/clientshared request,websocket/client,cron.Fixes follow in separate PRs, one per family.
plugins/iterandplugins/cronare outsidego.work, somake fuzzdoes not run them: useGOWORK=off.