Skip to content

fix(extractor): SemaphoreSlim field dispose-optional unless AvailableWaitHandle is read (mined Npgsql)#92

Merged
PhysShell merged 3 commits into
mainfrom
claude/fp-semaphore-field-optional
Jun 23, 2026
Merged

fix(extractor): SemaphoreSlim field dispose-optional unless AvailableWaitHandle is read (mined Npgsql)#92
PhysShell merged 3 commits into
mainfrom
claude/fp-semaphore-field-optional

Conversation

@PhysShell

@PhysShell PhysShell commented Jun 23, 2026

Copy link
Copy Markdown
Owner

The sound, scoped version of the SemaphoreSlim FP — per the review on #91 and your choice of field-scoped + AvailableWaitHandle-gated.

What

SemaphoreSlim.Dispose() only frees a lazily-allocated wait handle — allocated solely when AvailableWaitHandle is read — so a SemaphoreSlim field used purely for Wait/WaitAsync/Release leaks nothing and is dispose-optional. Mined: NpgsqlDataSource._setupMappingsSemaphore.

Two guards (both raised on the earlier attempt in #91)

  • FIELD-scoped — the exemption lives in the field-disposable detector, not in the shared IsDisposeOptional. So the flow-locals detector and the deliberate method-bounded semLeak control (a prior ShareX decision: SemaphoreSlim stays tracked for locals) are left untouched. (CodeRabbit)
  • AvailableWaitHandle-GATED — if .AvailableWaitHandle is read on the field, the wait handle exists and Dispose must release it, so the field stays tracked. (Codex) The gate keys on a this/bare-receiver .AvailableWaitHandle access.

Regression sample (SemaphoreFieldSample.cs)

  • OptionalSemaphore._optionalSem (Wait/Release only) → silent
  • Controls still warn: WaitHandleSemaphore._handleSem (reads AvailableWaitHandle) and HoldsCtsField._ctsControl (a non-SemaphoreSlim CTS — proves the exemption is type-scoped)
  • The existing FlowLocalsSample.semLeak local control is unaffected (separate detector path) — its --flow-locals assertion still passes.

Relationship to #91

Independent of #91 (which is the .Close()-release fix, now green & ready to merge). Different code locations in the field detector; whichever merges second takes a trivial ci.yml rebase.

🤖 Generated with Claude Code


Generated by Claude Code

Summary by CodeRabbit

  • Tests

    • Added new Roslyn sample scenarios covering SemaphoreSlim field disposal behavior, including cases with no wait-handle access, aliased access, and verification that non-SemaphoreSlim owned-disposable fields are unaffected.
    • Updated CI checks to ensure OWN001 output depends on whether AvailableWaitHandle is read (including alias-based reads), while confirming unrelated leak expectations remain unchanged.
  • New Features

    • Improved analyzer precision for owned-disposable (WPF003) detection by treating certain SemaphoreSlim fields as dispose-optional when the wait handle is never accessed, while still warning when it is read.

…bleWaitHandle is read (mined Npgsql)

The sound, scoped version of the SemaphoreSlim FP (per review on #91 + the user's
choice). SemaphoreSlim.Dispose() only frees a LAZILY-allocated wait handle —
allocated solely when AvailableWaitHandle is read — so a SemaphoreSlim field used
purely for Wait/WaitAsync/Release leaks nothing and is dispose-optional. Mined:
NpgsqlDataSource._setupMappingsSemaphore.

Two guards keep it sound, both flagged by reviewers on the earlier attempt:
- FIELD-scoped: the exemption lives in the field-disposable detector, NOT in the
  shared IsDisposeOptional — so the flow-locals detector and the deliberate
  method-bounded `semLeak` control (a prior ShareX decision: SemaphoreSlim stays
  tracked for locals) are left untouched (CodeRabbit).
- AvailableWaitHandle-GATED: if `.AvailableWaitHandle` is read on the field, the
  wait handle exists and Dispose must release it, so the field STAYS tracked
  (Codex). The gate keys on a this/bare-receiver `.AvailableWaitHandle` access.

Regression sample SemaphoreFieldSample.cs: OptionalSemaphore._optionalSem (Wait/
Release only) is SILENT; controls still warn — WaitHandleSemaphore._handleSem
(reads AvailableWaitHandle) and HoldsCtsField._ctsControl (a non-SemaphoreSlim
CTS). The existing FlowLocalsSample.semLeak local control is unaffected (separate
detector path), asserted in the --flow-locals step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED
// Dispose must release it -> the field must STILL warn OWN001.
public sealed class WaitHandleSemaphore
{
private readonly SemaphoreSlim _handleSem = new SemaphoreSlim(0, 1);
// must STILL warn — the exemption is SemaphoreSlim-specific, not a blanket "any field".
public sealed class HoldsCtsField
{
private readonly CancellationTokenSource _ctsControl = new CancellationTokenSource();
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 91fa35dc-6966-4006-9906-f56e96baa08c

📥 Commits

Reviewing files that changed from the base of the PR and between 018b1f7 and 5d577d4.

📒 Files selected for processing (2)
  • .github/workflows/ci.yml
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
  • .github/workflows/ci.yml

📝 Walkthrough

Walkthrough

Adds a SemaphoreSlim-specific dispose-optional suppression to the OwnSharp extractor: it collects per-class SemaphoreSlim field names where AvailableWaitHandle is read (including aliased access) and skips emitting OWN001 for those not in the set. Four sample classes and corresponding CI assertions are added to validate the exemption boundaries.

Changes

SemaphoreSlim Dispose-Optional Suppression

Layer / File(s) Summary
Sample classes for SemaphoreSlim field scenarios
frontend/roslyn/samples/SemaphoreFieldSample.cs
Adds four sealed sample classes: OptionalSemaphore (uses WaitAsync/Release only), WaitHandleSemaphore (reads AvailableWaitHandle), AliasedWaitHandleSemaphore (reads AvailableWaitHandle via local alias), and HoldsCtsField (CancellationTokenSource field) to cover intended detection cases.
Extractor SemaphoreSlim suppression logic
frontend/roslyn/OwnSharp.Extractor/Program.cs
Adds a waitHandleSemaphores set populated by scanning AvailableWaitHandle member accesses on this/bare receivers and alias-aware locals; extends the owned-disposable field emission loop to skip emitting a leak fact for System.Threading.SemaphoreSlim fields absent from that set.
CI wiring and OWN001 assertions
.github/workflows/ci.yml
Registers SemaphoreFieldSample.cs as a wpf-extractor input and adds grep-based OWN001 assertions: silence for OptionalSemaphore, warn for WaitHandleSemaphore and AliasedWaitHandleSemaphore, warn for HoldsCtsField (non-SemaphoreSlim control), with a deferred note for the local SemaphoreSlim leak.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 A semaphore slim sits quietly still,
No wait-handle read? Then no warning, no drill.
But access that handle and OWN001 rings,
The rabbit checks carefully each of these things.
Facts flow through the extractor, CI agrees—
Dispose-optional fields handled with ease! ✨

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately summarizes the main change: introducing a SemaphoreSlim-specific dispose-optional exemption that depends on AvailableWaitHandle access, matching the PR's core implementation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/fp-semaphore-field-optional

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f545f3dfc8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +2309 to +2310
if (ma.Name.Identifier.Text == "AvailableWaitHandle" && ThisFieldName(ma.Expression) is { } whf)
waitHandleSemaphores.Add(whf);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Track AvailableWaitHandle reads through field aliases

When a SemaphoreSlim field is copied to a local and the local reads the handle, e.g. var sem = _sem; sem.AvailableWaitHandle.WaitOne(), ThisFieldName(ma.Expression) records the local name (sem) rather than the field name (_sem). The later exemption therefore still treats _sem as dispose-optional and suppresses the leak even though AvailableWaitHandle allocated the handle; the alias map built just above needs to be used here to credit aliased field reads.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/roslyn/OwnSharp.Extractor/Program.cs`:
- Around line 2308-2310: In the foreach loop iterating over
MemberAccessExpressionSyntax nodes where the Name is "AvailableWaitHandle",
replace the current approach of using ThisFieldName(ma.Expression) which keys by
bare identifier text. Instead, use symbol binding to resolve ma.Expression to
its actual IFieldSymbol and add the field symbol itself to waitHandleSemaphores
rather than the text-based field name. This will prevent shadowing locals or
parameters from being mistaken for the actual field.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 84c54c6f-364e-4185-a76b-6171a0b630e4

📥 Commits

Reviewing files that changed from the base of the PR and between f9605da and f545f3d.

📒 Files selected for processing (3)
  • .github/workflows/ci.yml
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
  • frontend/roslyn/samples/SemaphoreFieldSample.cs

Comment thread frontend/roslyn/OwnSharp.Extractor/Program.cs Outdated
…field + alias reads (Codex + CodeRabbit)

The first cut keyed the AvailableWaitHandle gate by bare identifier text via
ThisFieldName, which (a) missed a read through a field ALIAS — `var s = _sem;
s.AvailableWaitHandle` recorded `s`, not `_sem`, so the field stayed wrongly
exempt (Codex) — and (b) could conflate a shadowing local/parameter with a
same-named field (CodeRabbit).

Resolve the receiver by SYMBOL: credit the field's AvailableWaitHandle read only
when the receiver binds to a real field symbol via a this/bare access (excludes
`other._f` and shadowing locals), OR to a field-alias local (reusing the #90
aliasToField map). Anything else is ignored.

Regression: SemaphoreFieldSample gains AliasedWaitHandleSemaphore._aliasedSem
(AvailableWaitHandle read through `var s = _aliasedSem`) which must STILL warn —
the field is tracked through the alias. The existing controls (_optionalSem
silent, _handleSem direct-read warns, _ctsControl type-scope warns) are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED
// because the read went through the local name).
public sealed class AliasedWaitHandleSemaphore
{
private readonly SemaphoreSlim _aliasedSem = new SemaphoreSlim(0, 1);
@PhysShell
PhysShell merged commit a221bbd into main Jun 23, 2026
22 checks passed
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.

3 participants