fsprojects/FSharpx.Extras

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

rspeele opened this issue · 2 comments

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

  • Operating system: Windows 7, Windows 10
  • Branch: master
  • .NET Runtime, CoreCLR or Mono Version: .NET framework 4.6.2

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

Closing as this will be fixed with #368