current commit
261031.6 · node-api: fix crash on TSFN finalization reentrance at shutdown
#include <node_api.h>
#include <stdlib.h>
#include "../../js-native-api/common.h"

typedef struct {
  napi_threadsafe_function tsfn;
} Holder;

static void CallJs(napi_env env, napi_value js_cb, void* context, void* data) {
  NODE_API_BASIC_ASSERT_RETURN_VOID(false, "The queue stays empty");
}

// Written on the worker thread while its napi_env tears down. Read on the main
// thread after the worker thread is joined.
static bool tsfn_finalized = false;
static bool reentered = false;

static void FinalizeTsfn(napi_env env, void* data, void* hint) {
  tsfn_finalized = true;
}

static napi_value GetReentered(napi_env env, napi_callback_info info) {
  napi_value result;
  NODE_API_CALL(env, napi_get_boolean(env, reentered, &result));
  return result;
}

// Runs while the napi_env tears down, reached from the thread-safe function
// dropping its own napi_env reference. Releasing the thread-safe function
// from here reenters TSFN finalization.
static void FinalizeHolder(napi_env env, void* data, void* hint) {
  Holder* holder = data;
  reentered = tsfn_finalized;
  napi_status status =
      napi_release_threadsafe_function(holder->tsfn, napi_tsfn_abort);
  if (status != napi_ok) {
    abort();
  }
  free(holder);
}

NAPI_MODULE_INIT() {
  napi_value name;
  napi_value external;
  napi_value getter;
  Holder* holder = malloc(sizeof(*holder));

  NODE_API_CALL(
      env,
      napi_create_string_utf8(env, "tsfn_teardown", NAPI_AUTO_LENGTH, &name));

  // The initial thread count is never released, so the thread-safe function is
  // still ref-ed by another thread when the environment tears down.
  NODE_API_CALL(env,
                napi_create_threadsafe_function(env,
                                                NULL,
                                                NULL,
                                                name,
                                                0,
                                                1,
                                                NULL,
                                                FinalizeTsfn,
                                                NULL,
                                                CallJs,
                                                &holder->tsfn));
  // Allow the worker uv_loop to exit.
  NODE_API_CALL(env, napi_unref_threadsafe_function(env, holder->tsfn));

  // Held by the module exports, which the module cache keeps alive, so the
  // napi_external finalizer runs during napi_env teardown.
  NODE_API_CALL(
      env, napi_create_external(env, holder, FinalizeHolder, NULL, &external));
  NODE_API_CALL(env, napi_set_named_property(env, exports, "holder", external));
  NODE_API_CALL(
      env,
      napi_create_function(
          env, "reentered", NAPI_AUTO_LENGTH, GetReentered, NULL, &getter));
  NODE_API_CALL(env,
                napi_set_named_property(env, exports, "reentered", getter));

  return exports;
}