Skip to content

test(soak) astubbs#175: re-run on v0.6.0.0 with the intake latch held open finds no commit-response timeout - #518

Merged
astubbs merged 1 commit into
masterfrom
test/175-soak-rerun-on-v6
Sep 18, 2026
Merged

astubbs merged 1 commit into
masterfrom
test/175-soak-rerun-on-v6

Conversation

@astubbs

@astubbs astubbs commented Sep 18, 2026 •

Copy link
Copy Markdown
Owner

Description

Records three arms of CommitResponseTimeoutSoakIT on the released code (v0.6.0.0 plus two docs commits, 2e6f13e) in the scenario's Calibration status and in docs/inflight/bug-177-commit-response-timeout-unreproduced.md. No product or test code changes - the record is the deliverable.

The question since #471: does a BARE "Timeout waiting for commit response" - the poller wedged but alive, the one mechanism nobody has characterised - ever appear on the released code when the workload is arranged so the intake gate does not latch and commits keep happening for the whole soak. #471's two 30-minute runs could not answer it: the retry-forever poison latched the record-intake gate inside the first minute and no commit was attempted after that, which #487 measured and #497 made loud.

Common to all arms: seed 3747722682837130843, failureFraction 0.5, PERIODIC_CONSUMER_SYNC at 1s, 1000 keys over 20 partitions, maxConcurrency 14, 100ms user function, 1000 records every 20s, retry-forever; Testcontainers confluentinc/cp-kafka:7.9.0 on Docker; a Linux x86_64 workstation with every JVM pinned to 8 processors; no other Maven JVM on the box at the start of any arm; -Dpc.loadgate.log.level=info and -Dsoak.progressInterval=PT30S. Commit activity was read from the broker's __consumer_offsets with the broker's own formatter, because every commit-path line in the product is DEBUG.

Arm A - control, unchanged, 6 minutes. The latch reproduces on the released code to the number: succeeded frozen at 451 from the first 30s sample, the #497 WARN 13.2s after the banner (inShards=549 parkedForRetry=140 workable=409 vs 42, pausedPartitions=20), every partition paused at all 11 samples, failed 49,710 (138/s). The broker saw 8 commit instants, all inside the first 7.3s, and none in the remaining 5m50s. Load average 2.93 -> 1.54.

Arm B - 30 minutes, -Dsoak.messageBufferSize=20000, KEY, everything else the reporter's. No findings. The gate held open for 7m06s (loaded=false, zero paused partitions, at every sample to inShards=20,103), then latched at inShards=20993 vs target(14)*loadingFactor(1429)=20006 and stayed latched for the remaining 22m54s - so the #487 arm-3 value buys about seven minutes of this producer, not thirty, because under KEY nearly everything produced is held. But the assertion had already stopped being falsifiable at 3m06s, four minutes before the latch, and the gate is not why: successes went 680, 784, 869, 884, 893, 895, 897 and froze with the gate open, and the broker's last commit landed 185.7s after the first (27 commit instants). Under KEY each key retires records until its first poisoned one and never again, so permanent poison bounds successes by the key space, and dirty is derived from completions, so once successes stop commitAndWait is never entered. No buffer size changes that under KEY. Succeeded 897, failed 250,067 (139/s). Load average 1.17 -> 0.13.

Arm C - 30 minutes, -Dsoak.messageBufferSize=100000 -Dsoak.ordering=UNORDERED: the only combination of the existing knobs that holds both conditions. UNORDERED lifts the head-of-line bound so healthy records keep retiring; 100,000 clears the ~90,000 a thirty-minute producer can have held. Both held for the whole run: loaded=false and zero paused partitions at all 59 samples, inShards peaking at 51,949 against 100,002, no WARN, no back-pressure line; succeeded 37,641 and still rising at the end, failed 213,533 (119/s); a commit in every one of the thirty minutes - 619 commit instants, the last 1794.8s after the first, offset-map metadata growing to 768 base64 chars per partition. No findings: no bare timeout, no poller death, no thread dump written. Load average 0.12 -> 1.46.

