perf(backend): terminate stalled websocket connections

Resolve #10885
This commit is contained in:
syuilo 2023-06-02 09:13:41 +09:00
parent 8bdf0dd135
commit 9eaca966a4
1 changed files with 21 additions and 2 deletions

View File

@ -19,6 +19,8 @@ import type * as http from 'node:http';
@Injectable() @Injectable()
export class StreamingApiServerService { export class StreamingApiServerService {
#wss: WebSocket.WebSocketServer; #wss: WebSocket.WebSocketServer;
#connections = new Map<WebSocket.WebSocket, number>();
#cleanConnectionsIntervalId: NodeJS.Timeout | null = null;
constructor( constructor(
@Inject(DI.config) @Inject(DI.config)
@ -109,7 +111,9 @@ export class StreamingApiServerService {
await stream.listen(ev, connection); await stream.listen(ev, connection);
const intervalId = user ? setInterval(() => { this.#connections.set(connection, Date.now());
const userUpdateIntervalId = user ? setInterval(() => {
this.usersRepository.update(user.id, { this.usersRepository.update(user.id, {
lastActiveDate: new Date(), lastActiveDate: new Date(),
}); });
@ -124,19 +128,34 @@ export class StreamingApiServerService {
ev.removeAllListeners(); ev.removeAllListeners();
stream.dispose(); stream.dispose();
this.redisForSub.off('message', onRedisMessage); this.redisForSub.off('message', onRedisMessage);
if (intervalId) clearInterval(intervalId); if (userUpdateIntervalId) clearInterval(userUpdateIntervalId);
}); });
connection.on('message', async (data) => { connection.on('message', async (data) => {
this.#connections.set(connection, Date.now());
if (data.toString() === 'ping') { if (data.toString() === 'ping') {
connection.send('pong'); connection.send('pong');
} }
}); });
}); });
this.#cleanConnectionsIntervalId = setInterval(() => {
const now = Date.now();
for (const [connection, lastActive] of this.#connections.entries()) {
if (now - lastActive > 1000 * 60 * 5) {
connection.terminate();
this.#connections.delete(connection);
}
}
}, 1000 * 60 * 5);
} }
@bindThis @bindThis
public detach(): Promise<void> { public detach(): Promise<void> {
if (this.#cleanConnectionsIntervalId) {
clearInterval(this.#cleanConnectionsIntervalId);
this.#cleanConnectionsIntervalId = null;
}
return new Promise((resolve) => { return new Promise((resolve) => {
this.#wss.close(() => resolve()); this.#wss.close(() => resolve());
}); });