Repository navigation
Commit d78b149
authored
fix: keep late observations out of subsequent collection buffers (#2471)
## Problem and proof
A late appender can increment its stripe during collection A, then read
`activeGeneration` only after A has completed and collection B has
started.
B already includes that observation in `expectedCount`. If the appender
puts it into B's
buffer, B waits for an observation that will only be replayed after B's
wait ends.
This deterministically causes the five-second collection timeout.
The old late-appender test paused after reading the generation, missing
this window.
Moving the existing injected pause hook before that read, without the
fix, reproduced:
```text
BufferTest.lateAppenderCountedByNextGenerationMustNotBeBufferedAgain
Time elapsed: 5.037 s
ExecutionException: IllegalStateException:
Timed out while waiting for in-flight observations.
```
The production hook remains a no-op; the test forces a scheduling window
that real threads can hit.
This has the same failure signature as the histogram failures observed
in
[#2468](#2468) and
[#2441](#2441).
The reproduction proves this race exists, not which interleaving
occurred in any particular CI run.
## Fix
- Record each stripe's absolute observation count at generation
activation, under `appendLock`.
- Under that same lock, reject buffering an observation whose ticket is
already included in the
current generation's starting count. It completes through the existing
direct observation path.
- Reuse a single cutoff array per Buffer: one additional long per
stripe, plus array overhead,
with no new per-collection allocation or additional fast-path lock.
- Keep cutoffs independent of the reset-adjusted aggregate observation
count.
- Do not change the five-second timeout, buffer capacity, or public API.
## Regression coverage
Strengthen the late-appender test to force the previously untested
window and capture worker
exceptions through futures, with bounded waits and executor cleanup.
Cover both normal collection and a preceding reset. Each case also
verifies that a genuinely new
observation buffers normally, stays out of the current snapshot, replays
exactly once, and appears
in the following collection.
## Validation
- Before fix: deterministic timeout reproduced as above.
- After fix: `BufferTest`, `HistogramTest`, and `SummaryTest` — 50 tests
passed.
- Temporary local JUnit launcher: both regression cases and the original
`HistogramTest.testObserveMultithreaded`, repeated 50 times with each of
`-XX:ActiveProcessorCount=1`, `2`, and `4` — **450 successful test
executions** on JDK 25.
- `mise run lint:fix` — passed; formatter changes retained.
- `mise run build -- -DskipITs=true` — passed. Docker-backed integration
tests skipped because
Docker is unavailable locally.
- `mise run test` — full unit-test suite passed.
- `./mvnw verify -pl prometheus-metrics-core -am` — passed, including
coverage checks.
- `git diff --check` — passed.
Kept separate from the benchmark-only PR.
## Current scope after update onto main
The original production fix is already present on `main` through
[#2441](#2441) and
[#2468](#2468). This PR
now contains the remaining merge-update and review hardening: explicit
coverage for both generation handoff windows, fail-closed absolute
cutoffs, clearer test seams, and non-masking executor cleanup
assertions. The historical race reproduction and validation context
above are retained.
---------
Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>1 parent 085263b commit d78b149
2 files changed
Lines changed: 43 additions & 17 deletions
File tree
- prometheus-metrics-core/src
- main/java/io/prometheus/metrics/core/metrics
- test/java/io/prometheus/metrics/core/metrics
Lines changed: 24 additions & 7 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
62 | 62 | | |
63 | 63 | | |
64 | 64 | | |
65 | | - | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
66 | 68 | | |
67 | 69 | | |
68 | | - | |
| 70 | + | |
69 | 71 | | |
70 | 72 | | |
71 | 73 | | |
72 | | - | |
| 74 | + | |
73 | 75 | | |
74 | 76 | | |
75 | | - | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
76 | 86 | | |
77 | 87 | | |
78 | 88 | | |
79 | 89 | | |
80 | 90 | | |
81 | | - | |
| 91 | + | |
| 92 | + | |
82 | 93 | | |
83 | 94 | | |
84 | 95 | | |
| |||
87 | 98 | | |
88 | 99 | | |
89 | 100 | | |
| 101 | + | |
90 | 102 | | |
91 | 103 | | |
92 | 104 | | |
| |||
97 | 109 | | |
98 | 110 | | |
99 | 111 | | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
100 | 116 | | |
101 | | - | |
| 117 | + | |
102 | 118 | | |
| 119 | + | |
103 | 120 | | |
104 | 121 | | |
105 | 122 | | |
| |||
193 | 210 | | |
194 | 211 | | |
195 | 212 | | |
196 | | - | |
| 213 | + | |
197 | 214 | | |
198 | 215 | | |
199 | 216 | | |
| |||
Lines changed: 19 additions & 10 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
223 | 223 | | |
224 | 224 | | |
225 | 225 | | |
226 | | - | |
| 226 | + | |
227 | 227 | | |
228 | 228 | | |
229 | 229 | | |
230 | 230 | | |
231 | | - | |
| 231 | + | |
232 | 232 | | |
233 | 233 | | |
234 | | - | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
235 | 241 | | |
236 | 242 | | |
237 | 243 | | |
| |||
240 | 246 | | |
241 | 247 | | |
242 | 248 | | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
243 | 256 | | |
244 | 257 | | |
245 | 258 | | |
246 | 259 | | |
247 | | - | |
248 | | - | |
249 | | - | |
250 | | - | |
251 | | - | |
252 | | - | |
| 260 | + | |
| 261 | + | |
253 | 262 | | |
254 | 263 | | |
255 | 264 | | |
| |||
329 | 338 | | |
330 | 339 | | |
331 | 340 | | |
332 | | - | |
333 | 341 | | |
| 342 | + | |
334 | 343 | | |
335 | 344 | | |
336 | 345 | | |
| |||
0 commit comments