neon-bindings / neon

Rust bindings for writing safe and fast native Node.js modules.
https://www.neon-bindings.com/
Apache License 2.0
8k stars 283 forks source link

The garbage collector thinks my object is garbage :( #1005

Open joepie91 opened 11 months ago

joepie91 commented 11 months ago

I'm currently writing Neon bindings for Veilid, an overlay network written in Rust (and exposed as a Rust library), so that I can use this network from Node.

It uses its own internal event loop (Tokio, in this case), and allows specifying of a callback that is called for every update struct; this ranges from network status to received messages to configuration stanzas.

I've wired this up fairly 1:1 (don't mind the mess, etc.), so that you can likewise specify a JS callback, and it will be invoked for every update. This all works so far.

Now the problem is that fairly shortly after connecting, the core object gets garbage-collected, triggering the finalize that shuts down the connection (without this 'clean shutdown' implementation, things seem to break considerably more loudly when GC happens). I am currently preventing this by keeping a reference to it on the JS side in an interval, but that is of course a hack, and not a real long-term solution.

Unfortunately, I've been unable to find anything in the Neon API that lets me affect how the garbage collection works, and I can't say that I'm very experienced at Rust, so I'm stumped on how to solve this 'properly', and there doesn't seem to be much documentation on dealing with the JS GC from Neon. What would be the best way to approach this with Neon?

kjvalencik commented 11 months ago

Neat project! It would also work to leave the variable in a global scope in JS where it could still be referenced--this is how I would probably do it.

If the intent is that core never gets dropped (it's a permanent global), you can also accomplish this by leaking a reference to it on the Rust side.

Root is a Rust handle to a JavaScript object. As long as the Root exists, the value won't be garbage collected. Any Rust method for leaking variables should work.

For example, putting it in a global static with OnceCell or Box::leak.