Java Spring 启动应用程序中的 Graalvm Polyglot 线程问题

Graalvm Polyglot Thread issue in Java Spring boot application

我们从 Spring 引导项目调用 GraalVM 来处理一些在 JavaScript 中编写的规则。 但是当我使用多线程调用 GraalVM 时,它给出了以下异常。 如果我们使用 synchronized 那么下面的问题就不会出现了。我知道 JavaScript 运行s 在单个线程上,但我想 运行 graalVM 使用多个线程。有什么方法可以同时在多个线程上 运行 多个 GraalVM 吗?

关于项目结构的更多细节:我有 kafka 消费者,它从 Kafka 主题接收大量消息,然后调用 graalvm 使用一些 JavaScript 规则处理它们。

2020-08-14 11:00:28.363 [te-4-C-1] DEBUG c.e.d.j.t.RuleExecutor#110 函数 MessageBroker_get_error_info 在 192546300 ns 内执行。 2020-08-14 11:00:28.363 [te-0-C-1] 错误 c.e.d.j.t.RuleExecutor#102 执行 TE 规则时出现意外错误:customer_entities 函数: com.oracle.truffle.polyglot.PolyglotIllegalStateException:线程 Thread[te-0-C-1,5,main] 请求的多线程访问,但语言 js 不允许。 在 com.oracle.truffle.polyglot.PolyglotContextImpl.throwDeniedThreadAccess(PolyglotContextImpl.java:649) 在 com.oracle.truffle.polyglot.PolyglotContextImpl.checkAllThreadAccesses(PolyglotContextImpl.java:567) 在 com.oracle.truffle.polyglot.PolyglotContextImpl.enterThreadChanged(PolyglotContextImpl.java:486) 在 com.oracle.truffle.polyglot.PolyglotContextImpl.enter(PolyglotContextImpl.java:447) 在 com.oracle.truffle.polyglot.HostToGuestRootNode.execute(HostToGuestRootNode.java:82) 在 com.oracle.truffle.api.impl.DefaultCallTarget.call(DefaultCallTarget.java:102) 在 com.oracle.truffle.api.impl.DefaultCallTarget$2.call(DefaultCallTarget.java:130) 在 com.oracle.truffle.polyglot.PolyglotValue$InteropValue.getMember(PolyglotValue.java:2259) 在 org.graalvm.polyglot.Value.getMember(Value.java:280) 在 com.ericsson.datamigration.js.transformation.RuleExecutor.run(RuleExecutor.java:73) 在 com.ericsson.datamigration.js.transformation.TransformationProcess.process(TransformationProcess.java:149) 在 com.ericsson.datamigration.bridging.converter.core.wfm.yaml.steps.ApplyTransformationMessageBroker.execute(ApplyTransformationMessageBroker.java:104) 在 com.ericsson.datamigration.bss.wfm.core.AbstractStep.run(AbstractStep.java:105) 在 com.ericsson.datamigration.bss.wfm.yaml.definition.SimpleWorkflow.execute(SimpleWorkflow.java:103) 在 com.ericsson.datamigration.bss.wfm.core.AbstractProcessor.run(AbstractProcessor.java:64) 在 com.ericsson.datamigration.bss.wfm.yaml.definition.ConditionalWorkflow.execute(ConditionalWorkflow.java:95) 在 com.ericsson.datamigration.bss.wfm.core.AbstractProcessor.run(AbstractProcessor.java:64) 在 com.ericsson.datamigration.bss.wfm.application.WorkflowManagerApplication.process(WorkflowManagerApplication.java:243) 在 com.ericsson.datamigration.bridging.dispatcher.core.kafka.consumer.KafkaMessageConsumer.processRequest(KafkaMessageConsumer.java:198) 在 com.ericsson.datamigration.bridging.dispatcher.core.kafka.consumer.KafkaMessageConsumer.listen(KafkaMessageConsumer.java:89) 在 sun.reflect.GeneratedMethodAccessor114.invoke(来源不明) 在 sun.reflect.DelegatingMethodAccessorImpl.invoke(未知来源) 在 java.lang.reflect.Method.invoke(来源不明) 在 org.springframework.messaging.handler.invocation.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:181) 在 org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:114) 在 org.springframework.kafka.listener.adapter.HandlerAdapter.invoke(HandlerAdapter.java:48) 在 org.springframework.kafka.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:248) 在 org.springframework.kafka.listener.adapter.RecordMessagingMessageListenerAdapter.onMessage(记录MessagingMessageListenerAdapter.java:80) 在 org.springframework.kafka.listener.adapter.RecordMessagingMessageListenerAdapter.onMessage(记录MessagingMessageListenerAdapter.java:51) 在 org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeRecordListener(KafkaMessageListenerContainer.java:1071) 在 org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeWithRecords(KafkaMessageListenerContainer.java:1051) 在 org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeRecordListener(KafkaMessageListenerContainer.java:998) 在 org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeListener(KafkaMessageListenerContainer.java:866) 在 org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.run(KafkaMessageListenerContainer.java:724) 在 java.util.concurrent.Executors$RunnableAdapter.call(未知来源) 在 java.util.concurrent.FutureTask.run(来源不明) 在 java.lang.Thread.run(来源不明)

