diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt index 40c03b08d..c6d83c83a 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt @@ -37,6 +37,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.login.ConfigPushSvc import net.mamoe.mirai.qqandroid.network.protocol.packet.login.Heartbeat import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc import net.mamoe.mirai.qqandroid.network.protocol.packet.login.WtLogin +import net.mamoe.mirai.qqandroid.utils.NoRouteToHostException import net.mamoe.mirai.qqandroid.utils.PlatformSocket import net.mamoe.mirai.qqandroid.utils.SocketException import net.mamoe.mirai.qqandroid.utils.io.readPacketExact @@ -130,8 +131,12 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler channel.connect(coroutineContext + CoroutineName("Socket"), host, port) break } catch (e: SocketException) { - logger.warning { "No route to host (Mostly due to no Internet connection). Retrying in 3s..." } - delay(3000) + if (e is NoRouteToHostException || e.message?.contains("Network is unreachable") == true) { + logger.warning { "No route to host (Mostly due to no Internet connection). Retrying in 3s..." } + delay(3000) + } else { + throw e + } } } logger.info { "Connected to server $host:$port" } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt index c82eaa35c..fc6b882e2 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt @@ -126,11 +126,14 @@ internal open class QQAndroidClient( lateinit var fileStoragePushFSSvcList: FileStoragePushFSSvcListFuckKotlin internal suspend inline fun useNextServers(crossinline block: suspend (host: String, port: Int) -> Unit) { + if (bot.client.serverList.isEmpty()) { + throw NoServerAvailableException(null) + } retryCatching(bot.client.serverList.size, except = LoginFailedException::class) { val pair = bot.client.serverList.random() kotlin.runCatching { block(pair.first, pair.second) - return + return@retryCatching }.getOrElse { bot.client.serverList.remove(pair) bot.logger.warning(it) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/tryNTimes.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/tryNTimes.kt index 1bcc3b960..6bed29889 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/tryNTimes.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/tryNTimes.kt @@ -24,7 +24,7 @@ internal expect fun Throwable.addSuppressedMirai(e: Throwable) @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE") @kotlin.internal.InlineOnly internal inline fun retryCatching(n: Int, except: KClass? = null, block: () -> R): Result { - require(n >= 0) { "param n for retryCatching must not be negative" } + require(n > 0) { "param n for retryCatching must not be negative" } var exception: Throwable? = null repeat(n) { try { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt index 0188a80fa..badb5a5ac 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt @@ -107,25 +107,37 @@ abstract class BotImpl constructor( } bot.logger.info { "Connection dropped by server or lost, retrying login" } - retryCatching(configuration.reconnectionRetryTimes, - except = LoginFailedException::class) { tryCount, _ -> - if (tryCount != 0) { - delay(configuration.reconnectPeriodMillis) + tailrec suspend fun reconnect() { + retryCatching(configuration.reconnectionRetryTimes, + except = LoginFailedException::class) { tryCount, _ -> + if (tryCount != 0) { + delay(configuration.reconnectPeriodMillis) + } + network.withConnectionLock { + /** + * [BotImpl.relogin] only, no [BotNetworkHandler.init] + */ + @OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class) + relogin((event as? BotOfflineEvent.Dropped)?.cause) + } + logger.info { "Reconnected successfully" } + BotReloginEvent(bot, (event as? BotOfflineEvent.Dropped)?.cause).broadcast() + return + }.getOrElse { + if (it is LoginFailedException && !it.killBot) { + logger.info { "Cannot reconnect" } + logger.warning(it) + logger.info { "Retrying in 3s..." } + delay(3000) + return@getOrElse + } + logger.info { "Cannot reconnect" } + throw it } - network.withConnectionLock { - /** - * [BotImpl.relogin] only, no [BotNetworkHandler.init] - */ - @OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class) - relogin((event as? BotOfflineEvent.Dropped)?.cause) - } - logger.info { "Reconnected successfully" } - BotReloginEvent(bot, (event as? BotOfflineEvent.Dropped)?.cause).broadcast() - return@subscribeAlways - }.getOrElse { - logger.info { "Cannot reconnect" } - throw it + reconnect() } + + reconnect() } is BotOfflineEvent.Active -> { val msg = if (event.cause == null) { @@ -158,7 +170,14 @@ abstract class BotImpl constructor( relogin(null) return } catch (e: LoginFailedException) { - throw e + if (e.killBot) { + throw e + } else { + logger.warning("Login failed. Retrying in 3s...") + _network.closeAndJoin(e) + delay(3000) + continue + } } catch (e: Exception) { network.logger.error(e) _network.closeAndJoin(e) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/LoginFailedException.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/LoginFailedException.kt index bde375a40..cc683da8f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/LoginFailedException.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/LoginFailedException.kt @@ -17,35 +17,37 @@ import net.mamoe.mirai.utils.MiraiExperimentalAPI /** * 在 [登录][Bot.login] 失败时抛出, 可正常地中断登录过程. */ -sealed class LoginFailedException : RuntimeException { - constructor() : super() - constructor(message: String?) : super(message) - constructor(message: String?, cause: Throwable?) : super(message, cause) - constructor(cause: Throwable?) : super(cause) -} +sealed class LoginFailedException constructor( + /** + * 是否可因此登录失败而关闭 [Bot]. 一般是密码错误, 被冻结等异常时. + */ + val killBot: Boolean = false, + message: String? = null, + cause: Throwable? = null +) : RuntimeException(message, cause) /** * 密码输入错误 */ -class WrongPasswordException(message: String?) : LoginFailedException(message) +class WrongPasswordException(message: String?) : LoginFailedException(true, message) /** * 无可用服务器 */ -class NoServerAvailableException(override val cause: Throwable?) : LoginFailedException("no server available") +class NoServerAvailableException(override val cause: Throwable?) : LoginFailedException(false, "no server available") /** * 需要短信验证时抛出. mirai 目前还不支持短信验证. */ @MiraiExperimentalAPI -class UnsupportedSMSLoginException(message: String?) : LoginFailedException(message) +class UnsupportedSMSLoginException(message: String?) : LoginFailedException(true, message) /** * 非 mirai 实现的异常 */ abstract class CustomLoginFailedException : LoginFailedException { - constructor() : super() - constructor(message: String?) : super(message) - constructor(message: String?, cause: Throwable?) : super(message, cause) - constructor(cause: Throwable?) : super(cause) + constructor(killBot: Boolean) : super(killBot) + constructor(killBot: Boolean, message: String?) : super(killBot, message) + constructor(killBot: Boolean, message: String?, cause: Throwable?) : super(killBot, message, cause) + constructor(killBot: Boolean, cause: Throwable?) : super(killBot, cause = cause) } \ No newline at end of file