Weak References?

# Jeff Watkins (8 years ago)

Has there been any talk about adding Weak References to EcmaScript? If
so, forgive me, because I missed it.

This would be a huge help for implementing client-side caching (and
other things, I'm sure).

-- Jeff Watkins metrocat.org

# Brendan Eich (8 years ago)

On Jul 31, 2009, at 1:18 PM, Jeff Watkins wrote:

Has there been any talk about adding Weak References to EcmaScript?
If so, forgive me, because I missed it.

Yes, there has been. Searching site:wiki.ecmascript.org "weak
reference" shows some hits. The current place to look is a stub:

strawman:strawman

Mark Miller is developing a proposal here, which I said yesterday at
the TC39 meeting "sounds great!" Look for it to show up where the
dangling

strawman:weak_references

link points from the strawman page.

# Mark S. Miller (8 years ago)

On Fri, Jul 31, 2009 at 1:49 PM, Brendan Eich <brendan at mozilla.com> wrote:

On Jul 31, 2009, at 1:18 PM, Jeff Watkins wrote:

Has there been any talk about adding Weak References to EcmaScript? If so,

forgive me, because I missed it.

Yes, there has been. Searching site:wiki.ecmascript.org "weak reference" shows some hits. The current place to look is a stub:

strawman:strawman

Mark Miller is developing a proposal here, which I said yesterday at the TC39 meeting "sounds great!" Look for it to show up where the dangling

strawman:weak_references

link points from the strawman page.

Thanks for the kind words. A first draft proposal is now there. I hope it lives up to your expectations ;).

# Isiah Meadows (2 years ago)

I'm resurrecting this 1 because I have found a use case where I needed a weak reference. Is there any chance this could get into the language?

Here's my particular use case: making a stream with a rotating destination that itself acts as a stream (snippet of that code, or what I'd like it to be).

function rotateStream(interval, format) {
    // Without the weak reference here, there's a memory leak
    const ref = new WeakReference({})
    setStream(ref.get(), format)
    setInterval(function () {
        if (ref.exists()) setStream(ref.get(), format)
        else clearInterval(this)
    }, interval)
    return ref.get()
}

This basically rotates an active stream, with the weak reference pointing to the said stream. The alternative requires explicit marking (in this case, with a close method):

function leakyRotateStream(interval, format) {
    let stream = {close() {
        clearInterval(timer)
        timer = stream = null
    }}
    const timer = setInterval(function () {
        setStream(stream, format)
    }, interval)
    setStream(stream, format)
    return stream
}

In this use case, weak references would simplify the implementation and reduce memory costs. And I would rather take advantage of the GC machinery than explicit memory management (as explicit as C), as it would be easier to collect when that's the only thing referenced.

(The setInterval callback is gc'd with all its references when it's cleared from within.)

Thankfully, I'm using Node.js, so weak 2 is an option (the only option, really). But I would definitely appreciate if it was available in JS proper, across engines. V8, SpiderMonkey, and JSC all have weak references to JS objects as part of their public API, and I would be surprised if the implementation work would be anything significant.

# Mark S. Miller (2 years ago)

On Sat, Sep 5, 2015 at 4:07 AM, Isiah Meadows <isiahmeadows at gmail.com>

wrote:

I'm resurrecting this [1] because I have found a use case where I needed a weak reference. Is there any chance this could get into the language?

I wrote the [1] strawman and continue to be positive on the idea. The strawman text is old and needs revision, as well as reconciliation with the separately written strawman at < strawman:weak_refs>. Together these

capture the needed abstract idea, but are not necessarily the best API. FWIW, the ParcPlaceSmalltalk API helped amortize some costs by providing a weak-array as the primitive, where notification includes the array index. (If someone has a link for that API, please post.)

I'll note that the pattern in your code:

    if (ref.exists()) setStream(ref.get(), format)

would not work on any previous weak-reference system, since the designated object might be collected, and ref nulled, between your ref.exists() and your ref.get(). It would happen to work in the strawman because observable gc events only happen between turns/jobs. Nevertheless, frankly, I don't like this pattern and would recommend instead:

  const strong = ref.get();
  if (strong !== undefined) { setStream(strong, format); }

On the flip side, despite my recommendation, we should certainly expect that many people will independently use your pattern. The fact that the strawman's turn atomicity will happen to make this code more reliable does corroborate the attractiveness of turn atomicity. I mention this because turn atomicity was a controversial aspect of this strawman.

