fsprojects / FSharpx.Extras

Functional programming and other utilities from the original "fsharpx" project
https://fsprojects.github.io/FSharpx.Extras/
The Unlicense
683 stars 146 forks source link

FSharpx.Task monad leaks memory when used to write long-running loops #353

Closed rspeele closed 6 years ago

rspeele commented 7 years ago

Description

There is no reasonable way to write an infinite loop, such as one that polls looking for work to do, using FSharpx.Task.

Repro steps

The simplest repro is a console application:

open FSharpx.Task

let whileLoop() =
    task {
        while true do
            do! Task.FromResult(())
    }

let main argv =
    whileLoop().Wait()
    0

Expected behavior

The application runs indefinitely, doing nothing, with stable memory usage.

Actual behavior

The application runs for a minute or two, doing nothing, but steadily growing in memory usage until throwing an OutOfMemoryException.

Known workarounds

As far as I know there is no clean functional way to chain tasks together that does not suffer from this problem. That is, implementing TaskBuilder.Bind using ContinueWith and Unwrap inevitably causes this. One known workaround is to use System.Runtime.CompilerServices.AsyncMethodBuilder to implement the loop (like the state machines that C#'s async/await construct compiles to).

I'm reporting this just as a heads-up after I realized it while trying to support constant-space tail recursion in my own TaskBuilder. My TaskBuilder is fine in imperative loops like while true because it uses an AsyncMethodBuilder, but its API is not 100% compatible with the one in FSharpx, so it's not a drop-in replacement. Its code is public domain though, so anybody who wishes to fix this issue in FSharpx is free to reference/copy it.

Related information

panesofglass commented 6 years ago

We should remove this one in favor of https://github.com/rspeele/TaskBuilder.fs. Thanks, @rspeele

panesofglass commented 6 years ago

Closing as this will be fixed with #368