mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-09 11:30:11 +08:00
AbstractExternalResource (#1637)
* AbstractExternalResource * typo * make `ResourceCleanCallback` `fun interface` * custom display name * update logic * Update docs * Update ExternalResource.kt
This commit is contained in:
parent
12e3c4fa90
commit
31399efe40
@ -5572,6 +5572,29 @@ public final class net/mamoe/mirai/network/UnsupportedSliderCaptchaException : n
|
||||
public final class net/mamoe/mirai/network/WrongPasswordException : net/mamoe/mirai/network/LoginFailedException {
|
||||
}
|
||||
|
||||
public abstract class net/mamoe/mirai/utils/AbstractExternalResource : net/mamoe/mirai/utils/ExternalResource {
|
||||
public fun <init> ()V
|
||||
public fun <init> (Ljava/lang/String;)V
|
||||
public fun <init> (Ljava/lang/String;Lnet/mamoe/mirai/utils/AbstractExternalResource$ResourceCleanCallback;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Lnet/mamoe/mirai/utils/AbstractExternalResource$ResourceCleanCallback;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun <init> (Lnet/mamoe/mirai/utils/AbstractExternalResource$ResourceCleanCallback;)V
|
||||
public synthetic fun <init> (Lnet/mamoe/mirai/utils/AbstractExternalResource$ResourceCleanCallback;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun close ()V
|
||||
protected final fun dontRegisterLeakObserver ()V
|
||||
public final fun getClosed ()Lkotlinx/coroutines/Deferred;
|
||||
public fun getFormatName ()Ljava/lang/String;
|
||||
public fun getMd5 ()[B
|
||||
public fun getSha1 ()[B
|
||||
public final fun inputStream ()Ljava/io/InputStream;
|
||||
protected abstract fun inputStream0 ()Ljava/io/InputStream;
|
||||
protected final fun registerToLeakObserver ()V
|
||||
protected final fun setResourceCleanCallback (Lnet/mamoe/mirai/utils/AbstractExternalResource$ResourceCleanCallback;)V
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/utils/AbstractExternalResource$ResourceCleanCallback {
|
||||
public abstract fun cleanup ()V
|
||||
}
|
||||
|
||||
public class net/mamoe/mirai/utils/BotConfiguration {
|
||||
public static final field Companion Lnet/mamoe/mirai/utils/BotConfiguration$Companion;
|
||||
public fun <init> ()V
|
||||
|
@ -5572,6 +5572,29 @@ public final class net/mamoe/mirai/network/UnsupportedSliderCaptchaException : n
|
||||
public final class net/mamoe/mirai/network/WrongPasswordException : net/mamoe/mirai/network/LoginFailedException {
|
||||
}
|
||||
|
||||
public abstract class net/mamoe/mirai/utils/AbstractExternalResource : net/mamoe/mirai/utils/ExternalResource {
|
||||
public fun <init> ()V
|
||||
public fun <init> (Ljava/lang/String;)V
|
||||
public fun <init> (Ljava/lang/String;Lnet/mamoe/mirai/utils/AbstractExternalResource$ResourceCleanCallback;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Lnet/mamoe/mirai/utils/AbstractExternalResource$ResourceCleanCallback;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun <init> (Lnet/mamoe/mirai/utils/AbstractExternalResource$ResourceCleanCallback;)V
|
||||
public synthetic fun <init> (Lnet/mamoe/mirai/utils/AbstractExternalResource$ResourceCleanCallback;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun close ()V
|
||||
protected final fun dontRegisterLeakObserver ()V
|
||||
public final fun getClosed ()Lkotlinx/coroutines/Deferred;
|
||||
public fun getFormatName ()Ljava/lang/String;
|
||||
public fun getMd5 ()[B
|
||||
public fun getSha1 ()[B
|
||||
public final fun inputStream ()Ljava/io/InputStream;
|
||||
protected abstract fun inputStream0 ()Ljava/io/InputStream;
|
||||
protected final fun registerToLeakObserver ()V
|
||||
protected final fun setResourceCleanCallback (Lnet/mamoe/mirai/utils/AbstractExternalResource$ResourceCleanCallback;)V
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/utils/AbstractExternalResource$ResourceCleanCallback {
|
||||
public abstract fun cleanup ()V
|
||||
}
|
||||
|
||||
public class net/mamoe/mirai/utils/BotConfiguration {
|
||||
public static final field Companion Lnet/mamoe/mirai/utils/BotConfiguration$Companion;
|
||||
public fun <init> ()V
|
||||
|
@ -18,7 +18,7 @@ import java.io.InputStream
|
||||
import java.io.RandomAccessFile
|
||||
|
||||
|
||||
private fun InputStream.detectFileTypeAndClose(): String? {
|
||||
internal fun InputStream.detectFileTypeAndClose(): String? {
|
||||
val buffer = ByteArray(COUNT_BYTES_USED_FOR_DETECTING_FILE_TYPE)
|
||||
return use {
|
||||
kotlin.runCatching { it.read(buffer) }.onFailure { return null }
|
||||
|
@ -24,11 +24,17 @@ internal object ExternalResourceLeakObserver : Runnable {
|
||||
MiraiLogger.Factory.create(ExternalResourceLeakObserver::class, "ExternalResourceLeakObserver")
|
||||
}
|
||||
|
||||
internal class ERReference(
|
||||
resourceInternal: ExternalResourceInternal
|
||||
) : WeakReference<ExternalResource>(resourceInternal, queue) {
|
||||
internal class ERReference : WeakReference<Any> {
|
||||
constructor(resource: ExternalResourceInternal) : super(resource, queue) {
|
||||
this.holder = resource.holder
|
||||
}
|
||||
|
||||
constructor(resource: ExternalResource, holder: ExternalResourceHolder) : super(resource, queue) {
|
||||
this.holder = holder
|
||||
}
|
||||
|
||||
@JvmField
|
||||
internal val holder: ExternalResourceHolder = resourceInternal.holder
|
||||
internal val holder: ExternalResourceHolder
|
||||
}
|
||||
|
||||
class ExternalResourceCreateStackTrace : Throwable() {
|
||||
@ -44,6 +50,11 @@ internal object ExternalResourceLeakObserver : Runnable {
|
||||
references.add(ERReference(resource))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun register(resource: ExternalResource, holder: ExternalResourceHolder) {
|
||||
references.add(ERReference(resource, holder))
|
||||
}
|
||||
|
||||
init {
|
||||
val thread = Thread(this, "Mirai ExternalResource Leak Observer Thread")
|
||||
thread.isDaemon = true
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.Deferred
|
||||
import net.mamoe.kjbb.JvmBlockingBridge
|
||||
@ -20,12 +21,12 @@ import net.mamoe.mirai.contact.Contact.Companion.sendImage
|
||||
import net.mamoe.mirai.contact.Contact.Companion.uploadImage
|
||||
import net.mamoe.mirai.contact.FileSupported
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.internal.utils.ExternalResourceImplByByteArray
|
||||
import net.mamoe.mirai.internal.utils.ExternalResourceImplByFile
|
||||
import net.mamoe.mirai.internal.utils.*
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.FileMessage
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
import net.mamoe.mirai.message.data.sendTo
|
||||
import net.mamoe.mirai.utils.AbstractExternalResource.ResourceCleanCallback
|
||||
import net.mamoe.mirai.utils.ExternalResource.Companion.sendAsImageTo
|
||||
import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource
|
||||
import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsImage
|
||||
@ -531,6 +532,243 @@ public interface ExternalResource : Closeable {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 一个实现了基本方法的外部资源
|
||||
*
|
||||
* ## 实现
|
||||
*
|
||||
* [AbstractExternalResource] 实现了大部分必要的方法,
|
||||
* 只有 [ExternalResource.inputStream], [ExternalResource.size] 还未实现
|
||||
*
|
||||
* 其中 [ExternalResource.inputStream] 要求每次读取的内容都是一致的
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* class MyCustomExternalResource: AbstractExternalResource() {
|
||||
* override fun inputStream0(): InputStream = FileInputStream("/test.txt")
|
||||
* override val size: Long get() = File("/test.txt").length()
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ## 资源释放
|
||||
*
|
||||
* 如同 mirai 内置的 [ExternalResource] 实现一样,
|
||||
* [AbstractExternalResource] 也会被注册进入资源泄露监视器
|
||||
* (即意味着 [AbstractExternalResource] 也要求手动关闭)
|
||||
*
|
||||
* 为了确保逻辑正确性, [AbstractExternalResource] 不允许覆盖其 [close] 方法,
|
||||
* 必须在构造 [AbstractExternalResource] 的时候给定一个 [ResourceCleanCallback] 以进行资源释放
|
||||
*
|
||||
* 对于 [ResourceCleanCallback], 有以下要求
|
||||
*
|
||||
* - 没有对 [AbstractExternalResource] 的访问 (即没有 [AbstractExternalResource] 的任何引用)
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* class MyRes(
|
||||
* cleanup: ResourceCleanCallback,
|
||||
* val delegate: Closable,
|
||||
* ): AbstractExternalResource(cleanup) {
|
||||
* }
|
||||
*
|
||||
* // 错误, 该写法会导致 Resource 永远也不会被自动释放
|
||||
* lateinit var myRes: MyRes
|
||||
* val cleanup = ResourceCleanCallback {
|
||||
* myRes.delegate.close()
|
||||
* }
|
||||
* myRes = MyRes(cleanup, fetchDelegate())
|
||||
*
|
||||
* // 正确
|
||||
* val delegate: Closable
|
||||
* val cleanup = ResourceCleanCallback {
|
||||
* delegate.close()
|
||||
* }
|
||||
* val myRes = MyRes(cleanup, delegate)
|
||||
* ```
|
||||
*
|
||||
* @since 2.9
|
||||
*
|
||||
* @see ExternalResource
|
||||
* @see AbstractExternalResource.setResourceCleanCallback
|
||||
* @see AbstractExternalResource.registerToLeakObserver
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
public abstract class AbstractExternalResource
|
||||
@JvmOverloads
|
||||
public constructor(
|
||||
displayName: String? = null,
|
||||
cleanup: ResourceCleanCallback? = null,
|
||||
) : ExternalResource {
|
||||
|
||||
public constructor(
|
||||
cleanup: ResourceCleanCallback? = null,
|
||||
): this(null, cleanup)
|
||||
|
||||
public fun interface ResourceCleanCallback {
|
||||
@Throws(IOException::class)
|
||||
public fun cleanup()
|
||||
}
|
||||
|
||||
override val md5: ByteArray by lazy { inputStream().md5() }
|
||||
override val sha1: ByteArray by lazy { inputStream().sha1() }
|
||||
override val formatName: String by lazy {
|
||||
inputStream().detectFileTypeAndClose() ?: ExternalResource.DEFAULT_FORMAT_NAME
|
||||
}
|
||||
|
||||
private val leakObserverRegistered = atomic(false)
|
||||
|
||||
/**
|
||||
* 注册 [ExternalResource] 资源泄露监视器
|
||||
*
|
||||
* 受限于类继承构造器调用顺序, [AbstractExternalResource] 无法做到在完成初始化后马上注册监视器
|
||||
*
|
||||
* 该方法以允许 实现类 在完成初始化后直接注册资源监视器以避免意外的资源泄露
|
||||
*
|
||||
* 在不调用本方法的前提下, 如果没有相关的资源访问操作, `this` 可能会被意外泄露
|
||||
*
|
||||
* 正确示例:
|
||||
* ```
|
||||
* // Kotlin
|
||||
* public class MyResource: AbstractExternalResource() {
|
||||
* init {
|
||||
* val res: SomeResource
|
||||
* // 一些资源初始化
|
||||
* registerToLeakObserver()
|
||||
* setResourceCleanCallback(Releaser(res))
|
||||
* }
|
||||
*
|
||||
* private class Releaser(
|
||||
* private val res: SomeResource,
|
||||
* ) : AbstractExternalResource.ResourceCleanCallback {
|
||||
* override fun cleanup() = res.close()
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // Java
|
||||
* public class MyResource extends AbstractExternalResource {
|
||||
* public MyResource() throws IOException {
|
||||
* SomeResource res;
|
||||
* // 一些资源初始化
|
||||
* registerToLeakObserver();
|
||||
* setResourceCleanCallback(new Releaser(res));
|
||||
* }
|
||||
*
|
||||
* private static class Releaser implements ResourceCleanCallback {
|
||||
* private final SomeResource res;
|
||||
* Releaser(SomeResource res) { this.res = res; }
|
||||
*
|
||||
* public void cleanup() throws IOException { res.close(); }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @see setResourceCleanCallback
|
||||
*/
|
||||
protected fun registerToLeakObserver() {
|
||||
// 用户自定义 AbstractExternalResource 也许会在 <init> 的时候失败
|
||||
// 于是在第一次使用 ExternalResource 相关的函数的时候注册 LeakObserver
|
||||
if (leakObserverRegistered.compareAndSet(expect = false, update = true)) {
|
||||
ExternalResourceLeakObserver.register(this, holder)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 该方法用于告知 [AbstractExternalResource] 不需要注册资源泄露监视器。
|
||||
* **仅在我知道我在干什么的前提下调用此方法**
|
||||
*
|
||||
* 不建议取消注册监视器, 这可能带来意外的错误
|
||||
*
|
||||
* @see registerToLeakObserver
|
||||
*/
|
||||
protected fun dontRegisterLeakObserver() {
|
||||
leakObserverRegistered.value = true
|
||||
}
|
||||
|
||||
final override fun inputStream(): InputStream {
|
||||
registerToLeakObserver()
|
||||
return inputStream0()
|
||||
}
|
||||
|
||||
protected abstract fun inputStream0(): InputStream
|
||||
|
||||
/**
|
||||
* 修改 `this` 的资源释放回调。
|
||||
* **仅在我知道我在干什么的前提下调用此方法**
|
||||
*
|
||||
* ```
|
||||
* class MyRes {
|
||||
* // region kotlin
|
||||
*
|
||||
* private inner class Releaser : ResourceCleanCallback
|
||||
*
|
||||
* private class NotInnerReleaser : ResourceCleanCallback
|
||||
*
|
||||
* init {
|
||||
* // 错误, 内部类, Releaser 存在对 MyRes 的引用
|
||||
* setResourceCleanCallback(Releaser())
|
||||
* // 错误, 匿名对象, 可能存在对 MyRes 的引用, 取决于编译器
|
||||
* setResourceCleanCallback(object : ResourceCleanCallback {})
|
||||
* // 正确, 无 inner 修饰, 等同于 java 的 private static class
|
||||
* setResourceCleanCallback(NotInnerReleaser(directResource))
|
||||
* }
|
||||
*
|
||||
* // endregion kotlin
|
||||
*
|
||||
* // region java
|
||||
*
|
||||
* private class Releaser implements ResourceCleanCallback {}
|
||||
* private static class StaticReleaser implements ResourceCleanCallback {}
|
||||
*
|
||||
* MyRes() {
|
||||
* // 错误, 内部类, 存在对 MyRes 的引用
|
||||
* setResourceCleanCallback(new Releaser());
|
||||
* // 错误, 匿名对象, 可能存在对 MyRes 的引用, 取决于 javac
|
||||
* setResourceCleanCallback(new ResourceCleanCallback() {});
|
||||
* // 正确
|
||||
* setResourceCleanCallback(new StaticReleaser(directResource));
|
||||
* }
|
||||
*
|
||||
* // endregion java
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @see registerToLeakObserver
|
||||
*/
|
||||
protected fun setResourceCleanCallback(cleanup: ResourceCleanCallback?) {
|
||||
holder.cleanup = cleanup
|
||||
}
|
||||
|
||||
private class UsrCustomResHolder(
|
||||
@JvmField var cleanup: ResourceCleanCallback?,
|
||||
private val resourceName: String,
|
||||
) : ExternalResourceHolder() {
|
||||
|
||||
override val closed: Deferred<Unit> = CompletableDeferred()
|
||||
|
||||
override fun closeImpl() {
|
||||
cleanup?.cleanup()
|
||||
}
|
||||
|
||||
// display on logger of ExternalResourceLeakObserver
|
||||
override fun toString(): String = resourceName
|
||||
}
|
||||
|
||||
private val holder = UsrCustomResHolder(cleanup, displayName ?: buildString {
|
||||
append("ExternalResourceHolder<")
|
||||
append(this@AbstractExternalResource.javaClass.name)
|
||||
append('@')
|
||||
append(System.identityHashCode(this@AbstractExternalResource))
|
||||
append('>')
|
||||
})
|
||||
|
||||
final override val closed: Deferred<Unit> get() = holder.closed.also { registerToLeakObserver() }
|
||||
|
||||
@Throws(IOException::class)
|
||||
final override fun close() {
|
||||
holder.close()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 [action], 如果 [ExternalResource.isAutoClose], 在执行完成后调用 [ExternalResource.close].
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user