Fix corner cases in connect/disconnect lifecycle and resource cleanup

1. Move TS6 handler patch to after client.connect() — the library's
   connect() internally replaces handler via #S(), discarding any
   patch applied beforehand. Patching after connect() is safe because
   clientinit is sent in async message callbacks after Init1 round-trips.

2. Preserve detectedProtocol across reconnect — disconnect() resets it
   to "unknown", causing the TS6 patch to be skipped on reconnect.

3. Prevent double "disconnected" event in BotInstance — disconnect()
   emitted it directly AND the async TS3Client disconnect triggered
   another through the event chain.

4. Guard playNext() against running after disconnect — check connected
   flag to avoid ghost queue processing.

5. Fix UDP error timer leak — clear previous timer before setting a new
   one to prevent accumulation.

6. Fix isPortFree() FD leak — close the test server on error path.

https://claude.ai/code/session_01QzvMLUT3UkhsffShcY1qzD
This commit is contained in:
Claude committed 2026-04-04 15:26:41 +00:00
1 parent 7ad9559653
commit b00b6637d2
3 files changed
+51 -28

No files matched your search

+8 -3
View File
@@ -105,6 +105,9 @@ export class BotInstance extends EventEmitter {
});
this.tsClient.on("disconnected", () => {
// Avoid duplicate event if disconnect() was called explicitly
// (it already set connected = false and emitted "disconnected").
if (!this.connected) return;
this.connected = false;
this.player.stop();
this.emit("disconnected");
@@ -119,8 +122,10 @@ export class BotInstance extends EventEmitter {
disconnect(): void {
this.player.stop();
this.tsClient.disconnect();
// Set connected = false BEFORE tsClient.disconnect() so that the
// "disconnected" event handler (setupTsEvents) won't emit a duplicate.
this.connected = false;
this.tsClient.disconnect();
this.emit("disconnected");
}
@@ -488,7 +493,7 @@ export class BotInstance extends EventEmitter {
}
private async playNext(): Promise<void> {
if (this.isAdvancing) return;
if (this.isAdvancing || !this.connected) return;
this.isAdvancing = true;
try {
this.voteSkipUsers.clear();
@@ -497,7 +502,7 @@ export class BotInstance extends EventEmitter {
let started = await this.resolveAndPlay(next);
if (!started) {
// Skip to next if URL resolve fails (up to 3 retries)
for (let i = 0; i < 3; i++) {
for (let i = 0; i < 3 && this.connected; i++) {
const retry = this.queue.next();
if (!retry) break;
if (await this.resolveAndPlay(retry)) {