2012-01-28 22 views
15

Aşağıdaki sınamayı (F # 2.0 ile oluşturulmuş) çalıştırdığımda OutOfMemoryException olsun. Sistemimde istisna ulaşmak yaklaşık 5 dakika alır (x86 işlemi olarak çalışıyorsa i7-920 6gb ram), ancak her durumda belleğin görev yöneticisinde nasıl büyüdüğünü görebiliriz.Async.StartChild'de bir bellek sızıntısı var mı?

module start_child_test 
    open System 
    open System.Diagnostics 
    open System.Threading 
    open System.Threading.Tasks 

    let cnt = ref 0 
    let sw = Stopwatch.StartNew() 
    Async.RunSynchronously(async{ 
     while true do 
      let! x = Async.StartChild(async{ 
       if (Interlocked.Increment(cnt) % 100000) = 0 then 
        if sw.ElapsedMilliseconds > 0L then 
         printfn "ops per sec = %d" (100000L*1000L/sw.ElapsedMilliseconds) 
        else 
         printfn "ops per sec = INF" 
        sw.Restart() 
        GC.Collect() 
      }) 
      do! x 
    }) 

    printfn "done...." 

Bu kod ile yanlış bir şey görmüyorum, ve büyüyen bellek için herhangi bir neden göremiyorum. herhangi ölçüde bellek tüketimi olmadan iyi çalışır Bu test için

module start_child_fix 
    open System 
    open System.Collections 
    open System.Collections.Generic 
    open System.Threading 
    open System.Threading.Tasks 


    type IAsyncCallbacks<'T> = interface 
     abstract member OnSuccess: result:'T -> unit 
     abstract member OnError: error:Exception -> unit 
     abstract member OnCancel: error:OperationCanceledException -> unit 
    end 

    type internal AsyncResult<'T> = 
     | Succeeded of 'T 
     | Failed of Exception 
     | Canceled of OperationCanceledException 

    type internal AsyncGate<'T> = 
     | Completed of AsyncResult<'T> 
     | Subscribed of IAsyncCallbacks<'T> 
     | Started 
     | Notified 

    type Async with 
     static member StartChildEx (comp:Async<'TRes>) = async{ 
      let! ct = Async.CancellationToken 

      let gate = ref AsyncGate.Started 
      let CompleteWith(result:AsyncResult<'T>, callbacks:IAsyncCallbacks<'T>) = 
       if Interlocked.Exchange(gate, Notified) <> Notified then 
        match result with 
         | Succeeded v -> callbacks.OnSuccess(v) 
         | Failed e -> callbacks.OnError(e) 
         | Canceled e -> callbacks.OnCancel(e) 

      let ProcessResults (result:AsyncResult<'TRes>) = 
       let t = Interlocked.CompareExchange<AsyncGate<'TRes>>(gate, AsyncGate.Completed(result), AsyncGate.Started) 
       match t with 
       | Subscribed callbacks -> 
        CompleteWith(result, callbacks) 
       | _ ->() 
      let Subscribe (success, error, cancel) = 
       let callbacks = { 
        new IAsyncCallbacks<'TRes> with 
         member this.OnSuccess v = success v 
         member this.OnError e = error e 
         member this.OnCancel e = cancel e 
       } 
       let t = Interlocked.CompareExchange<AsyncGate<'TRes>>(gate, AsyncGate.Subscribed(callbacks), AsyncGate.Started) 
       match t with 
       | AsyncGate.Completed result -> 
        CompleteWith(result, callbacks) 
       | _ ->() 

      Async.StartWithContinuations(
       computation = comp, 
       continuation = (fun v -> ProcessResults(AsyncResult.Succeeded(v))), 
       exceptionContinuation = (fun e -> ProcessResults(AsyncResult.Failed(e))), 
       cancellationContinuation = (fun e -> ProcessResults(AsyncResult.Canceled(e))), 
       cancellationToken = ct 
      ) 
      return Async.FromContinuations(fun (success, error, cancel) -> 
       Subscribe(success, error, cancel) 
      ) 
     } 

: Eminim benim argümanlar geçerlidir yapmak için alternatif uygulama yaptı. Ne yazık ki F # 'da fazla tecrübem yok ve bazı şeyleri özlüyorsam şüphelerim var. Hata durumunda, F # takımına nasıl rapor edebilirim?

cevap

15

Doğru olduğunu düşünüyorum - StartChild uygulamasında bellek sızıntısı var gibi görünüyor.

Biraz profil oluşturma (fantastic tutorial by Dave Thomas'u izleyerek) ve open-source F# release'u yaptım ve bunun nasıl düzeltileceğini bile biliyorum. Eğer StartChild uygulanmasına bakarsak, bu iş akışının şimdiki iptal jetonu bir işleyici kaydeder: yığın hayatta kalmak

let _reg = ct.Register(
    (fun _ -> 
     match !ctsRef with 
     | null ->() 
     | otherwise -> otherwise.Cancel()), null) 

nesneler bu kayıtlı bir işleve örnekleridir. _reg.Dispose() numaralı telefonu arayarak kayıtsız olabilirler, ancak bu F # kaynak kodunda asla gerçekleşmez. Ben zaman uyumsuz tamamlandığında denilen olsun işlevlerine _reg.Dispose() ekleme çalıştı:

(fun res -> _reg.Dispose(); ctsRef := null; resultCell.RegisterResult (Ok res, reuseThread=true)) 
(fun err -> _reg.Dispose(); ctsRef := null; resultCell.RegisterResult (Error err,reuseThread=true)) 
(fun err -> _reg.Dispose(); ctsRef := null; resultCell.RegisterResult (Canceled err,reuseThread=true)) 

... ve deneylere dayanarak, bu sorunu düzeltir. Dolayısıyla, bir geçici çözüm istiyorsanız, muhtemelen gereken tüm kodu control.fs'dan kopyalayıp bunu bir düzeltme olarak ekleyebilirsiniz.

Sorularınıza bir bağlantı içeren F # ekibine bir hata raporu göndereceğim. Başka bir şey bulursanız, fsbugs no'lu hata raporlarını microsoft dot com numaralı telefondan göndererek onlarla iletişime geçebilirsiniz.

+0

Bunun neden gerekli olduğunu biliyor musunuz? Neden yeni bir "CTS" oluşturuldu? Sadece orijinal 'ct'yi kullanmak yeterli olmaz mı? – svick

+0

@svick - İyi soru. İç iptal belirtecinin 'StartChild 'için belirtilen zaman aşımını ele almak için kullanıldığını düşünüyorum (bu zaman aşımı, aslında daha sonra sonuç beklemediğiniz sürece' StartChild' denilen hesaplamayı iptal etmemelidir). –

+0

Bunu düşünmedim. Evet, bu mantıklı. – svick