-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
GC behaviour in sync execution #4
Comments
I was actually unaware of this behavior, this is quite irritating that WeakRef behaves this way, as the documentation seems like it clearly indicates that users should be aware of the non-determinism of GC.
|
Yeah, the thing is - I don't want my code to rely on any specific GC behaviour (as in use finalizers as something similar to deconstrucors etc), but I do kind of need to make sure potentially huge list of objects to be just possible to GC (on memory pressure etc). As the thing I'm working on strictly relate to just getting OOM situations.
Nope - you can swap from
Yup, that's the current plan right now (hello
For me it's mostly about reads right now (cases like iterating on documents to create reverse lookup tables / indexes after documents were already written + I know what fields to index on as those are not hardcoded, but computed/discovered later so I can't generate indexes at the same time as I do my writes). One thing I thought of that is also less than ideal, is to try to make my reads use "cached" As I mentioned in general I do want to use In any case - thanks for very quick reply. I will think on it some more. |
Actually, I have some potentially good news: V8 exposes a ClearKeptObjects() method, which does basically what you would expect, synchronously clears kept objects. And I could expose this in JS. And there is also a --harmony-weak-refs-with-cleanup-some that allows for forced/synchronous execution of the finalization registry callbacks. As a proof of concept, this (completely synchronous) function will successfully clear the weakref, return undefined from the deref() call, and execute the finalization registry callback, if run with
Ultimately, I think would allow you to periodically clear the kept objects and trigger finalization registry callbacks without ever needing to wait for event turns. I inclined, at least initially, to just add clearKeptObjects to lmdb-js, so I don't have to create a native add-on just for this one function in weak-lru-cache. And there are a couple ways this could be implemented:
|
Wow, you did dive deep there - I really appreciate that! I added some comments to just show how I understood your above snippet - is that correct? (can't really run this myself) (function() {
let finalized
let fr = new FinalizationRegistry(() => {
finalized = true;
});
function makeRef() {
let o = {hi:'hello'}
fr.register(o, 'test')
return new WeakRef(o);
}
let w = makeRef();
clearKeptObjects();
gc(); // after GC our `{hi:'hello'}` is no longer in the heap, but finalizer wasn't/might not have been called (?)
console.log(!!w.deref());
fr.cleanupSome(); // this triggers finalizer (in `weak-lru-cache` cleanup `Map<string,WeakRef>` map from entries that no longer are in the heap)
console.log({finalized})
})()
This does sound perfect to me (too good to be true?). Selflishly I would appreciate exposing it (lmdb is more than fine for me as that's "top-level" lib I work with) as I am already aware of this WeakRef behavior and being able to make use of Some default calls (I guess with default settings like you proposed) do however make sense for consumers in general (as abstracting "low level" details like that is one of the reasons we do use libs at all :) ) as long as consumer has a chance to adjust setting (or straight disable and handle it themselves if I do wonder however if doing something like that (exposing |
Oh and also - from my understanding |
API changes are possible. However, this is actually part of the V8 api, not Node's directly, and Node's traditional add-on API is based on giving direct access to V8 objects and V8 itself. Using the NAN layer is encouraged, but enforcing that would be a major change that would break a lot of stuff. It is certainly possible that there could be a other ramifications that I am not aware. The docs say you aren't supposed to do this, but I presume that's just to conform to the ES spec, which we are intentionally defying (because we disagree with it):
Yeah, that's my expectation as well, but will run some quick benchmarks when I get a chance.
Yes, that's correct, finalizer callbacks are queued and not immediately executed during GC (which I believe can even happen from a separate thread). There is actually a TC39/ES proposal for cleanupSome, which is what V8 implements with the --harmony-weak-refs-with-cleanup-some flag: https://github.com/tc39/proposal-cleanup-some The proposal kinda makes it sound like this function should also clear kept objects, and this would be the one solution for our issue, but I didn't observe that in my testing, I still had to manually clear kept objects, and then this function just enabled running the finalizers. Of course it is possible that this API will change as well. Running the finalizer callbacks isn't strictly as essential as clearing the kept objects because presumably the kept objects are the biggest memory holder, and once kept objects are cleared and GC'ed it is feasible to occasionally poll weak references to find cleared references and remove the corresponding Map entries. |
Some quick tests show that not only is cheap (baseline time for calling
This takes about 110ms to execute (110ns per cycle), whereas without clearKeptObjects it takes about 500-600ms. Anyway, I will try to get this in lmdb-js and published soon for you to try out. |
I must say I enjoyed reading this thread! Thanks to both of you for exploring this stuff 👍 I was pretty sure that the only option here is to stick with periodic |
Published lmdb-js v2.1.7 which now exports a clearKeptObjects() function. I haven't done anything else with any automated/periodic clearing, but this should give you access to the main function for manually clearing the kept objects periodically on your own. Good luck, and let me know if it works for you :). |
Would have reply earlier but I had to migrate from I managed to keep my completely sync iterations as they were before and it worked in env with purposefully limited memory. I put ---edit |
Great to hear.
Actually, I think this is a bug/regression in lmdb-js. lmdb-js is supposed to compute an expiration priority based on entry size that should cause larger entries to expire from the LFRU expirer much quicker, but looks like the value isn't getting passed through. This should be an easy fix. That being said, probably still worthwhile to have a little smaller lru table/cache size with large entries/docs. |
Hi Kris, first of all huge thanks for this lib (and lmdb-store/lmdb-js).
This is not really a bug report or feature request, more of a question(s)
I've been debugging some unexpected (to me) interaction of
WeakRef
and sync execution. Primarly that as soon as soon as I usecache
setting withlmdb-store
(even withexpirer: false
) it seems like newly created WeakRefs (or dereferenced already existing ones) prevent target objects from being GCed within same tick (doesn't happen whencache
is disabled, but I would like to avoid going down this route if at all possible).During my checks I did find that unfortunately this behaviour of
WeakRef
is actually in the spec ( https://tc39.es/ecma262/#sec-weak-ref-objects ). In particularAddToKeptObjects
/ClearKeptObjects
are of importance here. I had quite a difficult time finding rationale for this and only thing I found was this comment https://bugs.chromium.org/p/v8/issues/detail?id=12145#c8Which is quite frustrating (if it's only reason for that behaviour) as I (or this lib) really have no intention on abusing it in such way.
Just to illustrate how this can happen (at the
debugger
breakpoint I was getting heap snapshots which does trigger GC):So, finally my question(s):
The text was updated successfully, but these errors were encountered: