Skip to content

Memory management questions #550

Closed
Closed

Description

@marco-ms
  1. I see that SuppressDestructor is used often in the object wrap examples, like:
    https://github.com/nodejs/node-addon-api/blob/master/doc/object_wrap.md
    // Call the SuppressDestruct() method on the static data prevent the calling
    // to this destructor to reset the reference when the environment is no longer
    // available.
    constructor.SuppressDestruct();

But what exactly means this comment? I honestly don't understand if I have to manage the life cycle of any object created by this code.
2. Similarly if using a Napi::FunctionReference created with Napi::Persistent() and assigned to a class member to call a JS callback at any point in time, should the code take care of dereferencing it at some point?
https://github.com/nodejs/node-addon-api/blob/master/doc/function_reference.md

Activity

  1. KevinEady commented on Oct 2, 2019

    @KevinEady
    Contributor

    Simply put, the v8 engine works with reference counting for garbage collection. The Napi::Function func constructor function is created on the stack inside the Init method, but it stored statically in constructor. We need to tell the engine that we are still using this value even after Init finishes, which is why we store it in a reference.

    Classes that extend Reference have a destructor to call napi_delete_reference on themselves unless SupressDestruct() has been called.

  2. mhdawson commented on Oct 3, 2019

    @mhdawson
    Member

    One small correction. I don't think that V8 uses reference counting, but that does not change the comment that if we use the method across methods, that we need to either have a reference to it in a live JavaScript Object or tell V8 that it needs to be kept alive.

  3. marco-ms commented on Oct 14, 2019

    @marco-ms
    Author

    Sure I understand now, still to complete a 100% shutdown of the module, some Uninit() method should dereference all these static properties then?
    It is not clear to me when a node module goes out of scope, how exactly that works out on the allocated memory, but it could be lack of understanding of how Node modules are allocated/deallocated.

  4. gabrielschulhof commented on Oct 15, 2019

    @gabrielschulhof
    Contributor

    @marco-ms The life cycle of Node.js native addons coincides with that of the Node.js environment. This means that once a Node.js module is loaded, it is not unloaded unless the Node.js environment terminates. For the main thread, this means that modules are unloaded only when the process terminates. For worker threads, this means that modules are unloaded when the worker thread terminates. Until recently, strong references and weak references that were not garbage collected up until environment exit were not deleted. This changes with nodejs/node#28428 having landed. Now the environment will make one final pass over all remaining references and delete them before exiting.

  5. github-actions commented on Dec 17, 2020

    @github-actions
    Contributor

    This issue is stale because it has been open many days with no activity. It will be closed soon unless the stale label is removed or a comment is made.

  6. mhdawson commented on Dec 17, 2020

    @mhdawson
    Member

    I'm going to close this as there has not been a response since the last response quite some time ago. Please let us know if you think that was not the right thing to do.

  7. JCMais commented on Sep 2, 2025

    @JCMais

    @mhdawson @KevinEady sorry to revive this quite old thread, I am trying to wrap my head around NAPI now that I am trying to migrate my addon to NAPI (migrating from nan, the addon is node-libcurl).

    As an addon author, if I call SupressDestruct on the constructor of a class I am exposing from my module, which is then saved as a static property of its C++ class, do I need to do any cleanup on it? Given the above comments, I would assume no.

    Imagine I am not using env.SetInstanceData to keep track of these, so I need the static reference around in case I need to create these instances in another function.

    Another follow-up question is, does that make my addon not thread-safe? If I want to stay thread-safe, am I required to use SetInstanceData?

    And one last question/confirmation: I suppose that if I have multiple constructors (from multiple classes I am exposing using object_wrap), I need to have some struct to store all of them as the environment instance data, given that the following calls to that method will overwrite the previously set value. Is that correct?

  8. KevinEady commented on Sep 4, 2025

    @KevinEady
    Contributor

    Hi @JCMais ,

    if I call SupressDestruct on the constructor of a class I am exposing from my module, which is then saved as a static property of its C++ class, do I need to do any cleanup on it? Given the above comments, I would assume no.

    The FunctionReference will be cleaned up on environment shutdown, and you don't need to worry about it unless you have some additional finalizer that cleans up non-Node.js data (eg. some dynamically created new data to clean with delete)

    Imagine I am not using env.SetInstanceData to keep track of these, so I need the static reference around in case I need to create these instances in another function.

    Another follow-up question is, does that make my addon not thread-safe? If I want to stay thread-safe, am I required to use SetInstanceData?

    Yes, because if your addon is loaded in multiple environments, the static data set in Instance 1 will be overwritten by Instance 2, and when Instance 1 calls it, the references would be invalid.

    And one last question/confirmation: I suppose that if I have multiple constructors (from multiple classes I am exposing using object_wrap), I need to have some struct to store all of them as the environment instance data, given that the following calls to that method will overwrite the previously set value. Is that correct?

    One solution: Your addon can only have one entry point , eg.Napi::Value Init( Napi::Env env, Napi:: Object exports ). If you pass these arguments to your individual objectwrap constructors eg. MyObject1::Init( env, exports ); and MyObject2::Init( env, exports ), and these functions modify exports by adding the class declaration eg. exports.set("MyObject1", DefineClass(...) ); and exports.set("MyObject2", DefineClass(...) ); , then your addon's main Init can SetInstanceData to an ObjectReference of exports, and when you want to call a specific constructor within your c++ addon, you'll GetInstanceData<ObjectReference>() and can grab the specific property ("MyObject1" or "MyObject2") from the instance data.

    Let me know if you have any other questions. Good luck!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions