perf(utils): 优化自动释放钩子的资源引用.

原本自动释放钩子对资源的引用, 可能会出现资源已经被关闭, 但仍然无法被 GC 回收的问题.
此次改动, 将会让钩子在关闭资源后, 将资源从列表中移除.
虽然, 自动释放钩子设计上仅会被 System.exit 动作触发, 但保险起见还是加上这个改动.
This commit is contained in:
LamGC 2022-05-05 16:52:29 +08:00
parent 830f05c90a
commit 478480014a
Signed by: LamGC
GPG Key ID: 6C5AE2A913941E1D
2 changed files with 75 additions and 14 deletions

View File

@ -81,17 +81,20 @@ private object UtilsInternal {
val autoCloseableSet = mutableSetOf<AutoCloseable>() val autoCloseableSet = mutableSetOf<AutoCloseable>()
init { init {
Runtime.getRuntime().addShutdownHook(Thread({ Runtime.getRuntime().addShutdownHook(Thread(this::doCloseResources, "Shutdown-AutoCloseable"))
}
fun doCloseResources() {
log.debug { "Closing registered hook resources..." } log.debug { "Closing registered hook resources..." }
autoCloseableSet.forEach { autoCloseableSet.removeIf {
try { try {
it.close() it.close()
} catch (e: Exception) { } catch (e: Exception) {
log.error(e) { "An exception occurred while closing the resource. (Resource: `$it`)" } log.error(e) { "An exception occurred while closing the resource. (Resource: `$it`)" }
} }
true
} }
log.debug { "All registered hook resources have been closed." } log.debug { "All registered hook resources have been closed." }
}, "Shutdown-AutoCloseable"))
} }
} }

View File

@ -1,18 +1,21 @@
package net.lamgc.scalabot.util package net.lamgc.scalabot.util
import io.mockk.every
import io.mockk.justRun
import io.mockk.mockk
import io.mockk.verify
import net.lamgc.scalabot.ExtensionPackageFinder import net.lamgc.scalabot.ExtensionPackageFinder
import net.lamgc.scalabot.FinderPriority import net.lamgc.scalabot.FinderPriority
import net.lamgc.scalabot.FinderRules import net.lamgc.scalabot.FinderRules
import net.lamgc.scalabot.FoundExtensionPackage import net.lamgc.scalabot.FoundExtensionPackage
import org.eclipse.aether.artifact.Artifact import org.eclipse.aether.artifact.Artifact
import org.eclipse.aether.artifact.DefaultArtifact import org.eclipse.aether.artifact.DefaultArtifact
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.assertThrows
import java.io.File import java.io.File
import java.lang.reflect.InvocationTargetException
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import kotlin.test.Test import kotlin.test.*
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
internal class UtilsKtTest { internal class UtilsKtTest {
@ -58,4 +61,59 @@ internal class UtilsKtTest {
} }
} }
@Test
fun `AutoCloseable shutdown hook`() {
val utilsInternalClass = Class.forName("net.lamgc.scalabot.util.UtilsInternal")
val utilsInternalObject = utilsInternalClass.getDeclaredField("INSTANCE").get(null)
?: fail("无法获取 UtilsInternal 对象.")
val doCloseResourcesMethod = utilsInternalClass.getDeclaredMethod("doCloseResources")
.apply {
isAccessible = true
}
// 正常的运行过程.
val mockResource = mockk<AutoCloseable> {
justRun { close() }
}.registerShutdownHook()
doCloseResourcesMethod.invoke(utilsInternalObject)
verify { mockResource.close() }
// 异常捕获检查.
val exceptionMockResource = mockk<AutoCloseable> {
every { close() } throws RuntimeException("Expected exception.")
}.registerShutdownHook()
assertDoesNotThrow("在关闭资源时出现未捕获异常.") {
doCloseResourcesMethod.invoke(utilsInternalObject)
}
verify { exceptionMockResource.close() }
// 错误抛出检查.
val errorMockResource = mockk<AutoCloseable> {
every { close() } throws Error("Expected error.")
}.registerShutdownHook()
assertThrows<Error>("关闭资源时捕获了不该捕获的 Error.") {
try {
doCloseResourcesMethod.invoke(utilsInternalObject)
} catch (e: InvocationTargetException) {
throw e.targetException
}
}
verify { errorMockResource.close() }
@Suppress("UNCHECKED_CAST")
val resourceSet = utilsInternalClass.getDeclaredMethod("getAutoCloseableSet").invoke(utilsInternalObject)
as MutableSet<AutoCloseable>
resourceSet.clear()
val closeRef = mockk<AutoCloseable> {
justRun { close() }
}
resourceSet.add(closeRef)
assertTrue(resourceSet.contains(closeRef), "测试用资源虚引用添加失败.")
doCloseResourcesMethod.invoke(utilsInternalObject)
assertFalse(resourceSet.contains(closeRef), "资源虚引用未从列表中删除.")
resourceSet.clear()
}
} }