The completion check was scheduling-dependent and the test could lose its TSan
overlap window. The producer now signals done with a release store, the consumer
reads in a do/while keyed on that flag so at least one read is guaranteed by
construction, and a started/reading handshake makes the producer's remaining
pushes provably overlap the reader's loop. Bounded to 4096 pushes on the 256-slot
ring, and the assertions moved after join() so a failure reports instead of
terminating. With the pre-fix ring restored, four TSan runs still flag push vs
copy_from, so the case keeps its teeth.
The pyramid's public query surface is the bucket-grained trio
(reduce_buckets/sum_buckets/sumsq_buckets); reduce() was the one
non-*_buckets query and silently over-covered non-aligned ranges, which
is exactly the misuse the exactness split exists to prevent. Exact range
queries stay on Series.
- delete the declaration, its doc paragraph and the definition; no
helper became dead (fold_range/cover_buckets/ceil_to_bucket are all
still used by the *_buckets queries)
- pyramid_clear_and_rebuild: complete bucket 0 and assert its extrema via
reduce_buckets(0, kBucket) -- a pyramid has no exact sub-bucket query
- stream_series_acquire_publishes_snapshot: s.pyramid().reduce(0, 100)
-> s.reduce(0, 100), keeping the == 99.0 assertion
cover_buckets chose one level per pass, so an odd first whole bucket (half of all
measurement ranges) never reached a level above 0 and folded every remaining
bucket at level 0 -- 15624 entries for a 1e6-sample pyramid. Pick the largest
aligned, in-range, materialised level at each cursor instead; the new cost test
counts folded entries through a PLOT_PYRAMID_COUNT_COVERS-only hook (absent from
normal builds) and fails on the old body.
Ring slots are now relaxed std::atomic: overwrite-on-full means the producer
reuses the slot of a snapshot's oldest sample while the consumer reads it, which
was a data race, not just a stale value. copy_from loads explicitly instead of
std::copy; push stays lock-free. A new producer/consumer case is the one TSan
flags without this.
Also extends the wrapped-window test with x0/bounds/pyramid/sum/sumsq/gather
checks, and corrects the StreamSeries threading note to the sample-count bound.
- loader: add available(), true only after load() has resolved every
required entry point (false at the start of load()). Program::from_source
and the other fallible constructors return ErrorCode::Unsupported while it
is false, so a failed load() with a partially populated table can no longer
fault on an unresolved entry point.
- caps: GL_CONTEXT_PROFILE_MASK only exists from 3.2, so query it only on
3.2+ contexts. A 3.0/3.1 query latches GL_INVALID_ENUM in the context, which
the next unrelated glGetError() (e.g. read_pixel) then reports as failure.
- tests: gate the dlsym/libGL.so.1 fallback assertions on libGL being
resolvable, so the GL suite also passes on a GLES-only host.
A zero pixel span previously produced sx/sy == 0, so to_data_* returned
NaN or +/-inf. axis_scale now substitutes a unit pixel span (1.0) when
|p1 - p0| <= 1e-12, keeping the mapping finite and invertible while
leaving non-collapsed spans byte-identical.
Add committed coverage for from_range (linear, flipped pixel axis, log x,
degenerate data range, collapsed pixel span) and log10_positive clamping.