Thursday, December 29, 2011

F# Frustration: More Async Memory Leaks

Can anyone explain why this code leaks memory?

Looks like using Async.StartAsTask makes it complete in constant space.


  1. As far as I can tell, it's not leaking here when run in fsi (VS 11 Preview).

  2. Thanks. I played a bit more with it, looks like I can only make it leak by allocating data in a tight loop. Injecting Async.Sleep(1) in the loop makes for correct behavior.

  3. If you are still reading this, can you try running `leak2` in a program? I get it to use 200+ megabytes of memory without reclaiming it. Really counter-intuitive. This time I think "sleeping" does not help, it only slows down the rate at which memory is being leaked.

  4. This comment has been removed by the author.

  5. I don't get a memory leak with this.

    let! complete = async { return a1 () }

    There must be something wrong with Async.StartChild.

  6. This code behave exactly the same:

    let leak3 () =
    for x = 0 to 10 do
    Array.init 10000000 id |> ignore
    stdout.Write("PRESS ANY KEY")
    |> ignore

    I wouldn't expect any collection unless there's allocation going on.

  7. Indentation is messed up, nothing you can't figure out.

    For my own knowledge, would it be possible for me to host code on gist and then correctly display it in a comment?

  8. I pulled the following from Expert F# 2.0 but I don't think it has anything to do with what you are experiencing:

    "You should also be aware of the potential for memory stickiness. Memory stickiness occurs when the .NET
    Common Language Runtime is unable to garbage-collect memory even though objects have become
    unreachable. This happens especially when long-running computations and inactive callbacks hold on to
    object handles related to the earlier phases of execution of a program."

  9. @David, you can post links to GIST in comments but I can also read it without indentation.

    Thanks a lot for looking this up.

    I have experimented a lot along the same lines. Apparently yes, some scenarios make CLR retain memory for no obvious reason. Annoying.. At least leak3 does not seem to grow linearly on repeated invocations, so it reuses the memory.

    I was also experimenting with having a worker thread and a queue of tasks, and I got bad results somehow when the queue of tasks got overwhelmed with millions of them. A memory profiler I used claimed most of the memory used by the program after the thread has run is "holes", indicating fragmentation. The only way I found to make it satisfactory is putting a limit on the queue.

    Concerning StartChild though, the memory profiler is showing cancellation-related objects being live in large quantities (proportional to the number of invocations). I think @gradbot has correctly identified a problem with not disposing CancellationTokenSource. Some pinned/unmanaged stuff holding on.