Skip to content

Commit 5bca4bb

Browse files
authored
Fix locking issues (#1660)
Changes: * introduce dedicated ex on timeout * up default timeouts (from 30s to 900s) * improve message by listing all lock subjects * improve message by mentioning the property that user should use to increase timeouts (there is no one size fits all; we could go with "infinite" timeouts. but am unsure about that)
1 parent 170b043 commit 5bca4bb

File tree

3 files changed

+74
-9
lines changed

3 files changed

+74
-9
lines changed

maven-resolver-api/src/main/java/org/eclipse/aether/SyncContext.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,39 @@ public interface SyncContext extends Closeable {
6161
*
6262
* @param artifacts The artifacts to acquire, may be {@code null} or empty if none.
6363
* @param metadatas The metadatas to acquire, may be {@code null} or empty if none.
64+
* @throws FailedToAcquireLockException if method calls to acquire lock within configured time.
6465
*/
65-
void acquire(Collection<? extends Artifact> artifacts, Collection<? extends Metadata> metadatas);
66+
void acquire(Collection<? extends Artifact> artifacts, Collection<? extends Metadata> metadatas)
67+
throws FailedToAcquireLockException;
6668

6769
/**
6870
* Releases all previously acquired artifacts/metadatas. If no resources have been acquired before or if this
6971
* synchronization context has already been closed, this method does nothing.
7072
*/
73+
@Override
7174
void close();
75+
76+
/**
77+
* Specific exception thrown by {@link #acquire(Collection, Collection)} method when it cannot acquire the lock.
78+
*
79+
* @since 1.9.25
80+
*/
81+
final class FailedToAcquireLockException extends IllegalStateException {
82+
private final boolean shared;
83+
84+
/**
85+
* Constructor.
86+
*/
87+
public FailedToAcquireLockException(boolean shared, String message) {
88+
super(message);
89+
this.shared = shared;
90+
}
91+
92+
/**
93+
* Returns {@code true} for shared and {@code false} for exclusive sync contexts.
94+
*/
95+
public boolean isShared() {
96+
return shared;
97+
}
98+
}
7299
}

maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapter.java

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.Collection;
2323
import java.util.Deque;
2424
import java.util.concurrent.TimeUnit;
25+
import java.util.stream.Collectors;
2526

2627
import org.eclipse.aether.ConfigurationProperties;
2728
import org.eclipse.aether.RepositorySystemSession;
@@ -33,6 +34,7 @@
3334
import org.eclipse.aether.named.NamedLockKey;
3435
import org.eclipse.aether.named.providers.FileLockNamedLockFactory;
3536
import org.eclipse.aether.util.ConfigUtils;
37+
import org.eclipse.aether.util.artifact.ArtifactIdUtils;
3638
import org.slf4j.Logger;
3739
import org.slf4j.LoggerFactory;
3840

@@ -54,7 +56,7 @@ public final class NamedLockFactoryAdapter {
5456
*/
5557
public static final String CONFIG_PROP_TIME = CONFIG_PROPS_PREFIX + "time";
5658

57-
public static final long DEFAULT_TIME = 30L;
59+
public static final long DEFAULT_TIME = 900L;
5860

5961
/**
6062
* The unit of maximum time amount to be blocked to obtain lock. Use TimeUnit enum names.
@@ -161,7 +163,7 @@ private AdaptedLockSyncContext(
161163
this.shared = shared;
162164
this.lockNaming = lockNaming;
163165
this.namedLockFactory = namedLockFactory;
164-
this.time = getTime(session);
166+
this.time = getTime(session, DEFAULT_TIME, CONFIG_PROP_TIME);
165167
this.timeUnit = getTimeUnit(session);
166168
this.retry = getRetry(session);
167169
this.retryWait = getRetryWait(session);
@@ -178,8 +180,8 @@ private AdaptedLockSyncContext(
178180
}
179181
}
180182

181-
private long getTime(final RepositorySystemSession session) {
182-
return ConfigUtils.getLong(session, DEFAULT_TIME, CONFIG_PROP_TIME);
183+
private long getTime(final RepositorySystemSession session, long defaultValue, String... keys) {
184+
return ConfigUtils.getLong(session, defaultValue, keys);
183185
}
184186

185187
private TimeUnit getTimeUnit(final RepositorySystemSession session) {
@@ -255,12 +257,48 @@ public void acquire(Collection<? extends Artifact> artifacts, Collection<? exten
255257
}
256258
// if we are here, means all attempts were unsuccessful: fail
257259
close();
258-
IllegalStateException ex = new IllegalStateException("Could not acquire " + lockKind + " lock for "
259-
+ namedLock.key().resources() + " using lock "
260-
+ namedLock.key().name() + " in " + timeStr);
260+
String message = "Could not acquire " + lockKind + " lock for "
261+
+ lockSubjects(artifacts, metadatas) + " in " + timeStr
262+
+ "; consider using '" + CONFIG_PROP_TIME
263+
+ "' property to increase lock timeout to a value that fits your environment";
264+
FailedToAcquireLockException ex = new FailedToAcquireLockException(shared, message);
261265
throw namedLockFactory.onFailure(ex);
262266
}
263267

268+
private String lockSubjects(
269+
Collection<? extends Artifact> artifacts, Collection<? extends Metadata> metadatas) {
270+
StringBuilder builder = new StringBuilder();
271+
if (artifacts != null && !artifacts.isEmpty()) {
272+
builder.append("artifacts: ")
273+
.append(artifacts.stream().map(ArtifactIdUtils::toId).collect(Collectors.joining(", ")));
274+
}
275+
if (metadatas != null && !metadatas.isEmpty()) {
276+
if (builder.length() != 0) {
277+
builder.append("; ");
278+
}
279+
builder.append("metadata: ")
280+
.append(metadatas.stream().map(this::metadataSubjects).collect(Collectors.joining(", ")));
281+
}
282+
return builder.toString();
283+
}
284+
285+
private String metadataSubjects(Metadata metadata) {
286+
String name = "";
287+
if (!metadata.getGroupId().isEmpty()) {
288+
name += metadata.getGroupId();
289+
if (!metadata.getArtifactId().isEmpty()) {
290+
name += ":" + metadata.getArtifactId();
291+
if (!metadata.getVersion().isEmpty()) {
292+
name += ":" + metadata.getVersion();
293+
}
294+
}
295+
}
296+
if (!metadata.getType().isEmpty()) {
297+
name += (name.isEmpty() ? "" : ":") + metadata.getType();
298+
}
299+
return name;
300+
}
301+
264302
@Override
265303
public void close() {
266304
while (!locks.isEmpty()) {

src/site/markdown/configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ To modify this file, edit the template and regenerate.
118118
| `"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 |
119119
| `"aether.syncContext.named.retry"` | `Integer` | The amount of retries on time-out. | `1` | 1.7.0 | No | Session Configuration |
120120
| `"aether.syncContext.named.retry.wait"` | `Long` | The amount of milliseconds to wait between retries on time-out. | `200l` | 1.7.0 | No | Session Configuration |
121-
| `"aether.syncContext.named.time"` | `Long` | The maximum of time amount to be blocked to obtain lock. | `30l` | 1.7.0 | No | Session Configuration |
121+
| `"aether.syncContext.named.time"` | `Long` | The maximum of time amount to be blocked to obtain lock. | `900l` | 1.7.0 | No | Session Configuration |
122122
| `"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 |
123123
| `"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 |
124124
| `"aether.transport.apache.followRedirects"` | `Boolean` | If enabled, Apache HttpClient will follow HTTP redirects. | `true` | 2.0.2 | Yes | Session Configuration |

0 commit comments

Comments
 (0)