GraalVM多语言线程问题在Java Spring Boot应用程序中。

huangapple go评论73阅读模式
英文:

Graalvm Polyglot Thread issue in Java Spring boot application

问题

从 Spring Boot 项目中,我们调用 GraalVM 来处理一些用 JavaScript 编写的规则。但是,当我使用多个线程调用 GraalVM 时,出现了以下异常。如果我们使用 synchronized 关键字,就不会出现下面的问题。我知道 JavaScript 在单个线程上运行,但我想要在多个线程上运行 GraalVM。是否有任何方法可以同时在多个线程上运行多个 GraalVM?

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

英文:

From Spring boot project we are calling GraalVM for processing some rules written in JavaScript.
But when I am calling GraalVM using multiple threads, it is giving the below exception.
If we use synchronized then the below issue is not coming. I know JavaScript runs on a single thread but I wanted to run graalVM using multiple threads. Is there any way to run multiple GraalVMs on multiple threads simultneously?

Some more details about project structure: I have kafka consumer, which is receiving huge messages from Kafka topics and then calling graalvm to process them using some JavaScript rules.

2020-08-14 11:00:28.363 [te-4-C-1] DEBUG c.e.d.j.t.RuleExecutor#110 Function MessageBroker_get_error_info executed in 192546300 ns.
2020-08-14 11:00:28.363 [te-0-C-1] ERROR c.e.d.j.t.RuleExecutor#102 Unexpected error executing TE rule: customer_entities function :
com.oracle.truffle.polyglot.PolyglotIllegalStateException: Multi threaded access requested by thread Thread[te-0-C-1,5,main] but is not allowed for language(s) js.
at com.oracle.truffle.polyglot.PolyglotContextImpl.throwDeniedThreadAccess(PolyglotContextImpl.java:649)
at com.oracle.truffle.polyglot.PolyglotContextImpl.checkAllThreadAccesses(PolyglotContextImpl.java:567)
at com.oracle.truffle.polyglot.PolyglotContextImpl.enterThreadChanged(PolyglotContextImpl.java:486)
at com.oracle.truffle.polyglot.PolyglotContextImpl.enter(PolyglotContextImpl.java:447)
at com.oracle.truffle.polyglot.HostToGuestRootNode.execute(HostToGuestRootNode.java:82)
at com.oracle.truffle.api.impl.DefaultCallTarget.call(DefaultCallTarget.java:102)
at com.oracle.truffle.api.impl.DefaultCallTarget$2.call(DefaultCallTarget.java:130)
at com.oracle.truffle.polyglot.PolyglotValue$InteropValue.getMember(PolyglotValue.java:2259)
at org.graalvm.polyglot.Value.getMember(Value.java:280)
at com.ericsson.datamigration.js.transformation.RuleExecutor.run(RuleExecutor.java:73)
at com.ericsson.datamigration.js.transformation.TransformationProcess.process(TransformationProcess.java:149)
at com.ericsson.datamigration.bridging.converter.core.wfm.yaml.steps.ApplyTransformationMessageBroker.execute(ApplyTransformationMessageBroker.java:104)
at com.ericsson.datamigration.bss.wfm.core.AbstractStep.run(AbstractStep.java:105)
at com.ericsson.datamigration.bss.wfm.yaml.definition.SimpleWorkflow.execute(SimpleWorkflow.java:103)
at com.ericsson.datamigration.bss.wfm.core.AbstractProcessor.run(AbstractProcessor.java:64)
at com.ericsson.datamigration.bss.wfm.yaml.definition.ConditionalWorkflow.execute(ConditionalWorkflow.java:95)
at com.ericsson.datamigration.bss.wfm.core.AbstractProcessor.run(AbstractProcessor.java:64)
at com.ericsson.datamigration.bss.wfm.application.WorkflowManagerApplication.process(WorkflowManagerApplication.java:243)
at com.ericsson.datamigration.bridging.dispatcher.core.kafka.consumer.KafkaMessageConsumer.processRequest(KafkaMessageConsumer.java:198)
at com.ericsson.datamigration.bridging.dispatcher.core.kafka.consumer.KafkaMessageConsumer.listen(KafkaMessageConsumer.java:89)
at sun.reflect.GeneratedMethodAccessor114.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:181)
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:114)
at org.springframework.kafka.listener.adapter.HandlerAdapter.invoke(HandlerAdapter.java:48)
at org.springframework.kafka.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:248)
at org.springframework.kafka.listener.adapter.RecordMessagingMessageListenerAdapter.onMessage(RecordMessagingMessageListenerAdapter.java:80)
at org.springframework.kafka.listener.adapter.RecordMessagingMessageListenerAdapter.onMessage(RecordMessagingMessageListenerAdapter.java:51)
at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeRecordListener(KafkaMessageListenerContainer.java:1071)
at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeWithRecords(KafkaMessageListenerContainer.java:1051)
at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeRecordListener(KafkaMessageListenerContainer.java:998)
at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeListener(KafkaMessageListenerContainer.java:866)
at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.run(KafkaMessageListenerContainer.java:724)
at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.util.concurrent.FutureTask.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)

答案1

得分: 1

是的,您可以同时运行多个GraalVM上下文。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 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(() -> {
        /*
         * 为简单起见,允许所有访问。在实际应用中,对资源的访问应该受到限制。
         */
        Context cx = Context.newBuilder(JS).allowHostAccess(HostAccess.ALL).allowPolyglotAccess(PolyglotAccess.ALL)
                .engine(sharedEngine).build();
        /*
         * 在上下文全局范围内注册一个Java方法作为JavaScript函数。
         */
        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并在其中运行消息处理的JavaScript代码。

英文:

Yes you can run multiple GraalVM contexts simultaneously.

As described in the following article: https://medium.com/graalvm/multi-threaded-java-javascript-language-interoperability-in-graalvm-2f19c1f9c37b

GraalVM’s JavaScript runtime supports parallel execution via multiple threads in a simple yet powerful way, which we believe is convenient for a variety of embedding scenarios.
The model is based on the following three simple rules:

  • In a polyglot application, an arbitrary number of JS runtimes can be created, but they should be used by one thread at a time.
  • Concurrent access to Java objects is allowed: any Java object can be accessed by any Java or JavaScript thread, concurrently.
  • Concurrent access to JavaScript objects is not allowed: any JavaScript object cannot be accessed by more than one thread at a time.

GraalVM enforces these rules at runtime, therefore making it easier and safer to reason about parallel and concurrent execution in a polyglot application.

So when you're trying to access a JS object (function) concurrently from multiple threads, you see the exception you showed.

What you can do is ensure that only 1 thread has access to your JS objects. One way to do this is to use synchronization. Another -- creating multiple Context objects 1 per thread.

This approach is used in this demo application: https://github.com/graalvm/graalvm-demos/tree/master/js-java-async-helidon

it uses a context provider helper 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;
        }
    }

And initializes them for every thread using a threadlocal:

    private final ThreadLocal&lt;ContextProvider&gt; jsContext = ThreadLocal.withInitial(() -&gt; {
        /*
         * 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(&quot;computeFromJava&quot;, createJavaInteropComputeFunction(provider));
        System.out.println(&quot;Created new JS context for thread &quot; + Thread.currentThread());
        return provider;
    });

Note here the Context object can share the engine for efficiency, you probably want to do the same if your JS source is the same for every thread.

Then when you process messages, every thread can retrieve its own Context and run message processing JavaScript in it.

huangapple
  • 本文由 发表于 2020年8月17日 20:47:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/63451148.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定