mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-05 07:30:09 +08:00
Rework re-init, fix #282
This commit is contained in:
parent
40b8cabd5f
commit
9b4006222f
@ -85,6 +85,7 @@ kotlin {
|
||||
val jvmMain by getting {
|
||||
dependencies {
|
||||
runtimeOnly(files("build/classes/kotlin/jvm/main")) // classpath is not properly set by IDE
|
||||
// api(kotlinx("coroutines-debug", "1.3.5"))
|
||||
api(kotlinx("serialization-runtime", Versions.Kotlin.serialization))
|
||||
//api(kotlinx("serialization-protobuf", Versions.Kotlin.serialization))
|
||||
|
||||
|
@ -58,7 +58,6 @@ import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
import kotlin.jvm.Synchronized
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.random.Random
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.FriendInfo as JceFriendInfo
|
||||
@ -294,7 +293,7 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
}
|
||||
|
||||
override fun createNetworkHandler(coroutineContext: CoroutineContext): QQAndroidBotNetworkHandler {
|
||||
return QQAndroidBotNetworkHandler(this as QQAndroidBot)
|
||||
return QQAndroidBotNetworkHandler(coroutineContext, this as QQAndroidBot)
|
||||
}
|
||||
|
||||
override val groups: ContactList<Group> = ContactList(LockFreeLinkedList())
|
||||
|
@ -14,6 +14,8 @@ package net.mamoe.mirai.qqandroid.contact
|
||||
|
||||
import kotlinx.atomicfu.AtomicInt
|
||||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.io.core.Closeable
|
||||
import net.mamoe.mirai.LowLevelAPI
|
||||
import net.mamoe.mirai.contact.Friend
|
||||
@ -59,10 +61,12 @@ internal inline fun Friend.checkIsFriendImpl(): FriendImpl {
|
||||
|
||||
internal class FriendImpl(
|
||||
bot: QQAndroidBot,
|
||||
override val coroutineContext: CoroutineContext,
|
||||
coroutineContext: CoroutineContext,
|
||||
override val id: Long,
|
||||
private val friendInfo: FriendInfo
|
||||
) : Friend() {
|
||||
override val coroutineContext: CoroutineContext = coroutineContext + SupervisorJob(coroutineContext[Job])
|
||||
|
||||
@Suppress("unused") // bug
|
||||
val lastMessageSequence: AtomicInt = atomic(-1)
|
||||
|
||||
|
@ -12,6 +12,8 @@
|
||||
|
||||
package net.mamoe.mirai.qqandroid.contact
|
||||
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.io.core.Closeable
|
||||
import net.mamoe.mirai.LowLevelAPI
|
||||
@ -28,12 +30,10 @@ import net.mamoe.mirai.qqandroid.message.MessageSourceToGroupImpl
|
||||
import net.mamoe.mirai.qqandroid.message.ensureSequenceIdAvailable
|
||||
import net.mamoe.mirai.qqandroid.message.firstIsInstanceOrNull
|
||||
import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImgStore
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.ProfileService
|
||||
import net.mamoe.mirai.qqandroid.utils.encodeToString
|
||||
import net.mamoe.mirai.qqandroid.utils.estimateLength
|
||||
import net.mamoe.mirai.utils.*
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
@ -61,13 +61,16 @@ internal fun Group.checkIsGroupImpl() {
|
||||
@OptIn(MiraiExperimentalAPI::class, LowLevelAPI::class)
|
||||
@Suppress("PropertyName")
|
||||
internal class GroupImpl(
|
||||
bot: QQAndroidBot, override val coroutineContext: CoroutineContext,
|
||||
bot: QQAndroidBot,
|
||||
coroutineContext: CoroutineContext,
|
||||
override val id: Long,
|
||||
groupInfo: GroupInfo,
|
||||
members: Sequence<MemberInfo>
|
||||
) : Group() {
|
||||
companion object;
|
||||
|
||||
override val coroutineContext: CoroutineContext = coroutineContext + SupervisorJob(coroutineContext[Job])
|
||||
|
||||
override val bot: QQAndroidBot by bot.unsafeWeakRef()
|
||||
|
||||
val uin: Long = groupInfo.uin
|
||||
|
@ -13,6 +13,8 @@ package net.mamoe.mirai.qqandroid.contact
|
||||
|
||||
import kotlinx.atomicfu.AtomicInt
|
||||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import net.mamoe.mirai.LowLevelAPI
|
||||
import net.mamoe.mirai.contact.*
|
||||
@ -42,10 +44,11 @@ import kotlin.jvm.JvmSynthetic
|
||||
internal class MemberImpl constructor(
|
||||
val qq: FriendImpl, // 不要 WeakRef
|
||||
group: GroupImpl,
|
||||
override val coroutineContext: CoroutineContext,
|
||||
coroutineContext: CoroutineContext,
|
||||
memberInfo: MemberInfo
|
||||
) : Member() {
|
||||
override val group: GroupImpl by group.unsafeWeakRef()
|
||||
override val coroutineContext: CoroutineContext = coroutineContext + SupervisorJob(coroutineContext[Job])
|
||||
|
||||
@Suppress("unused") // false positive
|
||||
val lastMessageSequence: AtomicInt = atomic(-1)
|
||||
|
@ -46,18 +46,19 @@ import net.mamoe.mirai.qqandroid.utils.retryCatching
|
||||
import net.mamoe.mirai.utils.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.jvm.Volatile
|
||||
import kotlin.random.Random
|
||||
import kotlin.time.ExperimentalTime
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler() {
|
||||
internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bot: QQAndroidBot) : BotNetworkHandler() {
|
||||
override val bot: QQAndroidBot by bot.unsafeWeakRef()
|
||||
override val supervisor: CompletableJob = SupervisorJob(bot.coroutineContext[Job])
|
||||
override val supervisor: CompletableJob = SupervisorJob(coroutineContext[Job])
|
||||
override val logger: MiraiLogger get() = bot.configuration.networkLoggerSupplier(this)
|
||||
|
||||
override val coroutineContext: CoroutineContext = bot.coroutineContext + CoroutineExceptionHandler { _, throwable ->
|
||||
override val coroutineContext: CoroutineContext = coroutineContext + CoroutineExceptionHandler { _, throwable ->
|
||||
logger.error("Exception in NetworkHandler", throwable)
|
||||
}
|
||||
} + supervisor
|
||||
|
||||
private lateinit var channel: PlatformSocket
|
||||
|
||||
@ -209,10 +210,17 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
internal var pendingIncomingPackets: LockFreeLinkedList<KnownPacketFactories.IncomingPacket<*>>? =
|
||||
LockFreeLinkedList()
|
||||
|
||||
private var initFriendOk = false
|
||||
private var initGroupOk = false
|
||||
|
||||
/**
|
||||
* Don't use concurrently
|
||||
*/
|
||||
suspend fun reloadFriendList() {
|
||||
if (initFriendOk) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.info { "开始加载好友信息" }
|
||||
var currentFriendCount = 0
|
||||
var totalFriendCount: Short
|
||||
@ -242,6 +250,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
// delay(200)
|
||||
}
|
||||
logger.info { "好友列表加载完成, 共 ${currentFriendCount}个" }
|
||||
initFriendOk = true
|
||||
}
|
||||
|
||||
suspend fun StTroopNum.reloadGroup() {
|
||||
@ -280,20 +289,25 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
}
|
||||
|
||||
suspend fun reloadGroupList() {
|
||||
if (initGroupOk) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.info { "开始加载群组列表与群成员列表" }
|
||||
val troopListData = FriendList.GetTroopListSimplify(bot.client)
|
||||
.sendAndExpect<FriendList.GetTroopListSimplify.Response>(retry = 3)
|
||||
.sendAndExpect<FriendList.GetTroopListSimplify.Response>(retry = 5)
|
||||
|
||||
troopListData.groups.chunked(50).forEach { chunk ->
|
||||
troopListData.groups.chunked(30).forEach { chunk ->
|
||||
coroutineScope {
|
||||
chunk.forEach {
|
||||
launch {
|
||||
retryCatching(3) { it.reloadGroup() }.getOrThrow()
|
||||
retryCatching(5) { it.reloadGroup() }.getOrThrow()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.info { "群组列表与群成员加载完成, 共 ${troopListData.groups.size}个" }
|
||||
initGroupOk = true
|
||||
}
|
||||
|
||||
@OptIn(MiraiExperimentalAPI::class, ExperimentalTime::class)
|
||||
@ -302,8 +316,12 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
check(this@QQAndroidBotNetworkHandler.isActive) { "network is dead therefore can't init" }
|
||||
|
||||
CancellationException("re-init").let { reInitCancellationException ->
|
||||
bot.friends.delegate.clear { it.cancel(reInitCancellationException) }
|
||||
bot.groups.delegate.clear { it.cancel(reInitCancellationException) }
|
||||
if (!initFriendOk) {
|
||||
bot.friends.delegate.clear { it.cancel(reInitCancellationException) }
|
||||
}
|
||||
if (!initGroupOk) {
|
||||
bot.groups.delegate.clear { it.cancel(reInitCancellationException) }
|
||||
}
|
||||
}
|
||||
|
||||
if (!pendingEnabled) {
|
||||
@ -313,7 +331,12 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
|
||||
coroutineScope {
|
||||
launch { reloadFriendList() }
|
||||
launch { reloadGroupList() }
|
||||
launch {
|
||||
if (Random.nextInt() > 50) {
|
||||
error("boom")
|
||||
}
|
||||
reloadGroupList()
|
||||
}
|
||||
}
|
||||
|
||||
this@QQAndroidBotNetworkHandler.launch {
|
||||
@ -325,7 +348,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
when (resp) {
|
||||
null -> logger.info { "Missing ConfigPushSvc.PushReq." }
|
||||
is ConfigPushSvc.PushReq.PushReqResponse.Success -> {
|
||||
logger.info { "ConfigPushSvc.PushReq: success" }
|
||||
logger.info { "ConfigPushSvc.PushReq: Success" }
|
||||
}
|
||||
is ConfigPushSvc.PushReq.PushReqResponse.ChangeServer -> {
|
||||
logger.info { "Server requires reconnect." }
|
||||
@ -340,28 +363,39 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
}
|
||||
}
|
||||
|
||||
logger.info { "Syncing friend message history..." }
|
||||
withTimeoutOrNull(30000) {
|
||||
launch { syncFromEvent<MessageSvc.PbGetMsg.GetMsgSuccess, Unit> { Unit } }
|
||||
MessageSvc.PbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendAndExpect<Packet>()
|
||||
} ?: error("timeout syncing friend message history")
|
||||
logger.info { "Syncing friend message history: Success" }
|
||||
|
||||
bot.firstLoginSucceed = true
|
||||
|
||||
_pendingEnabled.value = false
|
||||
pendingIncomingPackets?.forEach {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
KnownPacketFactories.handleIncomingPacket(
|
||||
it as KnownPacketFactories.IncomingPacket<Packet>,
|
||||
bot,
|
||||
it.flag2,
|
||||
it.consumer
|
||||
)
|
||||
runCatching {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
KnownPacketFactories.handleIncomingPacket(
|
||||
it as KnownPacketFactories.IncomingPacket<Packet>,
|
||||
bot,
|
||||
it.flag2,
|
||||
it.consumer
|
||||
)
|
||||
}.getOrElse {
|
||||
logger.error("Exception on processing pendingIncomingPackets", it)
|
||||
}
|
||||
}
|
||||
val list = pendingIncomingPackets
|
||||
pendingIncomingPackets = null // release, help gc
|
||||
list?.clear() // help gc
|
||||
|
||||
BotOnlineEvent(bot).broadcast()
|
||||
runCatching {
|
||||
BotOnlineEvent(bot).broadcast()
|
||||
}.getOrElse {
|
||||
logger.error("Exception on broadcasting BotOnlineEvent", it)
|
||||
}
|
||||
|
||||
Unit // dont remove. can help type inference
|
||||
}
|
||||
|
||||
@ -583,9 +617,9 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
* 不推荐使用它, 可能产生意外的情况.
|
||||
*/
|
||||
suspend fun OutgoingPacket.sendWithoutExpect() {
|
||||
check(bot.isActive) { "bot is dead therefore can't send any packet" }
|
||||
check(bot.isActive) { "bot is dead therefore can't send ${this.commandName}" }
|
||||
check(this@QQAndroidBotNetworkHandler.isActive) { "network is dead therefore can't send any packet" }
|
||||
logger.verbose("Send: ${this.commandName}")
|
||||
logger.verbose { "Send: ${this.commandName}" }
|
||||
channel.send(delegate)
|
||||
}
|
||||
|
||||
@ -598,7 +632,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
require(timeoutMillis > 100) { "timeoutMillis must > 100" }
|
||||
require(retry >= 0) { "retry must >= 0" }
|
||||
|
||||
check(bot.isActive) { "bot is dead therefore can't send any packet" }
|
||||
check(bot.isActive) { "bot is dead therefore can't send ${this.commandName}" }
|
||||
check(this@QQAndroidBotNetworkHandler.isActive) { "network is dead therefore can't send any packet" }
|
||||
|
||||
suspend fun doSendAndReceive(handler: PacketListener, data: Any, length: Int): E {
|
||||
@ -607,8 +641,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
is ByteReadPacket -> channel.send(data)
|
||||
else -> error("Internal error: unexpected data type: ${data::class.simpleName}")
|
||||
}
|
||||
|
||||
logger.verbose { "Send done: $commandName" }
|
||||
logger.verbose { "Send: $commandName" }
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return withTimeoutOrNull(timeoutMillis) {
|
||||
@ -651,6 +684,12 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
this.commandName == commandName && this.sequenceId == sequenceId
|
||||
}
|
||||
|
||||
init {
|
||||
this.supervisor.invokeOnCompletion {
|
||||
close(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun close(cause: Throwable?) {
|
||||
if (::channel.isInitialized) {
|
||||
channel.close()
|
||||
|
@ -173,7 +173,7 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
|
||||
if (e.killBot) {
|
||||
throw e
|
||||
} else {
|
||||
logger.warning("Login failed. Retrying in 3s...")
|
||||
logger.warning { "Login failed. Retrying in 3s..." }
|
||||
_network.closeAndJoin(e)
|
||||
delay(3000)
|
||||
continue
|
||||
@ -182,35 +182,34 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
|
||||
network.logger.error(e)
|
||||
_network.closeAndJoin(e)
|
||||
}
|
||||
logger.warning("Login failed. Retrying in 3s...")
|
||||
logger.warning { "Login failed. Retrying in 3s..." }
|
||||
delay(3000)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun doInit() {
|
||||
retryCatching(2) { count, lastException ->
|
||||
retryCatching(5) { count, lastException ->
|
||||
if (count != 0) {
|
||||
if (!isActive) {
|
||||
logger.error("Cannot init due to fatal error")
|
||||
if (lastException == null) {
|
||||
logger.error("<no exception>")
|
||||
} else {
|
||||
logger.error(lastException)
|
||||
}
|
||||
throw lastException ?: error("<No lastException>")
|
||||
}
|
||||
logger.warning("Init failed. Retrying in 3s...")
|
||||
logger.warning { "Init failed. Retrying in 3s..." }
|
||||
delay(3000)
|
||||
}
|
||||
|
||||
_network.init()
|
||||
}.getOrElse {
|
||||
network.logger.error(it)
|
||||
logger.error("Cannot init. some features may be affected")
|
||||
logger.error { "Cannot init. some features may be affected" }
|
||||
throw it // abort
|
||||
}
|
||||
}
|
||||
|
||||
// logger.info("Initializing BotNetworkHandler")
|
||||
|
||||
if (::_network.isInitialized) {
|
||||
_network.cancel(CancellationException("manual re-login", cause = cause))
|
||||
|
||||
BotReloginEvent(this, cause).broadcast()
|
||||
doRelogin()
|
||||
return
|
||||
@ -220,7 +219,7 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
|
||||
doInit()
|
||||
}
|
||||
|
||||
logger.info("Logging in...")
|
||||
logger.info { "Logging in..." }
|
||||
if (::_network.isInitialized) {
|
||||
network.withConnectionLock {
|
||||
@OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class)
|
||||
@ -230,7 +229,7 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
|
||||
@OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class)
|
||||
reinitializeNetworkHandler(null)
|
||||
}
|
||||
logger.info("Login successful")
|
||||
logger.info { "Login successful" }
|
||||
}
|
||||
|
||||
protected abstract fun createNetworkHandler(coroutineContext: CoroutineContext): N
|
||||
@ -241,7 +240,7 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
|
||||
init {
|
||||
coroutineContext[Job]!!.invokeOnCompletion { throwable ->
|
||||
network.close(throwable)
|
||||
offlineListener.cancel(CancellationException("bot cancelled", throwable))
|
||||
offlineListener.cancel(CancellationException("Bot cancelled", throwable))
|
||||
|
||||
groups.delegate.clear() // job is cancelled, so child jobs are to be cancelled
|
||||
friends.delegate.clear()
|
||||
|
@ -40,6 +40,7 @@ import net.mamoe.mirai.utils.WeakRefProperty
|
||||
*
|
||||
* @suppress 此为**内部 API**, 可能在任意时刻被改动, 且不会给出任何警告.
|
||||
*/
|
||||
@MiraiInternalAPI
|
||||
@Suppress("PropertyName")
|
||||
abstract class BotNetworkHandler : CoroutineScope {
|
||||
|
||||
@ -122,6 +123,7 @@ abstract class BotNetworkHandler : CoroutineScope {
|
||||
}
|
||||
}
|
||||
|
||||
@MiraiInternalAPI
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
suspend fun BotNetworkHandler.closeAndJoin(cause: Throwable? = null) {
|
||||
this.close(cause)
|
||||
|
@ -31,6 +31,7 @@ open class BotConfiguration {
|
||||
/**
|
||||
* 网络层日志构造器
|
||||
*/
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
var networkLoggerSupplier: ((BotNetworkHandler) -> MiraiLogger) = { DefaultLogger("Network(${it.bot.id})") }
|
||||
|
||||
/**
|
||||
@ -85,6 +86,7 @@ open class BotConfiguration {
|
||||
/**
|
||||
* 不显示网络日志
|
||||
*/
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
@ConfigurationDsl
|
||||
fun noNetworkLog() {
|
||||
networkLoggerSupplier = { _: BotNetworkHandler -> SilentLogger }
|
||||
|
Loading…
Reference in New Issue
Block a user