This is an automated email from the ASF dual-hosted git repository.
cstamas pushed a commit to branch maven-resolver-1.9.x
in repository https://gitbox.apache.org/repos/asf/maven-resolver.git
The following commit(s) were added to refs/heads/maven-resolver-1.9.x by this
push:
new 82766b0e2 [1.9.x] TrackingFileManager changes (#1695)
82766b0e2 is described below
commit 82766b0e211f8bfa0ca346bf7352d0de7a7d6706
Author: Tamas Cservenak <[email protected]>
AuthorDate: Fri Nov 28 15:45:34 2025 +0100
[1.9.x] TrackingFileManager changes (#1695)
Backport from master. Needs Maven changes as well. Aligned with master
fully.
Master PR: https://github.com/apache/maven-resolver/pull/1692
---
.../internal/impl/DefaultTrackingFileManager.java | 98 ++++++++++++----------
.../internal/impl/DefaultUpdateCheckManager.java | 2 +-
.../aether/internal/impl/TrackingFileManager.java | 7 ++
.../impl/DefaultTrackingFileManagerTest.java | 13 +++
4 files changed, 77 insertions(+), 43 deletions(-)
diff --git
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultTrackingFileManager.java
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultTrackingFileManager.java
index 8f41f7d2f..7076f38f7 100644
---
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultTrackingFileManager.java
+++
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultTrackingFileManager.java
@@ -21,17 +21,19 @@ package org.eclipse.aether.internal.impl;
import javax.inject.Named;
import javax.inject.Singleton;
-import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
-import java.io.FileInputStream;
import java.io.IOException;
-import java.io.RandomAccessFile;
import java.io.UncheckedIOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
import java.util.Map;
import java.util.Properties;
@@ -45,6 +47,9 @@ import org.slf4j.LoggerFactory;
* to back off two parallel implementations that coexist in Maven (this class
and {@code maven-compat} one), as in
* certain cases the two implementations may collide on properties files. This
locking must remain in place for as long
* as {@code maven-compat} code exists.
+ *
+ * <em>IMPORTANT:</em> This class is kept fully in sync with the master branch
one (w/ simple change to convert File
+ * to Path instances).
*/
@Singleton
@Named
@@ -53,15 +58,18 @@ public final class DefaultTrackingFileManager implements
TrackingFileManager {
@Override
public Properties read(File file) {
- if (Files.isReadable(file.toPath())) {
- synchronized (getMutex(file)) {
- try (FileInputStream stream = new FileInputStream(file);
- FileLock unused = fileLock(stream.getChannel(),
Math.max(1, file.length()), true)) {
+ Path path = file.toPath();
+ if (Files.isReadable(path)) {
+ synchronized (mutex(path)) {
+ try (FileChannel fileChannel = FileChannel.open(path,
StandardOpenOption.READ);
+ FileLock unused = fileLock(fileChannel, true)) {
Properties props = new Properties();
- props.load(stream);
+ props.load(Channels.newInputStream(fileChannel));
return props;
+ } catch (NoSuchFileException e) {
+ LOGGER.debug("No such file to read {}: {}", path,
e.getMessage());
} catch (IOException e) {
- LOGGER.warn("Failed to read tracking file '{}'", file, e);
+ LOGGER.warn("Failed to read tracking file '{}'", path, e);
throw new UncheckedIOException(e);
}
}
@@ -71,22 +79,20 @@ public final class DefaultTrackingFileManager implements
TrackingFileManager {
@Override
public Properties update(File file, Map<String, String> updates) {
- Properties props = new Properties();
-
+ Path path = file.toPath();
try {
- Files.createDirectories(file.getParentFile().toPath());
+ Files.createDirectories(path.getParent());
} catch (IOException e) {
- LOGGER.warn("Failed to create tracking file parent '{}'", file, e);
+ LOGGER.warn("Failed to create tracking file parent '{}'", path, e);
throw new UncheckedIOException(e);
}
-
- synchronized (getMutex(file)) {
- try (RandomAccessFile raf = new RandomAccessFile(file, "rw");
- FileLock unused = fileLock(raf.getChannel(), Math.max(1,
raf.length()), false)) {
- if (raf.length() > 0) {
- byte[] buffer = new byte[(int) raf.length()];
- raf.readFully(buffer);
- props.load(new ByteArrayInputStream(buffer));
+ synchronized (mutex(path)) {
+ try (FileChannel fileChannel = FileChannel.open(
+ path, StandardOpenOption.READ,
StandardOpenOption.WRITE, StandardOpenOption.CREATE);
+ FileLock unused = fileLock(fileChannel, false)) {
+ Properties props = new Properties();
+ if (fileChannel.size() > 0) {
+ props.load(Channels.newInputStream(fileChannel));
}
for (Map.Entry<String, String> update : updates.entrySet()) {
@@ -97,46 +103,54 @@ public final class DefaultTrackingFileManager implements
TrackingFileManager {
}
}
- LOGGER.debug("Writing tracking file '{}'", file);
+ LOGGER.debug("Writing tracking file '{}'", path);
ByteArrayOutputStream stream = new ByteArrayOutputStream(1024
* 2);
props.store(
stream,
"NOTE: This is a Maven Resolver internal
implementation file"
+ ", its format can be changed without prior
notice.");
- raf.seek(0L);
- raf.write(stream.toByteArray());
- raf.setLength(raf.getFilePointer());
+ fileChannel.position(0);
+ int written =
fileChannel.write(ByteBuffer.wrap(stream.toByteArray()));
+ fileChannel.truncate(written);
+ return props;
} catch (IOException e) {
- LOGGER.warn("Failed to write tracking file '{}'", file, e);
+ LOGGER.warn("Failed to write tracking file '{}'", path, e);
throw new UncheckedIOException(e);
}
}
+ }
- return props;
+ @Override
+ public boolean delete(File file) {
+ Path path = file.toPath();
+ if (Files.isReadable(path)) {
+ synchronized (mutex(path)) {
+ try (FileChannel fileChannel = FileChannel.open(path,
StandardOpenOption.WRITE);
+ FileLock unused = fileLock(fileChannel, false)) {
+ Files.delete(path);
+ return true;
+ } catch (NoSuchFileException e) {
+ LOGGER.debug("No such file to delete {}: {}", path,
e.getMessage());
+ } catch (IOException e) {
+ LOGGER.warn("Failed to delete tracking file '{}'", path,
e);
+ throw new UncheckedIOException(e);
+ }
+ }
+ }
+ return false;
}
- private Object getMutex(File file) {
+ private Object mutex(Path path) {
// The interned string of path is (mis)used as mutex, to exclude
different threads going for same file,
// as JVM file locking happens on JVM not on Thread level. This is how
original code did it ¯\_(ツ)_/¯
- /*
- * NOTE: Locks held by one JVM must not overlap and using the
canonical path is our best bet, still another
- * piece of code might have locked the same file (unlikely though) or
the canonical path fails to capture file
- * identity sufficiently as is the case with Java 1.6 and symlinks on
Windows.
- */
- try {
- return file.getCanonicalPath().intern();
- } catch (IOException e) {
- LOGGER.warn("Failed to canonicalize path {}", file, e);
- // TODO This is code smell and deprecated
- return file.getAbsolutePath().intern();
- }
+ return path.toAbsolutePath().normalize().toString().intern();
}
- private FileLock fileLock(FileChannel channel, long size, boolean shared)
throws IOException {
+ private FileLock fileLock(FileChannel channel, boolean shared) throws
IOException {
FileLock lock = null;
for (int attempts = 8; attempts >= 0; attempts--) {
try {
- lock = channel.lock(0, size, shared);
+ lock = channel.lock(0, Long.MAX_VALUE, shared);
break;
} catch (OverlappingFileLockException e) {
if (attempts <= 0) {
diff --git
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultUpdateCheckManager.java
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultUpdateCheckManager.java
index 50a960bfa..7f64b200f 100644
---
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultUpdateCheckManager.java
+++
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultUpdateCheckManager.java
@@ -476,7 +476,7 @@ public class DefaultUpdateCheckManager implements
UpdateCheckManager, Service {
Properties props = write(touchFile, dataKey, transferKey,
check.getException());
if (artifactFile.exists() && !hasErrors(props)) {
- touchFile.delete();
+ trackingFileManager.delete(touchFile);
}
}
diff --git
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/TrackingFileManager.java
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/TrackingFileManager.java
index 5e922bf58..93639d708 100644
---
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/TrackingFileManager.java
+++
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/TrackingFileManager.java
@@ -36,4 +36,11 @@ public interface TrackingFileManager {
* as in updated file, never {@code null}.
*/
Properties update(File file, Map<String, String> updates);
+
+ /**
+ * Deletes the specified properties file, if exists. If file existed and
was deleted, returns {@code true}.
+ *
+ * @since 1.9.25
+ */
+ boolean delete(File file);
}
diff --git
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultTrackingFileManagerTest.java
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultTrackingFileManagerTest.java
index 7209ed798..035b96a1d 100644
---
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultTrackingFileManagerTest.java
+++
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultTrackingFileManagerTest.java
@@ -20,6 +20,7 @@ package org.eclipse.aether.internal.impl;
import java.io.File;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -31,6 +32,7 @@ import org.eclipse.aether.internal.test.util.TestFileUtils;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -104,6 +106,17 @@ public class DefaultTrackingFileManagerTest {
}
}
+ @Test
+ public void testDeleteFileIsGone() throws Exception {
+ TrackingFileManager tfm = new DefaultTrackingFileManager();
+
+ for (int i = 0; i < 1000; i++) {
+ File propFile =
TestFileUtils.createTempFile("#COMMENT\nkey1=value1\nkey2 : value2");
+ assertTrue(tfm.delete(propFile));
+ assertFalse("File is not gone",
Files.isRegularFile(propFile.toPath()));
+ }
+ }
+
@Test
public void testLockingOnCanonicalPath() throws Exception {
final TrackingFileManager tfm = new DefaultTrackingFileManager();