Skip to content

Commit 29d78a7

Browse files
committed
bug: memory leak fixes with locks
1 parent d71b576 commit 29d78a7

8 files changed

Lines changed: 72 additions & 70 deletions

File tree

src/cache/CacheTag.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export class CacheTag {
1111
this.tag = tags.join(":");
1212
this.forgetTag = `forget:${this.tag}`;
1313
}
14+
1415
async get(key?: string): Promise<CachedValue> {
1516
return await this.waitForLock(async () => {
1617
const values = await this.cacheStore.get(this.tag);
@@ -20,9 +21,11 @@ export class CacheTag {
2021
return values;
2122
});
2223
}
24+
2325
async has(key: string): Promise<boolean> {
2426
return (await this.get(key)) !== undefined;
2527
}
28+
2629
async put(key: string, value: CachedValue, seconds?: number) {
2730
return await this.waitForLock(async () => {
2831
const values = (await this.cacheStore.get(this.tag)) || {};
@@ -39,6 +42,7 @@ export class CacheTag {
3942
return true;
4043
});
4144
}
45+
4246
async forget(key?: string) {
4347
return await this.waitForLock(async () => {
4448
if (key) {
@@ -55,9 +59,10 @@ export class CacheTag {
5559
return true;
5660
});
5761
}
62+
5863
async waitForLock(
5964
callback: () => Promise<CachedValue>,
6065
): Promise<CachedValue> {
61-
return await this.cacheStore.lock(this.tag, callback, 60);
66+
return await this.cacheStore.lock(this.tag, callback, 10);
6267
}
6368
}

src/cache/cache.service.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,22 +87,22 @@ export class CacheService {
8787
key: string,
8888
callback: () => Promise<CachedValue>,
8989
expires = 60,
90+
maxAttempts = 10,
9091
): Promise<CachedValue> {
9192
const lockKey = `lock:${key}`;
92-
if (await this.connection.set(lockKey, 1, "EX", expires, "NX")) {
93-
try {
94-
return await callback();
95-
} finally {
96-
await this.forget(lockKey);
93+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
94+
if (await this.connection.set(lockKey, 1, "EX", expires, "NX")) {
95+
try {
96+
return await callback();
97+
} finally {
98+
await this.forget(lockKey);
99+
}
97100
}
101+
await new Promise((resolve) => setTimeout(resolve, 100));
98102
}
99-
await new Promise((resolve) => {
100-
setTimeout(() => {
101-
resolve(true);
102-
}, 100);
103-
});
104-
return await this.lock(key, callback);
103+
throw new Error(`Failed to acquire lock for ${key} after ${maxAttempts} attempts`);
105104
}
105+
106106
private async expireIn(key: string, seconds: number) {
107107
return this.connection.expire(key, seconds);
108108
}

src/dedicated-servers/dedicated-servers.service.ts

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,8 @@ export class DedicatedServersService {
601601
this.logger.log(`[${serverId}] waiting for pod to be ready`);
602602

603603
return new Promise((resolve, reject) => {
604+
let timer: NodeJS.Timeout;
605+
604606
const checkPodStatus = async () => {
605607
try {
606608
const deployment = await this.apps.readNamespacedDeployment({
@@ -615,33 +617,22 @@ export class DedicatedServersService {
615617
resolve();
616618
return;
617619
}
618-
619-
if (Date.now() - startTime >= maxWaitTime) {
620-
reject(
621-
new Error(
622-
`[${serverId}] timeout waiting for pod to be ready after ${maxWaitTime}ms`,
623-
),
624-
);
625-
return;
626-
}
627-
628-
setTimeout(checkPodStatus, 5000);
629620
} catch (error) {
630621
this.logger.warn(
631622
`[${serverId}] error checking pod status: ${error.message}`,
632623
);
624+
}
633625

634-
if (Date.now() - startTime >= maxWaitTime) {
635-
reject(
636-
new Error(
637-
`[${serverId}] timeout waiting for pod to be ready after ${maxWaitTime}ms`,
638-
),
639-
);
640-
return;
641-
}
642-
643-
setTimeout(checkPodStatus, 5000);
626+
if (Date.now() - startTime >= maxWaitTime) {
627+
reject(
628+
new Error(
629+
`[${serverId}] timeout waiting for pod to be ready after ${maxWaitTime}ms`,
630+
),
631+
);
632+
return;
644633
}
634+
635+
timer = setTimeout(checkPodStatus, 5000);
645636
};
646637

647638
void checkPodStatus();

src/game-server-node/game-server-node.service.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,7 @@ export class GameServerNodeService {
590590
gameServerNodeId: string,
591591
attempts = 0,
592592
game = "cs2",
593-
) {
593+
): Promise<void> {
594594
try {
595595
const pod = await this.loggingService.getJobPod(
596596
GameServerNodeService.GET_UPDATE_JOB_NAME(gameServerNodeId, game),
@@ -641,7 +641,9 @@ export class GameServerNodeService {
641641
}
642642

643643
const stream = new PassThrough();
644-
void this.loggingService.getLogsForPod(pod, stream);
644+
void this.loggingService.getLogsForPod(pod, stream).catch(() => {
645+
stream.destroy();
646+
});
645647

646648
stream.on("data", async (data) => {
647649
const { log } = JSON.parse(data.toString());

src/rcon/rcon.service.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,16 +78,21 @@ export class RconService {
7878
return payload;
7979
};
8080

81-
rcon
82-
.on("error", async () => {
83-
await this.disconnect(serverId);
84-
})
85-
.on("end", () => {
86-
if (!this.connections[serverId]) {
87-
return;
88-
}
89-
delete this.connections[serverId];
90-
});
81+
const onError = async () => {
82+
(rcon as any).off("error", onError);
83+
(rcon as any).off("end", onEnd);
84+
await this.disconnect(serverId);
85+
};
86+
const onEnd = () => {
87+
(rcon as any).off("error", onError);
88+
(rcon as any).off("end", onEnd);
89+
if (!this.connections[serverId]) {
90+
return;
91+
}
92+
delete this.connections[serverId];
93+
};
94+
95+
rcon.on("error", onError).on("end", onEnd);
9196

9297
try {
9398
const timeoutPromise = new Promise<never>((_, reject) => {

src/redis/redis-manager/redis-manager.service.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ export class RedisManagerService {
1111
[key: string]: Redis;
1212
} = {};
1313

14+
private healthCheckIntervals: {
15+
[key: string]: NodeJS.Timeout;
16+
} = {};
17+
1418
constructor(
1519
private readonly logger: Logger,
1620
private readonly configService: ConfigService,
@@ -36,16 +40,14 @@ export class RedisManagerService {
3640
/**
3741
* We may get disconnected, and we may need to force a re-connect.
3842
*/
39-
let setupPingPong = false;
43+
const pingTimeoutError = `did not receive ping in time (5 seconds)`;
44+
4045
currentConnection.on("online", () => {
41-
if (setupPingPong) {
42-
return;
46+
if (this.healthCheckIntervals[connection]) {
47+
clearInterval(this.healthCheckIntervals[connection]);
4348
}
44-
setupPingPong = true;
45-
46-
const pingTimeoutError = `did not receive ping in time (5 seconds)`;
4749

48-
setInterval(async () => {
50+
this.healthCheckIntervals[connection] = setInterval(async () => {
4951
if (currentConnection.status === "ready") {
5052
await new Promise(async (resolve, reject) => {
5153
const timer = setTimeout(() => {

src/system/system.service.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,8 @@ export class SystemService {
6161
}
6262

6363
public async detectFeatures() {
64-
while (this.featuresDetected === false) {
65-
try {
66-
const tailscaleConfig = this.config.get<TailscaleConfig>("tailscale");
64+
try {
65+
const tailscaleConfig = this.config.get<TailscaleConfig>("tailscale");
6766

6867
let supportsGameServerNodes = false;
6968
if (
@@ -141,11 +140,11 @@ export class SystemService {
141140
});
142141

143142
this.featuresDetected = true;
144-
return;
145-
} catch (error) {
146-
this.logger.warn("Error detecting features", error);
147-
}
148-
await new Promise((resolve) => setTimeout(resolve, 5000));
143+
} catch (error) {
144+
this.logger.warn("Error detecting features", error);
145+
setTimeout(() => {
146+
void this.detectFeatures();
147+
}, 5000);
149148
}
150149
}
151150

src/type-sense/type-sense.service.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,14 @@ export class TypeSenseService {
3636
connectionTimeoutSeconds: 2,
3737
});
3838

39-
let setup = false;
40-
while (!setup) {
41-
try {
42-
await this.createCvarsCollection();
43-
await this.createPlayerCollection();
44-
setup = true;
45-
} catch (error) {
46-
this.logger.error(`unable to setup typesense: ${error}`);
47-
await new Promise((resolve) => setTimeout(resolve, 1000));
48-
}
39+
try {
40+
await this.createCvarsCollection();
41+
await this.createPlayerCollection();
42+
} catch (error) {
43+
this.logger.error(`unable to setup typesense: ${error}`);
44+
setTimeout(() => {
45+
void this.setup();
46+
}, 1000);
4947
}
5048
}
5149

0 commit comments

Comments
 (0)