Search code examples
javascriptnode.jsweb-workerworkerworker-thread

Can one prevent a node.js worker from exiting the main process


I am using nodejs 12 and this questions is related to https://nodejs.org/api/worker_threads.html I know that Workers are sharing the same process as the main JS "thread"; still I am wondering if there's a way to prevent any error in the worker code from crashing the main process.

I want to use a worker thread instead of forked process but this issue would be a blocker.

worker.js

const { parentPort } = require( "worker_threads");

let i = 0;
setInterval(() => {
  parentPort.postMessage({ hello: "world" });
  i++;
  if (i > 5) {
    throw new Error('Hello'); // imagine here some error that hasn't been caught
  }
}, 200);

setInterval(() => {
  parentPort.postMessage({ foo: "bar" });
}, 600);

index.js

const { Worker } = require("worker_threads");
const worker = new Worker("./worker.js");

worker.on("error", (err) => {
  console.error(err);
});

worker.on("exit", () => {
  console.log('Worker exited')
});

worker.on("message", msg => {
  console.log(msg)
});

Output

➜ node index.js
{ hello: 'world' }
{ hello: 'world' }
{ foo: 'bar' }
{ hello: 'world' }
{ hello: 'world' }
{ hello: 'world' }
{ foo: 'bar' }
{ hello: 'world' }
Error: Something happened
    at Timeout._onTimeout (/Users/micael/Desktop/crashing/worker.js:10:11)
    at listOnTimeout (internal/timers.js:549:17)
    at processTimers (internal/timers.js:492:7)
Worker exited

Solution

  • Errors in the worker do not crash the main thread. The reason your main process exits is because there are no more pending events for the event loop to process once the worker thread exits. This is the same as regular main scripts such as:

    // index.js
    console.log('hello');
    // this scripts exits here
    

    If you run the simple script above you will see node.js exit the main process at the end of the script. This is normal behavior.

    To prove this give your main thread something else to do:

    // index.js
    const { Worker } = require("worker_threads");
    const worker = new Worker("./worker.js");
    
    worker.on("error", (err) => {
      console.error(err);
    });
    
    worker.on("exit", () => {
      console.log('Worker exited')
    });
    
    worker.on("message", msg => {
      console.log(msg)
    });
    
    setInterval(() => console.log('main is running'), 500);
    

    You will find the main thread continue to run after your worker thread crashes.


    Additional answer

    From your comment it appears that what you want is not for the main thread to continue running but for the worker thread to continue running. This is not possible by the nature of how javascript handles uncaught errors: the interpreter will simply log the error and exit. The interpreter exiting will cause the thread to terminate.

    If you want the second setTimeout to continue after the worker thread dies you need to run it in a separate thread:

    worker1.js:

    const { parentPort } = require( "worker_threads");
    
    let i = 0;
    setInterval(() => {
      parentPort.postMessage({ hello: "world" });
      i++;
      if (i > 5) {
        throw new Error('Hello'); // imagine here some error that hasn't been caught
      }
    }, 200);
    

    worker2.js:

    const { parentPort } = require( "worker_threads");
    
    setInterval(() => {
      parentPort.postMessage({ foo: "bar" });
    }, 600);
    

    index.js:

    const { Worker } = require("worker_threads");
    const worker1 = new Worker("./worker.js");
    const worker2 = new Worker("./worker.js");
    
    worker1.on("error", (err) => {
      console.error(err);
    });
    
    worker1.on("exit", () => {
      console.log('Worker exited')
    });
    
    worker1.on("message", msg => {
      console.log(msg)
    });
    
    worker2.on("error", (err) => {
      console.error(err);
    });
    
    worker2.on("exit", () => {
      console.log('Worker exited')
    });
    
    worker2.on("message", msg => {
      console.log(msg)
    });
    

    Alternatively you can run the second setInterval in the main thread:

    worker.js:

    const { parentPort } = require( "worker_threads");
    
    let i = 0;
    setInterval(() => {
      parentPort.postMessage({ hello: "world" });
      i++;
      if (i > 5) {
        throw new Error('Hello'); // imagine here some error that hasn't been caught
      }
    }, 200);
    

    index.js:

    const { Worker } = require("worker_threads");
    const worker = new Worker("./worker.js");
    
    worker.on("error", (err) => {
      console.error(err);
    });
    
    worker.on("exit", () => {
      console.log('Worker exited')
    });
    
    worker.on("message", msg => {
      console.log(msg)
    });
    
    setInterval(() => {
      parentPort.postMessage({ foo: "bar" });
    }, 600);