0

I've found that using ThreadSafeFunction in an ObjectWrap class blocks the event loop from exiting even after the program has finished. As soon as I remove the functions which use ThreadSafeFunction (onScanStart and onScanStop), it exits correctly. But when ThreadSafeFunction is used, the program continues running until I hit CTRL+C.

Here's part of the code I'm using. I'd appreciate any help understanding what's wrong:

class AdapterWrapper : public Napi::ObjectWrap<Adapter> {
public:
  static Napi::Object Init(Napi::Env env, Napi::Object exports);
  AdapterWrapper(const Napi::CallbackInfo &info);
  ~AdapterWrapper();

  static Napi::FunctionReference constructor;

private:
  adapter_t handle;
  Napi::ThreadSafeFunction onScanStartFn;
  Napi::ThreadSafeFunction onScanStopFn;

  static void onScanStart(adapter_t handle, void *userdata);
  static void onScanStop(adapter_t handle, void *userdata);

  Napi::Value Scan(const Napi::CallbackInfo &info);
  void SetOnScanStart(const Napi::CallbackInfo &info);
  void SetOnScanStop(const Napi::CallbackInfo &info);
};

Napi::FunctionReference AdapterWrapper::constructor;

Napi::Object AdapterWrapper::Init(Napi::Env env, Napi::Object exports) {
  Napi::Function func = DefineClass(env, "Adapter", {
    InstanceMethod("scan", &AdapterWrapper::ScanStart),
    InstanceMethod("setOnScanStart", &AdapterWrapper::SetOnScanStart),
    InstanceMethod("setOnScanStop", &AdapterWrapper::SetOnScanStop)
  });

  constructor = Napi::Persistent(func);
  constructor.SuppressDestruct();

  exports.Set("Adapter", func);
  return exports;
}

AdapterWrapper::AdapterWrapper(const Napi::CallbackInfo &info)
    : Napi::ObjectWrap<AdapterWrapper>(info) {
  Napi::Env env = info.Env();

  this->handle = adapter_get_handle();
}

AdapterWrapper::~AdapterWrapper() {
  adapter_release_handle(this->handle);
  if (this->onScanStartFn) {
    this->onScanStartFn.Release();
  }
  if (this->onScanStopFn) {
    this->onScanStopFn.Release();
  }
}

Napi::Value AdapterWrapper::SetOnScanStart(const Napi::CallbackInfo &info) {
  Napi::Env env = info.Env();

  this->onScanStartFn = Napi::ThreadSafeFunction::New(env, info[0].As<Napi::Function>(), "onScanStartFn", 0, 1);

  adapter_set_on_scan_start(this->handle, onScanStart, this);
}

Napi::Value AdapterWrapper::SetOnScanStop(const Napi::CallbackInfo &info) {
  Napi::Env env = info.Env();

  this->onScanStopFn = Napi::ThreadSafeFunction::New(env, info[0].As<Napi::Function>(), "onScanStopFn", 0, 1);

  adapter_set_on_scan_stop(this->handle, onScanStop, this);
}

void AdapterWrapper::onScanStart(adapter_t handle, void *userdata) {
  auto adapter = reinterpret_cast<AdapterWrapper *>(userdata);
  auto callback = [](Napi::Env env, Napi::Function jsCallback) {
    jsCallback.Call({});
  };
  onScanStartFn.BlockingCall(callback);
}

void AdapterWrapper::onScanStop(adapter_t handle, void *userdata) {
  auto adapter = reinterpret_cast<AdapterWrapper *>(userdata);
  auto callback = [](Napi::Env env, Napi::Function jsCallback) {
    jsCallback.Call({});
  };
  adapter->onScanStopFn.BlockingCall(callback);
}
Alex Shaw
  • 163
  • 1
  • 7

1 Answers1

0

The answer turned out to be calling Unref after each ThreadSafeFunction was created. That tells the event loop that it can be garbage collected once the program is finished without altering the behavior. HandleScope is also added for extra safety.

The modified source code:

class AdapterWrapper : public Napi::ObjectWrap<Adapter> {
public:
  static Napi::Object Init(Napi::Env env, Napi::Object exports);
  AdapterWrapper(const Napi::CallbackInfo &info);
  ~AdapterWrapper();

  static Napi::FunctionReference constructor;

private:
  adapter_t handle;
  Napi::ThreadSafeFunction onScanStartFn;
  Napi::ThreadSafeFunction onScanStopFn;

  static void onScanStart(adapter_t handle, void *userdata);
  static void onScanStop(adapter_t handle, void *userdata);

  Napi::Value Scan(const Napi::CallbackInfo &info);
  void SetOnScanStart(const Napi::CallbackInfo &info);
  void SetOnScanStop(const Napi::CallbackInfo &info);
};

Napi::FunctionReference AdapterWrapper::constructor;

Napi::Object AdapterWrapper::Init(Napi::Env env, Napi::Object exports) {
  Napi::Function func = DefineClass(env, "Adapter", {
    InstanceMethod("scan", &AdapterWrapper::ScanStart),
    InstanceMethod("setOnScanStart", &AdapterWrapper::SetOnScanStart),
    InstanceMethod("setOnScanStop", &AdapterWrapper::SetOnScanStop)
  });

  constructor = Napi::Persistent(func);
  constructor.SuppressDestruct();

  exports.Set("Adapter", func);
  return exports;
}

AdapterWrapper::AdapterWrapper(const Napi::CallbackInfo &info)
    : Napi::ObjectWrap<AdapterWrapper>(info) {
  Napi::Env env = info.Env();

  this->handle = adapter_get_handle();
}

AdapterWrapper::~AdapterWrapper() {
  adapter_release_handle(this->handle);
  if (this->onScanStartFn) {
    this->onScanStartFn.Release();
  }
  if (this->onScanStopFn) {
    this->onScanStopFn.Release();
  }
}

Napi::Value AdapterWrapper::SetOnScanStart(const Napi::CallbackInfo &info) {
  Napi::Env env = info.Env();
  Napi::HandleScope scope(env);

  this->onScanStartFn = Napi::ThreadSafeFunction::New(env, info[0].As<Napi::Function>(), "onScanStartFn", 0, 1);
  this->onScanStartFn.Unref(env);

  adapter_set_on_scan_start(this->handle, onScanStart, this);
}

Napi::Value AdapterWrapper::SetOnScanStop(const Napi::CallbackInfo &info) {
  Napi::Env env = info.Env();
  Napi::HandleScope scope(env);

  this->onScanStopFn = Napi::ThreadSafeFunction::New(env, info[0].As<Napi::Function>(), "onScanStopFn", 0, 1);
  this->onScanStopFn.Unref(env);

  adapter_set_on_scan_stop(this->handle, onScanStop, this);
}

void AdapterWrapper::onScanStart(adapter_t handle, void *userdata) {
  auto adapter = reinterpret_cast<AdapterWrapper *>(userdata);
  auto callback = [](Napi::Env env, Napi::Function jsCallback) {
    jsCallback.Call({});
  };
  onScanStartFn.BlockingCall(callback);
}

void AdapterWrapper::onScanStop(adapter_t handle, void *userdata) {
  auto adapter = reinterpret_cast<AdapterWrapper *>(userdata);
  auto callback = [](Napi::Env env, Napi::Function jsCallback) {
    jsCallback.Call({});
  };
  adapter->onScanStopFn.BlockingCall(callback);
}
Alex Shaw
  • 163
  • 1
  • 7