3

I am implementing a Random Sampling / Montecarlo heuristic for the Travelling Salesman Problem. I want to perform maximum c iterations, while being able to stop the search when I want by Ctrl+C or sending SIGINT to my process.

I know this has been asked before (related: Quitting node.js gracefully) but the given solution is not working for me. The process just doesn't exit when I give Ctrl+C and if I kill it, the finalizing code is not executed.

My code:

var tsp = require("./Tsp.js");
var data = tsp.load("./input/it16862.tsp");

var starting_point = data.splice(10927, 1)[0];
var c = 0;
var cost = Number.POSITIVE_INFINITY;
var new_cost, solution, candidate_solution;
var interrupt = false;
var c = 0, interrupt = false;

process.on('SIGINT', function() {
    interrupt = true;
});

while(c < 1000000000) {
    if (interrupt) {
        break;
    }
    candidate_solution = shuffle(data);
    new_cost = tsp.cost(candidate_solution, starting_point);
    if (new_cost < cost) {
        cost = new_cost;
        solution = candidate_solution;
        console.log("Found a new better solution! %d", cost);
    }
    c++;
}

if (interrupt) {
    console.log("Caught interrupt signal");
}
console.log("Examined %d solutions", c);
console.log("Best: %j", cost);
console.log("Solution written to: %s", tsp.write(solution, starting_point, cost));

I am on Ubuntu 14.04.1, Nodejs 4.2.4. Any idea what might be wrong?

Community
  • 1
  • 1
Andrea Casaccia
  • 4,802
  • 4
  • 29
  • 54

1 Answers1

3

JavaScript is a single-threaded language, so there is no way for the runtime to interrupt the process in the middle of a while loop like this. That process.on handler function will not be invoked until the main event loop is free, which won’t happen until c >= 1000000000 because you never yield.

In order for this to work you need to change your work loop to occasionally yield back to the Node.js runtime, like this:

// ...
var c = 0, interrupt = false;
function doWork() {
  while(c < 1000000000) {
      if (interrupt) {
          break;
      }
      // yield every 10000 iterations to
      // allow the event loop to handle events
      if ((c % 10000) === 0) {
          setImmediate(doWork);
          return;
      }
      candidate_solution = shuffle(data);
      new_cost = tsp.cost(candidate_solution, starting_point);
      if (new_cost < cost) {
          cost = new_cost;
          solution = candidate_solution;
          console.log("Found a new better solution! %d", cost);
      }
      c++;
  }
}

doWork();

You can tune performance versus responsiveness by choosing a different value for the number of iterations between yields (higher numbers improve performance by avoiding overhead caused by yielding, but reduce responsiveness by making it take longer until an interrupt can be acknowledged).

C Snover
  • 17,908
  • 5
  • 29
  • 39
  • Interesting explanation, definetely makes sense, but I still can't get it to work, the `SIGINT` callback is never executed. – Andrea Casaccia Jan 16 '16 at 17:38
  • Interestingly if I use `setTimeout(doWork, 100);` instead of `process.nextTick(doWork)` it works... – Andrea Casaccia Jan 16 '16 at 17:45
  • Sorry I think the semantics of `nextTick` may have changed over time (or my brain is broken!). I’ve updated the answer to use `setImmediate` instead which should allow I/O to occur. – C Snover Jan 16 '16 at 18:54
  • Thanks, related http://stackoverflow.com/questions/15349733/setimmediate-vs-nexttick – Andrea Casaccia Jan 16 '16 at 18:59
  • This works fine, unless you want anything close to realtime - you might have to wait for 10000 loop cycles before it finally sees that an outside event has occurred! oof – Alexander Mills Nov 19 '22 at 21:52