4
votes

Présentation de HandleScope de l'API Node Addon (N-API)

J'ai des difficultés à comprendre comment utiliser correctement HandleScope et EscapableHandleScope . Par exemple, à partir de cet exemple de nœud :

Napi::Object CreateObject(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();
  Napi::Object obj = Napi::Object::New(env);
  obj.Set(Napi::String::New(env, "msg"), info[0].ToString());

  return obj;
}

Pourquoi avons-nous besoin de créer un nouveau HandleScope dans ce cas? Et depuis cet autre exemple :

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

  this->val_ = info[0].As<Napi::Number>().DoubleValue();
};

Pourquoi n'est-il pas nécessaire ici?

De plus, je n'ai trouvé aucun exemple utilisant EscapableHandleScope, quand est-ce nécessaire?


0 commentaires

3 Réponses :


2
votes

Pour une explication de ce que sont les HandleScopes et de leur utilisation, consultez Documentation de la V8 , par exemple pour la classe Local :

Il existe deux types de poignées: les poignées locales et persistantes.

Les poignées locales sont légères et transitoires et généralement utilisées dans opérations locales. Ils sont gérés par HandleScopes. Cela signifie qu'un HandleScope doit exister sur la pile lors de leur création et qu'ils ne sont valides qu'à l'intérieur du HandleScope actif pendant leur création. Pour passer un handle local à un HandleScope externe, un EscapableHandleScope et sa méthode Escape () doivent être utilisés.

Et pour la classe HandleScope :

Une classe allouée par pile qui régit un certain nombre de descripteurs locaux. Après une portée de handle a été créée, tous les handles locaux seront alloués dans cette portée de handle jusqu'à ce que la portée de handle soit supprimée ou une autre portée de poignée est créée. S'il existe déjà une portée de poignée et un nouveau est créé, toutes les allocations auront lieu dans le nouveau gérer la portée jusqu'à ce qu'elle soit supprimée. Après cela, de nouvelles poignées seront à nouveau être alloué dans la portée du handle d'origine.

Une fois que la portée du handle d'un handle local a été supprimée, le garbage le collecteur ne suivra plus l'objet stocké dans la poignée et pourrait le désallouer. Le comportement d'accès à un handle pour lequel le la portée du handle a été supprimée n'est pas définie.

De manière pragmatique:

  • Lors de l'appel de JavaScript vers C ++, vous aurez besoin d'au moins un HandleScope si le code C ++ crée des Local<> s. En général, un seul HandleScope est le bon nombre.
  • La création et la destruction de HandleScopes ont un coût, donc si vous avez de nombreux HandleScopes à granularité fine, vous perdez du temps. D'autre part, un HandleScope (de par sa conception, c'est son but!) Maintient en vie tous les objets (au sens GC) auxquels les poignées qu'il contient font référence, donc pour du code de très longue durée, ou des boucles avec de nombreuses itérations, vous voudrez peut-être introduire des HandleScopes de courte durée afin de libérer les objets temporaires dont vous avez terminé.
  • Comme le dit la documentation, vous avez besoin d'un EscapableHandleScope si vous souhaitez renvoyer un objet au-delà de la fin de la durée de vie de la portée.

3 commentaires

Ceci est une erreur. Un HandleScope est généralement superflu au niveau supérieur d'une méthode C ++. Pour citer la documentation NAPI: "La durée de vie de la portée par défaut est liée à la durée de vie de l'appel de méthode native. Le résultat est que, par défaut, les descripteurs restent valides et les objets associés à ces descripteurs seront conservés en direct pendant la durée de vie de l'appel de la méthode native. "


Ce n'est pas une contradiction avec ce que j'ai écrit. Vous avez toujours besoin d'un HandleScope. Si une couche d'abstraction (comme NAPI) en crée déjà implicitement une (cette "portée par défaut" que vous mentionnez), vous n'avez pas besoin d'en créer une autre manuellement.


Mais la question de l'OP portait spécifiquement sur l'API Node Addon et Napi :: HandleScope, pas sur V8 et v8 :: HandleScope.



4
votes

Ce qui suit semble être vrai pour nan, N-API et node-addon-api:

[Handle Scope] est une abstraction utilisée pour contrôler et modifier la durée de vie des objets créés dans une portée particulière. En général, les valeurs N-API sont créées dans le contexte d'une étendue de poignée. Lorsqu'une méthode native est appelée à partir de JavaScript, une portée de handle par défaut existe. Si l'utilisateur ne crée pas explicitement une nouvelle portée de handle, les valeurs N-API seront créées dans la portée de handle par défaut. Pour toute invocation de code en dehors de l'exécution d'une méthode native (par exemple, lors d'un appel de rappel de libuv), le module est requis pour créer une portée avant d'appeler toute fonction pouvant entraîner la création de JavaScript valeurs.

Source: https://nodejs.org/api/n-api.html #n_api_napi_handle_scope

Ce qui signifie dans les exemples donnés, puisque les deux attendent const Napi :: CallbackInfo & info , il est évident que les deux sont appelés directement à partir de JavaScript, ils ont donc déjà une portée fournie par le runtime JS - le complément les appels pour créer une portée ne sont nécessaires que si vous souhaitez effectuer vous-même la gestion de la mémoire, ou dans les cas où votre code s'exécute indépendamment du moteur JS (comme sur un minuteur, un rappel depuis autre chose que du code JS, etc.)


0 commentaires

2
votes

Pourquoi avons-nous besoin de créer un nouveau HandleScope dans l'extrait de code?

En fait, nous pouvons choisir de ne pas créer de nouveau HandleScope ici. Il existe une portée externe dans Node.js qui englobe nos poignées créées dans cette fonction. Mais alors toutes les poignées créées dans cette fonction vivront plus longtemps que nécessaire et géreront les ressources de coût.

Quand avons-nous besoin d'EscapableHandleScope?

Lorsqu'un descripteur d'une portée interne doit vivre au-delà de la durée de vie de cette portée. Par exemple, lors du renvoi de données nouvellement créées dans une fonction.

Référence

Intégration V8: https://v8.dev/docs/embed# poignées et ramassage des ordures

node-addon-api: https: / /github.com/nodejs/node-addon-api/blob/master/doc/object_lifetime_management.md


0 commentaires