'Are event listeners from an EventSource automatically garbage collected?
I've seen answers on Stackoverflow mentioning how if a DOM element goes out of scope, its event listeners are automatically garbage collected. Is that also the case with non-DOM elements, such as EventSource?
Here's an example of what I mean:
function checkStatus(events) {
return function() {
if (events.readyState === EventSource.CLOSED) {
establishSSE(); // Is this fine?
} else {
setTimeout(checkStatus(events), 500);
}
}
}
function establishSSE() {
const events = new EventSource("/sse/" + sseID);
events.addEventListener("reply", sseReply);
events.addEventListener("refresh", sseRefresh);
setTimeout(checkStatus(events), 500);
}
establishSSE();
(I know I can use the onerror event. This is just an example)
In this case, would the previous event handlers be garbage collected when establishSSE was re-ran on line 4?
Edit:
I don't understand how these values can be garbage collected. For example, the following works:
function sseSubscribe() {
let events = new EventSource("/v1/sse/mySSEID");
events.addEventListener("message", sseHeartbeat);
events.addEventListener("reply", sseReply);
}
That is, new messages still cause the message and reply handlers to run.
But events is no longer accessible from anywhere within the program. So, shouldn't it have been garbage collected? It seems like EventSource is never garbage collected (unless maybe the connection is broken?)
Edit #2:
It seems that even if the event handlers are anonymous, like so:
function sseSubscribe() {
let events = new EventSource("/v1/sse/SSEID");
events.addEventListener("message", function() {
console.log("Heartbeat");
});
}
sseSubscribe();
This still works on my end. That is, Heartbeat is being logged into the console every 30 seconds (the interval my server sends them). This is despite events immediately going out of scope and being inaccessible anywhere else in the program.
Solution 1:[1]
Answer to question title
Yes and no (from comment). The event source value can be garbage collected when it can't be accessed anywhere1. When and if it is garbage collected, any anonymous event handler functions declared as inline arguments to addEventListener will be garbage collected. Functions defined more globally that can still be reached can't be collected.
JavaScript values become eligible for garbage collection when they can't be reached in code.
The event listener functions of an
eventsvalue are recorded in a map of event listeners of the object: if the object can't be reached in code, the event listeners can't be reached either. If the only way or reaching them was through the event handler map (they were anonymous) the can be garbage collected.
Each time establishSSE is called it returns a new EventSource object - which, as an object "value", is different than the one returned the previous time it was called.
When passed to checkStatus, the value of events is held in a closure of the function passed to setTimeout. Hence the value will be "reached" when the timeout expires and the timer function executed.
One of two cases occur next
The EventSource object in
eventsis still open. A new timeout is created by callingcheckStatusagain. This creates a new closure for a new timer callback function, but with the same value of theeventsargument. Hence the (unchanged) value ofeventscan still be reached and can't be garbage collected.The event source in
eventshas been closed. In this casecheckStatuscallsestablishSSE(to reopen the source) and returns from the timer call without storing its argument anywhere or retaining it in the scope of a closure that can be executed at a later time. The closedeventsobject becomes eligible for garbage collection at this point because it can't be reached.establishSSEgoes on to create a new value held in a variable calledevents, and callscheckStatuswith it, but it's not the same value as the one going out of scope in thecheckStatuscall above it in the call stack.
However, since sseReply and sseRefresh are not nested within estabishSSE, they won't be garbage collected just because an event object value was gc'ed.
1 With an exception: blob or file object arguments passed to createObjectURL are held in memory (while network traffic retrieves their data) and must be explicitly released by calling URL.revokeObjectURL.
Answer to Edits
Updated after question edit
But
eventsis no longer accessible from anywhere within the program.
This is an invalid assumption which may have lead to the question:
The
eventsargument ofcheckStatusis in scope of the timer callback function returned bycheckStatusbecause the callback function is nested withincheckStatus.The callback function is prevented from being garbage collected because the [native code] implementation of
setTimeoutnecessarily holds a reference to it.If the value of
eventsis not in a closed state, the timer callback creates a new timer callback function by making a fresh call tocheckStatususing the existingeventsobject value, to generate a new callback function for a newsetTimeoutcall.Repeat from step 1.
It might be more straight forward and less confusing if checkStatus were used as the timer callback directly without making a delayed recursive call to itself, say by passing arguments via setTimeout:
function checkStatus(events) {
if (events.readyState === EventSource.CLOSED) {
establishSSE(); // create a new EventSource object
return; // the actual argument object value is now inaccessible
// and becomes eligble for garbage collection.
}
setTimeout( checkStatus, 500, events);
}
If implementing the change above, establishSS would also need to pass checkStatus to setTimeout as its first argument without calling it first:
function establishSSE() {
const events = new EventSource("/sse/" + sseID);
events.addEventListener("reply", sseReply);
events.addEventListener("refresh", sseRefresh);
setTimeout(checkStatus, 500, events);
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
