Question

I am stuck with fixing my TFS tests runs for an F# project that uses MailboxProcessor. The problem is the following warning that I receive from the TFS test runner:

System.AppDomainUnloadedException: Attempted to access an unloaded AppDomain. This can happen if the test(s) started a thread but did not stop it. Make sure that all the threads started by the test(s) are stopped before completion.

I guess the problem is caused by the MailboxProcessor. The following snippet demonstrates the problem (I run it from fsi):

open System.Threading
open System

type TestDisposable () =
    let cts = new CancellationTokenSource ()
    let processMessage (inbox:MailboxProcessor<int>) =
        let rec loop n =
            async {
                let! msg = inbox.Receive ()
                return! loop (n+msg)
            }
        loop 0

    let agent = MailboxProcessor<int>.Start (processMessage, cts.Token)

    interface IDisposable with
        member this.Dispose () =
            (agent :> IDisposable).Dispose ()
            cts.Cancel ()
            cts.Dispose ()
            printfn "TestDisposable.Dispose called"

do
    let weakTarget = 
        use target = new TestDisposable ()
        new WeakReference (target)

    GC.Collect()
    GC.WaitForPendingFinalizers()
    GC.WaitForFullGCComplete() |> ignore
    GC.Collect()

    printfn "WeakTarget is alive: %b" weakTarget.IsAlive

I expect the output line to say that weakTarget is dead. But its alive. I think it indicates some memory leak. The question is what do I do wrong? And the second question is whether the GC problem is related to the TFS test runner problem.

Was it helpful?

Solution

The example code you have posted will keep a reference to target, probably because you have a top level binding to it (use target = new TestDisposable()).

If you change the code to something like the below code you will see that weakTarget is dead because the reference to target is only local to the test() function.

do
    let test() =
        use target = new TestDisposable()
        new WeakReference(target)

    let weakTarget = test()    

    GC.Collect()
    GC.WaitForPendingFinalizers()
    GC.WaitForFullGCComplete() |> ignore
    GC.Collect()

    printfn "WeakTarget is alive: %b" weakTarget.IsA

I don't know if this fixes your original problem though, as this is fairly specfic to the way you have written the example code.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top