Skip to content

Commit 88aa40f

Browse files
committed
src: implement Napi::SharedValueReference<T>
Ref: nodejs#301
1 parent d8e9c22 commit 88aa40f

File tree

7 files changed

+369
-0
lines changed

7 files changed

+369
-0
lines changed

napi-inl.h

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2526,6 +2526,157 @@ inline Object FunctionReference::New(const std::vector<napi_value>& args) const
25262526
return scope.Escape(Value().New(args)).As<Object>();
25272527
}
25282528

2529+
////////////////////////////////////////////////////////////////////////////////
2530+
// SharedValueReference class
2531+
////////////////////////////////////////////////////////////////////////////////
2532+
2533+
template <typename T>
2534+
SharedValueReference<T> SharedValueReference<T>::New(T value) {
2535+
return SharedValueReference<T>(value.Env(), value);
2536+
}
2537+
2538+
template <typename T>
2539+
SharedValueReference<T>::SharedValueReference() {}
2540+
2541+
template <typename T>
2542+
SharedValueReference<T>::SharedValueReference(Env env, T value) : _env(env) {
2543+
napi_status status;
2544+
{
2545+
std::lock_guard<std::mutex> lock(shared_value_reference_mutex);
2546+
status = napi_create_reference(env, value, 1, &_ref);
2547+
}
2548+
NAPI_THROW_IF_FAILED_VOID(env, status);
2549+
}
2550+
2551+
template <typename T>
2552+
SharedValueReference<T>::~SharedValueReference() {
2553+
Reset();
2554+
}
2555+
2556+
template <typename T>
2557+
SharedValueReference<T>::SharedValueReference(const SharedValueReference<T>& rhs) {
2558+
(*this) = rhs;
2559+
}
2560+
2561+
template <typename T>
2562+
SharedValueReference<T>& SharedValueReference<T>::operator=(const SharedValueReference<T>& rhs) {
2563+
if (this == &rhs) {
2564+
return *this;
2565+
}
2566+
2567+
Reset();
2568+
2569+
if (rhs.IsEmpty()) {
2570+
return *this;
2571+
}
2572+
2573+
napi_status status;
2574+
{
2575+
std::lock_guard<std::mutex> lock(shared_value_reference_mutex);
2576+
status = napi_reference_ref(rhs._env, rhs._ref, nullptr);
2577+
}
2578+
2579+
if (status != napi_ok) {
2580+
// we cannot throw from any thread
2581+
// at least IsEmpty() == true now
2582+
return *this;
2583+
}
2584+
2585+
_env = rhs._env;
2586+
_ref = rhs._ref;
2587+
return *this;
2588+
}
2589+
2590+
template <typename T>
2591+
SharedValueReference<T>::SharedValueReference(SharedValueReference<T>&& rhs) {
2592+
(*this) = std::move(rhs);
2593+
}
2594+
2595+
template <typename T>
2596+
SharedValueReference<T>& SharedValueReference<T>::operator=(SharedValueReference<T>&& rhs) {
2597+
Reset();
2598+
2599+
std::swap(_env, rhs._env);
2600+
std::swap(_ref, rhs._ref);
2601+
2602+
return *this;
2603+
}
2604+
2605+
template <typename T>
2606+
template <typename T2>
2607+
bool SharedValueReference<T>::operator==(const SharedValueReference<T2>& rhs) const {
2608+
if (this == &rhs) {
2609+
return true;
2610+
}
2611+
2612+
return _env == rhs._env && _ref == rhs._ref;
2613+
}
2614+
2615+
template <typename T>
2616+
template <typename T2>
2617+
bool SharedValueReference<T>::operator!=(const SharedValueReference<T2>& rhs) const {
2618+
return !((*this) == rhs);
2619+
}
2620+
2621+
template <typename T>
2622+
bool SharedValueReference<T>::IsEmpty() const {
2623+
return _ref == nullptr;
2624+
}
2625+
2626+
template <typename T>
2627+
uint64_t SharedValueReference<T>::RefId() const {
2628+
return reinterpret_cast<uint64_t>(_ref);
2629+
}
2630+
2631+
template <typename T>
2632+
T SharedValueReference<T>::Value() const {
2633+
napi_value result;
2634+
napi_status status;
2635+
{
2636+
std::lock_guard<std::mutex> lock(shared_value_reference_mutex);
2637+
status = napi_get_reference_value(_env, _ref, &result);
2638+
}
2639+
2640+
if (status != napi_ok) {
2641+
return T();
2642+
}
2643+
2644+
return T(_env, result);
2645+
}
2646+
2647+
template <typename T>
2648+
void SharedValueReference<T>::Reset() {
2649+
ResetMtUnsafeRefcount();
2650+
}
2651+
2652+
template <typename T>
2653+
int32_t SharedValueReference<T>::ResetMtUnsafeRefcount() {
2654+
if (!_ref) {
2655+
return kInvalidRef;
2656+
}
2657+
2658+
napi_status status;
2659+
uint32_t refcount = 0;
2660+
{
2661+
std::lock_guard<std::mutex> lock(shared_value_reference_mutex);
2662+
status = napi_reference_unref(_env, _ref, &refcount);
2663+
}
2664+
2665+
_env = nullptr;
2666+
_ref = nullptr;
2667+
2668+
if (status != napi_ok) {
2669+
return kUnrefFailed;
2670+
}
2671+
2672+
return refcount;
2673+
}
2674+
2675+
template <typename T>
2676+
SharedValueReference<T> MakeShared(T value) {
2677+
return SharedValueReference<T>::New(value);
2678+
}
2679+
25292680
////////////////////////////////////////////////////////////////////////////////
25302681
// CallbackInfo class
25312682
////////////////////////////////////////////////////////////////////////////////

napi.h

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <initializer_list>
77
#include <string>
88
#include <vector>
9+
#include <mutex>
910

1011
// VS2015 RTM has bugs with constexpr, so require min of VS2015 Update 3 (known good version)
1112
#if !defined(_MSC_VER) || _MSC_FULL_VER >= 190024210
@@ -1131,6 +1132,74 @@ namespace Napi {
11311132
ObjectReference Persistent(Object value);
11321133
FunctionReference Persistent(Function value);
11331134

1135+
/// Possible return fail-values of `SharedValueReference<T>::Reset()`
1136+
enum SharedValueReferenceReset {
1137+
kInvalidRef = -1, ///< ref is nullptr
1138+
kUnrefFailed = -2 ///< napi unref call failed
1139+
};
1140+
1141+
// Mutex for synchronizing `napi_*_reference_*` calls from SharedValueReference<T>
1142+
static std::mutex shared_value_reference_mutex;
1143+
1144+
/// Holds a reference to a shared value.
1145+
/// SharedValueReference<T> access at all is not threadsafe! However accessing same underlying
1146+
/// reference from different SharedValueReference<T> objects from multiple threads is.
1147+
/// Can only be constructed in node thread, copied or moved from any thread.
1148+
/// Works well with capturing in lambdas.
1149+
template <typename T>
1150+
class SharedValueReference {
1151+
public:
1152+
/// Create SharedValueReference<T> from a value.
1153+
static SharedValueReference<T> New(T value);
1154+
1155+
public:
1156+
SharedValueReference();
1157+
~SharedValueReference();
1158+
1159+
SharedValueReference(const SharedValueReference&);
1160+
SharedValueReference& operator=(const SharedValueReference&);
1161+
1162+
SharedValueReference(SharedValueReference&&);
1163+
SharedValueReference& operator=(SharedValueReference&&);
1164+
1165+
/// Equal, if underlying reference pointers are equal.
1166+
template <typename T2>
1167+
bool operator==(const SharedValueReference<T2>& rhs) const;
1168+
1169+
/// Not equal, if underlying reference pointers are different.
1170+
template <typename T2>
1171+
bool operator!=(const SharedValueReference<T2>& rhs) const;
1172+
1173+
private:
1174+
SharedValueReference(Env env, T value);
1175+
1176+
public:
1177+
/// Returns whether object holding a reference.
1178+
bool IsEmpty() const;
1179+
1180+
/// Returns reference id. Equal to the underlying `napi_ref` value, but for consistency reasons
1181+
/// not returned as `napi_ref` to not be used with unsynchronized `napi_*_reference_*` calls.
1182+
uint64_t RefId() const;
1183+
1184+
/// Returns referenced value.
1185+
/// Only invoke from node thread.
1186+
T Value() const;
1187+
1188+
/// Resets object and unref reference.
1189+
void Reset();
1190+
1191+
/// Resets object and unref reference.
1192+
/// Invoked outside the node thread may return inconsistent ref counts.
1193+
int32_t ResetMtUnsafeRefcount();
1194+
1195+
private:
1196+
napi_env _env = nullptr;
1197+
napi_ref _ref = nullptr;
1198+
};
1199+
1200+
/// Creates SharedValueReference<T> from a value.
1201+
template <typename T> SharedValueReference<T> MakeShared(T value);
1202+
11341203
/// A persistent reference to a JavaScript error object. Use of this class depends somewhat
11351204
/// on whether C++ exceptions are enabled at compile time.
11361205
///

test/binding.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Object InitObject(Env env);
3232
Object InitObjectDeprecated(Env env);
3333
#endif // !NODE_ADDON_API_DISABLE_DEPRECATED
3434
Object InitPromise(Env env);
35+
Object InitSharedValueReference(Env env);
3536
Object InitTypedArray(Env env);
3637
Object InitObjectWrap(Env env);
3738
Object InitObjectReference(Env env);
@@ -69,6 +70,7 @@ Object Init(Env env, Object exports) {
6970
exports.Set("object_deprecated", InitObjectDeprecated(env));
7071
#endif // !NODE_ADDON_API_DISABLE_DEPRECATED
7172
exports.Set("promise", InitPromise(env));
73+
exports.Set("sharedvaluereference", InitSharedValueReference(env));
7274
exports.Set("typedarray", InitTypedArray(env));
7375
exports.Set("objectwrap", InitObjectWrap(env));
7476
exports.Set("objectreference", InitObjectReference(env));

test/binding.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
'object/object.cc',
3232
'object/set_property.cc',
3333
'promise.cc',
34+
'sharedvaluereference.cc',
3435
'typedarray.cc',
3536
'objectwrap.cc',
3637
'objectreference.cc',

test/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ let testModules = [
3434
'object/object_deprecated',
3535
'object/set_property',
3636
'promise',
37+
'sharedvaluereference',
3738
'typedarray',
3839
'typedarray-bigint',
3940
'objectwrap',

test/sharedvaluereference.cc

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#include "napi.h"
2+
#include <thread>
3+
#include <chrono>
4+
#include <vector>
5+
#include <atomic>
6+
7+
using namespace Napi;
8+
9+
Value TestEmptyIsEmpty(const CallbackInfo& info) {
10+
SharedValueReference<Object> empty_shared_ref;
11+
return Boolean::New(info.Env(), empty_shared_ref.IsEmpty());
12+
}
13+
14+
Value TestEmptyRefIsNullptr(const CallbackInfo& info) {
15+
SharedValueReference<Object> empty_shared_ref;
16+
return Boolean::New(info.Env(), empty_shared_ref.RefId() == 0);
17+
}
18+
19+
Value TestEmptyValueIsEmpty(const CallbackInfo& info) {
20+
SharedValueReference<Object> empty_shared_ref;
21+
return Boolean::New(info.Env(), empty_shared_ref.Value().IsEmpty());
22+
}
23+
24+
Value TestEmptyIsMovable(const CallbackInfo& info) {
25+
SharedValueReference<Object> empty_shared_ref;
26+
auto moved_shared_ref = std::move(empty_shared_ref);
27+
28+
return Boolean::New(info.Env(), moved_shared_ref.IsEmpty() && empty_shared_ref.IsEmpty());
29+
}
30+
31+
Value TestInitializedIsNotEmpty(const CallbackInfo& info) {
32+
Object obj = Object::New(info.Env());
33+
auto&& shared_ref = MakeShared(obj);
34+
35+
return Boolean::New(info.Env(), !shared_ref.IsEmpty());
36+
}
37+
38+
Value TestInitializedRefIsNotNullptr(const CallbackInfo& info) {
39+
Object obj = Object::New(info.Env());
40+
auto&& shared_ref = MakeShared(obj);
41+
42+
return Boolean::New(info.Env(), shared_ref.RefId() != 0);
43+
}
44+
45+
Value TestInitializedResetRefcountZero(const CallbackInfo& info) {
46+
Object obj = Object::New(info.Env());
47+
auto&& shared_ref = MakeShared(obj);
48+
49+
return Boolean::New(info.Env(), shared_ref.ResetMtUnsafeRefcount() == 0);
50+
}
51+
52+
Value TestInitializedIsMovable(const CallbackInfo& info) {
53+
Object obj = Object::New(info.Env());
54+
auto&& shared_ref = MakeShared(obj);
55+
uint64_t refid_before_move = shared_ref.RefId();
56+
57+
auto moved_shared_ref = std::move(shared_ref);
58+
return Boolean::New(info.Env(), shared_ref.IsEmpty() && !moved_shared_ref.IsEmpty()
59+
&& moved_shared_ref.RefId() == refid_before_move);
60+
}
61+
62+
Value TestCopyRefAreEqual(const CallbackInfo& info) {
63+
Object obj = Object::New(info.Env());
64+
auto&& shared_ref = MakeShared(obj);
65+
auto copy_shared_ref = shared_ref;
66+
67+
return Boolean::New(info.Env(), copy_shared_ref.RefId() == shared_ref.RefId());
68+
}
69+
70+
Value TestCopyResetRefcountOne(const CallbackInfo& info) {
71+
Object obj = Object::New(info.Env());
72+
auto&& shared_ref = MakeShared(obj);
73+
auto copy_shared_ref = shared_ref;
74+
75+
return Boolean::New(info.Env(), shared_ref.ResetMtUnsafeRefcount() == 1);
76+
}
77+
78+
Value TestRefcountResetInOtherThreads(const CallbackInfo& info) {
79+
Object obj = Object::New(info.Env());
80+
auto&& shared_ref = MakeShared(obj);
81+
82+
const int thread_count = info[0].As<Number>().Int32Value();
83+
std::vector<std::thread> threads(thread_count);
84+
85+
std::atomic_int thread_counter(0);
86+
for (auto&& thread : threads) {
87+
thread = std::thread([shared_ref, &thread_counter]() mutable {
88+
std::this_thread::sleep_for(std::chrono::microseconds(1));
89+
shared_ref.Reset();
90+
91+
thread_counter++;
92+
});
93+
}
94+
95+
for (auto&& thread : threads) {
96+
thread.join();
97+
}
98+
99+
return Boolean::New(info.Env(), shared_ref.ResetMtUnsafeRefcount() == 0 && (int)thread_counter == thread_count);
100+
}
101+
102+
103+
Object InitSharedValueReference(Env env) {
104+
Object exports = Object::New(env);
105+
106+
exports["testEmptyIsEmpty"] = Function::New(env, TestEmptyIsEmpty);
107+
exports["testEmptyRefIsNullptr"] = Function::New(env, TestEmptyRefIsNullptr);
108+
exports["testEmptyValueIsEmpty"] = Function::New(env, TestEmptyValueIsEmpty);
109+
exports["testEmptyIsMovable"] = Function::New(env, TestEmptyIsMovable);
110+
exports["testInitializedIsNotEmpty"] = Function::New(env, TestInitializedIsNotEmpty);
111+
exports["testInitializedRefIsNotNullptr"] = Function::New(env, TestInitializedRefIsNotNullptr);
112+
exports["testInitializedResetRefcountZero"] = Function::New(env, TestInitializedResetRefcountZero);
113+
exports["testInitializedIsMovable"] = Function::New(env, TestInitializedIsMovable);
114+
exports["testCopyRefAreEqual"] = Function::New(env, TestCopyRefAreEqual);
115+
exports["testCopyResetRefcountOne"] = Function::New(env, TestCopyResetRefcountOne);
116+
exports["testRefcountResetInOtherThreads"] = Function::New(env, TestRefcountResetInOtherThreads);
117+
118+
return exports;
119+
}

0 commit comments

Comments
 (0)