简体   繁体   中英

javascript stop an infinite loop

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.

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.

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++;
  }
}

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.

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.

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.

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().

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM