Skip to content

Commit

Permalink
lib: fix workers thread-safety issues
Browse files Browse the repository at this point in the history
Fixes: #1393
  • Loading branch information
rmnilin authored and mscdex committed Feb 5, 2025
1 parent 0fe2643 commit dd5510c
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 24 deletions.
72 changes: 48 additions & 24 deletions lib/protocol/crypto/src/binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,15 @@ class ChaChaPolyCipher : public ObjectWrap {
SetPrototypeMethod(tpl, "encrypt", Encrypt);
SetPrototypeMethod(tpl, "free", Free);

constructor().Reset(Nan::GetFunction(tpl).ToLocalChecked());
Local<Function> func = Nan::GetFunction(tpl).ToLocalChecked();
Local<Context> context = Nan::GetCurrentContext();
v8::Isolate* isolate = context->GetIsolate();

constructor().Set(isolate, func);

Nan::Set(target,
Nan::New("ChaChaPolyCipher").ToLocalChecked(),
Nan::GetFunction(tpl).ToLocalChecked());
func);
}

private:
Expand Down Expand Up @@ -387,8 +391,8 @@ class ChaChaPolyCipher : public ObjectWrap {
obj->clear();
}

static inline Nan::Persistent<Function> & constructor() {
static Nan::Persistent<Function> my_constructor;
static inline v8::Eternal<v8::Function> & constructor() {
static v8::Eternal<v8::Function> my_constructor;
return my_constructor;
}

Expand All @@ -414,11 +418,15 @@ class AESGCMCipher : public ObjectWrap {
SetPrototypeMethod(tpl, "encrypt", Encrypt);
SetPrototypeMethod(tpl, "free", Free);

constructor().Reset(Nan::GetFunction(tpl).ToLocalChecked());
Local<Function> func = Nan::GetFunction(tpl).ToLocalChecked();
Local<Context> context = Nan::GetCurrentContext();
v8::Isolate* isolate = context->GetIsolate();

constructor().Set(isolate, func);

Nan::Set(target,
Nan::New("AESGCMCipher").ToLocalChecked(),
Nan::GetFunction(tpl).ToLocalChecked());
func);
}

private:
Expand Down Expand Up @@ -633,8 +641,8 @@ class AESGCMCipher : public ObjectWrap {
obj->clear();
}

static inline Nan::Persistent<Function> & constructor() {
static Nan::Persistent<Function> my_constructor;
static inline v8::Eternal<v8::Function> & constructor() {
static v8::Eternal<v8::Function> my_constructor;
return my_constructor;
}

Expand All @@ -651,11 +659,15 @@ class GenericCipher : public ObjectWrap {
SetPrototypeMethod(tpl, "encrypt", Encrypt);
SetPrototypeMethod(tpl, "free", Free);

constructor().Reset(Nan::GetFunction(tpl).ToLocalChecked());
Local<Function> func = Nan::GetFunction(tpl).ToLocalChecked();
Local<Context> context = Nan::GetCurrentContext();
v8::Isolate* isolate = context->GetIsolate();

constructor().Set(isolate, func);

Nan::Set(target,
Nan::New("GenericCipher").ToLocalChecked(),
Nan::GetFunction(tpl).ToLocalChecked());
func);
}

private:
Expand Down Expand Up @@ -1014,8 +1026,8 @@ class GenericCipher : public ObjectWrap {
obj->clear();
}

static inline Nan::Persistent<Function> & constructor() {
static Nan::Persistent<Function> my_constructor;
static inline v8::Eternal<v8::Function> & constructor() {
static v8::Eternal<v8::Function> my_constructor;
return my_constructor;
}

Expand Down Expand Up @@ -1044,11 +1056,15 @@ class ChaChaPolyDecipher : public ObjectWrap {
SetPrototypeMethod(tpl, "decryptLen", DecryptLen);
SetPrototypeMethod(tpl, "free", Free);

constructor().Reset(Nan::GetFunction(tpl).ToLocalChecked());
Local<Function> func = Nan::GetFunction(tpl).ToLocalChecked();
Local<Context> context = Nan::GetCurrentContext();
v8::Isolate* isolate = context->GetIsolate();

constructor().Set(isolate, func);

Nan::Set(target,
Nan::New("ChaChaPolyDecipher").ToLocalChecked(),
Nan::GetFunction(tpl).ToLocalChecked());
func);
}

