Memory leak
Closed this issue · 0 comments
matchish commented
Previously we have false positive memory leak here
Originally posted by @George-Payne in #172 (comment)
But I've tried this code on 3.3.0 and looks like now we really have memory leak
If we increase the number of loops you can see that you're not waiting long enough for the garbage collector to kick in.
View code
const {
EventStoreDBClient,
jsonEvent,
persistentSubscriptionSettingsFromDefaults,
} = require("@eventstore/db-client");
const client = new EventStoreDBClient(
{
endpoint: "localhost:2113",
},
{
insecure: true,
}
);
async function simpleTest() {
const streamName = "demo";
for (let index = 0; index < 100000; index++) {
try {
console.log(
// round to mb
Math.round((process.memoryUsage().heapUsed / 1024 / 1024) * 100) / 100
);
await new Promise((resolve, reject) => {
const subscription = client.connectToPersistentSubscription(
streamName,
"demo",
{ bufferSize: 1 }
);
subscription.on("error", reject).on("confirmation", resolve);
});
break;
} catch (error) {
continue;
}
}
}
(async () => {
await simpleTest();
})();
I think this is exasperated by the for loop, as v8 prefers not to interrupt them with GC (as far as I am aware).
If we rewrite your code to give it more opportunities to garbage collect, you can see it keeps the memory footprint lower:
(original in blue, rewrite in red)
const {
EventStoreDBClient,
jsonEvent,
persistentSubscriptionSettingsFromDefaults,
} = require("@eventstore/db-client");
const client = new EventStoreDBClient(
{
endpoint: "localhost:2113",
},
{
insecure: true,
}
);
async function backoffConnect(maxRetries) {
let current = 0;
const connect = async () => {
try {
await new Promise((resolve, reject) => {
const subscription = client.connectToPersistentSubscription(
"demo",
"demo",
{ bufferSize: 1 }
);
subscription.on("error", reject).on("confirmation", resolve);
});
} catch (error) {
current++;
console.log(
// convert to mb
Math.round((process.memoryUsage().heapUsed / 1024 / 1024) * 100) / 100
);
if (current >= maxRetries) {
throw new Error("max retry count reached");
}
// We should calculate a reasonable backoff here, for example:
// const delayLength = interval * Math.pow(2, current);
// but we'll just do 10ms for the sake of expediance.
const delayLength = 10;
await new Promise((r) => setTimeout(r, delayLength));
return connect();
}
};
return connect();
}
(async () => {
try {
await backoffConnect(50000);
} catch (error) {
// it failed :(
}
})();