Prevent deadlocks by avoiding calling callbacks in locked blocks
This commit is contained in:
@@ -25,18 +25,24 @@ object DefaultNetworkMonitor : UpstreamMonitor() {
|
|||||||
override fun onAvailable(network: Network) {
|
override fun onAvailable(network: Network) {
|
||||||
val properties = app.connectivity.getLinkProperties(network)
|
val properties = app.connectivity.getLinkProperties(network)
|
||||||
val ifname = properties?.interfaceName ?: return
|
val ifname = properties?.interfaceName ?: return
|
||||||
|
var switching = false
|
||||||
synchronized(this@DefaultNetworkMonitor) {
|
synchronized(this@DefaultNetworkMonitor) {
|
||||||
val oldProperties = currentLinkProperties
|
val oldProperties = currentLinkProperties
|
||||||
if (currentNetwork != network || ifname != oldProperties?.interfaceName) {
|
if (currentNetwork != network || ifname != oldProperties?.interfaceName) {
|
||||||
callbacks.forEach { it.onLost() } // we are using the other default network now
|
switching = true // we are using the other default network now
|
||||||
currentNetwork = network
|
currentNetwork = network
|
||||||
}
|
}
|
||||||
currentLinkProperties = properties
|
currentLinkProperties = properties
|
||||||
callbacks.forEach { it.onAvailable(ifname, properties) }
|
callbacks.toList()
|
||||||
|
}.forEach {
|
||||||
|
if (switching) it.onLost()
|
||||||
|
it.onAvailable(ifname, properties)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLinkPropertiesChanged(network: Network, properties: LinkProperties) {
|
override fun onLinkPropertiesChanged(network: Network, properties: LinkProperties) {
|
||||||
|
var losing = true
|
||||||
|
var ifname: String?
|
||||||
synchronized(this@DefaultNetworkMonitor) {
|
synchronized(this@DefaultNetworkMonitor) {
|
||||||
if (currentNetwork == null) {
|
if (currentNetwork == null) {
|
||||||
onAvailable(network)
|
onAvailable(network)
|
||||||
@@ -45,30 +51,28 @@ object DefaultNetworkMonitor : UpstreamMonitor() {
|
|||||||
if (currentNetwork != network) return
|
if (currentNetwork != network) return
|
||||||
val oldProperties = currentLinkProperties!!
|
val oldProperties = currentLinkProperties!!
|
||||||
currentLinkProperties = properties
|
currentLinkProperties = properties
|
||||||
val ifname = properties.interfaceName
|
ifname = properties.interfaceName
|
||||||
when {
|
when (ifname) {
|
||||||
ifname == null -> {
|
null -> Timber.w("interfaceName became null: $oldProperties -> $properties")
|
||||||
Timber.w("interfaceName became null: $oldProperties -> $properties")
|
oldProperties.interfaceName -> losing = false
|
||||||
onLost(network)
|
else -> Timber.w(RuntimeException("interfaceName changed: $oldProperties -> $properties"))
|
||||||
}
|
|
||||||
ifname != oldProperties.interfaceName -> {
|
|
||||||
Timber.w(RuntimeException("interfaceName changed: $oldProperties -> $properties"))
|
|
||||||
callbacks.forEach {
|
|
||||||
it.onLost()
|
|
||||||
it.onAvailable(ifname, properties)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> callbacks.forEach { it.onAvailable(ifname, properties) }
|
|
||||||
}
|
}
|
||||||
|
callbacks.toList()
|
||||||
|
}.forEach {
|
||||||
|
if (losing) {
|
||||||
|
if (ifname == null) return onLost(network)
|
||||||
|
it.onLost()
|
||||||
|
}
|
||||||
|
ifname?.let { ifname -> it.onAvailable(ifname, properties) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLost(network: Network) = synchronized(this@DefaultNetworkMonitor) {
|
override fun onLost(network: Network) = synchronized(this@DefaultNetworkMonitor) {
|
||||||
if (currentNetwork != network) return
|
if (currentNetwork != network) return
|
||||||
callbacks.forEach { it.onLost() }
|
|
||||||
currentNetwork = null
|
currentNetwork = null
|
||||||
currentLinkProperties = null
|
currentLinkProperties = null
|
||||||
}
|
callbacks.toList()
|
||||||
|
}.forEach { it.onLost() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun registerCallbackLocked(callback: Callback) {
|
override fun registerCallbackLocked(callback: Callback) {
|
||||||
|
|||||||
@@ -27,10 +27,10 @@ abstract class FallbackUpstreamMonitor private constructor() : UpstreamMonitor()
|
|||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
val old = monitor
|
val old = monitor
|
||||||
val callbacks = synchronized(old) {
|
val callbacks = synchronized(old) {
|
||||||
val callbacks = old.callbacks.toList()
|
old.callbacks.toList().also {
|
||||||
old.callbacks.clear()
|
old.callbacks.clear()
|
||||||
old.destroyLocked()
|
old.destroyLocked()
|
||||||
callbacks
|
}
|
||||||
}
|
}
|
||||||
val new = generateMonitor()
|
val new = generateMonitor()
|
||||||
monitor = new
|
monitor = new
|
||||||
|
|||||||
@@ -1,21 +1,27 @@
|
|||||||
package be.mygod.vpnhotspot.net.monitor
|
package be.mygod.vpnhotspot.net.monitor
|
||||||
|
|
||||||
|
import android.net.LinkProperties
|
||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class InterfaceMonitor(val iface: String) : UpstreamMonitor() {
|
class InterfaceMonitor(val iface: String) : UpstreamMonitor() {
|
||||||
private fun setPresent(present: Boolean) = synchronized(this) {
|
private fun setPresent(present: Boolean) {
|
||||||
val old = currentIface != null
|
var available: Pair<String, LinkProperties>? = null
|
||||||
if (present == old) return
|
synchronized(this) {
|
||||||
currentIface = if (present) iface else null
|
val old = currentIface != null
|
||||||
if (present) Pair(iface, currentLinkProperties) else null
|
if (present == old) return
|
||||||
}.let { pair ->
|
currentIface = if (present) iface else null
|
||||||
if (pair != null) {
|
if (present) available = iface to (currentLinkProperties ?: return)
|
||||||
val (iface, lp) = pair
|
callbacks.toList()
|
||||||
lp ?: return
|
}.forEach {
|
||||||
callbacks.forEach { it.onAvailable(iface, lp) }
|
@Suppress("NAME_SHADOWING")
|
||||||
} else callbacks.forEach { it.onLost() }
|
val available = available
|
||||||
|
if (available != null) {
|
||||||
|
val (iface, lp) = available
|
||||||
|
it.onAvailable(iface, lp)
|
||||||
|
} else it.onLost()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var registered = false
|
private var registered = false
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import android.os.Build
|
|||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
|
|
||||||
abstract class UpstreamMonitor {
|
abstract class UpstreamMonitor {
|
||||||
companion object : SharedPreferences.OnSharedPreferenceChangeListener {
|
companion object : SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
@@ -40,11 +38,10 @@ abstract class UpstreamMonitor {
|
|||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
val old = monitor
|
val old = monitor
|
||||||
val (active, callbacks) = synchronized(old) {
|
val (active, callbacks) = synchronized(old) {
|
||||||
val active = old.currentIface != null
|
(old.currentIface != null) to old.callbacks.toList().also {
|
||||||
val callbacks = old.callbacks.toList()
|
old.callbacks.clear()
|
||||||
old.callbacks.clear()
|
old.destroyLocked()
|
||||||
old.destroyLocked()
|
}
|
||||||
Pair(active, callbacks)
|
|
||||||
}
|
}
|
||||||
val new = generateMonitor()
|
val new = generateMonitor()
|
||||||
monitor = new
|
monitor = new
|
||||||
@@ -78,7 +75,7 @@ abstract class UpstreamMonitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val callbacks = Collections.newSetFromMap(ConcurrentHashMap<Callback, Boolean>())
|
val callbacks = mutableSetOf<Callback>()
|
||||||
protected abstract val currentLinkProperties: LinkProperties?
|
protected abstract val currentLinkProperties: LinkProperties?
|
||||||
open val currentIface: String? get() = currentLinkProperties?.interfaceName
|
open val currentIface: String? get() = currentLinkProperties?.interfaceName
|
||||||
protected abstract fun registerCallbackLocked(callback: Callback)
|
protected abstract fun registerCallbackLocked(callback: Callback)
|
||||||
|
|||||||
@@ -23,17 +23,23 @@ object VpnMonitor : UpstreamMonitor() {
|
|||||||
override fun onAvailable(network: Network) {
|
override fun onAvailable(network: Network) {
|
||||||
val properties = app.connectivity.getLinkProperties(network)
|
val properties = app.connectivity.getLinkProperties(network)
|
||||||
val ifname = properties?.interfaceName ?: return
|
val ifname = properties?.interfaceName ?: return
|
||||||
|
var switching = false
|
||||||
synchronized(this@VpnMonitor) {
|
synchronized(this@VpnMonitor) {
|
||||||
val oldProperties = available.put(network, properties)
|
val oldProperties = available.put(network, properties)
|
||||||
if (currentNetwork != network || ifname != oldProperties?.interfaceName) {
|
if (currentNetwork != network || ifname != oldProperties?.interfaceName) {
|
||||||
if (currentNetwork != null) callbacks.forEach { it.onLost() }
|
if (currentNetwork != null) switching = true
|
||||||
currentNetwork = network
|
currentNetwork = network
|
||||||
}
|
}
|
||||||
callbacks.forEach { it.onAvailable(ifname, properties) }
|
callbacks.toList()
|
||||||
|
}.forEach {
|
||||||
|
if (switching) it.onLost()
|
||||||
|
it.onAvailable(ifname, properties)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLinkPropertiesChanged(network: Network, properties: LinkProperties) {
|
override fun onLinkPropertiesChanged(network: Network, properties: LinkProperties) {
|
||||||
|
var losing = true
|
||||||
|
var ifname: String?
|
||||||
synchronized(this@VpnMonitor) {
|
synchronized(this@VpnMonitor) {
|
||||||
if (currentNetwork == null) {
|
if (currentNetwork == null) {
|
||||||
onAvailable(network)
|
onAvailable(network)
|
||||||
@@ -41,33 +47,37 @@ object VpnMonitor : UpstreamMonitor() {
|
|||||||
}
|
}
|
||||||
if (currentNetwork != network) return
|
if (currentNetwork != network) return
|
||||||
val oldProperties = available.put(network, properties)!!
|
val oldProperties = available.put(network, properties)!!
|
||||||
val ifname = properties.interfaceName
|
ifname = properties.interfaceName
|
||||||
when {
|
when (ifname) {
|
||||||
ifname == null -> {
|
null -> Timber.w("interfaceName became null: $oldProperties -> $properties")
|
||||||
Timber.w("interfaceName became null: $oldProperties -> $properties")
|
oldProperties.interfaceName -> losing = false
|
||||||
onLost(network)
|
else -> Timber.w(RuntimeException("interfaceName changed: $oldProperties -> $properties"))
|
||||||
}
|
|
||||||
ifname != oldProperties.interfaceName -> {
|
|
||||||
Timber.w("interfaceName changed: $oldProperties -> $properties")
|
|
||||||
callbacks.forEach {
|
|
||||||
it.onLost()
|
|
||||||
it.onAvailable(ifname, properties)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> callbacks.forEach { it.onAvailable(ifname, properties) }
|
|
||||||
}
|
}
|
||||||
|
callbacks.toList()
|
||||||
|
}.forEach {
|
||||||
|
if (losing) {
|
||||||
|
if (ifname == null) return onLost(network)
|
||||||
|
it.onLost()
|
||||||
|
}
|
||||||
|
ifname?.let { ifname -> it.onAvailable(ifname, properties) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLost(network: Network) = synchronized(this@VpnMonitor) {
|
override fun onLost(network: Network) {
|
||||||
if (available.remove(network) == null || currentNetwork != network) return
|
var newProperties: LinkProperties? = null
|
||||||
callbacks.forEach { it.onLost() }
|
synchronized(this@VpnMonitor) {
|
||||||
if (available.isNotEmpty()) {
|
if (available.remove(network) == null || currentNetwork != network) return
|
||||||
val next = available.entries.first()
|
if (available.isNotEmpty()) {
|
||||||
currentNetwork = next.key
|
val next = available.entries.first()
|
||||||
Timber.d("Switching to ${next.value.interfaceName} as VPN interface")
|
currentNetwork = next.key
|
||||||
callbacks.forEach { it.onAvailable(next.value.interfaceName!!, next.value) }
|
Timber.d("Switching to ${next.value.interfaceName} as VPN interface")
|
||||||
} else currentNetwork = null
|
newProperties = next.value
|
||||||
|
} else currentNetwork = null
|
||||||
|
callbacks.toList()
|
||||||
|
}.forEach {
|
||||||
|
it.onLost()
|
||||||
|
newProperties?.let { prop -> it.onAvailable(prop.interfaceName!!, prop) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user