Skip to content

Keep the send callback scalar-replaceable: drop the volatile flag - #21

Merged
dirkjink merged 1 commit into
mainfrom
perf/sender-callback-scalar-replaceable
Sep 7, 2026
Merged

Keep the send callback scalar-replaceable: drop the volatile flag#21
dirkjink merged 1 commit into
mainfrom
perf/sender-callback-scalar-replaceable

Conversation

@dirkjink

@dirkjink dirkjink commented Sep 7, 2026

Copy link
Copy Markdown
Member

Follow-up to the benchmark validity check: SenderPathBenchmark unbound had moved from 112 to 160 B/op since 1c8b048 (the SendCallback class). Cause, verified by experiment: the @Volatile on errorReported pins the callback object against C2's scalar replacement wherever the callback does not escape.

Change

  • SendCallback.errorReported is a plain field with a Safety: rationale: the only concurrent writer is an asynchronous error callback racing the read after send() returns, and both outcomes are correct (fallback-only, or dispatched plus the later send.error fallback). Production semantics unchanged; with the real KafkaProducer the callback escapes into the batch either way.
  • SenderPathBenchmark KDoc notes the scalar-replaceability guard; changelog entry.

Acceptance check (benchmark report protocol: 3 forks, 5+5 iterations, -prof gc)

variant 2026-08-30 this PR
unbound 112 B/op 112.000 B/op (all forks)
bound 176 B/op 224 B/op — and the pre-change code 6c4f2b8 also reads 224 B/op on the same JVM in the same session (control file), i.e. JIT-profile variance, not a code effect

Raw outputs under benchmarks/results/2026-09-07/. A value-forwarding onCompletion was tried against the bound-path difference and reverted as measurably neutral.

mvn verify (offline, incl. ktlint): 269 tests green; dokka:dokka with failOnWarning passes.

🤖 Generated with Claude Code

https://claude.ai/code/session_015GfdGp7eUrjJKBvcJUx3q2

The synchronous-failure flag of SendCallback is a plain field. Its only
concurrent writer is an asynchronous error callback racing the read
after send() returns, and both outcomes of that race are correct
(fallback-only or dispatched-plus-later-fallback), so the volatile
bought a guarantee no reader needs - and pinned the callback object
against C2's scalar replacement wherever the client does not retain
it (+48 B/op on the SenderPathBenchmark unbound path since 1c8b048).

Acceptance check per the benchmark report's protocol (3 forks, 5+5
iterations, -prof gc), raw output under benchmarks/results/2026-09-07:
unbound 112.000 B/op - the 2026-08-30 baseline restored. The
metrics-bound variant reads 224 B/op, but so does the pre-change code
6c4f2b8 measured on the same JVM in the same session (control file),
i.e. JIT-profile variance the report already documents, not a code
effect. A value-forwarding onCompletion was tried against it and
reverted as measurably neutral.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015GfdGp7eUrjJKBvcJUx3q2
@dirkjink
dirkjink merged commit 6ee82a7 into main Sep 7, 2026
6 checks passed
@dirkjink
dirkjink deleted the perf/sender-callback-scalar-replaceable branch September 7, 2026 20:11
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