From 478480014a9ff9669e83294a8604ca2c47f19364 Mon Sep 17 00:00:00 2001 From: LamGC Date: Thu, 5 May 2022 16:52:29 +0800 Subject: [PATCH] =?UTF-8?q?perf(utils):=20=E4=BC=98=E5=8C=96=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E9=87=8A=E6=94=BE=E9=92=A9=E5=AD=90=E7=9A=84=E8=B5=84?= =?UTF-8?q?=E6=BA=90=E5=BC=95=E7=94=A8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原本自动释放钩子对资源的引用, 可能会出现资源已经被关闭, 但仍然无法被 GC 回收的问题. 此次改动, 将会让钩子在关闭资源后, 将资源从列表中移除. 虽然, 自动释放钩子设计上仅会被 System.exit 动作触发, 但保险起见还是加上这个改动. --- scalabot-app/src/main/kotlin/util/Utils.kt | 23 ++++--- .../src/test/kotlin/util/UtilsKtTest.kt | 66 +++++++++++++++++-- 2 files changed, 75 insertions(+), 14 deletions(-) diff --git a/scalabot-app/src/main/kotlin/util/Utils.kt b/scalabot-app/src/main/kotlin/util/Utils.kt index 4d9d566..550aae3 100644 --- a/scalabot-app/src/main/kotlin/util/Utils.kt +++ b/scalabot-app/src/main/kotlin/util/Utils.kt @@ -81,17 +81,20 @@ private object UtilsInternal { val autoCloseableSet = mutableSetOf() init { - Runtime.getRuntime().addShutdownHook(Thread({ - log.debug { "Closing registered hook resources..." } - autoCloseableSet.forEach { - try { - it.close() - } catch (e: Exception) { - log.error(e) { "An exception occurred while closing the resource. (Resource: `$it`)" } - } + Runtime.getRuntime().addShutdownHook(Thread(this::doCloseResources, "Shutdown-AutoCloseable")) + } + + fun doCloseResources() { + log.debug { "Closing registered hook resources..." } + autoCloseableSet.removeIf { + try { + it.close() + } catch (e: Exception) { + log.error(e) { "An exception occurred while closing the resource. (Resource: `$it`)" } } - log.debug { "All registered hook resources have been closed." } - }, "Shutdown-AutoCloseable")) + true + } + log.debug { "All registered hook resources have been closed." } } } diff --git a/scalabot-app/src/test/kotlin/util/UtilsKtTest.kt b/scalabot-app/src/test/kotlin/util/UtilsKtTest.kt index ac88b55..7881d33 100644 --- a/scalabot-app/src/test/kotlin/util/UtilsKtTest.kt +++ b/scalabot-app/src/test/kotlin/util/UtilsKtTest.kt @@ -1,18 +1,21 @@ 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.FinderPriority import net.lamgc.scalabot.FinderRules import net.lamgc.scalabot.FoundExtensionPackage import org.eclipse.aether.artifact.Artifact import org.eclipse.aether.artifact.DefaultArtifact +import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows import java.io.File +import java.lang.reflect.InvocationTargetException import java.nio.charset.StandardCharsets -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue +import kotlin.test.* 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 { + justRun { close() } + }.registerShutdownHook() + doCloseResourcesMethod.invoke(utilsInternalObject) + verify { mockResource.close() } + + // 异常捕获检查. + val exceptionMockResource = mockk { + every { close() } throws RuntimeException("Expected exception.") + }.registerShutdownHook() + assertDoesNotThrow("在关闭资源时出现未捕获异常.") { + doCloseResourcesMethod.invoke(utilsInternalObject) + } + verify { exceptionMockResource.close() } + + // 错误抛出检查. + val errorMockResource = mockk { + every { close() } throws Error("Expected error.") + }.registerShutdownHook() + assertThrows("关闭资源时捕获了不该捕获的 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 + resourceSet.clear() + + val closeRef = mockk { + justRun { close() } + } + resourceSet.add(closeRef) + assertTrue(resourceSet.contains(closeRef), "测试用资源虚引用添加失败.") + doCloseResourcesMethod.invoke(utilsInternalObject) + assertFalse(resourceSet.contains(closeRef), "资源虚引用未从列表中删除.") + + resourceSet.clear() + } } \ No newline at end of file