profile
viewpoint
Jinho Bang romandev Samsung Electronics Suwon, Korea https://romandev.github.io Open Web Platform Developer

startedNavidZ/input-for-workers

started time in 3 days

pull request commentw3c/test-results

Update Samsung Internet 9.4 implementation report

@ianbjacobs PTAL (Sorry for delayed update.)

romandev

comment created time in 7 days

push eventromandev/w3c-test-results

Jinho Bang

commit sha bbdc6fd15d6ad9626339ae61be1ef1c45a686c32

Update Samsung Internet 9.4 implementation report (#197)

view details

aestes

commit sha cfb079d71fb3a73ca6c43466d996336f0ed604b5

Added Payment Request implementation report for Safari 13 on macOS 10.15 beta. (#198) * payment-request/SF13.json: Added.

view details

Ian Jacobs

commit sha 9417cc83116697a9c3a50e50d6abab63cf6a49ea

Addition of SF13 from Andy

view details

Ian Jacobs

commit sha 04a8dcaf1f4c0e6920762a7d500585d43f7bf731

in favor of SF13.json

view details

Ian Jacobs

commit sha d396e6ec3f50542d2e3d220cf4b8e12ee06edd7d

remove SF12

view details

Danyao Wang

commit sha 7be9138cb10d5d77da575925dddc476af6be9246

Replace CH76 with CH77 results (#199)

view details

Ian Jacobs

commit sha 5d8d9f0afa9253dfcd5c80427f94be74e89224b7

updated for CH77

view details

Jinho Bang

commit sha 5b91db8791a3ace39ea828eb4dcc498d2f656e3b

Update Samsung Internet 9.4 implementation report

view details

push time in 7 days

startedromandev/wpt

started time in 8 days

fork romandev/wpt

Test suites for Web platform specs — including WHATWG, W3C, and others

http://irc.w3.org/?channels=testing

fork in 8 days

pull request commentnodejs/node-addon-api

Implement ThreadSafeFunction class

@gabrielschulhof, In fact, I already did that here: https://github.com/nodejs/node-addon-api/pull/442/files#diff-f546e4cc35b454813e5264fe9c9e6fc8R3898

However, I think we still need a way to reset the ThreadSafeFunction to empty in ThreadSafeFinalizeCallback if the ThreadSafeFunction is aborted or released.

For example, in the current test, declare ThreadSafeFunction as a static global variable, and assign a new ThreadSafeFunction whenever StartThreadInternal is called. If it's non-empty at this time, it would throw an exception. However, if it's empty (by ThreadSafeFinalizeCallback), we can re-assign a new object again.

If there was no such logic, we would have to have a static ThreadSafeFunction array to create a new ThreadSafeFunction whenever StartThreadInternal is called. (because there would be no way to re-assign a new object) Or, we might need to make a wrapper object for ThreadSafeFunction on heap to maintain creating/destroying the object.

romandev

comment created time in 8 days

pull request commentnodejs/node-addon-api

Implement ThreadSafeFunction class

@fholzer, Thank you for finding the bug :) I addressed the bug in latest patch. Could you please check it again?

@gabrielschulhof, As I mentioned in my previous comment[1], this code is necessary to reset the internal tsfn_ value of ThreadSafeFunction automatically. [1] https://github.com/nodejs/node-addon-api/pull/442#discussion_r285868822

Otherwise, ThreadSafeFunction wrapper should provide Reset() to reset the internal value and then users should explicitly call the method in finalize callback.

The problem is caused that ThreadSafeFunction is already destroyed but finalize callback tries to write nullptr value to the internal value of destroyed TSFN. I used raw pointer in the FinalizeCallback, so, there was no way to detect whether TSFN is already destroyed. So, I used std::weak_ptr instead of raw pointer in latest patch set. It looks to work well.

romandev

comment created time in 10 days

push eventromandev/node-addon-api

Jinho Bang

commit sha 86223cf417822117dec368db5ac4244a7bd49afd

Implement ThreadSafeFunction class This PR is implementing ThreadSafeFunction class wraps napi_threadsafe_function features. FYI, the test files that included in this PR have come from Node.js repo[1]. They've been rewritten based on C++ and node-addon-api. Fixes #312. [1] https://github.com/nodejs/node/tree/master/test/node-api/test_threadsafe_function

view details

push time in 10 days

push eventromandev/node-addon-api

Hitesh Kanwathirtha

commit sha a3b4d99c456ddedb4defec7652033dae1184926e

doc: Add contribution philosophy doc

view details

Alba Mendez

commit sha 3ad5dfc7d9c4ae4b6a1f2cd9ba0eb15dc885b869

Fix link PR-URL: https://github.com/nodejs/node-addon-api/pull/481 Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com> Reviewed-By: NickNaso <nicoladelgobbo@gmail.com>

view details

NickNaso

commit sha e1cf9a35a1b6edc404c1b465ec94f8a821641059

Use `Value::IsEmpty` to check for empty value PR-URL: https://github.com/nodejs/node-addon-api/pull/478 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Gabriel Schulhof <gabriel.schulhof@intel.com> Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>

view details

Nicola Del Gobbo

commit sha aaea55eda990c42d181a67084f7a17a3f9e56497

Little fix on code example PR-URL: https://github.com/nodejs/node-addon-api/pull/470 Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com> Reviewed-By: Hitesh Kanwathirtha <digitalinfinity@gmail.com>

view details

Tux3

commit sha f633fbd95d233adc8f030954651a76ec4c8d07f9

string.md: Document existing New(env, value, length) APIs PR-URL: https://github.com/nodejs/node-addon-api/pull/486 Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com> Reviewed-By: NickNaso <nicoladelgobbo@gmail.com>

view details

Gabriel Schulhof

commit sha 3b6b9eb88aaef2a971908aa1daf56b00276f94e2

AsyncWorker: introduce Destroy() method `AsyncWorker` contained the assumption that instances of its subclasses were allocated using `new`, because it unconditionally destroyed instances using `delete`. This change replaces the call to `delete` with a call to a protected instance method `Destroy()`, which can be overridden by subclasses. This ensures that users can employ their own allocators when creating `AsyncWorker` subclass instances because they can override the `Destroy()` method to use their deallocator of choice. Re: https://github.com/nodejs/node-addon-api/issues/231#issuecomment-480928142 PR-URL: https://github.com/nodejs/node-addon-api/pull/488 Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>

view details

Michael Dawson

commit sha ab7d8fcc48f5727961a0f09442d6ae4c5f9b5a17

src: fix objectwrap test case Refs: https://github.com/nodejs/node-addon-api/issues/485 The test case was relyingon the ordering of "for in" which is not guarranteed as per https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in Update the testcase to check in an way that does not depend on ordering. PR-URL: https://github.com/nodejs/node-addon-api/pull/495 Refs: https://github.com/nodejs/node-addon-api/issues/485 Reviewed-By: Gabriel Schulhof <gabriel.schulhof@intel.com> Reviewed-By: NickNaso <nicoladelgobbo@gmail.com>

view details

Jinho Bang

commit sha 30b1087ef5602fd275262f4cf48791d369b990ed

Implement ThreadSafeFunction class This PR is implementing ThreadSafeFunction class wraps napi_threadsafe_function features. FYI, the test files that included in this PR have come from Node.js repo[1]. They've been rewritten based on C++ and node-addon-api. Fixes #312. [1] https://github.com/nodejs/node/tree/master/test/node-api/test_threadsafe_function

view details

push time in 13 days

Pull request review commentnodejs/node-addon-api

Implement ThreadSafeFunction class

 namespace Napi {     bool _suppress_destruct;   }; +  class ThreadSafeFunction {+  public:+    // Default set+    template <typename ResourceString>+    static ThreadSafeFunction New(napi_env env,+                                  const Function& callback,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount);++    // Default set + Context+    template <typename ResourceString, typename ContextType>+    static ThreadSafeFunction New(napi_env env,+                                  const Function& callback,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  ContextType* context);++    // Default set + Finalizer+    template <typename ResourceString, typename Finalizer>+    static ThreadSafeFunction New(napi_env env,+                                  const Function& callback,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  Finalizer finalizeCallback);++    // Default set + Finalizer + Data+    template <typename ResourceString, typename Finalizer,+              typename FinalizerDataType>+    static ThreadSafeFunction New(napi_env env,+                                  const Function& callback,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  Finalizer finalizeCallback,+                                  FinalizerDataType* data);++    // Default set + Context + Finalizer+    template <typename ResourceString, typename ContextType, typename Finalizer>+    static ThreadSafeFunction New(napi_env env,+                                  const Function& callback,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  ContextType* context,+                                  Finalizer finalizeCallback);++    // Default set + Context + Finalizer + Data+    template <typename ResourceString, typename ContextType,+              typename Finalizer, typename FinalizerDataType>+    static ThreadSafeFunction New(napi_env env,+                                  const Function& callback,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  ContextType* context,+                                  Finalizer finalizeCallback,+                                  FinalizerDataType* data);++    // Default set + Resource+    template <typename ResourceString>+    static ThreadSafeFunction New(napi_env env,+                                  const Function& callback,+                                  const Object& resource,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount);++    // Default set + Resource + Context+    template <typename ResourceString, typename ContextType>+    static ThreadSafeFunction New(napi_env env,+                                  const Function& callback,+                                  const Object& resource,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  ContextType* context);++    // Default set + Resource + Finalizer+    template <typename ResourceString, typename Finalizer>+    static ThreadSafeFunction New(napi_env env,+                                  const Function& callback,+                                  const Object& resource,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  Finalizer finalizeCallback);++    // Default set + Resource + Finalizer + Data+    template <typename ResourceString, typename Finalizer,+              typename FinalizerDataType>+    static ThreadSafeFunction New(napi_env env,+                                  const Function& callback,+                                  const Object& resource,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  Finalizer finalizeCallback,+                                  FinalizerDataType* data);++    // Default set + Resource + Context + Finalizer+    template <typename ResourceString, typename ContextType, typename Finalizer>+    static ThreadSafeFunction New(napi_env env,+                                  const Function& callback,+                                  const Object& resource,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  ContextType* context,+                                  Finalizer finalizeCallback);++    // Default set + Resource + Context + Finalizer + Data+    template <typename ResourceString, typename ContextType,+              typename Finalizer, typename FinalizerDataType>+    static ThreadSafeFunction New(napi_env env,+                                  const Function& callback,+                                  const Object& resource,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  ContextType* context,+                                  Finalizer finalizeCallback,+                                  FinalizerDataType* data);

Could you let me know more details about the consistency you mentioned? I'm not sure but the most of New() methods are already using napi_env instead of Napi::Env.

FYI, please see the following grep result.

$ grep -Enr "New\((Napi::Env|Env|napi_env)" napi.h

693:    static External New(napi_env env, T* data);
697:    static External New(napi_env env,
702:    static External New(napi_env env,
715:    static Array New(napi_env env);
716:    static Array New(napi_env env, size_t length);
908:    static DataView New(napi_env env,
910:    static DataView New(napi_env env,
913:    static DataView New(napi_env env,
961:    static Function New(napi_env env,
968:    static Function New(napi_env env,
1005:      static Deferred New(napi_env env);
1026:    static Buffer<T> New(napi_env env, size_t length);
1027:    static Buffer<T> New(napi_env env, T* data, size_t length);
1031:    static Buffer<T> New(napi_env env, T* data,
1036:    static Buffer<T> New(napi_env env, T* data,
1285:    static Error New(napi_env env);
1286:    static Error New(napi_env env, const char* message);
1287:    static Error New(napi_env env, const std::string& message);
1312:    static TError New(napi_env env,
1324:    static TypeError New(napi_env env, const char* message);
1325:    static TypeError New(napi_env env, const std::string& message);
1333:    static RangeError New(napi_env env, const char* message);
1334:    static RangeError New(napi_env env, const std::string& message);
1829:    static ThreadSafeFunction New(napi_env env,
1837:    static ThreadSafeFunction New(napi_env env,
1846:    static ThreadSafeFunction New(napi_env env,
1856:    static ThreadSafeFunction New(napi_env env,
1866:    static ThreadSafeFunction New(napi_env env,
1877:    static ThreadSafeFunction New(napi_env env,
1888:    static ThreadSafeFunction New(napi_env env,
1897:    static ThreadSafeFunction New(napi_env env,
1907:    static ThreadSafeF1897:    static ThreadSafeFunction New(napi_env env,
1907:    static ThreadSafeFunction New(napi_env env,
1918:    static ThreadSafeFunction New(napi_env env,
1929:    static ThreadSafeFunction New(napi_env env,
1941:    static ThreadSafeFunction New(napi_env env,
2003:    static ThreadSafeFunction New(napi_env env,
romandev

comment created time in 13 days

Pull request review commentnodejs/node-addon-api

Implement ThreadSafeFunction class

 namespace Napi {     bool _suppress_destruct;   }; +  class ThreadSafeFunction {+  public:+    // Default set

What does "Default set" mean?

Unmeaningful. There are many overloaded methods. So, I was so confusing. To clarify it during development, it's just used for my convenience.

Also, we should have comments that split the API into functions which can only be called from the main thread, and those that are thread-safe.

Done

romandev

comment created time in 13 days

Pull request review commentnodejs/node-addon-api

Implement ThreadSafeFunction class

 inline void AsyncWorker::OnWorkComplete(   } } +////////////////////////////////////////////////////////////////////////////////+// ThreadSafeFunction class+////////////////////////////////////////////////////////////////////////////////++// static+template <typename ResourceString>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount);+}++// static+template <typename ResourceString, typename ContextType>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  ContextType* context) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount, context);+}++// static+template <typename ResourceString, typename Finalizer>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  Finalizer finalizeCallback) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount, finalizeCallback);+}++// static+template <typename ResourceString, typename Finalizer,+          typename FinalizerDataType>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  Finalizer finalizeCallback,+                                  FinalizerDataType* data) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount, finalizeCallback, data);+}++// static+template <typename ResourceString, typename ContextType, typename Finalizer>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  ContextType* context,+                                  Finalizer finalizeCallback) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount, context, finalizeCallback);+}++// static+template <typename ResourceString, typename ContextType,+          typename Finalizer, typename FinalizerDataType>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                                  const Function& callback,+                                                  ResourceString resourceName,+                                                  size_t maxQueueSize,+                                                  size_t initialThreadCount,+                                                  ContextType* context,+                                                  Finalizer finalizeCallback,+                                                  FinalizerDataType* data) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount, context, finalizeCallback, data);+}++// static+template <typename ResourceString>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  const Object& resource,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount) {+  return New(env, callback, resource, resourceName, maxQueueSize,+             initialThreadCount, static_cast<void*>(nullptr) /* context */);+}++// static+template <typename ResourceString, typename ContextType>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  const Object& resource,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  ContextType* context) {+  return New(env, callback, resource, resourceName, maxQueueSize,+             initialThreadCount, context,+             [](Env, ContextType*) {} /* empty finalizer */);+}++// static+template <typename ResourceString, typename Finalizer>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  const Object& resource,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  Finalizer finalizeCallback) {+  return New(env, callback, resource, resourceName, maxQueueSize,+             initialThreadCount, static_cast<void*>(nullptr) /* context */,+             finalizeCallback, static_cast<void*>(nullptr) /* data */,+             details::ThreadSafeFinalize<void, Finalizer>::Wrapper);+}++// static+template <typename ResourceString, typename Finalizer,+          typename FinalizerDataType>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  const Object& resource,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  Finalizer finalizeCallback,+                                  FinalizerDataType* data) {+  return New(env, callback, resource, resourceName, maxQueueSize,+             initialThreadCount, static_cast<void*>(nullptr) /* context */,+             finalizeCallback, data,+             details::ThreadSafeFinalize<+                 void, Finalizer, FinalizerDataType>::WrapperWithData);+}++// static+template <typename ResourceString, typename ContextType, typename Finalizer>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  const Object& resource,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  ContextType* context,+                                  Finalizer finalizeCallback) {+  return New(env, callback, resource, resourceName, maxQueueSize,+             initialThreadCount, context, finalizeCallback,+             static_cast<void*>(nullptr) /* data */,+             details::ThreadSafeFinalize<+                 ContextType, Finalizer>::WrapperWithContext);+}++// static+template <typename ResourceString, typename ContextType,+          typename Finalizer, typename FinalizerDataType>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                                  const Function& callback,+                                                  const Object& resource,+                                                  ResourceString resourceName,+                                                  size_t maxQueueSize,+                                                  size_t initialThreadCount,+                                                  ContextType* context,+                                                  Finalizer finalizeCallback,+                                                  FinalizerDataType* data) {+  return New(env, callback, resource, resourceName, maxQueueSize,+             initialThreadCount, context, finalizeCallback, data,+             details::ThreadSafeFinalize<ContextType, Finalizer,+                 FinalizerDataType>::WrapperWithDataAndContext);+}++inline ThreadSafeFunction::ThreadSafeFunction()+  : _tsfn(std::make_unique<napi_threadsafe_function>(nullptr)) {+}++inline ThreadSafeFunction::ThreadSafeFunction(+    napi_threadsafe_function tsfn)+  : _tsfn(std::make_unique<napi_threadsafe_function>(tsfn)) {+}++inline ThreadSafeFunction::ThreadSafeFunction(ThreadSafeFunction&& other)+  : _tsfn(std::move(other._tsfn)) {+  other._tsfn.reset();+}++inline ThreadSafeFunction& ThreadSafeFunction::operator =(+    ThreadSafeFunction&& other) {+  if (*_tsfn != nullptr) {+    Error::Fatal("ThreadSafeFunction::operator =",+        "You cannot assign a new TSFN because existing one is still alive.");+    return *this;+  }+  _tsfn = std::move(other._tsfn);+  other._tsfn.reset();+  return *this;+}++inline napi_status ThreadSafeFunction::BlockingCall() const {+  return CallInternal(nullptr, napi_tsfn_blocking);+}++template <typename Callback>+inline napi_status ThreadSafeFunction::BlockingCall(+    Callback callback) const {+  return CallInternal(new CallbackWrapper(callback), napi_tsfn_blocking);+}++template <typename DataType, typename Callback>+inline napi_status ThreadSafeFunction::BlockingCall(+    DataType* data, Callback callback) const {+  auto wrapper = [data, callback](Env env, Function jsCallback) {+    callback(env, jsCallback, data);+  };+  return CallInternal(new CallbackWrapper(wrapper), napi_tsfn_blocking);+}++inline napi_status ThreadSafeFunction::NonBlockingCall() const {+  return CallInternal(nullptr, napi_tsfn_nonblocking);+}++template <typename Callback>+inline napi_status ThreadSafeFunction::NonBlockingCall(+    Callback callback) const {+  return CallInternal(new CallbackWrapper(callback), napi_tsfn_nonblocking);+}++template <typename DataType, typename Callback>+inline napi_status ThreadSafeFunction::NonBlockingCall(+    DataType* data, Callback callback) const {+  auto wrapper = [data, callback](Env env, Function jsCallback) {+    callback(env, jsCallback, data);+  };+  return CallInternal(new CallbackWrapper(wrapper), napi_tsfn_nonblocking);+}++inline napi_status ThreadSafeFunction::Acquire() const {+  return napi_acquire_threadsafe_function(*_tsfn);+}++inline napi_status ThreadSafeFunction::Release() {+  return napi_release_threadsafe_function(*_tsfn, napi_tsfn_release);+}++inline napi_status ThreadSafeFunction::Abort() {+  return napi_release_threadsafe_function(*_tsfn, napi_tsfn_abort);+}++inline ThreadSafeFunction::ConvertibleContext+ThreadSafeFunction::GetContext() const {+  void* context;+  napi_get_threadsafe_function_context(*_tsfn, &context);+  return ConvertibleContext({ context });+}++// static+template <typename ResourceString, typename ContextType,+          typename Finalizer, typename FinalizerDataType>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                                  const Function& callback,+                                                  const Object& resource,+                                                  ResourceString resourceName,+                                                  size_t maxQueueSize,+                                                  size_t initialThreadCount,+                                                  ContextType* context,+                                                  Finalizer finalizeCallback,+                                                  FinalizerDataType* data,+                                                  napi_finalize wrapper) {+  static_assert(details::can_make_string<ResourceString>::value+      || std::is_convertible<ResourceString, napi_value>::value,+      "Resource name should be string convertible type");

Done

romandev

comment created time in 13 days

Pull request review commentnodejs/node-addon-api

Implement ThreadSafeFunction class

 inline void AsyncWorker::OnWorkComplete(   } } +////////////////////////////////////////////////////////////////////////////////+// ThreadSafeFunction class+////////////////////////////////////////////////////////////////////////////////++// static+template <typename ResourceString>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount);+}++// static+template <typename ResourceString, typename ContextType>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  ContextType* context) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount, context);+}++// static+template <typename ResourceString, typename Finalizer>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  Finalizer finalizeCallback) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount, finalizeCallback);+}++// static+template <typename ResourceString, typename Finalizer,+          typename FinalizerDataType>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  Finalizer finalizeCallback,+                                  FinalizerDataType* data) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount, finalizeCallback, data);+}++// static+template <typename ResourceString, typename ContextType, typename Finalizer>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  ContextType* context,+                                  Finalizer finalizeCallback) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount, context, finalizeCallback);+}++// static+template <typename ResourceString, typename ContextType,+          typename Finalizer, typename FinalizerDataType>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                                  const Function& callback,+                                                  ResourceString resourceName,+                                                  size_t maxQueueSize,+                                                  size_t initialThreadCount,+                                                  ContextType* context,+                                                  Finalizer finalizeCallback,+                                                  FinalizerDataType* data) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount, context, finalizeCallback, data);+}++// static+template <typename ResourceString>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  const Object& resource,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount) {+  return New(env, callback, resource, resourceName, maxQueueSize,+             initialThreadCount, static_cast<void*>(nullptr) /* context */);+}++// static+template <typename ResourceString, typename ContextType>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  const Object& resource,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  ContextType* context) {+  return New(env, callback, resource, resourceName, maxQueueSize,+             initialThreadCount, context,+             [](Env, ContextType*) {} /* empty finalizer */);+}++// static+template <typename ResourceString, typename Finalizer>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  const Object& resource,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  Finalizer finalizeCallback) {+  return New(env, callback, resource, resourceName, maxQueueSize,+             initialThreadCount, static_cast<void*>(nullptr) /* context */,+             finalizeCallback, static_cast<void*>(nullptr) /* data */,+             details::ThreadSafeFinalize<void, Finalizer>::Wrapper);+}++// static+template <typename ResourceString, typename Finalizer,+          typename FinalizerDataType>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  const Object& resource,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  Finalizer finalizeCallback,+                                  FinalizerDataType* data) {+  return New(env, callback, resource, resourceName, maxQueueSize,+             initialThreadCount, static_cast<void*>(nullptr) /* context */,+             finalizeCallback, data,+             details::ThreadSafeFinalize<+                 void, Finalizer, FinalizerDataType>::WrapperWithData);+}++// static+template <typename ResourceString, typename ContextType, typename Finalizer>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  const Object& resource,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  ContextType* context,+                                  Finalizer finalizeCallback) {+  return New(env, callback, resource, resourceName, maxQueueSize,+             initialThreadCount, context, finalizeCallback,+             static_cast<void*>(nullptr) /* data */,+             details::ThreadSafeFinalize<+                 ContextType, Finalizer>::WrapperWithContext);+}++// static+template <typename ResourceString, typename ContextType,+          typename Finalizer, typename FinalizerDataType>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                                  const Function& callback,+                                                  const Object& resource,+                                                  ResourceString resourceName,+                                                  size_t maxQueueSize,+                                                  size_t initialThreadCount,+                                                  ContextType* context,+                                                  Finalizer finalizeCallback,+                                                  FinalizerDataType* data) {+  return New(env, callback, resource, resourceName, maxQueueSize,+             initialThreadCount, context, finalizeCallback, data,+             details::ThreadSafeFinalize<ContextType, Finalizer,+                 FinalizerDataType>::WrapperWithDataAndContext);+}++inline ThreadSafeFunction::ThreadSafeFunction()+  : _tsfn(std::make_unique<napi_threadsafe_function>(nullptr)) {+}++inline ThreadSafeFunction::ThreadSafeFunction(+    napi_threadsafe_function tsfn)+  : _tsfn(std::make_unique<napi_threadsafe_function>(tsfn)) {+}++inline ThreadSafeFunction::ThreadSafeFunction(ThreadSafeFunction&& other)+  : _tsfn(std::move(other._tsfn)) {+  other._tsfn.reset();+}++inline ThreadSafeFunction& ThreadSafeFunction::operator =(+    ThreadSafeFunction&& other) {+  if (*_tsfn != nullptr) {+    Error::Fatal("ThreadSafeFunction::operator =",+        "You cannot assign a new TSFN because existing one is still alive.");+    return *this;+  }+  _tsfn = std::move(other._tsfn);+  other._tsfn.reset();+  return *this;+}++inline napi_status ThreadSafeFunction::BlockingCall() const {+  return CallInternal(nullptr, napi_tsfn_blocking);+}++template <typename Callback>+inline napi_status ThreadSafeFunction::BlockingCall(+    Callback callback) const {+  return CallInternal(new CallbackWrapper(callback), napi_tsfn_blocking);+}++template <typename DataType, typename Callback>+inline napi_status ThreadSafeFunction::BlockingCall(+    DataType* data, Callback callback) const {+  auto wrapper = [data, callback](Env env, Function jsCallback) {+    callback(env, jsCallback, data);+  };+  return CallInternal(new CallbackWrapper(wrapper), napi_tsfn_blocking);+}++inline napi_status ThreadSafeFunction::NonBlockingCall() const {+  return CallInternal(nullptr, napi_tsfn_nonblocking);+}++template <typename Callback>+inline napi_status ThreadSafeFunction::NonBlockingCall(+    Callback callback) const {+  return CallInternal(new CallbackWrapper(callback), napi_tsfn_nonblocking);+}++template <typename DataType, typename Callback>+inline napi_status ThreadSafeFunction::NonBlockingCall(+    DataType* data, Callback callback) const {+  auto wrapper = [data, callback](Env env, Function jsCallback) {+    callback(env, jsCallback, data);+  };+  return CallInternal(new CallbackWrapper(wrapper), napi_tsfn_nonblocking);+}++inline napi_status ThreadSafeFunction::Acquire() const {+  return napi_acquire_threadsafe_function(*_tsfn);+}++inline napi_status ThreadSafeFunction::Release() {+  return napi_release_threadsafe_function(*_tsfn, napi_tsfn_release);+}++inline napi_status ThreadSafeFunction::Abort() {+  return napi_release_threadsafe_function(*_tsfn, napi_tsfn_abort);+}++inline ThreadSafeFunction::ConvertibleContext+ThreadSafeFunction::GetContext() const {+  void* context;+  napi_get_threadsafe_function_context(*_tsfn, &context);+  return ConvertibleContext({ context });+}++// static+template <typename ResourceString, typename ContextType,+          typename Finalizer, typename FinalizerDataType>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                                  const Function& callback,+                                                  const Object& resource,+                                                  ResourceString resourceName,+                                                  size_t maxQueueSize,+                                                  size_t initialThreadCount,+                                                  ContextType* context,+                                                  Finalizer finalizeCallback,+                                                  FinalizerDataType* data,+                                                  napi_finalize wrapper) {+  static_assert(details::can_make_string<ResourceString>::value+      || std::is_convertible<ResourceString, napi_value>::value,+      "Resource name should be string convertible type");++  ThreadSafeFunction tsfn;+  auto* finalizeData = new details::ThreadSafeFinalize<ContextType, Finalizer,+      FinalizerDataType>({ data, finalizeCallback, tsfn._tsfn.get() });+  napi_status status = napi_create_threadsafe_function(env, callback, resource,+      Value::From(env, resourceName), maxQueueSize, initialThreadCount,+      finalizeData, wrapper, context, CallJS, tsfn._tsfn.get());+  if (status != napi_ok) {+    delete finalizeData;+    NAPI_THROW_IF_FAILED(env, status, ThreadSafeFunction());+  }++  return tsfn;+}++inline napi_status ThreadSafeFunction::CallInternal(+    CallbackWrapper* callbackWrapper,+    napi_threadsafe_function_call_mode mode) const {+  napi_status status = napi_call_threadsafe_function(+      *_tsfn, callbackWrapper, mode);+  if (status != napi_ok && callbackWrapper != nullptr) {+    delete callbackWrapper;+  }++  return status;+}++// static+inline void ThreadSafeFunction::CallJS(napi_env env,+                                       napi_value jsCallback,+                                       void* /* context */,+                                       void* data) {+  if (env == nullptr && jsCallback == nullptr)+    return;++  if (data != nullptr) {+    auto* callbackWrapper = static_cast<CallbackWrapper*>(data);+    (*callbackWrapper)(env, Function(env, jsCallback));+    delete callbackWrapper;+  } else {

Done

romandev

comment created time in 13 days

Pull request review commentnodejs/node-addon-api

Implement ThreadSafeFunction class

 inline void AsyncWorker::OnWorkComplete(   } } +////////////////////////////////////////////////////////////////////////////////+// ThreadSafeFunction class+////////////////////////////////////////////////////////////////////////////////++// static+template <typename ResourceString>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount);+}++// static+template <typename ResourceString, typename ContextType>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  ContextType* context) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount, context);+}++// static+template <typename ResourceString, typename Finalizer>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  Finalizer finalizeCallback) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount, finalizeCallback);+}++// static+template <typename ResourceString, typename Finalizer,+          typename FinalizerDataType>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  Finalizer finalizeCallback,+                                  FinalizerDataType* data) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount, finalizeCallback, data);+}++// static+template <typename ResourceString, typename ContextType, typename Finalizer>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  ContextType* context,+                                  Finalizer finalizeCallback) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount, context, finalizeCallback);+}++// static+template <typename ResourceString, typename ContextType,+          typename Finalizer, typename FinalizerDataType>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                                  const Function& callback,+                                                  ResourceString resourceName,+                                                  size_t maxQueueSize,+                                                  size_t initialThreadCount,+                                                  ContextType* context,+                                                  Finalizer finalizeCallback,+                                                  FinalizerDataType* data) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount, context, finalizeCallback, data);+}++// static+template <typename ResourceString>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  const Object& resource,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount) {+  return New(env, callback, resource, resourceName, maxQueueSize,+             initialThreadCount, static_cast<void*>(nullptr) /* context */);+}++// static+template <typename ResourceString, typename ContextType>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  const Object& resource,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  ContextType* context) {+  return New(env, callback, resource, resourceName, maxQueueSize,+             initialThreadCount, context,+             [](Env, ContextType*) {} /* empty finalizer */);+}++// static+template <typename ResourceString, typename Finalizer>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  const Object& resource,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  Finalizer finalizeCallback) {+  return New(env, callback, resource, resourceName, maxQueueSize,+             initialThreadCount, static_cast<void*>(nullptr) /* context */,+             finalizeCallback, static_cast<void*>(nullptr) /* data */,+             details::ThreadSafeFinalize<void, Finalizer>::Wrapper);+}++// static+template <typename ResourceString, typename Finalizer,+          typename FinalizerDataType>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  const Object& resource,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  Finalizer finalizeCallback,+                                  FinalizerDataType* data) {+  return New(env, callback, resource, resourceName, maxQueueSize,+             initialThreadCount, static_cast<void*>(nullptr) /* context */,+             finalizeCallback, data,+             details::ThreadSafeFinalize<+                 void, Finalizer, FinalizerDataType>::WrapperWithData);+}++// static+template <typename ResourceString, typename ContextType, typename Finalizer>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                  const Function& callback,+                                  const Object& resource,+                                  ResourceString resourceName,+                                  size_t maxQueueSize,+                                  size_t initialThreadCount,+                                  ContextType* context,+                                  Finalizer finalizeCallback) {+  return New(env, callback, resource, resourceName, maxQueueSize,+             initialThreadCount, context, finalizeCallback,+             static_cast<void*>(nullptr) /* data */,+             details::ThreadSafeFinalize<+                 ContextType, Finalizer>::WrapperWithContext);+}++// static+template <typename ResourceString, typename ContextType,+          typename Finalizer, typename FinalizerDataType>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                                  const Function& callback,+                                                  const Object& resource,+                                                  ResourceString resourceName,+                                                  size_t maxQueueSize,+                                                  size_t initialThreadCount,+                                                  ContextType* context,+                                                  Finalizer finalizeCallback,+                                                  FinalizerDataType* data) {+  return New(env, callback, resource, resourceName, maxQueueSize,+             initialThreadCount, context, finalizeCallback, data,+             details::ThreadSafeFinalize<ContextType, Finalizer,+                 FinalizerDataType>::WrapperWithDataAndContext);+}++inline ThreadSafeFunction::ThreadSafeFunction()+  : _tsfn(std::make_unique<napi_threadsafe_function>(nullptr)) {+}++inline ThreadSafeFunction::ThreadSafeFunction(+    napi_threadsafe_function tsfn)+  : _tsfn(std::make_unique<napi_threadsafe_function>(tsfn)) {+}++inline ThreadSafeFunction::ThreadSafeFunction(ThreadSafeFunction&& other)+  : _tsfn(std::move(other._tsfn)) {+  other._tsfn.reset();+}++inline ThreadSafeFunction& ThreadSafeFunction::operator =(+    ThreadSafeFunction&& other) {+  if (*_tsfn != nullptr) {+    Error::Fatal("ThreadSafeFunction::operator =",+        "You cannot assign a new TSFN because existing one is still alive.");+    return *this;+  }+  _tsfn = std::move(other._tsfn);+  other._tsfn.reset();+  return *this;+}++inline napi_status ThreadSafeFunction::BlockingCall() const {+  return CallInternal(nullptr, napi_tsfn_blocking);+}++template <typename Callback>+inline napi_status ThreadSafeFunction::BlockingCall(+    Callback callback) const {+  return CallInternal(new CallbackWrapper(callback), napi_tsfn_blocking);+}++template <typename DataType, typename Callback>+inline napi_status ThreadSafeFunction::BlockingCall(+    DataType* data, Callback callback) const {+  auto wrapper = [data, callback](Env env, Function jsCallback) {+    callback(env, jsCallback, data);+  };+  return CallInternal(new CallbackWrapper(wrapper), napi_tsfn_blocking);+}++inline napi_status ThreadSafeFunction::NonBlockingCall() const {+  return CallInternal(nullptr, napi_tsfn_nonblocking);+}++template <typename Callback>+inline napi_status ThreadSafeFunction::NonBlockingCall(+    Callback callback) const {+  return CallInternal(new CallbackWrapper(callback), napi_tsfn_nonblocking);+}++template <typename DataType, typename Callback>+inline napi_status ThreadSafeFunction::NonBlockingCall(+    DataType* data, Callback callback) const {+  auto wrapper = [data, callback](Env env, Function jsCallback) {+    callback(env, jsCallback, data);+  };+  return CallInternal(new CallbackWrapper(wrapper), napi_tsfn_nonblocking);+}++inline napi_status ThreadSafeFunction::Acquire() const {+  return napi_acquire_threadsafe_function(*_tsfn);+}++inline napi_status ThreadSafeFunction::Release() {+  return napi_release_threadsafe_function(*_tsfn, napi_tsfn_release);+}++inline napi_status ThreadSafeFunction::Abort() {+  return napi_release_threadsafe_function(*_tsfn, napi_tsfn_abort);+}++inline ThreadSafeFunction::ConvertibleContext+ThreadSafeFunction::GetContext() const {+  void* context;+  napi_get_threadsafe_function_context(*_tsfn, &context);+  return ConvertibleContext({ context });+}++// static+template <typename ResourceString, typename ContextType,+          typename Finalizer, typename FinalizerDataType>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                                  const Function& callback,+                                                  const Object& resource,+                                                  ResourceString resourceName,+                                                  size_t maxQueueSize,+                                                  size_t initialThreadCount,+                                                  ContextType* context,+                                                  Finalizer finalizeCallback,+                                                  FinalizerDataType* data,+                                                  napi_finalize wrapper) {+  static_assert(details::can_make_string<ResourceString>::value+      || std::is_convertible<ResourceString, napi_value>::value,+      "Resource name should be string convertible type");++  ThreadSafeFunction tsfn;+  auto* finalizeData = new details::ThreadSafeFinalize<ContextType, Finalizer,+      FinalizerDataType>({ data, finalizeCallback, tsfn._tsfn.get() });+  napi_status status = napi_create_threadsafe_function(env, callback, resource,+      Value::From(env, resourceName), maxQueueSize, initialThreadCount,+      finalizeData, wrapper, context, CallJS, tsfn._tsfn.get());+  if (status != napi_ok) {+    delete finalizeData;+    NAPI_THROW_IF_FAILED(env, status, ThreadSafeFunction());+  }++  return tsfn;+}++inline napi_status ThreadSafeFunction::CallInternal(+    CallbackWrapper* callbackWrapper,+    napi_threadsafe_function_call_mode mode) const {+  napi_status status = napi_call_threadsafe_function(+      *_tsfn, callbackWrapper, mode);+  if (status != napi_ok && callbackWrapper != nullptr) {+    delete callbackWrapper;+  }++  return status;+}++// static+inline void ThreadSafeFunction::CallJS(napi_env env,+                                       napi_value jsCallback,+                                       void* /* context */,+                                       void* data) {+  if (env == nullptr && jsCallback == nullptr)+    return;

Done

romandev

comment created time in 13 days

Pull request review commentnodejs/node-addon-api

Implement ThreadSafeFunction class

 struct FinalizeData {   Hint* hint; }; +template <typename ContextType=void,+          typename Finalizer=std::function<void(Env, void*, ContextType*)>,+          typename FinalizerDataType=void>+struct ThreadSafeFinalize {+  static inline+  void Wrapper(napi_env env, void* rawFinalizeData, void* /* rawContext */) {+    if (rawFinalizeData == nullptr)+      return;++    ThreadSafeFinalize* finalizeData =+        static_cast<ThreadSafeFinalize*>(rawFinalizeData);+    finalizeData->callback(Env(env));+    if (finalizeData->tsfn) {+      *finalizeData->tsfn = nullptr;+    }+    delete finalizeData;+  }++  static inline+  void WrapperWithData(napi_env env,+                       void* rawFinalizeData,+                       void* /* rawContext */) {+    if (rawFinalizeData == nullptr)+      return;++    ThreadSafeFinalize* finalizeData =+        static_cast<ThreadSafeFinalize*>(rawFinalizeData);+    finalizeData->callback(Env(env), finalizeData->data);+    if (finalizeData->tsfn) {+      *finalizeData->tsfn = nullptr;+    }+    delete finalizeData;+  }++  static inline+  void WrapperWithContext(napi_env env,+                          void* rawFinalizeData,+                          void* rawContext) {+    if (rawFinalizeData == nullptr)+      return;++    ThreadSafeFinalize* finalizeData =+        static_cast<ThreadSafeFinalize*>(rawFinalizeData);+    finalizeData->callback(Env(env), static_cast<ContextType*>(rawContext));+    if (finalizeData->tsfn) {+      *finalizeData->tsfn = nullptr;+    }+    delete finalizeData;+  }++  static inline+  void WrapperWithDataAndContext(napi_env env,

Done

romandev

comment created time in 13 days

Pull request review commentnodejs/node-addon-api

Implement ThreadSafeFunction class

 struct FinalizeData {   Hint* hint; }; +template <typename ContextType=void,+          typename Finalizer=std::function<void(Env, void*, ContextType*)>,+          typename FinalizerDataType=void>+struct ThreadSafeFinalize {+  static inline+  void Wrapper(napi_env env, void* rawFinalizeData, void* /* rawContext */) {+    if (rawFinalizeData == nullptr)+      return;++    ThreadSafeFinalize* finalizeData =+        static_cast<ThreadSafeFinalize*>(rawFinalizeData);+    finalizeData->callback(Env(env));+    if (finalizeData->tsfn) {+      *finalizeData->tsfn = nullptr;+    }+    delete finalizeData;+  }++  static inline+  void WrapperWithData(napi_env env,

Done

romandev

comment created time in 13 days

Pull request review commentnodejs/node-addon-api

Implement ThreadSafeFunction class

 struct FinalizeData {   Hint* hint; }; +template <typename ContextType=void,+          typename Finalizer=std::function<void(Env, void*, ContextType*)>,+          typename FinalizerDataType=void>+struct ThreadSafeFinalize {+  static inline+  void Wrapper(napi_env env, void* rawFinalizeData, void* /* rawContext */) {+    if (rawFinalizeData == nullptr)+      return;++    ThreadSafeFinalize* finalizeData =+        static_cast<ThreadSafeFinalize*>(rawFinalizeData);+    finalizeData->callback(Env(env));+    if (finalizeData->tsfn) {+      *finalizeData->tsfn = nullptr;+    }+    delete finalizeData;+  }++  static inline+  void WrapperWithData(napi_env env,+                       void* rawFinalizeData,+                       void* /* rawContext */) {+    if (rawFinalizeData == nullptr)+      return;++    ThreadSafeFinalize* finalizeData =+        static_cast<ThreadSafeFinalize*>(rawFinalizeData);+    finalizeData->callback(Env(env), finalizeData->data);+    if (finalizeData->tsfn) {+      *finalizeData->tsfn = nullptr;+    }+    delete finalizeData;+  }++  static inline+  void WrapperWithContext(napi_env env,

Done

romandev

comment created time in 13 days

pull request commentnodejs/node-addon-api

Implement ThreadSafeFunction class

@KevinEady Thank you for your update.

I'm not doing anything too "crazy" and not really using all your PRs features to the fullest

I think that we can fix the problem in follow-up PR before release if we found some problem :)

I'm not sure how we want to proceed... I don't know if I target this nodejs repo instead but I created a PR against your fork for now. We can figure out the right approach later,

I recommend that you forks node/node-addon-api and then send a new PR apart from my PR. It's not problem because your .md file change is not depending on my PR. (It will make your change easier to review.)

but take a look at ...

Looks overall good to me. BTW, you might have to match the format with existing documents as follows (e.g. AsyncContext):

ThreadSafeFunction
  ...
Methods
  ...
Operator
  ...
Example
  ...
romandev

comment created time in 22 days

pull request commentw3c/test-results

Update Samsung Internet 9.4 implementation report

@ianbjacobs PTAL

romandev

comment created time in a month

PR opened w3c/test-results

Update Samsung Internet 9.4 implementation report
+1438 -2713

0 comment

6 changed files

pr created time in a month

push eventromandev/w3c-test-results

Jinho Bang

commit sha 5b8a143fcbda7f42c99c68665ebcbadce046062a

Update Samsung Internet 9.4 implementation report

view details

push time in a month

push eventromandev/romandev.github.io

Jinho Bang

commit sha 26de22a1a49682c6f4e10194ff07bf7ec3f87ae7

Create CNAME

view details

push time in a month

push eventromandev/romandev.github.io

Jinho Bang

commit sha 19d294e4c180df1a18d356dbbf893d12bffba9a6

Delete CNAME

view details

push time in a month

push eventromandev/romandev.github.io

Jinho Bang

commit sha 4a0db9f38aa7501add848ddf901c65a0cdacf096

Create CNAME

view details

push time in a month

push eventromandev/romandev.github.io

Jinho Bang

commit sha 23aacd4dc4d42f4aaecfeb65b00acb20cb3b00fd

Delete CNAME

view details

push time in a month

push eventromandev/romandev.github.io

Jinho Bang

commit sha b2f6ecb585267b6e9ae84edb373df893f4a2ed96

Create CNAME

view details

push time in a month

push eventromandev/romandev.github.io

Jinho Bang

commit sha bdd21b87d289da1abbf3dcfcc7e99d24b86d8ca0

Delete CNAME

view details

push time in a month

push eventromandev/romandev.github.io

Jinho Bang

commit sha 3adb6e9b4abe6c55e9b3979111a0a3530f0ac6ef

Create CNAME

view details

push time in a month

push eventromandev/romandev.github.io

Jinho Bang

commit sha c37d112dd47f4324f5dc82fcc412a96d3ad03292

Delete CNAME

view details

push time in a month

push eventromandev/romandev.github.io

Jinho Bang

commit sha 168c4ffb68beafd104c7cc54a7b7e0a4cb2b89ab

Create CNAME

view details

push time in a month

push eventromandev/romandev.github.io

Jinho Bang

commit sha 33b9c40f767565f62f0fe9e5d831d1ed7430bef7

Delete CNAME

view details

push time in a month

push eventromandev/romandev.github.io

Jinho Bang

commit sha f67e92966c895d840226f530467d7125e3a51b95

Create CNAME

view details

push time in a month

push eventromandev/romandev.github.io

Jinho Bang

commit sha bb99c62c377a6a32906f08b31078c3468f4c4c87

Delete CNAME

view details

push time in a month

push eventromandev/romandev.github.io

Jinho Bang

commit sha 89ec1630fc39ad0cb2536d127942fe65d3db28d6

Create CNAME

view details

push time in a month

create barnchromandev/romandev.github.io

branch : master

created branch time in a month

push eventromandev/proxy

Jinho Bang

commit sha b1c58a25b2b3a8ebb1f730299d157970c7f0fa62

Update proxy.pac

view details

push time in a month

push eventromandev/proxy

Jinho Bang

commit sha 581693a5d2b446f8564a745ee1983e70bcee6288

Update proxy.pac

view details

push time in a month

push eventromandev/proxy

Jinho Bang

commit sha fb1c8a49b29310d1c759f143ac6db0b7e81d45b4

Update proxy.pac

view details

push time in a month

push eventromandev/proxy

Jinho Bang

commit sha c6f626f1f6ae08d8f25a84bd8c4bd0ce6af585a8

Update proxy.pac

view details

push time in a month

push eventromandev/proxy

Jinho Bang

commit sha 9853b81b8ac9f45db14d222864b74409c5aebefc

Update proxy.pac

view details

push time in a month

push eventromandev/proxy

Jinho Bang

commit sha ca6a9962673246824d675ab43929dd341f5eaae1

Update proxy.pac

view details

push time in a month

Pull request review commentnodejs/node-addon-api

Implement ThreadSafeFunction class

 struct FinalizeData {   Hint* hint; }; +template <typename ContextType=void,+          typename Finalizer=std::function<void(Env, void*, ContextType*)>,+          typename FinalizerDataType=void>+struct ThreadSafeFinalize {+  static inline+  void Wrapper(napi_env env, void* rawFinalizeData, void* /* rawContext */) {+    if (rawFinalizeData == nullptr)+      return;++    ThreadSafeFinalize* finalizeData =+        static_cast<ThreadSafeFinalize*>(rawFinalizeData);+    finalizeData->callback(Env(env));+    if (finalizeData->tsfn) {+      *finalizeData->tsfn = nullptr;+    }+    delete finalizeData;+  }++  static inline+  void WrapperWithData(napi_env env,+                       void* rawFinalizeData,+                       void* /* rawContext */) {+    if (rawFinalizeData == nullptr)+      return;++    ThreadSafeFinalize* finalizeData =+        static_cast<ThreadSafeFinalize*>(rawFinalizeData);+    finalizeData->callback(Env(env), finalizeData->data);+    if (finalizeData->tsfn) {+      *finalizeData->tsfn = nullptr;+    }+    delete finalizeData;+  }++  static inline+  void WrapperWithContext(napi_env env,+                          void* rawFinalizeData,+                          void* rawContext) {+    if (rawFinalizeData == nullptr)+      return;++    ThreadSafeFinalize* finalizeData =+        static_cast<ThreadSafeFinalize*>(rawFinalizeData);+    finalizeData->callback(Env(env), static_cast<ContextType*>(rawContext));+    if (finalizeData->tsfn) {+      *finalizeData->tsfn = nullptr;

The following code will pointer to a internal object newly created on heap by engine.

napi_threadsafe_function tsfn = napi_create_threadsafe_function(...);

The internal object is destroyed by napi_release_threadsafe_function and then finalize_callback will be invoked at that time.

napi_release_threadsafe_function(tsfn, napi_tsfn_abort);

But tsfn is still pointing to the destroyed internal object's address. So, we might access the invalid address.

napi_*_threadsafe_function(tsfn, ...);

Hence, we should make tsfn variable set to null pointer when finalize_callback is called. In case of C API, we might delegate the work to N-API users because users set a null pointer to the tsfn variable manually. But in case of C++ wrapper, we have to do it internally because there is no way for users to reset the internal tsfn variable. (+ for convenience and preventing invalid use-cases)

BTW, if we write some code as follows, it's difficult to trace _tsfn's variable to modify its value in finalize_callback. Because the internal object pointed to by _tsfn is on heap, but the _tsfn variable itself is still on stack. If the ThreadSafeFunction instance is (move) assigned or (move) constructed to the other instance, then the _tsfn variable is moved on another stack.

class ThreadSafeFunction {
  ...
  private:
    napi_threadsafe_function _tsfn;
};

Let's create a new TSFN wrapper object. We named it as t1. t1._tsfn's address will be passed to finalize_callback data.

ThreadSafeFunction t1 = ThreadSafeFunction::New(...);

And then create a new TSFN wrapper object by using move constructor from t1. We named it as t2. At that time, t1's finalize_callback data is still valid. It means that the data still remembers t1._tsfn's address instead of t2._tsfn's address. But there is no way to update the finalize_callback data because we can only pass the data when napi_create_threadsafe_function is called.

ThreadSafeFunction t2 = std::move(t1);

When finalize_callback is eventually called, the callback tries to modify t1._tsfn instead of t2._tsfn. (The t1._tsfn will be nullptr by move constructor.)

Therefore, one solution is that we put _tsfn pointer variable on heap instead of stack.

class ThreadSafeFunction {
  ...
  private:
    std::unique_ptr<napi_threadsafe_function> _tsfn;
};

If we do that, we can easily reset the value pointed to by _tsfn when finalize_callback is called. Does it make sense? If I'm missing something, please let me know :)

romandev

comment created time in a month

pull request commentnodejs/node-addon-api

Implement ThreadSafeFunction class

Gentle ping

romandev

comment created time in a month

issue commentnodejs/node-addon-api

Accessor for user defined types

Okay, I made a simple Wrap() method in SteeringWheelWrapper. This is not perfect(creating new JS instance every time) but you might be able to modify and use it.

static Napi::Object Wrap(Napi::Env env, SteeringWheel* steeringWheel);
class SteeringWheel {
  public:
    void turnLeft() { printf("turnLeft\n"); }
    void turnRight() { printf("turnRight\n"); }
};

class Car {
  public:
    SteeringWheel steeringWheel;
    void start() { printf("start\n"); }
    void stop() { printf("stop\n"); }
};

class SteeringWheelWrapper : public Napi::ObjectWrap<SteeringWheelWrapper> {
  public:
    SteeringWheelWrapper(const Napi::CallbackInfo& info) :
      Napi::ObjectWrap<SteeringWheelWrapper>(info) {
    }

    static Napi::Object Wrap(Napi::Env env, SteeringWheel* steeringWheel) {
      Napi::HandleScope scope(env);
      Napi::Object obj = DefineClass(env, "SteeringWheelWrapper", {
        InstanceMethod("turnLeft", &SteeringWheelWrapper::TurnLeft, napi_enumerable),
        InstanceMethod("turnRight", &SteeringWheelWrapper::TurnRight, napi_enumerable)
      }).New({});
      SteeringWheelWrapper* s = Unwrap(obj);
      s->_steeringWheel = steeringWheel;
      return obj;
    }

    Napi::Value TurnLeft(const Napi::CallbackInfo&) {
      _steeringWheel->turnLeft();
      return Napi::Value();
    }

    Napi::Value TurnRight(const Napi::CallbackInfo&) {
      _steeringWheel->turnRight();
      return Napi::Value();
    }
  private:
    SteeringWheel* _steeringWheel;
};

class CarWrapper : public Napi::ObjectWrap<CarWrapper> {
  public:
    CarWrapper(const Napi::CallbackInfo& info) :
      Napi::ObjectWrap<CarWrapper>(info), _car(new Car()) {
    }

    Napi::Value GetSteeringWheel(const Napi::CallbackInfo& info) {
      return SteeringWheelWrapper::Wrap(info.Env(), &_car->steeringWheel);
    }

    Napi::Value Start(const Napi::CallbackInfo&) {
      _car->start();
      return Napi::Value();
    }

    Napi::Value Stop(const Napi::CallbackInfo&) {
      _car->stop();
      return Napi::Value();
    }

    static void Initialize(Napi::Env env, Napi::Object exports) {
      exports.Set("Car", DefineClass(env, "Car", {
        InstanceAccessor("steeringWheel", &CarWrapper::GetSteeringWheel, nullptr, napi_enumerable),
        InstanceMethod("start", &CarWrapper::Start, napi_enumerable),
        InstanceMethod("stop", &CarWrapper::Start, napi_enumerable)
      }));
    }

  private:
    Car* _car;
};
tastnt

comment created time in a month

issue commentnodejs/node-addon-api

Accessor for user defined types

I hope that the following code help you.

// C++ side
class SteeringWheel : public Napi::ObjectWrap<SteeringWheel> {
  public:
    SteeringWheel(const Napi::CallbackInfo& info) :
      Napi::ObjectWrap<SteeringWheel>(info) {
    }

    static Napi::Object Create(Napi::Env env) {
      return DefineClass(env, "SteeringWheel", {
        InstanceMethod("turnLeft", &SteeringWheel::TurnLeft, napi_enumerable),
        InstanceMethod("turnRight", &SteeringWheel::TurnRight, napi_enumerable)
      }).New({});
    }

    Napi::Value TurnLeft(const Napi::CallbackInfo&) {
      printf("turnLeft\n");
      return Napi::Value();
    }

    Napi::Value TurnRight(const Napi::CallbackInfo&) {
      printf("turnRight\n");
      return Napi::Value();
    }
};

class Car : public Napi::ObjectWrap<Car> {
  public:
    Car(const Napi::CallbackInfo& info) :
      Napi::ObjectWrap<Car>(info), _steeringWheel(Napi::Persistent(SteeringWheel::Create(info.Env()))) {
    }

    Napi::Value GetSteeringWheel(const Napi::CallbackInfo&) {
      return _steeringWheel.Value();
    }

    Napi::Value Start(const Napi::CallbackInfo& info) {
      printf("start\n");

      // If you need a native SteeringWheel object, then you can use Unwrap()
      SteeringWheel* s = SteeringWheel::Unwrap(_steeringWheel.Value().ToObject());
      s->TurnLeft(info);
      s->TurnRight(info);

      return Napi::Value();
    }

    Napi::Value Stop(const Napi::CallbackInfo&) {
      printf("stop\n");
      return Napi::Value();
    }

    static void Initialize(Napi::Env env, Napi::Object exports) {
      exports.Set("Car", DefineClass(env, "Car", {
        InstanceAccessor("steeringWheel", &Car::GetSteeringWheel, nullptr, napi_enumerable),
        InstanceMethod("start", &Car::Start, napi_enumerable),
        InstanceMethod("stop", &Car::Start, napi_enumerable)
      }));
    }

  private:
    Napi::ObjectReference _steeringWheel;
};

Napi::Object InitObjectWrap(Napi::Env env) {
  Car::Initialize(env, exports);
  return exports;
}
// JS side
const c = new Car();
c.steeringWheel.turnLeft();
c.start();
tastnt

comment created time in a month

Pull request review commentnodejs/node-addon-api

Implement ThreadSafeFunction class

+#include <uv.h>+#include "napi.h"++using namespace Napi;++constexpr size_t ARRAY_LENGTH = 10;+constexpr size_t MAX_QUEUE_SIZE = 2;++static uv_thread_t uvThreads[2];+static ThreadSafeFunction tsfn;++struct ThreadSafeFunctionInfo {+  enum CallType {+    DEFAULT,+    BLOCKING,+    NON_BLOCKING+  } type;+  bool abort;+  bool startSecondary;+  FunctionReference jsFinalizeCallback;+  uint32_t maxQueueSize;+} tsfnInfo;++// Thread data to transmit to JS+static int ints[ARRAY_LENGTH];++static void SecondaryThread(void* data) {+  auto* tsFunction = static_cast<ThreadSafeFunction*>(data);++  if (tsFunction->Release() != napi_ok) {+    Error::Fatal("SecondaryThread", "ThreadSafeFunction.Release() failed");+  }+}++// Source thread producing the data+static void DataSourceThread(void* data) {+  auto* tsFunction = static_cast<ThreadSafeFunction*>(data);++  ThreadSafeFunctionInfo* info = tsFunction->GetContext();++  if (info->startSecondary) {+    if (tsFunction->Acquire() != napi_ok) {+      Error::Fatal("DataSourceThread", "ThreadSafeFunction.Acquire() failed");+    }++    if (uv_thread_create(&uvThreads[1], SecondaryThread, tsFunction) != 0) {+      Error::Fatal("DataSourceThread", "Failed to start secondary thread");+    }+  }++  bool queueWasFull = false;+  bool queueWasClosing = false;+  for (int index = ARRAY_LENGTH - 1; index > -1 && !queueWasClosing; index--) {+    napi_status status = napi_generic_failure;+    auto callback = [](Env env, Function jsCallback, int* data) {+      jsCallback.Call({ Number::New(env, *data) });+    };++    switch (info->type) {+      case ThreadSafeFunctionInfo::DEFAULT:+        status = tsFunction->BlockingCall();+        break;+      case ThreadSafeFunctionInfo::BLOCKING:+        status = tsFunction->BlockingCall(&ints[index], callback);+        break;+      case ThreadSafeFunctionInfo::NON_BLOCKING:+        status = tsFunction->NonBlockingCall(&ints[index], callback);+        break;+    }++    if (info->maxQueueSize == 0) {+      // Let's make this thread really busy for 200 ms to give the main thread a+      // chance to abort.+      uint64_t start = uv_hrtime();+      for (; uv_hrtime() - start < 200000000;);+    }++    switch (status) {+    case napi_queue_full:+      queueWasFull = true;+      index++;+      // fall through++    case napi_ok:+      continue;++    case napi_closing:+      queueWasClosing = true;+      break;++    default:+      Error::Fatal("DataSourceThread", "ThreadSafeFunction.*Call() failed");+    }+  }++  if (info->type == ThreadSafeFunctionInfo::NON_BLOCKING && !queueWasFull) {+    Error::Fatal("DataSourceThread", "Queue was never full");+  }++  if (info->abort && !queueWasClosing) {+    Error::Fatal("DataSourceThread", "Queue was never closing");+  }++  if (!queueWasClosing && tsFunction->Release() != napi_ok) {+    Error::Fatal("DataSourceThread", "ThreadSafeFunction.Release() failed");+  }+}++static Value StopThread(const CallbackInfo& info) {+  tsfnInfo.jsFinalizeCallback = Napi::Persistent(info[0].As<Function>());+  bool abort = info[1].As<Boolean>();+  if (abort) {+    tsfn.Abort();+  } else {+    tsfn.Release();+  }+  return Value();+}++// Join the thread and inform JS that we're done.+static void JoinTheThreads(Env /* env */,+                           uv_thread_t* theThreads,+                           ThreadSafeFunctionInfo* info) {+  uv_thread_join(&theThreads[0]);+  if (info->startSecondary) {+    uv_thread_join(&theThreads[1]);+  }++  info->jsFinalizeCallback.Call({});+  info->jsFinalizeCallback.Reset();+}++static Value StartThreadInternal(const CallbackInfo& info,+    ThreadSafeFunctionInfo::CallType type) {+  tsfnInfo.type = type;+  tsfnInfo.abort = info[1].As<Boolean>();+  tsfnInfo.startSecondary = info[2].As<Boolean>();+  tsfnInfo.maxQueueSize = info[3].As<Number>().Uint32Value();++  tsfn = ThreadSafeFunction::New(info.Env(), info[0].As<Function>(),+      "Test", tsfnInfo.maxQueueSize, 2, &tsfnInfo, JoinTheThreads, uvThreads);

@KevinEady,

Thank you for your input! My new commit includes the following things:

  • Remove _env member from TSFN
  • Reset internal _tsfn pointer in Finalize callback
  • Add a check whether the internal _tsfn pointer is valid in move assign operator.

It works well in my local env. Could you please review it again?

romandev

comment created time in a month

push eventromandev/node-addon-api

Jinho Bang

commit sha 5ebfabd9451bd06f03858d5997f9d195ab2053b7

Implement ThreadSafeFunction class This PR is implementing ThreadSafeFunction class wraps napi_threadsafe_function features. FYI, the test files that included in this PR have come from Node.js repo[1]. They've been rewritten based on C++ and node-addon-api. Fixes #312. [1] https://github.com/nodejs/node/tree/master/test/node-api/test_threadsafe_function

view details

push time in a month

Pull request review commentnodejs/node-addon-api

Implement ThreadSafeFunction class

+#include <uv.h>+#include "napi.h"++using namespace Napi;++constexpr size_t ARRAY_LENGTH = 10;+constexpr size_t MAX_QUEUE_SIZE = 2;++static uv_thread_t uvThreads[2];+static ThreadSafeFunction tsfn;++struct ThreadSafeFunctionInfo {+  enum CallType {+    DEFAULT,+    BLOCKING,+    NON_BLOCKING+  } type;+  bool abort;+  bool startSecondary;+  FunctionReference jsFinalizeCallback;+  uint32_t maxQueueSize;+} tsfnInfo;++// Thread data to transmit to JS+static int ints[ARRAY_LENGTH];++static void SecondaryThread(void* data) {+  auto* tsFunction = static_cast<ThreadSafeFunction*>(data);++  if (tsFunction->Release() != napi_ok) {+    Error::Fatal("SecondaryThread", "ThreadSafeFunction.Release() failed");+  }+}++// Source thread producing the data+static void DataSourceThread(void* data) {+  auto* tsFunction = static_cast<ThreadSafeFunction*>(data);++  ThreadSafeFunctionInfo* info = tsFunction->GetContext();++  if (info->startSecondary) {+    if (tsFunction->Acquire() != napi_ok) {+      Error::Fatal("DataSourceThread", "ThreadSafeFunction.Acquire() failed");+    }++    if (uv_thread_create(&uvThreads[1], SecondaryThread, tsFunction) != 0) {+      Error::Fatal("DataSourceThread", "Failed to start secondary thread");+    }+  }++  bool queueWasFull = false;+  bool queueWasClosing = false;+  for (int index = ARRAY_LENGTH - 1; index > -1 && !queueWasClosing; index--) {+    napi_status status = napi_generic_failure;+    auto callback = [](Env env, Function jsCallback, int* data) {+      jsCallback.Call({ Number::New(env, *data) });+    };++    switch (info->type) {+      case ThreadSafeFunctionInfo::DEFAULT:+        status = tsFunction->BlockingCall();+        break;+      case ThreadSafeFunctionInfo::BLOCKING:+        status = tsFunction->BlockingCall(&ints[index], callback);+        break;+      case ThreadSafeFunctionInfo::NON_BLOCKING:+        status = tsFunction->NonBlockingCall(&ints[index], callback);+        break;+    }++    if (info->maxQueueSize == 0) {+      // Let's make this thread really busy for 200 ms to give the main thread a+      // chance to abort.+      uint64_t start = uv_hrtime();+      for (; uv_hrtime() - start < 200000000;);+    }++    switch (status) {+    case napi_queue_full:+      queueWasFull = true;+      index++;+      // fall through++    case napi_ok:+      continue;++    case napi_closing:+      queueWasClosing = true;+      break;++    default:+      Error::Fatal("DataSourceThread", "ThreadSafeFunction.*Call() failed");+    }+  }++  if (info->type == ThreadSafeFunctionInfo::NON_BLOCKING && !queueWasFull) {+    Error::Fatal("DataSourceThread", "Queue was never full");+  }++  if (info->abort && !queueWasClosing) {+    Error::Fatal("DataSourceThread", "Queue was never closing");+  }++  if (!queueWasClosing && tsFunction->Release() != napi_ok) {+    Error::Fatal("DataSourceThread", "ThreadSafeFunction.Release() failed");+  }+}++static Value StopThread(const CallbackInfo& info) {+  tsfnInfo.jsFinalizeCallback = Napi::Persistent(info[0].As<Function>());+  bool abort = info[1].As<Boolean>();+  if (abort) {+    tsfn.Abort();+  } else {+    tsfn.Release();+  }+  return Value();+}++// Join the thread and inform JS that we're done.+static void JoinTheThreads(Env /* env */,+                           uv_thread_t* theThreads,+                           ThreadSafeFunctionInfo* info) {+  uv_thread_join(&theThreads[0]);+  if (info->startSecondary) {+    uv_thread_join(&theThreads[1]);+  }++  info->jsFinalizeCallback.Call({});+  info->jsFinalizeCallback.Reset();+}++static Value StartThreadInternal(const CallbackInfo& info,+    ThreadSafeFunctionInfo::CallType type) {+  tsfnInfo.type = type;+  tsfnInfo.abort = info[1].As<Boolean>();+  tsfnInfo.startSecondary = info[2].As<Boolean>();+  tsfnInfo.maxQueueSize = info[3].As<Number>().Uint32Value();++  tsfn = ThreadSafeFunction::New(info.Env(), info[0].As<Function>(),+      "Test", tsfnInfo.maxQueueSize, 2, &tsfnInfo, JoinTheThreads, uvThreads);

I removed Abort() on destructor and assign-operator but there is one issue here. For example, if we assign new TSFN again as follows, and then previous object will be memory leaked.

tsfn = ThreadSafeFunction::New(info.Env(), info[0].As<Function>(),
      "Test", tsfnInfo.maxQueueSize, 2, &tsfnInfo, JoinTheThreads, uvThreads);
tsfn = ThreadSafeFunction::New(info.Env(), info[0].As<Function>(),
      "Test", tsfnInfo.maxQueueSize, 2, &tsfnInfo, JoinTheThreads, uvThreads); // Assign new TSFN object

I thought that the C++ wrapper should have an ability to manage the TSFN's life-cycle(create/destroy). Do you have any ideas?

romandev

comment created time in 2 months

push eventromandev/node-addon-api

Jinho Bang

commit sha 8a201449ba9b37c04e3ae9a441e4ea98db896bbb

Implement ThreadSafeFunction class This PR is implementing ThreadSafeFunction class wraps napi_threadsafe_function features. FYI, the test files that included in this PR have come from Node.js repo[1]. They've been rewritten based on C++ and node-addon-api. Fixes #312. [1] https://github.com/nodejs/node/tree/master/test/node-api/test_threadsafe_function

view details

push time in 2 months

pull request commentnodejs/node-addon-api

Implement ThreadSafeFunction class

@gabrielschulhof, @KevinEady

Sorry for delayed update. From now on, I'll respond quickly. I addressed all your comments. PTAL.

romandev

comment created time in 2 months

push eventromandev/node-addon-api

Jinho Bang

commit sha a8c5884fa66e81d7d04dc5099e1e62a05a982b9e

Implement ThreadSafeFunction class This PR is implementing ThreadSafeFunction class wraps napi_threadsafe_function features. FYI, the test files that included in this PR have come from Node.js repo[1]. They've been rewritten based on C++ and node-addon-api. Fixes #312. [1] https://github.com/nodejs/node/tree/master/test/node-api/test_threadsafe_function

view details

push time in 2 months

push eventromandev/proxy

Jinho Bang

commit sha 6b830b9fdf7bb37a181b0e6e877b61089dc28c2b

Initial Commit :)

view details

push time in 2 months

PublicEvent

Pull request review commentnodejs/node-addon-api

Implement ThreadSafeFunction class

 inline void AsyncWorker::OnWorkComplete(   delete self; } +////////////////////////////////////////////////////////////////////////////////+// ThreadSafeFunction class+////////////////////////////////////////////////////////////////////////////////++// static+template <typename ResourceString>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                                  const Function& callback,+                                                  ResourceString resourceName,+                                                  size_t maxQueueSize,+                                                  size_t initialThreadCount) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount, static_cast<void*>(nullptr));+}++// static+template <typename ResourceString>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                                  const Function& callback,+                                                  const Object& resource,+                                                  ResourceString resourceName,+                                                  size_t maxQueueSize,+                                                  size_t initialThreadCount) {+  return New(env, callback, resource, resourceName, maxQueueSize,+             initialThreadCount, static_cast<void*>(nullptr));+}++// static+template <typename DataType, typename Finalizer, typename ResourceString>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                                  const Function& callback,+                                                  ResourceString resourceName,+                                                  size_t maxQueueSize,+                                                  size_t initialThreadCount,+                                                  DataType* data,+                                                  Finalizer finalizeCallback) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount, data, finalizeCallback);+}++// static+template <typename Context, typename ResourceString>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                                  const Function& callback,+                                                  ResourceString resourceName,+                                                  size_t maxQueueSize,+                                                  size_t initialThreadCount,+                                                  Context* context) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount, context);+}++// static+template <typename DataType, typename Finalizer,+              typename Context, typename ResourceString>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                                  const Function& callback,+                                                  ResourceString resourceName,+                                                  size_t maxQueueSize,+                                                  size_t initialThreadCount,+                                                  DataType* data,+                                                  Finalizer finalizeCallback,+                                                  Context* context) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount, data, finalizeCallback, context);+}++// static+template <typename DataType, typename Finalizer, typename ResourceString>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                                  const Function& callback,+                                                  const Object& resource,+                                                  ResourceString resourceName,+                                                  size_t maxQueueSize,+                                                  size_t initialThreadCount,+                                                  DataType* data,+                                                  Finalizer finalizeCallback) {+  static_assert(details::can_make_string<ResourceString>::value+      || std::is_convertible<ResourceString, napi_value>::value,+      "Resource name should be string convertible type");++  napi_threadsafe_function tsFunctionValue;+  auto* finalizeData = new details::FinalizeData<DataType, Finalizer>({+      finalizeCallback, nullptr });+  napi_status status = napi_create_threadsafe_function(env, callback, resource,+      Value::From(env, resourceName), maxQueueSize, initialThreadCount, data,+      details::FinalizeData<DataType, Finalizer>::Wrapper,+      finalizeData, CallJS, &tsFunctionValue);+  if (status != napi_ok) {+    delete finalizeData;+    NAPI_THROW_IF_FAILED(env, status, ThreadSafeFunction());+  }++  return ThreadSafeFunction(env, tsFunctionValue);+}++// static+template <typename Context, typename ResourceString>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                                  const Function& callback,+                                                  const Object& resource,+                                                  ResourceString resourceName,+                                                  size_t maxQueueSize,+                                                  size_t initialThreadCount,+                                                  Context* context) {+  static_assert(details::can_make_string<ResourceString>::value+      || std::is_convertible<ResourceString, napi_value>::value,+      "Resource name should be string convertible type");++  napi_threadsafe_function tsFunctionValue;+  napi_status status = napi_create_threadsafe_function(env, callback, resource,+      Value::From(env, resourceName), maxQueueSize, initialThreadCount, nullptr,+      nullptr, context, CallJS, &tsFunctionValue);+  if (status != napi_ok) {+    NAPI_THROW_IF_FAILED(env, status, ThreadSafeFunction());+  }++  return ThreadSafeFunction(env, tsFunctionValue);+}++// static+template <typename DataType, typename Finalizer,+          typename Context, typename ResourceString>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                                  const Function& callback,+                                                  const Object& resource,+                                                  ResourceString resourceName,+                                                  size_t maxQueueSize,+                                                  size_t initialThreadCount,+                                                  DataType* data,+                                                  Finalizer finalizeCallback,+                                                  Context* context) {+  static_assert(details::can_make_string<ResourceString>::value+      || std::is_convertible<ResourceString, napi_value>::value,+      "Resource name should be string convertible type");++  napi_threadsafe_function tsFunctionValue;+  auto* finalizeData = new details::FinalizeData<DataType, Finalizer>({+      finalizeCallback, context });+  napi_status status = napi_create_threadsafe_function(env, callback, resource,+      Value::From(env, resourceName), maxQueueSize, initialThreadCount, data,+      details::FinalizeData<DataType, Finalizer, Context>::WrapperWithHint,+      finalizeData, CallJS, &tsFunctionValue);+  if (status != napi_ok) {+    delete finalizeData;+    NAPI_THROW_IF_FAILED(env, status, ThreadSafeFunction());+  }++  return ThreadSafeFunction(env, tsFunctionValue);+}++inline ThreadSafeFunction::Status ThreadSafeFunction::BlockingCall() const {+  return CallInternal(nullptr, napi_tsfn_blocking);+}++template <typename Callback>+inline ThreadSafeFunction::Status ThreadSafeFunction::BlockingCall(+    Callback callback) const {+  return CallInternal(new CallbackWrapper(callback), napi_tsfn_blocking);+}++template <typename DataType, typename Callback>+inline ThreadSafeFunction::Status ThreadSafeFunction::BlockingCall(+    DataType* data, Callback callback) const {+  auto wrapper = [data, callback](Env env, Function jsCallback) {+    callback(env, jsCallback, data);+  };+  return CallInternal(new CallbackWrapper(wrapper), napi_tsfn_blocking);+}++inline ThreadSafeFunction::Status ThreadSafeFunction::NonBlockingCall() const {+  return CallInternal(nullptr, napi_tsfn_nonblocking);+}++template <typename Callback>+inline ThreadSafeFunction::Status ThreadSafeFunction::NonBlockingCall(+    Callback callback) const {+  return CallInternal(new CallbackWrapper(callback), napi_tsfn_nonblocking);+}++template <typename DataType, typename Callback>+inline ThreadSafeFunction::Status ThreadSafeFunction::NonBlockingCall(+    DataType* data, Callback callback) const {+  auto wrapper = [data, callback](Env env, Function jsCallback) {+    callback(env, jsCallback, data);+  };+  return CallInternal(new CallbackWrapper(wrapper), napi_tsfn_nonblocking);+}++inline bool ThreadSafeFunction::Acquire() const {+  return !IsAborted() && napi_acquire_threadsafe_function(+      _tsFunctionValue) == napi_ok;+}++inline bool ThreadSafeFunction::Release() {+  return !IsAborted() && napi_release_threadsafe_function(+      _tsFunctionValue, napi_tsfn_release) == napi_ok;+}++inline bool ThreadSafeFunction::Abort() {+  if (IsAborted()) {+    return false;+  }++  napi_status status = napi_release_threadsafe_function(+      _tsFunctionValue, napi_tsfn_abort);++  _tsFunctionValue = nullptr;+  _env = nullptr;++  return status == napi_ok;

@gabrielschulhof Gentle ping

romandev

comment created time in 2 months

pull request commentnodejs/node-addon-api

Implement ThreadSafeFunction class

@extmchristensen Sorry for belated response.

Will this change allow calls into JS from another 3rd party thread similar to the napi-thread-safe-callback module?

Yes, it is. this patch wraps the N-APIs for thread safe function calls. https://nodejs.org/dist/latest-v11.x/docs/api/n-api.html#n_api_asynchronous_thread_safe_function_calls

romandev

comment created time in 2 months

pull request commentnodejs/node-addon-api

Implement ThreadSafeFunction class

@KevinEady, I implemented my idea for GetContext() without class template into latest patch. Could you please review the patch? If you mind my idea, I'll get rid of it from my patch. Thank you.

romandev

comment created time in 2 months

push eventromandev/node-addon-api

Jinho Bang

commit sha 41da82e9df4c56a833f5e654b9ec82b13b205bdd

Implement ThreadSafeFunction class This PR is implementing ThreadSafeFunction class wraps napi_threadsafe_function features. FYI, the test files that included in this PR have come from Node.js repo[1]. They've been rewritten based on C++ and node-addon-api. Fixes #312. [1] https://github.com/nodejs/node/tree/master/test/node-api/test_threadsafe_function

view details

push time in 2 months

push eventromandev/node-addon-api

Ryuichi Okumura

commit sha 72b1975cfffbc55a04c64047467d7dd0bfa2934e

doc: fix links to the Property Descriptor docs PR-URL: https://github.com/nodejs/node-addon-api/pull/458 Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com> Reviewed-By: NickNaso <nicoladelgobbo@gmail.com>

view details

Gabriel Schulhof

commit sha b0f6b601aaa0a8f488fec2b4f09e0f3f65f4a241

src: add AsyncWorker destruction suppression Add method `SuppressDestruct()` to `AsyncWorker`, which will cause an instance of the class to remain allocated even after the `OnOK` callback fires. Such an instance must be explicitly `delete`-ed from user code. Re: https://github.com/nodejs/node-addon-api/issues/231 Re: https://github.com/nodejs/abi-stable-node/issues/353 PR-URL: https://github.com/nodejs/node-addon-api/pull/407 Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com> Reviewed-By: NickNaso <nicoladelgobbo@gmail.com>

view details

Bill Gallafent

commit sha 1ed7ad87696a54fab9edc91e329cc9cd7ef842b9

doc: correct return type of Int32Value to int32_t It currently reads uint32_t, which would be very strange, and is also untrue (I checked napi.h) ;) PR-URL: https://github.com/nodejs/node-addon-api/pull/459 Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com> Reviewed-By: NickNaso <nicoladelgobbo@gmail.com>

view details

Nicola Del Gobbo

commit sha 83b41c2fe46a7693aba291ffa740932a9f142be2

Document adding -fvisibility=hidden flag for macOS users * -fvisibility=hidden flag for macOS user Added section to remember the macOS user to add the `-fvisibility=hidden` flag. PR-URL: https://github.com/nodejs/node-addon-api/pull/460 Reviewed-By: Gabriel Schulhof <gabriel.schulhof@intel.com> Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>

view details

NickNaso

commit sha c12c42519c108b859c7d9f3a3ebb902f948eabd6

Prepare release 1.6.3

view details

Gabriel "_|Nix|_" Schulhof

commit sha 36863f087b1f879184ae64ebfef9c19e07c4ae0d

doc: refer to TypedArray and ArrayBuffer from Array Add a blurb to the `Napi::Array` documentation that refers readers to `Napi::TypedArray` and `Napi::ArrayBuffer` for using arrays with large amounts of data. PR-URL: https://github.com/nodejs/node-addon-api/pull/465 Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com> Reviewed-By: Nicola Del Gobbo <nicoladelgobbo@NickNaso.local>

view details

Jinho Bang

commit sha 2d549684ddd13b1c6ea14f0ccaff810e35c0c249

Implement ThreadSafeFunction class This PR is implementing ThreadSafeFunction class wraps napi_threadsafe_function features. FYI, the test files that included in this PR have come from Node.js repo[1]. They've been rewritten based on C++ and node-addon-api. Fixes #312. [1] https://github.com/nodejs/node/tree/master/test/node-api/test_threadsafe_function

view details

push time in 2 months

startedSamsung/Castanets

started time in 3 months

startedgdgsuwon/solocoding2019_base

started time in 3 months

pull request commentw3c/payment-request

Set [[waitForUpdate]] to true on dispatch of payerdetailchange

https://bugs.chromium.org/p/chromium/issues/detail?id=947938

marcoscaceres

comment created time in 3 months

pull request commentnodejs/node-addon-api

Implement ThreadSafeFunction class

@KevinEady If you're already implementing GetContext(), I'll leave a FIXME comment and then you can land your patch in follow-up PR. If you'd like to include the GetContext() implementation in this patch, I'll continue the work. Basically, I wanted to implement GetContext without explicit template. (But I'm not sure if it is possible or not.)

romandev

comment created time in 3 months

Pull request review commentnodejs/node-addon-api

Implement ThreadSafeFunction class

 inline void AsyncWorker::OnWorkComplete(   delete self; } +////////////////////////////////////////////////////////////////////////////////+// ThreadSafeFunction class+////////////////////////////////////////////////////////////////////////////////++// static+template <typename ResourceString>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                                  const Function& callback,+                                                  ResourceString resourceName,+                                                  size_t maxQueueSize,+                                                  size_t initialThreadCount) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount, static_cast<void*>(nullptr));+}++// static+template <typename ResourceString>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                                  const Function& callback,+                                                  const Object& resource,+                                                  ResourceString resourceName,+                                                  size_t maxQueueSize,+                                                  size_t initialThreadCount) {+  return New(env, callback, resource, resourceName, maxQueueSize,+             initialThreadCount, static_cast<void*>(nullptr));+}++// static+template <typename DataType, typename Finalizer, typename ResourceString>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                                  const Function& callback,+                                                  ResourceString resourceName,+                                                  size_t maxQueueSize,+                                                  size_t initialThreadCount,+                                                  DataType* data,+                                                  Finalizer finalizeCallback) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount, data, finalizeCallback);+}++// static+template <typename Context, typename ResourceString>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                                  const Function& callback,+                                                  ResourceString resourceName,+                                                  size_t maxQueueSize,+                                                  size_t initialThreadCount,+                                                  Context* context) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount, context);+}++// static+template <typename DataType, typename Finalizer,+              typename Context, typename ResourceString>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                                  const Function& callback,+                                                  ResourceString resourceName,+                                                  size_t maxQueueSize,+                                                  size_t initialThreadCount,+                                                  DataType* data,+                                                  Finalizer finalizeCallback,+                                                  Context* context) {+  return New(env, callback, Object(), resourceName, maxQueueSize,+             initialThreadCount, data, finalizeCallback, context);+}++// static+template <typename DataType, typename Finalizer, typename ResourceString>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                                  const Function& callback,+                                                  const Object& resource,+                                                  ResourceString resourceName,+                                                  size_t maxQueueSize,+                                                  size_t initialThreadCount,+                                                  DataType* data,+                                                  Finalizer finalizeCallback) {+  static_assert(details::can_make_string<ResourceString>::value+      || std::is_convertible<ResourceString, napi_value>::value,+      "Resource name should be string convertible type");++  napi_threadsafe_function tsFunctionValue;+  auto* finalizeData = new details::FinalizeData<DataType, Finalizer>({+      finalizeCallback, nullptr });+  napi_status status = napi_create_threadsafe_function(env, callback, resource,+      Value::From(env, resourceName), maxQueueSize, initialThreadCount, data,+      details::FinalizeData<DataType, Finalizer>::Wrapper,+      finalizeData, CallJS, &tsFunctionValue);+  if (status != napi_ok) {+    delete finalizeData;+    NAPI_THROW_IF_FAILED(env, status, ThreadSafeFunction());+  }++  return ThreadSafeFunction(env, tsFunctionValue);+}++// static+template <typename Context, typename ResourceString>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                                  const Function& callback,+                                                  const Object& resource,+                                                  ResourceString resourceName,+                                                  size_t maxQueueSize,+                                                  size_t initialThreadCount,+                                                  Context* context) {+  static_assert(details::can_make_string<ResourceString>::value+      || std::is_convertible<ResourceString, napi_value>::value,+      "Resource name should be string convertible type");++  napi_threadsafe_function tsFunctionValue;+  napi_status status = napi_create_threadsafe_function(env, callback, resource,+      Value::From(env, resourceName), maxQueueSize, initialThreadCount, nullptr,+      nullptr, context, CallJS, &tsFunctionValue);+  if (status != napi_ok) {+    NAPI_THROW_IF_FAILED(env, status, ThreadSafeFunction());+  }++  return ThreadSafeFunction(env, tsFunctionValue);+}++// static+template <typename DataType, typename Finalizer,+          typename Context, typename ResourceString>+inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,+                                                  const Function& callback,+                                                  const Object& resource,+                                                  ResourceString resourceName,+                                                  size_t maxQueueSize,+                                                  size_t initialThreadCount,+                                                  DataType* data,+                                                  Finalizer finalizeCallback,+                                                  Context* context) {+  static_assert(details::can_make_string<ResourceString>::value+      || std::is_convertible<ResourceString, napi_value>::value,+      "Resource name should be string convertible type");++  napi_threadsafe_function tsFunctionValue;+  auto* finalizeData = new details::FinalizeData<DataType, Finalizer>({+      finalizeCallback, context });+  napi_status status = napi_create_threadsafe_function(env, callback, resource,+      Value::From(env, resourceName), maxQueueSize, initialThreadCount, data,+      details::FinalizeData<DataType, Finalizer, Context>::WrapperWithHint,+      finalizeData, CallJS, &tsFunctionValue);+  if (status != napi_ok) {+    delete finalizeData;+    NAPI_THROW_IF_FAILED(env, status, ThreadSafeFunction());+  }++  return ThreadSafeFunction(env, tsFunctionValue);+}++inline ThreadSafeFunction::Status ThreadSafeFunction::BlockingCall() const {+  return CallInternal(nullptr, napi_tsfn_blocking);+}++template <typename Callback>+inline ThreadSafeFunction::Status ThreadSafeFunction::BlockingCall(+    Callback callback) const {+  return CallInternal(new CallbackWrapper(callback), napi_tsfn_blocking);+}++template <typename DataType, typename Callback>+inline ThreadSafeFunction::Status ThreadSafeFunction::BlockingCall(+    DataType* data, Callback callback) const {+  auto wrapper = [data, callback](Env env, Function jsCallback) {+    callback(env, jsCallback, data);+  };+  return CallInternal(new CallbackWrapper(wrapper), napi_tsfn_blocking);+}++inline ThreadSafeFunction::Status ThreadSafeFunction::NonBlockingCall() const {+  return CallInternal(nullptr, napi_tsfn_nonblocking);+}++template <typename Callback>+inline ThreadSafeFunction::Status ThreadSafeFunction::NonBlockingCall(+    Callback callback) const {+  return CallInternal(new CallbackWrapper(callback), napi_tsfn_nonblocking);+}++template <typename DataType, typename Callback>+inline ThreadSafeFunction::Status ThreadSafeFunction::NonBlockingCall(+    DataType* data, Callback callback) const {+  auto wrapper = [data, callback](Env env, Function jsCallback) {+    callback(env, jsCallback, data);+  };+  return CallInternal(new CallbackWrapper(wrapper), napi_tsfn_nonblocking);+}++inline bool ThreadSafeFunction::Acquire() const {+  return !IsAborted() && napi_acquire_threadsafe_function(+      _tsFunctionValue) == napi_ok;+}++inline bool ThreadSafeFunction::Release() {+  return !IsAborted() && napi_release_threadsafe_function(+      _tsFunctionValue, napi_tsfn_release) == napi_ok;+}++inline bool ThreadSafeFunction::Abort() {+  if (IsAborted()) {+    return false;+  }++  napi_status status = napi_release_threadsafe_function(+      _tsFunctionValue, napi_tsfn_abort);++  _tsFunctionValue = nullptr;+  _env = nullptr;++  return status == napi_ok;

Sorry, I don't understand what you want exactly. Do you want this function to return three status instead of boolean? (like napi_release_threadsafe_function) If so, could you please explain the use cases in more details?

romandev

comment created time in 3 months

push eventromandev/kosscontributhon2018

Jinho Bang

commit sha 5369fe115700d9aa289c1f3efcc0a5e20d89f217

GDG Suwon Dashboard

view details

push time in 3 months

fork romandev/kosscontributhon2018

2018 컨트리뷰톤 실시간 현황 확인 및 행사 진행 페이지 입니다.

https://contributhon.herokuapp.com/

fork in 3 months

more