Fix application hangs in disconnection state

This commit is contained in:
albexk 2023-12-07 22:43:33 +03:00
parent 2eaaf01ca1
commit 8cc5846808

View file

@ -12,6 +12,7 @@ import android.os.IBinder
import android.os.Looper import android.os.Looper
import android.os.Message import android.os.Message
import android.os.Messenger import android.os.Messenger
import android.os.Process
import androidx.annotation.MainThread import androidx.annotation.MainThread
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat import androidx.core.app.ServiceCompat
@ -21,12 +22,14 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeout
import org.amnezia.vpn.protocol.BadConfigException import org.amnezia.vpn.protocol.BadConfigException
import org.amnezia.vpn.protocol.LoadLibraryException import org.amnezia.vpn.protocol.LoadLibraryException
import org.amnezia.vpn.protocol.Protocol import org.amnezia.vpn.protocol.Protocol
@ -57,7 +60,8 @@ const val AFTER_PERMISSION_CHECK = "AFTER_PERMISSION_CHECK"
private const val PREFS_CONFIG_KEY = "LAST_CONF" private const val PREFS_CONFIG_KEY = "LAST_CONF"
private const val NOTIFICATION_ID = 1337 private const val NOTIFICATION_ID = 1337
private const val STATISTICS_SENDING_TIMEOUT = 1000L private const val STATISTICS_SENDING_TIMEOUT = 1000L
private const val DISCONNECT_TIMEOUT = 1500L private const val DISCONNECT_TIMEOUT = 5000L
private const val STOP_SERVICE_TIMEOUT = 5000L
class AmneziaVpnService : VpnService() { class AmneziaVpnService : VpnService() {
@ -214,7 +218,7 @@ class AmneziaVpnService : VpnService() {
isServiceBound = false isServiceBound = false
stopSendingStatistics() stopSendingStatistics()
clientMessenger.reset() clientMessenger.reset()
if (isUnknown || isDisconnected) stopSelf() if (isUnknown || isDisconnected) stopService()
} }
return true return true
} }
@ -239,16 +243,25 @@ class AmneziaVpnService : VpnService() {
override fun onDestroy() { override fun onDestroy() {
Log.v(TAG, "Destroy service") Log.v(TAG, "Destroy service")
runBlocking { runBlocking {
withTimeoutOrNull(DISCONNECT_TIMEOUT) { disconnect()
disconnect() disconnectionJob?.join()
disconnectionJob?.join()
}
} }
connectionScope.cancel() connectionScope.cancel()
mainScope.cancel() mainScope.cancel()
super.onDestroy() super.onDestroy()
} }
private fun stopService() {
Log.v(TAG, "Stop service")
// the coroutine below will be canceled during the onDestroy call
mainScope.launch {
delay(STOP_SERVICE_TIMEOUT)
Log.w(TAG, "Stop service timeout, kill process")
Process.killProcess(Process.myPid())
}
stopSelf()
}
/** /**
* Methods responsible for processing VPN connection * Methods responsible for processing VPN connection
*/ */
@ -265,7 +278,7 @@ class AmneziaVpnService : VpnService() {
DISCONNECTED -> { DISCONNECTED -> {
clientMessenger.send(ServiceEvent.DISCONNECTED) clientMessenger.send(ServiceEvent.DISCONNECTED)
stopSendingStatistics() stopSendingStatistics()
if (!isServiceBound) stopSelf() if (!isServiceBound) stopService()
} }
DISCONNECTING -> { DISCONNECTING -> {
@ -301,10 +314,10 @@ class AmneziaVpnService : VpnService() {
@MainThread @MainThread
private fun connect(vpnConfig: String?) { private fun connect(vpnConfig: String?) {
Log.v(TAG, "Start VPN connection")
if (isConnected || protocolState.value == CONNECTING) return if (isConnected || protocolState.value == CONNECTING) return
Log.v(TAG, "Start VPN connection")
protocolState.value = CONNECTING protocolState.value = CONNECTING
val config = parseConfigToJson(vpnConfig) val config = parseConfigToJson(vpnConfig)
@ -330,10 +343,10 @@ class AmneziaVpnService : VpnService() {
@MainThread @MainThread
private fun disconnect() { private fun disconnect() {
Log.v(TAG, "Stop VPN connection")
if (isUnknown || isDisconnected || protocolState.value == DISCONNECTING) return if (isUnknown || isDisconnected || protocolState.value == DISCONNECTING) return
Log.v(TAG, "Stop VPN connection")
protocolState.value = DISCONNECTING protocolState.value = DISCONNECTING
disconnectionJob = connectionScope.launch { disconnectionJob = connectionScope.launch {
@ -342,6 +355,15 @@ class AmneziaVpnService : VpnService() {
protocol?.stopVpn() protocol?.stopVpn()
protocol = null protocol = null
try {
withTimeout(DISCONNECT_TIMEOUT) {
// waiting for disconnect state
protocolState.first { it == DISCONNECTED }
}
} catch (e: TimeoutCancellationException) {
Log.w(TAG, "Disconnect timeout")
stopService()
}
} }
} }