What it establishes, as a rate under conditions: on v0.6.0.0, one instance, a quiet box, zero bare commit-response timeouts and zero poller deaths across 654 acknowledged commit instants, 619 of them in thirty minutes of a continuously exercised commit path. One run at one shape; it says the shape did not reproduce once, not that it cannot.

What it does not establish: anything about #175's own configuration (128 partitions, concurrency 64, a user function of minutes - still unrun), a loaded box, a rebalance, more than one instance, durations past thirty minutes, or KEY ordering with the commit path alive - which on this evidence needs the per-attempt-failure arm rather than a buffer size. Arm C departs from #177's reporter's ordering deliberately, and says so in both records.

No new knobs were added: -Dsoak.duration, -Dsoak.messageBufferSize and -Dsoak.ordering already existed. The only thing genuinely missing from the harness - evidence that commits were being attempted - was taken from the broker rather than by adding a log line, and the command is recorded in the javadoc so the next run can do the same.

Tracker: #175 (confluentinc#809).

Checklist

  • Docs updated - the class javadoc calibration block and docs/inflight/bug-177-commit-response-timeout-unreproduced.md
  • User-facing feature documentation data added under docs/features/ - N/A - a soak record, no capability
  • Tests added/updated - N/A - no code change; the runs are recorded in the javadoc of the test they exercised
  • docs/inflight/ working note (pr-/branch-) started at the PR's first commit - N/A - one commit recording three runs; the note it updates is the working note
  • Title & body reflect the final content of this PR
  • Ran ce-simplify and ce-code-review locally - N/A - docs only; bin/check-all.sh passes

🤖 Generated with Claude Code

https://claude.ai/code/session_019XS64Xttx4vF5datYh7fmk

…inds no commit-response timeout

Records three arms of CommitResponseTimeoutSoakIT on the released code (v0.6.0.0
plus two docs commits, 2e6f13e) in the scenario's Calibration status and in
docs/inflight/bug-177-commit-response-timeout-unreproduced.md. No product or
test code changes - the record is the deliverable.

The question since #471: does a BARE "Timeout waiting for commit
response" - the poller wedged but alive, the one mechanism nobody has
characterised - ever appear on the released code when the workload is arranged
so the intake gate does not latch and commits keep happening for the whole
soak. #471's two 30-minute runs could not answer it: the retry-forever
poison latched the record-intake gate inside the first minute and no commit
was attempted after that, which #487 measured and #497 made loud.

Common to all arms: seed 3747722682837130843, failureFraction 0.5,
PERIODIC_CONSUMER_SYNC at 1s, 1000 keys over 20 partitions, maxConcurrency 14,
100ms user function, 1000 records every 20s, retry-forever; Testcontainers
confluentinc/cp-kafka:7.9.0 on Docker; a Linux x86_64 workstation with every
JVM pinned to 8 processors; no other Maven JVM on the box at the start of any
arm; -Dpc.loadgate.log.level=info and -Dsoak.progressInterval=PT30S. Commit
activity was read from the broker's __consumer_offsets with the broker's own
formatter, because every commit-path line in the product is DEBUG.

Arm A - control, unchanged, 6 minutes. The latch reproduces on the released
code to the number: succeeded frozen at 451 from the first 30s sample, the
#497 WARN 13.2s after the banner (inShards=549 parkedForRetry=140
workable=409 vs 42, pausedPartitions=20), every partition paused at all 11
samples, failed 49,710 (138/s). The broker saw 8 commit instants, all inside
the first 7.3s, and none in the remaining 5m50s. Load average 2.93 -> 1.54.

Arm B - 30 minutes, -Dsoak.messageBufferSize=20000, KEY, everything else the
reporter's. No findings. The gate held open for 7m06s (loaded=false, zero
paused partitions, at every sample to inShards=20,103), then latched at
inShards=20993 vs target(14)*loadingFactor(1429)=20006 and stayed latched for
the remaining 22m54s - so the #487 arm-3 value buys about seven minutes
of this producer, not thirty, because under KEY nearly everything produced is
held. But the assertion had already stopped being falsifiable at 3m06s, four
minutes before the latch, and the gate is not why: successes went 680, 784,
869, 884, 893, 895, 897 and froze with the gate open, and the broker's last
commit landed 185.7s after the first (27 commit instants). Under KEY each key
retires records until its first poisoned one and never again, so permanent
poison bounds successes by the key space, and dirty is derived from
completions, so once successes stop commitAndWait is never entered. No buffer
size changes that under KEY. Succeeded 897, failed 250,067 (139/s). Load
average 1.17 -> 0.13.

