Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can not resolve NodeJS Promise Concurrently from Java Side #817

Open
pbk20191 opened this issue Apr 19, 2024 · 6 comments
Open

Can not resolve NodeJS Promise Concurrently from Java Side #817

pbk20191 opened this issue Apr 19, 2024 · 6 comments

Comments

@pbk20191
Copy link

pbk20191 commented Apr 19, 2024

It is clear that javascript is not supported under multi thread environment, but I suppose some components should handle multi-thread.

  1. Promise-Like
    Promise is good option to express concurrent control flow. However in current implementation we can not resolve Promise from different Thread!. (Except for few case)

(1) NodeJS -> Java

const JvmContextClass = Java.type("com.example.Executor")
const jvmContext = new JvmContextClass()
const myPromise = new Promise(jvmContext);
await myPromise
package com.example

class Executor {

    void then(Value resolve, Value reject) {
        Executors.newSingleThreadExecutor().execute(() => {
            try {
                Object someResult = computeSomething();
// Error, NodeJS is holding the context
                resolve.executeVoid(someResult);
            } catch (Throwable t) {
// Error, NodeJS runtime is holding the context
                reject.executeVoid(t);
            }
        })
// return control of current context back to NodeJS
    }

}

Only solution for this situation is to block the nodejs until Java resolve the Promise.

(2) Java -> embedded Javascript
This case is not a problem, since Java developer can synchronize the access to the Context

  1. NodeJS- microTasks
    We cannot queue runnable to the nodejs taskqueue e.g) setInterval, setTimeout, setImmediate when nodejs is taking control. So we cannot use those method to resolve Promise or queue task to notify the nodejs.
const JvmContextClass = Java.type("com.example.Executor")
const jvmContext = new JvmContextClass()
const myPromise = new Promise(jvmContext);
await myPromise
package com.example

class Executor {

    void then(Value resolve, Value reject) {
        Value setImmediate = resolve.getContext().eval("js", "setImmediate");
        Executors.newSingleThreadExecutor().execute(() => {
            try {
                Object someResult = computeSomething();
// Error, NodeJS is holding the context
                setImmediate.execute(() => {
                       resolve.executeVoid(someResult)
                }).asInt();
                resolve.executeVoid(someResult)
            } catch (Throwable t) {
// Error, NodeJS runtime is holding the context
                setImmediate.execute(() => {
                       reject.executeVoid(t);
                }).asInt();
                
            }
        })
// return control of current context back to NodeJS
    }

}

NodeJS task queue api and Promise-like object needs to support multi-threading out-of the box.

Creating nodejs worker thread to resolve one promise seems like pretty expensive.

Work Around

I make a workaround using Worker and Blocking Queue.
But it would be nice to use, something that nodeJS event loop can poll it, rather than blocking.
And still can not schedule task to NodeJS

data object JavaMain {
    @JvmStatic
    fun main(vararg args: String) {
//
        val mainContext = Context.getCurrent()
        val blockingQueue = LinkedBlockingQueue<Runnable>()
        val uuid = "X" + UUID.randomUUID().toString().replace("-","")
        val binding = mainContext.getBindings("js")
        val jsScript = """
            const { Worker } = require('worker_threads');
            const worker = new Worker(`
                const { workerData, parentPort } = require('worker_threads');
                const assert = require('assert');
                while (true) {
                    parentPort.postMessage(workerData.take());
                }
            `, { eval: true, workerData: $uuid })
             worker.on('message', block => {
                block()
            });
            worker;
        """.trimIndent()
        binding.putMember(uuid, blockingQueue)
        val proxyWorker = mainContext.eval("js", jsScript)
        mainContext.getBindings("js")
            .removeMember(uuid)
        blockingQueue.add{
            mainContext.eval("js", "console.log(1234)")
            println(proxyWorker)
        }
    }
}
@mikehearn
Copy link
Contributor

You might be interested in https://mikehearn.github.io/nodejvm/ which has an API and other utilities for synchronizing and scheduling work back and forth between NodeJS and Java.

@pbk20191
Copy link
Author

