You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
N-API: memory leak with call to napi_create_function #28988
Platform: Linux atc 5.1.20-300.fc30.x86_64 #1 SMP Fri Jul 26 15:03:11 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
Subsystem:
This is again with respect to the node-oracledb driver (Oracle Database driver for Node.js). I have adjusted the code once again to eliminate any requirement for an Oracle database. I'll attach the test case shortly after this issue is created.
For Oracle Objects I have created a class dynamically at run-time based on the name. This can be seen in the test case as a call to conn.getDbObjectClass(). Internally, the call to conn.getDbObjectClass() calls into a C module which then makes a call to conn._getDbObjectClassJS(). This function then builds the class and stores the result in a cache on the connection called _dbObjectClasses.
When the connection is closed by a call to conn.close(), I have to iterate over the entries in the cache and deliberately break the prototype chain; otherwise, the classes that are built are never garbage collected.
But, even if I do that and the classes are garbage collected, there is still a memory leak. The pattern shows that there are periods of relatively stable memory usage followed by jumps in memory usage. The periods of stable memory usage grow longer as the number of iterations increases but the jump in memory also increases. It would seem that a list of some kind is being populated with intermittent, increasing size allocation. Looking at the heap memory in the Chrome development tools indicates that the memory is all found in noscript_shared_function_infos -- but I'm not sure what that means! Interestingly, though, if I remove the call to napi_define_properties in njsDbObjectType_populate(), the memory leak goes away.
I'm not sure if this is related to the fix introduced in PR #27805 or not, but version 12.0 is the first version that has this issue. Prior to 12.0 the code suffers from the memory leak that that issue corrected.
I hope this is sufficient to discover the source of this issue. Let me know if you need anything else!
Interestingly, a call to napi_define_properties() is not required. A simple call to napi_create_function(), even if that object is never tied to any other object, is all that is required.
Here is a much simplified package that contains only a single method called leak_memory() and all it does is calls napi_create_function(). It leaks memory quite rapidly!
You can wait until it finishes or stop it after a few hundred thousand iterations and then examine the file stats.txt, or you can look at the output from the top command.
Here is a much simplified package that contains only a single method called leak_memory() and all it does is calls napi_create_function(). It leaks memory quite rapidly!
You can wait until it finishes or stop it after a few hundred thousand iterations and then examine the file stats.txt, or you can look at the output from the top command.
Major objects that retained in heap in this case were TickObject which would gets collected once they was been ran.
With changing the while loop in function run to looping with setInterval, the seemingly major leaking behavior had just gone.
Though there still exists a minor heap size increasing on noscript_shared_function_infos.
and I didn't see a whole lot of difference between the two. I see a leak of about 12 bytes/iteration but there are periods of stability followed by a jump. The periods of stability lengthen but the jumps also get bigger. 12 bytes doesn't seem like a lot....until you realize that if you build a class with a number of getter/setter and specialized functions that adds up quickly. If you adjust the call to the leak_memory function to do the following instead:
for(letj=0;j<20;j++){issue_28988.leak_memory();}
then the leak jumps to about 250 bytes/iteration. This quite closely matches the behaviour I was seeing with the full test case which probably calls napi_create_function() internally about 20 times.
I seem to remember @gabrielschulhof mentioning that V8 never collects functions but I'll have to confirm if that is the case and if we have it documented somewhere.
I think @gabrielschulhof is still away for another week and I've not been able to find documentation or earlier issues where that was discussed @hashseed can you confirm one way or the other if V8 collects functions created in native code?
V8 has something called “template instantiations cache” whose entries refer back to the Function instances indexed by their FunctionTemplates – I could imagine the underlying issue here is that those entries are not cleared when the FunctionTemplate is garbage collected. But this may require a V8 person to confirm and fix.
Function template::New results in cached templates, whereas Function::New creates an uncached template.
Cached here doesn't mean it's a weak pointer: in the browser for correctness they /need/ to be unique instances that have identity. Since they are mutable objects like any other it's observable, and the fact that they aren't instantiated eagerly is simply a memory and performance optimization. So the cache isn't there (just) for performance, it's actually needed for correctness in the browser.
This cached way of using templates is possibly not what you want. Doesn't Function::New fit your usecase?
The V8 test did not seem to leak, whereas the Node.js addon ran out of heap space very quickly.
Now, it's true that I ran the V8 test on its master, rather than on whatever version of V8 we have in Node.js master, so the leak may have been fixed. Alternatively, it may be that the settings with which we compile and launch V8 may be causing it to leak. I'll try and figure out how to make sure that the version of V8 I'm building separately is exactly the same as the version of V8 in the Node.js master source tree.
@mhdawson looks like this is a problem that was fixed. Looking at deps/v8/ChangeLog, I found that the version in master is 2.7.299. When I checked out that version of V8 in its tree and re-ran the cctest, it also ran out of heap space. So, looks like this'll be fixed when we upgrade past the fix in V8.
@gabrielf which version of V8 will the change be in? We are already at 7.7 in 12.x but I'm not sure I've 7.8 will be backported (@targos can you comment) and due to having upped the mininum OSX devtools level to accommodate later V8 versions in 13.x my guess is that 7.9 and later most likely will not be backported.
Talked to @gabrielschulhof, sounds like the fix is in 7.8 and @targos and @addaleax are going to try to land that in 12.x #29694 (comment). If that does not pan out we may need to look at floating the relevant fix.
@targos is there any chance that 7.8 will go into 12.x as a SemVer minor or do we need to look to see if we can float the change that @gabrielschulhof mentioned fixes this?
Since v10.x is going into maintenance it's unlikely that a new major version of V8 will be backported to it. So, this memory leak will go away completely only when we drop support for v10.x.
@gabrielschulhof I wonder if we should add a test that validates this is not regressed at some point or do we think it should be covered by the testing on the V8 side?
It looks like this is complete to me since 10.x is out of service. I'm going to close the issue. Is you feel that was not the right thing to do please re-open.
Activity
anthony-tuininga commentedon Aug 6, 2019
The test case can be seen in the zip file. Included is a sample
stats.txtthat demonstrates the memory leak.Run the tests with
anthony-tuininga commentedon Aug 6, 2019
I just tried this with Node.js 12.8 (just released) and the issue is still there, unfortunately.
[-]N-API: memory leak with descriptors[/-][+]N-API: memory leak with call to napi_create_function[/+]anthony-tuininga commentedon Aug 9, 2019
Interestingly, a call to
napi_define_properties()is not required. A simple call tonapi_create_function(), even if that object is never tied to any other object, is all that is required.anthony-tuininga commentedon Aug 9, 2019
Here is a much simplified package that contains only a single method called leak_memory() and all it does is calls napi_create_function(). It leaks memory quite rapidly!
issue_28988_simplified.zip
Run it with
You can wait until it finishes or stop it after a few hundred thousand iterations and then examine the file
stats.txt, or you can look at the output from thetopcommand.legendecas commentedon Aug 11, 2019
Major objects that retained in heap in this case were
TickObjectwhich would gets collected once they was been ran.With changing the while loop in function
runto looping withsetInterval, the seemingly major leaking behavior had just gone.Though there still exists a minor heap size increasing on
noscript_shared_function_infos.anthony-tuininga commentedon Aug 12, 2019
Which version did you use? I am using Node.js 12.8. I modified demo.js to look like this:
and I didn't see a whole lot of difference between the two. I see a leak of about 12 bytes/iteration but there are periods of stability followed by a jump. The periods of stability lengthen but the jumps also get bigger. 12 bytes doesn't seem like a lot....until you realize that if you build a class with a number of getter/setter and specialized functions that adds up quickly. If you adjust the call to the leak_memory function to do the following instead:
then the leak jumps to about 250 bytes/iteration. This quite closely matches the behaviour I was seeing with the full test case which probably calls
napi_create_function()internally about 20 times.mhdawson commentedon Aug 14, 2019
I seem to remember @gabrielschulhof mentioning that V8 never collects functions but I'll have to confirm if that is the case and if we have it documented somewhere.
EDIT: functions created in native code that is.
mhdawson commentedon Aug 14, 2019
I think @gabrielschulhof is still away for another week and I've not been able to find documentation or earlier issues where that was discussed @hashseed can you confirm one way or the other if V8 collects functions created in native code?
addaleax commentedon Aug 14, 2019
V8 has something called “template instantiations cache” whose entries refer back to the
Functioninstances indexed by theirFunctionTemplates – I could imagine the underlying issue here is that those entries are not cleared when theFunctionTemplateis garbage collected. But this may require a V8 person to confirm and fix.hashseed commentedon Aug 15, 2019
@verwaest knows the details here.
Yeah I think we keep instantiated functions around for faster instantiations.
mhdawson commentedon Aug 26, 2019
@verwaest can you confirm
verwaest commentedon Aug 26, 2019
Function template::New results in cached templates, whereas Function::New creates an uncached template.
Cached here doesn't mean it's a weak pointer: in the browser for correctness they /need/ to be unique instances that have identity. Since they are mutable objects like any other it's observable, and the fact that they aren't instantiated eagerly is simply a memory and performance optimization. So the cache isn't there (just) for performance, it's actually needed for correctness in the browser.
This cached way of using templates is possibly not what you want. Doesn't Function::New fit your usecase?
anthony-tuininga commentedon Aug 26, 2019
I am not using the C++ API directly, but indirectly through N-API. Is there a way to create a function with Function::New() using N-API?
11 remaining items
mhdawson commentedon Sep 4, 2019
@verwaest, @hashseed can you help confirm if what I've seen matches expected behaviour or not?
gabrielschulhof commentedon Sep 24, 2019
@mhdawson I ran the following code as both a Node.js addon and as a V8 cctest:
I wrote the following V8 test:
I also wrote a corresponding Node.js native addon test:
I ran the V8 test with
and the Node.js addon with
The V8 test did not seem to leak, whereas the Node.js addon ran out of heap space very quickly.
Now, it's true that I ran the V8 test on its master, rather than on whatever version of V8 we have in Node.js master, so the leak may have been fixed. Alternatively, it may be that the settings with which we compile and launch V8 may be causing it to leak. I'll try and figure out how to make sure that the version of V8 I'm building separately is exactly the same as the version of V8 in the Node.js master source tree.
gabrielschulhof commentedon Sep 24, 2019
@mhdawson looks like this is a problem that was fixed. Looking at deps/v8/ChangeLog, I found that the version in master is 2.7.299. When I checked out that version of V8 in its tree and re-ran the cctest, it also ran out of heap space. So, looks like this'll be fixed when we upgrade past the fix in V8.
Alxspb commentedon Sep 24, 2019
Check that bug https://bugs.chromium.org/p/v8/issues/detail?id=9674
mhdawson commentedon Sep 30, 2019
@gabrielf which version of V8 will the change be in? We are already at 7.7 in 12.x but I'm not sure I've 7.8 will be backported (@targos can you comment) and due to having upped the mininum OSX devtools level to accommodate later V8 versions in 13.x my guess is that 7.9 and later most likely will not be backported.
We may need to float the fix.
mhdawson commentedon Oct 10, 2019
Talked to @gabrielschulhof, sounds like the fix is in 7.8 and @targos and @addaleax are going to try to land that in 12.x #29694 (comment). If that does not pan out we may need to look at floating the relevant fix.
mhdawson commentedon Oct 24, 2019
@targos is there any chance that 7.8 will go into 12.x as a SemVer minor or do we need to look to see if we can float the change that @gabrielschulhof mentioned fixes this?
targos commentedon Oct 25, 2019
Maybe, if someone can help to do the ABI compat patch
targos commentedon Oct 25, 2019
I opened #30109 to make it more visible
gabrielschulhof commentedon Apr 17, 2020
Current state:
Since v10.x is going into maintenance it's unlikely that a new major version of V8 will be backported to it. So, this memory leak will go away completely only when we drop support for v10.x.
mhdawson commentedon Apr 17, 2020
@gabrielschulhof I wonder if we should add a test that validates this is not regressed at some point or do we think it should be covered by the testing on the V8 side?
gabrielschulhof commentedon May 1, 2020
@mhdawson IMO this is internal to V8.
mhdawson commentedon May 4, 2020
@gabrielschulhof thanks.
mhdawson commentedon Feb 17, 2022
It looks like this is complete to me since 10.x is out of service. I'm going to close the issue. Is you feel that was not the right thing to do please re-open.