diff --git a/doc/api/errors.md b/doc/api/errors.md
index 2e2033a1e583ad..8de1b49e260ea1 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -2007,6 +2007,11 @@ meaning of the error depends on the specific function.
The WASI instance has already started.
+
+### `ERR_WORKER_INIT_FAILED`
+
+The `Worker` initialization failed.
+
### `ERR_WORKER_INVALID_EXEC_ARGV`
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index ac42b07f095b44..7611e461ad69f7 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -1344,11 +1344,12 @@ E('ERR_VM_MODULE_NOT_MODULE',
'Provided module is not an instance of Module', Error);
E('ERR_VM_MODULE_STATUS', 'Module status %s', Error);
E('ERR_WASI_ALREADY_STARTED', 'WASI instance has already started', Error);
+E('ERR_WORKER_INIT_FAILED', 'Worker initialization failure: %s', Error);
E('ERR_WORKER_INVALID_EXEC_ARGV', (errors, msg = 'invalid execArgv flags') =>
`Initiated Worker with ${msg}: ${errors.join(', ')}`,
Error);
-E('ERR_WORKER_OUT_OF_MEMORY', 'Worker terminated due to reaching memory limit',
- Error);
+E('ERR_WORKER_OUT_OF_MEMORY',
+ 'Worker terminated due to reaching memory limit: %s', Error);
E('ERR_WORKER_PATH',
'The worker script filename must be an absolute path or a relative ' +
'path starting with \'./\' or \'../\'. Received "%s"',
diff --git a/lib/internal/worker.js b/lib/internal/worker.js
index 1c02f3a8f5b9e6..9924554ff96c8d 100644
--- a/lib/internal/worker.js
+++ b/lib/internal/worker.js
@@ -24,6 +24,8 @@ const {
ERR_WORKER_UNSUPPORTED_EXTENSION,
ERR_WORKER_INVALID_EXEC_ARGV,
ERR_INVALID_ARG_TYPE,
+ // eslint-disable-next-line no-unused-vars
+ ERR_WORKER_INIT_FAILED,
} = errorCodes;
const { validateString } = require('internal/validators');
const { getOptionValue } = require('internal/options');
@@ -135,7 +137,9 @@ class Worker extends EventEmitter {
throw new ERR_WORKER_INVALID_EXEC_ARGV(
this[kHandle].invalidNodeOptions, 'invalid NODE_OPTIONS env variable');
}
- this[kHandle].onexit = (code, customErr) => this[kOnExit](code, customErr);
+ this[kHandle].onexit = (code, customErr, customErrReason) => {
+ this[kOnExit](code, customErr, customErrReason);
+ };
this[kPort] = this[kHandle].messagePort;
this[kPort].on('message', (data) => this[kOnMessage](data));
this[kPort].start();
@@ -180,14 +184,15 @@ class Worker extends EventEmitter {
this[kHandle].startThread();
}
- [kOnExit](code, customErr) {
+ [kOnExit](code, customErr, customErrReason) {
debug(`[${threadId}] hears end event for Worker ${this.threadId}`);
drainMessagePort(this[kPublicPort]);
drainMessagePort(this[kPort]);
this[kDispose]();
if (customErr) {
- debug(`[${threadId}] failing with custom error ${customErr}`);
- this.emit('error', new errorCodes[customErr]());
+ debug(`[${threadId}] failing with custom error ${customErr} \
+ and with reason {customErrReason}`);
+ this.emit('error', new errorCodes[customErr](customErrReason));
}
this.emit('exit', code);
this.removeAllListeners();
diff --git a/src/node_worker.cc b/src/node_worker.cc
index 30d3a138b16cf7..1cf02b19619984 100644
--- a/src/node_worker.cc
+++ b/src/node_worker.cc
@@ -138,7 +138,16 @@ class WorkerThreadData {
public:
explicit WorkerThreadData(Worker* w)
: w_(w) {
- CHECK_EQ(uv_loop_init(&loop_), 0);
+ int ret = uv_loop_init(&loop_);
+ if (ret != 0) {
+ char err_buf[128];
+ uv_err_name_r(ret, err_buf, sizeof(err_buf));
+ w->custom_error_ = "ERR_WORKER_INIT_FAILED";
+ w->custom_error_str_ = err_buf;
+ w->loop_init_failed_ = true;
+ w->stopped_ = true;
+ return;
+ }
Isolate::CreateParams params;
SetIsolateCreateParamsForNode(¶ms);
@@ -149,6 +158,8 @@ class WorkerThreadData {
Isolate* isolate = Isolate::Allocate();
if (isolate == nullptr) {
w->custom_error_ = "ERR_WORKER_OUT_OF_MEMORY";
+ w->custom_error_str_ = "Failed to create new Isolate";
+ w->stopped_ = true;
return;
}
@@ -206,11 +217,14 @@ class WorkerThreadData {
isolate->Dispose();
// Wait until the platform has cleaned up all relevant resources.
- while (!platform_finished)
+ while (!platform_finished) {
+ CHECK(!w_->loop_init_failed_);
uv_run(&loop_, UV_RUN_ONCE);
+ }
+ }
+ if (!w_->loop_init_failed_) {
+ CheckedUvLoopClose(&loop_);
}
-
- CheckedUvLoopClose(&loop_);
}
private:
@@ -225,6 +239,7 @@ size_t Worker::NearHeapLimit(void* data, size_t current_heap_limit,
size_t initial_heap_limit) {
Worker* worker = static_cast(data);
worker->custom_error_ = "ERR_WORKER_OUT_OF_MEMORY";
+ worker->custom_error_str_ = "JS heap out of memory";
worker->Exit(1);
// Give the current GC some extra leeway to let it finish rather than
// crash hard. We are not going to perform further allocations anyway.
@@ -244,6 +259,7 @@ void Worker::Run() {
WorkerThreadData data(this);
if (isolate_ == nullptr) return;
+ CHECK(!data.w_->loop_init_failed_);
Debug(this, "Starting worker with id %llu", thread_id_);
{
@@ -289,9 +305,8 @@ void Worker::Run() {
TryCatch try_catch(isolate_);
context = NewContext(isolate_);
if (context.IsEmpty()) {
- // TODO(addaleax): Inform the target about the actual underlying
- // failure.
custom_error_ = "ERR_WORKER_OUT_OF_MEMORY";
+ custom_error_str_ = "Failed to create new Context";
return;
}
}
@@ -421,10 +436,14 @@ void Worker::JoinThread() {
Undefined(env()->isolate())).Check();
Local args[] = {
- Integer::New(env()->isolate(), exit_code_),
- custom_error_ != nullptr ?
- OneByteString(env()->isolate(), custom_error_).As() :
- Null(env()->isolate()).As(),
+ Integer::New(env()->isolate(), exit_code_),
+ custom_error_ != nullptr
+ ? OneByteString(env()->isolate(), custom_error_).As()
+ : Null(env()->isolate()).As(),
+ !custom_error_str_.empty()
+ ? OneByteString(env()->isolate(), custom_error_str_.c_str())
+ .As()
+ : Null(env()->isolate()).As(),
};
MakeCallback(env()->onexit_string(), arraysize(args), args);
diff --git a/src/node_worker.h b/src/node_worker.h
index 3a15a1d3dbbd7f..ff2864e3193174 100644
--- a/src/node_worker.h
+++ b/src/node_worker.h
@@ -87,6 +87,8 @@ class Worker : public AsyncWrap {
bool thread_joined_ = true;
const char* custom_error_ = nullptr;
+ std::string custom_error_str_;
+ bool loop_init_failed_ = false;
int exit_code_ = 0;
uint64_t thread_id_ = -1;
uintptr_t stack_base_ = 0;
diff --git a/test/parallel/test-worker-resource-limits.js b/test/parallel/test-worker-resource-limits.js
index 2d4ebbc0ce6011..9332a132694e78 100644
--- a/test/parallel/test-worker-resource-limits.js
+++ b/test/parallel/test-worker-resource-limits.js
@@ -25,7 +25,8 @@ if (!process.env.HAS_STARTED_WORKER) {
}));
w.on('error', common.expectsError({
code: 'ERR_WORKER_OUT_OF_MEMORY',
- message: 'Worker terminated due to reaching memory limit'
+ message: 'Worker terminated due to reaching memory limit: ' +
+ 'JS heap out of memory'
}));
return;
}