Extract network logic to separate classes to speed up inspections

This commit is contained in:
Him188 2021-01-05 18:16:20 +08:00
parent 7d43ab20f8
commit 44053ae85f

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2019-2020 Mamoe Technologies and contributors. * Copyright 2019-2021 Mamoe Technologies and contributors.
* *
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. * 此源代码的使用受 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. * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@ -75,7 +75,7 @@ internal abstract class AbstractBot<N : BotNetworkHandler> constructor(
internal var _isConnecting: Boolean = false internal var _isConnecting: Boolean = false
override val isOnline: Boolean get() = _network.areYouOk() override val isOnline: Boolean get() = _network.areYouOk()
override val eventChannel: EventChannel<BotEvent> = final override val eventChannel: EventChannel<BotEvent> =
GlobalEventChannel.filterIsInstance<BotEvent>().filter { it.bot === this@AbstractBot } GlobalEventChannel.filterIsInstance<BotEvent>().filter { it.bot === this@AbstractBot }
val otherClientsLock = Mutex() // lock sync val otherClientsLock = Mutex() // lock sync
@ -91,10 +91,7 @@ internal abstract class AbstractBot<N : BotNetworkHandler> constructor(
@OptIn(ExperimentalTime::class) @OptIn(ExperimentalTime::class)
@Suppress("unused") @Suppress("unused")
private val offlineListener: Listener<BotOfflineEvent> = private val offlineListener: Listener<BotOfflineEvent> =
this@AbstractBot.subscribeAlways(concurrency = Listener.ConcurrencyKind.LOCKED) { event -> this@AbstractBot.eventChannel.subscribeAlways(concurrency = Listener.ConcurrencyKind.LOCKED) { event ->
if (event.bot != this@AbstractBot) {
return@subscribeAlways
}
if (!event.bot.isActive) { if (!event.bot.isActive) {
// bot closed // bot closed
return@subscribeAlways return@subscribeAlways
@ -130,49 +127,10 @@ internal abstract class AbstractBot<N : BotNetworkHandler> constructor(
} else serverList.removeAt(0) } else serverList.removeAt(0)
} }
var failed = false val success: Boolean
val time = measureTime { val time = measureTime { success = Reconnect().reconnect(event) }
tailrec suspend fun reconnect() {
retryCatching<Unit>(
configuration.reconnectionRetryTimes,
except = LoginFailedException::class
) { tryCount, _ ->
if (tryCount != 0) {
delay(configuration.reconnectPeriodMillis)
}
network.withConnectionLock {
/**
* [AbstractBot.relogin] only, no [BotNetworkHandler.init]
*/
@OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class)
relogin((event as? BotOfflineEvent.Dropped)?.cause)
}
launch {
BotReloginEvent(
bot,
(event as? BotOfflineEvent.CauseAware)?.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 due to fatal error." }
bot.cancel(CancellationException("Cannot reconnect due to fatal error.", it))
failed = true
return
}
reconnect()
}
reconnect()
}
if (!failed) { if (success) {
logger.info { "Reconnected successfully in ${time.toHumanReadableString()}" } logger.info { "Reconnected successfully in ${time.toHumanReadableString()}" }
} }
} }
@ -181,77 +139,112 @@ internal abstract class AbstractBot<N : BotNetworkHandler> constructor(
val msg = if (cause == null) { val msg = if (cause == null) {
"" ""
} else { } else {
" with exception: " + cause.message " with exception: $cause"
} }
bot.logger.info { "Bot is closed manually: $msg" } bot.logger.info("Bot is closed manually: $msg", cause)
bot.cancel(CancellationException(event.toString())) bot.cancel(CancellationException("Bot is closed manually: $msg", cause))
} }
is BotOfflineEvent.Force -> { is BotOfflineEvent.Force -> {
bot.logger.info { "Connection occupied by another android device: ${event.message}" } bot.logger.info { "Connection occupied by another android device: ${event.message}" }
bot.cancel(ForceOfflineException(event.toString())) bot.cancel(ForceOfflineException("Connection occupied by another android device: ${event.message}"))
} }
} }
} }
private inner class Reconnect {
suspend fun reconnect(event: BotOfflineEvent): Boolean {
while (true) {
retryCatching<Unit>(
configuration.reconnectionRetryTimes,
except = LoginFailedException::class
) { tryCount, _ ->
if (tryCount != 0) {
delay(configuration.reconnectPeriodMillis)
}
network.withConnectionLock {
/**
* [AbstractBot.relogin] only, no [BotNetworkHandler.init]
*/
@OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class)
relogin((event as? BotOfflineEvent.Dropped)?.cause)
}
launch {
BotReloginEvent(bot, (event as? BotOfflineEvent.CauseAware)?.cause).broadcast()
}
return true
}.getOrElse { exception ->
if (exception is LoginFailedException && !exception.killBot) {
logger.info { "Cannot reconnect." }
logger.warning(exception)
logger.info { "Retrying in 3s..." }
delay(3000)
return@getOrElse
}
logger.info { "Cannot reconnect due to fatal error." }
bot.cancel(CancellationException("Cannot reconnect due to fatal error.", exception))
return false
}
}
}
}
/** /**
* **Exposed public API** * 仅用在 [login]
* [AbstractBot.relogin] && [BotNetworkHandler.init]
*/ */
final override suspend fun login() { private inner class Login {
private suspend fun doRelogin() {
while (true) {
_network = createNetworkHandler(coroutineContext)
try {
_isConnecting = true
@OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class)
relogin(null)
return
} catch (e: Exception) {
if (e is LoginFailedException) {
if (e.killBot) throw e
} else {
network.logger.error(e)
}
logger.warning { "Login failed. Retrying in 3s... (rootCause=${e.rootCause})" }
_network.closeAndJoin(e)
delay(3000)
continue
} finally {
_isConnecting = false
}
// unreachable here
}
}
private suspend fun doInit() {
retryCatching(5) { count, lastException ->
if (count != 0) {
if (!isActive) {
logger.error("Cannot init due to fatal error")
throw lastException ?: error("<No lastException>")
}
logger.warning { "Init failed. Retrying in 3s... (rootCause=${lastException?.rootCause})" }
delay(3000)
}
_network.init()
}.getOrElse {
logger.error { "Cannot init. some features may be affected" }
throw it // abort
}
}
@ThisApiMustBeUsedInWithConnectionLockBlock @ThisApiMustBeUsedInWithConnectionLockBlock
suspend fun reinitializeNetworkHandler(cause: Throwable?) { private suspend fun reinitializeNetworkHandler(cause: Throwable?) {
suspend fun doRelogin() {
while (true) {
_network = createNetworkHandler(this.coroutineContext)
try {
_isConnecting = true
@OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class)
relogin(null)
return
} catch (e: Exception) {
if (e is LoginFailedException) {
if (e.killBot) {
throw e
}
} else {
network.logger.error(e)
}
logger.warning { "Login failed. Retrying in 3s... (rootCause=${e.rootCause})" }
_network.closeAndJoin(e)
delay(3000)
continue
} finally {
_isConnecting = false
}
// unreachable here
}
}
suspend fun doInit() {
retryCatching(5) { count, lastException ->
if (count != 0) {
if (!isActive) {
logger.error("Cannot init due to fatal error")
throw lastException ?: error("<No lastException>")
}
logger.warning { "Init failed. Retrying in 3s... (rootCause=${lastException?.rootCause})" }
delay(3000)
}
_network.init()
}.getOrElse {
logger.error { "Cannot init. some features may be affected" }
throw it // abort
}
}
// logger.info("Initializing BotNetworkHandler") // logger.info("Initializing BotNetworkHandler")
if (::_network.isInitialized) { if (::_network.isInitialized) {
_network.cancel(CancellationException("manual re-login", cause = cause)) _network.cancel(CancellationException("manual re-login", cause = cause))
BotReloginEvent(this, cause).broadcast() BotReloginEvent(this@AbstractBot, cause).broadcast()
doRelogin() doRelogin()
return return
} }
@ -260,17 +253,28 @@ internal abstract class AbstractBot<N : BotNetworkHandler> constructor(
doInit() doInit()
} }
logger.info { "Logging in..." } suspend fun doLogin() {
if (::_network.isInitialized) { logger.info { "Logging in..." }
network.withConnectionLock { if (::_network.isInitialized) {
network.withConnectionLock {
@OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class)
reinitializeNetworkHandler(null)
}
} else {
@OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class) @OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class)
reinitializeNetworkHandler(null) reinitializeNetworkHandler(null)
} }
} else { logger.info { "Login successful" }
@OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class)
reinitializeNetworkHandler(null)
} }
logger.info { "Login successful" } }
/**
* **Exposed public API**
* [AbstractBot.relogin] && [BotNetworkHandler.init]
*/
final override suspend fun login() {
Login().doLogin()
} }
protected abstract fun createNetworkHandler(coroutineContext: CoroutineContext): N protected abstract fun createNetworkHandler(coroutineContext: CoroutineContext): N