Skip to content

Modify Graal engine creation to allow dynamic creation#20810

Open
Nadahar wants to merge 1 commit into
openhab:mainfrom
Nadahar:graalvm-cache
Open

Modify Graal engine creation to allow dynamic creation#20810
Nadahar wants to merge 1 commit into
openhab:mainfrom
Nadahar:graalvm-cache

Conversation

@Nadahar

@Nadahar Nadahar commented May 30, 2026

Copy link
Copy Markdown
Contributor

This alters the classloader used during engine registration and Language retrieval, and caches the Language instance. It must be seen together with openhab/openhab-core#5614, and the goal is to allow dynamic registration of Graal engines.

A description of both changes is given here, and this should allow installing and uninstalling Graal based add-ons without having to restart OH. It should also prevent the risk of one of them being unavailable if the timing of their creation during startup is unfortunate.

It needs proper testing to make sure that creating these using the alternative classloader don't have other side effects, like failing to see resources that it previously could. I'm ill-equipped to do this testing, as I don't use any of these languages myself, and lack both "source rules" and the knowledge of how to properly test it.

…e visibility, cache the Language instance.

Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com>
@Nadahar Nadahar requested a review from HolgerHees as a code owner May 30, 2026 01:30
@Nadahar Nadahar added the additional testing preferred The change works for the pull request author. A test from someone else is preferred though. label May 30, 2026
@Nadahar Nadahar requested a review from florian-h05 as a code owner May 30, 2026 01:30
@openhab-bot

Copy link
Copy Markdown
Collaborator

This pull request has been mentioned on openHAB Community. There might be relevant details there:

https://community.openhab.org/t/running-org-openhab-demo-app-with-org-openhab-automation-jsscripting-enabled/162466/23

@Nadahar Nadahar changed the title Moidfy Graal engine creation to allow dynamic creation Modify Graal engine creation to allow dynamic creation May 30, 2026
@HolgerHees

Copy link
Copy Markdown
Contributor

@Nadahar will test it during the next days.

@HolgerHees

HolgerHees commented May 31, 2026

Copy link
Copy Markdown
Contributor

I have compiled patched pythonscripting for openhab 5.1 and tested it in my production setup, which includes 141 file based rules. So far, everything appears to be working.

What I have not tested is openhab 5.2, or whether jsscripting and pythonscripting interfere with each other in any way.

@Nadahar

Nadahar commented May 31, 2026

Copy link
Copy Markdown
Contributor Author

I don't think there's any difference between 5.1 and 5.2 in this regard, I can't think of anything relevant that has changed lately.. what would be interesting is if you see what happens to your rules if you uninstall and reinstall the add-on without rebooting.

@Nadahar

Nadahar commented May 31, 2026

Copy link
Copy Markdown
Contributor Author

Also, to make the test "valid", you should start OH with truffle.engine.DisableLanguageCache set to true. It can be done by modifying the launch script.

@HolgerHees

HolgerHees commented May 31, 2026

Copy link
Copy Markdown
Contributor

what would be interesting is if you see what happens to your rules if you uninstall and reinstall the add-on without rebooting.

This worked before, as I only use pythonscripting on its own. I always delete the old kar file in the addons folder, which triggers an uninstall. Then I place the new kar file in the addons folder, which again triggers an install.

A proper test would be:

  • Compile both kar files.
  • Prepare both a jsscripting test rule and a pythonscripting test rule.
  • Put the jsscripting kar file into the addons folder and wait until the engine is activated and check if the rule is working.
  • Now put the second pythonscripting kar file into the addons folder and wait again until the engine is activated and the python rule is working.
  • And finally, test the install/uninstall process of both engines in reverse order.

this was exactly the scenario which was not working before.

@Nadahar

Nadahar commented May 31, 2026

Copy link
Copy Markdown
Contributor Author

Ok, you have spent more time fighting this than I have. But, the reason uninstalling and reinstalling has worked in the past is actually a bug, because the Language instance that has been cached was from the original plugin and not the new. Apparently it still worked, despite pointing to the wrong instance, but that doesn't mean that "all was well".

@HolgerHees

HolgerHees commented May 31, 2026

Copy link
Copy Markdown
Contributor

but that doesn't mean that "all was well".

