Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

分离 torrent 连接管理逻辑, helps #1606 #1616

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

StageGuard
Copy link
Member

No description provided.

@Him188 Him188 linked an issue Feb 1, 2025 that may be closed by this pull request
@StageGuard StageGuard marked this pull request as ready for review February 2, 2025 05:26
@StageGuard StageGuard requested a review from Him188 February 2, 2025 05:26
@Him188 Him188 added this to the 4.5.0 milestone Feb 2, 2025
}
}

class TestLifecycleOwner : LifecycleOwner {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

androidx.lifecycle.testing.TestLifecycleOwner

image

这个是安卓官方的 2.8.7 版本的, 复制过来变成 commonMain 了

}
}

private class TestLifecycle(private val owner: LifecycleOwner) : Lifecycle() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use androidx

}
}

@RequiresApi(34)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing this

Comment on lines +165 to +192

/**
* APP 已进入前台, 此时需要保证服务可用.
*/
@CallSuper
override fun onResume(owner: LifecycleOwner) {
scope.launch(CoroutineName("TorrentServiceConnection - Lifecycle Resume")) {
isAtForeground.value = true
// 服务已经连接了, 不需要再次处理
if (isServiceConnected.value) return@launch

lock.withLock {
if (isServiceConnected.value) return@launch

logger.debug { "Service is not started, starting." }
startServiceWithRetry()
}
}
}

@CallSuper
override fun onPause(owner: LifecycleOwner) {
scope.launch(CoroutineName("TorrentServiceConnection - Lifecycle Pause")) {
lock.withLock {
isAtForeground.value = false
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个处理有线程安全问题

设想 onResume 被调用, 然后onPause立即被调用分别开了一个协程.
注意到 scope 是并行的 (可以同时执行两个协程).
有三个导致逻辑错误的场景:

  1. scope.launch 是没有顺序保证的, 也就是有可能 onPause 的 launch 会先执行, 然后执行 onResume 的.
  2. onPause 协程先执行了, 但是 onResume 同时也在执行, 有可能 onPause 抢到了 lock, onResume 只能等

isAtForeground 这个 var 非常容易出错. 建议保证只有一个地方会修改这个值. 注意多线程可见性问题.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我的建议是, 不使用 onPause 等 callback API. 用我之前改的那样 launch 一个协程 collectLatest lifecycle flow 处理, 是没有这些问题的.

Comment on lines +194 to +207
private suspend fun startServiceWithRetry(maxAttempts: Int = Int.MAX_VALUE) {
var retries = 0
while (retries <= maxAttempts) {
val startResult = startService()
if (startResult == TorrentServiceConnection.StartResult.FAILED) {
logger.warn { "[#$retries] Failed to start service." }
retries++
delay(7500)
} else {
return
}
}
logger.error { "Failed to start service after $maxAttempts retries." }
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

启动失败应该一直等吧

7500s 太久了

Comment on lines +220 to +226
override suspend fun getBinder(): T {
// track cancellation of [scope]
scope.async {
isServiceConnected.first { it }
}.await()
return binderDeferred.await()
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not thread safe. isServiceConnected.first { it } 访问 binderDeferred.await()isServiceConnected.first { it } 未上锁. 上一行检测到 isServiceConnected 为 true, 但下一行可能 binderDeferred 变了.

* 服务已连接, 服务通信对象一定可用.
* 无论当前 [Lifecycle] 什么状态都要应用新的 [binder].
*/
protected fun onServiceConnected(binder: T) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not safe.

继承者可能会在不适合的时机调用 onServiceConnected. 而此函数没有检查状态是否合理.
继承者不调用此函数的情况也没处理.

* 若返回了 [StartResult.STARTED] 或 [StartResult.FAILED],
* [connected] 将在未来变为 `true`, 届时 [getBinder] 将会立刻返回服务通信接口.
*/
suspend fun startService(): StartResult
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这看起来不应该是需要暴露的接口

/**
* torrent 服务与 APP 通信接口.
*/
interface TorrentServiceConnection<T : Any> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

注释解释一下 T 是什么意思

// 请求 wake lock, 如果在 app 中息屏可以保证 service 正常跑 [acquireWakeLockIntent] 分钟.
context.startService(acquireWakeLockIntent)
} catch (ex: IllegalStateException) {
// 大概率是 ServiceStartForegroundException, 服务已经终止了, 不需要再请求 wakelock.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

catch ForegroundServiceStartNotAllowedException explicitly

Comment on lines +55 to +60
private val startupIntentFilter by lazy { IntentFilter(AniTorrentService.INTENT_STARTUP) }
private val acquireWakeLockIntent by lazy {
Intent(context, AniTorrentService.actualServiceClass).apply {
putExtra("acquireWakeLock", 1.minutes.inWholeMilliseconds)
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is lazy really needed here?

Lazy introduces synchornization overhead (which is on main thread).

Comment on lines +128 to +133
ContextCompat.registerReceiver(
context,
timeExceedLimitReceiver,
timeExceedLimitIntentFilter,
ContextCompat.RECEIVER_NOT_EXPORTED,
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个没有 unregister, 会有问题吗

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

BT 连接稳定性非常不可靠
2 participants