locked
Memory leak consuming WinRT C++ object from JavaScript RRS feed

  • Question

  • It is fairly well known (see http://support.microsoft.com/kb/830555) that IE will leak memory when there is a circular reference between a JavaScript object and a DOM object. This occurs because there is not one memory manager for both types of objects; JS uses garbage collection, but DOM elements are reference-counted COM objects (presumably with some special logic for parent/child relationships). When a reference cycle exists, each "side" thinks its object needs to be kept alive, because not all references to it have been released.

    In my testing, JavaScript code appears to leak (some) C++ WinRT objects that are created. I'm not sure if this is caused by a reference cycle, or something else.

    I built a project according to the instructions in "Walkthrough: Creating a basic Windows Runtime component in C++ and calling it from JavaScript" (http://msdn.microsoft.com/en-us/library/windows/apps/hh755833(v=vs.110).aspx). (Note: the HTML and JavaScript code in the "Adding the HTML that invokes the JavaScript event handlers" and "Adding the JavaScript event handlers that call into the component DLL" sections is duplicated, with slight changes. The code will start to repeat itself about halfway through each block; delete the first half of each. Otherwise, the project won't run properly.)

    The sample project uses the following code (in ButtonUnordered_Click):

        nativeObject.onprimefoundevent = handler_unordered;

    I presume this is to avoid creating a closure that would capture nativeObject (as explained by KB830555). The other anonymous functions in this method presumably do capture it, so I assume the object graph looks something like:

    anonymousFunction -> nativeObject ->  handler_unordered

    At first glance, there doesn't appear to be a reference cycle here; however, I'm finding that the C++ object ("nativeObject", implemented by WinRTComponent.cpp) is never released. (Maybe there is a cycle, via some path I'm not thinking of?)

    I removed the anonymous JavaScript functions, and changed the code so that it creates 9 garbage objects before using the 10th one. Replace the ButtonUnordered_Click function in the sample code with this:

    function ButtonUnordered_Click() {
        var primeFinder;
        for (var i = 0; i < 10; i++)
            primeFinder = new CppLib.WinRTComponent();
    
        primeFinder.onprimefoundevent = handler_unordered;
        document.getElementById("Primes").innerHTML = 'Primes found: ';
        document.getElementById("determinateProgressBar").value = 0;
    
        primeFinder.getPrimesUnordered(2, 1000).then(oncomplete, onerror, onprogress);
    }
    
    function oncomplete() { }
    function onerror(error) {
        document.getElementById("Primes").innerHTML += " " + error.description;
    }
    function onprogress(p) {
        var progressBar = document.getElementById("determinateProgressBar");
        progressBar.value = p;
    }
    

    Additionally, add a constructor (and destructor) to WinRTComponent.cpp that increment (decrement) the number of live objects:

    unsigned int g_alive = 0;
    
    WinRTComponent::WinRTComponent()
    {
    	InterlockedIncrement(&g_alive);
    }
    
    WinRTComponent::~WinRTComponent()
    {
    	InterlockedDecrement(&g_alive);
    }
    

    Build the app and put a breakpoint in WinRTComponent::GetPrimesUnordered. Run the app and repeatedly click the "Primes returned as they are produced" button in the HTML view. Add a watch on the g_alive variable (tracking the number of live instances).

    On my system, the variable took the following values (one per click):

    10 20 30 40 14 24 34 44 18 ...

    My interpretation of this is that a GC didn't happen until after the 4th click. At that point, 40 objects were alive. 36 (= 9 x 4) were temporaries created during the loop, which were freed. The other four were the ones that had event handlers attached, and they leaked. During the 5th click, another ten objects were created, bringing the total to 14. Similarly, after the 8th click, 8 objects had been leaked, and on the 9th click ten more objects were created, bringing the total to 18.

    Why are the C++ objects not being freed? Is it caused by a reference cycle? If so, how does the JavaScript programmer break it?

    Thursday, March 22, 2012 3:06 PM

All replies

  • Thanks Bradley for the detailed explanation!

    The GC is not under your control so part of your observation is expected.  What happens if you set the handler to null in the completed event, does this relieve the leaking from what we think is the handler assignment?

    -Jeff


    Jeff Sanders (MSFT)

    Thursday, March 22, 2012 3:27 PM
    Moderator
  • What happens if you set the handler to null in the completed event, does this relieve the leaking from what we think is the handler assignment?

    To test this, I changed the code as follows:

        primeFinder.getPrimesUnordered(2, 1000).then(
            function () { primeFinder.onprimefoundevent = null; },
            onerror, onprogress);
    

    (I hadn't done this at first, because it seemed like the anonymous function would capture primeFinder and could potentially create a cycle.)

    The only difference I saw was that the GC appeared to run at a slightly different time. The "live object" counts I got at each breakpoint were:

    10 20 30 13 23 33 16 26 ...

    This seems to demonstrate the same pattern of 1 unfreed object per click.

    Thursday, March 22, 2012 3:49 PM
  • Thanks a ton for testing that Bradley,

    I will drill into this further.  Could you contact me here please?  I would like to get your sample project to speed things up:

    http://blogs.msdn.com/jpsanders/contact.aspx

    -Jeff


    Jeff Sanders (MSFT)

    Thursday, March 22, 2012 3:57 PM
    Moderator