Skip to content

Commit

Permalink
Merge pull request #88 from PandaIN95/main
Browse files Browse the repository at this point in the history
Fix: tries to reconnect to node when the node is deleted from nodeManager.nodes list
  • Loading branch information
Tomato6966 authored Jan 15, 2025
2 parents 78216ee + ebced59 commit 8803da4
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 20 deletions.
2 changes: 2 additions & 0 deletions src/structures/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ export enum DestroyReasons {
NodeReconnectFail = "NodeReconnectFail",
Disconnected = "Disconnected",
PlayerReconnectFail = "PlayerReconnectFail",
PlayerChangeNodeFail = "PlayerChangeNodeFail",
PlayerChangeNodeFailNoEligibleNode = "PlayerChangeNodeFailNoEligibleNode",
ChannelDeleted = "ChannelDeleted",
DisconnectAllNodes = "DisconnectAllNodes",
ReconnectAllNodes = "ReconnectAllNodes",
Expand Down
106 changes: 88 additions & 18 deletions src/structures/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,33 +475,99 @@ export class LavalinkNode {
* Destroys the Node-Connection (Websocket) and all player's of the node
* @param destroyReason Destroy Reason to use when destroying the players
* @param deleteNode wether to delete the nodte from the nodes list too, if false it will emit a disconnect. @default true
* @param movePlayers whether to movePlayers to different eligible connected node. If false players won't be moved @default false
* @returns void
*
* @example
* Destroys node and its players
* ```ts
* player.node.destroy("custom Player Destroy Reason", true);
* ```
* destroys only the node and moves its players to different connected node.
* ```ts
* player.node.destroy("custom Player Destroy Reason", true, true);
* ```
*/
public destroy(destroyReason?: DestroyReasonsType, deleteNode = true): void {
public destroy(destroyReason?: DestroyReasonsType, deleteNode: boolean = true, movePlayers: boolean = false): void {
if (!this.connected) return;

const players = this.NodeManager.LavalinkManager.players.filter(p => p.node.id === this.id);
if (players) players.forEach(p => {
p.destroy(destroyReason || DestroyReasons.NodeDestroy);
});

this.socket.close(1000, "Node-Destroy");
this.socket.removeAllListeners();
this.socket = null;

this.reconnectAttempts = 1;
clearTimeout(this.reconnectTimeout);

if (deleteNode) {
this.NodeManager.emit("destroy", this, destroyReason);
this.NodeManager.nodes.delete(this.id);
} else {
this.NodeManager.emit("disconnect", this, { code: 1000, reason: destroyReason });
if (players.size) {
const enableDebugEvents = this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents;
const handlePlayerOperations = () => {
if (movePlayers) {
const nodeToMove = Array.from(this.NodeManager.leastUsedNodes("playingPlayers"))
.find(n => n.connected && n.options.id !== this.id);

if (nodeToMove) {
return Promise.allSettled(Array.from(players.values()).map(player =>
player.changeNode(nodeToMove.options.id)
.catch(error => {
if (enableDebugEvents) {
console.error(`Node > destroy() Failed to move player ${player.guildId}: ${error.message}`);
}
return player.destroy(error.message ?? DestroyReasons.PlayerChangeNodeFail)
.catch(destroyError => {
if (enableDebugEvents) {
console.error(`Node > destroy() Failed to destroy player ${player.guildId} after move failure: ${destroyError.message}`);
}
});
})
));
} else {
return Promise.allSettled(Array.from(players.values()).map(player =>
player.destroy(DestroyReasons.PlayerChangeNodeFailNoEligibleNode)
.catch(error => {
if (enableDebugEvents) {
console.error(`Node > destroy() Failed to destroy player ${player.guildId}: ${error.message}`);
}
})
));
}
} else {
return Promise.allSettled(Array.from(players.values()).map(player =>
player.destroy(destroyReason || DestroyReasons.NodeDestroy)
.catch(error => {
if (enableDebugEvents) {
console.error(`Node > destroy() Failed to destroy player ${player.guildId}: ${error.message}`);
}
})
));
}
};

// Handle all player operations first, then clean up the socket
handlePlayerOperations().finally(() => {
this.socket.close(1000, "Node-Destroy");
this.socket.removeAllListeners();
this.socket = null;
this.reconnectAttempts = 1;
clearTimeout(this.reconnectTimeout);

if (deleteNode) {
this.NodeManager.emit("destroy", this, destroyReason);
this.NodeManager.nodes.delete(this.id);
clearInterval(this.heartBeatInterval);
clearTimeout(this.pingTimeout);
} else {
this.NodeManager.emit("disconnect", this, { code: 1000, reason: destroyReason });
}
});
} else { // If no players, proceed with socket cleanup immediately
this.socket.close(1000, "Node-Destroy");
this.socket.removeAllListeners();
this.socket = null;
this.reconnectAttempts = 1;
clearTimeout(this.reconnectTimeout);

if (deleteNode) {
this.NodeManager.emit("destroy", this, destroyReason);
this.NodeManager.nodes.delete(this.id);
clearInterval(this.heartBeatInterval);
clearTimeout(this.pingTimeout);
} else {
this.NodeManager.emit("disconnect", this, { code: 1000, reason: destroyReason });
}
}
return;
}
Expand Down Expand Up @@ -1059,7 +1125,11 @@ export class LavalinkNode {

this.NodeManager.emit("disconnect", this, { code, reason });

if (code !== 1000 || reason !== "Node-Destroy") this.reconnect();
if (code !== 1000 || reason !== "Node-Destroy") {
if (this.NodeManager.nodes.has(this.id)) { // try to reconnect only when the node is still in the nodeManager.nodes list
this.reconnect();
}
}
}

/** @private util function for handling error events from websocket */
Expand Down
16 changes: 14 additions & 2 deletions src/structures/NodeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,12 +187,24 @@ export class NodeManager extends EventEmitter {
/**
* Delete a node from the nodeManager and destroy it
* @param node The node to delete
* @param movePlayers whether to movePlayers to different connected node before deletion. @default false
* @returns
*
* @example
* Deletes the node
* ```ts
* client.lavalink.nodeManager.deleteNode("nodeId to delete");
* ```
* Moves players to a different node before deleting
* ```ts
* client.lavalink.nodeManager.deleteNode("nodeId to delete", true);
* ```
*/
deleteNode(node: LavalinkNodeIdentifier | LavalinkNode): void {
deleteNode(node: LavalinkNodeIdentifier | LavalinkNode, movePlayers: boolean = false): void {
const decodeNode = typeof node === "string" ? this.nodes.get(node) : node || this.leastUsedNodes()[0];
if (!decodeNode) throw new Error("Node was not found");
decodeNode.destroy(DestroyReasons.NodeDeleted);
if (movePlayers) decodeNode.destroy(DestroyReasons.NodeDeleted, true, true);
else decodeNode.destroy(DestroyReasons.NodeDeleted);
this.nodes.delete(decodeNode.id);
return;
}
Expand Down

0 comments on commit 8803da4

Please sign in to comment.