Skip to content
This repository has been archived by the owner on Jan 25, 2022. It is now read-only.

Q: Why not to add getKeys() : array instead? #217

Open
c-smile opened this issue Mar 24, 2021 · 5 comments
Open

Q: Why not to add getKeys() : array instead? #217

c-smile opened this issue Mar 24, 2021 · 5 comments

Comments

@c-smile
Copy link

c-smile commented Mar 24, 2021

Referring to this use case:
https://github.com/tc39/proposal-weakrefs#iterable-weakmaps

If we would have WeakMap.prototype.getEntries() method that will return current set of valid entries {key,value} then enumeration will be trivial:

for(let entry of weakMap.getEntries())
   ...

getEntries will return current WeakMap snapshot - not an iterator.

That is a) dead simple and b) will be sufficient for most cases.

@ljharb
Copy link
Member

ljharb commented Mar 24, 2021

WeakMaps must not be enumerable in any way - the objects held as keys must not be reachable unless you already have the object.

@c-smile
Copy link
Author

c-smile commented Mar 25, 2021

the objects held as keys must not be reachable ...

Ok, but why? That's the first question.

And the second, consider PubSub scenario:

let map = new WeakMap();

export function subscribe(element,signal) {
    map.set(element,signal);
}

export function notify(bySignal) {
   for(let [element,signal] of map.getEntries())
      if( signal == bySignal )
        element[signal]();
}

By using WeakMap here I want to avoid need for unsubscribe (not always feasible).

How to do that otherwise (without getEntries) ?

@jridgewell
Copy link
Member

b) will be sufficient for most cases.

I don't think that's the case, unfortunately. Being able to associate a string key to a weakly held value is core need here, and it's not possible via a WeakMap.

Ok, but why? That's the first question.

Technically, it'd be possible, but it'd probably cause WeakMaps to be even slower than they already are. Allowing iteration by default would mean any holder of the WeakMap can now suddenly resurrect an otherwise dead object for as long as this snapshot is alive.

How to do that otherwise (without getEntries) ?

You can build getEntries on top of a regular Map with WeakRef.

@hotsphink
Copy link
Contributor

the objects held as keys must not be reachable ...

Ok, but why? That's the first question.

It makes garbage collection visible. You can tell whether something has been collected or not, which should be an internal detail of the engine. Exposing it has two main problems. First, it's a side channel that makes it possible to exfiltrate information from higher privilege code. Second, and more practically, it means your code will behave differently depending on the JS engine, version, other stuff that's running, time you've been idle in an interactive embedding like a browser, etc. It makes it very easy for code that works in a development environment to break in a production environment. If such code is in a popular enough site, framework, or library, it could also prevent future GC optimizations.

WeakMaps are specifically designed to keep GC invisible. The implementation is called "ephemerons", though note that the Wikipedia page isn't very good. With a WeakMap, the GC can't collect an entry until you no longer have a way of looking it up (ie, either the key or the WeakMap is unreachable). If you could enumerate the keys, you would have a way of looking them up.

WeakRefs have all of the problems I listed above, but were deemed valuable enough to add to the language anyway. At least with WeakRef and FinalizationRegistry, you know you're making things nondeterministic. Also, the spec went to some length to make it less nondeterministic than a naive version would be.

And the second, consider PubSub scenario:

let map = new WeakMap();

export function subscribe(element,signal) {
    map.set(element,signal);
}

export function notify(bySignal) {
   for(let [element,signal] of map.getEntries())
      if( signal == bySignal )
        element[signal]();
}

By using WeakMap here I want to avoid need for unsubscribe (not always feasible).

How to do that otherwise (without getEntries) ?

For full correctness, you don't. The GC is not guaranteed to collect anything, ever. And there are realistic scenarios where it won't. Also, the engine's internal notion of reachability does not necessarily match your application's. On one hand, it might cache a reference that is not visible to you -- JIT code could hold onto a key in an IC, for example. On the other hand, it might not see something that is still available in your embedding -- a reference to a DOM node, say, that you could re-retrieve and then may or may not be missing from your WeakMap. (I think there's magic to prevent this specific case from happening in the browser.)

If you're willing to ignore the semantic problem, you can do it with WeakRef/FinalizationRegistry. You will then get the nondeterminism -- if unsubscription is visible in any way, then you'll find that the timing of it is random.

To be fully correct, you really have to use your application's definition of when a given element is "active"/"alive" or not. Which means doing explicit unsubscribes, which are admittedly a pain. But it's the only way to be 100% correct.

Breaking WeakMap by allowing its keys to be retrieved would be great for some uses (albeit with nondeterminism), but make them useless for the core set of problems they were introduced to solve.

@FUDCo
Copy link
Contributor

FUDCo commented Mar 25, 2021 via email

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants