mirror of
https://github.com/KRTirtho/spotube.git
synced 2026-11-04 10:13:59 +00:00
fix(server): don't crash when the proxy port is still in use
CIO reports a failed bind from its accept coroutine; unhandled, that exception terminates the process on Kotlin/Native before the port fallback can run, so relaunching iOS shortly after playback crashed (EADDRINUSE). Contain it with a parent CoroutineExceptionHandler and enable reuseAddress so the preferred port can be rebound while old connections are in TIME_WAIT.
This commit is contained in:
parent
7eb56c8593
commit
99ba08501b
@ -21,7 +21,10 @@ package org.spotube.spotube.core.server
|
||||
import io.ktor.server.application.Application
|
||||
import io.ktor.server.cio.CIO
|
||||
import io.ktor.server.engine.EmbeddedServer
|
||||
import io.ktor.server.engine.connector
|
||||
import io.ktor.server.engine.embeddedServer
|
||||
import io.ktor.server.application.serverConfig
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.currentCoroutineContext
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.withTimeout
|
||||
@ -37,6 +40,9 @@ internal class BoundServer(val engine: EmbeddedServer<*, *>, val port: Int, val
|
||||
* preferred port can't be bound, retry on an OS-assigned free port (port 0).
|
||||
*
|
||||
* The caller must read [BoundServer.port] instead of assuming [preferredPort].
|
||||
*
|
||||
* `reuseAddress` lets a restarted app rebind its old port while connections from the previous
|
||||
* run (e.g. the player's) are still in TIME_WAIT.
|
||||
*/
|
||||
internal suspend fun startServerWithPortFallback(
|
||||
host: String,
|
||||
@ -47,8 +53,25 @@ internal suspend fun startServerWithPortFallback(
|
||||
val candidates = listOf(preferredPort, 0).distinct()
|
||||
var lastFailure: Throwable? = null
|
||||
|
||||
// CIO reports a failed bind from its accept coroutine. Unhandled, that exception kills the
|
||||
// process on Kotlin/Native (iOS) before the fallback below gets to run; the failure still
|
||||
// reaches us through resolvedConnectors(), so it only needs to be contained here.
|
||||
val swallowBindFailure = CoroutineExceptionHandler { _, _ -> }
|
||||
|
||||
for (candidate in candidates) {
|
||||
val engine = embeddedServer(factory = CIO, host = host, port = candidate, module = module)
|
||||
val engine = embeddedServer(
|
||||
factory = CIO,
|
||||
rootConfig = serverConfig {
|
||||
parentCoroutineContext = swallowBindFailure
|
||||
module { module() }
|
||||
},
|
||||
configure = {
|
||||
connector {
|
||||
this.host = host
|
||||
this.port = candidate
|
||||
}
|
||||
reuseAddress = true
|
||||
})
|
||||
try {
|
||||
engine.start(wait = false)
|
||||
val boundPort = withTimeout(5.seconds) { engine.engine.resolvedConnectors().first().port }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user