Fix application hangs in disconnection state
This commit is contained in:
parent
2eaaf01ca1
commit
8cc5846808
1 changed files with 34 additions and 12 deletions
|
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue