1

I'm using Angular 1.3.15 and I want to prevent the client code to go into an infinite error loop by handling exceptions properly.

I'm using Sentry for reporting exceptions, but I'm not communicating with Sentry from the client code. Instead I'm reporting exceptions from the client code to a Web API (using XHR), which then reports it to Sentry.

What happened few days ago was that someone visited our site on FireFox 10, so the client code went into an infinite error loop because of badly supported JavaScript, so it literally spammed/DoS'd our API with exceptions. Our API is behind NetScaler so we decided to set up a rate-limiter, and if the requests hit the rate-limiter we now return a 503 HTTP status code (Service Unavailable).

The error looked something like this:

([object Object],[object Object],(void 0))@/Static/Scripts/app/vendor/angular-translate/angular-translate.min.js:6 applyDirectivesToNode([object Array],[object HTMLAnchorElement],[object Object],(void 0),(void 0),null,[object Array],[object Array],[object Object])@/Static/Scripts/app/vendor/angular/angular.js:7498 compileNodes([object Proxy],(void 0))@/Static/Scripts/app/vendor/angular/angular.js:7040 compileNodes([object Proxy],(void 0))@/Static/Scripts/app/vendor/angular/angular.js:7054 compileNodes([object Proxy],(void 0))@/Static/Scripts/app/vendor/angular/angular.js:7054 compileNodes([object Proxy],(void 0))@/Static/Scripts/app/vendor/angular/angular.js:7054 compileNodes([object Proxy],(void 0))@/Static/Scripts/app/vendor/angular/angular.js:7054 compileNodes([object Object],(void 0),[object Object],(void 0),(void 0),null)@/Static/Scripts/app/vendor/angular/angular.js:7054 ...

Now to the problem. I want to implement a proper exception handling in the client code, so it continues to report errors to the API/Sentry, but if it receives HTTP 503 status code response, it should immediately stop script execution and halt.

So I tried to implement an $exceptionHandler decorator in my Angular module config:

// Flag indicating that the JavaScript execution should be stopped and Sentry should NOT be notified
var stopExecution = false;

$provide.decorator("$exceptionHandler", ['$delegate', function($delegate) {
    return function(exception, cause) {
        $delegate(exception, cause);

        if(stopExecution) return;

        // Call Sentry and report the exception
        SentryServiceProvider.$get().captureException(exception)
            .error(function (data, status, headers, config) {
                if(status === 503) {
                    if(stopExecution) return;
                    stopExecution = true;
                    throw new Error('GOT AN 503 ERROR FROM API, STOPPING EXECUTION');
                }
            });
    };
}]);

My captureException() function in SentryServiceProvider just calls the API with a POST request and sends the exception message and stack trace in a JSON object as a payload, and returns a $http promise.

sentryService.captureException = function (ex) {
    return $http.post(API_URL + '/sentry', { Message: ex.message, StackTrace: ex.stack });
};

Now the problem is because it's in an infinite loop, it never goes to the error() callback and keeps spamming the API forever. How could I do this to prevent infinite error loops in my client code, so it halts when the API says so?

Gaui
  • 8,723
  • 16
  • 64
  • 91
  • From the code you posted, I can't quite understand where it goes into an infinite loop. Also might be easier and user friendly to detect the faulty browser and refuse to load the page or redirect to a fall back. – M.P. Korstanje Aug 30 '15 at 00:06
  • The code I posted does not contain the infinite error. The code is trying to prevent it. The cause of the error is not known upfront, so I'm just trying to prevent this so it doesn't happen ever again. This has happened (because someone ran our JavaScript on FireFox 10) and it _could_ happen again (infinite digest loop, faulty browser, bad logic, etc.). We have a browser detection where we warn the user, but he ignored the warning and entered the site, and that caused an infinite error loop in the code. – Gaui Aug 30 '15 at 00:10
  • Well you can't just break a loop without knowing where it's at. If the loop happens before you could throw the error it just won't throw it. – MinusFour Aug 30 '15 at 00:19
  • The exception is delegated through the `$exceptionHandler` decorator in Angular, so the JavaScript execution can be halted - thus breaking the infinite error loop. However, I only want this to happen when the API hits the rate-limiter and responds with a 503 _(Service Unavailable)_. I've already implemented that, but I have to implement the client code. – Gaui Aug 30 '15 at 00:22
  • @Gaui , it sounds like you're trying to solve the halting problem. You can't protected against every possible exception from an unreliable interpreter. For example, if the code throws an exception after sending to your server but before reaching the check that tells it to stop sending more exceptions. You could however check *before* you send the error to the WebApi how many errors you've send that went unacknowledged and stop doing that after say 10 occurrences. Then reset the counter after an error is acknowledged. – M.P. Korstanje Aug 30 '15 at 00:24
  • @mpkorstanje No I'm not trying to solve the halting problem. I'm only making sure the code STOPS executing when the API hits the rate-limiter and reports it to the client. But the problem is that it never gets to go to the `error()` callback, because it's too busy in an infinite loop and hitting the API with XHR exception reports. So you're saying I have to implement another "rate-limiter" client side? – Gaui Aug 30 '15 at 00:30
  • For this specific instance of the problem yes. That might work. For the general case of arbitrary failure of the interpreter no. – M.P. Korstanje Aug 30 '15 at 08:49
  • But another aspect of the problem, how are you preventing that the client code goes into an infinite error loop that crashes the user's browser? Maybe because he has an outdated browser which doesn't handle JavaScript very well. – Gaui Aug 30 '15 at 13:50

0 Answers0