diff --git a/mirai-core-utils/src/commonMain/kotlin/StandardUtils.kt b/mirai-core-utils/src/commonMain/kotlin/StandardUtils.kt index 854a23d01..1b4f41b01 100644 --- a/mirai-core-utils/src/commonMain/kotlin/StandardUtils.kt +++ b/mirai-core-utils/src/commonMain/kotlin/StandardUtils.kt @@ -184,4 +184,14 @@ public fun Throwable.getRootCause(maxDepth: Int = 20): Throwable { if (depth++ >= maxDepth) break } return rootCause ?: this +} + +public fun Throwable.causes(maxDepth: Int = 20): Sequence = sequence { + var depth = 0 + var rootCause: Throwable? = this@causes + while (rootCause?.cause != null) { + yield(rootCause.cause!!) + rootCause = rootCause.cause + if (depth++ >= maxDepth) break + } } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/AbstractBot.kt b/mirai-core/src/commonMain/kotlin/AbstractBot.kt index 3d2b654bb..53ae6c8ce 100644 --- a/mirai-core/src/commonMain/kotlin/AbstractBot.kt +++ b/mirai-core/src/commonMain/kotlin/AbstractBot.kt @@ -25,10 +25,7 @@ import net.mamoe.mirai.internal.network.components.SsoProcessor import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.impl.netty.asCoroutineExceptionHandler import net.mamoe.mirai.supervisorJob -import net.mamoe.mirai.utils.BotConfiguration -import net.mamoe.mirai.utils.MiraiLogger -import net.mamoe.mirai.utils.childScopeContext -import net.mamoe.mirai.utils.info +import net.mamoe.mirai.utils.* import kotlin.collections.set import kotlin.coroutines.CoroutineContext @@ -121,8 +118,9 @@ internal abstract class AbstractBot constructor( if (!components[SsoProcessor].firstLoginSucceed) { this.close() // failed to do first login. } - throw e + throw e.causes().find { it !is CancellationException } ?: e // emit internal errors } + logger.info { "Bot login successful." } } protected abstract fun createNetworkHandler(): NetworkHandler diff --git a/mirai-core/src/commonMain/kotlin/network/components/BotInitProcessor.kt b/mirai-core/src/commonMain/kotlin/network/components/BotInitProcessor.kt index 53bbe44ac..864371a04 100644 --- a/mirai-core/src/commonMain/kotlin/network/components/BotInitProcessor.kt +++ b/mirai-core/src/commonMain/kotlin/network/components/BotInitProcessor.kt @@ -24,7 +24,6 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.MsgSvc import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbGetMsg -import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.info @@ -62,7 +61,8 @@ internal class BotInitProcessorImpl( check(bot.isActive) { "bot is dead therefore network can't init." } context[ContactUpdater].closeAllContacts(CancellationException("re-init")) - val registerResp = registerClientOnline() + val registerResp = + context[SsoProcessor].registerResp ?: error("Internal error: registerResp is not yet available.") bot.launch(CoroutineName("Awaiting ConfigPushSvc.PushReq")) { context[ConfigPushSyncer].awaitSync() @@ -78,10 +78,6 @@ internal class BotInitProcessorImpl( bot.components[SsoProcessor].firstLoginSucceed = true } - private suspend fun registerClientOnline(): StatSvc.Register.Response { - return StatSvc.Register.online(context[SsoProcessor].client).sendAndExpect(bot) - } - private suspend fun syncMessageSvc() { logger.info { "Syncing friend message history..." } withTimeoutOrNull(30000) { diff --git a/mirai-core/src/commonMain/kotlin/network/components/SsoProcessor.kt b/mirai-core/src/commonMain/kotlin/network/components/SsoProcessor.kt index f1a76d2d5..82926722e 100644 --- a/mirai-core/src/commonMain/kotlin/network/components/SsoProcessor.kt +++ b/mirai-core/src/commonMain/kotlin/network/components/SsoProcessor.kt @@ -46,6 +46,7 @@ internal interface SsoProcessor { val ssoSession: SsoSession var firstLoginSucceed: Boolean + val registerResp: StatSvc.Register.Response? /** * The observers to launch jobs for states. @@ -80,8 +81,12 @@ internal class SsoProcessorImpl( // public /////////////////////////////////////////////////////////////////////////// + @Volatile override var firstLoginSucceed: Boolean = false + @Volatile + override var registerResp: StatSvc.Register.Response? = null + @Volatile override var client = createClient(ssoContext.bot) @@ -115,7 +120,12 @@ internal class SsoProcessorImpl( SlowLoginImpl(handler).doLogin() } ssoContext.accountSecretsManager.saveSecrets(ssoContext.account, AccountSecretsImpl(client)) - ssoContext.bot.logger.info { "Login successful." } + registerClientOnline(handler) + ssoContext.bot.logger.info { "SSO login successful." } + } + + private suspend fun registerClientOnline(handler: NetworkHandler): StatSvc.Register.Response { + return StatSvc.Register.online(client).sendAndExpect(handler).also { registerResp = it } } override suspend fun logout(handler: NetworkHandler) { diff --git a/mirai-core/src/commonMain/kotlin/network/handler/NetworkHandlerSupport.kt b/mirai-core/src/commonMain/kotlin/network/handler/NetworkHandlerSupport.kt index 2a3b59828..791c4f488 100644 --- a/mirai-core/src/commonMain/kotlin/network/handler/NetworkHandlerSupport.kt +++ b/mirai-core/src/commonMain/kotlin/network/handler/NetworkHandlerSupport.kt @@ -179,13 +179,18 @@ internal abstract class NetworkHandlerSupport( */ override val onStateChanged: SelectClause1 get() = _stateChangedDeferred.onAwait + protected data class StateSwitchingException( + val old: BaseStateImpl, + val new: BaseStateImpl, + ) : CancellationException("State is switched from $old to $new") + /** - * Calculate [new state][new] and set it as the current. + * Calculate [new state][new] and set it as the current, returning the new state, or `null` if state has concurrently been set to CLOSED. * * You may need to call [BaseStateImpl.resumeConnection] to activate the new state, as states are lazy. */ - protected inline fun setState(crossinline new: () -> S): S = synchronized(this) { - if (_state.correspondingState == NetworkHandler.State.CLOSED) error("Cannot change state while it has already been CLOSED.") + protected fun setState(new: () -> S): S? = synchronized(this) { + if (_state.correspondingState == NetworkHandler.State.CLOSED) return null // error("Cannot change state while it has already been CLOSED.") val stateObserver = context.getOrNull(StateObserver) @@ -208,7 +213,7 @@ internal abstract class NetworkHandlerSupport( // 2. Update state to [state]. This affects selectors. _state = impl // switch state first. selector may be busy selecting. // 3. Cleanup, cancel old states. - old.cancel(CancellationException("State is switched from $old to $impl")) + old.cancel(StateSwitchingException(old, impl)) return impl } diff --git a/mirai-core/src/commonMain/kotlin/network/handler/state/LoggingStateObserver.kt b/mirai-core/src/commonMain/kotlin/network/handler/state/LoggingStateObserver.kt index 7f4e6927f..15d4de081 100644 --- a/mirai-core/src/commonMain/kotlin/network/handler/state/LoggingStateObserver.kt +++ b/mirai-core/src/commonMain/kotlin/network/handler/state/LoggingStateObserver.kt @@ -55,10 +55,7 @@ internal class LoggingStateObserver( logger.debug { "State resumed: ${state.correspondingState}." } }, onFailure = { - logger.debug( - { "State resumed: ${state.correspondingState} ${result.exceptionOrNull()}" }, - result.exceptionOrNull() - ) + logger.debug { "State resumed: ${state.correspondingState} ${result.exceptionOrNull()}" } } ) } diff --git a/mirai-core/src/commonMain/kotlin/network/impl/netty/NettyNetworkHandler.kt b/mirai-core/src/commonMain/kotlin/network/impl/netty/NettyNetworkHandler.kt index 9c59d2b5e..1fdd2a9a3 100644 --- a/mirai-core/src/commonMain/kotlin/network/impl/netty/NettyNetworkHandler.kt +++ b/mirai-core/src/commonMain/kotlin/network/impl/netty/NettyNetworkHandler.kt @@ -27,6 +27,7 @@ import net.mamoe.mirai.internal.network.context.SsoProcessorContext import net.mamoe.mirai.internal.network.handler.NetworkHandler.State import net.mamoe.mirai.internal.network.handler.NetworkHandlerContext import net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport +import net.mamoe.mirai.internal.network.handler.logger import net.mamoe.mirai.internal.network.handler.state.StateObserver import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.utils.ExceptionCollector @@ -181,7 +182,8 @@ internal open class NettyNetworkHandler( override suspend fun resumeConnection0() { setState { StateConnecting(ExceptionCollector()) } - .resumeConnection() + ?.resumeConnection() + ?: this@NettyNetworkHandler.resumeConnection() // concurrently closed by other thread. } override fun toString(): String = "StateInitialized" @@ -217,6 +219,9 @@ internal open class NettyNetworkHandler( }.apply { invokeOnCompletion { error -> if (error != null) { + if (error is StateSwitchingException && error.new is StateConnecting) { + return@invokeOnCompletion // already been switching to CONNECTING + } setState { StateConnecting( collectiveExceptions.apply { collect(error) }, @@ -239,7 +244,8 @@ internal open class NettyNetworkHandler( connectResult.await() // propagates exceptions val connection = connection.await() setState { StateLoading(connection) } - .resumeConnection() + ?.resumeConnection() + ?: this@NettyNetworkHandler.resumeConnection() // concurrently closed by other thread. } override fun toString(): String = "StateConnecting" @@ -294,6 +300,12 @@ internal open class NettyNetworkHandler( } } } + }.apply { + invokeOnCompletion { e -> + if (e != null) { + logger.debug { "x" } + } + } } private val configPush = launch(CoroutineName("ConfigPush sync")) { diff --git a/mirai-core/src/commonTest/kotlin/network/framework/AbstractRealNetworkHandlerTest.kt b/mirai-core/src/commonTest/kotlin/network/framework/AbstractRealNetworkHandlerTest.kt index e0368e49d..c47d47104 100644 --- a/mirai-core/src/commonTest/kotlin/network/framework/AbstractRealNetworkHandlerTest.kt +++ b/mirai-core/src/commonTest/kotlin/network/framework/AbstractRealNetworkHandlerTest.kt @@ -25,6 +25,7 @@ import net.mamoe.mirai.internal.network.handler.NetworkHandler.State import net.mamoe.mirai.internal.network.handler.NetworkHandlerContextImpl import net.mamoe.mirai.internal.network.handler.NetworkHandlerFactory import net.mamoe.mirai.internal.network.handler.state.StateObserver +import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.debug @@ -72,6 +73,7 @@ internal abstract class AbstractRealNetworkHandlerTest : Abs override val client: QQAndroidClient get() = bot.client override val ssoSession: SsoSession get() = bot.client override var firstLoginSucceed: Boolean = false + override var registerResp: StatSvc.Register.Response? = null override fun createObserverChain(): StateObserver = get(StateObserver) override suspend fun login(handler: NetworkHandler) { nhEvents.add(NHEvent.Login)