Include third party dependencies in Ignition module

I did some investigation of the class file and it looks like it is trying to load a platform-specific library when it runs (in my case, a .so library as I am on Linux). These libraries are accessible in my LD_LIBRARY_PATH environmental variable. Should I be including this library in the modl file, or should Ignition be able to access the library via LD_LIBRARY_PATH?

Native libraries need to be in one of the directories specified by the java.library.path system property (generally). You can print this out and see what the values are. I'm pretty sure lib and/or lib/core/gateway in the Ignition install dir are included.

Okay, thanks. It looks like the libraries I am trying to access do exist in java.library.path, so I don't think that is the issue now.

Keep in mind that the designer's script console doesn't use the gateway's native libraries, unless they are packaged and handled entirely within the relevant jars.

If you are targeting gateway scope, use a gateway message handler to actually test in gateway scope. (Call the handler with system.util.sendMessage or system.util.sendRequest.)

Thanks for the response, @pturmel. I am calling a command (system.example.startPublishing) and am now getting an error (after some revisions to the my java file that my nddsjava library isn't found. I printed the java.library.path system property in Script Console and the folder containing the nddsjava library was included there.

Does this error have anything to do with Script Console not using the gateway's native libraries?

Likely. If this is something that you should always be calling from gateway scope, consider implementing an RPC handler and having your vision client/designer scope scripting stubs use that.

No, because you've apparently followed the scripting RPC example, and your code is executing on the gateway when it tries to load the native library.

Ah, didn't see that.

{ Hmmm. My advice has [expletive] hasn't been great the past couple days. :frowning_face: }

@BenS one thing you do want to make sure of is that you check / print the value of java.library.path from the Gateway scope, i.e. print it when your module starts up or from a tag change script, and not from the Designer scope, which is where checking it in the Script Console would execute.

1 Like

@Kevin.Herron I printed the value of java.library.path from the Gateway scope and, sure enough, the folder containing the nddsjava library is not there. Can I add libraries to java.library.path?

@pturmel no worries, thanks for chiming in regardless!

You can set the value of java.library.path, but you'd need to be careful that the folders expected by Ignition remain on there.

It would be better if you just copied your native libraries into one of the folders already on the path.

Ok, great - thanks for all the help so far! I added the libraries to one of the folders on the path and can tell that Ignition is loading the libraries. Now I have a new issue - it looks like Ignition is trying to load the libraries multiple times (presumably in the Gateway, Client, and/or Designer scopes). Here is the error message I am getting:

Jython 2.7.2 (uncontrolled:000000000000, Jan 25 2022, 14:39:15)
[OpenJDK 64-Bit Server VM (Azul Systems, Inc.)] on java11.0.18

>>> 
Java Traceback:
Traceback (most recent call last):
  File "<input>", line 1, in <module>
	at com.sun.proxy.$Proxy58.startPublishing(Unknown Source)
	at com.inductiveautomation.ignition.examples.scripting.client.ClientScriptModule.startPublishingOnServer(ClientScriptModule.java:18)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.base/java.lang.reflect.Method.invoke(Unknown Source)
java.lang.reflect.UndeclaredThrowableException: java.lang.reflect.UndeclaredThrowableException

	at org.python.core.Py.JavaError(Py.java:547)
	at org.python.core.Py.JavaError(Py.java:538)
	at org.python.core.PyReflectedFunction.__call__(PyReflectedFunction.java:192)
	at com.inductiveautomation.ignition.common.script.ScriptManager$ReflectedInstanceFunction.__call__(ScriptManager.java:552)
	at org.python.core.PyObject.__call__(PyObject.java:461)
	at org.python.core.PyObject.__call__(PyObject.java:465)
	at org.python.pycode._pyx4.f$0(<input>:1)
	at org.python.pycode._pyx4.call_function(<input>)
	at org.python.core.PyTableCode.call(PyTableCode.java:173)
	at org.python.core.PyCode.call(PyCode.java:18)
	at org.python.core.Py.runCode(Py.java:1687)
	at org.python.core.Py.exec(Py.java:1731)
	at org.python.util.PythonInterpreter.exec(PythonInterpreter.java:277)
	at org.python.util.InteractiveInterpreter.runcode(InteractiveInterpreter.java:130)
	at com.inductiveautomation.ignition.designer.gui.tools.jythonconsole.JythonConsole$ConsoleWorker.doInBackground(JythonConsole.java:626)
	at com.inductiveautomation.ignition.designer.gui.tools.jythonconsole.JythonConsole$ConsoleWorker.doInBackground(JythonConsole.java:614)
	at java.desktop/javax.swing.SwingWorker$1.call(Unknown Source)
	at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
	at java.desktop/javax.swing.SwingWorker.run(Unknown Source)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
	at java.base/java.lang.Thread.run(Unknown Source)
Caused by: java.lang.reflect.UndeclaredThrowableException
	at com.sun.proxy.$Proxy58.startPublishing(Unknown Source)
	at com.inductiveautomation.ignition.examples.scripting.client.ClientScriptModule.startPublishingOnServer(ClientScriptModule.java:18)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.base/java.lang.reflect.Method.invoke(Unknown Source)
	at org.python.core.PyReflectedFunction.__call__(PyReflectedFunction.java:190)
	... 19 more
Caused by: com.inductiveautomation.ignition.client.gateway_interface.GatewayException: Native Library /usr/local/bin/ignition/lib/libnddsjava.so already loaded in another classloader
	at com.inductiveautomation.ignition.client.gateway_interface.GatewayInterface.newGatewayException(GatewayInterface.java:351)
	at com.inductiveautomation.ignition.client.gateway_interface.GatewayInterface.sendMessage(GatewayInterface.java:325)
	at com.inductiveautomation.ignition.client.gateway_interface.GatewayInterface.sendMessage(GatewayInterface.java:278)
	at com.inductiveautomation.ignition.client.gateway_interface.GatewayInterface.moduleInvokeSafe(GatewayInterface.java:917)
	at com.inductiveautomation.ignition.client.gateway_interface.ModuleRPCFactory$DynamicRPCHandler.invoke(ModuleRPCFactory.java:53)
	... 26 more
Caused by: java.lang.UnsatisfiedLinkError: Native Library /usr/local/bin/ignition/lib/libnddsjava.so already loaded in another classloader
	at java.lang.ClassLoader$NativeLibrary.loadLibrary(null)
	at java.lang.ClassLoader.loadLibrary0(null)
	at java.lang.ClassLoader.loadLibrary(null)
	at java.lang.Runtime.loadLibrary0(null)
	at java.lang.System.loadLibrary(null)
	at com.rti.dds.util.NativeInterface.loadNativeLibrary(NativeInterface.java:153)
	at com.rti.dds.util.NativeInterface.loadNativeLibraries(NativeInterface.java:125)
	at com.rti.dds.domain.DomainParticipantFactory.<clinit>(DomainParticipantFactory.java:77)
	at com.inductiveautomation.ignition.examples.scripting.common.HMItoSICCommand.startPublishing(HMItoSICCommand.java:56)
	at com.inductiveautomation.ignition.examples.scripting.gateway.GatewayScriptModule.startPublishing(GatewayScriptModule.java:15)
	at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(null)
	at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(null)
	at java.lang.reflect.Method.invoke(null)
	at com.inductiveautomation.ignition.gateway.servlets.gateway.functions.ModuleInvoke.invoke(ModuleInvoke.java:167)
	at com.inductiveautomation.ignition.gateway.servlets.Gateway.doPost(Gateway.java:434)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:523)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:590)
	at com.inductiveautomation.ignition.gateway.bootstrap.MapServlet.service(MapServlet.java:86)
	at org.eclipse.jetty.servlet.ServletHolder$NotAsync.service(ServletHolder.java:1410)
	at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:764)
	at org.eclipse.jetty.servlet.ServletHandler$ChainEnd.doFilter(ServletHandler.java:1665)
	at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:527)
	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:131)
	at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:578)
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:223)
	at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1570)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:221)
	at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1383)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:176)
	at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:484)
	at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1543)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:174)
	at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1305)
	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:129)
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
	at com.inductiveautomation.catapult.handlers.RemoteHostNameLookupHandler.handle(RemoteHostNameLookupHandler.java:121)
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
	at org.eclipse.jetty.rewrite.handler.RewriteHandler.handle(RewriteHandler.java:301)
	at org.eclipse.jetty.server.handler.HandlerList.handle(HandlerList.java:51)
	at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:141)
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
	at org.eclipse.jetty.server.Server.handle(Server.java:563)
	at org.eclipse.jetty.server.HttpChannel.lambda$handle$0(HttpChannel.java:505)
	at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:762)
	at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:497)
	at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:282)
	at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:314)
	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
	at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.runTask(AdaptiveExecutionStrategy.java:416)
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.consumeTask(AdaptiveExecutionStrategy.java:385)
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.tryProduce(AdaptiveExecutionStrategy.java:272)
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.lambda$new$0(AdaptiveExecutionStrategy.java:140)
	at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:411)
	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:934)
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1078)
	at java.lang.Thread.run(null)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
	at com.sun.proxy.$Proxy58.startPublishing(Unknown Source)
	at com.inductiveautomation.ignition.examples.scripting.client.ClientScriptModule.startPublishingOnServer(ClientScriptModule.java:18)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.base/java.lang.reflect.Method.invoke(Unknown Source)

Any suggestions on how to ensure that Ignition just loads the libraries once?

Ignition doesn't load these libraries, your dependency does.

It's not a problem that the client, designer, and gateway would all load them because that's 3 different JVMs.

I don't know why it's being loaded multiple times.

One guess: if you've been trying to hot-update your module instead of restart the Ignition Gateway every time, it's very possible you're leaking ClassLoaders by not shutting down / unloading completely (which may not be possible since you're loading a native library...)

2 Likes

Looks like that was it - restarting the Gateway fixed my problem. Had a few other things to debug but the module prototype is functioning now. Thanks for all the help today!