private:
Expand Down Expand Up @@ -1440,8 +1456,8 @@ class ChaChaPolyDecipher : public ObjectWrap {
obj->clear();
}

static inline Nan::Persistent<Function> & constructor() {
static Nan::Persistent<Function> my_constructor;
static inline v8::Eternal<v8::Function> & constructor() {
static v8::Eternal<v8::Function> my_constructor;
return my_constructor;
}

Expand All @@ -1468,11 +1484,15 @@ class AESGCMDecipher : public ObjectWrap {
SetPrototypeMethod(tpl, "decrypt", Decrypt);
SetPrototypeMethod(tpl, "free", Free);

constructor().Reset(Nan::GetFunction(tpl).ToLocalChecked());
Local<Function> func = Nan::GetFunction(tpl).ToLocalChecked();
Local<Context> context = Nan::GetCurrentContext();
v8::Isolate* isolate = context->GetIsolate();

constructor().Set(isolate, func);

Nan::Set(target,
Nan::New("AESGCMDecipher").ToLocalChecked(),
Nan::GetFunction(tpl).ToLocalChecked());
func);
}

private:
Expand Down Expand Up @@ -1697,8 +1717,8 @@ class AESGCMDecipher : public ObjectWrap {
obj->clear();
}

static inline Nan::Persistent<Function> & constructor() {
static Nan::Persistent<Function> my_constructor;
static inline v8::Eternal<v8::Function> & constructor() {
static v8::Eternal<v8::Function> my_constructor;
return my_constructor;
}

Expand All @@ -1716,11 +1736,15 @@ class GenericDecipher : public ObjectWrap {
SetPrototypeMethod(tpl, "decrypt", Decrypt);
SetPrototypeMethod(tpl, "free", Free);

constructor().Reset(Nan::GetFunction(tpl).ToLocalChecked());
Local<Function> func = Nan::GetFunction(tpl).ToLocalChecked();
Local<Context> context = Nan::GetCurrentContext();
v8::Isolate* isolate = context->GetIsolate();

constructor().Set(isolate, func);

Nan::Set(target,
Nan::New("GenericDecipher").ToLocalChecked(),
Nan::GetFunction(tpl).ToLocalChecked());
func);
}

private:
Expand Down Expand Up @@ -2183,8 +2207,8 @@ class GenericDecipher : public ObjectWrap {
obj->clear();
}

static inline Nan::Persistent<Function> & constructor() {
static Nan::Persistent<Function> my_constructor;
static inline v8::Eternal<v8::Function> & constructor() {
static v8::Eternal<v8::Function> my_constructor;
return my_constructor;
}

Expand Down
25 changes: 25 additions & 0 deletions test/test-worker-imports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Test for thread-safety issues caused by subsequent imports of the module
// in worker threads: https://github.com/mscdex/ssh2/issues/1393.
// Each subsequent worker increases probability of abnormal termination.
// The probability of a false pass due to zero response becomes negligible
// for 4 consecutive workers.
'use strict';

let Worker, isMainThread;
try {
({ Worker, isMainThread } = require('worker_threads'));
} catch (e) {
if (e.code !== 'MODULE_NOT_FOUND') throw e;
process.exit(0);
}
require('../lib/index.js');

if (isMainThread) {
async function runWorker() {
return new Promise((r) => new Worker(__filename).on('exit', r));
}
runWorker()
.then(runWorker)
.then(runWorker)
.then(runWorker);
}

0 comments on commit dd5510c

Please sign in to comment.