totally agree ;-) I just expressed myself imprecisely.

I'm certainly happy that things are getting better now, too.

@HolgerHees

Copy link
Copy Markdown
Contributor

now, pythonscripting is running with "-Dtruffle.engine.DisableLanguageCache=true" as EXTRA_JAVA_OPTS, provided to my podman container without any problems.

@HolgerHees

Copy link
Copy Markdown
Contributor

I tried to test the final scenario, but without success.

First Time, when I activated the second addon. I got

2026-06-01 11:16:24.665 [ERROR] [ipt.internal.ScriptEngineManagerImpl] - Error while creating ScriptEngine
org.graalvm.polyglot.PolyglotException: java.lang.ExceptionInInitializerError
        at com.oracle.graal.python.PythonLanguageProvider.create(PythonLanguageProvider.java:34)
        at com.oracle.truffle.api.provider.LanguageProviderSupportImpl.create(LanguageProviderSupportImpl.java:59)
        at com.oracle.truffle.polyglot.LanguageCache.loadLanguage(LanguageCache.java:596)
        at com.oracle.truffle.polyglot.PolyglotLanguageInstance.<init>(PolyglotLanguageInstance.java:108)
        at com.oracle.truffle.polyglot.PolyglotLanguage.getInitLanguage(PolyglotLanguage.java:174)
        at com.oracle.truffle.polyglot.PolyglotLanguage.ensureInitialized(PolyglotLanguage.java:164)
        at com.oracle.truffle.polyglot.PolyglotLanguage.getOptionsInternal(PolyglotLanguage.java:146)
        at com.oracle.truffle.polyglot.PolyglotLanguage.getOptionValues(PolyglotLanguage.java:235)
        at com.oracle.truffle.polyglot.PolyglotContextConfig.<init>(PolyglotContextConfig.java:320)
        at com.oracle.truffle.polyglot.PolyglotEngineImpl.createContext(PolyglotEngineImpl.java:1931)
        at com.oracle.truffle.polyglot.PolyglotEngineDispatch.createContext(PolyglotEngineDispatch.java:172)
        at org.graalvm.polyglot.Context$Builder.build(Context.java:2148)
        at org.openhab.automation.pythonscripting.internal.scriptengine.graal.GraalPythonBindings.requireContext(GraalPythonBindings.java:118)
        at org.openhab.automation.pythonscripting.internal.scriptengine.graal.GraalPythonBindings.put(GraalPythonBindings.java:57)
        at java.scripting/javax.script.SimpleScriptContext.setAttribute(SimpleScriptContext.java:246)
        at org.openhab.core.automation.module.script.internal.ScriptEngineManagerImpl.addAttributeToScriptContext(ScriptEngineManagerImpl.java:262)
        at org.openhab.core.automation.module.script.internal.ScriptEngineManagerImpl.createScriptEngine(ScriptEngineManagerImpl.java:137)
        at org.openhab.core.automation.module.script.rulesupport.loader.AbstractScriptFileWatcher.createAndLoad(AbstractScriptFileWatcher.java:333)
        at org.openhab.core.automation.module.script.rulesupport.loader.AbstractScriptFileWatcher.lambda$13(AbstractScriptFileWatcher.java:311)
        at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1804)
        at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
        at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
        at java.base/java.lang.Thread.run(Thread.java:1583)

and then I recompiled it again with 'logger.info(">>>> TEST {}", System.getProperty("truffle.engine.DisableLanguageCache"));` to check if the property is active. (It was active) and I got

