Commit 27589723 authored by Mygod's avatar Mygod

Fix leaks after service switches

parent 24bd70a4
......@@ -61,15 +61,12 @@ object BaseService {
const val CONFIG_FILE = "shadowsocks.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
val processes = GuardedProcessPool()
@Volatile var proxy: 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
val closeReceiver = broadcastReceiver { _, intent ->
when (intent.action) {
......@@ -79,94 +76,118 @@ object BaseService {
}
var closeReceiverRegistered = false
val binder = object : IShadowsocksService.Stub() {
override fun getState(): Int = this@Data.state
override fun getProfileName(): String = proxy?.profile?.name ?: "Idle"
val binder = Binder(this)
override fun registerCallback(cb: IShadowsocksServiceCallback) {
callbacks.register(cb)
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) {
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() =
Core.handler.postAtTime(this::onTimeout, this, SystemClock.uptimeMillis() + 1000)
private fun onTimeout() {
val proxies = listOfNotNull(proxy, udpFallback)
val stats = proxies
.map { Pair(it.profile.id, it.trafficMonitor?.requestUpdate()) }
.filter { it.second != null }
.map { Triple(it.first, it.second!!.first, it.second!!.second) }
if (stats.any { it.third } && state == CONNECTED && bandwidthListeners.isNotEmpty()) {
val sum = stats.fold(TrafficStats()) { a, b -> a + b.second }
val n = callbacks.beginBroadcast()
for (i in 0 until n) try {
val item = callbacks.getBroadcastItem(i)
if (bandwidthListeners.contains(item.asBinder())) {
stats.forEach { (id, stats) -> item.trafficUpdated(id, stats) }
item.trafficUpdated(0, sum)
}
} catch (e: Exception) {
printLog(e)
private fun registerTimeout() =
Core.handler.postAtTime(this::onTimeout, this, SystemClock.uptimeMillis() + 1000)
private fun onTimeout() {
val proxies = listOfNotNull(data!!.proxy, data!!.udpFallback)
val stats = proxies
.map { Pair(it.profile.id, it.trafficMonitor?.requestUpdate()) }
.filter { it.second != null }
.map { Triple(it.first, it.second!!.first, it.second!!.second) }
if (stats.any { it.third } && state == CONNECTED && bandwidthListeners.isNotEmpty()) {
val sum = stats.fold(TrafficStats()) { a, b -> a + b.second }
broadcast { item ->
if (bandwidthListeners.contains(item.asBinder())) {
stats.forEach { (id, stats) -> item.trafficUpdated(id, stats) }
item.trafficUpdated(0, sum)
}
callbacks.finishBroadcast()
}
registerTimeout()
}
registerTimeout()
}
override fun startListeningForBandwidth(cb: IShadowsocksServiceCallback) {
val wasEmpty = bandwidthListeners.isEmpty()
if (bandwidthListeners.add(cb.asBinder())) {
if (wasEmpty) registerTimeout()
if (state != CONNECTED) return
var sum = TrafficStats()
val proxy = proxy ?: return
proxy.trafficMonitor?.out.also { stats ->
cb.trafficUpdated(proxy.profile.id, if (stats == null) sum else {
override fun startListeningForBandwidth(cb: IShadowsocksServiceCallback) {
val wasEmpty = bandwidthListeners.isEmpty()
if (bandwidthListeners.add(cb.asBinder())) {
if (wasEmpty) registerTimeout()
if (state != CONNECTED) return
var sum = TrafficStats()
val proxy = data!!.proxy ?: return
proxy.trafficMonitor?.out.also { stats ->
cb.trafficUpdated(proxy.profile.id, if (stats == null) sum else {
sum += stats
stats
})
}
data!!.udpFallback?.also { udpFallback ->
udpFallback.trafficMonitor?.out.also { stats ->
cb.trafficUpdated(udpFallback.profile.id, if (stats == null) TrafficStats() else {
sum += stats
stats
})
}
udpFallback?.also { udpFallback ->
udpFallback.trafficMonitor?.out.also { stats ->
cb.trafficUpdated(udpFallback.profile.id, if (stats == null) TrafficStats() else {
sum += stats
stats
})
}
}
cb.trafficUpdated(0, sum)
}
cb.trafficUpdated(0, sum)
}
}
override fun stopListeningForBandwidth(cb: IShadowsocksServiceCallback) {
if (bandwidthListeners.remove(cb.asBinder()) && bandwidthListeners.isEmpty()) {
Core.handler.removeCallbacksAndMessages(this)
}
override fun stopListeningForBandwidth(cb: IShadowsocksServiceCallback) {
if (bandwidthListeners.remove(cb.asBinder()) && bandwidthListeners.isEmpty()) {
Core.handler.removeCallbacksAndMessages(this)
}
}
override fun unregisterCallback(cb: IShadowsocksServiceCallback) {
stopListeningForBandwidth(cb) // saves an RPC, and safer
callbacks.unregister(cb)
}
override fun unregisterCallback(cb: IShadowsocksServiceCallback) {
stopListeningForBandwidth(cb) // saves an RPC, and safer
callbacks.unregister(cb)
}
fun changeState(s: Int, msg: String? = null) {
if (state == s && msg == null) return
if (callbacks.registeredCallbackCount > 0) Core.handler.post {
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 stateChanged(s: Int, msg: String?) {
val profileName = profileName
broadcast { it.stateChanged(s, profileName, msg) }
}
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 {
val data: Data
val tag: String
fun createNotification(profileName: String): ServiceNotification
fun onBind(intent: Intent): IBinder? = if (intent.action == Action.SERVICE) data.binder else null
......@@ -206,8 +227,6 @@ object BaseService {
"-U")
}
fun createNotification(profileName: String): ServiceNotification
fun startRunner() {
this as Context
if (Build.VERSION.SDK_INT >= 26) startForegroundService(Intent(this, javaClass))
......@@ -240,20 +259,7 @@ object BaseService {
it.profile.id
}
data.proxy = null
if (ids.isNotEmpty()) Core.handler.post {
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()
}
}
data.binder.trafficPersisted(ids)
// change the state
data.changeState(STOPPED, msg)
......@@ -262,8 +268,6 @@ object BaseService {
if (stopService) stopSelf()
}
val data: Data get() = instances[this]!!
fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val data = data
if (data.state != STOPPED) return Service.START_NOT_STICKY
......@@ -320,15 +324,5 @@ object BaseService {
}
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
* Shadowsocks service at its minimum.
*/
class ProxyService : Service(), BaseService.Interface {
init {
BaseService.register(this)
}
override val data = BaseService.Data(this)
override val tag: String get() = "ShadowsocksProxyService"
override fun createNotification(profileName: String): ServiceNotification =
ServiceNotification(this, profileName, "service-proxy", true)
......@@ -39,7 +36,7 @@ class ProxyService : Service(), BaseService.Interface {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int =
super<BaseService.Interface>.onStartCommand(intent, flags, startId)
override fun onDestroy() {
super<Service>.onDestroy()
super<BaseService.Interface>.onDestroy()
super.onDestroy()
data.binder.close()
}
}
......@@ -27,10 +27,7 @@ import com.github.shadowsocks.preference.DataStore
import java.io.File
class TransproxyService : Service(), LocalDnsService.Interface {
init {
BaseService.register(this)
}
override val data = BaseService.Data(this)
override val tag: String get() = "ShadowsocksTransproxyService"
override fun createNotification(profileName: String): ServiceNotification =
ServiceNotification(this, profileName, "service-transproxy", true)
......@@ -79,7 +76,7 @@ redsocks {
}
override fun onDestroy() {
super<Service>.onDestroy()
super<LocalDnsService.Interface>.onDestroy()
super.onDestroy()
data.binder.close()
}
}
......@@ -108,10 +108,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
override fun getLocalizedMessage() = getString(R.string.reboot_required)
}
init {
BaseService.register(this)
}
override val data = BaseService.Data(this)
override val tag: String get() = "ShadowsocksVpnService"
override fun createNotification(profileName: String): ServiceNotification =
ServiceNotification(this, profileName, "service-vpn")
......@@ -282,7 +279,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
}
override fun onDestroy() {
super<BaseVpnService>.onDestroy()
super<LocalDnsService.Interface>.onDestroy()
super.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