To answer your question, I think there is a good chance that we could get this into the language if someone else would champion it. Although I am positive, given the scarcity of my time and where this idea falls on my priority queue, I would not find the time to be its champion. If someone else does step up as champion, I would certainly help.

Volunteers?

# Mark S. Miller (2 years ago)

On Sat, Sep 5, 2015 at 4:07 AM, Isiah Meadows <isiahmeadows at gmail.com> wrote:

Thankfully, I'm using Node.js, so weak is an option (the only option, really). But I would definitely appreciate if it was available in JS proper, across engines. V8, SpiderMonkey, and JSC all have weak references to JS objects as part of their public API, and I would be surprised if the implementation work would be anything significant.

I just skimmed it and it recapitulates the most common mistake that people make when first approaching this issue: pre-mortem finalization, like java.lang.Object.finalize(). This jumped out at me as soon as I saw Boolean weak.isNearDeath(Weakref ref) on that page. The key constraint of post-mortem finalization is that execution can only ever reach non-condemned, i.e., normally reachable, objects.

Separately, for reasons of discouraging the hazardous pattern of separating the testing and fetching that I criticized in your code, no the test-only methods should be present in the API (isDead, isNearDeath). I don't see a reason for a isWeakRef -- is there any reason for a wrapper around a weak reference not to be able to emulate a weak reference? There may be -- I am generally sympathetic to such integrity checks when some guarantee follows from the check. Is there such a guarantee in this case?

And finally of course, the whole callback approach is wrong. However, it does make me wonder about whether the strawman's callback API is now inappropriate as well. In the new post-promise world, perhaps a better API is simply to provide a query that returns one promise-for-undefined that becomes fulfilled only when the weak reference has been nulled. OTOH, this would prevent the economy of the ParcPlaceSmalltalk amortized weak-array approach. An interesting question.

# Gary Guo (2 years ago)

I think weak references are discussed before, and get postponed because it can cause information leaks between realms.

# Mark S. Miller (2 years ago)

On Sun, Sep 6, 2015 at 3:37 AM, Gary Guo <nbdd0121 at hotmail.com> wrote:

I think weak references are discussed before, and get postponed because it can cause information leaks between realms.

strawman:weak_refs#security says

To plug this leak, a weak reference created within realm A should only point weakly within realm A. When set to point at an object from another realm, it should either point strongly or throw an error, depending on whether a surprising leak or a surprising error is expected to be more inconvenient for the caller. Security demands only that such inter-realm references not point weakly.

This requires an object model change: functions are already specific to their realm of birth. This solution to the information leakage problem requires that all objects that could be weakly pointed to are already specific to the realm of their birth. Many implementations already do the necessary bookkeeping to keep track of an object's birth realm, but we would not need to mandate that bookkeeping and include it in the object model.

See esdiscuss/2013-January/028542 for a nice refinement for pointing weakly at objects from a set of realms.

Since then I have come to the opinion that an inter-realm weak reference should silently point strongly, not throw an error.

The other security issue is how to make the WeakRef constructor available at all within a realm, since it should not be available to unprivileged code within a defensive realm. At whatwg/loader#34

The loader is the third example of an (existing or proposed) primordial

object that does grant authority:

  • The global object, for which we rejected Reflect.global for the same reason.

  • The proposed WeakReference factory (e.g., WeakRef or makeWeakRef) which makes GC observable, opening a covert channel. This is why we carefully separated WeakMap from any notion of WeakRef.

  • The default loader, which causes external I/O and internally has a mutable mapping from module names to mutable module instances.

We do need a good way to provide standard access to authority-providing primordials such as these. In particular, I think all three of these are well motivated and should somehow become standard. But, because they provide authority they should be clearly separated from the means of providing abstractions that are safely sharable.

From the way the discussion proceeded, it seems an acceptable place to put these is on a global System object, i.e.,

  • System.global
  • System.defaultLoader
  • System.WeakRef
# Isiah Meadows (2 years ago)

On Sat, Sep 5, 2015, 09:48 Thomas <thomasjamesfoster at bigpond.com> wrote:

I haven't had too much of a look at the weak references chatter, but the npm module you linked to provides a callback for when the object is garbage collected. Are you using that feature? Perhaps destructors might be worth thinking about as well alongside weak references (then again, maybe this putting too much of the underlying gc implementation into the spec).

That's actually the feature I need...

# Mark S. Miller (2 years ago)

On Sun, Sep 6, 2015 at 10:32 AM, Isiah Meadows <isiahmeadows at gmail.com> wrote:

