Reputation: 1403
Our home PC has come to a halt all of a sudden unexpectedly numerous times in the past couple days. It takes the UI about half an hour to recover, and sometime a hard reboot is needed. Opening Task Manager reveals that one particular PID is using about 96% of the PC's RAM, and using Chrome Task Manager, I have determined it to be a webpage I made.
I don't know much about memory leaks, as I've only heard of them in passing, but I am almost certain that my Javascript code has somehow caused a memory leak. Is there a best practice to modify code to stop memory leaks? I suspect the leak is being caused by my response()
function.
Before this question is downvoted for being a duplicate or providing no research, this other Stack Overflow question has led me to believe that case "12124561414":
could be the culprit; however, I have not been able to test this. Furthermore, the loop contains within this function is not executing, so I am uncertain of whether or not that is truly the culprit, since I am not aware of any built-in JS mechanisms to isolate memory leaks.
Is there any way to run indefinite loops without causing memory leaks? (if that is indeed why I am experiencing huge memory leaks) Or some way to release these resources that are apparently not being released? My goal is to have the busy tone just run over and over again for eternity until the page is refreshed, but I see no reason that should crash the computer.
The complete JS code for this webpage is below:
var availableNumbers = ["0", "911", "1 (847) 765-1008" , "867-5309", "1 (212) 456-1414", "555-1212", "555-5555"];
function numberSuggestion() {
var randomNumber = Math.floor(Math.random() * (availableNumbers.length));
var suggestedNumber = availableNumbers[randomNumber];
document.getElementById("suggestion").innerHTML = "How about dialing <strong id='suggestedTelephoneNumber'>" + suggestedNumber + "</strong>? Don't like this number? Click the button above again!";
}
var dialTone;
function offHook() {
document.getElementById("WE2500").style.display = "none";
document.getElementById("dialPad").style.display = "block";
dialTone = new Audio('dialTone.m4a');
dialTone.play();
}
var number = "";
var timeout;
function numberDial() {
if (dialTone) {
dialTone.pause();
dialTone.currentTime = 0;
}
clearTimeout(timeout);
timeout = setTimeout(dial, 2000);
}
function dial1() {
numberDial();
number = number + "1";
var tone1 = new Audio('DTMF-1.wav');
tone1.play();
}
function dial2() {
numberDial();
number = number + "2";
var tone2 = new Audio('DTMF-2.wav');
tone2.play();
}
function dial3() {
numberDial();
number = number + "3";
var tone3 = new Audio('DTMF-3.wav');
tone3.play();
}
function dial4() {
numberDial();
number = number + "4";
var tone4 = new Audio('DTMF-5.wav');
tone4.play();
}
function dial5() {
numberDial();
number = number + "5";
var tone5 = new Audio('DTMF-5.wav');
tone5.play();
}
function dial6() {
numberDial();
number = number + "6";
var tone6 = new Audio('DTMF-6.wav');
tone6.play();
}
function dial7() {
numberDial();
number = number + "7";
var tone7 = new Audio('DTMF-7.wav');
tone7.play();
}
function dial8() {
numberDial();
number = number + "8";
var tone8 = new Audio('DTMF-8.wav');
tone8.play();
}
function dial9() {
numberDial();
number = number + "9";
var tone9 = new Audio('DTMF-9.wav');
tone9.play();
}
function dial0() {
numberDial();
number = number + "0";
var tone0 = new Audio('DTMF-0.wav');
tone0.play();
}
function dialStar() {
numberDial();
number = number + "*";
var toneStar = new Audio('DTMF-star.wav');
toneStar.play();
}
function dialPound() {
numberDial();
number = number + "#";
var tonePound = new Audio('DTMF-pound.wav');
tonePound.play();
}
var ringingTone = new Audio('DTMF-ringbackTone.mp3');
var timesRung = 0;
function dial() {
function ring() {
ringingTone.play();
timesRung++;
if (timesRung > 1) {
setTimeout(response, 700);
}
}
ring();
setTimeout(ring, 4000);
}
function response() {
switch(number) {
case "0":
var operatorPickup = new Audio('OperatorAnswer.wav');
operatorPickup.addEventListener("ended", function(){
number = prompt("Operator, your number please? (Numbers only; enter 'police' for police and emergency)");
if (number == null) {
number = "0";
}
operatorPutCallThrough();
});
operatorPickup.play();
break;
case "911":
var pickup911 = new Audio('911-xxx-fleet.mp3');
pickup911.play();
break;
case "18477651008":
var pickupMCI = new Audio('MCI.wav');
pickupMCI.play();
break;
case "8675309":
var pickup8675309 = new Audio('discoornis-bell-f1.mp3');
pickup8675309.play();
break;
case "12124561414":
var pickupBusy = new Audio('tele-busy.wav');
console.log(number);
while (number == "12124561414") {
pickupBusy.play();
pickupBusy.currentTime = 0;
}
break;
case "5551212":
var pickupLocalInfo = new Audio('tele-busy.wav');
pickupLocalInfo.play();
break;
case "5555555":
var pickup5555555 = new Audio('timeout-bell-f1.mp3');
pickup5555555.play();
break;
case "police":
break;
default:
var pickupDefault = new Audio('ldcircuits-bell-f1.mp3');
pickupDefault.play();
}
}
function operatorPutCallThrough() {
alert("One moment please, ringing the line now.");
if (number == "police") {
var operatorPoliceTransfer = new Audio('OperatorPolice.wav');
operatorPoliceTransfer.play();
}
response();
}
My hunch is this might be a better approach, but I'm a little scared to test it in case the PC crashes again.
case "12124561414":
busy();
break;
case "5551212":
busy();
break;
case "5555555":
var pickup5555555 = new Audio('timeout-bell-f1.mp3');
pickup5555555.play();
break;
case "police":
break;
default:
var pickupDefault = new Audio('ldcircuits-bell-f1.mp3');
pickupDefault.play();
}
}
function busy() {
function() busyTone {
var pickupBusy = new Audio('tele-busy.wav');
pickupBusy.play();
pickupBusy.currentTime = 0;
}
setInterval(function() busyTone, 1);
}
Upvotes: 0
Views: 1841
Reputation: 15642
Most significantly, it seems you're confused about infinite loops. Some browsers will hang when you enter one. For example, enter this into your JS console, and you might see a hang similar to yours:
var number = "12124561414";
while (number == "12124561414") { /* do nothing */ }
The issue here is that the while
loop runs in the same thread as the GUI loop, and so the GUI will lock up until that loop halts.
A solution to this problem presents itself in a similar form to other pieces of code you've provided here. For example, this code plays a tone once after some period of time, allowing the GUI to update between that period of time:
function numberDial() {
if (dialTone) {
dialTone.pause();
dialTone.currentTime = 0;
}
clearTimeout(timeout);
timeout = setTimeout(dial, 2000);
}
Use setInterval instead, and you'll have dial
invoked every 2 seconds until you use clearInterval
to stop it, allowing the GUI loop to continue its job (refreshing the user interface) until that two seconds has passed.
To answer your questions:
Is there a best practice to modify code to stop memory leaks?
Put heavy critical thought into designing your software so that objects don't hang around unnecessarily.
Is there any way to run indefinite loops without causing memory leaks?
I think a Rust doc answers this nicely...
... It's quite trivial to initialize a collection at the start of a program, fill it with tons of objects with destructors, and then enter an infinite event loop that never refers to it. The collection will sit around uselessly, holding on to its precious resources until the program terminates (at which point all those resources would have been reclaimed by the OS anyway).
In other words, it depends upon the loop, and what occurred before the loop!
Let us consider what's happening in this line of code immediately before your (browser-hanging) loop: console.log(number);
. I believe this to be the only possible leak in the code you've identified, as this causes a reference to number
, as an object, to be stored into such a collection (the debug logs).
As it turns out from further analysis, this probably isn't a leak, as number
is declared outside of the function anyway... It's worthwhile to note, however, that console.log
can prevent an object from being considered garbage!
Or some way to release these resources that are apparently not being released?
I suggest ensuring that the garbage collector can do its job to reclaim those resources by logging conditionally to reduce the amount that gets stored. As it is, the reference you're pushing onto the logs will also store other references (back to the global object, for example).
Upvotes: 1