Commit 27589723 authored by Mygod's avatar Mygod

Fix leaks after service switches

parent 24bd70a4
...@@ -61,15 +61,12 @@ object BaseService { ...@@ -61,15 +61,12 @@ object BaseService {
const val CONFIG_FILE = "shadowsocks.conf" const val CONFIG_FILE = "shadowsocks.conf"
const val CONFIG_FILE_UDP = "shadowsocks-udp.conf" const val CONFIG_FILE_UDP = "shadowsocks-udp.conf"
class Data internal constructor(private val service: Interface): AutoCloseable { class Data internal constructor(private val service: Interface) {
@Volatile var state = STOPPED @Volatile var state = STOPPED
val processes = GuardedProcessPool() val processes = GuardedProcessPool()
@Volatile var proxy: ProxyInstance? = null @Volatile var proxy: ProxyInstance? = null
@Volatile var udpFallback: ProxyInstance? = null @Volatile var udpFallback: ProxyInstance? = null
val callbacks = RemoteCallbackList<IShadowsocksServiceCallback>()
val bandwidthListeners = HashSet<IBinder>() // the binder is the real identifier
var notification: ServiceNotification? = null var notification: ServiceNotification? = null
val closeReceiver = broadcastReceiver { _, intent -> val closeReceiver = broadcastReceiver { _, intent ->
when (intent.action) { when (intent.action) {
...@@ -79,35 +76,56 @@ object BaseService { ...@@ -79,35 +76,56 @@ object BaseService {
} }
var closeReceiverRegistered = false var closeReceiverRegistered = false
val binder = object : IShadowsocksService.Stub() { val binder = Binder(this)
override fun getState(): Int = this@Data.state
override fun getProfileName(): String = proxy?.profile?.name ?: "Idle" fun changeState(s: Int, msg: String? = null) {
if (state == s && msg == null) return
binder.stateChanged(s, msg)
state = s
}
init {
RemoteConfig.fetch()
}
}
class Binder(private var data: Data? = null) : IShadowsocksService.Stub(), AutoCloseable {
val callbacks = RemoteCallbackList<IShadowsocksServiceCallback>()
private val bandwidthListeners = HashSet<IBinder>() // the binder is the real identifier
override fun getState(): Int = data!!.state
override fun getProfileName(): String = data!!.proxy?.profile?.name ?: "Idle"
override fun registerCallback(cb: IShadowsocksServiceCallback) { override fun registerCallback(cb: IShadowsocksServiceCallback) {
callbacks.register(cb) callbacks.register(cb)
} }
private fun broadcast(work: (IShadowsocksServiceCallback) -> Unit) {
val n = callbacks.beginBroadcast()
for (i in 0 until n) try {
work(callbacks.getBroadcastItem(i))
} catch (e: Exception) {
printLog(e)
}
callbacks.finishBroadcast()
}
private fun registerTimeout() = private fun registerTimeout() =
Core.handler.postAtTime(this::onTimeout, this, SystemClock.uptimeMillis() + 1000) Core.handler.postAtTime(this::onTimeout, this, SystemClock.uptimeMillis() + 1000)
private fun onTimeout() { private fun onTimeout() {
val proxies = listOfNotNull(proxy, udpFallback) val proxies = listOfNotNull(data!!.proxy, data!!.udpFallback)
val stats = proxies val stats = proxies
.map { Pair(it.profile.id, it.trafficMonitor?.requestUpdate()) } .map { Pair(it.profile.id, it.trafficMonitor?.requestUpdate()) }
.filter { it.second != null } .filter { it.second != null }
.map { Triple(it.first, it.second!!.first, it.second!!.second) } .map { Triple(it.first, it.second!!.first, it.second!!.second) }
if (stats.any { it.third } && state == CONNECTED && bandwidthListeners.isNotEmpty()) { if (stats.any { it.third } && state == CONNECTED && bandwidthListeners.isNotEmpty()) {
val sum = stats.fold(TrafficStats()) { a, b -> a + b.second } val sum = stats.fold(TrafficStats()) { a, b -> a + b.second }
val n = callbacks.beginBroadcast() broadcast { item ->
for (i in 0 until n) try {
val item = callbacks.getBroadcastItem(i)
if (bandwidthListeners.contains(item.asBinder())) { if (bandwidthListeners.contains(item.asBinder())) {
stats.forEach { (id, stats) -> item.trafficUpdated(id, stats) } stats.forEach { (id, stats) -> item.trafficUpdated(id, stats) }
item.trafficUpdated(0, sum) item.trafficUpdated(0, sum)
} }
} catch (e: Exception) {
printLog(e)
} }
callbacks.finishBroadcast()
} }
registerTimeout() registerTimeout()
} }
...@@ -118,14 +136,14 @@ object BaseService { ...@@ -118,14 +136,14 @@ object BaseService {
if (wasEmpty) registerTimeout() if (wasEmpty) registerTimeout()
if (state != CONNECTED) return if (state != CONNECTED) return
var sum = TrafficStats() var sum = TrafficStats()
val proxy = proxy ?: return val proxy = data!!.proxy ?: return
proxy.trafficMonitor?.out.also { stats -> proxy.trafficMonitor?.out.also { stats ->
cb.trafficUpdated(proxy.profile.id, if (stats == null) sum else { cb.trafficUpdated(proxy.profile.id, if (stats == null) sum else {
sum += stats sum += stats
stats stats
}) })
} }
udpFallback?.also { udpFallback -> data!!.udpFallback?.also { udpFallback ->
udpFallback.trafficMonitor?.out.also { stats -> udpFallback.trafficMonitor?.out.also { stats ->
cb.trafficUpdated(udpFallback.profile.id, if (stats == null) TrafficStats() else { cb.trafficUpdated(udpFallback.profile.id, if (stats == null) TrafficStats() else {
sum += stats sum += stats
...@@ -147,26 +165,29 @@ object BaseService { ...@@ -147,26 +165,29 @@ object BaseService {
stopListeningForBandwidth(cb) // saves an RPC, and safer stopListeningForBandwidth(cb) // saves an RPC, and safer
callbacks.unregister(cb) callbacks.unregister(cb)
} }
}
fun changeState(s: Int, msg: String? = null) { fun stateChanged(s: Int, msg: String?) {
if (state == s && msg == null) return val profileName = profileName
if (callbacks.registeredCallbackCount > 0) Core.handler.post { broadcast { it.stateChanged(s, profileName, msg) }
val n = callbacks.beginBroadcast()
for (i in 0 until n) try {
callbacks.getBroadcastItem(i).stateChanged(s, binder.profileName, msg)
} catch (e: Exception) {
printLog(e)
} }
callbacks.finishBroadcast()
fun trafficPersisted(ids: List<Long>) {
if (bandwidthListeners.isNotEmpty() && ids.isNotEmpty()) broadcast { item ->
if (bandwidthListeners.contains(item.asBinder())) ids.forEach(item::trafficPersisted)
} }
state = s
} }
override fun close() = callbacks.kill() override fun close() {
callbacks.kill()
Core.handler.removeCallbacksAndMessages(this)
data = null
}
} }
interface Interface { interface Interface {
val data: Data
val tag: String val tag: String
fun createNotification(profileName: String): ServiceNotification
fun onBind(intent: Intent): IBinder? = if (intent.action == Action.SERVICE) data.binder else null fun onBind(intent: Intent): IBinder? = if (intent.action == Action.SERVICE) data.binder else null
...@@ -206,8 +227,6 @@ object BaseService { ...@@ -206,8 +227,6 @@ object BaseService {
"-U") "-U")
} }
fun createNotification(profileName: String): ServiceNotification
fun startRunner() { fun startRunner() {
this as Context this as Context
if (Build.VERSION.SDK_INT >= 26) startForegroundService(Intent(this, javaClass)) if (Build.VERSION.SDK_INT >= 26) startForegroundService(Intent(this, javaClass))
...@@ -240,20 +259,7 @@ object BaseService { ...@@ -240,20 +259,7 @@ object BaseService {
it.profile.id it.profile.id
} }
data.proxy = null data.proxy = null
if (ids.isNotEmpty()) Core.handler.post { data.binder.trafficPersisted(ids)
if (data.bandwidthListeners.isNotEmpty()) {
val n = data.callbacks.beginBroadcast()
for (i in 0 until n) {
try {
val item = data.callbacks.getBroadcastItem(i)
if (data.bandwidthListeners.contains(item.asBinder())) ids.forEach(item::trafficPersisted)
} catch (e: Exception) {
printLog(e)
}
}
data.callbacks.finishBroadcast()
}
}
// change the state // change the state
data.changeState(STOPPED, msg) data.changeState(STOPPED, msg)
...@@ -262,8 +268,6 @@ object BaseService { ...@@ -262,8 +268,6 @@ object BaseService {
if (stopService) stopSelf() if (stopService) stopSelf()
} }
val data: Data get() = instances[this]!!
fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val data = data val data = data
if (data.state != STOPPED) return Service.START_NOT_STICKY if (data.state != STOPPED) return Service.START_NOT_STICKY
...@@ -320,15 +324,5 @@ object BaseService { ...@@ -320,15 +324,5 @@ object BaseService {
} }
return Service.START_NOT_STICKY return Service.START_NOT_STICKY
} }
fun onDestroy() {
data.close()
}
}
private val instances = WeakHashMap<Interface, Data>()
internal fun register(instance: Interface) {
instances[instance] = Data(instance)
RemoteConfig.fetch()
} }
} }
...@@ -27,10 +27,7 @@ import android.content.Intent ...@@ -27,10 +27,7 @@ import android.content.Intent
* Shadowsocks service at its minimum. * Shadowsocks service at its minimum.
*/ */
class ProxyService : Service(), BaseService.Interface { class ProxyService : Service(), BaseService.Interface {
init { override val data = BaseService.Data(this)
BaseService.register(this)
}
override val tag: String get() = "ShadowsocksProxyService" override val tag: String get() = "ShadowsocksProxyService"
override fun createNotification(profileName: String): ServiceNotification = override fun createNotification(profileName: String): ServiceNotification =
ServiceNotification(this, profileName, "service-proxy", true) ServiceNotification(this, profileName, "service-proxy", true)
...@@ -39,7 +36,7 @@ class ProxyService : Service(), BaseService.Interface { ...@@ -39,7 +36,7 @@ class ProxyService : Service(), BaseService.Interface {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int = override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int =
super<BaseService.Interface>.onStartCommand(intent, flags, startId) super<BaseService.Interface>.onStartCommand(intent, flags, startId)
override fun onDestroy() { override fun onDestroy() {
super<Service>.onDestroy() super.onDestroy()
super<BaseService.Interface>.onDestroy() data.binder.close()
} }
} }
...@@ -27,10 +27,7 @@ import com.github.shadowsocks.preference.DataStore ...@@ -27,10 +27,7 @@ import com.github.shadowsocks.preference.DataStore
import java.io.File import java.io.File
class TransproxyService : Service(), LocalDnsService.Interface { class TransproxyService : Service(), LocalDnsService.Interface {
init { override val data = BaseService.Data(this)
BaseService.register(this)
}
override val tag: String get() = "ShadowsocksTransproxyService" override val tag: String get() = "ShadowsocksTransproxyService"
override fun createNotification(profileName: String): ServiceNotification = override fun createNotification(profileName: String): ServiceNotification =
ServiceNotification(this, profileName, "service-transproxy", true) ServiceNotification(this, profileName, "service-transproxy", true)
...@@ -79,7 +76,7 @@ redsocks { ...@@ -79,7 +76,7 @@ redsocks {
} }
override fun onDestroy() { override fun onDestroy() {
super<Service>.onDestroy() super.onDestroy()
super<LocalDnsService.Interface>.onDestroy() data.binder.close()
} }
} }
...@@ -108,10 +108,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -108,10 +108,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
override fun getLocalizedMessage() = getString(R.string.reboot_required) override fun getLocalizedMessage() = getString(R.string.reboot_required)
} }
init { override val data = BaseService.Data(this)
BaseService.register(this)
}
override val tag: String get() = "ShadowsocksVpnService" override val tag: String get() = "ShadowsocksVpnService"
override fun createNotification(profileName: String): ServiceNotification = override fun createNotification(profileName: String): ServiceNotification =
ServiceNotification(this, profileName, "service-vpn") ServiceNotification(this, profileName, "service-vpn")
...@@ -282,7 +279,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -282,7 +279,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
} }
override fun onDestroy() { override fun onDestroy() {
super<BaseVpnService>.onDestroy() super.onDestroy()
super<LocalDnsService.Interface>.onDestroy() data.binder.close()
} }
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment