Commit d9135cb8 authored by Him188's avatar Him188

Fix #248

parent 72fb82c0
...@@ -37,6 +37,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.login.ConfigPushSvc ...@@ -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.Heartbeat
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc 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.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.PlatformSocket
import net.mamoe.mirai.qqandroid.utils.SocketException import net.mamoe.mirai.qqandroid.utils.SocketException
import net.mamoe.mirai.qqandroid.utils.io.readPacketExact import net.mamoe.mirai.qqandroid.utils.io.readPacketExact
...@@ -130,8 +131,12 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -130,8 +131,12 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
channel.connect(coroutineContext + CoroutineName("Socket"), host, port) channel.connect(coroutineContext + CoroutineName("Socket"), host, port)
break break
} catch (e: SocketException) { } catch (e: SocketException) {
logger.warning { "No route to host (Mostly due to no Internet connection). Retrying in 3s..." } if (e is NoRouteToHostException || e.message?.contains("Network is unreachable") == true) {
delay(3000) 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" } logger.info { "Connected to server $host:$port" }
......
...@@ -126,11 +126,14 @@ internal open class QQAndroidClient( ...@@ -126,11 +126,14 @@ internal open class QQAndroidClient(
lateinit var fileStoragePushFSSvcList: FileStoragePushFSSvcListFuckKotlin lateinit var fileStoragePushFSSvcList: FileStoragePushFSSvcListFuckKotlin
internal suspend inline fun useNextServers(crossinline block: suspend (host: String, port: Int) -> Unit) { 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) { retryCatching(bot.client.serverList.size, except = LoginFailedException::class) {
val pair = bot.client.serverList.random() val pair = bot.client.serverList.random()
kotlin.runCatching { kotlin.runCatching {
block(pair.first, pair.second) block(pair.first, pair.second)
return return@retryCatching
}.getOrElse { }.getOrElse {
bot.client.serverList.remove(pair) bot.client.serverList.remove(pair)
bot.logger.warning(it) bot.logger.warning(it)
......
...@@ -24,7 +24,7 @@ internal expect fun Throwable.addSuppressedMirai(e: Throwable) ...@@ -24,7 +24,7 @@ internal expect fun Throwable.addSuppressedMirai(e: Throwable)
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE") @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE")
@kotlin.internal.InlineOnly @kotlin.internal.InlineOnly
internal inline fun <R> retryCatching(n: Int, except: KClass<out Throwable>? = null, block: () -> R): Result<R> { 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 var exception: Throwable? = null
repeat(n) { repeat(n) {
try { try {
......
...@@ -107,25 +107,37 @@ abstract class BotImpl<N : BotNetworkHandler> constructor( ...@@ -107,25 +107,37 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
} }
bot.logger.info { "Connection dropped by server or lost, retrying login" } bot.logger.info { "Connection dropped by server or lost, retrying login" }
retryCatching(configuration.reconnectionRetryTimes, tailrec suspend fun reconnect() {
except = LoginFailedException::class) { tryCount, _ -> retryCatching<Unit>(configuration.reconnectionRetryTimes,
if (tryCount != 0) { except = LoginFailedException::class) { tryCount, _ ->
delay(configuration.reconnectPeriodMillis) if (tryCount != 0) {
} delay(configuration.reconnectPeriodMillis)
network.withConnectionLock { }
/** network.withConnectionLock {
* [BotImpl.relogin] only, no [BotNetworkHandler.init] /**
*/ * [BotImpl.relogin] only, no [BotNetworkHandler.init]
@OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class) */
relogin((event as? BotOfflineEvent.Dropped)?.cause) @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" } reconnect()
BotReloginEvent(bot, (event as? BotOfflineEvent.Dropped)?.cause).broadcast()
return@subscribeAlways
}.getOrElse {
logger.info { "Cannot reconnect" }
throw it
} }
reconnect()
} }
is BotOfflineEvent.Active -> { is BotOfflineEvent.Active -> {
val msg = if (event.cause == null) { val msg = if (event.cause == null) {
...@@ -158,7 +170,14 @@ abstract class BotImpl<N : BotNetworkHandler> constructor( ...@@ -158,7 +170,14 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
relogin(null) relogin(null)
return return
} catch (e: LoginFailedException) { } 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) { } catch (e: Exception) {
network.logger.error(e) network.logger.error(e)
_network.closeAndJoin(e) _network.closeAndJoin(e)
......
...@@ -17,35 +17,37 @@ import net.mamoe.mirai.utils.MiraiExperimentalAPI ...@@ -17,35 +17,37 @@ import net.mamoe.mirai.utils.MiraiExperimentalAPI
/** /**
* 在 [登录][Bot.login] 失败时抛出, 可正常地中断登录过程. * 在 [登录][Bot.login] 失败时抛出, 可正常地中断登录过程.
*/ */
sealed class LoginFailedException : RuntimeException { sealed class LoginFailedException constructor(
constructor() : super() /**
constructor(message: String?) : super(message) * 是否可因此登录失败而关闭 [Bot]. 一般是密码错误, 被冻结等异常时.
constructor(message: String?, cause: Throwable?) : super(message, cause) */
constructor(cause: Throwable?) : super(cause) 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 目前还不支持短信验证. * 需要短信验证时抛出. mirai 目前还不支持短信验证.
*/ */
@MiraiExperimentalAPI @MiraiExperimentalAPI
class UnsupportedSMSLoginException(message: String?) : LoginFailedException(message) class UnsupportedSMSLoginException(message: String?) : LoginFailedException(true, message)
/** /**
* 非 mirai 实现的异常 * 非 mirai 实现的异常
*/ */
abstract class CustomLoginFailedException : LoginFailedException { abstract class CustomLoginFailedException : LoginFailedException {
constructor() : super() constructor(killBot: Boolean) : super(killBot)
constructor(message: String?) : super(message) constructor(killBot: Boolean, message: String?) : super(killBot, message)
constructor(message: String?, cause: Throwable?) : super(message, cause) constructor(killBot: Boolean, message: String?, cause: Throwable?) : super(killBot, message, cause)
constructor(cause: Throwable?) : super(cause) constructor(killBot: Boolean, cause: Throwable?) : super(killBot, cause = cause)
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment