From 02691c4fc6adbcf969b8c0ef2c48f5406aed078e Mon Sep 17 00:00:00 2001 From: XenoAmess Date: Sat, 14 Jun 2025 20:48:25 +0800 Subject: [PATCH 1/9] BfDependencyCollector can share executorService between resolvers --- .../collect/bf/BfDependencyCollector.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java index 34718c2c8..fb3258f35 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java @@ -119,11 +119,11 @@ public class BfDependencyCollector extends DependencyCollectorDelegate { public static final String CONFIG_PROP_THREADS = CONFIG_PROPS_PREFIX + "threads"; /** - * The default value for {@link #CONFIG_PROP_THREADS}, default value 5. + * The default value for {@link #CONFIG_PROP_THREADS}, default value {@code Runtime.getRuntime().availableProcessors()}. * * @since 1.9.0 */ - public static final int DEFAULT_THREADS = 5; + public static final int DEFAULT_THREADS = Runtime.getRuntime().availableProcessors(); @Inject public BfDependencyCollector( @@ -470,7 +470,7 @@ private ArtifactDescriptorResult resolveDescriptorForVersion( } static class ParallelDescriptorResolver implements Closeable { - private final ExecutorService executorService; + private static volatile ExecutorService executorService; /** * Artifact ID -> Future of DescriptorResolutionResult @@ -478,11 +478,20 @@ static class ParallelDescriptorResolver implements Closeable { private final Map> results = new ConcurrentHashMap<>(256); ParallelDescriptorResolver(int threads) { - this.executorService = ExecutorUtils.threadPool(threads, getClass().getSimpleName() + "-"); + if (executorService == null) { + synchronized (ParallelDescriptorResolver.class) { + if (executorService == null) { + // create only once, so that we can reuse it across multiple instances of this class + // and avoid creating too many threads / recreating it for too many times + executorService = ExecutorUtils.threadPool( + threads, ParallelDescriptorResolver.class.getSimpleName() + "-"); + } + } + } } void resolveDescriptors(Artifact artifact, Callable callable) { - results.computeIfAbsent(ArtifactIdUtils.toId(artifact), key -> this.executorService.submit(callable)); + results.computeIfAbsent(ArtifactIdUtils.toId(artifact), key -> executorService.submit(callable)); } void cacheVersionRangeDescriptor(Artifact artifact, DescriptorResolutionResult resolutionResult) { @@ -494,9 +503,7 @@ Future find(Artifact artifact) { } @Override - public void close() { - executorService.shutdown(); - } + public void close() {} } static class DoneFuture implements Future { From d0a0269c06e2936b9b2f4c64a9e6cb8dcafbb0ad Mon Sep 17 00:00:00 2001 From: XenoAmess Date: Sun, 15 Jun 2025 01:34:19 +0800 Subject: [PATCH 2/9] intern some strings to make use of the new hash cache in String class in jdk. --- .../aether/artifact/DefaultArtifact.java | 54 +++++++++---------- .../aether/util/artifact/SubArtifact.java | 4 +- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/artifact/DefaultArtifact.java b/maven-resolver-api/src/main/java/org/eclipse/aether/artifact/DefaultArtifact.java index 080993ec1..4128875a5 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/artifact/DefaultArtifact.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/artifact/DefaultArtifact.java @@ -112,11 +112,11 @@ public DefaultArtifact(String coords, Map properties, ArtifactTy throw new IllegalArgumentException("Bad artifact coordinates " + coords + ", expected format is :[:[:]]:"); } - groupId = m.group(1); - artifactId = m.group(2); - extension = get(m.group(4), type == null ? "jar" : type.getExtension()); - classifier = get(m.group(6), type == null ? "" : type.getClassifier()); - this.version = emptify(m.group(7)); + groupId = m.group(1).intern(); + artifactId = m.group(2).intern(); + extension = get(m.group(4), type == null ? "jar" : type.getExtension()).intern(); + classifier = get(m.group(6), type == null ? "" : type.getClassifier()).intern(); + this.version = emptify(m.group(7)).intern(); this.path = null; this.properties = mergeArtifactProperties(properties, (type != null) ? type.getProperties() : null); } @@ -193,19 +193,19 @@ public DefaultArtifact( String version, Map properties, ArtifactType type) { - this.groupId = emptify(groupId); - this.artifactId = emptify(artifactId); + this.groupId = emptify(groupId).intern(); + this.artifactId = emptify(artifactId).intern(); if (classifier != null || type == null) { - this.classifier = emptify(classifier); + this.classifier = emptify(classifier).intern(); } else { - this.classifier = emptify(type.getClassifier()); + this.classifier = emptify(type.getClassifier()).intern(); } if (extension != null || type == null) { - this.extension = emptify(extension); + this.extension = emptify(extension).intern(); } else { - this.extension = emptify(type.getExtension()); + this.extension = emptify(type.getExtension()).intern(); } - this.version = emptify(version); + this.version = emptify(version).intern(); this.path = null; this.properties = mergeArtifactProperties(properties, (type != null) ? type.getProperties() : null); } @@ -255,11 +255,11 @@ public DefaultArtifact( String version, Map properties, File file) { - this.groupId = emptify(groupId); - this.artifactId = emptify(artifactId); - this.classifier = emptify(classifier); - this.extension = emptify(extension); - this.version = emptify(version); + this.groupId = emptify(groupId).intern(); + this.artifactId = emptify(artifactId).intern(); + this.classifier = emptify(classifier).intern(); + this.extension = emptify(extension).intern(); + this.version = emptify(version).intern(); this.path = file != null ? file.toPath() : null; this.properties = copyProperties(properties); } @@ -284,11 +284,11 @@ public DefaultArtifact( String version, Map properties, Path path) { - this.groupId = emptify(groupId); - this.artifactId = emptify(artifactId); - this.classifier = emptify(classifier); - this.extension = emptify(extension); - this.version = emptify(version); + this.groupId = emptify(groupId).intern(); + this.artifactId = emptify(artifactId).intern(); + this.classifier = emptify(classifier).intern(); + this.extension = emptify(extension).intern(); + this.version = emptify(version).intern(); this.path = path; this.properties = copyProperties(properties); } @@ -302,11 +302,11 @@ public DefaultArtifact( Path path, Map properties) { // NOTE: This constructor assumes immutability of the provided properties, for internal use only - this.groupId = emptify(groupId); - this.artifactId = emptify(artifactId); - this.classifier = emptify(classifier); - this.extension = emptify(extension); - this.version = emptify(version); + this.groupId = emptify(groupId).intern(); + this.artifactId = emptify(artifactId).intern(); + this.classifier = emptify(classifier).intern(); + this.extension = emptify(extension).intern(); + this.version = emptify(version).intern(); this.path = path; this.properties = properties; } diff --git a/maven-resolver-util/src/main/java/org/eclipse/aether/util/artifact/SubArtifact.java b/maven-resolver-util/src/main/java/org/eclipse/aether/util/artifact/SubArtifact.java index 5e8c677e7..cfe152b87 100644 --- a/maven-resolver-util/src/main/java/org/eclipse/aether/util/artifact/SubArtifact.java +++ b/maven-resolver-util/src/main/java/org/eclipse/aether/util/artifact/SubArtifact.java @@ -137,8 +137,8 @@ public SubArtifact( public SubArtifact( Artifact mainArtifact, String classifier, String extension, Map properties, Path path) { this.mainArtifact = requireNonNull(mainArtifact, "main artifact cannot be null"); - this.classifier = classifier; - this.extension = extension; + this.classifier = classifier == null ? null : classifier.intern(); + this.extension = extension == null ? null : extension.intern(); this.path = path; this.properties = copyProperties(properties); } From f295e159a120a194a4837823706696e69061ddcc Mon Sep 17 00:00:00 2001 From: XenoAmess Date: Sun, 15 Jun 2025 01:34:40 +0800 Subject: [PATCH 3/9] bugfix for MMap with List value usage.(also performance related) fix the bug about using MMap when value be collection, but the old codes didn't copy the collection, thus may cause appending to a list value in a DoneMMap, thus makes infinity long value, thus makes the run slow in super multi module project. --- .../manager/AbstractDependencyManager.java | 10 +-- .../aether/util/graph/manager/MMap.java | 70 ++++++++++++++++++- 2 files changed, 72 insertions(+), 8 deletions(-) diff --git a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java index 90bb3c1eb..eb6f47816 100644 --- a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java +++ b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java @@ -151,7 +151,7 @@ public DependencyManager deriveChildManager(DependencyCollectionContext context) String version = artifact.getVersion(); if (!version.isEmpty() && !managedVersions.containsKey(key)) { if (managedVersions == this.managedVersions) { - managedVersions = MMap.copy(this.managedVersions); + managedVersions = MMap.append(this.managedVersions); } managedVersions.put(key, new Holder<>(depth, version)); } @@ -159,7 +159,7 @@ public DependencyManager deriveChildManager(DependencyCollectionContext context) String scope = managedDependency.getScope(); if (!scope.isEmpty() && !managedScopes.containsKey(key)) { if (managedScopes == this.managedScopes) { - managedScopes = MMap.copy(this.managedScopes); + managedScopes = MMap.append(this.managedScopes); } managedScopes.put(key, new Holder<>(depth, scope)); } @@ -167,7 +167,7 @@ public DependencyManager deriveChildManager(DependencyCollectionContext context) Boolean optional = managedDependency.getOptional(); if (optional != null && !managedOptionals.containsKey(key)) { if (managedOptionals == this.managedOptionals) { - managedOptionals = MMap.copy(this.managedOptionals); + managedOptionals = MMap.append(this.managedOptionals); } managedOptionals.put(key, new Holder<>(depth, optional)); } @@ -177,7 +177,7 @@ public DependencyManager deriveChildManager(DependencyCollectionContext context) : systemDependencyScope.getSystemPath(managedDependency.getArtifact()); if (localPath != null && !managedLocalPaths.containsKey(key)) { if (managedLocalPaths == this.managedLocalPaths) { - managedLocalPaths = MMap.copy(this.managedLocalPaths); + managedLocalPaths = MMap.append(this.managedLocalPaths); } managedLocalPaths.put(key, new Holder<>(depth, localPath)); } @@ -185,7 +185,7 @@ public DependencyManager deriveChildManager(DependencyCollectionContext context) Collection exclusions = managedDependency.getExclusions(); if (!exclusions.isEmpty()) { if (managedExclusions == this.managedExclusions) { - managedExclusions = MMap.copy(this.managedExclusions); + managedExclusions = MMap.copyWithListValue(this.managedExclusions); } Collection>> managed = managedExclusions.get(key); if (managed == null) { diff --git a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/MMap.java b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/MMap.java index af7aa34cf..8027fb941 100644 --- a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/MMap.java +++ b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/MMap.java @@ -18,7 +18,11 @@ */ package org.eclipse.aether.util.graph.manager; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; +import java.util.Map; +import java.util.Objects; /** * Warning: this is a special map-like construct that suits only and should be used only in this package! @@ -47,6 +51,18 @@ public static MMap copy(MMap orig) { return new MMap<>(new HashMap<>(orig.delegate)); } + public static MMap> copyWithListValue(MMap> orig) { + HashMap> newMap = new HashMap<>((int) Math.ceil(orig.size() / 0.75D)); + for (Map.Entry> entry : orig.delegate.entrySet()) { + newMap.put(entry.getKey(), entry.getValue() == null ? null : new ArrayList<>(entry.getValue())); + } + return new MMap<>(newMap); + } + + public static MMap append(MMap orig) { + return new AppendMMap<>(orig); + } + protected final HashMap delegate; private MMap(HashMap delegate) { @@ -69,6 +85,10 @@ public MMap done() { return new DoneMMap<>(delegate); } + public MMap append() { + return new AppendMMap<>(this); + } + @Override public int hashCode() { throw new IllegalStateException("MMap is not done yet"); @@ -79,12 +99,20 @@ public boolean equals(Object o) { throw new IllegalStateException("MMap is not done yet"); } + public int size() { + return delegate.size(); + } + private static class DoneMMap extends MMap { - private final int hashCode; + volatile long hashCode = Long.MAX_VALUE; private DoneMMap(HashMap delegate) { super(delegate); - this.hashCode = delegate.hashCode(); + } + + private DoneMMap(HashMap delegate, long hashCode) { + super(delegate); + this.hashCode = hashCode; } @Override @@ -99,7 +127,12 @@ public MMap done() { @Override public int hashCode() { - return hashCode; + if (this.hashCode != Long.MAX_VALUE) { + return (int) hashCode; + } + int result = delegate.hashCode(); + this.hashCode = result; + return result; } @Override @@ -111,4 +144,35 @@ public boolean equals(Object o) { return delegate.equals(other.delegate); } } + + private static class AppendMMap extends DoneMMap { + + AppendMMap(MMap orig) { + super(new HashMap<>(orig.delegate)); + if (orig instanceof DoneMMap) { + this.hashCode = ((DoneMMap) orig).hashCode; + } else { + this.hashCode = Long.MAX_VALUE; + } + } + + @Override + public V put(K key, V value) { + boolean ifPresent = delegate.containsKey(key); + V originalValue = delegate.put(key, value); + if (hashCode != Long.MAX_VALUE) { + int keyHash = Objects.hashCode(key); + if (ifPresent) { + hashCode -= keyHash ^ Objects.hashCode(originalValue); + } + hashCode += keyHash ^ Objects.hashCode(value); + } + return originalValue; + } + + @Override + public MMap done() { + return new DoneMMap<>(delegate, hashCode); + } + } } From 89b00629ec40b71d3bcaf739edfb534cdff3d144 Mon Sep 17 00:00:00 2001 From: XenoAmess Date: Wed, 18 Jun 2025 21:07:31 +0800 Subject: [PATCH 4/9] Revert "BfDependencyCollector can share executorService between resolvers" This reverts commit 02691c4fc6adbcf969b8c0ef2c48f5406aed078e. --- .../collect/bf/BfDependencyCollector.java | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java index fb3258f35..34718c2c8 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java @@ -119,11 +119,11 @@ public class BfDependencyCollector extends DependencyCollectorDelegate { public static final String CONFIG_PROP_THREADS = CONFIG_PROPS_PREFIX + "threads"; /** - * The default value for {@link #CONFIG_PROP_THREADS}, default value {@code Runtime.getRuntime().availableProcessors()}. + * The default value for {@link #CONFIG_PROP_THREADS}, default value 5. * * @since 1.9.0 */ - public static final int DEFAULT_THREADS = Runtime.getRuntime().availableProcessors(); + public static final int DEFAULT_THREADS = 5; @Inject public BfDependencyCollector( @@ -470,7 +470,7 @@ private ArtifactDescriptorResult resolveDescriptorForVersion( } static class ParallelDescriptorResolver implements Closeable { - private static volatile ExecutorService executorService; + private final ExecutorService executorService; /** * Artifact ID -> Future of DescriptorResolutionResult @@ -478,20 +478,11 @@ static class ParallelDescriptorResolver implements Closeable { private final Map> results = new ConcurrentHashMap<>(256); ParallelDescriptorResolver(int threads) { - if (executorService == null) { - synchronized (ParallelDescriptorResolver.class) { - if (executorService == null) { - // create only once, so that we can reuse it across multiple instances of this class - // and avoid creating too many threads / recreating it for too many times - executorService = ExecutorUtils.threadPool( - threads, ParallelDescriptorResolver.class.getSimpleName() + "-"); - } - } - } + this.executorService = ExecutorUtils.threadPool(threads, getClass().getSimpleName() + "-"); } void resolveDescriptors(Artifact artifact, Callable callable) { - results.computeIfAbsent(ArtifactIdUtils.toId(artifact), key -> executorService.submit(callable)); + results.computeIfAbsent(ArtifactIdUtils.toId(artifact), key -> this.executorService.submit(callable)); } void cacheVersionRangeDescriptor(Artifact artifact, DescriptorResolutionResult resolutionResult) { @@ -503,7 +494,9 @@ Future find(Artifact artifact) { } @Override - public void close() {} + public void close() { + executorService.shutdown(); + } } static class DoneFuture implements Future { From 6accca26f1cc2f5dee75889e5620ebb6ac1b7989 Mon Sep 17 00:00:00 2001 From: XenoAmess Date: Thu, 19 Jun 2025 00:39:56 +0800 Subject: [PATCH 5/9] Revert "intern some strings to make use of the new hash cache in String class in jdk." This reverts commit d0a0269c06e2936b9b2f4c64a9e6cb8dcafbb0ad. --- .../aether/artifact/DefaultArtifact.java | 54 +++++++++---------- .../aether/util/artifact/SubArtifact.java | 4 +- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/artifact/DefaultArtifact.java b/maven-resolver-api/src/main/java/org/eclipse/aether/artifact/DefaultArtifact.java index 4128875a5..080993ec1 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/artifact/DefaultArtifact.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/artifact/DefaultArtifact.java @@ -112,11 +112,11 @@ public DefaultArtifact(String coords, Map properties, ArtifactTy throw new IllegalArgumentException("Bad artifact coordinates " + coords + ", expected format is :[:[:]]:"); } - groupId = m.group(1).intern(); - artifactId = m.group(2).intern(); - extension = get(m.group(4), type == null ? "jar" : type.getExtension()).intern(); - classifier = get(m.group(6), type == null ? "" : type.getClassifier()).intern(); - this.version = emptify(m.group(7)).intern(); + groupId = m.group(1); + artifactId = m.group(2); + extension = get(m.group(4), type == null ? "jar" : type.getExtension()); + classifier = get(m.group(6), type == null ? "" : type.getClassifier()); + this.version = emptify(m.group(7)); this.path = null; this.properties = mergeArtifactProperties(properties, (type != null) ? type.getProperties() : null); } @@ -193,19 +193,19 @@ public DefaultArtifact( String version, Map properties, ArtifactType type) { - this.groupId = emptify(groupId).intern(); - this.artifactId = emptify(artifactId).intern(); + this.groupId = emptify(groupId); + this.artifactId = emptify(artifactId); if (classifier != null || type == null) { - this.classifier = emptify(classifier).intern(); + this.classifier = emptify(classifier); } else { - this.classifier = emptify(type.getClassifier()).intern(); + this.classifier = emptify(type.getClassifier()); } if (extension != null || type == null) { - this.extension = emptify(extension).intern(); + this.extension = emptify(extension); } else { - this.extension = emptify(type.getExtension()).intern(); + this.extension = emptify(type.getExtension()); } - this.version = emptify(version).intern(); + this.version = emptify(version); this.path = null; this.properties = mergeArtifactProperties(properties, (type != null) ? type.getProperties() : null); } @@ -255,11 +255,11 @@ public DefaultArtifact( String version, Map properties, File file) { - this.groupId = emptify(groupId).intern(); - this.artifactId = emptify(artifactId).intern(); - this.classifier = emptify(classifier).intern(); - this.extension = emptify(extension).intern(); - this.version = emptify(version).intern(); + this.groupId = emptify(groupId); + this.artifactId = emptify(artifactId); + this.classifier = emptify(classifier); + this.extension = emptify(extension); + this.version = emptify(version); this.path = file != null ? file.toPath() : null; this.properties = copyProperties(properties); } @@ -284,11 +284,11 @@ public DefaultArtifact( String version, Map properties, Path path) { - this.groupId = emptify(groupId).intern(); - this.artifactId = emptify(artifactId).intern(); - this.classifier = emptify(classifier).intern(); - this.extension = emptify(extension).intern(); - this.version = emptify(version).intern(); + this.groupId = emptify(groupId); + this.artifactId = emptify(artifactId); + this.classifier = emptify(classifier); + this.extension = emptify(extension); + this.version = emptify(version); this.path = path; this.properties = copyProperties(properties); } @@ -302,11 +302,11 @@ public DefaultArtifact( Path path, Map properties) { // NOTE: This constructor assumes immutability of the provided properties, for internal use only - this.groupId = emptify(groupId).intern(); - this.artifactId = emptify(artifactId).intern(); - this.classifier = emptify(classifier).intern(); - this.extension = emptify(extension).intern(); - this.version = emptify(version).intern(); + this.groupId = emptify(groupId); + this.artifactId = emptify(artifactId); + this.classifier = emptify(classifier); + this.extension = emptify(extension); + this.version = emptify(version); this.path = path; this.properties = properties; } diff --git a/maven-resolver-util/src/main/java/org/eclipse/aether/util/artifact/SubArtifact.java b/maven-resolver-util/src/main/java/org/eclipse/aether/util/artifact/SubArtifact.java index cfe152b87..5e8c677e7 100644 --- a/maven-resolver-util/src/main/java/org/eclipse/aether/util/artifact/SubArtifact.java +++ b/maven-resolver-util/src/main/java/org/eclipse/aether/util/artifact/SubArtifact.java @@ -137,8 +137,8 @@ public SubArtifact( public SubArtifact( Artifact mainArtifact, String classifier, String extension, Map properties, Path path) { this.mainArtifact = requireNonNull(mainArtifact, "main artifact cannot be null"); - this.classifier = classifier == null ? null : classifier.intern(); - this.extension = extension == null ? null : extension.intern(); + this.classifier = classifier; + this.extension = extension; this.path = path; this.properties = copyProperties(properties); } From 82f1b17615d65cd2d8c739534b2bfc30355f0cd0 Mon Sep 17 00:00:00 2001 From: XenoAmess Date: Thu, 19 Jun 2025 23:02:08 +0800 Subject: [PATCH 6/9] revert most of the mmap changes to focus on the most severe problem --- .../manager/AbstractDependencyManager.java | 8 +-- .../aether/util/graph/manager/MMap.java | 64 ++----------------- 2 files changed, 10 insertions(+), 62 deletions(-) diff --git a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java index eb6f47816..ddba34f5d 100644 --- a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java +++ b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java @@ -151,7 +151,7 @@ public DependencyManager deriveChildManager(DependencyCollectionContext context) String version = artifact.getVersion(); if (!version.isEmpty() && !managedVersions.containsKey(key)) { if (managedVersions == this.managedVersions) { - managedVersions = MMap.append(this.managedVersions); + managedVersions = MMap.copy(this.managedVersions); } managedVersions.put(key, new Holder<>(depth, version)); } @@ -159,7 +159,7 @@ public DependencyManager deriveChildManager(DependencyCollectionContext context) String scope = managedDependency.getScope(); if (!scope.isEmpty() && !managedScopes.containsKey(key)) { if (managedScopes == this.managedScopes) { - managedScopes = MMap.append(this.managedScopes); + managedScopes = MMap.copy(this.managedScopes); } managedScopes.put(key, new Holder<>(depth, scope)); } @@ -167,7 +167,7 @@ public DependencyManager deriveChildManager(DependencyCollectionContext context) Boolean optional = managedDependency.getOptional(); if (optional != null && !managedOptionals.containsKey(key)) { if (managedOptionals == this.managedOptionals) { - managedOptionals = MMap.append(this.managedOptionals); + managedOptionals = MMap.copy(this.managedOptionals); } managedOptionals.put(key, new Holder<>(depth, optional)); } @@ -177,7 +177,7 @@ public DependencyManager deriveChildManager(DependencyCollectionContext context) : systemDependencyScope.getSystemPath(managedDependency.getArtifact()); if (localPath != null && !managedLocalPaths.containsKey(key)) { if (managedLocalPaths == this.managedLocalPaths) { - managedLocalPaths = MMap.append(this.managedLocalPaths); + managedLocalPaths = MMap.copy(this.managedLocalPaths); } managedLocalPaths.put(key, new Holder<>(depth, localPath)); } diff --git a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/MMap.java b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/MMap.java index 8027fb941..3b2825015 100644 --- a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/MMap.java +++ b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/MMap.java @@ -22,7 +22,6 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; -import java.util.Objects; /** * Warning: this is a special map-like construct that suits only and should be used only in this package! @@ -52,17 +51,14 @@ public static MMap copy(MMap orig) { } public static MMap> copyWithListValue(MMap> orig) { - HashMap> newMap = new HashMap<>((int) Math.ceil(orig.size() / 0.75D)); - for (Map.Entry> entry : orig.delegate.entrySet()) { + HashMap> delegateLocal = orig.delegate; + HashMap> newMap = new HashMap<>((int) Math.ceil(delegateLocal.size() / 0.75D)); + for (Map.Entry> entry : delegateLocal.entrySet()) { newMap.put(entry.getKey(), entry.getValue() == null ? null : new ArrayList<>(entry.getValue())); } return new MMap<>(newMap); } - public static MMap append(MMap orig) { - return new AppendMMap<>(orig); - } - protected final HashMap delegate; private MMap(HashMap delegate) { @@ -85,10 +81,6 @@ public MMap done() { return new DoneMMap<>(delegate); } - public MMap append() { - return new AppendMMap<>(this); - } - @Override public int hashCode() { throw new IllegalStateException("MMap is not done yet"); @@ -99,20 +91,12 @@ public boolean equals(Object o) { throw new IllegalStateException("MMap is not done yet"); } - public int size() { - return delegate.size(); - } - private static class DoneMMap extends MMap { - volatile long hashCode = Long.MAX_VALUE; + private final int hashCode; private DoneMMap(HashMap delegate) { super(delegate); - } - - private DoneMMap(HashMap delegate, long hashCode) { - super(delegate); - this.hashCode = hashCode; + this.hashCode = delegate.hashCode(); } @Override @@ -127,12 +111,7 @@ public MMap done() { @Override public int hashCode() { - if (this.hashCode != Long.MAX_VALUE) { - return (int) hashCode; - } - int result = delegate.hashCode(); - this.hashCode = result; - return result; + return hashCode; } @Override @@ -144,35 +123,4 @@ public boolean equals(Object o) { return delegate.equals(other.delegate); } } - - private static class AppendMMap extends DoneMMap { - - AppendMMap(MMap orig) { - super(new HashMap<>(orig.delegate)); - if (orig instanceof DoneMMap) { - this.hashCode = ((DoneMMap) orig).hashCode; - } else { - this.hashCode = Long.MAX_VALUE; - } - } - - @Override - public V put(K key, V value) { - boolean ifPresent = delegate.containsKey(key); - V originalValue = delegate.put(key, value); - if (hashCode != Long.MAX_VALUE) { - int keyHash = Objects.hashCode(key); - if (ifPresent) { - hashCode -= keyHash ^ Objects.hashCode(originalValue); - } - hashCode += keyHash ^ Objects.hashCode(value); - } - return originalValue; - } - - @Override - public MMap done() { - return new DoneMMap<>(delegate, hashCode); - } - } } From 22aeddc44609f3af3d48f7237642f3b5a4893d48 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 25 Jun 2025 13:43:46 +0200 Subject: [PATCH 7/9] Sinplify: copy only that list that is about to be modded --- .../graph/manager/AbstractDependencyManager.java | 2 +- .../org/eclipse/aether/util/graph/manager/MMap.java | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java index ddba34f5d..ed251cfe4 100644 --- a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java +++ b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java @@ -185,7 +185,7 @@ public DependencyManager deriveChildManager(DependencyCollectionContext context) Collection exclusions = managedDependency.getExclusions(); if (!exclusions.isEmpty()) { if (managedExclusions == this.managedExclusions) { - managedExclusions = MMap.copyWithListValue(this.managedExclusions); + managedExclusions = MMap.copyWithKey(key, this.managedExclusions); } Collection>> managed = managedExclusions.get(key); if (managed == null) { diff --git a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/MMap.java b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/MMap.java index 3b2825015..78789b41f 100644 --- a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/MMap.java +++ b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/MMap.java @@ -21,7 +21,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; -import java.util.Map; /** * Warning: this is a special map-like construct that suits only and should be used only in this package! @@ -50,13 +49,10 @@ public static MMap copy(MMap orig) { return new MMap<>(new HashMap<>(orig.delegate)); } - public static MMap> copyWithListValue(MMap> orig) { - HashMap> delegateLocal = orig.delegate; - HashMap> newMap = new HashMap<>((int) Math.ceil(delegateLocal.size() / 0.75D)); - for (Map.Entry> entry : delegateLocal.entrySet()) { - newMap.put(entry.getKey(), entry.getValue() == null ? null : new ArrayList<>(entry.getValue())); - } - return new MMap<>(newMap); + public static MMap> copyWithKey(K key, MMap> orig) { + HashMap> delegateLocal = new HashMap<>(orig.delegate); + delegateLocal.computeIfPresent(key, (k, v) -> new ArrayList<>(v)); + return new MMap<>(delegateLocal); } protected final HashMap delegate; From 169706d7271aa1dcae364e68c5bed5559aacc285 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 25 Jun 2025 14:24:20 +0200 Subject: [PATCH 8/9] Fixes: * deriveUntil and applyFrom are invariants within same session; do not spend cpu cycles on them * remove managedLocalPaths as we want javadoc say: first to apply always/consistently --- .../graph/manager/AbstractDependencyManager.java | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java index ed251cfe4..e5610017d 100644 --- a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java +++ b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java @@ -113,15 +113,8 @@ protected AbstractDependencyManager( // nullable: if using scope manager, but there is no system scope defined this.systemDependencyScope = systemDependencyScope; - this.hashCode = Objects.hash( - depth, - deriveUntil, - applyFrom, - managedVersions, - managedScopes, - managedOptionals, - managedLocalPaths, - managedExclusions); + // exclude managedLocalPaths + this.hashCode = Objects.hash(depth, managedVersions, managedScopes, managedOptionals, managedExclusions); } protected abstract DependencyManager newInstance( @@ -320,13 +313,11 @@ public boolean equals(Object obj) { } AbstractDependencyManager that = (AbstractDependencyManager) obj; + // exclude managedLocalPaths return depth == that.depth - && deriveUntil == that.deriveUntil - && applyFrom == that.applyFrom && managedVersions.equals(that.managedVersions) && managedScopes.equals(that.managedScopes) && managedOptionals.equals(that.managedOptionals) - && managedLocalPaths.equals(that.managedLocalPaths) && managedExclusions.equals(that.managedExclusions); } From 603fe367d9746bf5a27883122ad360600adfec0a Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 25 Jun 2025 14:42:33 +0200 Subject: [PATCH 9/9] Add clue that this is intentional --- .../aether/util/graph/manager/AbstractDependencyManager.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java index e5610017d..9996eb6a4 100644 --- a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java +++ b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/AbstractDependencyManager.java @@ -51,6 +51,9 @@ * or applied in same POM) are always applied. This implementation makes sure, that version and scope are not applied * onto same node that actually provided the rules, to no override work that ModelBuilder did. It achieves this goal * by tracking "depth" for each collected rule and ignoring rules coming from same depth as processed dependency node is. + *

+ * Note for future: the field {@code managedLocalPaths} is intentionally left out of hash/equals, with + * reason explained above. * * @since 2.0.0 */