2026-06-01 11:23:47.299 [ERROR] [ipt.internal.ScriptEngineManagerImpl] - Error while executing script
javax.script.ScriptException: org.graalvm.polyglot.PolyglotException: java.lang.NoClassDefFoundError: Could not initialize class com.oracle.graal.python.PythonLanguage
        at org.openhab.automation.pythonscripting.internal.scriptengine.graal.GraalPythonScriptEngine.toScriptException(GraalPythonScriptEngine.java:309)
        at org.openhab.automation.pythonscripting.internal.scriptengine.InvocationInterceptingPythonScriptEngine.invokeFunction(InvocationInterceptingPythonScriptEngine.java:78)
        at org.openhab.core.automation.module.script.internal.ScriptEngineManagerImpl.removeEngine(ScriptEngineManagerImpl.java:201)
        at org.openhab.core.automation.module.script.internal.ScriptEngineManagerImpl.createScriptEngine(ScriptEngineManagerImpl.java:115)
        at org.openhab.core.automation.module.script.rulesupport.loader.AbstractScriptFileWatcher.createAndLoad(AbstractScriptFileWatcher.java:333)
        at org.openhab.core.automation.module.script.rulesupport.loader.AbstractScriptFileWatcher.lambda$13(AbstractScriptFileWatcher.java:311)
        at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1804)
        at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
        at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
        at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: org.graalvm.polyglot.PolyglotException: java.lang.NoClassDefFoundError: Could not initialize class com.oracle.graal.python.PythonLanguage
        at com.oracle.graal.python.PythonLanguageProvider.create(PythonLanguageProvider.java:34)
        at com.oracle.truffle.api.provider.LanguageProviderSupportImpl.create(LanguageProviderSupportImpl.java:59)
        at com.oracle.truffle.polyglot.LanguageCache.loadLanguage(LanguageCache.java:596)
        at com.oracle.truffle.polyglot.PolyglotLanguageInstance.<init>(PolyglotLanguageInstance.java:108)
        at com.oracle.truffle.polyglot.PolyglotLanguage.getInitLanguage(PolyglotLanguage.java:174)
        at com.oracle.truffle.polyglot.PolyglotLanguage.ensureInitialized(PolyglotLanguage.java:164)
        at com.oracle.truffle.polyglot.PolyglotLanguage.getOptionsInternal(PolyglotLanguage.java:146)
        at com.oracle.truffle.polyglot.PolyglotLanguage.getOptionValues(PolyglotLanguage.java:235)
        at com.oracle.truffle.polyglot.PolyglotContextConfig.<init>(PolyglotContextConfig.java:320)
        at com.oracle.truffle.polyglot.PolyglotEngineImpl.createContext(PolyglotEngineImpl.java:1931)
        at com.oracle.truffle.polyglot.PolyglotEngineDispatch.createContext(PolyglotEngineDispatch.java:172)
        at org.graalvm.polyglot.Context$Builder.build(Context.java:2148)
        at org.openhab.automation.pythonscripting.internal.scriptengine.graal.GraalPythonBindings.requireContext(GraalPythonBindings.java:118)
        at org.openhab.automation.pythonscripting.internal.scriptengine.graal.GraalPythonBindings.entrySet(GraalPythonBindings.java:89)
        at java.base/java.util.AbstractMap.containsKey(AbstractMap.java:148)
        at java.scripting/javax.script.SimpleScriptContext.getAttribute(SimpleScriptContext.java:158)
        at org.openhab.automation.pythonscripting.internal.PythonScriptEngine.beforeInvocation(PythonScriptEngine.java:233)
        at org.openhab.automation.pythonscripting.internal.scriptengine.InvocationInterceptingPythonScriptEngine.invokeFunction(InvocationInterceptingPythonScriptEngine.java:73)
        ... 11 more
