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"
Problem
In vsintegration/src/FSharp.Editor/Common/DocumentCache.fs, the
tryGetCachedValueAsyncandsetCacheValueAsynchelpers cannot be implemented with thecancellableTaskcomputation 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 plaintask { ... }blocks and acceptsCancellationTokenexplicitly:The public API still returns
CancellableTask<'T>, but the implementation avoids thecancellableTaskCE.Why
cancellableTaskis problematic hereCancellableTaskfromCancellableTasksis defined as a functionCancellationToken -> Task<'T>. ThecancellableTask { ... }CE builds that function by allocating a closure that, when invoked with a token, runs an ordinarytask { ... }block and callsCancellableTask.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 innertask { ... }block, while the wrapper closure is still allocated per call (or per construction, depending on usage). In a high-traffic cache helper this means:TryGetValueAsync/SetAsyncinvocation.CancellableTaskwrapper into a struct state machine.Request
Please confirm whether the compiler and/or the
CancellableTaskslibrary can be improved so thatcancellableTask { ... }can be compiled into a static state machine (or an equivalent zero-allocation shape), or document the limitation and recommend the manualfun ct -> task { ... }pattern for performance-sensitive code paths.Related code
924d6a76d— "Fix resumable code unable to be compiled statically"