Skip to content

Avoid cancellableTask CE in DocumentCache to preserve static-state-machine optimization - #20273

Merged
T-Gro merged 3 commits into
dotnet:mainfrom
xperiandri:fix-DocumentCache.fs-cannot-use-cancellableTask-CE
Aug 21, 2026
Merged

Avoid cancellableTask CE in DocumentCache to preserve static-state-machine optimization#20273
T-Gro merged 3 commits into
dotnet:mainfrom
xperiandri:fix-DocumentCache.fs-cannot-use-cancellableTask-CE

Conversation

@xperiandri

Copy link
Copy Markdown
Contributor

Summary

Fixes#20268 by avoiding the cancellableTask computation expression in DocumentCache, where it cannot compile to a static state machine in the hot path. The cache helpers now use direct CancellationToken-aware task wrappers instead of a closure-based cancellableTask wrapper.

Changes

@github-actions

github-actionsBot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

❗ Release notes required

You can open this PR in browser to add release notes: open in github.dev


✅ Found changes and release notes in following paths:

Warning

No PR link found in some release notes, please consider adding it.

Change pathRelease notes pathDescription
`vsintegration/src`docs/release-notes/.VisualStudio/18.vNext.mdNo current pull request URL (#20273) found, please consider adding it

@xperiandrixperiandri changed the title Avoid cancellableTask in DocumentCache and add release notesAvoid cancellableTask CE in DocumentCache to preserve static-state-machine optimizationAug 17, 2026
@github-actionsgithub-actionsBot added the AI-Tooling-Check-Scanned-Clean Tooling check: diff analyzed, no interesting infrastructure files label Aug 17, 2026

@T-GroT-Gro left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for this — the direction is reasonable and the diff is small and readable. A few notes, mostly about the rationale rather than the mechanics.

1. The justification is imprecise — the CE does statically compile

cancellableTask.Run is built on __stateMachine / __useResumableCode, so the resumable body is compiled to a static state machine today. What cancellableTask { … } additionally does is:

  • allocate the AfterCode closure (fun ct -> …) that captures the struct state machine, and
  • because cancellableTask = CancellableTaskBuilder(true) (the background builder), inject a Task.Run offload when not already on the thread pool.

Your replacement fun ct -> tryGetCachedValueAsync (doc, cache, ct)still allocates a closure (it captures doc and cache), and the inner task { … } still boxes its state machine on the first suspension. So the path is not "allocation-free" — it's "one smaller closure, no background Task.Run, no chance of a dynamic fallback."

Suggest tightening the release note wording accordingly, e.g. "avoids the background Task.Run offload and a larger wrapper closure from the cancellableTask builder" rather than "allocation-free." The precise wording matters here because the note is the durable explanation for why this pattern exists.

2. Behavioral change: background offload is dropped (Low — looks intentional/safe)

cancellableTask is the background builder, so the old helpers were offloaded via Task.Run when invoked from a sync context; the new task { … } runs inline on the caller until the first real await. Both current callers (ClassificationService.AddSemanticClassificationsAsync and HintService.getHintsForDocument) already wrap the work in a background cancellableTask, so the inner offload was redundant and removing it is fine — arguably a small win. Worth a one-line note that this is intentional.

3. Behavioral change: eager cancellation check is lost (Low)

The CE Run entry checks ct.IsCancellationRequested and returns Task.FromCanceled<_>(ct) before running any code. The manual helpers skip that and call doc.GetTextVersionAsync ct directly. Roslyn's GetTextVersionAsync honors the token, so this is practically equivalent, but you no longer guarantee a canceled Task on an already-canceled token. If you want to preserve exact semantics, add a guard:

static lettryGetCachedValueAsync(doc:Document,cache:MemoryCache,ct:CancellationToken)=if ct.IsCancellationRequested then Task.FromCanceled<_ voption>(ct)elsetask{}

Otherwise fine to leave as-is.

4. Nits / positives

  • Making the helpers static let so they don't capture this is a nice touch. 👍
  • An alternative that keeps CE ergonomics while dropping the background offload is foregroundCancellableTask { … } — no need to change, just noting the CE didn't have to be abandoned to get the perf shape you want.
  • Please confirm CI is green: the secondary new(name, slidingExpirationSeconds) constructor now sits after the static let bindings. That ordering is valid F#, but worth a glance.

Net: I'd approve after the release-note wording is adjusted (item 1) — the code change itself is sound.

@T-Gro
T-Gro self-requested a review August 17, 2026 08:33
@T-GroT-Gro added the AI-reviewed PR reviewed by AI review council label Aug 17, 2026
@xperiandri
xperiandriforce-pushed the fix-DocumentCache.fs-cannot-use-cancellableTask-CE branch 4 times, most recently from dbf142a to 84a158cCompareAugust 18, 2026 13:01

@T-GroT-Gro left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖🕵️ LGTM ✅

The two conditions from the earlier review are met on the current head: the release-note wording was softened, and commit 84a158c restored the eager Task.FromCanceled cancellation guard. The refactor is behavior-preserving (both call sites already run under a background cancellableTask, so no UI-thread resume is introduced) and the manual fun ct -> task { ... } helpers honor the ambient token. Please verify independently.

@github-project-automationgithub-project-automationBot moved this from New to In Progress in F# Compiler and ToolingAug 19, 2026
@xperiandri
xperiandriforce-pushed the fix-DocumentCache.fs-cannot-use-cancellableTask-CE branch from 84a158c to 81fba9dCompareAugust 20, 2026 14:37
@T-Gro
T-Gro enabled auto-merge (squash) August 21, 2026 11:51
@T-Gro
T-Gro merged commit 491d71a into dotnet:mainAug 21, 2026
52 checks passed
@github-project-automationgithub-project-automationBot moved this from In Progress to Done in F# Compiler and ToolingAug 21, 2026
@xperiandri
xperiandri deleted the fix-DocumentCache.fs-cannot-use-cancellableTask-CE branch August 21, 2026 14:24
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI-reviewedPR reviewed by AI review councilAI-Tooling-Check-Scanned-CleanTooling check: diff analyzed, no interesting infrastructure files

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

DocumentCache.fs cannot use cancellableTask CE because it does not compile to a static state machine

2 participants

@xperiandri@T-Gro