Завершите работу Node.js независимо от ожидающего асинхронного вызова собственного модуля - PullRequest
1 голос
/ 04 октября 2019

Я пишу модуль Napi для Node.js.

Модуль использует WINAPI WaitForSingleObject(pid). Блокирующий вызов WINAPI обернут в Napi::AsyncWorker.

Проблема

Асинхронный вызов препятствует выходу Node.js. Я хочу, чтобы Node.js завершал работу, когда ему больше нечего делать, как это происходит с child_process.unref(). Поэтому я хочу отменить асинхронный вызов из цикла событий Node.js.

1 Ответ

0 голосов
/ 12 октября 2019

У меня нет времени, чтобы сделать из него пакет NPM, но вот мое решение. Решение действительно для всех блокирующих системных вызовов (которые переводят вызывающий поток в состояние ожидания, как Sleep() делает).

Идея заключается в следующем:

  1. Использование std::thread для блокирующего вызова WINAPI.
  2. Используйте napi_threadsafe_function для безопасного обратного вызова из нового потока в Javascript.
  3. Использование napi_unref_threadsafe_function для отмены асинхронной операции из цикла событий Node.js.

Подробнее:

Для безопасных одновременных вызовов создайте новый void* context с данными, относящимися к потоку, и передайте его (поддерживается N-API). Для безотзывных napi_threadsafe_function поддерживается финализатор. Остановите ожидающий поток здесь, чтобы предотвратить аварийные сообщения при выходе из Node.js. Я использую C N-API и C ++ node-addon-api взаимозаменяемо. К сожалению, чистое решение C ++ на момент написания невозможно, потому что napi_unref_threadsafe_function доступно только из C API.

addon.cc:

    #include <napi.h>
    #include <node_api.h>
    #include <windows.h>
    #include <tlhelp32.h>
    #include <sstream>
    #include <string>
    #include <iostream>
    #include <thread>

    using namespace std;
    using namespace Napi;

    struct my_context {
      std::thread nativeThread;
      HANDLE hStopEvent;
      napi_threadsafe_function tsfn;
    };

    std::string WaitForPid(int pid, my_context* context, bool* stopped) {
      HANDLE process = OpenProcess(SYNCHRONIZE, FALSE, pid);
      if (process == NULL)
      {
        std::stringstream stream;
        stream << "OpenProcess failed: " << (int)GetLastError();
        return stream.str();
      }
      context->hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
      if (process == NULL)
      {
        std::stringstream stream;
        stream << "CreateEvent failed: " << (int)GetLastError();
        return stream.str();
      }
      HANDLE hEvents[2] = {process, context->hStopEvent};
      DWORD waitResult = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE);
      if (waitResult == WAIT_FAILED)
      {
        std::stringstream stream;
        stream << "WaitForSingleObject failed: " << (int)GetLastError();
        return stream.str();
      } else if (waitResult == WAIT_OBJECT_0 + 1) {
        *stopped = true;
      }
      return std::string();
    }

    Value WaitForProcessToExit(CallbackInfo &info)
    {
      Env env = info.Env();

      if (info.Length() < 3) {
        TypeError::New(env, "Wrong number of arguments").ThrowAsJavaScriptException();
        return env.Null();
      }

      if (!info[0].IsNumber() || !info[1].IsBoolean() || !info[2].IsFunction()) {
        TypeError::New(env, "Wrong arguments").ThrowAsJavaScriptException();
        return env.Null();
      }

      int pid = info[0].As<Number>().Int32Value();
      bool unref = info[1].As<Boolean>();
      Function func = info[2].As<Function>();

      // Do async stuff

      my_context* context = new my_context();

      NAPI_THROW_IF_FAILED(env, napi_create_threadsafe_function(
      (napi_env) env,
      (napi_value) func,
      NULL,
      (napi_value) String::New(env, "WaitForProcessToExit"),
      0,
      1,
      NULL,
      [](napi_env env, void* finalize_data, void* finalize_hint /* Context attached to the TSFN */) {
        SetEvent(((my_context*)finalize_hint)->hStopEvent);
        ((my_context*)finalize_hint)->nativeThread.join();
        delete finalize_hint;
      }, 
      context, // Context attached to the TSFN
      [](napi_env env, napi_value js_callback, void* context, void* data) {
          std::string* error = (std::string*)data;
          // Transform native data into JS data, passing it to the provided `js_callback` (the TSFN's JavaScript function)
          napi_status status = napi_ok;

          napi_value global;
          status = napi_get_global(env, &global);
          if (status != napi_ok)
          {
            std::cout << "napi_get_global failed" << std::endl;
          }

          napi_value result;
          if (error->empty()) {
            status = napi_call_function(env, global, js_callback, 0, NULL, &result);
          } else {
            napi_value values[] = { (napi_value)Error::New(env, *error).Value() };
            status = napi_call_function(env, global, js_callback, 1, values, &result);
          }
          delete data;
          if (status != napi_ok)
          {
            std::cout << "napi_call_function failed" << std::endl;
          }
          status = napi_release_threadsafe_function(((my_context*)context)->tsfn, napi_tsfn_release);
          if (status != napi_ok)
          {
            std::cout << "napi_release_threadsafe_function failed" << std::endl;
          }
      },
      &(context->tsfn)), env.Undefined());

      context->nativeThread = std::thread([pid, context] {
        bool stopped = false;
        std::string error = WaitForPid(pid, context, &stopped);

        if (stopped) {
          return;
        }

        napi_status status = napi_call_threadsafe_function(context->tsfn, new std::string(error), napi_tsfn_blocking);
        if (status != napi_ok)
        {
          std::cout << "napi_call_threadsafe_function failed" << std::endl;
        }
      });

      if (unref) {
        NAPI_THROW_IF_FAILED(env, napi_unref_threadsafe_function((napi_env) env, context->tsfn), env.Undefined());
      }

      return env.Undefined();
    }

    Object Init(Env env, Object exports)
    {
      exports.Set(String::New(env, "WaitForProcessToExit"), Function::New(env, Winapi::WaitForProcessToExit));
      return exports;
    }

    NODE_API_MODULE(hello, Init)
...