8368633: (fs) Path.toRealPath(NOFOLLOW_LINKS) very slow on macOS

Reviewed-by: alanb
This commit is contained in:
Brian Burkhalter 2025-10-14 17:48:37 +00:00
parent 65b8fe62b4
commit 09e87971e8
3 changed files with 177 additions and 25 deletions

View File

@ -948,28 +948,51 @@ class UnixPath implements Path {
// Obtain the stream of entries in the directory corresponding
// to the path constructed thus far, and extract the entry whose
// key is equal to the key of the current element
// internal path bytes equal the internal path bytes of the current
// element, or whose key is equal to the key of the current element
boolean found = false;
DirectoryStream.Filter<Path> filter = (p) -> { return true; };
// compare path bytes until a match is found or no more entries
try (DirectoryStream<Path> entries = new UnixDirectoryStream(path, dp, filter)) {
boolean found = false;
for (Path entry : entries) {
UnixPath p = path.resolve(entry.getFileName());
UnixFileAttributes attributes = null;
try {
attributes = UnixFileAttributes.get(p, false);
UnixFileKey key = attributes.fileKey();
if (key.equals(elementKey)) {
path = path.resolve(entry);
found = true;
break;
Path name = entry.getFileName();
if (name.compareTo(element) == 0) {
found = true;
path = path.resolve(entry);
break;
}
}
}
// if no path match found, compare file keys
if (!found) {
try {
dp = opendir(path);
} catch (UnixException x) {
x.rethrowAsIOException(path);
}
try (DirectoryStream<Path> entries = new UnixDirectoryStream(path, dp, filter)) {
for (Path entry : entries) {
Path name = entry.getFileName();
UnixPath p = path.resolve(name);
UnixFileAttributes attributes = null;
try {
attributes = UnixFileAttributes.get(p, false);
UnixFileKey key = attributes.fileKey();
if (key.equals(elementKey)) {
found = true;
path = path.resolve(entry);
break;
}
} catch (UnixException ignore) {
continue;
}
} catch (UnixException ignore) {
continue;
}
}
// Fallback which should in theory never happen
if (!found) {
// Fallback which should in theory never happen
path = path.resolve(element);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -151,18 +151,18 @@ public class ToRealPath {
public void noCollapseDots1() throws IOException {
Path subPath = DIR.resolve(Path.of("dir", "subdir"));
Path sub = Files.createDirectories(subPath);
System.out.println("sub: " + sub);
System.err.println("sub: " + sub);
Files.createSymbolicLink(LINK, sub);
System.out.println("LINK: " + LINK + " -> " + sub);
System.err.println("LINK: " + LINK + " -> " + sub);
Path p = Path.of("..", "..", FILE.getFileName().toString());
System.out.println("p: " + p);
System.err.println("p: " + p);
Path path = LINK.resolve(p);
System.out.println("path: " + path);
System.err.println("path: " + path);
if (Platform.isWindows() && Files.notExists(path)) {
Files.createFile(path);
extraDeletions.add(path);
}
System.out.println("no follow: " + path.toRealPath(NOFOLLOW_LINKS));
System.err.println("no follow: " + path.toRealPath(NOFOLLOW_LINKS));
if (Platform.isWindows())
assertTrue(Files.isSameFile(path.toRealPath(NOFOLLOW_LINKS), path));
else
@ -181,23 +181,23 @@ public class ToRealPath {
Path out = Files.createFile(DIR.resolve(Path.of("out.txt")));
Path aaa = DIR.resolve(Path.of("aaa"));
Files.createSymbolicLink(aaa, sub);
System.out.println("aaa: " + aaa + " -> " + sub);
System.err.println("aaa: " + aaa + " -> " + sub);
Path bbb = DIR.resolve(Path.of("bbb"));
Files.createSymbolicLink(bbb, sub);
System.out.println("bbb: " + bbb + " -> " + sub);
System.err.println("bbb: " + bbb + " -> " + sub);
Path p = Path.of("aaa", "..", "..", "bbb", "..", "..", "out.txt");
Path path = DIR.resolve(p);
System.out.println("path: " + path);
System.err.println("path: " + path);
if (Platform.isWindows() && Files.notExists(path)) {
Files.createFile(path);
extraDeletions.add(path);
}
System.out.println("no follow: " + path.toRealPath(NOFOLLOW_LINKS));
System.err.println("no follow: " + path.toRealPath(NOFOLLOW_LINKS));
if (Platform.isWindows())
assertTrue(Files.isSameFile(path.toRealPath(NOFOLLOW_LINKS), path));
else
assertEquals(path.toRealPath(NOFOLLOW_LINKS), path);
System.out.println(path.toRealPath());
System.err.println(path.toRealPath());
Files.delete(sub);
Files.delete(sub.getParent());

View File

@ -0,0 +1,129 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.java.nio.file;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.FileVisitResult;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Random;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
@State(Scope.Benchmark)
public class ToRealPath {
static Random RND = new Random(17_000_126);
static final String NAME = "RealPath";
static final int LEN = NAME.length();
Path root;
Path[] files;
@Setup
public void init() throws IOException {
// root the test files at CWD/NAME
root = Path.of(System.getProperty("user.dir")).resolve(NAME);
// populate files array
StringBuilder sb = new StringBuilder();
files = new Path[100];
for (int i = 0; i < files.length; i++) {
// create directories up to a depth of 9, inclusive
sb.setLength(0);
int depth = RND.nextInt(10);
for (int j = 0; j < depth; j++) {
sb.append("dir");
sb.append(j);
sb.append(File.separatorChar);
}
Path dir = root.resolve(sb.toString());
Files.createDirectories(dir);
// set the file prefix with random case conversion
String prefix;
if (RND.nextBoolean()) {
sb.setLength(0);
for (int k = 0; k < LEN; k++) {
char c = NAME.charAt(k);
sb.append(RND.nextBoolean()
? Character.toLowerCase(c)
: Character.toUpperCase(c));
}
prefix = sb.append(i).toString();
} else {
prefix = NAME + i;
}
// create the file
Path tmpFile = Files.createTempFile(dir, prefix, ".tmp");
// set the array path to a version with a lower case name
String tmpName = tmpFile.getFileName().toString().toLowerCase();
files[i] = tmpFile.getParent().resolve(tmpName);
}
}
@TearDown
public void cleanup() throws IOException {
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file,
BasicFileAttributes attrs)
throws IOException
{
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir,
IOException e)
throws IOException
{
if (e == null) {
Files.delete(dir);
return FileVisitResult.CONTINUE;
} else {
// directory iteration failed
throw e;
}
}
});
}
@Benchmark
public Path noFollowLinks() throws IOException {
int i = RND.nextInt(0, files.length);
return files[i].toRealPath(LinkOption.NOFOLLOW_LINKS);
}
}