That's actually the feature I need...

Hi Isiah and Thomas, what "That"? If you mean pre-mortem finalization, like Java's Object.finalize or the cited node callbacks (whose pre-mortem nature is made clear by isNearDeath at TooTallNate/node-weak#boolean-weakisneardeathweakref-ref)

I would be very surprised if you actually need it. Could you show a very small but motivating example? I have never run across an example that couldn't be expressed at least as well with post-mortem finalization.

I ask for a "very small but motivating example" so that, hopefully, I'll be able to find the time to rewrite it using post-mortem finalization.

Note the C++ RAII is an immediate pre-mortem invocation of a destructor at a predictable and deterministic time. The general arguments against gc-driven pre-mortem finalization do not apply.

If you just mean some kind of finalization via callback, sure, that's always been part of the strawman.

# Isiah Meadows (2 years ago)

My original email featured a use case, although I had to rewrite it to use explicit garbage collection. I need a way to watch for when the object is garbage collected.

function rotateStream(interval, format) {
    const res = {}
    setStream(res, format)

    let stream = new WeakReference(res) , format)

    let timer = setInterval(() => {
        setStream(stream.get(), format)
    }, interval)

    stream.onCollect(() => {
        // reference is dead, clear this timer
        clearTimer(timer)
        // break local strong references
        timer = stream = null
    })

    // Return a strong reference
    return res
}

The complicating part is that I also need the interval callback to weakly reference the value. Otherwise, I've always got one active strong reference to the stream. This is with a listener for when the value is garbage collected.

Here's the version without a listener (it's simpler, but more crude):

function rotateStream(interval, format) {
    const res = {}
    setStream(res, format)

    let stream = new WeakReference(res)

    setInterval(function () {
        try {
            setStream(stream.get(), format)
        } catch (e) {
            // reference is dead, clear this timer
            clearInterval(this)

            // break strong reference to weak one
            stream = null
        }
    }, interval)

    // Return a strong reference
    return res
}

Sorry for the ambiguity.

(edited out a few bugs from the original)

# Mark S. Miller (2 years ago)

On Sun, Sep 6, 2015 at 8:04 PM, Isiah Meadows <isiahmeadows at gmail.com> wrote:

Since the code does not have the callback, I don't yet have enough context to understand your message. When the GC notifies that the stream is to be collected, by notifying some callback, why does that callback need to access the stream? What does the callback do with the stream during the notification that the stream is condemned?

Is the interval callback somehow related to the GC notification callback? As you see, I just don't get it yet.

# Isiah Meadows (2 years ago)

(re-post of previous message with edits, already incorporated into above)

# Mark S. Miller (2 years ago)

On Mon, Sep 7, 2015 at 10:19 AM, Isiah Meadows <isiahmeadows at gmail.com> wrote:

My original email featured a use case, although I had to rewrite it to use explicit garbage collection. I need a way to watch for when the object is garbage collected.

function rotateStream(interval, format) {
    const res = {}
    setStream(res, format)

    let stream = new WeakReference(res) , format)

Unbalanced parens.

# Mark S. Miller (2 years ago)

I also don't get the purpose of this code. What is it trying to achieve?

# Isiah Meadows (2 years ago)

It's replacing the prototype of a stream periodically to point to a new writable file output stream pointing to a new file, but I want to kill the interval timer when the object is garbage collected.

Something like this:

  1. Create new stream.
  2. Point it to a log file.
  3. Every tick on a given interval, do this: 3.1. Change the destination of the stream to a new log file.
  4. When the stream itself is GC'd, do this: 4.1. Clear the timer. 4.2. Clear all local references to both the timer and the stream.
  5. Return the stream.

The catch is that I don't want the stream strongly referenced in any local closures because it would end up never being collected - there would always be an active strong reference to it. I don't think a Reflect.onOnlyOneReferenceLeft(obj, callback) would make it into the language, so weak references are the best way to avoid this. And if I were doing this in Java or C++, I would most definitely use weak references/pointers for this use case. (This is out of the land of what automatic garbage collection can handle at this point.)

# Mark S. Miller (2 years ago)
function rotateStreamHelper(weakRes, interval, format) {
  let timer = setInterval(() => setStream(weakRes.get(), format),
                          interval);
  weakRes.register(() => clearInterval(timer));
}

function rotateStream(interval, format) {
  const res = {};
  setStream(res, format);

  rotateStreamHelper(new WeakRef(res), interval, format);

  // Return a strong reference
  return res;
}

Given strawman:weak_references does this implement your intention?