2026-06-01 11:23:47.309 [ERROR] [ipt.internal.ScriptEngineManagerImpl] - Error while creating ScriptEngine
org.graalvm.polyglot.PolyglotException: java.lang.ExceptionInInitializerError
        at com.oracle.graal.python.PythonLanguageProvider.create(PythonLanguageProvider.java:34)
        at com.oracle.truffle.api.provider.LanguageProviderSupportImpl.create(LanguageProviderSupportImpl.java:59)
        at com.oracle.truffle.polyglot.LanguageCache.loadLanguage(LanguageCache.java:596)
        at com.oracle.truffle.polyglot.PolyglotLanguageInstance.<init>(PolyglotLanguageInstance.java:108)
        at com.oracle.truffle.polyglot.PolyglotLanguage.getInitLanguage(PolyglotLanguage.java:174)
        at com.oracle.truffle.polyglot.PolyglotLanguage.ensureInitialized(PolyglotLanguage.java:164)
        at com.oracle.truffle.polyglot.PolyglotLanguage.getOptionsInternal(PolyglotLanguage.java:146)
        at com.oracle.truffle.polyglot.PolyglotLanguage.getOptionValues(PolyglotLanguage.java:235)
        at com.oracle.truffle.polyglot.PolyglotContextConfig.<init>(PolyglotContextConfig.java:320)
        at com.oracle.truffle.polyglot.PolyglotEngineImpl.createContext(PolyglotEngineImpl.java:1931)
        at com.oracle.truffle.polyglot.PolyglotEngineDispatch.createContext(PolyglotEngineDispatch.java:172)
        at org.graalvm.polyglot.Context$Builder.build(Context.java:2148)
        at org.openhab.automation.pythonscripting.internal.scriptengine.graal.GraalPythonBindings.requireContext(GraalPythonBindings.java:118)
        at org.openhab.automation.pythonscripting.internal.scriptengine.graal.GraalPythonBindings.put(GraalPythonBindings.java:57)
        at java.scripting/javax.script.SimpleScriptContext.setAttribute(SimpleScriptContext.java:246)
        at org.openhab.core.automation.module.script.internal.ScriptEngineManagerImpl.addAttributeToScriptContext(ScriptEngineManagerImpl.java:262)
        at org.openhab.core.automation.module.script.internal.ScriptEngineManagerImpl.createScriptEngine(ScriptEngineManagerImpl.java:137)
        at org.openhab.core.automation.module.script.rulesupport.loader.AbstractScriptFileWatcher.createAndLoad(AbstractScriptFileWatcher.java:333)
        at org.openhab.core.automation.module.script.rulesupport.loader.AbstractScriptFileWatcher.lambda$13(AbstractScriptFileWatcher.java:311)
        at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1804)
        at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
        at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
        at java.base/java.lang.Thread.run(Thread.java:1583)

After a restart, both engines are working.

@Nadahar

Nadahar commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

I'm not sure how to evaluate the "KAR test" above, there are too many unknowns involved with KAR files as I see it. It isn't even straight forward to build a "correct" KAR, and even if you do, I don't think we know which feature definitions will be used if the one in core and the one in the KAR are different..?

At least one of the errors seem to be related to something unrelated to this issue, NoClassDefFoundError points to problems on a completely different level.

This does solve running both JS and Python from Eclipse. I doubt that's the only thing it solves, even if there are other things that don't work out there.

The situation in Eclipse is this: When I try my customized Graal setup for Eclipse on a Linux VM, both work. When I try this on Windows, Python fails. The difference seems to be that of timing. Because of the static cache, for both to work, they must both be "initialized" in somewhat the same timeframe, so that by the time the first Graal based add-ons initialize, both "engines" are registered in Graal. Python has a lot of files/libs that must be unpacked, and since Windows use file locking, large number of operations on small files are typically quite a bit slower (every access must first check the "lock database"). This leads to Python not being ready in time for JS to "initialize", thus locking Python out of the static cache.

I very much doubt this timing issue is unique to Eclipse, it's most likely a general problem, but it's difficult to control these timings to absolutely "prove it". This change (together with setting the system property in core) solves this whole timing issue, so that both engines are available/visible even if they initialize at different speeds.

I therefor think that unless something "gets worse" by doing this, it should be merged, even if it doesn't solve all related problems.

@Nadahar

Nadahar commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

It should be mentioned that these changes (that I've had to apply locally) are what enabled me to look into openhab/openhab-core#5635 and #20853. I think it's very useful to be able to debug the Graal add-ons, so for that alone, I think this has value. I can't quite understand how that wouldn't have value for others as well..?

@lsiepel

lsiepel commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

@HolgerHees it looks like you where unable to test these changes. Need any help, maybe @Nadahar is able to assist here?
Without testing, it is somewhat risky to add now. We are days away from a code freeze. Hope to get this in as it seems very helpfull for debugging.

@Nadahar

Nadahar commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

I don't think we need this in for 5.2.0, if the main benefit is when developing. In that case, it might be better to merge it soon after 5.2.0 is released, so that we have lots of time to discover any potential side effects.

I just don't want it to die/become forgotten, since it solves something that have at least been bothering me for a very long time. I strongly suspect that it will also have some benefit for users when installing the add-ons, even if it doesn't solve all problems.

So, let's let this one wait for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

additional testing preferred The change works for the pull request author. A test from someone else is preferred though.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants