|
| 1 | +/* |
| 2 | + * Copyright (c) 2023-2025, The Electrostatic-Sandbox Distributed Simulation Framework, jSnapLoader |
| 3 | + * All rights reserved. |
| 4 | + * |
| 5 | + * Redistribution and use in source and binary forms, with or without |
| 6 | + * modification, are permitted provided that the following conditions are |
| 7 | + * met: |
| 8 | + * |
| 9 | + * * Redistributions of source code must retain the above copyright |
| 10 | + * notice, this list of conditions and the following disclaimer. |
| 11 | + * |
| 12 | + * * Redistributions in binary form must reproduce the above copyright |
| 13 | + * notice, this list of conditions and the following disclaimer in the |
| 14 | + * documentation and/or other materials provided with the distribution. |
| 15 | + * |
| 16 | + * * Neither the name of 'AvrSandbox' nor the names of its contributors |
| 17 | + * may be used to endorse or promote products derived from this software |
| 18 | + * without specific prior written permission. |
| 19 | + * |
| 20 | + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 21 | + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
| 22 | + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 23 | + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| 24 | + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| 25 | + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| 26 | + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| 27 | + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
| 28 | + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
| 29 | + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| 30 | + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 31 | + */ |
| 32 | + |
| 33 | +package electrostatic4j.snaploader.examples.api; |
| 34 | + |
| 35 | +import com.github.stephengold.joltjni.Jolt; |
| 36 | +import java.util.List; |
| 37 | +import java.util.logging.Level; |
| 38 | +import electrostatic4j.snaploader.ConcurrentNativeBinaryLoader; |
| 39 | +import electrostatic4j.snaploader.LibraryInfo; |
| 40 | +import electrostatic4j.snaploader.LoadingCriterion; |
| 41 | +import electrostatic4j.snaploader.NativeBinaryLoader; |
| 42 | +import electrostatic4j.snaploader.NativeBinaryLoadingListener; |
| 43 | +import electrostatic4j.snaploader.filesystem.FileLocalizingListener; |
| 44 | +import electrostatic4j.snaploader.filesystem.FileLocator; |
| 45 | +import electrostatic4j.snaploader.platform.NativeDynamicLibrary; |
| 46 | +import electrostatic4j.snaploader.throwable.FilesystemResourceInitializationException; |
| 47 | +import electrostatic4j.snaploader.throwable.LoadingRetryExhaustionException; |
| 48 | +import electrostatic4j.snaploader.throwable.UnSupportedSystemError; |
| 49 | +import electrostatic4j.snaploader.util.CallingStackMetaData; |
| 50 | +import electrostatic4j.snaploader.util.SnapLoaderLogger; |
| 51 | + |
| 52 | +/** |
| 53 | + * A suggested cross-platform algorithm to use fallback mechanisms for Jolt-Jni. |
| 54 | + * <p> |
| 55 | + * <ul> |
| 56 | + * <li> This is an efficient implementation that follows best |
| 57 | + * practices for software engineering, and the computational theory. </li> |
| 58 | + * <li> This is a theoretical implementation technique. </li> |
| 59 | + * <li> Feel free to copy-paste to your projects and modify as required. </li> |
| 60 | + * <li> Open issues as required. </li> |
| 61 | + * </ul> |
| 62 | + * </p> |
| 63 | + * |
| 64 | + * @author pavl_g. |
| 65 | + */ |
| 66 | +public class NativeDllLoader implements NativeBinaryLoadingListener, FileLocalizingListener { |
| 67 | + |
| 68 | + /** |
| 69 | + * Instance reference for the associated binary loader. |
| 70 | + */ |
| 71 | + protected final NativeBinaryLoader loader; |
| 72 | + /** |
| 73 | + * Instance reference for libraries with base features |
| 74 | + * for the {@link NativeDllLoader#loadBaseLibraries(LoadingCriterion)} |
| 75 | + * as a fallback mechanism. |
| 76 | + */ |
| 77 | + protected NativeDynamicLibrary[] baseLibs; |
| 78 | + /** |
| 79 | + * Instance reference for libraries with CPU-based specific features |
| 80 | + * for the {@link NativeDllLoader#loadCpuEnhancedLibs(LoadingCriterion)} routine. |
| 81 | + */ |
| 82 | + protected NativeDynamicLibrary[] cpuEnhancedLibs; |
| 83 | + |
| 84 | + /** |
| 85 | + * Instantiates a DLL Loader wrapper setting its logging, retry criteria, |
| 86 | + * and setting up its loading listeners. |
| 87 | + * |
| 88 | + * @param baseLibs the base feature libraries group (not null). |
| 89 | + * @param cpuEnhancedLibs the cpu enhanced libraries group (not null). |
| 90 | + * @param info the cross-platform library info metadata (not null). |
| 91 | + * @param enableLogging true to enable snap-loader logger and failure logs (default: false). |
| 92 | + * @param enableRetryCriterion true to enable retrying when loading from a jar file (default: false). |
| 93 | + * @throws IllegalArgumentException if the caller stack has passed undefined library groups and/or |
| 94 | + * undefined library information reference. |
| 95 | + */ |
| 96 | + public NativeDllLoader(NativeDynamicLibrary[] baseLibs, NativeDynamicLibrary[] cpuEnhancedLibs, |
| 97 | + LibraryInfo info, boolean enableLogging, boolean enableRetryCriterion) { |
| 98 | + this(info, enableLogging, enableRetryCriterion); |
| 99 | + |
| 100 | + if (baseLibs == null || cpuEnhancedLibs == null) { |
| 101 | + throw new IllegalArgumentException("Libraries groups cannot be null!"); |
| 102 | + } |
| 103 | + |
| 104 | + this.baseLibs = baseLibs; |
| 105 | + this.cpuEnhancedLibs = cpuEnhancedLibs; |
| 106 | + } |
| 107 | + |
| 108 | + /** |
| 109 | + * Instantiates a DLL Loader wrapper setting its logging, retry criteria, |
| 110 | + * and setting up its loading listeners. |
| 111 | + * |
| 112 | + * @param enableLogging enables the API level logger (default: false). |
| 113 | + * @param enableRetryCriterion enables retrying with clean extraction (default: false). |
| 114 | + * when loading failure for {@link LoadingCriterion#INCREMENTAL_LOADING} |
| 115 | + * routine. |
| 116 | + * @throws IllegalArgumentException if the caller stack has passed an undefined library information |
| 117 | + * reference. |
| 118 | + */ |
| 119 | + public NativeDllLoader(LibraryInfo info, boolean enableLogging, boolean enableRetryCriterion) { |
| 120 | + if (info == null) { |
| 121 | + throw new IllegalArgumentException("Cannot proceed with no library information!"); |
| 122 | + } |
| 123 | + loader = new ConcurrentNativeBinaryLoader(List.of(), info); |
| 124 | + loader.setLoggingEnabled(enableLogging); |
| 125 | + loader.setRetryWithCleanExtraction(enableRetryCriterion); |
| 126 | + loader.setNativeBinaryLoadingListener(this); |
| 127 | + loader.setLibraryLocalizingListener(this); |
| 128 | + } |
| 129 | + |
| 130 | + /** |
| 131 | + * Loads the base defined libraries providing an anti-failure routine |
| 132 | + * for loading from archive commands. This routine is thread-safe with other object routines. |
| 133 | + * |
| 134 | + * <p> |
| 135 | + * Possible execution stack: |
| 136 | + * Legend: |
| 137 | + * <code> |
| 138 | + * ">>" represents the initial state of the automata |
| 139 | + * "*" represents the terminal state of the automata; after |
| 140 | + * which the stack is owned by another machine. |
| 141 | + * "**" represents an imminent failure signal. |
| 142 | + * "->" represents a machine transitional delta. |
| 143 | + * </code> |
| 144 | + * <ul> |
| 145 | + * <li> Case 1: |
| 146 | + * <code> >> loadBaseLibraries(LoadingCriterion.INCREMENTAL_LOADING) -> |
| 147 | + * if (failure-cause == UnSupportedSystemError) -> exit() -> **. |
| 148 | + * </code> |
| 149 | + * </li> |
| 150 | + * <li> Case 2: |
| 151 | + * <code> >> loadBaseLibraries(LoadingCriterion.INCREMENTAL_LOADING) -> |
| 152 | + * if (failure-cause == LoadingRetryExhaustionException) -> onLoadingFailure() |
| 153 | + * -> loadBaseLibraries(LoadingCriterion.SYSTEM_LOAD) -> |
| 154 | + * if (failure-cause == UnsatisfiedLinkError) -> exit() -> **. |
| 155 | + * </code> |
| 156 | + * </li> |
| 157 | + * </ul> |
| 158 | + * </p> |
| 159 | + * |
| 160 | + * @param criterion the loading criterion, it's recommended to start with |
| 161 | + * {@link LoadingCriterion#INCREMENTAL_LOADING}; moreover |
| 162 | + * start with the {@link NativeDllLoader#loadCpuEnhancedLibs(LoadingCriterion)} (not null). |
| 163 | + * @throws Exception if I/O event or thread signal interrupt occurs. |
| 164 | + */ |
| 165 | + public synchronized void loadBaseLibraries(LoadingCriterion criterion) throws Exception { |
| 166 | + if (criterion == null) { |
| 167 | + throw new IllegalArgumentException("Cannot proceed with null loading criterion!"); |
| 168 | + } |
| 169 | + try { |
| 170 | + loader.registerNativeLibraries(baseLibs) |
| 171 | + .initPlatformLibrary() |
| 172 | + .loadLibrary(criterion); |
| 173 | + } catch (UnSupportedSystemError e) { |
| 174 | + signalImminentFailure( |
| 175 | + new CallingStackMetaData(Thread.currentThread().getStackTrace()[1], criterion, e)); |
| 176 | + } catch (LoadingRetryExhaustionException e) { |
| 177 | + // re-route retry failure to the same anti-failure routine |
| 178 | + this.onLoadingFailure(loader, |
| 179 | + new CallingStackMetaData(Thread.currentThread().getStackTrace()[1], criterion, e)); |
| 180 | + } |
| 181 | + } |
| 182 | + |
| 183 | + /** |
| 184 | + * Loads the registered CPU-enhanced libraries. Providing an anti-failure routine |
| 185 | + * for loading from archive commands. This is thread-safe with other routines. |
| 186 | + * |
| 187 | + * <p> |
| 188 | + * Possible execution stack: |
| 189 | + * <ul> |
| 190 | + * <li> Case 1 (Notice UnSupportedSystemError is thrown from the initPlatformLibrary |
| 191 | + * during which system selection is performed): |
| 192 | + * <code> >> exec: loadCpuEnhancedLibs(LoadingCriterion.INCREMENTAL_LOADING) -> |
| 193 | + * if (failure-cause == UnSupportedSystemError) -> onLoadingFailure -> |
| 194 | + * exec: loadBaseLibraries(LoadingCriterion.INCREMENTAL_LOADING) -> *. |
| 195 | + * </code> |
| 196 | + * </li> |
| 197 | + * <li> Case 2: |
| 198 | + * <code> >> exec: loadCpuEnhancedLibs(LoadingCriterion.INCREMENTAL_LOADING) -> |
| 199 | + * if (failure-cause == LoadingRetryExhaustionException) -> loadCpuEnhancedLibs(LoadingCriterion.SYSTEM_LOAD) |
| 200 | + * if (failure-cause == UnsatisfiedLinkError) -> onLoadingFailure -> exec: loadBaseLibraries(LoadingCriterion.INCREMENTAL_LOADING) |
| 201 | + * -> *. |
| 202 | + * </code> |
| 203 | + * </li> |
| 204 | + * <li> Case 3: |
| 205 | + * <code> |
| 206 | + * >> ...Some states... -> Any other failure cause -> exit() -> *. |
| 207 | + * </code> |
| 208 | + * </li> |
| 209 | + * </ul> |
| 210 | + * </p> |
| 211 | + * |
| 212 | + * @param criterion the type of loading; it's recommended to start with {@link LoadingCriterion#INCREMENTAL_LOADING}. |
| 213 | + * @throws Exception if I/O event or thread signal interrupt occurs. |
| 214 | + */ |
| 215 | + public synchronized void loadCpuEnhancedLibs(LoadingCriterion criterion) throws Exception { |
| 216 | + if (criterion == null) { |
| 217 | + throw new IllegalArgumentException("Cannot proceed with null loading criterion!"); |
| 218 | + } |
| 219 | + try { |
| 220 | + loader.registerNativeLibraries(cpuEnhancedLibs) |
| 221 | + .initPlatformLibrary() |
| 222 | + .loadLibrary(criterion); |
| 223 | + } catch (UnSupportedSystemError e) { |
| 224 | + // re-route system not found and retry failure to the same anti-failure routine |
| 225 | + this.onLoadingFailure(loader, |
| 226 | + new CallingStackMetaData(Thread.currentThread().getStackTrace()[1], criterion, e)); |
| 227 | + } catch (LoadingRetryExhaustionException e) { |
| 228 | + // retry with SYSTEM_LOAD |
| 229 | + // notice that LoadingRetryExhaustionException will never |
| 230 | + // happen with LoadingCriterion.SYSTEM_LOAD |
| 231 | + // the LoadingRetryExhaustionException is thrown as a result |
| 232 | + // of greater than 2 times throwing "UnSatisfiedLinkError" on the |
| 233 | + // INCREMENTAL Loading Stack (i.e., extracting stack). |
| 234 | + loadCpuEnhancedLibs(LoadingCriterion.SYSTEM_LOAD); |
| 235 | + } |
| 236 | + } |
| 237 | + |
| 238 | + @Override |
| 239 | + public void onLoadingSuccess(NativeBinaryLoader nativeBinaryLoader, CallingStackMetaData callingStackMetaData) { |
| 240 | + // initialize Jolt-Jni and physics update system |
| 241 | + // handling lifecycle to the internal Jolt-Physics Native |
| 242 | + |
| 243 | + // copied from Jolt-Jni Example |
| 244 | + String configuration = Jolt.getConfigurationString(); |
| 245 | + /* |
| 246 | + * Depending which native library was loaded, the configuration string |
| 247 | + * should be one of the following: |
| 248 | + * |
| 249 | + * On LINUX_X86_64 platforms, either |
| 250 | + * Single precision x86 64-bit with instructions: SSE2 SSE4.1 SSE4.2 AVX AVX2 F16C LZCNT TZCNT FMADD (Debug Renderer) (16-bit ObjectLayer) (Assertions) (ObjectStream) (Debug) (C++ RTTI) (C++ Exceptions) |
| 251 | + * or |
| 252 | + * Single precision x86 64-bit with instructions: SSE2 (Debug Renderer) (16-bit ObjectLayer) (Assertions) (ObjectStream) (Debug) (C++ RTTI) (C++ Exceptions) |
| 253 | + * |
| 254 | + * On WIN_X86_64 platforms, either |
| 255 | + * Single precision x86 64-bit with instructions: SSE2 SSE4.1 SSE4.2 AVX AVX2 F16C LZCNT TZCNT (FP Exceptions) (Debug Renderer) (16-bit ObjectLayer) (Assertions) (ObjectStream) (Debug) (C++ RTTI) (C++ Exceptions) |
| 256 | + * or |
| 257 | + * Single precision x86 64-bit with instructions: SSE2 (FP Exceptions) (Debug Renderer) (16-bit ObjectLayer) (Assertions) (ObjectStream) (Debug) (C++ RTTI) (C++ Exceptions) |
| 258 | + */ |
| 259 | + System.out.println(configuration); |
| 260 | + } |
| 261 | + |
| 262 | + @Override |
| 263 | + public synchronized void onLoadingFailure(NativeBinaryLoader nativeBinaryLoader, CallingStackMetaData callingStackMetaData) { |
| 264 | + // validate input! |
| 265 | + if (callingStackMetaData == null || callingStackMetaData.getCallingStack() == null || |
| 266 | + callingStackMetaData.getErrorCause() == null || callingStackMetaData.getLoadingCriterion() == null) { |
| 267 | + throw new IllegalArgumentException("Failure stack metadata structure cannot be null!"); |
| 268 | + } |
| 269 | + |
| 270 | + final String callingMethod = callingStackMetaData.getCallingStack().getMethodName(); |
| 271 | + // log calling stack! |
| 272 | + SnapLoaderLogger.log(Level.INFO, callingStackMetaData.getCallingStack().getClassName(), |
| 273 | + callingStackMetaData.getCallingStack().getMethodName(), |
| 274 | + "Failure Stack", callingStackMetaData.getErrorCause()); |
| 275 | + |
| 276 | + try { |
| 277 | + if (callingStackMetaData.getErrorCause() instanceof LoadingRetryExhaustionException) { |
| 278 | + if (callingMethod.contains("loadCpuEnhancedLibs")) { |
| 279 | + // try cpu enhanced libs from system directory |
| 280 | + loadCpuEnhancedLibs(LoadingCriterion.SYSTEM_LOAD); |
| 281 | + } else if (callingMethod.contains("loadBaseLibraries")) { |
| 282 | + // try loading base libraries from system directories if loading is exhausted! |
| 283 | + loadBaseLibraries(LoadingCriterion.SYSTEM_LOAD); |
| 284 | + } |
| 285 | + } else if (callingStackMetaData.getErrorCause() instanceof UnsatisfiedLinkError) { |
| 286 | + if (callingMethod.contains("loadCpuEnhancedLibs")) { |
| 287 | + // no retry criteria? |
| 288 | + // or loading from system directory? |
| 289 | + // Exit the loadCpuEnhancedLibs stack frames! |
| 290 | + loadBaseLibraries(LoadingCriterion.INCREMENTAL_LOADING); |
| 291 | + } else if (callingMethod.contains("loadBaseLibraries")) { |
| 292 | + signalImminentFailure(callingStackMetaData); |
| 293 | + } |
| 294 | + } else if (callingStackMetaData.getErrorCause() instanceof FilesystemResourceInitializationException) { |
| 295 | + if (callingMethod.contains("onFileLocalizationFailure")) { |
| 296 | + loadCpuEnhancedLibs(LoadingCriterion.SYSTEM_LOAD); |
| 297 | + } |
| 298 | + } |
| 299 | + } catch (Exception e) { |
| 300 | + signalImminentFailure(callingStackMetaData); |
| 301 | + } |
| 302 | + } |
| 303 | + |
| 304 | + @Override |
| 305 | + public void onRetryCriterionExecution(NativeBinaryLoader nativeBinaryLoader, CallingStackMetaData callingStackMetaData) { |
| 306 | + } |
| 307 | + |
| 308 | + @Override |
| 309 | + public void onFileLocalizationSuccess(FileLocator locator) { |
| 310 | + } |
| 311 | + |
| 312 | + @Override |
| 313 | + public void onFileLocalizationFailure(FileLocator locator, Throwable throwable) { |
| 314 | + } |
| 315 | + |
| 316 | + /** |
| 317 | + * Sets base feature libraries group strong reference. This command |
| 318 | + * will only take effect before dispatching the loading |
| 319 | + * routines. Warning: Non-thread safe. |
| 320 | + * |
| 321 | + * @param baseLibs the new base libraries groups reference. |
| 322 | + */ |
| 323 | + public void setBaseLibs(NativeDynamicLibrary[] baseLibs) { |
| 324 | + this.baseLibs = baseLibs; |
| 325 | + } |
| 326 | + |
| 327 | + /** |
| 328 | + * Sets the cpu-specific enhanced libraries group strong reference. This command |
| 329 | + * will only take effect before dispatching the loading routines. Warning: Non-thread safe. |
| 330 | + * |
| 331 | + * @param cpuEnhancedLibs the new enhanced libraries groups reference. |
| 332 | + */ |
| 333 | + public void setCpuEnhancedLibs(NativeDynamicLibrary[] cpuEnhancedLibs) { |
| 334 | + this.cpuEnhancedLibs = cpuEnhancedLibs; |
| 335 | + } |
| 336 | + |
| 337 | + /** |
| 338 | + * Signals an imminent failure disposing the application process with an |
| 339 | + * error code formed from the hashcode of the causing throwable on the |
| 340 | + * calling stack. |
| 341 | + * |
| 342 | + * @param callingStackMetaData a calling stack metadata structure strong reference. |
| 343 | + */ |
| 344 | + protected void signalImminentFailure(CallingStackMetaData callingStackMetaData) { |
| 345 | + SnapLoaderLogger.log(Level.SEVERE, callingStackMetaData.getCallingStack().getClassName(), |
| 346 | + callingStackMetaData.getCallingStack().getMethodName(), |
| 347 | + "Imminent Failure", callingStackMetaData.getErrorCause()); |
| 348 | + // signal an imminent failure and crash the application |
| 349 | + Runtime.getRuntime().exit(-callingStackMetaData.getErrorCause().hashCode()); |
| 350 | + } |
| 351 | +} |
0 commit comments