Skip to content

[GLUTEN-12401][CORE] Use Path.toRealPath() for JniLibLoader symlink resolution#12402

Merged
jackylee-ch merged 1 commit into
apache:mainfrom
LuciferYang:gluten-jni-loader-real-path
Jul 1, 2026
Merged

[GLUTEN-12401][CORE] Use Path.toRealPath() for JniLibLoader symlink resolution#12402
jackylee-ch merged 1 commit into
apache:mainfrom
LuciferYang:gluten-jni-loader-real-path

Conversation

@LuciferYang

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

JniLibLoader.toRealPath() previously walked symbolic links with a hand-rolled loop that called Files.readSymbolicLink and fed the stored (often relative) target back into Paths.get(...). That resolved relative targets against the process working directory instead of the link's own parent, and the loop had no cycle guard. This PR rewrites the method to delegate to Path#toRealPath so symlink-chain resolution (including ./.. normalisation) and cycle detection (FileSystemLoopException on Linux; FileSystemException carrying "Too many levels of symbolic links" on current openjdk macOS) are handled by the JDK. Visibility is widened from private to package-private so a unit test in the same package can call it directly. The caught type is Exception (not Throwable), wrapping IOException/InvalidPathException/SecurityException/NullPointerException as GlutenException while intentionally letting Error subclasses propagate. A JUnit 4 test covers a plain regular file (canonicalisation via a symlinked parent dir, so the assertion bites on Linux too), a versioned-library symlink chain with relative targets, and a 2-cycle. A @Before probe skips cleanly on filesystems that do not support symbolic-link creation or resolution.

Why are the changes needed?

Closes #12401. Versioned native libraries are typically published as a chain like libfoo.so -> libfoo.so.1 -> libfoo.so.1.2.3, where the stored target of libfoo.so is the relative libfoo.so.1. The old loop's second iteration called Files.isSymbolicLink(Paths.get("libfoo.so.1")), which probes the JVM's working directory instead of the directory holding the link, so resolution silently returned a wrong path that System.load then rejected (or, if the working directory happened to contain a matching name, loaded the wrong library). A symlink cycle would also spin forever inside the loop. Path#toRealPath removes both issues with a single JDK call.

Does this PR introduce any user-facing change?

No. Behaviour change is limited to error cases the old loop would have mishandled (relative-symlink failures and cycles), where callers now get a wrapped GlutenException instead of an incorrect or hung load.

How was this patch tested?

mvn -pl gluten-core -Pspark-3.5 spotless:check passes. New tests JniLibLoaderTest (3 tests) pass against the fix. Each test was also verified against the old buggy implementation and observed to fail with the expected red signal (input-verbatim assertion on the regular-file test, path-not-equal on the chain test, Assert.fail on the cycle test). Existing gluten-core tests are unchanged.

…lPath() for correct relative-symlink resolution and cycle detection
Copilot AI review requested due to automatic review settings June 30, 2026 10:54
@github-actions github-actions Bot added the CORE works for Gluten Core label Jun 30, 2026
@github-actions

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes JniLibLoader.toRealPath() symlink handling in gluten-core by delegating to the JDK’s Path#toRealPath(), ensuring correct resolution of relative symlink targets and proper cycle detection (instead of a hand-rolled loop that could mis-resolve paths or hang).

Changes:

  • Replaced manual symlink-walking logic with Paths.get(libPath).toRealPath() and narrowed exception handling from Throwable to Exception (letting Error propagate).
  • Widened toRealPath visibility to package-private to enable direct unit testing.
  • Added JUnit tests covering canonicalization through a symlinked parent directory, relative-target symlink chains, and symlink cycles (with environment-aware skipping when symlinks aren’t supported).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
gluten-core/src/main/java/org/apache/gluten/jni/JniLibLoader.java Delegates real-path resolution to Path#toRealPath() and updates visibility/exception handling accordingly.
gluten-core/src/test/java/org/apache/gluten/jni/JniLibLoaderTest.java Adds regression tests for relative symlink resolution and cycle detection, with a symlink capability probe.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@LuciferYang

Copy link
Copy Markdown
Contributor Author

cc @jackylee-ch

@jackylee-ch jackylee-ch changed the title [GLUTEN-12401][CORE] Delegate JniLibLoader.toRealPath() to Path.toRealPath() for correct relative-symlink resolution and cycle detection [GLUTEN-12401][CORE] Use Path.toRealPath() for JniLibLoader symlink resolution Jul 1, 2026

@jackylee-ch jackylee-ch left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@jackylee-ch jackylee-ch merged commit 85a7e09 into apache:main Jul 1, 2026
68 of 69 checks passed
@LuciferYang LuciferYang deleted the gluten-jni-loader-real-path branch July 3, 2026 13:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CORE works for Gluten Core

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JniLibLoader.toRealPath() mis-resolves relative symlinks and loops forever on cycles

3 participants