This commit is contained in:
Him188 2020-04-22 19:57:38 +08:00
parent 72fb82c01f
commit d9135cb8a3
5 changed files with 64 additions and 35 deletions

View File

@ -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" }

View File

@ -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)

View File

@ -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 {

View File

@ -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)
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
}
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<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)

View File

@ -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)
}