Issue
We have an application that uses OR-Tools, which consist on several modules (maven, all of which are Spring Boot modules), in particular:
- optimizer module, which loads the or-tools DLL through the following code:
public class Optimizer {
static {
try {
// extracts .dll from or-tools-windows jar and copies it into C:\users\my-user\AppData\Local\Temp\temp_ortools
// calls System.load("C:\users\my-user\AppData\Local\Temp\temp_ortools\jniortools.dll")
carregaBibliotecasORTools();
} catch (Exception e) {
log.info(e.getMessage());
} catch (Error e) {
log.info(e.getMessage());
}
}
public static OptimizerData getOptimizerData() {
OptimizerData optimizerData = new OptimizerData();
System.out.println("Instantiating Solver");
optimizerData.solver = MPSolver.createSolver("SCIP");
System.out.println("Solver Instantiated");
return optimizerData;
}
}
- service module, which uses:
@Service
public class PlanningService{
public void executePlan() {
OptimizerData optimizerData = Optimizer.getOptimizerData();
...
}
}
- web module (spring-boot-starter-web dependency ), which makes the service available through a controller
We have successfully run the code on the following situations:
- Running a test method (on netbeans) on the Optimizer module , which calls the
getOptimizerData
method - Generating a fat jar with the 3 modules (by building the web module on netbeans) and running the fat jar. the endpoint call works fine
However, when we run the web module on Netbeans the following error message is shown when calling the endpoint:
2021-05-09 22:59:38.587 ERROR 19300 --- [nio-8020-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.UnsatisfiedLinkError: com.google.ortools.linearsolver.main_research_linear_solverJNI.MPSolver_createSolver(Ljava/lang/String;)J] with root cause
Some important observations:
- This seems like a netbeans-specific issue given the fat jar works fine
- We have executed the following method (System.load) in order to verify whether the library had in fact been loaded:
public static void listAllLoadedNativeLibrariesFromJVM() {
ClassLoader appLoader = ClassLoader.getSystemClassLoader();
ClassLoader currentLoader = LibraryLoader.class.getClassLoader();
ClassLoader[] loaders = new ClassLoader[] { appLoader, currentLoader };
for (int i=0; i<loaders.length; i++) {
final String[] libraries = ClassScope.getLoadedLibraries(loaders[i]);
for (String library : libraries) {
System.out.println("Loader " + loaders[i].getClass().getName() + " : " + library);
}
}
}
The results from this method were:
- Test file in Optimizer module (which worked fine):
Loader sun.misc.Launcher$AppClassLoader : C:\Program Files\Java\jdk1.8.0_281\jre\bin\zip.dll
Loader sun.misc.Launcher$AppClassLoader : C:\Users\my-user\AppData\Local\Temp\temp_ortools\jniortools.dll
Loader sun.misc.Launcher$AppClassLoader : C:\Program Files\Java\jdk1.8.0_281\jre\bin\zip.dll
Loader sun.misc.Launcher$AppClassLoader : C:\Users\my-user\AppData\Local\Temp\temp_ortools\jniortools.dll
- Fat .jar generated by compiling the web module (which worked fine):
Loader sun.misc.Launcher$AppClassLoader : C:\Program Files\Java\jre1.8.0_281\bin\zip.dll
Loader sun.misc.Launcher$AppClassLoader : C:\Program Files\Java\jre1.8.0_281\bin\management.dll
Loader sun.misc.Launcher$AppClassLoader : C:\Program Files\Java\jre1.8.0_281\bin\net.dll
Loader sun.misc.Launcher$AppClassLoader : C:\Program Files\Java\jre1.8.0_281\bin\nio.dll
Loader sun.misc.Launcher$AppClassLoader : C:\Users\my-user\AppData\Local\Temp\temp_ortools\jniortools.dll
Loader org.springframework.boot.loader.LaunchedURLClassLoader : C:\Program Files\Java\jre1.8.0_281\bin\zip.dll
Loader org.springframework.boot.loader.LaunchedURLClassLoader : C:\Program Files\Java\jre1.8.0_281\bin\management.dll
Loader org.springframework.boot.loader.LaunchedURLClassLoader : C:\Program Files\Java\jre1.8.0_281\bin\net.dll
Loader org.springframework.boot.loader.LaunchedURLClassLoader : C:\Program Files\Java\jre1.8.0_281\bin\nio.dll
Loader org.springframework.boot.loader.LaunchedURLClassLoader : C:\Users\my-user\AppData\Local\Temp\temp_ortools\jniortools.dll
- Netbeans 'run' command on the web module
Loader sun.misc.Launcher$AppClassLoader : C:\Program Files\Java\jdk1.8.0_281\jre\bin\zip.dll
Loader sun.misc.Launcher$AppClassLoader : C:\Program Files\Java\jdk1.8.0_281\jre\bin\management.dll
Loader sun.misc.Launcher$AppClassLoader : C:\Program Files\Java\jdk1.8.0_281\jre\bin\net.dll
Loader sun.misc.Launcher$AppClassLoader : C:\Program Files\Java\jdk1.8.0_281\jre\bin\nio.dll
Loader sun.misc.Launcher$AppClassLoader : C:\Users\my-user\AppData\Local\Temp\temp_ortools\jniortools.dll
Loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader : C:\Program Files\Java\jdk1.8.0_281\jre\bin\zip.dll
Loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader : C:\Program Files\Java\jdk1.8.0_281\jre\bin\management.dll
Loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader : C:\Program Files\Java\jdk1.8.0_281\jre\bin\net.dll
Loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader : C:\Program Files\Java\jdk1.8.0_281\jre\bin\nio.dll
Loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader : C:\Users\my-user\AppData\Local\Temp\temp_ortools\jniortools.dll
Solution
By comparing the last 2 outputs from the listAllLoadedNativeLibrariesFromJVM call there was a slight difference between the fat jar and netbeans loaded classes, specifically:
- Netbeans run :
Loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader : C:\Users\my-user\AppData\Local\Temp\temp_ortools\jniortools.dll
- fat jar run :
Loader org.springframework.boot.loader.LaunchedURLClassLoader : C:\Users\my-user\AppData\Local\Temp\temp_ortools\jniortools.dll
Based on these logs we have identified that the following dependency was the cause of the issue:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
This dependency allows the Spring application to be restarted faster in the case of code changes. By removing this dependency the application runs on Netbeans without any problems.
Any insights on how to make Spring Boot Devtools work with statically loaded .dll libraries are welcome.
Answered By - epol
Answer Checked By - Willingham (JavaFixing Volunteer)