Arm C - 30 minutes, -Dsoak.messageBufferSize=100000 -Dsoak.ordering=UNORDERED:
the only combination of the existing knobs that holds both conditions.
UNORDERED lifts the head-of-line bound so healthy records keep retiring;
100,000 clears the ~90,000 a thirty-minute producer can have held. Both held
for the whole run: loaded=false and zero paused partitions at all 59 samples,
inShards peaking at 51,949 against 100,002, no WARN, no back-pressure line;
succeeded 37,641 and still rising at the end, failed 213,533 (119/s); a commit
in every one of the thirty minutes - 619 commit instants, the last 1794.8s
after the first, offset-map metadata growing to 768 base64 chars per partition.
No findings: no bare timeout, no poller death, no thread dump written. Load
average 0.12 -> 1.46.

What it establishes, as a rate under conditions: on v0.6.0.0, one instance, a
quiet box, zero bare commit-response timeouts and zero poller deaths across 654
acknowledged commit instants, 619 of them in thirty minutes of a continuously
exercised commit path. One run at one shape; it says the shape did not
reproduce once, not that it cannot.

What it does not establish: anything about #175's own configuration
(128 partitions, concurrency 64, a user function of minutes - still unrun),
a loaded box, a rebalance, more than one instance, durations past thirty
minutes, or KEY ordering with the commit path alive - which on this evidence
needs the per-attempt-failure arm rather than a buffer size. Arm C departs from
#177's reporter's ordering deliberately, and says so in both records.

No new knobs were added: -Dsoak.duration, -Dsoak.messageBufferSize and
-Dsoak.ordering already existed. The only thing genuinely missing from the
harness - evidence that commits were being attempted - was taken from the
broker rather than by adding a log line, and the command is recorded in the
javadoc so the next run can do the same.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XS64Xttx4vF5datYh7fmk
@astubbs

astubbs commented Sep 18, 2026

Copy link
Copy Markdown
Owner Author

@claude review this

@claude

claude Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

Claude finished @astubbs's task in 2m 52s —— View job


Review

  • Gather context (diff, branch record, PR body)
  • Review CommitResponseTimeoutSoakIT.java javadoc additions
  • Review docs/inflight/bug-177-commit-response-timeout-unreproduced.md changes
  • Post findings

Scope confirmed: every changed line in CommitResponseTimeoutSoakIT.java is inside a javadoc comment (git diff shows the only touched lines start with *) - this is genuinely a docs-only PR, no executable test/product code changed, matching the description.

