fix: Use Job.invokeOnCompletion(onCancelling=true) for responsive socket cancellation
This commit is contained in:
parent
b54089dacc
commit
163da57ea5
3 changed files with 24 additions and 9 deletions
|
|
@ -9,8 +9,8 @@ import app.hermes.mobile.core.network.HermesRestClient
|
||||||
import app.hermes.mobile.core.security.TokenVault
|
import app.hermes.mobile.core.security.TokenVault
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.currentCoroutineContext
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
import kotlinx.coroutines.job
|
import kotlinx.coroutines.ensureActive
|
||||||
import kotlinx.coroutines.runInterruptible
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
|
|
@ -62,11 +62,6 @@ class PkceLoopbackAuthManager(
|
||||||
val port = serverSocket.localPort
|
val port = serverSocket.localPort
|
||||||
serverSocket.soTimeout = 180_000 // 3 minutes timeout
|
serverSocket.soTimeout = 180_000 // 3 minutes timeout
|
||||||
|
|
||||||
currentCoroutineContext().job.invokeOnCompletion {
|
|
||||||
try {
|
|
||||||
serverSocket?.close()
|
|
||||||
} catch (_: Throwable) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
val redirectUri = "http://127.0.0.1:$port/callback"
|
val redirectUri = "http://127.0.0.1:$port/callback"
|
||||||
|
|
||||||
|
|
@ -92,7 +87,22 @@ class PkceLoopbackAuthManager(
|
||||||
var retries = 0
|
var retries = 0
|
||||||
val MAX_RETRIES = 5
|
val MAX_RETRIES = 5
|
||||||
while (authCode == null && retries < MAX_RETRIES) {
|
while (authCode == null && retries < MAX_RETRIES) {
|
||||||
val socket: Socket = runInterruptible(Dispatchers.IO) { serverSocket!!.accept() }
|
currentCoroutineContext().ensureActive()
|
||||||
|
val socket: Socket = suspendCancellableCoroutine { cont ->
|
||||||
|
cont.invokeOnCancellation {
|
||||||
|
try {
|
||||||
|
serverSocket?.close()
|
||||||
|
} catch (_: Throwable) {}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val s = serverSocket!!.accept()
|
||||||
|
cont.resumeWith(Result.success(s))
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
if (!cont.isCancelled) {
|
||||||
|
cont.resumeWith(Result.failure(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
authCode = handleCallbackSocket(socket, state)
|
authCode = handleCallbackSocket(socket, state)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
|
||||||
|
|
@ -154,11 +154,13 @@ class HermesConnectionManager(
|
||||||
|
|
||||||
suspend fun addHost(host: HermesHost) {
|
suspend fun addHost(host: HermesHost) {
|
||||||
hostDao.insertOrUpdateHost(host.toEntity())
|
hostDao.insertOrUpdateHost(host.toEntity())
|
||||||
|
_hosts.value = _hosts.value.filter { it.id != host.id } + host
|
||||||
getOrCreateRuntime(host)
|
getOrCreateRuntime(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateHost(host: HermesHost) {
|
suspend fun updateHost(host: HermesHost) {
|
||||||
hostDao.insertOrUpdateHost(host.toEntity())
|
hostDao.insertOrUpdateHost(host.toEntity())
|
||||||
|
_hosts.value = _hosts.value.map { if (it.id == host.id) host else it }
|
||||||
val rt = runtimes[host.id]
|
val rt = runtimes[host.id]
|
||||||
rt?.updateHost(host)
|
rt?.updateHost(host)
|
||||||
}
|
}
|
||||||
|
|
@ -168,6 +170,7 @@ class HermesConnectionManager(
|
||||||
rt?.close()
|
rt?.close()
|
||||||
tokenVault.clearTokens(hostId.value)
|
tokenVault.clearTokens(hostId.value)
|
||||||
hostDao.deleteHost(hostId.value)
|
hostDao.deleteHost(hostId.value)
|
||||||
|
_hosts.value = _hosts.value.filter { it.id != hostId }
|
||||||
if (_activeHostId.value == hostId) {
|
if (_activeHostId.value == hostId) {
|
||||||
_activeHostId.value = _hosts.value.firstOrNull { it.id != hostId }?.id
|
_activeHostId.value = _hosts.value.firstOrNull { it.id != hostId }?.id
|
||||||
}
|
}
|
||||||
|
|
@ -175,6 +178,7 @@ class HermesConnectionManager(
|
||||||
|
|
||||||
suspend fun connectHost(hostId: HermesHostId): Result<Unit> {
|
suspend fun connectHost(hostId: HermesHostId): Result<Unit> {
|
||||||
val host = _hosts.value.find { it.id == hostId }
|
val host = _hosts.value.find { it.id == hostId }
|
||||||
|
?: hostDao.getHost(hostId.value)?.toDomain()
|
||||||
?: return Result.failure(IllegalArgumentException("Host not found: ${hostId.value}"))
|
?: return Result.failure(IllegalArgumentException("Host not found: ${hostId.value}"))
|
||||||
val rt = getOrCreateRuntime(host)
|
val rt = getOrCreateRuntime(host)
|
||||||
return rt.connect()
|
return rt.connect()
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,8 @@ class LoopbackCancellationTest {
|
||||||
)
|
)
|
||||||
|
|
||||||
val authUrlDeferred = CompletableDeferred<String>()
|
val authUrlDeferred = CompletableDeferred<String>()
|
||||||
val job = launch(Dispatchers.IO) {
|
val testScope = kotlinx.coroutines.CoroutineScope(Dispatchers.IO)
|
||||||
|
val job = testScope.launch {
|
||||||
authManager.startAuthFlow(
|
authManager.startAuthFlow(
|
||||||
context = null,
|
context = null,
|
||||||
connectionId = "test-host",
|
connectionId = "test-host",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue