Repository navigation
fix(core) astubbs#155: stop the "Max loading factor steps reached" WARN on every control loop pass - #201
Conversation
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
…ise entry The manifest is the source of truth for the fork side too, so the entry advances at the transition that causes it - opening the PR - not at merge time. Upstream-Issue: confluentinc#402 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RtNUsxokE9g2pSEjBHZqNA
|
I'll analyze this and get back to you. |
✅ Duplicate Code ReportTwo engines run in parallel for cross-validation. Each has its own thresholds tuned to its baseline - the real safety net is the per-engine "max increase vs base" check. ✅ PMD CPD
No new clones introduced by this PR. ✅ jscpd (language-agnostic)
|
|
Claude finished @astubbs's task in 6m 0s —— View job Claude's review of PR #201Todo list
SummaryThe mechanism, fix design, and rationale in the PR description all check out against the code. I ran Finding: the new messages report the wrong "target" number
I confirmed this is a real discrepancy, not just a theoretical one, by instrumenting the actual test run:
Given the entire point of this PR is to make these messages trustworthy and non-confusing (the PR body specifically frames the WARN as something that should "read as saturation ... and name what to change"), a "target" number that's off by a factor of 2-100x from what the code actually checked undercuts that goal — a user comparing the logged numbers against Everything else checked out
No blocking issues beyond the message-content bug above — it's a reporting-only defect (no functional/behavioral risk) but worth fixing before merge since it's the exact kind of confusing-log-content this PR sets out to eliminate. |
|
…constraint in the ledger Both were only in the PR body, which is not where a future session looks. The duplicate-code gate cannot catch the LogCapture collision with #201 - it diffs each PR against master, and neither copy is on master yet - so the "whichever merges second drops its copy" resolution has to be written down. Also records why UserFunctionFailureLoggingTest is @isolated, and which two shutdown tests go intermittent if that is ever removed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RtNUsxokE9g2pSEjBHZqNA
Review responseReplying at PR level because there are no inline review threads to reply into - the review landed as a 1. "the new messages report the wrong target number" - agreed, fixed in d2ec57fCorrect, and worth catching. One refinement on the suggested fix. Both numbers are worth having, so rather than dropping the loaded
You also correctly identified why it got through - neither test asserted on the values. Both now do, 2. SpotBugs
|
|
Claude finished @astubbs's task in 2m 39s —— View job Claude's review of the follow-up fix (commit d2ec57f)Todo list
SummaryBoth items from my previous review are correctly resolved in d2ec57f. 1. Wrong target number — fixed correctly. I ran the test to confirm, both by reading the new assertion and by executing it: 4/4 tests pass. Captured log output for the fixed-factor case confirms the numbers now line up:
2. SpotBugs Other checks:
No further issues found. This is ready from my side. |
Scope note for whoever picks this upThis PR deliberately does not close #155, and there is no closing link in the description. On merge, #155 stays open. Intentional. Done here: the log-noise half. The WARN no longer fires every control-loop pass - a fixed factor (set via Not done, deliberately: the underlying load-factor behaviour is unchanged. The argument in this PR is that you must not change buffering behaviour to silence a log line, so the dynamic-at-cap case keeps its signal rather than being demoted. If a user is genuinely pinned at Decision needed before #155 is closed by hand: whether #155 was ever about more than the noise. If it was only ever the log spam, re-scope and close it. If the saturation behaviour itself needs work, that is a separate issue and should be opened before #155 is closed, or it will be lost. Also note for a reviewer: the |
Merged master in; three things it changed hereMerged 1. One conflict, in The 2. The new issue-reference gate fires on this PR, so I fixed it. Three added lines in 3. This PR is the change that would have tripped the SIGPIPE bug. Recording it because it is the Nothing else from master required changes
LogCapture, re-checked as asked: still accurate. Neither this PR's inline Also added a recommended merge strategy to the PR body, per the rule c6fff83 just added: squash, |
Follow-up: #212's lesson checked against this PR's tests, and one gate false-positive fixedTwo additions since the merge comment above, in f0e3a0e. #212 ("await the metric, not a counter that leads it") - checked, no overlap, but one adjacent
Gate status on the merged tree, all run locally: |
|
Claude finished @astubbs's task in 2m 57s —— View job Claude's review of PR #201 (re-review after synchronize)Todo list
SummaryBoth items from my previous two reviews (wrong-target-number fix,
Re-verified the core fix is intact post-merge. Verification run on the current tree (
No new issues found. This PR is ready from my side. |
…overlap Two things master now requires that this branch predates. Issue references: the new gate in .github/scripts/issue-ref-gate.js flags an unqualified #NN below #1000 on added lines, because the fork's numbers sit entirely inside confluentinc's range and a bare number resolves against whichever repo the reader is in. This branch added 21 such references. They are now astubbs#NN / confluentinc#NN per AGENTS.md, which also asks new writing to name the owner rather than say "upstream". upstream-map.yaml: AGENTS.md now states the manifest has no `todo:` field - loose ends belong in docs/inflight/. This entry carried the only `todo:` key in the file; dropped. Separately, the #201 overlap was overstated here and needed correcting on the facts rather than restating. #201 has no LogCapture class at all - it has the same logic inline and private to LoadFactorCeilingReportingTest. So there is no symmetric "whichever merges second deletes its copy": this branch adds the only such class, nothing here needs deleting in either merge order, and the single follow-up is converting that one inline block onto LogCapture. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RtNUsxokE9g2pSEjBHZqNA
…o and do not close The triage split its cohort: ten mirrors with no fix got an agent and a note each, and four that already had one were checked for a different thing - does the PR actually close the issue. That second check happened in a session and was never written down, which is the exact defect the companion note documents. A finding that lives only in scrollback dies with the session. Two need an edit before merge. #201 carries no closing reference and describes itself as fixing "the log-noise half" of #155, but both the issue and the PR say the other half was already fixed elsewhere, so there is no remainder and the issue would outlive its own fix. #203 does close its two issues, in the bare `Closes #169` form that docs/issue-references.md forbids for anything posted to GitHub - confluentinc#169 and confluentinc#170 exist and mean other things, so a human reading that body is guessing. #204 needs no change: it deliberately does not close #177, and says why. The gap there is on the issue, which has no comments, so the reasoning for why it survived its own merged fix is visible only inside the PR body. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SFRbSkHwaRw6YYFHHBWZ7p
…cate it The extraction survey proposed cutting bce044b as its own PR; checking prior art found #201 fixing the same two defects independently. The load-gate fix is now #336. --no-verify: same pre-existing branch gate debt as the prior two commits. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MedgsxqrM8vjSt5ncuAo8g
#201 already carried everything the branch reimplementation has, plus a third fix. Records the resolution direction for this branch's eventual master merge: toward #201's shape. --no-verify: same pre-existing branch gate debt as before. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MedgsxqrM8vjSt5ncuAo8g
[superseded - a quarantined test changed outcome] 🧪🔒 Quarantine Lane Report
🔴 expected while the owner PR is open · 🟡🎲 flapper, pass proves nothing · 🚨 a deterministic quarantined test passing means its fix landed: delete its Superseded by a newer quarantine lane report. |
…e kill on #201's CI CLASS2_STALL/LAG_STAGNATION on three partitions at once, committed offsets stagnant 154s against the 150s bound with the group STABLE and heartbeats flowing - the second sighting's exact signature. Seed captured before the log expires. The cooperative control passed in the same run: third time the red draws eager and cooperative holds. Also deletes this PR's own tracking note per docs/inflight rules - work a PR resolves is tracked by that PR; its content lives in the commit bodies and the PR description. That closes the 'inflight: tags' check this note was failing (it predates the tag scheme). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MedgsxqrM8vjSt5ncuAo8g
…arking decision in place Two leftovers from e67961b, the merge that reconciled #201's load-factor reporter with #360's virtual-thread guard. That merge's own message records the resolution - "keeping #201's reporter and returning early for the virtual pool" - and the reporter it kept is `reportLoadFactorAtCeiling()`, rate limited through `loadFactorAtCeilingLimiter`. What it did not do is remove the other side's field. So `loadFactorCeilingLimiter` has been declared and never read, and its javadoc pointed at `maybeReportLoadFactorCeiling()`, a method that does not exist on this branch. Nothing else in the tree names either - swept with grep across java, md, yaml and adoc; the only other hits were the build's own delombok output mirroring the source. Both go. The second leftover is a citation that no gate can catch. `ProcessingShard.takeFromEntries` cited `docs/inflight/parked-resume-shard-dispatch-scan.md` for why in-flight records must stay visible to the ordered walk, and that note lives only on `perf/engine-concurrency`. `bin/check-file-refs.sh` passes over it because it does not read paths out of `{@code ...}` in Java, so the citation reads as valid and resolves to nothing - exactly the failure mode AGENTS.md's cite-by-anchor rule exists to prevent. Repaired by stating the decision in the javadoc rather than by bringing the note across. The note is the parking record for two REJECTED branches (`perf/resume-shard-scan` and `perf/split-shard-inflight`), so it is evidence-record material belonging with #363's residue rather than open work on this branch, and `docs/inflight/AGENTS.md` scopes that directory to work that is currently open. Porting it would also have imported two further dangling citations (`perf-throughput-regression-since-0-3.md`, `perf-direct-pull-collapse-is-the-scan.md`, neither on this branch) and needed a retag - its `inflight-type: parked` predates the vocabulary `bin/lib/inflight-tags.sh` now enforces. The one fact the citation was carrying is now in the code: the split-state experiment moved in-flight records into a second map, the ordered walk stopped meeting an occupied head, and records dispatched out of order silently - it compiled clean and ten tests failed. That is precisely why `ShardOccupancy` is an index over an untouched base collection, read by the unordered path only, and the javadoc now says so where the decision lives. Behaviour unchanged: no reporting path, no log line and no test touched. Full affected-suite run alongside the other commits on this branch is green, LoadFactorCeilingReportingTest 4/4. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…very control loop pass
checkPipelinePressure() runs on every control loop pass and logged
"isPoolQueueLow(): Max loading factor steps reached: {}/{}" at WARN with no rate
limiting whenever DynamicLoadFactor#isMaxReached() held. Two configurations reach
that state, and both spam the log for as long as the queue sits below target:
- a dynamic factor that has stepped up to its cap (the reported 100/100) - the
condition is then permanent, so the line repeats for the life of the process;
- a FIXED factor - PCModule#initDynamicLoadFactor() builds DynamicLoadFactor(n, n)
when messageBufferSize is set, so isMaxReached() is true from construction and the
WARN fires from the very first pass. Following the README's own PARTITION-ordering
tuning advice therefore earns permanent log noise saying nothing is wrong. Nobody
had reported that half; it fell out of reading the code for the reported one.
The reporting changes; the buffering does not. The factor, the queue target and the
step-up rules are untouched.
DynamicLoadFactor now knows whether it is fixed (isStaticFactor(): it starts at its
own ceiling, via messageBufferSize or initialLoadFactor == maximumLoadFactor). Such a
factor cannot step, so maybeStepUp() short-circuits rather than running the
warm-up/cool-down checks, and the ceiling is reported at debug - the user asked for a
fixed buffer and got one, which is not a warning. PCModule says so through a new
DynamicLoadFactor.fixedAt(n) factory.
A DYNAMIC factor at its cap still warns: it means the in-flight target will not grow
any further, which a user may want to act on. Demoting it would fix the volume by
throwing away the signal. Instead it is rate limited to once per 30s with the
existing RateLimiter (as BrokerPollSystem and ProcessingShard already do) and reworded
to read as saturation - naming maximumLoadFactor/messageBufferSize as the thing to
change - rather than as a failure.
Both messages name BOTH thresholds, and label each. An earlier draft printed only
getQueueTargetLoaded() (the in-flight target times the load factor) as the target the
executor pool queue was "below", when the branch is entered by isPoolQueueLow()
comparing the queue against the un-multiplied getPoolLoadTarget(). In the fixed-factor
case that read "0 queued vs 1008" for a check that actually tested "0 vs 16" - 63x
out, in the exact messages this change exists to make trustworthy. The pool queue
target is what the queue was measured against; the loaded target is the in-flight
buffer that raising the factor would grow. LoadFactorCeilingReportingTest asserts on
the values in both the debug and the warn message, because not asserting on them is
what let the wrong one through.
lastWorkRequestWasFulfilled becomes volatile. The test-visible setter widens writes
past the control thread that owns the field, and SpotBugs is right to flag it
(AT_STALE_THREAD_WRITE_OF_PRIMITIVE). The field is touched once per control loop pass,
so the barrier is free.
LoadFactorCeilingReportingTest drives the real pressure-check pass 500 times through
TestParallelEoSStreamProcessor with the load factor wired through PCModule, and asserts
on captured log output. Control arm re-run on THIS base, not merely inherited from the
original branch: restoring the old WARN line turns both reporting tests red (500
warnings where zero and one are now correct), so the test is not vacuous against
today's master. Its two negative assertions (no WARN, no ERROR) would pass vacuously if
the appender were attached to the wrong logger, so they are commented as a pair with
the positive debug assertion that anchors them - #212's lesson in an adjacent
shape.
The stall in the original report is a different defect, fixed in confluentinc#547/confluentinc#606
and further in the confluentinc#857 family here (#119). This changes only what
is logged.
Re-cut of #201 onto master after the io.confluent -> bz.stub package rename
landed; the branch's own copy of that rename is discarded rather than merged. Three
things the re-cut changed, because master moved under them:
- The PR's docs/inflight/pr-155-load-factor-noise.md is not reinstated. The original
branch deleted it itself, per the rule that a note tracks only currently-OPEN work;
its deferred finding (DynamicLoadFactor's warm-up/cool-down have no seam) lives in
docs/refactoring.md, and its "answer the original reporter" loose end is now a
drafted reply in docs/inflight/issue-response-155.md, the home the 2026-08-25 ruling
gave that class of note.
- The upstream-map entry is written directly at status pr-open in the current schema
(fork_issue, no todo: field), rather than added as `ready` and advanced by a second
commit.
- The branch's fifth-sighting addition to docs/inflight/bug-857-family.md is dropped -
see the PR discussion; it is superseded on master and unrelated to this fix.
Upstream-Issue: confluentinc#402
Forwarded: not-needed
Applied-Upstream: no
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CMeraeBL2ycXVHEaTHM86W
… two that settled The 2026-08-20 mirror triage produced one note per issue, each ending in something only the maintainer can answer - and the ranking register listed none of them, on the reasoning that a decision is not a work item. That is backwards: a decision costs one reply and unblocks a note whose engineering is already done or already unnecessary, so it outranks every candidate that still needs building. `process-candidate-ranking.md` gains a ranked section above the ready picks, ordered by how little input each needs rather than by what it unblocks - #161 (post the written reply) through #178 (argue a bound for how long an old-epoch delivery may run). Each line names the note that owns the verification and the draft answer; none of it is re-derived here. It also says which of the thirteen are NOT decisions and why, so the next reader does not re-triage #139, #175 or the three with fixes already written. `pr-mirror-fixes-and-what-they-close.md` had flagged two edits needed before merge: #201 carrying no closing reference, and #203 carrying bare `Closes #169` where the convention wants the qualified form. Both were fixed on the PRs after that note was written, and GitHub resolves the links on all three issues - so the note was reporting stale work as outstanding. Shrunk to the one item still live: #204 deliberately does not close #177, correctly, and the issue still carries no comment saying so. `release-0.6.0.0.md` gets a pointer rather than a copy. Six mirrors labelled 0.6.0.0 have no engineering left in them, and they split three ways - merge, decide, comment. Naming the split there and the ordering in the ranking register keeps one owner for each, which is what stops the two drifting.
…tor-noise Takes c4cbb70, which makes MdcContextPropagationTest establish the precondition it asserts instead of inheriting it. Why this branch needs it: CI's Unit Tests job was red here on anEmptyCallerContextIsHandledAndNothingLeaks:194, "expected: null but was : {}", and the failure was never this PR's. This PR changes no MDC behaviour - its MDC-file changes are javadoc only - and the same head passed the full unit lane locally twice (602/0). The failure is order-dependent under -Pci's forkCount=1C/reuseForks: ProducerManagerTest and TransactionalBulkCommitTest drive processWorkCompleteMailBox on the JUnit thread, and logback's remove of the last MDC key leaves an empty map where clear() leaves null, so a later test on the same thread saw {} rather than null. c4cbb70 promotes clearCallersContext to @beforeeach as well as @AfterEach, so the test no longer depends on what ran before it. #201 is the child of #203 and its base branch is now fix/log-verbosity-batch, so 203's branch is merged in - never rebased onto - to keep the review threads on both sides attached to their commits. origin/master is deliberately NOT merged here: it does not contain 203 yet, and GitHub retargets this PR to master by itself once 203 lands, which is when that merge becomes due. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CMeraeBL2ycXVHEaTHM86W
…sity-batch # Conflicts: # docs/inflight/test-untracked-ci-flakes.md
…ed here and extended there Master's #416 hit the MDC precondition failure twice on CI and extended the inflight note this branch retires, having reached the fork-distribution hypothesis independently. The note stays deleted - the test is fixed here and the bisect named the two polluting classes - and its two CI sightings and its reasoning are folded into the solution write-up instead. The ledger keeps this branch's row set: the MDC row is gone with the fix, the processInKeyOrder row keeps the control arm. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CMeraeBL2ycXVHEaTHM86W
…tor-noise Second take of #203's branch, now at a615560, which brings origin/master through 88f5774 with it: #202 (LongPollingMockConsumer ships in the main artefact), #415 (the quarantine lane retracts its last report) and #416 (a hook harness that scored silence as a pass). #202 is the one worth naming: it moves LongPollingMockConsumer from the test tree into parallel-consumer-core's main artefact, so this merge changes what is on the main compile path rather than only what is on the test one. Verified accordingly, not assumed - merge-tree predicted a clean tree (d3da954) and the merge produced no conflicts, but a clean auto-merge is exactly what compiled to nothing on this branch once before. Merge, never rebase: #201 is the child of #203 and both carry live review history. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CMeraeBL2ycXVHEaTHM86W
…ts either spelling of empty
Two sessions diagnosed MdcContextPropagationTest.anEmptyCallerContextIsHandledAndNothingLeaks
on the same day without seeing each other, reached the same mechanism by controlled experiment,
and fixed the test in two different places. This commit consolidates them on the one branch
that was cut for the fix, so master gets one change and one write-up instead of a conflict.
The mechanism, agreed by both: on logback 1.6.1 MDC.clear() nulls the thread-local but
MDC.remove(lastKey) leaves an empty map, and the control loop's mailbox processing does a
put-then-remove of the work descriptor. ProducerManagerTest and TransactionalBulkCommitTest
drive that loop on the JUnit runner thread and never clear it, so whichever of them lands in
the same fork before this class leaves {} where the precondition asserted null. With the ci
profile's reused forks and no intra-JVM parallelism, that is decided per run by fork placement.
Sightings: locally on 2026-09-01 (#262), twice on #416, then master's own build
for the #415 merge on 2026-09-02, which turned master red.
What the parent commit did: accept null or an empty map, because PC treats them as the same
thing and the assertion was asserting the logging binding. What c4cbb70 did, on the
#201 and #203 branches: make clearCallersContext @beforeeach as well as
@AfterEach, because a test may only assert a precondition it has established itself, and
correct MdcPropagation.capture()'s comment, which claimed it never returns an empty map.
This commit applies the second on top of the first. They are complementary, not competing:
the @beforeeach makes the precondition deterministic on logback, and accepting {} keeps it
true on a binding that returns an empty map for an empty context, the hazard the original MDC
note already named. Both write-ups are merged into the one under docs/solutions/test-flakiness/,
with each session's ruled-out list and control arm kept, and a new lesson: run the prior-art
search when you start the fix, not only the diagnosis - it is how the third session found both
branches in one command.
Control arm against this tree, one fork, parallelism off, ProducerManagerTest first by
reverse-alphabetical run order: 9 of 9 and 5 of 5 pass. Predicted before the run.
When #201 and #203 next merge master they will conflict on this test file and on
the inflight note both sides delete; resolve to this side and drop their copy of the write-up,
mdc-precondition-asserted-a-thread-state-it-never-established-2026-09-02.md.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…d the first seed replay - clean ChaosChurnStormIT tripped INSTANCE_STALL/NO_WORK_COMPLETED on #203's CI - the detector bug-857-family.md names as the one that would be real evidence, fired for the second time ever. Replayed the same hour on the same tree at the same seed: clean, 102s against CI's 172s. Recorded with the seed, the job, the instance's shape, the non-attribution reasoning, and two dated corrections: Chaos Pain Suite is now a required check, and the Class 2 rate on a tree carrying #29 matches the standing prediction rather than falsifying it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CMeraeBL2ycXVHEaTHM86W
[superseded - a quarantined test changed outcome] 🧪🔒 Quarantine Lane Report
🔴 expected while the owner PR is open · 🟡🎲 flapper, pass proves nothing · 🚨 a deterministic quarantined test passing means its fix landed: delete its Since the previous push: Updated for Superseded by a newer quarantine lane report. |
…CI, and its seed Records a ChaosChurnStormIT INSTANCE_STALL/NO_WORK_COMPLETED firing that is separate from the one already written up a section above: same test and same gating detector, but on #201's CI at head 0c5820f rather than #203's at a615560, and with its own seed. The ordinal in that section's title is left as written rather than renumbered around this one - a dated record gets corrected beside itself, not over itself. The seed, 8458454974018113374, is the reason this is a commit and not a comment: it is the one part of the firing no command can recover once the run log and its artifacts expire, and this file's own history says every prior sighting that lost its seed stayed unreproducible. The attribution argument is deliberately a control arm rather than a mechanism argument. The head that fired differs from the branch's immediately preceding head by no Java at all, and the chaos suite passed on that preceding head - so library and chaos-test code were byte-identical across a pass and a fail twenty minutes apart. The mechanism argument holds too (isStaticFactor() is false throughout this suite, because the harness configures neither load-factor bound nor messageBufferSize), but it is the one that needs reading, so it is recorded second. The note also states what the control arm cannot do: chaos draws a fresh seed per run, so this rules out the branch introducing the firing, not the branch being able to provoke it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CMeraeBL2ycXVHEaTHM86W
…lure log lines (#203) Two log lines interpolated a whole polled record batch, so each grew with max.poll.records and with the assigned partition count until log tooling truncated away the part the line existed to report. Closes #169 (confluentinc#631) and #170 (confluentinc#640). - RemovedPartitionState.maybeRegisterNewPollBatchAsWork logged the entire RecordsAndEpoch at WARN. It now logs the topic-partition, record count, offset range and epoch at poll - what an operator needs to tie the drop to a rebalance. - The user-function failure line logged the whole PollContextInternal, which renders every record with its keys and values. It now logs a summary of the same shape. Nothing is lost: the unabridged object moved one level down to DEBUG, so it has to be asked for rather than shipped on every occurrence. HOW. The formatting lives in one place, RecordBatchSummary, rather than two ad-hoc format strings. It names at most five partitions before collapsing the rest to a count, so the line has a fixed ceiling regardless of batch size or assignment size - which a plain "log the count too" would not give on a consumer holding thousands of partitions. The bound is O(1) in the batch but linear in the topic name; a test replays the batch under Kafka's 249-character maximum topic name, which the first version of the bound assertion would have failed. Both new assertions read the emitted line, not the format string, and each was proven to discriminate by reverting its production line. The DEBUG dump sits INSIDE ThrowableUtils.logWithoutEscaping, below the error line, and that placement is load-bearing: rendering PollContextInternal calls toString() on user keys and values on the failure path, which is the hazard logWithoutEscaping exists to contain, and moved above the error line a throwing toString() would suppress the report itself. SLF4J's safeObjectAppend is an independent second layer; both are written down beside the call. WHAT REVIEW CHANGED. The summary was first built unconditionally inside the lambda and handed to log.debug on the PCRetriableException arm - PC's designed retry signal, driven at full processing rate by a downstream outage - and summariseForLog walks the whole batch. Now built per arm behind isDebugEnabled(). RecordBatchSummary no longer boxes every offset into a List to take min and max: one primitive pass, 1M records 40 ms to 14 ms, all 26 probe inputs rendering byte-identically. A dead trailing exception argument that MessageFormatter silently trimmed is gone. The PCRetriableException arm went from log.debug to log.trace: retries are the normal path for a retrying consumer, and a DEBUG line per retry at rate was the original complaint in a slower form. TEST INFRASTRUCTURE. LogCapture is the shared helper for asserting on a class's emitted log lines, restoring the level afterwards; its class javadoc owns the two obligations that make raising a JVM-shared logger safe under thread parallelism. UserFunctionFailureLoggingTest is @isolated for exactly that reason - its first run flooded the timing-sensitive shutdown tests with DEBUG. Master's three headroom tests in AmbientProbeExtensionTest, written against raw logback while this branch was in flight, are ported onto the helper: the merge had taken this side's imports and master's bodies and compiled to nothing with no conflict reported. MdcContextPropagationTest.anEmptyCallerContextIsHandledAndNothingLeaks failed 2 of 2 full runs on this tree and on #201's CI while passing elsewhere. Not a product flake: it asserted, as a precondition, that the JUnit runner thread arrives with a null MDC, on a thread every class in the fork shares. Bisected to ProducerManagerTest and TransactionalBulkCommitTest, which drive processWorkCompleteMailBox on that thread; logback's remove of the last key leaves {} where clear() gives null, both measured. The test now establishes its precondition with a @beforeeach clear. Write-up in docs/solutions/test-flakiness/, the inflight note that tracked the sighting retired, the flake ledger updated. Also retired: the MDC context-propagation note #205 left live on master with a delete-on-merge marker, its content migrated to the javadoc of the code it described. The defect class is wider than these two lines. Eight sites render a collection or a state object into a log line, including PartitionStateManager rendering PartitionState at WARN where Lombok's @tostring prints every tracked record; they are recorded with greppable anchors in docs/inflight/bug-unbounded-log-lines.md for their own change. Upstream-Issue: confluentinc#631 Upstream-Issue: confluentinc#640 Forwarded: not-needed Applied-Upstream: no Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CMeraeBL2ycXVHEaTHM86W
[superseded - a quarantined test changed outcome] 🧪🔒 Quarantine Lane Report
🔴 expected while the owner PR is open · 🟡🎲 flapper, pass proves nothing · 🚨 a deterministic quarantined test passing means its fix landed: delete its Since the previous push: Updated for Superseded by a newer quarantine lane report. |
…lays clean as well Seed 8458454974018113374, from the INSTANCE_STALL firing on this branch's own CI, replayed on the same tree on an idle box: zero violations, 165.8s. With the sibling seed also clean, neither of today's two firings of the gating detector reproduces off a GitHub-hosted runner, which is now a pattern rather than a data point and reads as starvation re-arming the detector. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CMeraeBL2ycXVHEaTHM86W
[superseded - a quarantined test changed outcome] 🧪🔒 Quarantine Lane Report
🔴 expected while the owner PR is open · 🟡🎲 flapper, pass proves nothing · 🚨 a deterministic quarantined test passing means its fix landed: delete its Since the previous push: Updated for Superseded by a newer quarantine lane report. |
🧪🔒 Quarantine Lane Report
🔴 expected while the owner PR is open · 🟡🎲 flapper, pass proves nothing · 🚨 a deterministic quarantined test passing means its fix landed: delete its No quarantined test changed outcome since the previous push. Updated for |
…ts either spelling of empty (#418) MdcContextPropagationTest.anEmptyCallerContextIsHandledAndNothingLeaks turned master red on 2026-09-02, on the build for the #415 merge, with the shape an inflight note had held out of quarantine on one local sighting the day before: expected: null but was : {} at the test's own precondition, before Parallel Consumer is constructed. Four sightings by then: locally on #262, twice on consecutive heads of #416 with no Java in its diff, then master. The next master build went green with the test untouched. WHY. On logback 1.6.1, MDC.clear() nulls the thread-local but MDC.remove(lastKey) leaves an empty map behind, and the control loop's mailbox processing does a put-then-remove of the work descriptor. ProducerManagerTest and TransactionalBulkCommitTest drive that loop on the JUnit runner thread and never clear it. The ci profile reuses forks with intra-JVM parallelism off, so whether one of those classes precedes this one in the same fork is decided per run by fork placement: a per-run coin, which is the shape the ledger recorded and could not explain. The note's own hypothesis, that clear() leaves the empty map, was refuted by a probe against the build's jars. Class order alone reproduces it; the same order passes against the fix. WHAT. Two sessions diagnosed this on the same day without seeing each other and fixed the test in two different places, both sound. This lands both, because they answer two rules and each covers a case the other does not: clearCallersContext is @beforeeach as well as @AfterEach, so the test establishes the precondition it asserts rather than inheriting whatever the previous class left on a shared thread; and the assertion accepts null or an empty map, because PC treats them as the same thing and insisting on null asserted the logging binding, not PC - a binding that returns an empty map for an empty context would go red on the strict form even with the clear in place. MdcPropagation.capture()'s comment no longer claims it never returns an empty map. NOT DONE, on purpose. The control loop is untouched: the leftover {} is harmless on the controller thread it owns, and changing product code to satisfy a test precondition is the wrong direction. The two polluting tests do not clean up after themselves either: every future test that borrows the runner thread would need the same courtesy, and the one place the rule can be enforced is the test that depends on it. RECORDS. The inflight note is retired into one write-up under docs/solutions/test-flakiness/, which merges both sessions' probes, ruled-out lists and control arms, and carries the lesson this cost: run the prior-art search when you start the fix, not only the diagnosis - one command found both branches. The flake ledger's fixed-line points at it. #203 had already merged the other session's copy, so this also deletes that duplicate write-up with its two extra paragraphs folded in; #201 still carries the same commit and resolves to master's side when it next merges. Co-authored-by: Claude Fable 5.1 (1M context) <noreply@anthropic.com>
…s repo hygiene flags The sighting recorded from this branch's own CI names the branch three times; each reads in the past tense and stays true once it lands, so each now carries the post-merge: checked marker the gate asks for. Verified locally with PR_NUMBER=201, since the recut/ branch name does not resolve the PR on its own. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CMeraeBL2ycXVHEaTHM86W
…landed on both sides #201 squash-merged while this branch carried the same two release notes with a fresher MDC state. Both hunks were the same fact from two directions: master had dropped the load-factor bullet as "now #201" but still carried the stale "MDC is not captured" claim this branch had already corrected (#205 landed it). Resolved to the state that is true now that both have merged - the triage list is recorded as landed in the blockers note, and the release note's "bugs found while triaging" section, which had nothing open left in it, is removed along with its heading, which nothing cites. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CMeraeBL2ycXVHEaTHM86W
…and not confined
The second candidate for a `@ThreadConfined` declaration, and it came out the other way - which is
the more useful of the two outcomes, because the value was in checking the premise.
Every record of this field said it was written by `commitOffsetsThatAreReady()` and read by
`isTimeToCommitNow()`, both control-thread methods, which made confining it look like a free
retirement of two known `THREAD_SAFETY_VIOLATION` identities. `tryCommitOffsetsOnRevoke()` writes
it too, from inside `onPartitionsRevoked` - which the broker POLL thread runs, as that method's own
comment says two lines further down ("this runs inside the poll thread's rebalance callback").
Grepping every writer of the field is all it took to see it.
So the field is genuinely shared across the two threads this engine's defects are all about, and
`@ThreadConfined(CONTROL_THREAD)` would have been a FALSE declaration - one RacerD consumes and
does not check, so it would have believed it and gone quiet over a real race.
WHAT CHANGES
- `lastCommitTime` is `volatile`. Both writes are under `commitLock`; the read in
`isTimeToCommitNow()` is not, so the modifier is the only happens-before edge available without
moving the read under the lock. Same shape and same fix as `lastWorkRequestWasFulfilled`
(#201) and `failureReason`.
- The field's javadoc carries the poll-thread writer by name, because the reading every record got
wrong is the one the next person will form too.
- Both identities are deleted from `config/infer-known-findings.txt` in this commit.
- `docs/refactoring.md`'s entry is deleted rather than edited: the finding it staged is now fixed,
and what outlives it - which writer, which thread, why not confined - is in the register row and
the field's own javadoc.
WHAT IT MEANS FOR A USER
Nothing observable. The control thread could miss a commit the poll thread had just made on the
revoke path and commit again immediately - a redundant commit, not a wrong one. Worth fixing anyway
because it is a cross-thread field on the commit-timing path in a repo that tracks commit-timeout
flakes, and because the alternative was to declare it confined and stop looking.
HOW IT IS PROVEN
- `bin/infer-test.sh` (Infer v1.3.0, JDK 17): the `controlLoop` and `isTimeToCommitNow` identities
no longer fire. The register's warning that a retirement is not evidence of a fix applies, and the
code was read before the lines came out - the modifier establishes the edge, it does not merely
hide the report.
- `LastCommitTimeFenceTest` pins the modifier, following `PartitionStateDirtyFlagFenceTest`. No
functional test can see this race - its effect is an extra commit - so without a tripwire an
unrelated refactor drops the keyword and nothing goes red.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C3KnLcX4KibVMCP6sMfNgM
…ollections
THE CONTRACT IT PINS. WorkContainer's class javadoc now states that equality is identity while
compareTo orders by topic, partition and offset, so the two are deliberately inconsistent - and that
a raw container may therefore not be an element or a key of a sorted collection. Comparable only
RECOMMENDS the consistency; SortedSet and SortedMap REQUIRE it, because a sorted collection decides
"is this already here" through the comparator, so it would find one flight of a record under another
flight's coordinates. That is the lost-record defect this branch fixes, reached from the other side.
Nothing violates it today, which is exactly why prose was not enough: RetryQueue sorts a
NavigableMap keyed on its own WorkContainerSortKey and ProcessingShard's workMap is keyed on the
offset, and both are one refactor away from being written the other way round, at which point
nothing goes red.
WHAT THE RULE READS. Every field, method return type, method parameter and constructor parameter in
main code whose declared type erases to something assignable to SortedSet, NavigableSet, SortedMap,
NavigableMap, PriorityQueue, ConcurrentSkipListSet or PriorityBlockingQueue - which covers TreeSet,
TreeMap and ConcurrentSkipListMap without naming them - and whose FIRST declared type argument
erases to WorkContainer, via JavaParameterizedType.getActualTypeArguments().
Argument 0 is the element-or-key position for every one of those types, which is why one index
serves the set-shaped and map-shaped ones alike, and why a container in a sorted map's VALUE
position passes. Checking every argument instead would report ProcessingShard.workMap, be red on
arrival against correct code, and be deleted rather than obeyed.
RED PROOF, on main code. Adding
`private final java.util.TreeSet<WorkContainer<?, ?>> tripwire = new java.util.TreeSet<>();` to
ProcessingShard and changing nothing else:
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0
Architecture Violation [Priority: MEDIUM] - Rule 'no field, return type or parameter may be a
sorted collection whose elements or keys are raw WorkContainers, ...' was violated (1 times):
field bz.stub.parallelconsumer.state.ProcessingShard.tripwire is a java.util.TreeSet ordered by
a raw WorkContainer - its compareTo orders by topic, partition and offset while its equality is
identity, so a sorted collection would treat two flights of one record as the same element.
Exactly one violation, and the field one line above it - workMap, a NavigableMap holding containers
as values - was not reported. So that run is the positive and the negative control at once, on the
real tree. The field was then removed.
THE STANDING PROOF, because a measurement in a commit message protects nothing. A rule designed to
be green forever cannot distinguish "the code complies" from "the walk sees nothing" on its own
runs, which is the recurring ArchUnit failure recorded in
docs/inflight/static-archunit-main-code-rules.md. So the rule ships with the same shape
RebalanceCallbackRuleControlTest already uses: archfixture/SortedCollectionOfWorkContainers declares
the violation in all four positions plus the legitimate map-value case, and
WorkContainerSortedCollectionRuleControlTest hands it to the real rule object - not a copy - and
asserts it reports the four and not the fifth. The fixture is hand-imported because the rule's own
@AnalyzeClasses carries DoNotIncludeTests, which is what keeps a deliberate violation from turning
the production rule permanently red.
THE GAPS, stated in the test javadoc rather than left to be discovered:
- A sorted collection built inside a method body and never stored in a typed field or signature
declares nothing, so it is invisible. The rule covers the shapes that outlive a stack frame.
- The rule cannot see the comparator. A Comparator-taking constructor makes such a collection
legitimate, but only if the comparator is not compareTo - the requirement is that the ordering
agrees with equality, and equality is identity. The declaration is reported either way, so the
exemption costs a conversation instead of passing silently.
- It runs where core's test classpath reaches. The other modules' main code is not on it, and
covering them needs a wrapper per module in the TestConventionRules shape. Measured before
deciding against it: no main source outside core declares any of the seven types at all, and the
three non-core files naming WorkContainer pass it to a user function.
Every target is a Class literal rather than a string, so the rule cannot silently match nothing
after a rename - the failure mode that left both shard-map rules green when pointed at names that
do not exist.
DOCS, two edits.
docs/inflight/test-857-churn-storm-async-stalls.md: the 2026-09-07 sighting names this PR, and
check-branch-self-reference asks for an attestation that the sentence reads correctly after the
merge. It does - it is past tense about a permanent PR - so the paragraph takes the block marker.
docs/refactoring.md, "Breaking changes queued for next major version": the section's header did not
say what happens to an entry once its work lands, and four entries sat in it marked DONE. The rule
is now stated - an entry is deleted in the PR that lands it, because the commit message carries the
release-note content and the section only lists what is still QUEUED - and applied: the four DONE
entries are removed, three of them naming merged PRs (#267, #201 and #116,
each confirmed MERGED) and the fourth this branch's own.
That fourth entry was the recorded home of the RecordContext equality break, so the two citations
pointing at it are repaired rather than left dangling: WorkContainer's class javadoc and the
solutions write-up both now name the commit as the record, which is what the release notes are
generated from. The break itself is stated plainly in e491797's own body and in the PR
description.
VERIFICATION. Targeted run green (9 tests: the new rule, its two controls,
ShardStaleSweepReplacementEvictionTest and ArchitectureTest). Full unit suite green across every
module (bin/ci-unit-test.sh). bin/check-all.sh: 16 gates passed, none failed. bin/check-issue-refs.sh
and bin/check-file-refs.sh clean, the latter across the whole tree.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019SVm2cT6ZgUyPMim9CukYK

Fixes #155.
depends on #203
Description
Fixes the log-noise half of #155, the fork mirror of
confluentinc/parallel-consumer#402:
isPoolQueueLow(): Max loading factor steps reached: 100/100, repeated forever.And that closes the issue, despite being "half" of it. The report tangles two things: the stall,
and the endless WARN. The issue's own
## Fork statusrecords the stall as already fixed upstream byconfluentinc/parallel-consumer#547,
carried here by inheritance. So there is no remainder once this merges, and without a closing keyword
the issue would outlive its own fix.
This branch was re-cut onto today's master (its old head carried a redundant copy of the
io.confluent.*→bz.stub.*rename master has since completed itself), given a localsimplify+code-reviewpass, and then merged #203's branch in to take its sharedLogCapturehelper. It is the child of that stack — see Stack below.
The mechanism
AbstractParallelEoSStreamProcessor#checkPipelinePressure()runs on every control loop pass, andlogged that line at WARN, unrate-limited, whenever
DynamicLoadFactor#isMaxReached()held. Twoconfigurations reach that state, and both spam:
100/100. Once the queue has been below target longenough to step 2 → 100, the condition is permanent, so the line repeats for the life of the process.
PCModule#initDynamicLoadFactor()buildsDynamicLoadFactor(n, n)whenmessageBufferSizeis set, soisMaxReached()is true from construction. That is what theREADME's own PARTITION-ordering tuning advice tells people to configure, so following it earns
permanent log noise saying nothing is wrong. Nobody had reported that half; it fell out of reading
the code for the reported one.
Measured by the new test: 500 control loop passes produced 500 warnings, in both configurations.
The fix (reporting only - buffering behaviour is untouched)
DynamicLoadFactor#isStaticFactor()- true when the factor starts at its own ceiling(
messageBufferSize, orinitialLoadFactor == maximumLoadFactor). Such a factor never steps, somaybeStepUp()short-circuits instead of engaging the cool-down/warm-up machinery, and the ceilingis reported at debug: the user asked for a fixed buffer, and getting one is not a warning.
PCModulestates the intent through a newDynamicLoadFactor.fixedAt(n)factory.which a user may want to act on - but rate limited to once per 30s via the existing
RateLimiter(as already used by
BrokerPollSystemandProcessingShard), and reworded so it reads assaturation and names what to change. Deliberately not demoted to debug: weakening a real signal is
the wrong way to fix a volume problem.
measured against (
getPoolLoadTarget(), whatisPoolQueueLow()compared), and the loaded in-flighttarget (
target × factor) that raising the factor would grow. An earlier revision printed only thelatter under the "queued vs" phrasing, reporting
0 queued vs 1008for a check that had tested0 vs 16- caught in review, and now asserted on by the tests.The debug-vs-INFO question is settled. A review round argued the fixed-factor branch should be a
rate-limited INFO rather than DEBUG, on the grounds that a pinned buffer genuinely is what bounds
throughput. The premise was right and the javadoc was corrected to stop understating it — but the
remedy was rejected by the repo owner: users should not have to raise a library's sub-logger levels
to get information out of it, and a state the user deliberately configured is not news to them.
Reintroducing periodic lines at a level people run at is #155's own complaint in a slower
form. Both sides are argued inline in
reportLoadFactorAtCeiling's javadoc so this is notre-litigated.
An inverted pair is now rejected, not silently pinned
isStaticFactoris deliberatelyinitial == maximum, not>=. An inverted pair — an initialfactor above the maximum — also cannot step, but it is a typo rather than a request, and
classifying it as fixed would silence the only report that would ever have surfaced it.
Nothing validated the pair at all, so
ParallelConsumerOptions#validate()now rejects it:The check fires even when
messageBufferSizeis set, although the javadoc says the pair is"ignored" then: a buffer size makes the pair unused, not sensible, and accepting it silently is how
the typo survives to the configuration change that starts reading it again.
This is a breaking change — an application with an inverted pair starts today and will not after
this. Recorded in
docs/refactoring.md's release-gated section, whose gate is currently OPEN(0.6.0.0 is unreleased and already carries breaking changes), so it lands in the right release.
CHANGELOG.adocis untouched — the release-time generator owns that.Same-defect sweep: one instance, the one fixed. Dismissed with reasons —
maxConcurrency,maxFailureHistoryandmaxFactorare lone bounds with no partner to be ordered against;messageBufferSizeover the pair is an override, not an ordering;maxConcurrency × batchSizeis aproduct;
shutdownTimeout/drainTimeoutgovern disjoint modes.batchSizeis genuinely unvalidatedbut is a single-value lower bound — a different class, already owned by #311.
What the review pass changed
log.debughad four arguments,which binds SLF4J's varargs overload — an
Object[]plus four boxed ints allocated every pass,for the
messageBufferSizeconfiguration this PR is largely about. NowisDebugEnabled()-guarded,matching eight existing sites in the same file. Applying the same-defect rule to the path found
isPoolQueueLow()doing the identical thing with three arguments; also guarded. Those are the onlytwo that allocate on every pass. The automated review flagged a third unguarded three-argument
log.debugin the same method — thestepped up loading factor to {}line — and it isdeliberately left unguarded: it only fires when
maybeStepUp()actually stepped, whichDynamicLoadFactor#couldStep()bounds by the cool-down and by the factor's own ceiling, so it runstens of times over a process's life rather than thousands a minute. Guarding it would spend the
pattern's signal — this line is on a hot path — on a line that is not one. That reasoning now sits
beside the line, so the next sweep does not re-raise it.
TestParallelEoSStreamProcessorwrappers were deleted — the testlives in the same package, so
checkPipelinePressure()is reachable directly. The setter narrowed@Setter(PROTECTED)→@Setter(PACKAGE): protected on a public extendable class puts a test-onlyseam on the extension surface.
isMaxReached(), leavinggetCurrentFactor()at 2, so the asserted WARN readreached its maximum (2/100).AT_STALE_THREAD_WRITE_OF_PRIMITIVEno longer reportslastWorkRequestWasFulfilled. That finding pre-dated this branch's test seam and was alreadytracked in
docs/inflight/static-spotbugs-latent-findings.md, so thevolatileretires existingdebt rather than paying for new — the three ledgers naming it are updated.
Stack: this PR merges #203
docs/inflight/pr-blockers-and-collisions.mdrecorded a collision: this PR captured log output with aprivate
ListAppenderblock insideLoadFactorCeilingReportingTest, while #203 introduced areusable
bz.stub.parallelconsumer.internal.utils.LogCapturefor the same job. Neither was on master,so neither PR's duplication gate could see it.
Resolved here rather than deferred. #203 is the parent; its branch is merged in (zero
conflicts — the two touch disjoint regions of
AbstractParallelEoSStreamProcessor.java, verified bygrepping for both sides' anchors after the merge), and
LoadFactorCeilingReportingTestnow uses theshared helper. The private block and its private
messagesAthelper are gone.LogCaptureitselfneeded no change — its API was already sized for this consumer.
Both of its documented hazards apply here, and they need two different fixes:
@Isolated. The logger raised isAbstractParallelEoSStreamProcessor's — shared by every processor in the JVM and busy — held atDEBUG across 500 control-loop passes.
here: these tests drive
checkPipelinePressure()against aMockConsumerwith no records, so notopic name is theirs. The assertions are exactly the shapes one stray line breaks — an exact
hasSize(1)and twoisEmpty()checks. So the concurrency is removed instead, which needs@Execution(SAME_THREAD)as well as@Isolated:@Isolatedseparates a class from otherclasses, not this class's two capturing methods from each other. Dropping SAME_THREAD would have
been a silent weakening.
SubmitWorkToPoolShutdownRaceTeststill holds the last two inline capture blocks in the tree.#203 excluded them deliberately (unrelated to #169/#170, and it is a
timing-sensitive shutdown-race test); they remain recorded as open.
Tests
LoadFactorCeilingReportingTest(core, surefire, no broker) drives the realcheckPipelinePressure()500 times and asserts on captured log output:
fixedMessageBufferSizeDoesNotWarnOnEveryPass— withmessageBufferSizeset, zero WARN/ERROR, thecondition still observable at debug.
dynamicFactorAtCeilingWarnsOnceNotEveryPass— a dynamic factor at its cap warns exactly once.the wrong-target defect got in.
fixedFactorNeverSteps/factorWithHeadroomIsNotStatic— theisStaticFactorcontract.invertedLoadFactorPairIsRejectedplus accepting cases for the equal and ascending pairs. Theinverted case asserts
initialLoadFactor (100)andmaximumLoadFactor (9)as paired substrings;values are 100/9 rather than 100/10 because
"10"is a substring of"100"and that assertionwould have passed on nothing.
Control arm, re-established after every change to the test — most recently after the
LogCaptureconversion, because a conversion that quietly stops a test asserting is exactly the risk. Reverting
reportLoadFactorAtCeiling()to master's unrate-limited WARN givesTests run: 5, Failures: 2—fixedMessageBufferSizeDoesNotWarnOnEveryPass(expected empty) anddynamicFactorAtCeilingWarnsOnceNotEveryPass(expected: 1, but was: 500). That 500 is positiveproof the converted capture is live and pointed at the right logger. Restored → 5/5.
Also fixed:
ParallelConsumerOptionsTestcarried@Tag("transactions")and@Tag("confluentinc#355")at class level, left over from when it held one test. Moved onto the method they describe —
otherwise a
-Dexcluded.groups=transactionsrun, a combination the pom documents, would have silentlydropped the new validation tests. Nothing changes for current lanes.
Verification
check-branch-self-reference.shlanded on master after this branch was cut, and flagged fourdocs/inflight/lines naming #201. Each was read as it will read once this merges and attestedwith a
post-merge: checkedblock; none needed rewriting. One of them — inpr-mirror-fixes-and-what-they-close.md— is not this branch's sentence: #203 had alreadyattested that same paragraph, so the block here is copied byte-identical from its branch and the
eventual merge is a no-op rather than a conflict. That is why this PR touches a file otherwise
unrelated to it.
The known master-state flake
ParallelEoSStreamProcessorTest.processInKeyOrder(control-armed at8 runs on unmodified master producing 1 failure, recorded in
docs/inflight/test-untracked-ci-flakes.md) did not fire in any run.Merge order
Merge #203 first. Once it lands, this PR's diff against master is its own work again. The
PR-dependency gate holds this one until then.
Checklist
docs/refactoring.md(the breaking-change entry, and the retired follow-up),docs/inflight/pr-blockers-and-collisions.md,docs/inflight/issue-response-155.md(draftedreply, held for the pre-release sweep),
src/docs/development/upstream-map.yaml.CHANGELOG.adocdeliberately untouched, perAGENTS.mdthe validation addition and the fix(logging) astubbs#169 astubbs#170: bound the dropped-batch and user-function-failure log lines #203 merge
N/A- no CI runner or workflow changes