Java Spring引导应用程序中的Graalvm Polyglot线程问题

问题描述

在Spring引导项目中,我们正在调用GraalVM来处理一些用JavaScript编写的规则。 但是,当我使用多个线程调用GraalVM时,出现了以下异常。 如果使用同步,则不会出现以下问题。我知道JavaScript在单个线程上运行,但是我想使用多个线程运行graalVM。有没有办法在多个线程上同时运行多个GraalVM?

有关项目结构的更多详细信息:我有kafka使用者,它正在从Kafka主题中接收大量消息,然后调用graalvm使用一些JavaScript规则对其进行处理。

2020-08-14 11:00:28.363 [te-4-C-1]调试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(RecordMessagingMessageListenerAdapter.java:80) 在org.springframework.kafka.listener.adapter.RecordMessagingMessageListenerAdapter.onMessage(RecordMessagingMessageListenerAdapter.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运行时以简单而强大的方式支持通过多个线程的并行执行,我们认为这对于各种嵌入方案都是方便的。 该模型基于以下三个简单规则:

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

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

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

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

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

它使用上下文提供程序帮助程序类:

    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;
    });

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

Context

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

然后,当您处理消息时,每个线程都可以检索自己的{{1}}并在其中运行消息处理JavaScript。