From 2b56fda4f32c7c4d48fe174eb05e1c66c4b0fda7 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Fri, 14 Nov 2025 20:55:43 +0100 Subject: [PATCH 1/9] Locking idea WIP --- .../java/org/eclipse/aether/SyncContext.java | 25 +++++++++++ .../impl/DefaultArtifactResolver.java | 2 +- .../named/NamedLockFactoryAdapter.java | 42 ++++++++++++++----- src/site/markdown/configuration.md | 3 +- 4 files changed, 59 insertions(+), 13 deletions(-) diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/SyncContext.java b/maven-resolver-api/src/main/java/org/eclipse/aether/SyncContext.java index 63e8394bf..5a82403ae 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/SyncContext.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/SyncContext.java @@ -61,6 +61,7 @@ public interface SyncContext extends Closeable { * * @param artifacts The artifacts to acquire, may be {@code null} or empty if none. * @param metadatas The metadatas to acquire, may be {@code null} or empty if none. + * @throws FailedToAcquireLockException if method calls to acquire lock within configured time. */ void acquire(Collection artifacts, Collection metadatas); @@ -69,4 +70,28 @@ public interface SyncContext extends Closeable { * synchronization context has already been closed, this method does nothing. */ void close(); + + /** + * Specific exception thrown by {@link #acquire(Collection, Collection)} method when it cannot acquire the lock. + * + * @since 2.0.14 + */ + final class FailedToAcquireLockException extends IllegalStateException { + private final boolean shared; + + /** + * Constructor. + */ + public FailedToAcquireLockException(boolean shared, String message) { + super(message); + this.shared = shared; + } + + /** + * Returns {@code true} for shared and {@code false} for exclusive sync contexts. + */ + public boolean isShared() { + return shared; + } + } } diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultArtifactResolver.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultArtifactResolver.java index 6c614d923..4f10b4b5b 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultArtifactResolver.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultArtifactResolver.java @@ -105,7 +105,7 @@ public class DefaultArtifactResolver implements ArtifactResolver { */ public static final String CONFIG_PROP_SNAPSHOT_NORMALIZATION = CONFIG_PROPS_PREFIX + "snapshotNormalization"; - public static final boolean DEFAULT_SNAPSHOT_NORMALIZATION = true; + public static final boolean DEFAULT_SNAPSHOT_NORMALIZATION = false; /** * Configuration to enable "interoperability" with Simple LRM, but this breaks RRF feature, hence this configuration diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapter.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapter.java index e96212f04..49b5598cf 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapter.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapter.java @@ -56,6 +56,18 @@ public final class NamedLockFactoryAdapter { public static final long DEFAULT_TIME = 30L; + /** + * The maximum of time amount to be blocked to obtain exclusive lock (keep it low). + * + * @since 2.0.14 + * @configurationSource {@link RepositorySystemSession#getConfigProperties()} + * @configurationType {@link java.lang.Long} + * @configurationDefaultValue {@link #DEFAULT_TIME_EXCLUSIVE} + */ + public static final String CONFIG_PROP_TIME_EXCLUSIVE = CONFIG_PROPS_PREFIX + "exclusiveTime"; + + public static final long DEFAULT_TIME_EXCLUSIVE = 10L; + /** * The unit of maximum time amount to be blocked to obtain lock. Use TimeUnit enum names. * @@ -142,7 +154,9 @@ private static class AdaptedLockSyncContext implements SyncContext { private final NamedLockFactory namedLockFactory; - private final long time; + private final long sharedTime; + + private final long exclusiveTime; private final TimeUnit timeUnit; @@ -161,15 +175,19 @@ private AdaptedLockSyncContext( this.shared = shared; this.lockNaming = lockNaming; this.namedLockFactory = namedLockFactory; - this.time = getTime(session); + this.sharedTime = getTime(session, DEFAULT_TIME, CONFIG_PROP_TIME); + this.exclusiveTime = getTime(session, DEFAULT_TIME_EXCLUSIVE, CONFIG_PROP_TIME_EXCLUSIVE, CONFIG_PROP_TIME); this.timeUnit = getTimeUnit(session); this.retry = getRetry(session); this.retryWait = getRetryWait(session); this.locks = new ArrayDeque<>(); - if (time < 0L) { + if (sharedTime < 0L) { throw new IllegalArgumentException(CONFIG_PROP_TIME + " value cannot be negative"); } + if (exclusiveTime < 0L) { + throw new IllegalArgumentException(CONFIG_PROP_TIME_EXCLUSIVE + " value cannot be negative"); + } if (retry < 0L) { throw new IllegalArgumentException(CONFIG_PROP_RETRY + " value cannot be negative"); } @@ -178,8 +196,8 @@ private AdaptedLockSyncContext( } } - private long getTime(final RepositorySystemSession session) { - return ConfigUtils.getLong(session, DEFAULT_TIME, CONFIG_PROP_TIME); + private long getTime(final RepositorySystemSession session, long defaultValue, String... keys) { + return ConfigUtils.getLong(session, defaultValue, keys); } private TimeUnit getTimeUnit(final RepositorySystemSession session) { @@ -201,7 +219,7 @@ public void acquire(Collection artifacts, Collection artifacts, Collection artifacts, Collectionall (is default and is what happened before), and project when the scope of verification are project dependencies only (i.e. plugins are not verified). | `"all"` | 2.0.11 | No | Session Configuration | | `"aether.artifactResolver.postProcessor.trustedChecksums.snapshots"` | `Boolean` | Should post processor process snapshots as well? | `false` | 1.9.0 | No | Session Configuration | | `"aether.artifactResolver.simpleLrmInterop"` | `Boolean` | Configuration to enable "interoperability" with Simple LRM, but this breaks RRF feature, hence this configuration is IGNORED when RRF is used, and is warmly recommended to leave it disabled even if no RRF is being used. | `false` | | No | Session Configuration | -| `"aether.artifactResolver.snapshotNormalization"` | `Boolean` | Configuration to enable "snapshot normalization", downloaded snapshots from remote with timestamped file names will have file names converted back to baseVersion. It replaces the timestamped snapshot file name with a filename containing the SNAPSHOT qualifier only. This only affects resolving/retrieving artifacts but not uploading those. | `true` | | No | Session Configuration | +| `"aether.artifactResolver.snapshotNormalization"` | `Boolean` | Configuration to enable "snapshot normalization", downloaded snapshots from remote with timestamped file names will have file names converted back to baseVersion. It replaces the timestamped snapshot file name with a filename containing the SNAPSHOT qualifier only. This only affects resolving/retrieving artifacts but not uploading those. | `false` | | No | Session Configuration | | `"aether.chainedLocalRepository.ignoreTailAvailability"` | `Boolean` | When using chained local repository, should be the artifact availability ignored in tail. | `true` | 1.9.2 | No | Session Configuration | | `"aether.checksums.omitChecksumsForExtensions"` | `String` | Comma-separated list of extensions with leading dot (example ".asc") that should have checksums omitted. These are applied to sub-artifacts only. Note: to achieve 1.7.x aether.checksums.forSignature=true behaviour, pass empty string as value for this property. | `".asc,.sigstore,.sigstore.json"` | | No | Session Configuration | | `"aether.conflictResolver.impl"` | `String` | The name of the conflict resolver implementation to use: "path" (default) or "classic" (same as Maven 3). | `"path"` | 2.0.11 | No | Session Configuration | @@ -107,6 +107,7 @@ To modify this file, edit the template and regenerate. | `"aether.syncContext.named.basedir.locksDir"` | `String` | The location of the directory toi use for locks. If relative path, it is resolved from the local repository root. | `".locks"` | 1.9.0 | No | Session Configuration | | `"aether.syncContext.named.discriminating.discriminator"` | `String` | Configuration property to pass in discriminator, if needed. If not present, it is auto-calculated. | - | 1.7.0 | No | Session Configuration | | `"aether.syncContext.named.discriminating.hostname"` | `String` | Configuration property to pass in hostname, if needed. If not present, hostname as reported by system will be used. | - | 1.7.0 | No | Session Configuration | +| `"aether.syncContext.named.exclusiveTime"` | `Long` | The maximum of time amount to be blocked to obtain exclusive lock (keep it low). | `10l` | 2.0.14 | No | Session Configuration | | `"aether.syncContext.named.factory"` | `String` | Name of the lock factory to use in session. | `"file-lock"` | 1.9.1 | No | Session Configuration | | `"aether.syncContext.named.hashing.depth"` | `Integer` | The depth how many levels should adapter create. Acceptable values are 0-4 (inclusive). | `2` | 1.9.0 | No | Session Configuration | | `"aether.syncContext.named.nameMapper"` | `String` | Name of the name mapper to use in session. Out of the box supported ones are "static", "gav", "file-gav", "file-hgav", "file-static" and "discriminating". | `"file-gav"` | 1.9.1 | No | Session Configuration | From 0a384bdfa73d5370711de7f4fbf46766cc6596c0 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Sat, 15 Nov 2025 20:20:44 +0100 Subject: [PATCH 2/9] Swap back --- .../impl/DefaultArtifactResolver.java | 58 +++++++++++++------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultArtifactResolver.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultArtifactResolver.java index 4f10b4b5b..5ceb9c780 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultArtifactResolver.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultArtifactResolver.java @@ -33,6 +33,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; import org.eclipse.aether.ConfigurationProperties; import org.eclipse.aether.RepositoryEvent; @@ -186,35 +187,52 @@ public List resolveArtifacts( throws ArtifactResolutionException { requireNonNull(session, "session cannot be null"); requireNonNull(requests, "requests cannot be null"); - try (SyncContext shared = syncContextFactory.newInstance(session, true); - SyncContext exclusive = syncContextFactory.newInstance(session, false)) { - Collection artifacts = new ArrayList<>(requests.size()); - SystemDependencyScope systemDependencyScope = session.getSystemDependencyScope(); - for (ArtifactRequest request : requests) { - if (systemDependencyScope != null - && systemDependencyScope.getSystemPath(request.getArtifact()) != null) { - continue; - } - artifacts.add(request.getArtifact()); - } - return resolve(shared, exclusive, artifacts, session, requests); + Collection artifacts = new ArrayList<>(requests.size()); + SystemDependencyScope systemDependencyScope = session.getSystemDependencyScope(); + for (ArtifactRequest request : requests) { + if (systemDependencyScope != null && systemDependencyScope.getSystemPath(request.getArtifact()) != null) { + continue; + } + artifacts.add(request.getArtifact()); } + + return resolve( + () -> syncContextFactory.newInstance(session, true), + () -> syncContextFactory.newInstance(session, false), + artifacts, + session, + requests); } @SuppressWarnings("checkstyle:methodlength") private List resolve( - SyncContext shared, - SyncContext exclusive, + Supplier sharedSupplier, + Supplier exclusiveSupplier, Collection subjects, RepositorySystemSession session, Collection requests) throws ArtifactResolutionException { SystemDependencyScope systemDependencyScope = session.getSystemDependencyScope(); - SyncContext current = shared; + boolean firstAttempt = true; // controls eventing; must happen only once + boolean currentShared = true; + SyncContext current = sharedSupplier.get(); try { while (true) { - current.acquire(subjects, null); + try { + current.acquire(subjects, null); + } catch (SyncContext.FailedToAcquireLockException e) { + if (currentShared) { + // we have to give up; timeout on shared lock acquire + throw e; + } else { + // assume "someone else is working on this"; swap back to shared and retry + current.close(); + current = sharedSupplier.get(); + currentShared = true; + continue; + } + } boolean failures = false; final List results = new ArrayList<>(requests.size()); @@ -234,7 +252,7 @@ private List resolve( Artifact artifact = request.getArtifact(); - if (current == shared) { + if (firstAttempt) { artifactResolving(session, trace, artifact); } @@ -389,9 +407,11 @@ private List resolve( } } - if (!groups.isEmpty() && current == shared) { + if (!groups.isEmpty() && currentShared) { + firstAttempt = false; // all "resolving" events fired, no more of them current.close(); - current = exclusive; + currentShared = false; + current = exclusiveSupplier.get(); continue; } From c40107ad28704a10b8760b83cf2e7cc06644f282 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Sat, 15 Nov 2025 23:26:26 +0100 Subject: [PATCH 3/9] WIP --- .../eclipse/aether/internal/impl/DefaultArtifactResolver.java | 3 ++- .../impl/synccontext/named/NamedLockFactoryAdapter.java | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultArtifactResolver.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultArtifactResolver.java index 5ceb9c780..a3b6db741 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultArtifactResolver.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultArtifactResolver.java @@ -98,7 +98,7 @@ public class DefaultArtifactResolver implements ArtifactResolver { * Configuration to enable "snapshot normalization", downloaded snapshots from remote with timestamped file names * will have file names converted back to baseVersion. It replaces the timestamped snapshot file name with a * filename containing the SNAPSHOT qualifier only. This only affects resolving/retrieving artifacts but not - * uploading those. + * uploading those. Provides Maven 2 compatibility. * * @configurationSource {@link RepositorySystemSession#getConfigProperties()} * @configurationType {@link java.lang.Boolean} @@ -111,6 +111,7 @@ public class DefaultArtifactResolver implements ArtifactResolver { /** * Configuration to enable "interoperability" with Simple LRM, but this breaks RRF feature, hence this configuration * is IGNORED when RRF is used, and is warmly recommended to leave it disabled even if no RRF is being used. + * Provides Maven 2 compatibility. * * @configurationSource {@link RepositorySystemSession#getConfigProperties()} * @configurationType {@link java.lang.Boolean} diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapter.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapter.java index 49b5598cf..3a24e000e 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapter.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapter.java @@ -54,7 +54,7 @@ public final class NamedLockFactoryAdapter { */ public static final String CONFIG_PROP_TIME = CONFIG_PROPS_PREFIX + "time"; - public static final long DEFAULT_TIME = 30L; + public static final long DEFAULT_TIME = 300L; /** * The maximum of time amount to be blocked to obtain exclusive lock (keep it low). @@ -66,7 +66,7 @@ public final class NamedLockFactoryAdapter { */ public static final String CONFIG_PROP_TIME_EXCLUSIVE = CONFIG_PROPS_PREFIX + "exclusiveTime"; - public static final long DEFAULT_TIME_EXCLUSIVE = 10L; + public static final long DEFAULT_TIME_EXCLUSIVE = 5L; /** * The unit of maximum time amount to be blocked to obtain lock. Use TimeUnit enum names. From 096079514b08ab6d7c4398781ba42c95c17dd150 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Sun, 16 Nov 2025 00:04:36 +0100 Subject: [PATCH 4/9] WIP --- .../src/main/java/org/eclipse/aether/SyncContext.java | 4 +++- src/site/markdown/configuration.md | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/SyncContext.java b/maven-resolver-api/src/main/java/org/eclipse/aether/SyncContext.java index 5a82403ae..430973d5b 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/SyncContext.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/SyncContext.java @@ -63,12 +63,14 @@ public interface SyncContext extends Closeable { * @param metadatas The metadatas to acquire, may be {@code null} or empty if none. * @throws FailedToAcquireLockException if method calls to acquire lock within configured time. */ - void acquire(Collection artifacts, Collection metadatas); + void acquire(Collection artifacts, Collection metadatas) + throws FailedToAcquireLockException; /** * Releases all previously acquired artifacts/metadatas. If no resources have been acquired before or if this * synchronization context has already been closed, this method does nothing. */ + @Override void close(); /** diff --git a/src/site/markdown/configuration.md b/src/site/markdown/configuration.md index 67c56732b..9a88f2ae6 100644 --- a/src/site/markdown/configuration.md +++ b/src/site/markdown/configuration.md @@ -36,8 +36,8 @@ To modify this file, edit the template and regenerate. | `"aether.artifactResolver.postProcessor.trustedChecksums.record"` | `Boolean` | Should post processor go into "record" mode (and collect checksums instead of validate them)? | `false` | 1.9.0 | No | Session Configuration | | `"aether.artifactResolver.postProcessor.trustedChecksums.scope"` | `String` | The scope to apply during post-processing. Accepted values are all (is default and is what happened before), and project when the scope of verification are project dependencies only (i.e. plugins are not verified). | `"all"` | 2.0.11 | No | Session Configuration | | `"aether.artifactResolver.postProcessor.trustedChecksums.snapshots"` | `Boolean` | Should post processor process snapshots as well? | `false` | 1.9.0 | No | Session Configuration | -| `"aether.artifactResolver.simpleLrmInterop"` | `Boolean` | Configuration to enable "interoperability" with Simple LRM, but this breaks RRF feature, hence this configuration is IGNORED when RRF is used, and is warmly recommended to leave it disabled even if no RRF is being used. | `false` | | No | Session Configuration | -| `"aether.artifactResolver.snapshotNormalization"` | `Boolean` | Configuration to enable "snapshot normalization", downloaded snapshots from remote with timestamped file names will have file names converted back to baseVersion. It replaces the timestamped snapshot file name with a filename containing the SNAPSHOT qualifier only. This only affects resolving/retrieving artifacts but not uploading those. | `false` | | No | Session Configuration | +| `"aether.artifactResolver.simpleLrmInterop"` | `Boolean` | Configuration to enable "interoperability" with Simple LRM, but this breaks RRF feature, hence this configuration is IGNORED when RRF is used, and is warmly recommended to leave it disabled even if no RRF is being used. Provides Maven 2 compatibility. | `false` | | No | Session Configuration | +| `"aether.artifactResolver.snapshotNormalization"` | `Boolean` | Configuration to enable "snapshot normalization", downloaded snapshots from remote with timestamped file names will have file names converted back to baseVersion. It replaces the timestamped snapshot file name with a filename containing the SNAPSHOT qualifier only. This only affects resolving/retrieving artifacts but not uploading those. Provides Maven 2 compatibility. | `false` | | No | Session Configuration | | `"aether.chainedLocalRepository.ignoreTailAvailability"` | `Boolean` | When using chained local repository, should be the artifact availability ignored in tail. | `true` | 1.9.2 | No | Session Configuration | | `"aether.checksums.omitChecksumsForExtensions"` | `String` | Comma-separated list of extensions with leading dot (example ".asc") that should have checksums omitted. These are applied to sub-artifacts only. Note: to achieve 1.7.x aether.checksums.forSignature=true behaviour, pass empty string as value for this property. | `".asc,.sigstore,.sigstore.json"` | | No | Session Configuration | | `"aether.conflictResolver.impl"` | `String` | The name of the conflict resolver implementation to use: "path" (default) or "classic" (same as Maven 3). | `"path"` | 2.0.11 | No | Session Configuration | @@ -107,7 +107,7 @@ To modify this file, edit the template and regenerate. | `"aether.syncContext.named.basedir.locksDir"` | `String` | The location of the directory toi use for locks. If relative path, it is resolved from the local repository root. | `".locks"` | 1.9.0 | No | Session Configuration | | `"aether.syncContext.named.discriminating.discriminator"` | `String` | Configuration property to pass in discriminator, if needed. If not present, it is auto-calculated. | - | 1.7.0 | No | Session Configuration | | `"aether.syncContext.named.discriminating.hostname"` | `String` | Configuration property to pass in hostname, if needed. If not present, hostname as reported by system will be used. | - | 1.7.0 | No | Session Configuration | -| `"aether.syncContext.named.exclusiveTime"` | `Long` | The maximum of time amount to be blocked to obtain exclusive lock (keep it low). | `10l` | 2.0.14 | No | Session Configuration | +| `"aether.syncContext.named.exclusiveTime"` | `Long` | The maximum of time amount to be blocked to obtain exclusive lock (keep it low). | `5l` | 2.0.14 | No | Session Configuration | | `"aether.syncContext.named.factory"` | `String` | Name of the lock factory to use in session. | `"file-lock"` | 1.9.1 | No | Session Configuration | | `"aether.syncContext.named.hashing.depth"` | `Integer` | The depth how many levels should adapter create. Acceptable values are 0-4 (inclusive). | `2` | 1.9.0 | No | Session Configuration | | `"aether.syncContext.named.nameMapper"` | `String` | Name of the name mapper to use in session. Out of the box supported ones are "static", "gav", "file-gav", "file-hgav", "file-static" and "discriminating". | `"file-gav"` | 1.9.1 | No | Session Configuration | @@ -115,7 +115,7 @@ To modify this file, edit the template and regenerate. | `"aether.syncContext.named.redisson.configFile"` | `String` | Path to a Redisson configuration file in YAML format. Read official documentation for details. | - | 1.7.0 | No | Java System Properties | | `"aether.syncContext.named.retry"` | `Integer` | The amount of retries on time-out. | `1` | 1.7.0 | No | Session Configuration | | `"aether.syncContext.named.retry.wait"` | `Long` | The amount of milliseconds to wait between retries on time-out. | `200l` | 1.7.0 | No | Session Configuration | -| `"aether.syncContext.named.time"` | `Long` | The maximum of time amount to be blocked to obtain lock. | `30l` | 1.7.0 | No | Session Configuration | +| `"aether.syncContext.named.time"` | `Long` | The maximum of time amount to be blocked to obtain lock. | `300l` | 1.7.0 | No | Session Configuration | | `"aether.syncContext.named.time.unit"` | `String` | The unit of maximum time amount to be blocked to obtain lock. Use TimeUnit enum names. | `"SECONDS"` | 1.7.0 | No | Session Configuration | | `"aether.system.dependencyVisitor"` | `String` | A flag indicating which visitor should be used to "flatten" the dependency graph into list. In Maven 4 the default is new "levelOrder", while Maven 3 used "preOrder". This property accepts values "preOrder", "postOrder" and "levelOrder". | `"levelOrder"` | 2.0.0 | No | Session Configuration | | `"aether.transport.apache.followRedirects"` | `Boolean` | If enabled, Apache HttpClient will follow HTTP redirects. | `true` | 2.0.2 | Yes | Session Configuration | From beb2675f32a569273bdf818b9242f0a896078547 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Sun, 16 Nov 2025 00:09:56 +0100 Subject: [PATCH 5/9] WIP --- .../named/NamedLockFactoryAdapter.java | 30 ++++--------------- src/site/markdown/configuration.md | 1 - 2 files changed, 6 insertions(+), 25 deletions(-) diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapter.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapter.java index 3a24e000e..f4d15c750 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapter.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapter.java @@ -56,18 +56,6 @@ public final class NamedLockFactoryAdapter { public static final long DEFAULT_TIME = 300L; - /** - * The maximum of time amount to be blocked to obtain exclusive lock (keep it low). - * - * @since 2.0.14 - * @configurationSource {@link RepositorySystemSession#getConfigProperties()} - * @configurationType {@link java.lang.Long} - * @configurationDefaultValue {@link #DEFAULT_TIME_EXCLUSIVE} - */ - public static final String CONFIG_PROP_TIME_EXCLUSIVE = CONFIG_PROPS_PREFIX + "exclusiveTime"; - - public static final long DEFAULT_TIME_EXCLUSIVE = 5L; - /** * The unit of maximum time amount to be blocked to obtain lock. Use TimeUnit enum names. * @@ -154,9 +142,7 @@ private static class AdaptedLockSyncContext implements SyncContext { private final NamedLockFactory namedLockFactory; - private final long sharedTime; - - private final long exclusiveTime; + private final long time; private final TimeUnit timeUnit; @@ -175,19 +161,15 @@ private AdaptedLockSyncContext( this.shared = shared; this.lockNaming = lockNaming; this.namedLockFactory = namedLockFactory; - this.sharedTime = getTime(session, DEFAULT_TIME, CONFIG_PROP_TIME); - this.exclusiveTime = getTime(session, DEFAULT_TIME_EXCLUSIVE, CONFIG_PROP_TIME_EXCLUSIVE, CONFIG_PROP_TIME); + this.time = getTime(session, DEFAULT_TIME, CONFIG_PROP_TIME); this.timeUnit = getTimeUnit(session); this.retry = getRetry(session); this.retryWait = getRetryWait(session); this.locks = new ArrayDeque<>(); - if (sharedTime < 0L) { + if (time < 0L) { throw new IllegalArgumentException(CONFIG_PROP_TIME + " value cannot be negative"); } - if (exclusiveTime < 0L) { - throw new IllegalArgumentException(CONFIG_PROP_TIME_EXCLUSIVE + " value cannot be negative"); - } if (retry < 0L) { throw new IllegalArgumentException(CONFIG_PROP_RETRY + " value cannot be negative"); } @@ -219,7 +201,7 @@ public void acquire(Collection artifacts, Collection artifacts, Collection Date: Sun, 16 Nov 2025 00:25:33 +0100 Subject: [PATCH 6/9] WIP --- .../impl/synccontext/named/NamedLockFactoryAdapter.java | 2 +- src/site/markdown/configuration.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapter.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapter.java index f4d15c750..aada3af68 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapter.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapter.java @@ -54,7 +54,7 @@ public final class NamedLockFactoryAdapter { */ public static final String CONFIG_PROP_TIME = CONFIG_PROPS_PREFIX + "time"; - public static final long DEFAULT_TIME = 300L; + public static final long DEFAULT_TIME = 900L; /** * The unit of maximum time amount to be blocked to obtain lock. Use TimeUnit enum names. diff --git a/src/site/markdown/configuration.md b/src/site/markdown/configuration.md index 4af6a0997..03fe77681 100644 --- a/src/site/markdown/configuration.md +++ b/src/site/markdown/configuration.md @@ -114,7 +114,7 @@ To modify this file, edit the template and regenerate. | `"aether.syncContext.named.redisson.configFile"` | `String` | Path to a Redisson configuration file in YAML format. Read official documentation for details. | - | 1.7.0 | No | Java System Properties | | `"aether.syncContext.named.retry"` | `Integer` | The amount of retries on time-out. | `1` | 1.7.0 | No | Session Configuration | | `"aether.syncContext.named.retry.wait"` | `Long` | The amount of milliseconds to wait between retries on time-out. | `200l` | 1.7.0 | No | Session Configuration | -| `"aether.syncContext.named.time"` | `Long` | The maximum of time amount to be blocked to obtain lock. | `300l` | 1.7.0 | No | Session Configuration | +| `"aether.syncContext.named.time"` | `Long` | The maximum of time amount to be blocked to obtain lock. | `900l` | 1.7.0 | No | Session Configuration | | `"aether.syncContext.named.time.unit"` | `String` | The unit of maximum time amount to be blocked to obtain lock. Use TimeUnit enum names. | `"SECONDS"` | 1.7.0 | No | Session Configuration | | `"aether.system.dependencyVisitor"` | `String` | A flag indicating which visitor should be used to "flatten" the dependency graph into list. In Maven 4 the default is new "levelOrder", while Maven 3 used "preOrder". This property accepts values "preOrder", "postOrder" and "levelOrder". | `"levelOrder"` | 2.0.0 | No | Session Configuration | | `"aether.transport.apache.followRedirects"` | `Boolean` | If enabled, Apache HttpClient will follow HTTP redirects. | `true` | 2.0.2 | Yes | Session Configuration | From 2eacf581cd6b3ba3401c981d05af77a579e9065e Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Mon, 17 Nov 2025 13:42:32 +0100 Subject: [PATCH 7/9] Improvements --- .../java/org/eclipse/aether/SyncContext.java | 2 +- .../named/NamedLockFactoryAdapter.java | 46 +++++++++++++++++-- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/SyncContext.java b/maven-resolver-api/src/main/java/org/eclipse/aether/SyncContext.java index 430973d5b..d9d0bfd45 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/SyncContext.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/SyncContext.java @@ -76,7 +76,7 @@ void acquire(Collection artifacts, Collection artifacts, Collection artifacts, Collection metadatas) { + StringBuilder builder = new StringBuilder(); + if (artifacts != null && !artifacts.isEmpty()) { + builder.append("artifacts: ") + .append(artifacts.stream().map(ArtifactIdUtils::toId).collect(Collectors.joining(", "))); + } + if (metadatas != null && !metadatas.isEmpty()) { + if (builder.length() != 0) { + builder.append("; "); + } + builder.append("metadata: ") + .append(metadatas.stream().map(this::metadataSubjects).collect(Collectors.joining(", "))); + } + return builder.toString(); + } + + private String metadataSubjects(Metadata metadata) { + String name = ""; + if (!metadata.getGroupId().isEmpty()) { + name += metadata.getGroupId(); + if (!metadata.getArtifactId().isEmpty()) { + name += ":" + metadata.getArtifactId(); + if (!metadata.getVersion().isEmpty()) { + name += ":" + metadata.getVersion(); + } + } + } + if (!metadata.getType().isEmpty()) { + name += (name.isEmpty() ? "" : ":") + metadata.getType(); + } + return name; + } + @Override public void close() { while (!locks.isEmpty()) { From 16d8971049125ec91ecbb24e7f13301bd0a17bd1 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Mon, 17 Nov 2025 13:45:53 +0100 Subject: [PATCH 8/9] Undo unrelated --- .../impl/DefaultArtifactResolver.java | 63 +++++++------------ 1 file changed, 21 insertions(+), 42 deletions(-) diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultArtifactResolver.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultArtifactResolver.java index a3b6db741..6c614d923 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultArtifactResolver.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultArtifactResolver.java @@ -33,7 +33,6 @@ import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Supplier; import org.eclipse.aether.ConfigurationProperties; import org.eclipse.aether.RepositoryEvent; @@ -98,7 +97,7 @@ public class DefaultArtifactResolver implements ArtifactResolver { * Configuration to enable "snapshot normalization", downloaded snapshots from remote with timestamped file names * will have file names converted back to baseVersion. It replaces the timestamped snapshot file name with a * filename containing the SNAPSHOT qualifier only. This only affects resolving/retrieving artifacts but not - * uploading those. Provides Maven 2 compatibility. + * uploading those. * * @configurationSource {@link RepositorySystemSession#getConfigProperties()} * @configurationType {@link java.lang.Boolean} @@ -106,12 +105,11 @@ public class DefaultArtifactResolver implements ArtifactResolver { */ public static final String CONFIG_PROP_SNAPSHOT_NORMALIZATION = CONFIG_PROPS_PREFIX + "snapshotNormalization"; - public static final boolean DEFAULT_SNAPSHOT_NORMALIZATION = false; + public static final boolean DEFAULT_SNAPSHOT_NORMALIZATION = true; /** * Configuration to enable "interoperability" with Simple LRM, but this breaks RRF feature, hence this configuration * is IGNORED when RRF is used, and is warmly recommended to leave it disabled even if no RRF is being used. - * Provides Maven 2 compatibility. * * @configurationSource {@link RepositorySystemSession#getConfigProperties()} * @configurationType {@link java.lang.Boolean} @@ -188,52 +186,35 @@ public List resolveArtifacts( throws ArtifactResolutionException { requireNonNull(session, "session cannot be null"); requireNonNull(requests, "requests cannot be null"); - - Collection artifacts = new ArrayList<>(requests.size()); - SystemDependencyScope systemDependencyScope = session.getSystemDependencyScope(); - for (ArtifactRequest request : requests) { - if (systemDependencyScope != null && systemDependencyScope.getSystemPath(request.getArtifact()) != null) { - continue; + try (SyncContext shared = syncContextFactory.newInstance(session, true); + SyncContext exclusive = syncContextFactory.newInstance(session, false)) { + Collection artifacts = new ArrayList<>(requests.size()); + SystemDependencyScope systemDependencyScope = session.getSystemDependencyScope(); + for (ArtifactRequest request : requests) { + if (systemDependencyScope != null + && systemDependencyScope.getSystemPath(request.getArtifact()) != null) { + continue; + } + artifacts.add(request.getArtifact()); } - artifacts.add(request.getArtifact()); - } - return resolve( - () -> syncContextFactory.newInstance(session, true), - () -> syncContextFactory.newInstance(session, false), - artifacts, - session, - requests); + return resolve(shared, exclusive, artifacts, session, requests); + } } @SuppressWarnings("checkstyle:methodlength") private List resolve( - Supplier sharedSupplier, - Supplier exclusiveSupplier, + SyncContext shared, + SyncContext exclusive, Collection subjects, RepositorySystemSession session, Collection requests) throws ArtifactResolutionException { SystemDependencyScope systemDependencyScope = session.getSystemDependencyScope(); - boolean firstAttempt = true; // controls eventing; must happen only once - boolean currentShared = true; - SyncContext current = sharedSupplier.get(); + SyncContext current = shared; try { while (true) { - try { - current.acquire(subjects, null); - } catch (SyncContext.FailedToAcquireLockException e) { - if (currentShared) { - // we have to give up; timeout on shared lock acquire - throw e; - } else { - // assume "someone else is working on this"; swap back to shared and retry - current.close(); - current = sharedSupplier.get(); - currentShared = true; - continue; - } - } + current.acquire(subjects, null); boolean failures = false; final List results = new ArrayList<>(requests.size()); @@ -253,7 +234,7 @@ private List resolve( Artifact artifact = request.getArtifact(); - if (firstAttempt) { + if (current == shared) { artifactResolving(session, trace, artifact); } @@ -408,11 +389,9 @@ private List resolve( } } - if (!groups.isEmpty() && currentShared) { - firstAttempt = false; // all "resolving" events fired, no more of them + if (!groups.isEmpty() && current == shared) { current.close(); - currentShared = false; - current = exclusiveSupplier.get(); + current = exclusive; continue; } From ec2a7edd55043586504577f8d5f37004f3128550 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Mon, 17 Nov 2025 13:47:39 +0100 Subject: [PATCH 9/9] Update generated doco --- src/site/markdown/configuration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/site/markdown/configuration.md b/src/site/markdown/configuration.md index 03fe77681..65ed9e0a9 100644 --- a/src/site/markdown/configuration.md +++ b/src/site/markdown/configuration.md @@ -36,8 +36,8 @@ To modify this file, edit the template and regenerate. | `"aether.artifactResolver.postProcessor.trustedChecksums.record"` | `Boolean` | Should post processor go into "record" mode (and collect checksums instead of validate them)? | `false` | 1.9.0 | No | Session Configuration | | `"aether.artifactResolver.postProcessor.trustedChecksums.scope"` | `String` | The scope to apply during post-processing. Accepted values are all (is default and is what happened before), and project when the scope of verification are project dependencies only (i.e. plugins are not verified). | `"all"` | 2.0.11 | No | Session Configuration | | `"aether.artifactResolver.postProcessor.trustedChecksums.snapshots"` | `Boolean` | Should post processor process snapshots as well? | `false` | 1.9.0 | No | Session Configuration | -| `"aether.artifactResolver.simpleLrmInterop"` | `Boolean` | Configuration to enable "interoperability" with Simple LRM, but this breaks RRF feature, hence this configuration is IGNORED when RRF is used, and is warmly recommended to leave it disabled even if no RRF is being used. Provides Maven 2 compatibility. | `false` | | No | Session Configuration | -| `"aether.artifactResolver.snapshotNormalization"` | `Boolean` | Configuration to enable "snapshot normalization", downloaded snapshots from remote with timestamped file names will have file names converted back to baseVersion. It replaces the timestamped snapshot file name with a filename containing the SNAPSHOT qualifier only. This only affects resolving/retrieving artifacts but not uploading those. Provides Maven 2 compatibility. | `false` | | No | Session Configuration | +| `"aether.artifactResolver.simpleLrmInterop"` | `Boolean` | Configuration to enable "interoperability" with Simple LRM, but this breaks RRF feature, hence this configuration is IGNORED when RRF is used, and is warmly recommended to leave it disabled even if no RRF is being used. | `false` | | No | Session Configuration | +| `"aether.artifactResolver.snapshotNormalization"` | `Boolean` | Configuration to enable "snapshot normalization", downloaded snapshots from remote with timestamped file names will have file names converted back to baseVersion. It replaces the timestamped snapshot file name with a filename containing the SNAPSHOT qualifier only. This only affects resolving/retrieving artifacts but not uploading those. | `true` | | No | Session Configuration | | `"aether.chainedLocalRepository.ignoreTailAvailability"` | `Boolean` | When using chained local repository, should be the artifact availability ignored in tail. | `true` | 1.9.2 | No | Session Configuration | | `"aether.checksums.omitChecksumsForExtensions"` | `String` | Comma-separated list of extensions with leading dot (example ".asc") that should have checksums omitted. These are applied to sub-artifacts only. Note: to achieve 1.7.x aether.checksums.forSignature=true behaviour, pass empty string as value for this property. | `".asc,.sigstore,.sigstore.json"` | | No | Session Configuration | | `"aether.conflictResolver.impl"` | `String` | The name of the conflict resolver implementation to use: "path" (default) or "classic" (same as Maven 3). | `"path"` | 2.0.11 | No | Session Configuration |