tjanczuk / edge

Run .NET and Node.js code in-process on Windows, MacOS, and Linux
http://tjanczuk.github.io/edge
Other
5.41k stars 641 forks source link

Managed memory leak when calling from JS to .NET #81

Closed tjanczuk closed 11 years ago

tjanczuk commented 11 years ago

The following code generates a managed memory leak (managed heap ever increasing). Ultimately (~10min on my box) the process fails with out of memory exception:

var func = require('edge').func(function () {/*
    async (object input) => {
        return null;
    }
*/});

var data = '{"data_package_id":"341f0a51-3521-400e-ba05-b86939433e9b","timestamp":1375988508262,"records":[{"id":"df63580b-5047-4ed2-a5f3-e928abd8414a","timestamp":1375988508118,"context":[],"target_users":[],"extension":{"eventRecordTime":1375988508118,"apiVersion":"v1","shortUrl":"shortUrl","httpMethod":"POST","callerUserAgent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36","azureDeployName":"localhost","connectionId":"3f813fd4-aea9-4e7b-a44c-e4a3f75da38b"},"sampling_data":{},"context_ids":{"connectionId":"1bb5c8d4-acc2-4a01-aa7e-79317eedd5d5"},"event_type":"edf_trouter_request_check","type":"trouter"}],"ids":{"deployment":"TrouterTest"},"schema":1,"type":"Service","source":"Node Test","version":"1.0.0.0"}';

function one() {
    func(data, function (error, data) {
        if (error) throw error;
        setTimeout(one, 1);
    });
}

one();
tjanczuk commented 11 years ago

Useful resource: http://www.codeproject.com/Articles/19490/Memory-Leak-Detection-in-NET

It appears the ClrFuncInvokeContext associated with a call from V8 to CLR is never collected, which also contains other objects in its closure:

!dumpheap -stat
...
03be0f14    77629      2173612 ClrFuncInvokeContext
64112b0c    77629      2484128 System.Action
6411acc0    78354    120662376 System.String
tjanczuk commented 11 years ago

Root cause: ClrFuncInvokeContext remained GC-rooted when CLR function completed synchronously on V8 thread. This prevented ClrFuncInvokeContext along with its closure (which includes payload passed from CLR to JS) from being reclaimed by the GC.

tjanczuk commented 11 years ago

The fix is to refactor code such the ClrFuncInvokeContext is being GC-rooted only when the CLR function does not complete synchronously.

image

tjanczuk commented 11 years ago

For future reference: here is a guide to native leak investigation with windbg: http://www.codeproject.com/Articles/31382/Memory-Leak-Detection-Using-Windbg