2012-01-28 27 views
15

जब मैं निम्न परीक्षण चलाता हूं (एफ # 2.0 के साथ बनाया गया) मुझे आउटऑफमेमरी एक्सेप्शन मिलता है। मेरे सिस्टम पर अपवाद तक पहुंचने में लगभग 5 मिनट लगते हैं (i7-920 6 जीबी राम अगर यह x86 प्रक्रिया के रूप में चल रहा था), लेकिन किसी भी मामले में हम देख सकते हैं कि कार्य प्रबंधक में स्मृति कैसे बढ़ रही है।क्या Async.StartChild में मेमोरी रिसाव है?

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...." 

मुझे इस कोड के साथ कुछ भी गलत नहीं दिख रहा है, और स्मृति बढ़ने के लिए कोई कारण नहीं दिख रहा है।

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) 
      ) 
     } 

इस परीक्षण यह किसी भी काफी स्मृति की खपत के बिना अच्छी तरह से काम के लिए: मुझे यकीन है कि मेरे तर्क मान्य हैं बनाने के लिए वैकल्पिक कार्यान्वयन कर दिया। दुर्भाग्यवश मैं एफ # में ज्यादा अनुभवी नहीं हूं और मुझे कुछ चीजों को याद करने पर संदेह है। अगर यह बग है तो मैं इसे एफ # टीम में कैसे रिपोर्ट कर सकता हूं?

उत्तर

15

मुझे लगता है कि आप सही हैं - StartChild के कार्यान्वयन में स्मृति स्मृति रिसाव प्रतीत होता है।

मैंने कुछ प्रोफाइलिंग किया (fantastic tutorial by Dave Thomas के बाद) और open-source F# release और मुझे लगता है कि मुझे यह भी पता है कि इसे कैसे ठीक किया जाए। आप StartChild के कार्यान्वयन को देखें, तो यह कार्यप्रवाह की वर्तमान रद्द टोकन के माध्यम से एक हैंडलर पंजीकृत करता है:

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

वस्तुओं है कि ढेर में जिंदा रहने के इस पंजीकृत समारोह के उदाहरण हैं। उन्हें _reg.Dispose() पर कॉल करके अनियंत्रित किया जा सकता है, लेकिन यह एफ # स्रोत कोड में कभी नहीं होता है। मैं कार्यों कि जब async पूरा करता है कहा जाता है करने के लिए _reg.Dispose() जोड़ने की कोशिश की:

(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)) 

... और मेरे प्रयोगों के आधार पर, इससे समस्या हल हो। इसलिए, यदि आप एक वर्कअराउंड चाहते हैं, तो आप शायद control.fs से सभी आवश्यक कोड कॉपी कर सकते हैं और इसे एक फिक्स के रूप में जोड़ सकते हैं।

मैं आपके प्रश्न के लिंक के साथ एफ # टीम को एक बग रिपोर्ट भेजूंगा। अगर आपको कुछ और मिलता है, तो आप fsbugs पर microsoft डॉट com पर बग रिपोर्ट भेजकर उनसे संपर्क कर सकते हैं।

+0

क्या आप जानते हैं कि यह भी आवश्यक क्यों है? एक नया 'सीटीएस' क्यों बनाया गया है? मूल 'ct' का उपयोग न केवल पर्याप्त होगा? – svick

+0

@svick - अच्छा सवाल। मुझे लगता है कि आंतरिक रद्दीकरण टोकन का उपयोग टाइमआउट को संभालने के लिए किया जाता है जिसे 'स्टार्ट चाइल्ड' के लिए निर्दिष्ट किया जा सकता है (इस टाइमआउट को 'स्टार्ट चाइल्ड' नामक गणना को रद्द नहीं करना चाहिए, जब तक कि आप वास्तव में परिणाम के बाद प्रतीक्षा न करें)। –

+0

मैंने इसके बारे में नहीं सोचा था। हाँ, यह समझ में आता है। – svick

संबंधित मुद्दे