jpg0 / ohj

Openhab Javascript Library
Eclipse Public License 2.0
6 stars 2 forks source link

Async rules not supported? #14

Open simonihmig opened 3 years ago

simonihmig commented 3 years ago

Note: I am not sure if that issue really belongs here, or somewhere upstream. If I should open this elsewhere, then please tell me.

I wanted to put a delay into a rule, but this somehow does not seem to work, neither

setTimeout(() => logger.info(`delay end`, 100);

nor

await new Promise(resolve => setTimeout(resolve, 100));
logger.info(`delay end`, 100);

... seem to execute anything that comes after the timeout. As if the VM shuts down the execution of the script.

This is unfortunate, as the need for delays will pop up quite often I guess. I have no idea how the Java and GraalVM side of things work, so don't want to speculate on what's happening or how to fix this. But in case the JS execution is indeed terminated as soon as the rule's execute function synchronously returns, then maybe it should at least be made "promise-aware", so at least the second example above would work?

JamieTemple commented 3 years ago

Not quite the same, but I do this:

var harmonyItem = getItem(ButtonPress());

harmonyItem.sendCommand("VolumeUp");

java.lang.Thread.sleep(500);

harmonyItem.sendCommand("VolumeUp");

:)

jpg0 commented 3 years ago

I've had this issue too, when trying to use a library that relied on promises.

I believe that the underlying cause is that the Node runtime queues these async tasks and schedules them for execution in the execution loop (so they are run when the current thread completes). I do not believe that this happens in the standard GraalJS runtime.

Saying this, it's probably possible to get it working. I believe that the Graal NodeJS runtime does support this functionality, but I don't think that the embedded runtime does. Maybe the first thing to try is request that it's made available in the embedded runtime too (or find out how to enable it). Failing this, it may be possible to intercept the tasks to be scheduled, and create an event loop in Java. This would be pretty straightforward for setTimeout (by replacing the function), although I'm not sure whether it's possible with promise creation directly.

I also see that it may be possible to switch to wrapping the NodeJS runtime, with something like https://github.com/mikehearn/nodejvm, to avoid needing to get it's things ported over to the embedded one.

jpg0 commented 3 years ago

Just dropping this here whilst I remember: I looked at https://github.com/mikehearn/nodejvm but it won't work here because it requires starting the GraalJS NodeJS binary and executing Java from there, with the ability to get back into the JS runtime (so inverting the embedding). In theory this would be possible with openHAB but would require reworking the openHAB startup to embed it into a GraalJS node runtime.

The node JS event loop is not available in the embedded JS runtime because it actually comes from node, not Graal. The embedded runtime does not include any node code so doesn't support it.

nils commented 3 years ago

@jpg0 Isn't the event loop an integral part of every ECMAScript implementation? So that's basically an issue in GraalJS (embedded runtime), isn't it?

Maybe you can address this topic there in a GitHub issue to discuss how an event loop might be supported in the embedded runtime from their side in the future?

PS: Seems like that discussion is already ongoing: https://github.com/oracle/graaljs/issues/321 Maybe you can add you thoughts and make them aware that there are more projects with this requirement.

PPS: This topic kept spinning in my head and I couldn't avoid reading more :-) I see that there are also other people disappointed by the "fundamentally broken promise of GraalVM JavaScript interop" https://github.com/oracle/graaljs/issues/2#issuecomment-775508050.

Seems like having full interoperability would require a heavy investment on GraalJS side.

An external process for running JavaScript/Node.js rules even seems simpler to do for me now after reading the full conversation. Maybe an external JS runtime could subscribe to the openHAB event bus (using SSE?) for rule triggers and manipulate items via the OpenHAB REST API? Could even be "real" node.js then instead of GraalJS...

jpg0 commented 3 years ago

@nils I read that Mozilla explanation and I wasn't aware that the event loop is actually used to for each function invocation, I assumed there was some standard stack-based thing like other languages, which is how I assume that GraalJS does it. In practice I don't think that it makes much difference (excluding execution order for which there is no guarantee anyway), other than the ability to manually add things to the loop, of which I can see a single ECMAScript primitive: setTimeout (well, I'm not sure if it's part of the ECMAScript spec, but it's expected in all JS runtimes afaics).

I also suspect that this call can be used as a primitive to build on other things - such as setImmediate and setInterval, and then also Promises. Implementing these on top of setTimeout doesn't feel too hard, and in fact I'd not be surprised if existing pure JS implementations exist already as polyfills etc. I think that it's important to separate this from the other things absent from the non-node GraalJS (which are primarily all the node APIs, like file/net/etc).

If this is the primary goal here, I wonder if it would actually be so hard to support? I'm thinking that it would be possible to modify the openHAB GraalJS plugin such that:

Whilst I haven't tried this, it doesn't feel like too much to do. It if did work, it would add support for Promises and setTimeout etc, but not for the other node APIs. Saying this, it could also be implemented in embedded GraalJS itself, which may be a better place for it.