Files
vpnhotspotmod/mobile/src/main/java/be/mygod/librootkotlinx/RootSession.kt
2020-07-03 10:55:21 +08:00

108 lines
3.1 KiB
Kotlin

package be.mygod.librootkotlinx
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.util.concurrent.TimeUnit
/**
* This object manages creation of [RootServer] and times them out automagically, with default timeout of 5 minutes.
*/
abstract class RootSession {
protected open fun createServer() = RootServer()
protected abstract suspend fun initServer(server: RootServer)
/**
* Timeout to close [RootServer] in milliseconds.
*/
protected open val timeout get() = TimeUnit.MINUTES.toMillis(5)
protected open val timeoutDispatcher get() = Dispatchers.Default
private val mutex = Mutex()
private var server: RootServer? = null
private var timeoutJob: Job? = null
private var usersCount = 0L
private var closePending = false
private suspend fun ensureServerLocked(): RootServer {
server?.let { return it }
check(usersCount == 0L)
val server = createServer()
try {
initServer(server)
this.server = server
return server
} catch (e: Throwable) {
try {
server.close()
} catch (eClose: Throwable) {
throw eClose.apply { addSuppressed(e) }
}
throw e
}
}
private suspend fun closeLocked() {
val server = server
this.server = null
server?.close()
}
private fun startTimeoutLocked() {
check(timeoutJob == null)
timeoutJob = GlobalScope.launch(timeoutDispatcher, CoroutineStart.UNDISPATCHED) {
delay(timeout)
mutex.withLock {
check(usersCount == 0L)
timeoutJob = null
closeLocked()
}
}
}
private fun haltTimeoutLocked() {
timeoutJob?.cancel()
timeoutJob = null
}
suspend fun acquire() = withContext(NonCancellable) {
mutex.withLock {
haltTimeoutLocked()
closePending = false
ensureServerLocked().also { ++usersCount }
}
}
suspend fun release(server: RootServer) = withContext(NonCancellable) {
mutex.withLock {
if (this@RootSession.server != server) return@withLock // outdated reference
require(usersCount > 0)
when {
!server.active -> {
usersCount = 0
closePending = false
closeLocked()
return@withLock
}
--usersCount > 0L -> return@withLock
closePending -> {
closePending = false
closeLocked()
}
else -> startTimeoutLocked()
}
}
}
suspend inline fun <T> use(block: (RootServer) -> T): T {
val server = acquire()
try {
return block(server)
} finally {
release(server)
}
}
suspend fun closeExisting() = mutex.withLock {
if (usersCount > 0) closePending = true else {
haltTimeoutLocked()
closeLocked()
}
}
}