Skip to content

The garbage collector does not collect objects while executing in a loop #1636

Closed

Description

@Keller18306

The garbage collector does not collect objects while executing in a loop, whether it is while(true) or for()

This is a minimal example. In fact, I have an eternal while(true) in my project, which calls the synchronous transaction code to the database. However, during the cycle, objects are not deleted at all, which completely eats up the server's memory. There is an option to wrap the code in an asynchronous function and write await at the end of the cycle body (for example, waiting for a promise for 100 ms) and then the collector is triggered, but why doesn't it trigger on global.gc() then?

test.h

#include <napi.h>

class MDBX_Test : public Napi::ObjectWrap<MDBX_Test> {
  public:
	Napi::Value Test(const Napi::CallbackInfo &info);
	void Finalize(Napi::BasicEnv env);

	MDBX_Test(const Napi::CallbackInfo &info);
	~MDBX_Test();
	static void Init(Napi::Env env, Napi::Object exports);
};

test.cpp

#include "test.h"
#include <chrono>
#include <thread>

MDBX_Test::MDBX_Test(const Napi::CallbackInfo &info) : ObjectWrap<MDBX_Test>(info) {}

void MDBX_Test::Init(Napi::Env env, Napi::Object exports) {
	Napi::Function func = DefineClass(env, "MDBXTest",
		{
			InstanceMethod("test", &MDBX_Test::Test),
		});

	exports.Set("Test", func);
}

MDBX_Test::~MDBX_Test() { printf("[MDBX_Test::~MDBX_Test()]"); }

void MDBX_Test::Finalize(Napi::BasicEnv env) { printf("[MDBX_Test::Finalize(Napi::BasicEnv env)]"); }

Napi::Value MDBX_Test::Test(const Napi::CallbackInfo &info) {
	std::this_thread::sleep_for(std::chrono::seconds(1));
	return Napi::Boolean::New(info.Env(), true);
}

test.js

const mdbx = require('./build/Debug/node-mdbx.node');

for (let i = 0; i < 3; i++) {
    const a = new mdbx.Test();
    console.log(a);
    console.log(a.test());
    global.gc();
}

Output

root@debian:~/node-mdbx# node --expose-gc test.js
MDBXTest {}
true
MDBXTest {}
true
MDBXTest {}
true
[MDBX_Test::Finalize(Napi::BasicEnv env)][MDBX_Test::~MDBX_Test()][MDBX_Test::Finalize(Napi::BasicEnv env)][MDBX_Test::~MDBX_Test()][MDBX_Test::Finalize(Napi::BasicEnv env)][MDBX_Test::~MDBX_Test()]

Activity

  1. KevinEady commented on Feb 9, 2025

    @KevinEady
    Contributor

    Hi @Keller18306 ,

    The default implementation of ObjectWrap defers finalization of objects to a second-pass to allow running JavaScript code during finalization.

    You have two options:

    1. Define preprocessor directive NAPI_EXPERIMENTAL and not NODE_API_EXPERIMENTAL_BASIC_ENV_OPT_OUT
    2. Allow the while-loop to break out of the current JavaScript event loop iteration, by making the function async and putting some sorta await ... in the loop.

    Please take a look at https://nodejs.org/api/n-api.html#node_api_basic_finalize for more information.

    Adding #define NAPI_EXPERIMENTAL to the top of your file, and changing your loop to unset a:

    for (let i = 0; i < 3; i++) {
        let a = new mdbx.Test();
        console.log(a);
        console.log(a.test());
        a = null;
        global.gc();
    }
    MDBXTest {}
    true
    [MDBX_Test::Finalize(Napi::BasicEnv env)]
    [MDBX_Test::~MDBX_Test()]
    MDBXTest {}
    true
    [MDBX_Test::Finalize(Napi::BasicEnv env)]
    [MDBX_Test::~MDBX_Test()]
    MDBXTest {}
    true
    [MDBX_Test::Finalize(Napi::BasicEnv env)]
    [MDBX_Test::~MDBX_Test()]
    

    Let us know if you have any other questions!

  2. Keller18306 commented on Feb 9, 2025

    @Keller18306
    Author

    Thank you, both options work... Although the first option is not very convenient for practical use... And the second one looks too "redundant" and not obvious. Maybe in the future some other method is planned, for example, when implementing your own close() method in c++ code, directly call garbage collection, without --expose-gc?

  3. KevinEady commented on Feb 10, 2025

    @KevinEady
    Contributor

    Thank you for your feedback with the NAPI_EXPERIMENTAL fix. This gives us additional confidence that the implementation works for the users.

    Maybe in the future some other method is planned, ...

    Eventually, the NAPI_EXPERIMENTAL behavior will not be experimental, and will become the default [edit to add] based on the target Node-API version you target during build. Until then, you must opt-in to the upcoming behavior with the define.

  4. moved this from Need Triage to In Progress in Node-API Team Projecton Feb 14, 2025
  5. changed the title [-]The garbage collector does not collect objects while executing in a loop[/-] [+]WAITING ON experimental feature promotion - The garbage collector does not collect objects while executing in a loop[/+] on Feb 14, 2025
  6. changed the title [-]WAITING ON experimental feature promotion - The garbage collector does not collect objects while executing in a loop[/-] [+]The garbage collector does not collect objects while executing in a loop[/+] on Feb 14, 2025
  7. mhdawson commented on Feb 14, 2025

    @mhdawson
    Member

    We've recorded that you are waiting on the API to be promoted in a tracking issue for the next Node-API version. Seems like the question has been answered, closing.

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

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions