Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 82766b0

Browse files
authored
[1.9.x] TrackingFileManager changes (#1695)
Backport from master. Needs Maven changes as well. Aligned with master fully. Master PR: #1692
1 parent 028404f commit 82766b0

File tree

4 files changed

+77
-43
lines changed

4 files changed

+77
-43
lines changed

maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultTrackingFileManager.java

Lines changed: 56 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,19 @@
2121
import javax.inject.Named;
2222
import javax.inject.Singleton;
2323

24-
import java.io.ByteArrayInputStream;
2524
import java.io.ByteArrayOutputStream;
2625
import java.io.File;
27-
import java.io.FileInputStream;
2826
import java.io.IOException;
29-
import java.io.RandomAccessFile;
3027
import java.io.UncheckedIOException;
28+
import java.nio.ByteBuffer;
29+
import java.nio.channels.Channels;
3130
import java.nio.channels.FileChannel;
3231
import java.nio.channels.FileLock;
3332
import java.nio.channels.OverlappingFileLockException;
3433
import java.nio.file.Files;
34+
import java.nio.file.NoSuchFileException;
35+
import java.nio.file.Path;
36+
import java.nio.file.StandardOpenOption;
3537
import java.util.Map;
3638
import java.util.Properties;
3739

@@ -45,6 +47,9 @@
4547
* to back off two parallel implementations that coexist in Maven (this class and {@code maven-compat} one), as in
4648
* certain cases the two implementations may collide on properties files. This locking must remain in place for as long
4749
* as {@code maven-compat} code exists.
50+
*
51+
* <em>IMPORTANT:</em> This class is kept fully in sync with the master branch one (w/ simple change to convert File
52+
* to Path instances).
4853
*/
4954
@Singleton
5055
@Named
@@ -53,15 +58,18 @@ public final class DefaultTrackingFileManager implements TrackingFileManager {
5358

5459
@Override
5560
public Properties read(File file) {
56-
if (Files.isReadable(file.toPath())) {
57-
synchronized (getMutex(file)) {
58-
try (FileInputStream stream = new FileInputStream(file);
59-
FileLock unused = fileLock(stream.getChannel(), Math.max(1, file.length()), true)) {
61+
Path path = file.toPath();
62+
if (Files.isReadable(path)) {
63+
synchronized (mutex(path)) {
64+
try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ);
65+
FileLock unused = fileLock(fileChannel, true)) {
6066
Properties props = new Properties();
61-
props.load(stream);
67+
props.load(Channels.newInputStream(fileChannel));
6268
return props;
69+
} catch (NoSuchFileException e) {
70+
LOGGER.debug("No such file to read {}: {}", path, e.getMessage());
6371
} catch (IOException e) {
64-
LOGGER.warn("Failed to read tracking file '{}'", file, e);
72+
LOGGER.warn("Failed to read tracking file '{}'", path, e);
6573
throw new UncheckedIOException(e);
6674
}
6775
}
@@ -71,22 +79,20 @@ public Properties read(File file) {
7179

7280
@Override
7381
public Properties update(File file, Map<String, String> updates) {
74-
Properties props = new Properties();
75-
82+
Path path = file.toPath();
7683
try {
77-
Files.createDirectories(file.getParentFile().toPath());
84+
Files.createDirectories(path.getParent());
7885
} catch (IOException e) {
79-
LOGGER.warn("Failed to create tracking file parent '{}'", file, e);
86+
LOGGER.warn("Failed to create tracking file parent '{}'", path, e);
8087
throw new UncheckedIOException(e);
8188
}
82-
83-
synchronized (getMutex(file)) {
84-
try (RandomAccessFile raf = new RandomAccessFile(file, "rw");
85-
FileLock unused = fileLock(raf.getChannel(), Math.max(1, raf.length()), false)) {
86-
if (raf.length() > 0) {
87-
byte[] buffer = new byte[(int) raf.length()];
88-
raf.readFully(buffer);
89-
props.load(new ByteArrayInputStream(buffer));
89+
synchronized (mutex(path)) {
90+
try (FileChannel fileChannel = FileChannel.open(
91+
path, StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
92+
FileLock unused = fileLock(fileChannel, false)) {
93+
Properties props = new Properties();
94+
if (fileChannel.size() > 0) {
95+
props.load(Channels.newInputStream(fileChannel));
9096
}
9197

9298
for (Map.Entry<String, String> update : updates.entrySet()) {
@@ -97,46 +103,54 @@ public Properties update(File file, Map<String, String> updates) {
97103
}
98104
}
99105

100-
LOGGER.debug("Writing tracking file '{}'", file);
106+
LOGGER.debug("Writing tracking file '{}'", path);
101107
ByteArrayOutputStream stream = new ByteArrayOutputStream(1024 * 2);
102108
props.store(
103109
stream,
104110
"NOTE: This is a Maven Resolver internal implementation file"
105111
+ ", its format can be changed without prior notice.");
106-
raf.seek(0L);
107-
raf.write(stream.toByteArray());
108-
raf.setLength(raf.getFilePointer());
112+
fileChannel.position(0);
113+
int written = fileChannel.write(ByteBuffer.wrap(stream.toByteArray()));
114+
fileChannel.truncate(written);
115+
return props;
109116
} catch (IOException e) {
110-
LOGGER.warn("Failed to write tracking file '{}'", file, e);
117+
LOGGER.warn("Failed to write tracking file '{}'", path, e);
111118
throw new UncheckedIOException(e);
112119
}
113120
}
121+
}
114122

115-
return props;
123+
@Override
124+
public boolean delete(File file) {
125+
Path path = file.toPath();
126+
if (Files.isReadable(path)) {
127+
synchronized (mutex(path)) {
128+
try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.WRITE);
129+
FileLock unused = fileLock(fileChannel, false)) {
130+
Files.delete(path);
131+
return true;
132+
} catch (NoSuchFileException e) {
133+
LOGGER.debug("No such file to delete {}: {}", path, e.getMessage());
134+
} catch (IOException e) {
135+
LOGGER.warn("Failed to delete tracking file '{}'", path, e);
136+
throw new UncheckedIOException(e);
137+
}
138+
}
139+
}
140+
return false;
116141
}
117142

118-
private Object getMutex(File file) {
143+
private Object mutex(Path path) {
119144
// The interned string of path is (mis)used as mutex, to exclude different threads going for same file,
120145
// as JVM file locking happens on JVM not on Thread level. This is how original code did it ¯\_(ツ)_/¯
121-
/*
122-
* NOTE: Locks held by one JVM must not overlap and using the canonical path is our best bet, still another
123-
* piece of code might have locked the same file (unlikely though) or the canonical path fails to capture file
124-
* identity sufficiently as is the case with Java 1.6 and symlinks on Windows.
125-
*/
126-
try {
127-
return file.getCanonicalPath().intern();
128-
} catch (IOException e) {
129-
LOGGER.warn("Failed to canonicalize path {}", file, e);
130-
// TODO This is code smell and deprecated
131-
return file.getAbsolutePath().intern();
132-
}
146+
return path.toAbsolutePath().normalize().toString().intern();
133147
}
134148

135-
private FileLock fileLock(FileChannel channel, long size, boolean shared) throws IOException {
149+
private FileLock fileLock(FileChannel channel, boolean shared) throws IOException {
136150
FileLock lock = null;
137151
for (int attempts = 8; attempts >= 0; attempts--) {
138152
try {
139-
lock = channel.lock(0, size, shared);
153+
lock = channel.lock(0, Long.MAX_VALUE, shared);
140154
break;
141155
} catch (OverlappingFileLockException e) {
142156
if (attempts <= 0) {

maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultUpdateCheckManager.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ public void touchArtifact(RepositorySystemSession session, UpdateCheck<Artifact,
476476
Properties props = write(touchFile, dataKey, transferKey, check.getException());
477477

478478
if (artifactFile.exists() && !hasErrors(props)) {
479-
touchFile.delete();
479+
trackingFileManager.delete(touchFile);
480480
}
481481
}
482482

maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/TrackingFileManager.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,11 @@ public interface TrackingFileManager {
3636
* as in updated file, never {@code null}.
3737
*/
3838
Properties update(File file, Map<String, String> updates);
39+
40+
/**
41+
* Deletes the specified properties file, if exists. If file existed and was deleted, returns {@code true}.
42+
*
43+
* @since 1.9.25
44+
*/
45+
boolean delete(File file);
3946
}

maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultTrackingFileManagerTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import java.io.File;
2222
import java.nio.charset.StandardCharsets;
23+
import java.nio.file.Files;
2324
import java.util.ArrayList;
2425
import java.util.Collections;
2526
import java.util.HashMap;
@@ -31,6 +32,7 @@
3132
import org.junit.Test;
3233

3334
import static org.junit.Assert.assertEquals;
35+
import static org.junit.Assert.assertFalse;
3436
import static org.junit.Assert.assertNotNull;
3537
import static org.junit.Assert.assertNull;
3638
import static org.junit.Assert.assertTrue;
@@ -104,6 +106,17 @@ public void testUpdateNoFileLeak() throws Exception {
104106
}
105107
}
106108

109+
@Test
110+
public void testDeleteFileIsGone() throws Exception {
111+
TrackingFileManager tfm = new DefaultTrackingFileManager();
112+
113+
for (int i = 0; i < 1000; i++) {
114+
File propFile = TestFileUtils.createTempFile("#COMMENT\nkey1=value1\nkey2 : value2");
115+
assertTrue(tfm.delete(propFile));
116+
assertFalse("File is not gone", Files.isRegularFile(propFile.toPath()));
117+
}
118+
}
119+
107120
@Test
108121
public void testLockingOnCanonicalPath() throws Exception {
109122
final TrackingFileManager tfm = new DefaultTrackingFileManager();

0 commit comments

Comments
 (0)