Issue with Third Party Dependent Libraries in Custom Module

Hi everyone,

I'm currently developing a module, and I'm encountering an issue where the module isn't recognizing the dependent libraries specified in my build.gradle file. These libraries are included in the common scope, and the module.xml correctly shows them with the right scope. I've used modlApi, and I’ve confirmed that the dependencies are bundled in the .modl file.

However, the module fails to locate the libraries during runtime, and the only workaround that seems to fix the issue is manually copying the JARs into the lib/core/gateway folder and restarting the Ignition Gateway. After doing this, the module works as expected with no errors when the script runs.

Am I missing something obvious here? Is there a better or programmatic way to ensure that these dependencies are added to the classpath and library path when the module is installed?

Any guidance or suggestions would be greatly appreciated!

Thanks in advance!

If the dependencies are listed in the module.xml and part of the .modl file that's a good start... assuming that's all correct - what does the error look like? How are you determining these dependencies are or are not on the class path?

So at the moment I get the output from "System.getProperty("java.class.path")". I have even tried to dynamically copy over my .jar files to the lib/core/gateway directory and add them to the "java.class.path" property, but still, it would only work after a gateway restart.

The error:

com.inductiveautomation.ignition.common.script.JythonExecException: Traceback (most recent call last): File "", line 11, in runAction File "", line 2, in faceDetection at ai.djl.repository.zoo.Criteria.loadModel(Criteria.java:125) at com.lidttech.deeplearning.facerecognition.FaceDetection.predictFaceDetection(FaceDetection.java:215) at com.lidttech.deeplearning.facerecognition.FaceDetection.runDetection(FaceDetection.java:60) at com.lidttech.deeplearning.facerecognition.FaceRecognitionGatewayScriptModule.runFaceDetectionImpl(FaceRecognitionGatewayScriptModule.java:28) at com.lidttech.deeplearning.facerecognition.DeepLearningModule.runFaceDetection(DeepLearningModule.java:43) 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) ai.djl.repository.zoo.ModelNotFoundException: ai.djl.repository.zoo.ModelNotFoundException: ModelZoo doesn't support specified engine: PyTorch

at org.python.core.Py.JavaError(Py.java:545)

at org.python.core.Py.JavaError(Py.java:536)

at org.python.core.PyReflectedFunction.__call__(PyReflectedFunction.java:192)

at com.inductiveautomation.ignition.common.script.ScriptManager$ReflectedInstanceFunction.__call__(ScriptManager.java:553)

at org.python.core.PyObject.__call__(PyObject.java:494)

at org.python.core.PyObject.__call__(PyObject.java:498)

at org.python.pycode._pyx7.faceDetection$1(:2)

at org.python.pycode._pyx7.call_function()

at org.python.core.PyTableCode.call(PyTableCode.java:173)

at org.python.core.PyBaseCode.call(PyBaseCode.java:168)

at org.python.core.PyFunction.__call__(PyFunction.java:437)

at org.python.pycode._pyx6.runAction$1(:20)

at org.python.pycode._pyx6.call_function()

at org.python.core.PyTableCode.call(PyTableCode.java:173)

at org.python.core.PyBaseCode.call(PyBaseCode.java:306)

at org.python.core.PyFunction.function___call__(PyFunction.java:474)

at org.python.core.PyFunction.__call__(PyFunction.java:469)

at org.python.core.PyFunction.__call__(PyFunction.java:464)

at com.inductiveautomation.ignition.common.script.ScriptManager.runFunction(ScriptManager.java:847)

at com.inductiveautomation.ignition.common.script.ScriptManager.runFunction(ScriptManager.java:829)

at com.inductiveautomation.ignition.gateway.project.ProjectScriptLifecycle$TrackingProjectScriptManager.runFunction(ProjectScriptLifecycle.java:868)

at com.inductiveautomation.ignition.common.script.ScriptManager$ScriptFunctionImpl.invoke(ScriptManager.java:1010)

at com.inductiveautomation.ignition.gateway.project.ProjectScriptLifecycle$AutoRecompilingScriptFunction.invoke(ProjectScriptLifecycle.java:950)

at com.inductiveautomation.perspective.gateway.script.ScriptFunctionHelper.invoke(ScriptFunctionHelper.java:161)

at com.inductiveautomation.perspective.gateway.script.ScriptFunctionHelper.invoke(ScriptFunctionHelper.java:98)

at com.inductiveautomation.perspective.gateway.action.ScriptAction.runAction(ScriptAction.java:80)

