Make disposed debugger clients inert

Detach the WebSocket handlers and cancel the pending hard reload before
closing, drop messages sent after dispose, and release the debugger client
and in-game editor references when the game is disposed.
This commit is contained in:
Aurélien Vivet
2026-10-08 15:18:27 +02:00
parent 3c6114432f
commit 9d0cbebdec
4 changed files with 31 additions and 2 deletions
@@ -614,6 +614,10 @@ namespace gdjs {
}
}
/**
* Release the listeners and the connection used by the client, which must
* not be used anymore afterwards: messages are then silently dropped.
*/
abstract dispose(): void;
/**
@@ -63,7 +63,7 @@ namespace gdjs {
// the WebSocket connection between the editor and the game is closed. When we are in in-game edition,
// we can't afford to lose the connection because it means the editor is unusable.
// In this case, we hard reload the game to re-establish a new connection.
setTimeout(() => {
that._hardReloadTimeoutId = setTimeout(() => {
logger.info(
'Debugger connection closed while in in-game edition - this is suspicious so hard reloading to re-establish a new connection.'
);
@@ -86,14 +86,33 @@ namespace gdjs {
}
private hasLoggedError: boolean = false;
private _wasDisposed: boolean = false;
/** The hard reload scheduled when the connection is lost in in-game edition. */
private _hardReloadTimeoutId: NodeJS.Timeout | null = null;
dispose(): void {
this._wasDisposed = true;
if (this._hardReloadTimeoutId !== null) {
clearTimeout(this._hardReloadTimeoutId);
this._hardReloadTimeoutId = null;
}
if (this._ws) {
// Detach the handlers before closing: `close()` fires `onclose` (and
// `onerror` if the connection was still being established), which must
// not trigger the hard reload done when the connection is lost in
// in-game edition.
this._ws.onopen = null;
this._ws.onclose = null;
this._ws.onerror = null;
this._ws.onmessage = null;
this._ws.close();
}
}
protected _sendMessage(message: string) {
// Once disposed, messages are silently dropped: the connection is closed
// and logging a warning would call this method again.
if (this._wasDisposed) return;
if (!this._ws) {
// The error can be logged only once, since logger.warn will call this function again,
// leading to an endless recursive call if we do not call it only once.
@@ -31,8 +31,10 @@ namespace gdjs {
window.addEventListener('message', this._onWindowMessage);
}
dispose() {
dispose(): void {
window.removeEventListener('message', this._onWindowMessage);
// Drop the opener so that no message is sent anymore once disposed.
this._opener = null;
}
protected _sendMessage(message: string) {
+4
View File
@@ -1448,6 +1448,10 @@ namespace gdjs {
this._sceneStack.dispose();
this._renderer.dispose(removeCanvas);
this._resourcesLoader.dispose();
// Drop the disposed helpers so that any late call (uncaught exception
// reporting, status updates...) is a no-op.
this._inGameEditor = null;
this._debuggerClient = null;
this._wasDisposed = true;
}