Files
romanett a504c3e248 Fix certificate leak when rejected-store enqueue races manager disposal (#4337)
The macOS legs of the Build and Test workflow intermittently failed the
assembly-level teardown of the Sessions (also History and Client) test
suites with "Certificate leak detected: 1 instance(s) created but not
disposed" while every test passed (master run 33004556144:
created=16777, disposed=16776; PR run 33207357247: created=17373,
disposed=17372). The count held steady through the teardown's full
quiescence window, so the missing disposal was permanent, not a slow
in-flight drain.

RejectedCertificateProcessor.EnqueueAsync takes a per-certificate AddRef
for the queued WriteRequest before TryWrite. Once
CertificateManager.DisposeAsync has completed the channel writer, the
bounded DropOldest channel refuses TryWrite and the WriteAsync fallback
throws ChannelClosedException, abandoning the request: nothing releases
the references it owns, so every certificate in the chain keeps one
leaked reference for the life of the process. A validation that races
manager disposal — a keep-alive, reconnect or fire-and-forget channel
teardown rejecting a certificate while the fixture tears down — lands in
exactly this window, and the slower loaded macOS runners make that race
likely. The abandoned request also left FlushRejectedAsync waiters
hanging on a drain marker that never resolves, and surfaced unobserved
ChannelClosedExceptions through the fire-and-forget
EnqueueRejectedCertificate discard.

Route both enqueue paths through a fallback that, when the write cannot
commit the request to the channel, disposes the request's chain
(releasing the per-certificate references), resolves its completion as
"not processed" so drain waiters are unblocked, and swallows only the
benign ChannelClosedException shutdown race; cancellation still
propagates. The teardown leak detector needs no change: its quiescence
poll was correctly reporting a genuine leak.

The new regression test reproduces the CI signature deterministically
against the previous code (ChannelClosedException plus "Certificate leak
detected: 1 instance(s) created but not disposed (created=1,
disposed=0)" in the assembly teardown) and passes with the fix.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-29 10:30:19 +02:00
..