Skip to content

Commit 37ab5af

Browse files
author
pavl-x86-machine
committed
NativeDllLoader: a techdemo API showcasing anti-failure mechanisms
1 parent a13f2ee commit 37ab5af

File tree

2 files changed

+382
-0
lines changed

2 files changed

+382
-0
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package electrostatic4j.snaploader.examples;
2+
3+
import electrostatic4j.snaploader.LibraryInfo;
4+
import electrostatic4j.snaploader.LoadingCriterion;
5+
import electrostatic4j.snaploader.examples.api.NativeDllLoader;
6+
import electrostatic4j.snaploader.filesystem.DirectoryPath;
7+
import electrostatic4j.snaploader.platform.NativeDynamicLibrary;
8+
import electrostatic4j.snaploader.platform.util.DefaultDynamicLibraries;
9+
import electrostatic4j.snaploader.platform.util.PlatformPredicate;
10+
11+
public final class TestNativeDllLoader {
12+
public static void main(String[] args) throws Exception {
13+
final NativeDynamicLibrary[] baseLibs = new NativeDynamicLibrary[] {
14+
DefaultDynamicLibraries.ANDROID_ALL,
15+
new NativeDynamicLibrary("linux/x86-64/com/github/stephengoldd", PlatformPredicate.LINUX_X86_64),
16+
new NativeDynamicLibrary("windows/x86-64/com/github/stephengoldd", PlatformPredicate.WIN_X86_64),
17+
};
18+
19+
final NativeDynamicLibrary[] cpuEnhancedLibs = new NativeDynamicLibrary[]{
20+
DefaultDynamicLibraries.ANDROID_ALL,
21+
new NativeDynamicLibrary("linux/x86-64-fma/com/github/stephengold", new PlatformPredicate(PlatformPredicate.LINUX_X86_64,
22+
"avx", "avx2", "bmi1", "f16c", "fma", "sse4_1", "sse4_2")),
23+
new NativeDynamicLibrary("windows/x86-64-avx2/com/github/stephengold", new PlatformPredicate(PlatformPredicate.WIN_X86_64,
24+
"avx", "avx2", "sse4_1", "sse4_2")),
25+
};
26+
final LibraryInfo info = new LibraryInfo(new DirectoryPath("linux/x86-64/com/github/stephengold"),
27+
"joltjnid", DirectoryPath.USER_DIR);
28+
final NativeDllLoader nativeDllLoader = new NativeDllLoader(baseLibs, cpuEnhancedLibs, info, true, true);
29+
nativeDllLoader.loadCpuEnhancedLibs(LoadingCriterion.INCREMENTAL_LOADING);
30+
}
31+
}
Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
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

Comments
 (0)