Cross-checked the numbers for internal consistency (the PR's own bar for a soak record) rather than taking them on faith:

  • Total commit instants: 8 (arm A) + 27 (arm B) + 619 (arm C) = 654, matching "654 acknowledged commit instants" and the 619/35 split in both the javadoc and the inflight note.
  • Failure rates: 49,710/360s ≈ 138/s (arm A), 250,067/1800s ≈ 139/s (arm B), 213,533/1800s ≈ 119/s (arm C) - all match the stated rates.
  • Load-gate thresholds: target(14)*loadingFactor = 42 (default), 20,006 (messageBufferSize=20000 → 1429), 100,002 (messageBufferSize=100000 → 7143) - the arithmetic behind each vs comparison holds, and the "latched"/"held open" verdicts are consistent with the reported inShards values against those thresholds.
  • Citations resolve: PartitionState#isDirtyAt, ConsumerOffsetCommitter, the referenced docs/inflight/upstream-tell-809-833-the-hang-is-fixed.md and upstream-175-sporadic-commit-timeouts.md, and the {@value} constants (KEY_SPACE, PARTITIONS, BURST_INTERVAL_SECONDS, COMMIT_RESPONSE_TIMEOUT, POLLER_DIED) all exist as referenced.
  • Issue references are all correctly qualified (astubbs#NNN / confluentinc#NNN), no bare numbers.
  • No duplicate/colliding <h2>Calibration status headings - this cleanly adds a third dated section alongside 2026-09-07 and 2026-09-08, following the established pattern.
  • The javadoc and the docs/inflight/ note tell the same story without contradicting each other, and the note's arm-B analysis (key-space-bounded successes under KEY, dirty derived from completions) is the same causal chain in both places, stated once in full in the javadoc and summarized in the note - no drift between the two.

Minor, non-blocking observation: the note's inflight-vetted marker is still dated 2026-09-08, predating this PR's substantial addition (a new dated section, a revised "what to run next" item 2). That's not a defect - vetting is a separate act from authoring - but since the note now describes a materially different state of the investigation, a future vet sweep should treat 2026-09-08's marker as covering only the pre-existing content, not the new section.

Content-wise, this is a well-instrumented negative result: it states plainly what it establishes (zero bare timeouts across 654 broker-observed commit instants on v0.6.0.0, one box, one run) and, more usefully, what it explicitly does not (#175's own 128-partition/concurrency-64 shape, a loaded box, rebalances, multi-instance, KEY ordering with a live commit path). Arm B's diagnosis - that under KEY the buffer size was never the blocker, key-space-bounded successes was - is a genuine finding buried in what could have read as a "no findings" arm, and it's correctly surfaced as such in both the javadoc and the inflight note.

No issues found that would block merge.

@github-actions

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@github-actions

Copy link
Copy Markdown

✅ Duplicate Code Report

Two 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

PR Base Change
Clones 27 27 ➖ 0
Duplicated lines 949 949 ➖ 0
Duplication 0.36% 0.36% ➖ 0
Rule Limit Status
Max duplication 0.5% ✅ Pass (0.36%)
Max increase vs base +0.1% ✅ Pass (+0.00%)

No new clones introduced by this PR.

✅ jscpd (language-agnostic)

PR Base Change
Clones 106 106 ➖ 0
Duplicated lines 1503 1503 ➖ 0
Duplication 0.81% 0.81% ➖ 0
Rule Limit Status
Max duplication 2% ✅ Pass (0.81%)
Max increase vs base +0.1% ✅ Pass (+0.00%)

No new clones introduced by this PR.

Powered by astubbs/duplicate-code-cross-check

astubbs added a commit that referenced this pull request Sep 18, 2026
…at carried the telling says so

confluentinc#809 got its reply with the soak rerun on the released code
(#518), #175 its corrected Fork status, and #44 the
correction to its attribution of 809 and 833 to #100. That was the
whole of upstream-tell-809-833-the-hang-is-fixed.md, which the earlier
audit missed because its wording differed; its three items now say done
with links, it is linked from the release-response note, and the two
boxes are ticked.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XS64Xttx4vF5datYh7fmk
@codecov

codecov Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.32%. Comparing base (2e6f13e) to head (9830783).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@             Coverage Diff              @@
##             master     #518      +/-   ##
============================================
+ Coverage     82.68%   83.32%   +0.63%     
- Complexity     1596     1602       +6     
============================================
  Files            96       96              
  Lines          5475     5475              
  Branches        554      554              
============================================
+ Hits           4527     4562      +35     
+ Misses          751      718      -33     
+ Partials        197      195       -2     
Flag Coverage Δ
chaos 53.22% <ø> (?)
integration 63.22% <ø> (+0.11%) ⬆️
performance 47.77% <ø> (?)
unit 79.54% <ø> (-0.08%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

Copy link
Copy Markdown

🟢 Throughput — OK

This branch measured about 3% faster than master, on the one test this measures. That is INSIDE this test's own run-to-run spread of about 17%, so read it as a reading and not as a result - re-running the same commit moves it by about as much.

What Value Meaning
Compared with master 1.032 above 1.00 is faster, below is slower
Subject test took 58.0s the test under measurement
Control tests took 33.2s the other tests in this same run
Subject ÷ controls, here 1.747 not a speed - a shape that cancels machine speed
Subject ÷ controls, master 1.803 median of recent master runs
Reported rate 70091 rec/s this machine only; not comparable across runners

Allowable range 🟢 ≥ 0.70 · 🟡 0.50–0.70 (about a 30% loss) · 🔴 < 0.50 (about a 50% loss)

What the numbers mean, and what they cannot tell you

The one that gets misread. Subject ÷ controls is a shape, not a speed. 1.7 means the subject took 1.7 times as long as the control tests in the same run - it says nothing about master on its own, and a reviewer has already read it as "1.7x faster than master". Only Compared with master answers that question.

Why a shape and not a rate. A rate depends on which runner you drew. A shape does not: every test here processes a fixed number of records, so a runner twice as slow doubles the subject and the controls together and leaves their ratio alone. That is the whole trick, and it is why the reported rate is shown last and labelled as this machine only.

Reading the comparison. master ÷ this run. Above 1.00 the subject is proportionally quicker here than on master; below 1.00 it is slower. 0.50 means it takes twice as long relative to its controls - that is the failing bound, not a small one.

By conservation, not by correction. Every test in this lane processes a fixed number of records, so within one run the ratio of one test's time to another's is invariant under machine speed — a runner twice as slow doubles both terms and leaves the ratio alone. There is no machine-index correction to be wrong, because nothing needed correcting. share = subjectSeconds / controlSeconds, both from this same run.

Per-method times, not class times. A class time is work + setup, and container startup and @BeforeAll do not scale with work — they are the non-conserved term, and leaving them in breaks the invariant.

Reference is the median of 10 recent perf baseline (master) run(s), read from their artifacts. There is no committed baseline to go stale, and a share is dimensionless, so an old entry stays comparable to a new one without re-baselining. Shares observed: 1.537 – 2.253.

What this still cannot do. It removes machine-to-machine variance. It does not remove this test's own run-to-run variance, measured at about 30% on a single unchanged commit while its controls stayed within 5%. That is a property of the test, not of the comparison, and no arithmetic here can touch it — which is why the reference is a median and the bounds are deliberately coarse. 🟡 means look at this; only 🔴 is outside the measured spread.

Runs used: 2e6f13e, e6a429d, 8730ff0, 12ba9c7, c1c8e5e, 78ec881, c9b01c5, 43ed239, b1a6dbd, a37d148

Updated for 9830783 · run 35295174457 · 2026-09-18 01:30 UTC

@github-actions

Copy link
Copy Markdown

⚠️ SpotBugs Report

354 bug(s) found (rule-level exclusions only - see docs/inflight/static-spotbugs-rule-registry.md). See the annotations on the Files Changed tab for details.

Updated for 9830783 · run 35295174457 · 2026-09-18 01:31 UTC

@astubbs astubbs left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

astubbs added a commit that referenced this pull request Sep 18, 2026
…te-up, and the ranking note drops its v6 gate

Two more retirements the v6 sweep left behind, both decided with the owner on 2026-09-18.

upstream-tell-809-833-the-hang-is-fixed.md had all three of its tellings done and its own vetted
line said it was kept only for the corrected diagnosis. That diagnosis had no durable home: the
role-holder-dies write-up covers the close path, not the commit-response seam. It now lives in
docs/solutions/logic-errors/the-only-producer-of-commit-responses-died-so-every-waiter-reported-a-timeout-2026-09-18.md:
the mechanism (the broker-poll thread is the only producer of commit responses), the inference the
note first got wrong (#100 removed one route; the class closed when #177 published the
death), the message that lied about its own budget, the dirty asymmetry a reproduction has to
respect, and who was told. Every citer moves with it: two inflight notes, the response tracker,
and the CommitResponseTimeoutSoakIT javadoc. #518 edits neither of those javadoc lines, so
the two branches merge cleanly in either order.

process-candidate-ranking.md carried "What gates v6, as the sweep read it" - a dated reading of a
release that has since shipped. It is a git-show pointer now, like the decisions section before it;
"Ready picks" stays, because it is the note's live purpose.

release-0.6.0.0.md is deliberately NOT retired: the owner's call is to keep it for a while, since
v6 was a special release and the register carries signal nothing else does. Revisit after
#517 merges.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XS64Xttx4vF5datYh7fmk
@astubbs
astubbs merged commit cf22050 into master Sep 18, 2026
37 of 41 checks passed
@astubbs
astubbs deleted the test/175-soak-rerun-on-v6 branch September 18, 2026 02:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant