Reputation: 17594
Today the servers were on fire because the app was crashing. After checking the servers, I found out that the memory kept going up which caused PM2 to eventually kill and restart the process.
This was caused because I made a fix. The original code was the following:
const cache = this.cache[script] = this.cache[script] || {};
After seeing that I thought: "Surely this is a typo, and the original creator meant something else, like this":
const cache = this.cache[script] || {};
By fixing that one line, the memory started growing and accumulating until filling the entire server.
I am blown away ... obviously I had to revert it back to the original version ...
What is the difference between those two lines?
Upvotes: 3
Views: 143
Reputation: 1075347
The first one writes {}
to this.cache[script]
if there is no property in this.cache
with the name in the variable script
(or there is but its value is falsy). The second doesn't.
So that means if you call this code more than once (or have it in more than one place), with the first code, all of them will end up sharing an object (stored on this.cache[script]
), but with the second code, they'll all create and use their own, new objects.
That's why you had memory ballooning, the code was never caching the objects and constantly creating new ones.
Example:
class Example {
constructor() {
this.cache = Object.create(null);
}
getTheCache1(script) {
const cache = this.cache[script] || {};
return cache;
}
getTheCache2(script) {
const cache = this.cache[script] = this.cache[script] || {};
return cache;
}
}
const e1 = new Example();
const e1c1 = e1.getTheCache1("foo");
const e1c2 = e1.getTheCache1("foo");
console.log("e1c1 === e1c2? " + (e1c1 === e1c2));
const e2 = new Example();
const e2c1 = e1.getTheCache2("foo");
const e2c2 = e1.getTheCache2("foo");
console.log("e2c1 === e2c2? " + (e2c1 === e2c2));
Or a simpler example:
let a; // a starts out undefined
console.log("const b = a || {};");
const b = a || {};
console.log("typeof a: " + typeof a); // "typeof a: undefined"
console.log("typeof b: " + typeof b); // "typeof b: object"
console.log("a === b? " + (a === b)); // "a === b? false"
console.log("const c = a = a || {};");
const c = a = a || {};
console.log("typeof a: " + typeof a); // "typeof a: object"
console.log("typeof c: " + typeof c); // "typeof c: object"
console.log("a === c? " + (a === c)); // "a === c? true"
.as-console-wrapper {
max-height: 100% !important;
}
All of this works because the result of an assignment is the value that was assigned. So here:
let a, b;
b = a = a || 42;
what happens is:
a || 42
is evaluated, resulting in the value 42
since undefined || 42
is 42
.a = <value>
is evaluated, where <value>
is the result of Step 1. So 42
gets assigned to a
. The result of that expression is the value that was assigned (42
).b = <value>
is evaluated, where <value>
is the result of Step 2. So 42
gets assigned to b
.But without the a =
part of that:
let a, b;
b = a || 42;
it's just
a || 42
is evaluated, resulting in the value 42
since undefined || 42
is 42
.b = <value>
is evaluated, where <value>
is the result of Step 1. So 42
gets assigned to b
....leaving a
unchanged.
Upvotes: 5
Reputation: 272336
Let us add some parenthesis:
const cache = (this.cache[script] = (this.cache[script] || {}));
This one line does two things:
this.cache[script]
if it is undefinedcache
variable with a reference to this.cache[script]
As a result, modifications to the cache
variable are also reflected inside this.cache[script]
.
The alternate code will either:
cache
variable with a reference to this.cache[script]
cache
variable with a reference to a blank objectDepending on the above, modifications to cache
variable will or won't be reflected inside this.cache[script]
.
I am guessing the portion of the code that broke relies on this.cache...
being up-to-date but the code that uses cache
variable updates... nothing.
Upvotes: 1