Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Reimplement watches without extraneous goroutines
The original implementation of Hypcast's watch.Value used a long-running goroutine per watch, along with a second goroutine to protect against runtime.Goexit breaking the watch invariants. I wanted to challenge myself to reimplement watches with one goroutine active only while executing a handler, while maintaining correct runtime.Goexit handling. The core idea seems simple, at least to me: use a mutex-protected "running" flag to track whether there's a handler goroutine ready to deal with new values, and simultaneously hand off those values without data races. On runtime.Goexit, start up a new copy of the same goroutine to avoid missing any new values. The WaitGroup handling might be the trickiest part, since it could be marked done by either Cancel or the goroutine. But the invariant is that the first party to see (w.running == false && w.cancel == true) is the one responsible for marking it done. I brought back and cleaned up some old benchmarks to see how this compared with the old implementation under load, and while it does better in a lot of cases it's not a *complete* win for every combination of watcher count and GOMAXPROCS. Or, my data is bad since I ran this on too noisy of a laptop and don't even have the raw data to tell whether the differences are significant. I have to say that after using a Rust port of Haskell's Criterion in another project and seeing the level of statistical analysis it does, I'm not impressed with the capabilities of the Go test framework here. But I can confidently say this is nowhere close to a net negative. I had to add a few new unit tests to restore 100% mutation testing coverage (via go-mutesting), which was a good opportunity to clean up the existing tests with newer patterns like range over int. One was an issue even in the original, where I didn't fully test that Cancel stops new handler executions. Others were related to the new ability to safely double-Cancel a watch. Finally, I wrote a Promela model to validate the "mutual exclusion of handlers" and "handler executed for at least one value" properties of this approach, which I might document and commit here later.
- Loading branch information