Skip to content

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

Description

@xperiandri

Problem

In vsintegration/src/FSharp.Editor/Common/DocumentCache.fs, the tryGetCachedValueAsync and setCacheValueAsync helpers cannot be implemented with the cancellableTask computation expression because the resulting code is not compiled into a static state machine by the F# compiler. Instead, it allocates a closure that captures the cancellation token and other arguments, which defeats the performance goals of using resumable code in a hot-path editor cache.

Background

The file currently (after the fix in commit 924d6a76d) uses plain task { ... } blocks and accepts CancellationToken explicitly:

static let tryGetCachedValueAsync (doc: Document, cache: MemoryCache, ct: CancellationToken) =
    task {
        let! currentVersion = doc.GetTextVersionAsync ct
        ...
    }

member _.TryGetValueAsync(doc: Document) : CancellableTask<'Value voption> =
    fun ct -> tryGetCachedValueAsync (doc, cache, ct)

The public API still returns CancellableTask<'T>, but the implementation avoids the cancellableTask CE.

Why cancellableTask is problematic here

CancellableTask from CancellableTasks is defined as a function CancellationToken -> Task<'T>. The cancellableTask { ... } CE builds that function by allocating a closure that, when invoked with a token, runs an ordinary task { ... } block and calls CancellableTask.getCancellationToken() inside it.

Because the CE value itself is a closure (fun ct -> task { ... }), the F# compiler does not apply the resumable-code / state-machine transformation to the outer function. The static-state-machine optimization is only applied to the inner task { ... } block, while the wrapper closure is still allocated per call (or per construction, depending on usage). In a high-traffic cache helper this means:

  • An extra delegate / closure allocation per TryGetValueAsync/SetAsync invocation.
  • No zero-allocation resumable entry point.
  • The compiler cannot inline or statically compile the CancellableTask wrapper into a struct state machine.

Request

Please confirm whether the compiler and/or the CancellableTasks library can be improved so that cancellableTask { ... } can be compiled into a static state machine (or an equivalent zero-allocation shape), or document the limitation and recommend the manual fun ct -> task { ... } pattern for performance-sensitive code paths.

Related code

  • DocumentCache.fs
  • Commit 924d6a76d — "Fix resumable code unable to be compiled statically"

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status
    New

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions