javascript stop an infinite loop
Asked Answered
L

6

5

This is node.js.

I have a function that might become an infinite loop if several conditions are met. Untrusted users set these conditions so for the purpose of this question please assume the infinite loop is unfixable.

Still I need a way to stop the infinite loop.

Here is some sample code for what i'm trying to do:

var infiniteloop = false;
var condition = true
function loop () {
  while (condition) {
    console.log('hi')
    if (infiniteloop) {
      condition = false
      console.log('oh last one')
    }
  }
}

loop()

So a few questions based on what I'm trying to do.

  1. If the infiniteloop variable is set to true, the loop will stop right?
  2. How do I detect the infinite loop? Something that checks every 3 seconds would be good.
  3. The infiniteloop variable cannot be changed while it's looping if it's on the same process. I have to store the variable in a different process?
  4. Whatever detects the infinite loop needs to live in a different process? Ideally same process would be nice but whatever works?

Thanks for your help.

Laoighis answered 13/7, 2012 at 18:26 Comment(2)
why don't you just put an upper bound on the number of iterations?Excite
You need to set the maximum number of loops and if you exceed that number, just break out of the loop. You are trying to find a solution to a halting problem.Vindicable
F
5

A solution based on a mix of the other proposals:

function Worker()
{
    this.MaxIterations = 1000000;
    this.Enabled = true;    
    this.condition = true;
    this.iteration = 0;
    this.Loop = function()
    {
        if (this.condition 
            && this.Enabled 
            && this.iteration++ < this.MaxIterations)
        {
            console.log(this.iteration);
            setTimeout(this.Loop.bind(this),0);
        }
    };  
    this.Stop = function()
    {
        this.Enabled = false;
    };
}
var w = new Worker();
setTimeout(w.Loop.bind(w), 0);
setTimeout(w.Stop.bind(w), 3000);

Not sure this is optimal, but that should work as expected.

The use of setTimeout to resume the loop allows the main node.js event loop to process other events, such a w.Stop.

Fiorenze answered 13/7, 2012 at 21:47 Comment(1)
This isn't exactly what I used but a bunch of answers on here led me to a solution. Thanks.Laoighis
A
2

Actually, you don't need to stop a infinite loop. use setImmediate

for instance:

var immediateId;

function loop () {
    console.log('hi');
    immediateId = setImmediate(loop);
}

loop();

This chunk of code will keep saying hi, until you stop it.

//stop the loop:
clearImmediate(immediateId);

why using setImmediate

  1. the Memory comsumption kept low, will not cause memory leek;
  2. will not throw a RangeError: Maximum call stack size exceeded;
  3. the performance is good;

Further more,

I created this module for easily managing infinite loop:

var util = require('util');
var ee = require('events').EventEmitter;

var Forever = function() {
    ee.call(this);
    this.args = [];
};

util.inherits(Forever, ee);

module.exports = Forever;

Forever.prototype.add = function() {
    if ('function' === typeof arguments[0]) {
        this.handler = arguments[0];
        var args = Array.prototype.slice.call(arguments, 1);
        if (args.length > 0) {
            this.args = args;
        }
    } else {
        this.emit('error', new Error('when using add function, the first argument should be a function'));
        return 0;
    }
    return this;
};

Forever.prototype.run = function() {
    var handler = this.handler;
    var args = this.args;
    var that = this;

this._immediateId = setImmediate(function() {
        if (typeof handler === 'function') {

            switch (args.length) {
                // fast cases
                case 0:
                    handler.call(that);
                    that.run();
                    break;
                case 1:
                    handler.call(that, args[0]);
                    that.run();
                    break;
                case 2:
                    handler.call(that, args[0], args[1]);
                    that.run();
                    break;
                    // slower
                default:
                    handler.apply(that, args);
                    that.run();
            }
                } else {
                //no function added
                that.emit('error', new Error('no function has been added to Forever'));
            }
        });
};

Forever.prototype.stop = function() {
    if (this._immediateId !== null) {
        clearImmediate(this._immediateId);
    } else {
        this.emit('error', new Error('You cannot stop a loop before it has been started'));
    }
};

Forever.prototype.onError = function(errHandler) {
    if ('function' === typeof errHandler) {
        this.on('error', errHandler);
    } else {
        this.emit('error', new Error('You should use a function to handle the error'));
    }
    return this;
};

example usage:

var Forever = require('path/to/this/file');
var f = new Forever();

// function to be runned
function say(content1, content2){
    console.log(content1 + content2);
}

//add function to the loop
//the first argument is the function, the rest are its arguments
//chainable api
f.add(say, 'hello', ' world!').run();

//stop it after 5s
setTimeout(function(){
    f.stop();
}, 5000);

That's it.

Addlebrained answered 13/7, 2012 at 18:26 Comment(1)
setImmediate is non-standard and should be avoided, as it will not work for every user. In fact, it appears to only be supported by IE, according to MDN. developer.mozilla.org/en-US/docs/Web/API/Window/setImmediatePredestinarian
C
2

Infinity in this case is up to you what the max iterations of a loop will be. This code is blocking the single threaded nature of JavaScript so you will lock up everything anyway unless you are using web workers. Better to not check it every x seconds because this code will block execution of an interval or timeout anyway, rather have it within the loop itself as a max threshold of loop iterations.

var infiniteloop = false;
var condition = true;
var loopCounter = 1;
var maxLoopIterations = 1000; 
function loop () {
  while (condition) {
    console.log('hi');
    infiniteLoop = (loopCounter >= maxLoopIterations); 
    if (infiniteloop) {
      condition = false;
      console.log('oh last one');
      break;
    }
    loopCounter++;
  }
}
Caponize answered 13/7, 2012 at 18:33 Comment(5)
The loop is infinite. I can't set a max iteration on it.Laoighis
you have to have a way of blocking execution of the loop, such as having setInterval spawn the work within it, otherwise it will just keep blocking. You should put the actual example up. If it has a network call or something similar, will be perfect to do what you are asking.Caponize
Yes that's right about the network call. This is serverside javascript, so I assume I can make a call to a different process that will return the infiniteloop var. That's what I asked about in question 3Laoighis
@Laoighis this is something I recently read from John Resig -> ejohn.org/blog/how-javascript-timers-work and I thought it was good to know and might be related to your issue in how you are thinking things work may not be lining up exactly.Caponize
Node.js ? Can't you just access a scoped var that shows # of tries, then break when you are done? I would have to see the code.Caponize
G
0

You could create a child process (fork) to check if your actual process is responding. Fork would be sending messages to parent if there is no response - kill parent and fork. Something similar to this what you could see in this gist: https://gist.github.com/kevinohara80/3173692

If you would be using express node.js server you could try out middleware harakiri which would do what you need.

Gide answered 28/9, 2018 at 7:34 Comment(0)
L
0

I want to present my solution. In my case I was having several infinite loops (while(true)) that are only terminated by break statements. Whether the conditions for these break statements are ever reached is hard to determine for sure, and the algorithm wasn't developed by myself so a complete refactoring was also not an option.

I like the simple solution by @JasonSebring with a loop counter. Just setting a limit for the number of iterations was fine in my case. But I did not want to insert all these variables into my code, plus I needed a solution that works with nested loops. So I came up with this wrapper function:

/**
 * Stop potential infinite loops after a certain number of iterations.
 * @param loopLimit - The maximum number of iterations before loop is aborted.
 * @param silentStop - Whether to abort the loop silently or throw an error instead.
 * @param callBack - Function representing the inner code of the loop.
 */
static finiteLoopHelper(loopLimit, silentStop, callBack) {
  let loopCounter = 0;
  let stopLoop = false;

  while (!stopLoop) {
    // Return value from the callback can invoke an early stop, like a break statement.
    stopLoop = callBack();

    loopCounter++;
    if (loopCounter >= loopLimit) {
      stopLoop = true;
      if (!silentStop) {
        throw Error(`Loop aborted after ${loopLimit} iterations.`);
      }
    }
  }
}

Usage is like this:

let someVariable = 0;

finiteLoopHelper(1000, false, () => { // this line replaces "while (true) {"
  someVariable = someCalculation();
  if (someVariable === someCondition) {
    return false; // like continue in a normal loop.
  }
  codeNotExecutedInCaseOfContinue();

  if (someVariable === someOtherCondition) {
    return true; // like break in a normal loop.
  }

  // Return value at the end can be omitted, because without it the function
  // will return undefined which also keeps the loop running.
  // return false;
});

If your loop had a condition before, you will have to check this condition inside the arrow function and use return true; like in the example above.

Since the loop counter is not exposed it is possible to nest this solution without the need to find different variable names for the counters of inner loops.

Leannleanna answered 27/9, 2019 at 9:14 Comment(0)
H
0

In a function, simply return false or undefined.

Manually throw new Error("ERROR") in a function.

Set a function to run on a timer – var timer = setInterval(FUNCTION, 1000). Then clear it to stop – clearInterval(timer)

Run the script with workers that can be terminated.

Use window.stop() to prevent the page from loading and running.

For NodeJS only – Use process.abort() or process.exit().

Hypothetical answered 19/2, 2022 at 23:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.