at com.inductiveautomation.perspective.gateway.model.ActionCollection$ActionSequence$ExecuteActionsTask.lambda$call$0(ActionCollection.java:263)

at com.inductiveautomation.perspective.gateway.api.LoggingContext.mdc(LoggingContext.java:54)

at com.inductiveautomation.perspective.gateway.model.ActionCollection$ActionSequence$ExecuteActionsTask.call(ActionCollection.java:252)

at com.inductiveautomation.perspective.gateway.model.ActionCollection$ActionSequence$ExecuteActionsTask.call(ActionCollection.java:221)

at com.inductiveautomation.perspective.gateway.threading.BlockingTaskQueue$TaskWrapper.run(BlockingTaskQueue.java:154)

at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)

at java.base/java.util.concurrent.FutureTask.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 com.inductiveautomation.perspective.gateway.threading.BlockingWork$BlockingWorkRunnable.run(BlockingWork.java:58)

at java.base/java.lang.Thread.run(Unknown Source)

Caused by: org.python.core.PyException: ai.djl.repository.zoo.ModelNotFoundException: ai.djl.repository.zoo.ModelNotFoundException: ModelZoo doesn't support specified engine: PyTorch

... 37 common frames omitted

Caused by: ai.djl.repository.zoo.ModelNotFoundException: ModelZoo doesn't support specified engine: PyTorch

at ai.djl.repository.zoo.Criteria.loadModel(Criteria.java:125)

at com.lidttech.deeplearning.facerecognition.FaceDetection.predictFaceDetection(FaceDetection.java:215)

at com.lidttech.deeplearning.facerecognition.FaceDetection.runDetection(FaceDetection.java:60)

at com.lidttech.deeplearning.facerecognition.FaceRecognitionGatewayScriptModule.runFaceDetectionImpl(FaceRecognitionGatewayScriptModule.java:28)

at com.lidttech.deeplearning.facerecognition.DeepLearningModule.runFaceDetection(DeepLearningModule.java:43)

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)


So it actually says :"Caused by: ai.djl.repository.zoo.ModelNotFoundException: ModelZoo doesn't support specified engine: PyTorch", but it gives this when it can't find the model class.

At the moment I have my own directory located inside the lib directory and I add my directory to the library and classpath inside the Ignition.conf file.

Still, I need a gateway restart after installing the module.

Any ideas?

Looks like a side effect of modules having isolated classloaders.

If the library does dynamic classloading, a Perspective thread won't be using the right classloader for the library guts to get what they need. Modules really need to expose everything necessary for scripting in system.something.*, and those exposed functions should temporarily switch their thread classloader to the module's.

I am actually using the system.somefunction.* structure to do the scripting. Still no luck. I am going to try do a fresh install of Ignition on a new OS and see if it gives the same issue.

I would be surprised if it didn't.

Are you changing classloaders while running your exposed scripting functions?

Uhm... Not sure to be honest, I don't think so. Any guidance here on best practice would be appreciated. (Not a frequent Iava developer, actually only learnt it for module development)

There's not so much a best practice, as "necessary" practice. Most libraries don't need any special classloader manipulation, as they make static references to all the other classes they use.

Libraries that load classes by string name instead of a static reference typically rely on the thread's current classloader, deliberately, so that the caller can control where the named class comes from. Your call trace suggests that is what is happening.

Your exposed script function(s) having this problem need to capture the calling thread's classloader into a local variable, then change to the module's classloader (simply grab the gateway hook's class's classloader), then in a finally clause, restore the caller's classloader.

Wow!! That solved it!! Thank you so much I added as you said and worked first time:

// Capture the current thread's classloader
        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();

        // Switch to the module's classloader
        ClassLoader moduleClassLoader = FaceDetection.class.getClassLoader();

        try {
            Thread.currentThread().setContextClassLoader(moduleClassLoader);
            // Proceed with the detection process
            if (inputType.equalsIgnoreCase("Image")) {
                Image img = (Image) setImage(inputSource);
                if (img != null) {
                    DetectedObjects detection = predictFaceDetection(img);
                    logger.info("Detected Objects: {}", detection);
                    return handleOutput(img, detection, outputType);
                }
            } else if (inputType.equalsIgnoreCase("Video")) {
                processVideoStream((String) inputSource, outputType);
            }
        } finally {
            // Restore the original classloader
            Thread.currentThread().setContextClassLoader(originalClassLoader);
        }

Appreciate all the help.

4 Likes