# Isiah Meadows (2 years ago)

Yes.

# Herby Vojčík (2 years ago)

A naive question: don't you get working weakref by using a WeakMap and getting and setting using the same key? A naive point of view suggests that. What am I not seeing here?

# Jordan Harband (2 years ago)

WeakMap holds the key weakly, not the value - it holds the value strongly. If you have the key such that you can use it to get at the value, you'll always have the value.

WeakRef is a container around a weakly held value, which I don't believe is possible to fully polyfill, even with WeakMap/WeakSet (because they are not enumerable).

# Uther Pendragon (6 months ago)

...does a weak map not provide that functionality?

...additionally, if writing something that sophisticated, then not managing your own instances (i.e. Relying on the language to do it for you) is the same mentality that produces horrible java code.

I also agree with what was said previously... how are weak references going to help you with ipc (inter process communication) referencing?

On Dec 28, 2016 6:00 AM, <es-discuss-request at mozilla.org> wrote:

Send es-discuss mailing list submissions to es-discuss at mozilla.org

To subscribe or unsubscribe via the World Wide Web, visit mail.mozilla.org/listinfo/es-discuss or, via email, send a message with subject or body 'help' to es-discuss-request at mozilla.org

You can reach the person managing the list at es-discuss-owner at mozilla.org

When replying, please edit your Subject line so it is more specific than "Re: Contents of es-discuss digest..."

Today's Topics:

  1. Weak Reference proposal (Isiah Meadows)
  2. Resource management (Isiah Meadows)
  3. Re: Weak Reference proposal (Steve Fink)

---------- Forwarded message ---------- From: Isiah Meadows <isiahmeadows at gmail.com>

To: "es-discuss at mozilla.org" <es-discuss at mozilla.org>

Cc: Date: Tue, 27 Dec 2016 07:45:32 -0500 Subject: Weak Reference proposal The weak reference proposal tc39/proposal-weakrefs hasn't

seen a lot of activity, and I haven't found much news elsewhere on it. What's the status on it?

Where I'm building a language-integrated process pool in Node.js, complete with shared "references" and async iterator support, I really badly need weak references to avoid otherwise inevitable memory leaks across multiple processes if the references aren't explicitly released. So far, my only option is to take a native dependency (I have no other dependencies), but that's very suboptimal, and it eliminates the possibility of porting to browsers. So I really badly need language-level weak references.


Isiah Meadows me at isiahmeadows.com

---------- Forwarded message ---------- From: Isiah Meadows <isiahmeadows at gmail.com>

To: "es-discuss at mozilla.org" <es-discuss at mozilla.org>

Cc: Date: Tue, 27 Dec 2016 11:25:11 -0500 Subject: Resource management In this GH issue in the async iteration proposal, I found that the async iteration concept itself could theoretically be used for resource management, such as in this example (copy/pasted from my initial issue):

const fsp = require("fs-promise")

async function *open(file, opts) {
    const fd = await fsp.open(file, opts)
    try { yield fd } finally { await fsp.close(fd) }
}

for await (const fd of open("/path/to/file", {mode: "r+"})) {
    const bit = await fsp.read(fd)
    // do other things...
}

// descriptor automatically closed, so no resource leaks!

It's neat and all, but we really should have something much better than that. Maybe something like this?

// Strict mode only
with (const fd = await open("/path/to/file", {mode: "r+"})) {
    const bit = await fsp.read(fd)
    // do other things...
}

(I'm not totally sure about what methods the closeable/etc. API or protocol should use.)


Isiah Meadows me at isiahmeadows.com

---------- Forwarded message ---------- From: Steve Fink <sphink at gmail.com>

To: es-discuss at mozilla.org Cc: Date: Tue, 27 Dec 2016 13:34:38 -0800 Subject: Re: Weak Reference proposal On 12/27/2016 04:45 AM, Isiah Meadows wrote:

The weak reference proposal tc39/proposal-weakrefs hasn't

seen a lot of activity, and I haven't found much news elsewhere on it. What's the status on it?

Where I'm building a language-integrated process pool in Node.js, complete with shared "references" and async iterator support, I really badly need weak references to avoid otherwise inevitable memory leaks across multiple processes if the references aren't explicitly released. So far, my only option is to take a native dependency (I have no other dependencies), but that's very suboptimal, and it eliminates the possibility of porting to browsers. So I really badly need language-level weak references.

Would weak references be enough to solve cross-process garbage collection? How would you recover a cycle of references among your processes?