Him188 5 年 前
コミット
d9135cb8a3

+ 7 - 2
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" }

+ 4 - 1
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)

+ 1 - 1
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 <R> retryCatching(n: Int, except: KClass<out Throwable>? = null, block: () -> R): Result<R> {
-    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 {

+ 37 - 18
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt

@@ -107,25 +107,37 @@ abstract class BotImpl<N : BotNetworkHandler> 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)
-                        }
-                        network.withConnectionLock {
-                            /**
-                             * [BotImpl.relogin] only, no [BotNetworkHandler.init]
-                             */
-                            @OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class)
-                            relogin((event as? BotOfflineEvent.Dropped)?.cause)
+                    tailrec suspend fun reconnect() {
+                        retryCatching<Unit>(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
                         }
-                        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<N : BotNetworkHandler> 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)

+ 15 - 13
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)
 }