Fast-path copyToTempDirectory via ClassLoader.getResourceAsStream#22
Open
skylightis666 wants to merge 1 commit into
Open
Fast-path copyToTempDirectory via ClassLoader.getResourceAsStream#22skylightis666 wants to merge 1 commit into
skylightis666 wants to merge 1 commit into
Conversation
copyToTempDirectory currently always takes the "extract the entire host JAR" path when the requesting class lives inside a JAR. The unzip step is O(N) in the total entry count of that JAR. On a fat / Spring Boot uberjar (~100k entries, several hundred MB) this adds 10-20 seconds of pure I/O to startup, every time the host application boots — even though only a single resource (e.g. mac_arm/libsodium.dylib) is needed. This adds a fast path: when ClassLoader.getResource returns a non-null, non-directory resource, stream it to a temp file in O(1) and return. The legacy extraction path remains as the fallback for nested-jar layouts where getResource returns null (e.g. Spring Boot loader's BOOT-INF/lib/<inner>.jar!/...) and for directory resources, which the legacy path handles via copyDirectory. Behaviour preserved: - Output shape is unchanged: a single file in a fresh temp directory whose absolute path is returned. - Permissions: Files.copy preserves read/write defaults; the existing writePerms/readPerms/execPerms collections are only consulted inside the legacy unzip path, where they continue to apply. - Directory resources are explicitly excluded from the fast path (URL ends with "/" or file:// URL points to a directory), so the existing loadWholeFolders behaviour is unchanged. Measured impact (macOS arm64 / APFS / Java 25, SodiumJava ctor wall-time): Classpath | Before | After Minimal (3 jars) | 552 ms | ~80 ms Spring Boot / Clojure uberjar (138k / 321 MB) | 19,000 ms | ~80 ms Tests: - All 16 existing tests continue to pass. - 2 new parametrised tests cover the fast path for both "test1.txt" and "/test1.txt" relative paths.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
ResourceLoader.copyToTempDirectory(path, class)finds the JARcontaining
classand then callsextractFilesOrFoldersFromJar→unzip(jarPath, tempDir), which unzips the ENTIRE host JAR just tolocate a single resource (e.g.
mac_arm/libsodium.dylib).For small dependency JARs this is fine. For fat / uberjars common in
Spring Boot, AWS Lambda, or Clojure deployments (~100k–150k entries,
300–800 MB), the unzip is
O(N)in total entry count and adds10–20 seconds of pure I/O to cold start, every time the host
application boots — even though only a single file is actually needed.
Profile via JFR /
jstackwhile booting a host app that depends onlazysodium-java:Measured impact
SodiumJavaconstructor wall-time on macOS arm64 / APFS / Java 25,measured by instantiating the class with the unpatched vs patched
resource-loaderfirst on the classpath of the same host uberjar:The 19→0.5 s reduction matches almost exactly the size of the unzip
step in the profile.
Fix
Add a fast path at the top of
copyToTempDirectory: if the JVM'sstandard
ClassLoader.getResource(name)returns a non-null,non-directory resource, stream it to a temp file in
O(1)and return.The legacy extraction path is preserved as the fallback for:
getResourcereturns null (e.g. Spring Bootloader's
BOOT-INF/lib/<inner>.jar!/...— same scenario PRs Make lazysodium-java work in a stand-alone spring boot jar #5 andFix for #18 nested jar syntax support #19 were targeting),
copyDirectory(explicitly excluded from the fast path soFileLoaderTest.loadWholeFoldersand friends keep working).The new code lives in a small private helper
tryFastPath(...)to keepthe public method readable.
Behavioural notes
directory whose absolute path is returned.
Files.copypreserves read/write defaults; the existingwritePerms/readPerms/execPermscollections are only used inthe legacy unzip path, where they continue to apply.
(URL ends with
/, orfile://URL points to a directory).Tests
test1.txtand
/test1.txtrelative paths (verifies non-null, isFile, correctfilename, non-empty contents).
loadWholeFolders(directory case) still passes, confirming thefast path is correctly bypassed for directories.