1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.aether.internal.impl.synccontext.named;
20
21 import java.util.ArrayDeque;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Deque;
25 import java.util.concurrent.TimeUnit;
26 import java.util.stream.Collectors;
27
28 import org.eclipse.aether.RepositorySystemSession;
29 import org.eclipse.aether.SyncContext;
30 import org.eclipse.aether.artifact.Artifact;
31 import org.eclipse.aether.metadata.Metadata;
32 import org.eclipse.aether.named.NamedLock;
33 import org.eclipse.aether.named.NamedLockFactory;
34 import org.eclipse.aether.named.providers.FileLockNamedLockFactory;
35 import org.eclipse.aether.util.ConfigUtils;
36 import org.eclipse.aether.util.artifact.ArtifactIdUtils;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 import static java.util.Objects.requireNonNull;
41
42
43
44
45 public final class NamedLockFactoryAdapter {
46 public static final String TIME_KEY = "aether.syncContext.named.time";
47
48 public static final long DEFAULT_TIME = 900L;
49
50 public static final String TIME_UNIT_KEY = "aether.syncContext.named.time.unit";
51
52 public static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.SECONDS;
53
54 public static final String RETRY_KEY = "aether.syncContext.named.retry";
55
56 public static final int DEFAULT_RETRY = 1;
57
58 public static final String RETRY_WAIT_KEY = "aether.syncContext.named.retry.wait";
59
60 public static final long DEFAULT_RETRY_WAIT = 200L;
61
62 private final NameMapper nameMapper;
63
64 private final NamedLockFactory namedLockFactory;
65
66 public NamedLockFactoryAdapter(final NameMapper nameMapper, final NamedLockFactory namedLockFactory) {
67 this.nameMapper = requireNonNull(nameMapper);
68 this.namedLockFactory = requireNonNull(namedLockFactory);
69
70 if (this.namedLockFactory instanceof FileLockNamedLockFactory && !this.nameMapper.isFileSystemFriendly()) {
71 throw new IllegalArgumentException(
72 "Misconfiguration: FileLockNamedLockFactory lock factory requires FS friendly NameMapper");
73 }
74 }
75
76 public SyncContext newInstance(final RepositorySystemSession session, final boolean shared) {
77 return new AdaptedLockSyncContext(session, shared, nameMapper, namedLockFactory);
78 }
79
80
81
82
83 public NameMapper getNameMapper() {
84 return nameMapper;
85 }
86
87
88
89
90 public NamedLockFactory getNamedLockFactory() {
91 return namedLockFactory;
92 }
93
94 public String toString() {
95 return getClass().getSimpleName()
96 + "(nameMapper=" + nameMapper
97 + ", namedLockFactory=" + namedLockFactory
98 + ")";
99 }
100
101 private static class AdaptedLockSyncContext implements SyncContext {
102 private static final Logger LOGGER = LoggerFactory.getLogger(AdaptedLockSyncContext.class);
103
104 private final RepositorySystemSession session;
105
106 private final boolean shared;
107
108 private final NameMapper lockNaming;
109
110 private final NamedLockFactory namedLockFactory;
111
112 private final long time;
113
114 private final TimeUnit timeUnit;
115
116 private final int retry;
117
118 private final long retryWait;
119
120 private final Deque<NamedLock> locks;
121
122 private AdaptedLockSyncContext(
123 final RepositorySystemSession session,
124 final boolean shared,
125 final NameMapper lockNaming,
126 final NamedLockFactory namedLockFactory) {
127 this.session = session;
128 this.shared = shared;
129 this.lockNaming = lockNaming;
130 this.namedLockFactory = namedLockFactory;
131 this.time = getTime(session);
132 this.timeUnit = getTimeUnit(session);
133 this.retry = getRetry(session);
134 this.retryWait = getRetryWait(session);
135 this.locks = new ArrayDeque<>();
136
137 if (time < 0L) {
138 throw new IllegalArgumentException(TIME_KEY + " value cannot be negative");
139 }
140 if (retry < 0L) {
141 throw new IllegalArgumentException(RETRY_KEY + " value cannot be negative");
142 }
143 if (retryWait < 0L) {
144 throw new IllegalArgumentException(RETRY_WAIT_KEY + " value cannot be negative");
145 }
146 }
147
148 private long getTime(final RepositorySystemSession session) {
149 return ConfigUtils.getLong(session, DEFAULT_TIME, TIME_KEY);
150 }
151
152 private TimeUnit getTimeUnit(final RepositorySystemSession session) {
153 return TimeUnit.valueOf(ConfigUtils.getString(session, DEFAULT_TIME_UNIT.name(), TIME_UNIT_KEY));
154 }
155
156 private int getRetry(final RepositorySystemSession session) {
157 return ConfigUtils.getInteger(session, DEFAULT_RETRY, RETRY_KEY);
158 }
159
160 private long getRetryWait(final RepositorySystemSession session) {
161 return ConfigUtils.getLong(session, DEFAULT_RETRY_WAIT, RETRY_WAIT_KEY);
162 }
163
164 @Override
165 public void acquire(Collection<? extends Artifact> artifacts, Collection<? extends Metadata> metadatas) {
166 Collection<String> keys = lockNaming.nameLocks(session, artifacts, metadatas);
167 if (keys.isEmpty()) {
168 return;
169 }
170
171 final int attempts = retry + 1;
172 final ArrayList<IllegalStateException> illegalStateExceptions = new ArrayList<>();
173 for (int attempt = 1; attempt <= attempts; attempt++) {
174 LOGGER.trace(
175 "Attempt {}: Need {} {} lock(s) for {}", attempt, keys.size(), shared ? "read" : "write", keys);
176 int acquiredLockCount = 0;
177 try {
178 if (attempt > 1) {
179 Thread.sleep(retryWait);
180 }
181 for (String key : keys) {
182 NamedLock namedLock = namedLockFactory.getLock(key);
183 LOGGER.trace("Acquiring {} lock for '{}'", shared ? "read" : "write", key);
184
185 boolean locked;
186 if (shared) {
187 locked = namedLock.lockShared(time, timeUnit);
188 } else {
189 locked = namedLock.lockExclusively(time, timeUnit);
190 }
191
192 if (!locked) {
193 String timeStr = time + " " + timeUnit;
194 LOGGER.trace(
195 "Failed to acquire {} lock for '{}' in {}",
196 shared ? "read" : "write",
197 key,
198 timeStr);
199
200 namedLock.close();
201 closeAll();
202 illegalStateExceptions.add(new IllegalStateException(
203 "Attempt " + attempt + ": Could not acquire " + (shared ? "read" : "write")
204 + " lock for '" + namedLock.name() + "' in " + timeStr));
205 break;
206 } else {
207 locks.push(namedLock);
208 acquiredLockCount++;
209 }
210 }
211 } catch (InterruptedException e) {
212 Thread.currentThread().interrupt();
213 throw new RuntimeException(e);
214 }
215 LOGGER.trace("Attempt {}: Total locks acquired: {}", attempt, acquiredLockCount);
216 if (acquiredLockCount == keys.size()) {
217 break;
218 }
219 }
220 if (!illegalStateExceptions.isEmpty()) {
221 String message = "Could not acquire " + (shared ? "shared" : "exclusive") + " lock for "
222 + lockSubjects(artifacts, metadatas) + " in " + time + " " + timeUnit
223 + "; consider using '" + TIME_KEY
224 + "' property to increase lock timeout to a value that fits your environment";
225 FailedToAcquireLockException ex = new FailedToAcquireLockException(shared, message);
226 illegalStateExceptions.forEach(ex::addSuppressed);
227 throw namedLockFactory.onFailure(ex);
228 }
229 }
230
231 private String lockSubjects(
232 Collection<? extends Artifact> artifacts, Collection<? extends Metadata> metadatas) {
233 StringBuilder builder = new StringBuilder();
234 if (artifacts != null && !artifacts.isEmpty()) {
235 builder.append("artifacts: ")
236 .append(artifacts.stream().map(ArtifactIdUtils::toId).collect(Collectors.joining(", ")));
237 }
238 if (metadatas != null && !metadatas.isEmpty()) {
239 if (builder.length() != 0) {
240 builder.append("; ");
241 }
242 builder.append("metadata: ")
243 .append(metadatas.stream().map(this::metadataSubjects).collect(Collectors.joining(", ")));
244 }
245 return builder.toString();
246 }
247
248 private String metadataSubjects(Metadata metadata) {
249 String name = "";
250 if (!metadata.getGroupId().isEmpty()) {
251 name += metadata.getGroupId();
252 if (!metadata.getArtifactId().isEmpty()) {
253 name += ":" + metadata.getArtifactId();
254 if (!metadata.getVersion().isEmpty()) {
255 name += ":" + metadata.getVersion();
256 }
257 }
258 }
259 if (!metadata.getType().isEmpty()) {
260 name += (name.isEmpty() ? "" : ":") + metadata.getType();
261 }
262 return name;
263 }
264
265 private void closeAll() {
266 if (locks.isEmpty()) {
267 return;
268 }
269
270
271 int released = 0;
272 while (!locks.isEmpty()) {
273 try (NamedLock namedLock = locks.pop()) {
274 LOGGER.trace("Releasing {} lock for '{}'", shared ? "read" : "write", namedLock.name());
275 namedLock.unlock();
276 released++;
277 }
278 }
279 LOGGER.trace("Total locks released: {}", released);
280 }
281
282 @Override
283 public void close() {
284 closeAll();
285 }
286 }
287 }