Abort first login if any error occurred. Fix #1963

This commit is contained in:
Him188 2022-04-06 16:18:10 +01:00
parent a96f9cc8e2
commit e387d4b4a5
13 changed files with 116 additions and 69 deletions

View File

@ -1,10 +1,10 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal.network.components
@ -78,6 +78,7 @@ internal class BotInitProcessorImpl(
override fun setLoginHalted() {
state.compareAndSet(expect = INITIALIZING, update = UNINITIALIZED)
bot.components[SsoProcessor].firstLoginResult.compareAndSet(null, FirstLoginResult.OTHER_FAILURE)
}
override suspend fun init() {
@ -101,7 +102,7 @@ internal class BotInitProcessorImpl(
}
state.value = INITIALIZED
bot.components[SsoProcessor].firstLoginSucceed = true
bot.components[SsoProcessor].firstLoginResult.compareAndSet(null, FirstLoginResult.PASSED)
} catch (e: Throwable) {
setLoginHalted()
throw e

View File

@ -1,10 +1,10 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal.network.components
@ -36,6 +36,7 @@ internal class ConfigPushProcessorImpl(
val e = IllegalStateException("Timeout waiting for ConfigPush.")
bdhSyncer.bdhSession.completeExceptionally(e)
logger.warning { "Missing ConfigPush. Switching server..." }
network.context[SsoProcessor].firstLoginResult.compareAndSet(null, FirstLoginResult.CHANGE_SERVER)
network.context.bot.components[EventDispatcher].broadcastAsync(
BotOfflineEvent.RequireReconnect(
network.context.bot,

View File

@ -1,14 +1,16 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal.network.components
import kotlinx.atomicfu.AtomicRef
import kotlinx.atomicfu.atomic
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.QQAndroidClient
@ -38,7 +40,8 @@ internal interface SsoProcessor {
val client: QQAndroidClient
val ssoSession: SsoSession
var firstLoginSucceed: Boolean
val firstLoginResult: AtomicRef<FirstLoginResult?> // null means just initialized
val firstLoginSucceed: Boolean get() = firstLoginResult.value?.success ?: false
val registerResp: StatSvc.Register.Response?
/**
@ -54,6 +57,15 @@ internal interface SsoProcessor {
companion object : ComponentKey<SsoProcessor>
}
internal enum class FirstLoginResult(
val success: Boolean,
val canRecoverOnFirstLogin: Boolean,
) {
PASSED(true, true),
CHANGE_SERVER(false, true), // by ConfigPush
OTHER_FAILURE(false, false),
}
/**
* Contains secrets for encryption and decryption during a session created by [SsoProcessor] and [PacketCodec].
*
@ -87,8 +99,7 @@ internal class SsoProcessorImpl(
// public
///////////////////////////////////////////////////////////////////////////
@Volatile
override var firstLoginSucceed: Boolean = false
override val firstLoginResult: AtomicRef<FirstLoginResult?> = atomic(null)
@Volatile
override var registerResp: StatSvc.Register.Response? = null

View File

@ -1,10 +1,10 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal.network.handler.selector
@ -14,6 +14,7 @@ import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.yield
import net.mamoe.mirai.internal.network.components.SsoProcessor
import net.mamoe.mirai.internal.network.handler.NetworkHandler
import net.mamoe.mirai.internal.network.handler.NetworkHandlerFactory
import net.mamoe.mirai.internal.network.handler.logger
@ -95,6 +96,14 @@ internal abstract class AbstractKeepAliveNetworkHandlerSelector<H : NetworkHandl
val current = getCurrentInstanceOrNull()
lastNetwork = current
if (current != null) {
if (current.context[SsoProcessor].firstLoginResult.value?.canRecoverOnFirstLogin == false) {
// == null 只表示
// == false 表示第一次登录失败, 且此失败没必要重试
throw current.getLastFailure() ?: error("Failed to login with unknown reason.")
}
}
/**
* @return `false` if failed
*/

View File

@ -275,6 +275,9 @@ internal open class NettyNetworkHandler(
if (error == null) {
this@NettyNetworkHandler.launch { resumeConnection() }
} else {
// failed in SSO stage
context[SsoProcessor].firstLoginResult.compareAndSet(null, FirstLoginResult.OTHER_FAILURE)
if (error is StateSwitchingException && error.new is StateConnecting) {
return@invokeOnCompletion // state already switched, so do not do it again.
}

View File

@ -1,20 +1,19 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal.network.framework.components
import kotlinx.atomicfu.AtomicRef
import kotlinx.atomicfu.atomic
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.components.AccountSecretsImpl
import net.mamoe.mirai.internal.network.components.SsoProcessor
import net.mamoe.mirai.internal.network.components.SsoSession
import net.mamoe.mirai.internal.network.components.createDeviceInfo
import net.mamoe.mirai.internal.network.components.*
import net.mamoe.mirai.internal.network.handler.NetworkHandler
import net.mamoe.mirai.internal.network.handler.logger
import net.mamoe.mirai.internal.network.protocol.data.jce.SvcRespRegister
@ -28,10 +27,9 @@ internal open class TestSsoProcessor(private val bot: QQAndroidBot) : SsoProcess
QQAndroidClient(bot.account, device = deviceInfo, accountSecrets = AccountSecretsImpl(deviceInfo, bot.account))
}
override val ssoSession: SsoSession get() = bot.client
override var firstLoginSucceed: Boolean = false
override val firstLoginResult: AtomicRef<FirstLoginResult?> = atomic(null)
override var registerResp: StatSvc.Register.Response? = null
override suspend fun login(handler: NetworkHandler) {
firstLoginSucceed = true
bot.network.logger.debug { "SsoProcessor.login" }
}

View File

@ -1,10 +1,10 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:OptIn(TestOnly::class)
@ -12,14 +12,18 @@
package net.mamoe.mirai.internal.network.handler
import io.netty.channel.Channel
import net.mamoe.mirai.internal.network.components.FirstLoginResult
import net.mamoe.mirai.internal.network.components.SsoProcessor
import net.mamoe.mirai.internal.network.framework.AbstractNettyNHTest
import net.mamoe.mirai.internal.network.framework.TestNettyNH
import net.mamoe.mirai.internal.network.handler.selector.MaxAttemptsReachedException
import net.mamoe.mirai.internal.network.handler.selector.NetworkException
import net.mamoe.mirai.internal.test.runBlockingUnit
import net.mamoe.mirai.utils.TestOnly
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.assertThrows
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertIs
internal class KeepAliveNetworkHandlerSelectorRealTest : AbstractNettyNHTest() {
@ -48,6 +52,13 @@ internal class KeepAliveNetworkHandlerSelectorRealTest : AbstractNettyNHTest() {
assertThrows<Throwable> { selector.awaitResumeInstance() }
}
// Since #1963, any error during first login will close the bot. So we assume first login succeed to do our test.
@BeforeEach
private fun setFirstLoginPassed() {
assertEquals(null, bot.components[SsoProcessor].firstLoginResult.value)
bot.components[SsoProcessor].firstLoginResult.value = FirstLoginResult.PASSED
}
@Test
fun `should tolerant NetworkException thrown by states`() = runBlockingUnit {
// selector should not tolerant any exception during state initialization, or in the Jobs launched in states.

View File

@ -1,10 +1,10 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal.network.handler
@ -22,6 +22,9 @@ import org.junit.jupiter.api.Test
import java.io.IOException
import kotlin.test.assertFails
/**
* Test whether the selector can recover the connection after first successful login.
*/
internal class SelectorRecoveryTest : AbstractNettyNHTestWithSelector() {
@Test
fun `stop on manual close`() = runBlockingUnit {
@ -79,6 +82,7 @@ internal class SelectorRecoveryTest : AbstractNettyNHTestWithSelector() {
overrideComponents[HeartbeatScheduler] = heartbeatScheduler
bot.login()
// Now first login succeed.
bot.network.context[EventDispatcher].joinBroadcast()
assertState(NetworkHandler.State.OK)

View File

@ -1,15 +1,14 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal.network.impl.netty
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import net.mamoe.mirai.internal.network.components.BotOfflineEventMonitor
@ -62,20 +61,37 @@ internal class NettyBotNormalLoginTest : AbstractNettyNHTest() {
assertFalse(bot.isActive)
}
// #1963
@Test
fun `test network broken`() = runBlockingUnit {
var retryCounter = 0
setSsoProcessor {
eventDispatcher.joinBroadcast()
if (retryCounter++ >= 15) {
return@setSsoProcessor
}
channel.pipeline().fireExceptionCaught(IOException("TestNetworkBroken"))
awaitCancellation() // receive exception from "network"
}
bot.login()
fun `test first login failure with internally handled exceptions`() = runBlockingUnit {
setSsoProcessor { throw IOException("test Connection reset by peer") }
assertFailsWith<IOException>("test Connection reset by peer") { bot.login() }
assertState(NetworkHandler.State.CLOSED)
}
// #1963
@Test
fun `test first login failure with internally handled exceptions2`() = runBlockingUnit {
setSsoProcessor { throw NettyChannelException("test Connection reset by peer") }
assertFailsWith<NettyChannelException>("test Connection reset by peer") { bot.login() }
assertState(NetworkHandler.State.CLOSED)
}
// 经过 #1963 考虑后在初次登录遇到任何错误都终止并传递异常
// @Test
// fun `test network broken`() = runBlockingUnit {
// var retryCounter = 0
// setSsoProcessor {
// eventDispatcher.joinBroadcast()
// if (retryCounter++ >= 15) {
// return@setSsoProcessor
// }
// channel.pipeline().fireExceptionCaught(IOException("TestNetworkBroken"))
// awaitCancellation() // receive exception from "network"
// }
// bot.login()
// }
@Test
fun `test resume after MsfOffline received`() = runBlockingUnit {
bot.login()

View File

@ -16,11 +16,11 @@ import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.event.events.BotOnlineEvent
import net.mamoe.mirai.event.events.BotReloginEvent
import net.mamoe.mirai.internal.network.components.FirstLoginResult
import net.mamoe.mirai.internal.network.components.SsoProcessor
import net.mamoe.mirai.internal.network.framework.AbstractNettyNHTest
import net.mamoe.mirai.internal.network.framework.eventDispatcher
import net.mamoe.mirai.internal.network.framework.setSsoProcessor
import net.mamoe.mirai.internal.network.framework.ssoProcessor
import net.mamoe.mirai.internal.network.handler.NetworkHandler.State.*
import net.mamoe.mirai.internal.test.assertEventBroadcasts
import net.mamoe.mirai.internal.test.assertEventNotBroadcast
@ -50,7 +50,7 @@ internal class NettyHandlerEventTest : AbstractNettyNHTest() {
fun `BotOnlineEvent after successful reconnection`() = runBlockingUnit {
assertEquals(INITIALIZED, network.state)
bot.login()
bot.components[SsoProcessor].firstLoginSucceed = true
bot.components[SsoProcessor].firstLoginResult.value = FirstLoginResult.PASSED
assertEquals(OK, network.state)
eventDispatcher.joinBroadcast() // `login` launches a job which broadcasts the event
assertEventBroadcasts<BotOnlineEvent>(1) {
@ -65,7 +65,7 @@ internal class NettyHandlerEventTest : AbstractNettyNHTest() {
fun `BotOfflineEvent after successful reconnection`() = runBlockingUnit {
assertEquals(INITIALIZED, network.state)
bot.login()
bot.components[SsoProcessor].firstLoginSucceed = true
bot.components[SsoProcessor].firstLoginResult.value = FirstLoginResult.PASSED
assertEquals(OK, network.state)
eventDispatcher.joinBroadcast() // `login` launches a job which broadcasts the event
assertEventBroadcasts<BotOfflineEvent>(1) {
@ -174,7 +174,7 @@ internal class NettyHandlerEventTest : AbstractNettyNHTest() {
assertState(INITIALIZED)
bot.login()
assertState(OK)
network.ssoProcessor.firstLoginSucceed = true
bot.components[SsoProcessor].firstLoginResult.value = FirstLoginResult.PASSED
network.setStateConnecting()
network.resumeConnection()
assertState(OK)

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@ -59,6 +59,7 @@ internal abstract class AbstractNoticeProcessorTest : AbstractNettyNHTest(), Gro
pipeline: NoticeProcessorPipeline = bot.components.noticeProcessorPipeline,
block: UseTestContext.() -> ProtocolStruct
): ProcessResult {
bot.components[SsoProcessor].firstLoginResult.value = FirstLoginResult.PASSED
val handler = LoggingPacketHandlerAdapter(PacketLoggingStrategyImpl(bot), bot.logger)
val context = UseTestContext(attributes.toMutableTypeSafeMap())
return pipeline.process(bot, block(context), context.attributes).also { list ->

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@ -14,7 +14,6 @@ import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.event.events.FriendMessageSyncEvent
import net.mamoe.mirai.event.events.GroupMessageSyncEvent
import net.mamoe.mirai.internal.network.components.NoticePipelineContext.Companion.KEY_FROM_SYNC
import net.mamoe.mirai.internal.network.components.SsoProcessor
import net.mamoe.mirai.message.data.content
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
@ -115,7 +114,6 @@ internal class MessageSyncTest : AbstractNoticeProcessorTest() {
@Test
suspend fun `can receive friend sync from macOS client`() {
suspend fun runTest() = use {
bot.components[SsoProcessor].firstLoginSucceed = true
attributes[KEY_FROM_SYNC] = true
net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.Msg(

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@ -17,7 +17,6 @@ import net.mamoe.mirai.event.events.FriendMessageEvent
import net.mamoe.mirai.event.events.GroupMessageEvent
import net.mamoe.mirai.event.events.GroupTempMessageEvent
import net.mamoe.mirai.internal.network.components.NoticePipelineContext.Companion.KEY_FROM_SYNC
import net.mamoe.mirai.internal.network.components.SsoProcessor
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.message.data.OnlineMessageSource
import net.mamoe.mirai.message.data.PlainText
@ -135,7 +134,6 @@ internal class MessageTest : AbstractNoticeProcessorTest() {
@Test
suspend fun `friend message test`() {
suspend fun runTest() = use(KEY_FROM_SYNC to false) {
bot.components[SsoProcessor].firstLoginSucceed = true
net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.Msg(
msgHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.MsgHead(
fromUin = 1230001,
@ -216,8 +214,6 @@ internal class MessageTest : AbstractNoticeProcessorTest() {
@Test
suspend fun `group temp message test`() {
suspend fun runTest() = use(KEY_FROM_SYNC to false) {
bot.components[SsoProcessor].firstLoginSucceed = true
net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.Msg(
msgHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.MsgHead(
fromUin = 1230001,
@ -312,8 +308,6 @@ internal class MessageTest : AbstractNoticeProcessorTest() {
@Test
suspend fun `group temp message test for issue 1410`() {
suspend fun runTest() = use(KEY_FROM_SYNC to false) {
bot.components[SsoProcessor].firstLoginSucceed = true
net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.Msg(
msgHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.MsgHead(
fromUin = 1230001,