diff --git a/.changelog/lang_zh-Hans.json b/.changelog/lang_zh-Hans.json index 6037ebf2..cea4e9e4 100644 --- a/.changelog/lang_zh-Hans.json +++ b/.changelog/lang_zh-Hans.json @@ -16,6 +16,7 @@ "timers.keepAlive 方法 timeout 参数功能无效的问题", "floaty.window/rawWindow 方法无法接受字符串参数的问题", "util.class[Name]/getClass[Name] 可能返回错误结果的问题", + "threads.disposable() 返回的对象在存取数据时可能被意外装箱的问题 _[`issue #435`](http://issues.autojs6.com/435)_", "无法使用 console/toast 等方法显示 BigInt 数据类型的问题", "部分全局对象可能丢失 JavaScript 原型属性及方法的问题", "使用 XML 语法将 JavaScript 表达式作为属性值时, this 对象可能出现指向错误的问题", diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/augment/threads/Threads.kt b/app/src/main/java/org/autojs/autojs/runtime/api/augment/threads/Threads.kt index 5652545f..a76b8ea4 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/api/augment/threads/Threads.kt +++ b/app/src/main/java/org/autojs/autojs/runtime/api/augment/threads/Threads.kt @@ -28,6 +28,7 @@ class Threads(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) { ::interrupt.name, ::start.name, ::pool.name, + ::disposable.name, ) override val globalAssignmentFunctions = listOf( @@ -79,6 +80,12 @@ class Threads(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) { Synchronizer(func, lock) } + @JvmStatic + @RhinoRuntimeFunctionInterface + fun disposable(scriptRuntime: ScriptRuntime, args: Array): VolatileDisposeNativeObject = ensureArgumentsIsEmpty(args) { + VolatileDisposeNativeObject() + } + } } diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/augment/threads/VolatileDisposeNativeObject.kt b/app/src/main/java/org/autojs/autojs/runtime/api/augment/threads/VolatileDisposeNativeObject.kt new file mode 100644 index 00000000..c44ef1d0 --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/runtime/api/augment/threads/VolatileDisposeNativeObject.kt @@ -0,0 +1,117 @@ +package org.autojs.autojs.runtime.api.augment.threads + +import org.autojs.autojs.annotation.RhinoStandardFunctionInterface +import org.autojs.autojs.extension.FlexibleArray +import org.autojs.autojs.extension.FlexibleArray.Companion.component1 +import org.autojs.autojs.extension.FlexibleArray.Companion.component2 +import org.autojs.autojs.extension.FlexibleArray.Companion.component3 +import org.autojs.autojs.util.RhinoUtils +import org.autojs.autojs.util.RhinoUtils.UNDEFINED +import org.autojs.autojs.util.RhinoUtils.coerceLongNumber +import org.mozilla.javascript.Context +import org.mozilla.javascript.Function +import org.mozilla.javascript.NativeObject +import org.mozilla.javascript.Scriptable +import org.mozilla.javascript.Undefined +import java.util.concurrent.TimeUnit +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.Volatile + +@Suppress("unused") +class VolatileDisposeNativeObject : NativeObject() { + + @Volatile + private var value: Any? = null + + private val lock = ReentrantLock() + private val ready = lock.newCondition() + + private val mFunctionNames = arrayOf( + ::blockedGet.name, + ::blockedGetOrThrow.name, + ::setAndNotify.name, + ) + + init { + RhinoUtils.initNativeObjectPrototype(this) + defineFunctionProperties(mFunctionNames, javaClass, PERMANENT) + } + + private inline fun withLock(block: () -> T): T { + lock.lock() + return try { + block() + } finally { + lock.unlock() + } + } + + private fun awaitValue(timeoutMillis: Long, onInterrupted: (() -> Unit)? = null): Any? = withLock { + when { + timeoutMillis <= 0L -> { + try { + ready.await() + } catch (e: InterruptedException) { + onInterrupted?.invoke() ?: throw RuntimeException(e) + } + } + else -> { + var nanos = TimeUnit.MILLISECONDS.toNanos(timeoutMillis) + while (nanos > 0L) { + try { + ready.awaitNanos(nanos).also { nanos = it } + } catch (e: InterruptedException) { + onInterrupted?.invoke() ?: throw RuntimeException(e) + } + } + } + } + value + } + + private fun instantiateRuntimeException(clazz: Class) = try { + clazz.getDeclaredConstructor().newInstance() + } catch (e: Exception) { + RuntimeException(e) + } + + companion object : FlexibleArray() { + + @JvmStatic + @RhinoStandardFunctionInterface + fun blockedGet(cx: Context, thisObj: Scriptable, args: Array, funObj: Function): Any? = ensureArgumentsAtMost(args, 1) { argList -> + val (timeout) = argList + val self = thisObj as VolatileDisposeNativeObject + val timeoutMillis = coerceLongNumber(timeout, 0L) + self.awaitValue(timeoutMillis) + } + + @JvmStatic + @RhinoStandardFunctionInterface + fun blockedGetOrThrow(cx: Context, thisObj: Scriptable, args: Array, funObj: Function): Any? = ensureArgumentsLengthInRange(args, 1..3) { argList -> + val (exception, timeout, defaultValue) = argList + require(exception is Class<*>) { + "Argument \"exception\" must be a RuntimeException Class for ${VolatileDisposeNativeObject::class.java.simpleName}::blockedGetOrThrow" + } + val self = thisObj as VolatileDisposeNativeObject + val timeoutMillis = coerceLongNumber(timeout, 0L) + self.awaitValue(timeoutMillis) { + @Suppress("UNCHECKED_CAST") + throw self.instantiateRuntimeException(exception as Class) + } ?: defaultValue + } + + @JvmStatic + @RhinoStandardFunctionInterface + fun setAndNotify(cx: Context, thisObj: Scriptable, args: Array, funObj: Function): Undefined = ensureArgumentsOnlyOne(args) { value -> + val self = thisObj as VolatileDisposeNativeObject + self.withLock { + self.value = value + self.ready.signalAll() + } + UNDEFINED + } + + } + +} \ No newline at end of file