-
Notifications
You must be signed in to change notification settings - Fork 465
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
tsfn: Implement copy constructor #546
Changes from 3 commits
1b42bfc
e9f38dc
e10e683
dfa9e5f
a033340
815c1fe
3219515
0e9d4ae
cba39bf
5b83cf2
5011023
4b3d134
bcdc46c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
#include "napi.h" | ||
#include <thread> | ||
#include <future> | ||
|
||
#if (NAPI_VERSION > 3) | ||
|
||
using namespace Napi; | ||
using namespace std; | ||
|
||
namespace { | ||
|
||
struct TestData { | ||
// Native Promise returned to JavaScript | ||
Promise::Deferred deferred; | ||
|
||
// List of threads created for test. This list only ever accessed via main | ||
// thread. | ||
vector<thread> threads = {}; | ||
|
||
ThreadSafeFunction tsfn = ThreadSafeFunction(); | ||
}; | ||
|
||
void FinalizerCallback(Napi::Env env, TestData* finalizeData){ | ||
for (size_t i = 0; i < finalizeData->threads.size(); ++i) { | ||
finalizeData->threads[i].join(); | ||
} | ||
finalizeData->deferred.Resolve(Boolean::New(env,true)); | ||
delete finalizeData; | ||
} | ||
|
||
/** | ||
* See threadsafe_function_sum.js for descriptions of the tests in this file | ||
*/ | ||
|
||
void entryWithTSFN(ThreadSafeFunction tsfn, int threadId) { | ||
std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 100 + 1)); | ||
tsfn.BlockingCall( [=](Napi::Env env, Function callback) { | ||
callback.Call( { Number::New(env, static_cast<double>(threadId))}); | ||
}); | ||
tsfn.Release(); | ||
} | ||
|
||
static Value TestWithTSFN(const CallbackInfo& info) { | ||
int threadCount = info[0].As<Number>().Int32Value(); | ||
Function cb = info[1].As<Function>(); | ||
|
||
// We pass the test data to the Finalizer for cleanup. The finalizer is | ||
// responsible for deleting this data as well. | ||
TestData *testData = new TestData({ | ||
Promise::Deferred::New(info.Env()) | ||
}); | ||
|
||
ThreadSafeFunction tsfn = ThreadSafeFunction::New( | ||
info.Env(), cb, "Test", 0, threadCount, | ||
std::function<decltype(FinalizerCallback)>(FinalizerCallback), testData); | ||
|
||
for (int i = 0; i < threadCount; ++i) { | ||
testData->threads.push_back( thread(entryWithTSFN, tsfn, i) ); | ||
} | ||
|
||
return testData->deferred.Promise(); | ||
} | ||
|
||
|
||
void entryDelayedTSFN(std::future<ThreadSafeFunction> tsfnFuture, int threadId) { | ||
std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 100 + 1)); | ||
ThreadSafeFunction tsfn = tsfnFuture.get(); | ||
tsfn.BlockingCall( [=](Napi::Env env, Function callback) { | ||
callback.Call( { Number::New(env, static_cast<double>(threadId))}); | ||
}); | ||
tsfn.Release(); | ||
} | ||
|
||
static Value TestDelayedTSFN(const CallbackInfo& info) { | ||
int threadCount = info[0].As<Number>().Int32Value(); | ||
Function cb = info[1].As<Function>(); | ||
|
||
TestData *testData = new TestData({ | ||
Promise::Deferred::New(info.Env()) | ||
}); | ||
|
||
vector< std::promise<ThreadSafeFunction> > tsfnPromises; | ||
|
||
for (int i = 0; i < threadCount; ++i) { | ||
tsfnPromises.emplace_back(); | ||
testData->threads.push_back( thread(entryDelayedTSFN, tsfnPromises[i].get_future(), i) ); | ||
} | ||
|
||
testData->tsfn = ThreadSafeFunction::New( | ||
info.Env(), cb, "Test", 0, threadCount, | ||
std::function<decltype(FinalizerCallback)>(FinalizerCallback), testData); | ||
|
||
for (int i = 0; i < threadCount; ++i) { | ||
tsfnPromises[i].set_value(testData->tsfn); | ||
} | ||
|
||
return testData->deferred.Promise(); | ||
} | ||
|
||
void entryAcquire(ThreadSafeFunction tsfn, int threadId) { | ||
tsfn.Acquire(); | ||
std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 100 + 1)); | ||
tsfn.BlockingCall( [=](Napi::Env env, Function callback) { | ||
callback.Call( { Number::New(env, static_cast<double>(threadId))}); | ||
}); | ||
tsfn.Release(); | ||
} | ||
|
||
static Value CreateThread(const CallbackInfo& info) { | ||
TestData* testData = static_cast<TestData*>(info.Data()); | ||
ThreadSafeFunction tsfn = testData->tsfn; | ||
int threadId = testData->threads.size(); | ||
testData->threads.push_back( thread(entryAcquire, tsfn, threadId) ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if it would be useful to highlight with a comment that this is where the copy constructor is being tested. Since the main change is to add the copy constructor it would be good to make it obvious in the test that it is being used. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added comment |
||
return Number::New(info.Env(), threadId); | ||
} | ||
|
||
static Value StopThreads(const CallbackInfo& info) { | ||
TestData* testData = static_cast<TestData*>(info.Data()); | ||
ThreadSafeFunction tsfn = testData->tsfn; | ||
tsfn.Release(); | ||
return info.Env().Undefined(); | ||
} | ||
|
||
static Value TestAcquire(const CallbackInfo& info) { | ||
Function cb = info[0].As<Function>(); | ||
Napi::Env env = info.Env(); | ||
|
||
// We pass the test data to the Finalizer for cleanup. The finalizer is | ||
// responsible for deleting this data as well. | ||
TestData *testData = new TestData({ | ||
Promise::Deferred::New(env) | ||
}); | ||
|
||
testData->tsfn = ThreadSafeFunction::New( | ||
env, cb, "Test", 0, 1, | ||
std::function<decltype(FinalizerCallback)>(FinalizerCallback), testData); | ||
|
||
Object result = Object::New(env); | ||
result["createThread"] = Function::New( env, CreateThread, "createThread", testData); | ||
result["stopThreads"] = Function::New( env, StopThreads, "stopThreads", testData); | ||
result["promise"] = testData->deferred.Promise(); | ||
|
||
return result; | ||
} | ||
} | ||
|
||
Object InitThreadSafeFunctionSum(Env env) { | ||
Object exports = Object::New(env); | ||
exports["testDelayedTSFN"] = Function::New(env, TestDelayedTSFN); | ||
exports["testWithTSFN"] = Function::New(env, TestWithTSFN); | ||
exports["testAcquire"] = Function::New(env, TestAcquire); | ||
return exports; | ||
} | ||
|
||
#endif |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
'use strict'; | ||
const assert = require('assert'); | ||
const buildType = process.config.target_defaults.default_configuration; | ||
|
||
/** | ||
* | ||
* ThreadSafeFunction Tests: Thread Id Sums | ||
* | ||
* Every native C++ function that utilizes the TSFN will call the registered | ||
* callback with the thread id. Passing Array.prototype.push with a bound array | ||
* will push the thread id to the array. Therefore, starting `N` threads, we | ||
* will expect the sum of all elements in the array to be `(N-1) * (N) / 2` (as | ||
* thread IDs are 0-based) | ||
* | ||
* We check different methods of passing a ThreadSafeFunction around multiple | ||
* threads: | ||
* - `testWithTSFN`: The main thread creates the TSFN. Then, it creates | ||
* threads, passing the TSFN at thread construction. The number of threads is | ||
* static (known at TSFN creation). | ||
* - `testDelayedTSFN`: The main thread creates threads, passing a promise to a | ||
* TSFN at construction. Then, it creates the TSFN, and resolves each | ||
* threads' promise. The number of threads is static. | ||
* - `testAcquire`: The native binding returns a function to start a new. A | ||
* call to this function will return `false` once `N` calls have been made. | ||
* Each thread will acquire its own use of the TSFN, call it, and then | ||
* release. | ||
*/ | ||
|
||
const THREAD_COUNT = 5; | ||
const EXPECTED_SUM = (THREAD_COUNT - 1) * (THREAD_COUNT) / 2; | ||
|
||
module.exports = Promise.all([ | ||
test(require(`../build/${buildType}/binding.node`)), | ||
test(require(`../build/${buildType}/binding_noexcept.node`)) | ||
]); | ||
|
||
/** @param {number[]} N */ | ||
const sum = (N) => N.reduce((sum, n) => sum + n, 0); | ||
|
||
function test(binding) { | ||
async function check(bindingFunction) { | ||
const calls = []; | ||
const result = await bindingFunction(THREAD_COUNT, Array.prototype.push.bind(calls)); | ||
assert.ok(result); | ||
assert.equal(sum(calls), EXPECTED_SUM); | ||
} | ||
|
||
async function checkAcquire() { | ||
const calls = []; | ||
const { promise, createThread, stopThreads } = binding.threadsafe_function_sum.testAcquire(Array.prototype.push.bind(calls)); | ||
for (let i = 0; i < THREAD_COUNT; i++) { | ||
createThread(); | ||
} | ||
stopThreads(); | ||
const result = await promise; | ||
assert.ok(result); | ||
assert.equal(sum(calls), EXPECTED_SUM); | ||
} | ||
|
||
return Promise.all([ | ||
check(binding.threadsafe_function_sum.testDelayedTSFN), | ||
check(binding.threadsafe_function_sum.testWithTSFN), | ||
checkAcquire() | ||
]); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Abort if failed here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the change doesn't change the behavior here, we could address this issue in another PR. Opened issue here #581.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@legendecas I agree, can you open an issue to make sure we fix it later?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mhdawson the issue is above, and PR for fix (which is also conflicting with this PR) is #583
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@KevinEady - oops should have read more carefullly. Thanks.