From 2cd09c9f0e7766bcd191662841b7b1ffc3b6dc3f Mon Sep 17 00:00:00 2001 From: Noam Lewis Date: Mon, 30 Dec 2013 20:10:23 -0500 Subject: [PATCH] fix($rootScope): prevent infinite $digest by checking if asyncQueue is empty when decrementing ttl An infinite $digest loop can be caused by expressions that invoke a promise. The problem is that $digest does not decrement ttl unless it finds dirty changes; it should check also if asyncQueue is empty. Generally the condition for decrementing ttl should be the same as the condition for terminating the $digest loop. Fixes #2622 --- src/ng/rootScope.js | 2 +- test/ng/rootScopeSpec.js | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/ng/rootScope.js b/src/ng/rootScope.js index 8259bf881bd9..bbf290b2909a 100644 --- a/src/ng/rootScope.js +++ b/src/ng/rootScope.js @@ -632,7 +632,7 @@ function $RootScopeProvider(){ // `break traverseScopesLoop;` takes us to here - if(dirty && !(ttl--)) { + if((dirty || asyncQueue.length) && !(ttl--)) { clearPhase(); throw $rootScopeMinErr('infdig', '{0} $digest() iterations reached. Aborting!\n' + diff --git a/test/ng/rootScopeSpec.js b/test/ng/rootScopeSpec.js index e47111e2d0eb..f9cf9412c605 100644 --- a/test/ng/rootScopeSpec.js +++ b/test/ng/rootScopeSpec.js @@ -258,6 +258,31 @@ describe('Scope', function() { })); + it('should prevent infinite loop when creating and resolving a promise in a watched expression', function() { + module(function($rootScopeProvider) { + $rootScopeProvider.digestTtl(10); + }); + inject(function($rootScope, $q) { + var d = $q.defer(); + + d.resolve('Hello, world.'); + $rootScope.$watch(function () { + var $d2 = $q.defer(); + $d2.resolve('Goodbye.'); + $d2.promise.then(function () { }); + return d.promise; + }, function () { return 0; }); + + expect(function() { + $rootScope.$digest(); + }).toThrowMinErr('$rootScope', 'infdig', '10 $digest() iterations reached. Aborting!\n'+ + 'Watchers fired in the last 5 iterations: []'); + + expect($rootScope.$$phase).toBeNull(); + }); + }); + + it('should not fire upon $watch registration on initial $digest', inject(function($rootScope) { var log = ''; $rootScope.a = 1;