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.application.Application
|
||||||
import io.ktor.server.cio.CIO
|
import io.ktor.server.cio.CIO
|
||||||
import io.ktor.server.engine.EmbeddedServer
|
import io.ktor.server.engine.EmbeddedServer
|
||||||
|
import io.ktor.server.engine.connector
|
||||||
import io.ktor.server.engine.embeddedServer
|
import io.ktor.server.engine.embeddedServer
|
||||||
|
import io.ktor.server.application.serverConfig
|
||||||
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
import kotlinx.coroutines.currentCoroutineContext
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
import kotlinx.coroutines.ensureActive
|
import kotlinx.coroutines.ensureActive
|
||||||
import kotlinx.coroutines.withTimeout
|
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).
|
* 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].
|
* 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(
|
internal suspend fun startServerWithPortFallback(
|
||||||
host: String,
|
host: String,
|
||||||
@ -47,8 +53,25 @@ internal suspend fun startServerWithPortFallback(
|
|||||||
val candidates = listOf(preferredPort, 0).distinct()
|
val candidates = listOf(preferredPort, 0).distinct()
|
||||||
var lastFailure: Throwable? = null
|
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) {
|
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 {
|
try {
|
||||||
engine.start(wait = false)
|
engine.start(wait = false)
|
||||||
val boundPort = withTimeout(5.seconds) { engine.engine.resolvedConnectors().first().port }
|
val boundPort = withTimeout(5.seconds) { engine.engine.resolvedConnectors().first().port }
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user