You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin JSR-223 ScriptEngine多次eval复用旧参数异常

kotlin-scripting-jsr223 执行预编译脚本时旧参数复用异常

使用Kotlin基于JSR-223规范的ScriptEngine实现kotlin-scripting-jsr223时,短时间内多次执行预编译的CompiledScript实例会出现参数复用问题,单线程调度编译、执行逻辑也无法解决该异常。

实现逻辑

脚本生成逻辑

先将事件判断条件拼接为可执行的Kotlin脚本代码,代码中定义接收事件对象的check方法,返回条件判断的布尔结果:

fun parse(clazz: KClass<out GenericEvent>, condition: String): String {
    val simpleName = clazz.simpleName
    return """
        import ${clazz.java.name}
        
        val condition: $simpleName.() -> Boolean = { ${trimQuotes(condition)} }
        
        fun check(event: $simpleName) = condition.invoke(event)
            
    """.trimIndent()
}

脚本编译逻辑

调用引擎的编译接口,将生成的脚本字符串编译为CompiledScript实例缓存,避免每次执行重复解析编译:

(engine as Compilable).compile(code)

脚本执行逻辑

事件触发时,先执行预编译脚本,再通过Invocable接口调用脚本内定义的check方法,传入当前事件对象获取判断结果:

listener.compiledScript.eval()
return@Callable (engine as Invocable).invokeFunction("check", event)

完整业务层执行代码如下,已使用单线程执行器保证所有脚本操作在同一线程运行:

override suspend fun onEvent(e: GenericEvent) {
    val events = listeners[e.javaClass] ?: return

    events.forEach { listener ->
        try {
            val event = e.javaClass.cast(e)
            if (listener.compiledScript != null) {
                val result = executor.submit(Callable { // executor = Executors.newSingleThreadExecutor()
                    println(Thread.currentThread().id) // 打印线程ID确认所有执行均在同一线程
                    listener.compiledScript.eval()
                    return@Callable (engine as Invocable).invokeFunction("check", event)
                }).get()
                if (result is Boolean && result)
                    listener.method.callSuspend(listener.instance, event)
            } else {
                listener.method.callSuspend(listener.instance, event)
            }
        } catch (throwable: Throwable) {
            log.error("监听条件执行出错", throwable)
        }
    }
}

异常现象

首次执行时逻辑完全符合预期:

Condition: message.contentRaw == "first"
Is actually: first

第二次及之后的执行出现固定异常:所有判断逻辑都复用第二次传入的事件参数,即使每次传入的都是完全独立的新事件对象。异常输出示例如下(按照逻辑,条件不成立时不应该触发对应消息打印):

Condition: message.contentRaw == "first"
Is actually: second

Condition: message.contentRaw == "second"
Is actually: second

Condition: message.contentRaw == "first"
Is actually: second

Condition: message.contentRaw == "second"
Is actually: second

Condition: message.contentRaw == "first"
Is actually: second

Condition: message.contentRaw == "second"
Is actually: second

调试时在编译后的脚本内部添加打印逻辑,确认check()函数确实复用了上一次传入的event入参,和本次调用传入的参数无关。

已尝试排查方案

  • 怀疑是线程安全问题,先后尝试使用Mutex协程锁、单线程执行器统一调度脚本编译、执行全流程,异常仍然存在。

内容的提问来源于stack exchange,提问作者Xirado

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 14:51:18