Workaround for IE10 setInterval Memory Leak

2019-02-04 19:17发布

问题:

During testing of our Javascript library I think we found a severe memory leak in IE10's (v10.0.9200.16519 - Windows 8 64 bit) Javascript implementation of setInterval.

A simple test case showed that if a variable is captured in the closure of the function being passed as the argument for later execution it does not seem to ever become eligible for garbage collection, i.e. the browser still seems to hold a reference to the function or at least the closure variables.

Our testcase executes the setInterval function only once and then clears the interval timer, i.e. after a while no code is running anymore and no variables are accessible anymore (as far as I can see no globals are introduced in this code, except for the method to run in onload), nevertheless the process takes up half a gigabyte of memory (depending on the number of iterations).

Interestingly this does not happen if we use the setTimeout method instead (and also the problem does not seem to exist in IE9, and current versions of Chrome, FF).

The problem can be seen with this fiddle.

Run it in a fresh instance of IE10 on Windows 8 and open the task manager to watch the memory usage. It will grow quickly to 350 Megabytes and will stay there after the script was executed.

This is the important part of the problematic code piece:

// the function that when called multiple times will cause the leak in IE10
var eatMemory = function() {
    var a = null; // the captured closure variable
    var intervalId = setInterval(function() {
       a = createBigArray(); // call a method that allocates a lot of memory
       clearInterval(intervalId); // stop the interval timer
    }, 100);
}

(I know that it is easy to fix this specific piece of code. But that's not the point - this is just the tiniest piece of code we came up with that reproduces the problem. The real code actually captures this in the closure and that object is never garbage collected.)

Is there a bug in our code or is there a way to use setInterval where a closure variable holds a reference to a large object without triggering the memory leak and without reverting to "recursive" setTimeout calls?

(I also posted the question on MSDN)

Update: This issue also exists in IE10 on Windows 7, but does not exist if you switch to IE9-standards mode. I submitted this to MS Connect and will report progress.

Update: Microsoft accepted the issue and reported it to be fixed in IE11 (preview version) - I haven't confirmed this myself, yet (anybody?)

Update: IE 11 has been officially released and I cannot reproduce the problem on that version with my system (Win 8.1 Pro 64bit) anymore.

回答1:

For completeness sake I am adding a possible workaround here:

As I have already written (and commenters suggested), this can be worked around (not fixed) by falling back to setTimeout. This is not trivial, since some id book-keeping needs to be done. Here is my suggested fix, that you can test and fork from this fiddle:

var registerSetIntervalFix = function(){
    var _setTimeout = window.setTimeout;
    var _clearTimeout = window.clearTimeout;
    window.setInterval = function(fn, interval){
        var recurse = function(){
            var newId = _setTimeout(recurse, interval);
            window.setInterval.mapping[returnValue] = newId;
            fn();
        }
        var id = _setTimeout(recurse, interval);
        var returnValue = id;
        while (window.setInterval.mapping[returnValue]){
            returnValue++;
        }
        window.setInterval.mapping[returnValue] = id;
        return returnValue;
    }
    window.setInterval.mapping = {};
    window.clearInterval = function(id){
        var realId = window.setInterval.mapping[id];
        _clearTimeout(realId);
        delete window.setInterval.mapping[id];
    }
}

The idea is to recursively call setTimeout to simulate recurring setInterval calls. There is a little overhead in this implementation since it has to perform the bookkeeping for the changing ids, so I wouldn't recommend applying this fix unless it is required.

Unfortunately I am not able to come up with a "feature" detection algorithm (more like a "bug"-detection algorithm), so I guess you have to revert to good old browser detection. Also my implementation cannot deal with strings as the first argument and does not pass additional arguments to the inner function. Lastly it is not safe to call this method twice, so use it at your own risk (and feel free to improve it)!

(Note: For our library we will stop using setInterval from now and instead rewrite the few parts in the code that rely on it to use setTimeout directly.)