是的,您可以同时 运行 多个 GraalVM 上下文。

如以下文章所述:https://medium.com/graalvm/multi-threaded-java-javascript-language-interoperability-in-graalvm-2f19c1f9c37b

GraalVM 的 JavaScript 运行time 以一种简单而强大的方式支持通过多线程并行执行,我们相信这对于各种嵌入场景都很方便。 该模型基于以下三个简单规则:

  • 在多语言应用程序中,可以创建任意数量的 JS 运行次,但它们应该一次由一个线程使用。
  • 允许并发访问 Java 对象:任何 Java 对象都可以由任何 Java 或 Java 脚本线程同时访问。
  • 不允许并发访问Java脚本对象:任何Java脚本对象不能同时被多个线程访问。

GraalVM 在 运行 时执行这些规则,因此可以更轻松、更安全地推理多语言应用程序中的并行和并发执行。

因此,当您尝试从多个线程同时访问 JS 对象(函数)时,您会看到所显示的异常。

您可以做的是确保只有 1 个线程可以访问您的 JS 对象。一种方法是使用同步。另一个 -- 每个线程创建多个 Context 对象 1。

此演示应用程序中使用了此方法:https://github.com/graalvm/graalvm-demos/tree/master/js-java-async-helidon

它使用上下文提供程序助手class:

private static class ContextProvider {

        private final Context context;
        private final ReentrantLock lock;

        ContextProvider(Context cx) {
            this.context = cx;
            this.lock = new ReentrantLock();
        }

        Context getContext() {
            return context;
        }

        Lock getLock() {
            return lock;
        }
    }

并使用 threadlocal 为每个线程初始化它们:

    private final ThreadLocal<ContextProvider> jsContext = ThreadLocal.withInitial(() -> {
        /*
         * For simplicity, allow ALL accesses. In a real application, access to resources should be restricted.
         */
        Context cx = Context.newBuilder(JS).allowHostAccess(HostAccess.ALL).allowPolyglotAccess(PolyglotAccess.ALL)
                .engine(sharedEngine).build();
        /*
         * Register a Java method in the Context global scope as a JavaScript function.
         */
        ContextProvider provider = new ContextProvider(cx);
        cx.getBindings(JS).putMember("computeFromJava", createJavaInteropComputeFunction(provider));
        System.out.println("Created new JS context for thread " + Thread.currentThread());
        return provider;
    });

注意这里 Context 对象可以共享引擎以提高效率,如果每个线程的 JS 源都相同,您可能希望这样做。

那么当你处理消息时,每个线程都可以检索自己的Context和运行消息处理Java脚本。