You might be interested in https://mikehearn.github.io/nodejvm/ which has an API and other utilities for synchronizing and scheduling work back and forth between NodeJS and Java.

I checked your code. and It does the exact same way I provided from work around.
My work-around also spawn nodejs worker which takes blocking queue to pull job from java side and dispatch to the nodejs main thread.
What I want to achieve is

  • Feed the job to the nodejs event loop directly or resovle the promise the java's any thread ( blocking the nodejs thread is not a good design any way, because it's event loop driven! )
  • ability to schedule job to nodejs event loop directly or some way to schedule some delayed jobs while preserving the delay ( work-around dispatch twice, it will always have more delay than it request)

@caoccao
Copy link

caoccao commented Apr 24, 2024

You might be interested in https://mikehearn.github.io/nodejvm/ which has an API and other utilities for synchronizing and scheduling work back and forth between NodeJS and Java.

I checked your code. and It does the exact same way I provided from work around. My work-around also spawn nodejs worker which takes blocking queue to pull job from java side and dispatch to the nodejs main thread. What I want to achieve is

  • Feed the job to the nodejs event loop directly or resovle the promise the java's any thread ( blocking the nodejs thread is not a good design any way, because it's event loop driven! )
  • ability to schedule job to nodejs event loop directly or some way to schedule some delayed jobs while preserving the delay ( work-around dispatch twice, it will always have more delay than it request)

I wonder if you have tried https://github.com/caoccao/Javet

@pbk20191
Copy link
Author

pbk20191 commented Apr 25, 2024

You might be interested in https://mikehearn.github.io/nodejvm/ which has an API and other utilities for synchronizing and scheduling work back and forth between NodeJS and Java.

I checked your code. and It does the exact same way I provided from work around. My work-around also spawn nodejs worker which takes blocking queue to pull job from java side and dispatch to the nodejs main thread. What I want to achieve is

  • Feed the job to the nodejs event loop directly or resovle the promise the java's any thread ( blocking the nodejs thread is not a good design any way, because it's event loop driven! )
  • ability to schedule job to nodejs event loop directly or some way to schedule some delayed jobs while preserving the delay ( work-around dispatch twice, it will always have more delay than it request)

I wonder if you have tried https://github.com/caoccao/Javet

This looks promising, didn't know that. But how can I send message from node to java?

It seems I can not inject Java to NodeJS

it.globalObject.set("FIII", Fooo())
// Undefined value
val foo = it.globalObject.get<V8ValueUndefined>("FIII")

@caoccao
Copy link

caoccao commented Apr 25, 2024

You might be interested in https://mikehearn.github.io/nodejvm/ which has an API and other utilities for synchronizing and scheduling work back and forth between NodeJS and Java.

I checked your code. and It does the exact same way I provided from work around. My work-around also spawn nodejs worker which takes blocking queue to pull job from java side and dispatch to the nodejs main thread. What I want to achieve is

  • Feed the job to the nodejs event loop directly or resovle the promise the java's any thread ( blocking the nodejs thread is not a good design any way, because it's event loop driven! )
  • ability to schedule job to nodejs event loop directly or some way to schedule some delayed jobs while preserving the delay ( work-around dispatch twice, it will always have more delay than it request)

I wonder if you have tried https://github.com/caoccao/Javet

This looks promising, didn't know that. But how can I send message from node to java?

It seems I can not inject Java to NodeJS

it.globalObject.set("FIII", Fooo())
// Undefined value
val foo = it.globalObject.get<V8ValueUndefined>("FIII")

Javet has its own universe tailed to Node.js and V8. In your case, you need a proxy converter to set up bi-directional binding. I recommend you to go over the tutorials and you'll get it. The Node.js env is genuine (100% compatible).

@pbk20191
Copy link
Author

pbk20191 commented Apr 30, 2024

I made node addon module which expose napi::threadsafefunction wrapped block as function pointer.
And than call that function pointer from jvm background thread. Which is very unsafe operation(I have to expose return type and parameter as raw pointer).
It would be great, if graaljs has its builtin solution to dispatch jvm Runnable to NodeJS main thread using napi::threadsafefunction .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants