Skip to content

Commit 75dc437

Browse files
committed
make worker instantiation thread safe + some other review findings
1 parent 8fe957e commit 75dc437

5 files changed

Lines changed: 33 additions & 13 deletions

File tree

lib/internal/modules/esm/hooks.js

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -515,12 +515,15 @@ class HooksProxy {
515515
#isWorkerOwner = false;
516516

517517
constructor() {
518-
const { InternalWorker, hooksPort, hasHooksThread } = require('internal/worker');
518+
const { InternalWorker, hooksPort } = require('internal/worker');
519519
const lock = new SharedArrayBuffer(SHARED_MEMORY_BYTE_LENGTH);
520520
this.#lock = new Int32Array(lock);
521521

522-
if (!hasHooksThread()) {
523-
// Main thread is the only one that creates the internal single hooks worker
522+
try {
523+
// The customization hooks thread is only created once. All other threads reuse
524+
// the existing instance. If another thread created it, the constructor will throw
525+
// Fallback is to use the existing hooksPort created by the thread that originally
526+
// spawned the customization hooks thread.
524527
this.#worker = new InternalWorker(loaderWorkerId, {
525528
stderr: false,
526529
stdin: false,
@@ -534,16 +537,21 @@ class HooksProxy {
534537
this.#worker.on('exit', process.exit);
535538
this.#isWorkerOwner = true;
536539
this.#portToHooksThread = this.#worker;
537-
} else {
538-
this.#portToHooksThread = hooksPort;
540+
} catch (e) {
541+
if (e.code === 'ERR_HOOKS_THREAD_EXISTS') {
542+
this.#portToHooksThread = hooksPort;
543+
} else {
544+
throw e;
545+
}
539546
}
540547
}
541548

542549
waitForWorker() {
543-
// There is one Hooks instance for each worker thread. But only one of these Hooks instances
544-
// has an InternalWorker. That was the Hooks instance created for the main thread.
545-
// It means for all Hooks instances that are not on the main thread => they are ready because they
546-
// delegate to the single InternalWorker anyway.
550+
// There is one Hooks instance for each worker thread. But only one of
551+
// these Hooks instances has an InternalWorker. That was the Hooks instance
552+
// created for the first thread that registers a hook set.
553+
// It means for all Hooks instances that are not on that thread => they are
554+
// done because they delegate to the single InternalWorker.
547555
if (!this.#isWorkerOwner) {
548556
return;
549557
}

lib/internal/modules/esm/loader.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -622,8 +622,6 @@ class CustomizedModuleLoader {
622622
* @returns {{ format: string, url: URL['href'] } | undefined}
623623
*/
624624
register(originalSpecifier, parentURL, data, transferList) {
625-
// Only the main thread has a Hooks instance with worker thread. All other Worker threads
626-
// delegate their hooks to the HooksThread of the main thread.
627625
return hooksProxy.makeSyncRequest('register', transferList, originalSpecifier, parentURL, data);
628626
}
629627

src/node_errors.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ void OOMErrorHandler(const char* location, const v8::OOMDetails& details);
7070
V(ERR_DLOPEN_FAILED, Error) \
7171
V(ERR_ENCODING_INVALID_ENCODED_DATA, TypeError) \
7272
V(ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE, Error) \
73+
V(ERR_HOOKS_THREAD_EXISTS, Error) \
7374
V(ERR_ILLEGAL_CONSTRUCTOR, Error) \
7475
V(ERR_INVALID_ADDRESS, Error) \
7576
V(ERR_INVALID_ARG_VALUE, TypeError) \

src/node_worker.cc

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ namespace worker {
4747

4848
constexpr double kMB = 1024 * 1024;
4949
std::atomic_bool Worker::internalExists{false};
50+
Mutex Worker::instantiationMutex;
5051

5152
Worker::Worker(Environment* env,
5253
Local<Object> wrap,
@@ -484,18 +485,28 @@ Worker::~Worker() {
484485
}
485486

486487
void Worker::New(const FunctionCallbackInfo<Value>& args) {
488+
Mutex::ScopedLock lock(instantiationMutex);
487489
Environment* env = Environment::GetCurrent(args);
488490
auto is_internal = args[5];
489491
CHECK(is_internal->IsBoolean());
490492
if (is_internal->IsFalse()) {
491493
THROW_IF_INSUFFICIENT_PERMISSIONS(
492494
env, permission::PermissionScope::kWorkerThreads, "");
493-
} else {
494-
internalExists = true;
495495
}
496496
Isolate* isolate = args.GetIsolate();
497497

498498
CHECK(args.IsConstructCall());
499+
auto creatingHooksThread = is_internal->IsTrue();
500+
501+
if (creatingHooksThread && internalExists) {
502+
isolate->ThrowException(ERR_HOOKS_THREAD_EXISTS(
503+
isolate, "Customization hooks thread already exists"));
504+
return;
505+
}
506+
507+
if (creatingHooksThread) {
508+
internalExists = true;
509+
}
499510

500511
if (env->isolate_data()->platform() == nullptr) {
501512
THROW_ERR_MISSING_PLATFORM_FOR_WORKER(env);

src/node_worker.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ class Worker : public AsyncWrap {
105105
// Optional name used for debugging in inspector and trace events.
106106
std::string name_;
107107
static std::atomic_bool internalExists;
108+
// this mutex is to synchronize ::New calls
109+
static Mutex instantiationMutex;
108110

109111
// Custom resource constraints:
110112
double resource_limits_[kTotalResourceLimitCount];

0 commit comments

Comments
 (0)