Skip to content

Commit e3682c9

Browse files
committed
Transfer orphaned players when possible
1 parent 4cfbb1e commit e3682c9

File tree

6 files changed

+68
-12
lines changed

6 files changed

+68
-12
lines changed

src/main/kotlin/dev/arbjerg/lavalink/client/LavalinkClient.kt

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,32 @@ class LavalinkClient(val userId: Long) : Closeable, Disposable {
125125
*/
126126
fun getLinkIfCached(guildId: Long): Link? = linkMap[guildId]
127127

128+
/**
129+
* Finds all players on unavailable nodes and transfers them to [node].
130+
*/
131+
internal fun transferOrphansTo(node: LavalinkNode) {
132+
// This *should* never happen, but just in case...
133+
if (!node.available) {
134+
return
135+
}
136+
137+
val orphans = findOrphanedPlayers()
138+
139+
orphans.mapNotNull { linkMap[it.guildId] }
140+
.forEach { link ->
141+
link.transferNode(node)
142+
}
143+
}
144+
145+
/**
146+
* Finds all players that are on unavailable nodes.
147+
*/
148+
private fun findOrphanedPlayers(): List<LavalinkPlayer> {
149+
val unavailableNodes = nodes.filter { !it.available }
150+
151+
return unavailableNodes.flatMap { it.playerCache.values }
152+
}
153+
128154
internal fun onNodeDisconnected(node: LavalinkNode) {
129155
// Don't do anything if we are shutting down.
130156
if (!clientOpen) {
@@ -138,9 +164,21 @@ class LavalinkClient(val userId: Long) : Closeable, Disposable {
138164
return
139165
}
140166

167+
// If we have no nodes available, don't attempt to load-balance.
168+
if (nodes.all { !it.available }) {
169+
linkMap.filter { (_, link) -> link.node == node }
170+
.forEach { (_, link) ->
171+
link.state = LinkState.DISCONNECTED
172+
}
173+
return
174+
}
175+
141176
linkMap.forEach { (_, link) ->
142-
if (link.node == node) {
143-
link.transferNode(loadBalancer.selectNode(region = null))
177+
if (link.node == node) {
178+
val voiceRegion = link.cachedPlayer?.voiceRegion
179+
180+
link.state = LinkState.CONNECTING
181+
link.transferNode(loadBalancer.selectNode(region = voiceRegion))
144182
}
145183
}
146184
}

src/main/kotlin/dev/arbjerg/lavalink/client/LavalinkNode.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ class LavalinkNode(
134134

135135
return rest.getPlayer(guildId)
136136
.map { it.toLavalinkPlayer(this) }
137+
// TODO: check for 404 status code, if not 404, don't create
137138
.onErrorResume { createOrUpdatePlayer(guildId) }
138139
.doOnSuccess {
139140
// Update the player internally upon retrieving it.
@@ -399,6 +400,10 @@ class LavalinkNode(
399400
*/
400401
fun getCachedPlayer(guildId: Long): LavalinkPlayer? = playerCache[guildId]
401402

403+
internal fun transferOrphansToSelf() {
404+
lavalink.transferOrphansTo(this)
405+
}
406+
402407
override fun equals(other: Any?): Boolean {
403408
if (this === other) return true
404409
if (javaClass != other?.javaClass) return false

src/main/kotlin/dev/arbjerg/lavalink/client/LavalinkPlayer.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dev.arbjerg.lavalink.client
22

3+
import dev.arbjerg.lavalink.client.loadbalancing.VoiceRegion
34
import dev.arbjerg.lavalink.client.protocol.Track
45
import dev.arbjerg.lavalink.client.protocol.toCustom
56
import dev.arbjerg.lavalink.protocol.v4.*
@@ -44,6 +45,15 @@ class LavalinkPlayer(private val node: LavalinkNode, protocolPlayer: Player) : I
4445
}
4546
}
4647

48+
val voiceRegion: VoiceRegion?
49+
get() {
50+
if (voiceState.endpoint.isBlank()) {
51+
return null
52+
}
53+
54+
return VoiceRegion.fromEndpoint(voiceState.endpoint)
55+
}
56+
4757
override fun setTrack(track: Track?) = PlayerUpdateBuilder(node, guildId)
4858
.setTrack(track)
4959

src/main/kotlin/dev/arbjerg/lavalink/client/Link.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dev.arbjerg.lavalink.client
22

33
import dev.arbjerg.lavalink.protocol.v4.VoiceState
4+
import java.time.Duration
45
import java.util.function.Consumer
56

67
/**
@@ -67,6 +68,8 @@ class Link(
6768
node.removeCachedPlayer(guildId)
6869
newNode.createOrUpdatePlayer(guildId)
6970
.applyBuilder(player.stateToBuilder())
71+
// Delay by 500ms to hopefully prevent a race-condition from triggering
72+
.delayElement(Duration.ofMillis(500))
7073
.subscribe()
7174
}
7275

src/main/kotlin/dev/arbjerg/lavalink/internal/LavalinkSocket.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ class LavalinkSocket(private val node: LavalinkNode) : WebSocketListener(), Clos
6060
.setNoReplace(false)
6161
.subscribe()
6262
}
63+
64+
// Move players from older, unavailable nodes to ourselves.
65+
node.transferOrphansToSelf()
6366
}
6467

6568
Message.Op.Stats -> {
@@ -126,19 +129,13 @@ class LavalinkSocket(private val node: LavalinkNode) : WebSocketListener(), Clos
126129
}
127130

128131
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
129-
node.lavalink.onNodeDisconnected(node)
130-
131132
when(t) {
132133
is EOFException -> {
133134
logger.debug("Got disconnected from ${node.name}, trying to reconnect", t)
134-
node.available = false
135-
open = false
136135
}
137136

138137
is SocketTimeoutException -> {
139138
logger.debug("Got disconnected from ${node.name} (timeout), trying to reconnect", t)
140-
node.available = false
141-
open = false
142139
}
143140

144141
is ConnectException -> {
@@ -153,14 +150,17 @@ class LavalinkSocket(private val node: LavalinkNode) : WebSocketListener(), Clos
153150
}
154151

155152
logger.warnOrTrace("Socket error on ${node.name}, reconnecting in ${reconnectInterval / 1000} seconds", t)
156-
node.available = false
157-
open = false
158153
}
159154

160155
else -> {
161156
logger.error("Unknown error on ${node.name}", t)
162157
}
163158
}
159+
160+
node.available = false
161+
open = false
162+
163+
node.lavalink.onNodeDisconnected(node)
164164
}
165165

166166
override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {

testbot/src/main/java/me/duncte123/testbot/Main.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ public static void main(String[] args) throws InterruptedException {
3838

3939
private static void registerLavalinkNodes(LavalinkClient client) {
4040
List.of(
41-
/*client.addNode(
41+
client.addNode(
4242
"Testnode",
4343
URI.create("ws://localhost:2333"),
4444
"youshallnotpass",
4545
RegionGroup.EUROPE
46-
)*/
46+
),
4747

4848
client.addNode(
4949
"Pi-local",

0 commit comments

Comments
 (0)