dotnet / dotnet/fsharp

Async.DefaultCancellationToken susceptible to memory leaks

Open
#3,590 8 comments 0 reactions 0 assignees View on GitHub
Area-Async Bug Impact-Medium
Dominant language
F#
Stars
4.3k
Forks
876
Avg merge
4d 11h
Merged PRs (30d)
131

Description

We experienced a major memory leak in one of our production systems which our investigation revealed to have been contributed by async's default cancellation token.

#### Minimal Reproduction

```fsharp
open System.Threading

let test() = async {
let! ct = Async.CancellationToken
let cts = CancellationTokenSource.CreateLinkedTokenSource ct
cts.Dispose() // comment out to trigger memory leak
()
}

while true do Async.RunSynchronously(test())
```
This happens because `Async.RunSynchronously` (and in fact most Async runners) will pass the [default, global cancellation token](https://github.com/Microsoft/visualfsharp/blob/4769a923d2bf277fbf1cae0441fee9a6fc527fb9/src/fsharp/FSharp.Core/control.fs#L1046) as a token if the caller doesn't specify one. As a result, any linked cancellation token will have its callback registered in the global cancellation token source. Failure to appropriately dispose any linked resource results in a memory leak.

### Workarounds

1. use an explicit fresh cancellation token for ``Async.Start``, ``Async.RunSynchrously``, ``Async.StartImmediate``, ``Async.StartAsTask``

### Proposed Fixes

1. Deprecate the global cancellation token. Cancelling in a global scope is simply too dangerous, but this would break backward compatibility.
2. Wrap the global cancellation in a linked cancellation token that gets properly disposed in `Async.RunSynchronously` et al.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.