20

In JavaScript, when you do something like this,

function connect() {

    var ws = new WebSocket(url);

    ws.onmessage = function(e) {
        window.alert(e.data);
    };
}

it will work. But why is that?

You would expect that after the connect() function exits, that local ws object goes out of scope and is garbage collected. But in JavaScript, this does not happen.

So does it have something to do with the ws object having event listeners attached to it? Or what is the reason the object continues to stay in memory?

7
  • 3
    In general if a variable can be used by JS code in the future, it can't be garbage collected. That's a fundamental property of garbage collection. So, the mere fact that the object can be referenced within an event callback in the future by use of the target property logically prohibits it from being collected. There are many situations where are a local variable is not immediately eligible for collection. Commented Oct 24 at 7:15
  • @XavierPedraza I'm not sure that makes sense. If we follow the logic of "any object with an event handler cannot be deleted because the event handler can reach the object", then there should be massive memory leaks when adding and removing DOM nodes. Any element with an event handler directly attached would be reachable from there, thus removing a node will not GC. This would be the node and the entire subtree under it, since the subtree is then traversable from the parent which is traversable from the event handler. Commented Oct 24 at 7:27
  • 2
    @VLAZ This is a correct assessment. The main intent that I wish to convey with my comment is that GC scope far is more complicated than lexical scope. One other such case where this property is respected is the readystatechange event of an XHR request object, since the request is a live object that corresponds with a (typically) non-uninterruptible network request. Much like how the WebSocket object is a live object that corresponds with an open socket which will not close itself. Commented Oct 24 at 8:36
  • Why do you expect the constructor to not stuff a reference to the websocket object into some global state? Commented Oct 24 at 9:25
  • @VLAZ You totally can cause leaks with event handlers -- BUT if the event handler is the only reference to the node, and the node is the only thing that can fire events at the handler, the GC will successfully detect that this cycle is unreachable. Apparently very old versions of Internet Explorer were NOT always able to do this correctly, and could leak the node in this case. But that was apparently already old news in the answers to a question 13 years ago, so you should be safe today. Commented Oct 24 at 19:28

2 Answers 2

22

The WebSocket specification has special instructions for handling garbage collection. It's not due to the listener inherently, but that it's a WebSocket object that you're listening to.

Per the spec, a WebSocket with listeners that might still be triggered or any pending data must not be garbage collected, regardless of whether any references to it are still active.

A WebSocket object whose ready state was set to CONNECTING (0) as of the last time the event loop reached step 1 must not be garbage collected if there are any event listeners registered for open events, message events, error events, or close events.

A WebSocket object whose ready state was set to OPEN (1) as of the last time the event loop reached step 1 must not be garbage collected if there are any event listeners registered for message events, error, or close events.

A WebSocket object whose ready state was set to CLOSING (2) as of the last time the event loop reached step 1 must not be garbage collected if there are any event listeners registered for error or close events.

A WebSocket object with an established connection that has data queued to be transmitted to the network must not be garbage collected.

For your example, you have a message event listener registered, which means that if the WebSocket is connecting or open (i.e. not closed from either side), it cannot be garbage collected, which results in the behavior that you see.

Sign up to request clarification or add additional context in comments.

Comments

12

Does it have something to do with the ws object having event listeners attached to it?

Yes, precisely. As long as the socket connection is open, the runtime (browser engine) should call those event listeners when an event happens. So it needs to hold onto this event listener (or the entire WebSocket instance, as that'll technically become the .target of the event), and this reference is what keeps it from getting garbage-collected. Only when there are no listeners, or no more events (because the socket was closed), the runtime can stop holding onto the instance, since it can be certain it won't be needed any longer.

This works pretty much the same for all asynchronous web APIs, whether it's network (WebSocket, XMLHttpRequest, fetch, RTCPeerConnection, …), timers (setTimeout, setInterval, requestAnimationFrame, …), file system (FileReader, …), or the whole DOM (document) itself. The runtime has a list of "active" objects that serve as the garbage collection roots, to keep your application alive after its initial setup code has been executed.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.