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.Collection;
23 import java.util.Deque;
24 import java.util.concurrent.TimeUnit;
25 import java.util.concurrent.atomic.AtomicBoolean;
26 import java.util.stream.Collectors;
27
28 import org.eclipse.aether.ConfigurationProperties;
29 import org.eclipse.aether.RepositorySystemSession;
30 import org.eclipse.aether.SyncContext;
31 import org.eclipse.aether.artifact.Artifact;
32 import org.eclipse.aether.internal.impl.named.DefaultNamedLockFactorySelector;
33 import org.eclipse.aether.metadata.Metadata;
34 import org.eclipse.aether.named.NamedLock;
35 import org.eclipse.aether.named.NamedLockFactory;
36 import org.eclipse.aether.named.NamedLockKey;
37 import org.eclipse.aether.named.providers.FileLockNamedLockFactory;
38 import org.eclipse.aether.util.ConfigUtils;
39 import org.eclipse.aether.util.artifact.ArtifactIdUtils;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 import static java.util.Objects.requireNonNull;
44
45
46
47
48 public final class NamedLockFactoryAdapter {
49 public static final String CONFIG_PROPS_PREFIX = ConfigurationProperties.PREFIX_SYNC_CONTEXT + "named.";
50
51
52
53
54
55
56
57
58
59
60 @Deprecated
61 public static final String CONFIG_PROP_TIME = CONFIG_PROPS_PREFIX + "time";
62
63 @Deprecated
64 public static final long DEFAULT_TIME = DefaultNamedLockFactorySelector.DEFAULT_LOCK_WAIT_TIME;
65
66
67
68
69
70
71
72
73
74
75 @Deprecated
76 public static final String CONFIG_PROP_TIME_UNIT = CONFIG_PROPS_PREFIX + "time.unit";
77
78 @Deprecated
79 public static final String DEFAULT_TIME_UNIT = DefaultNamedLockFactorySelector.DEFAULT_LOCK_WAIT_TIME_UNIT;
80
81
82
83
84
85
86
87
88
89 public static final String CONFIG_PROP_RETRY = CONFIG_PROPS_PREFIX + "retry";
90
91 public static final int DEFAULT_RETRY = 1;
92
93
94
95
96
97
98
99
100
101 public static final String CONFIG_PROP_RETRY_WAIT = CONFIG_PROPS_PREFIX + "retry.wait";
102
103 public static final long DEFAULT_RETRY_WAIT = 200L;
104
105 private final NameMapper nameMapper;
106
107 private final NamedLockFactory namedLockFactory;
108
109 private final long lockWait;
110
111 private final TimeUnit lockWaitUnit;
112
113 public NamedLockFactoryAdapter(
114 final NameMapper nameMapper,
115 final NamedLockFactory namedLockFactory,
116 long lockWait,
117 TimeUnit lockWaitUnit) {
118 this.nameMapper = requireNonNull(nameMapper);
119 this.namedLockFactory = requireNonNull(namedLockFactory);
120 this.lockWait = lockWait;
121 this.lockWaitUnit = requireNonNull(lockWaitUnit);
122
123 if (this.namedLockFactory instanceof FileLockNamedLockFactory && !this.nameMapper.isFileSystemFriendly()) {
124 throw new IllegalArgumentException(
125 "Misconfiguration: FileLockNamedLockFactory lock factory requires FS friendly NameMapper");
126 }
127 }
128
129 public SyncContext newInstance(final RepositorySystemSession session, final boolean shared) {
130 return new AdaptedLockSyncContext(session, shared, nameMapper, namedLockFactory, lockWait, lockWaitUnit);
131 }
132
133
134
135
136 public NameMapper getNameMapper() {
137 return nameMapper;
138 }
139
140
141
142
143 public NamedLockFactory getNamedLockFactory() {
144 return namedLockFactory;
145 }
146
147 public String toString() {
148 return getClass().getSimpleName()
149 + "(nameMapper=" + nameMapper
150 + ", namedLockFactory=" + namedLockFactory
151 + ")";
152 }
153
154 private static class AdaptedLockSyncContext implements SyncContext {
155 private static final Logger LOGGER = LoggerFactory.getLogger(AdaptedLockSyncContext.class);
156
157 private final RepositorySystemSession session;
158
159 private final boolean shared;
160
161 private final NameMapper lockNaming;
162
163 private final NamedLockFactory namedLockFactory;
164
165 private final long time;
166
167 private final TimeUnit timeUnit;
168
169 private final int retry;
170
171 private final long retryWait;
172
173 private final Deque<NamedLock> locks;
174
175 private final AtomicBoolean closed;
176
177 private AdaptedLockSyncContext(
178 final RepositorySystemSession session,
179 final boolean shared,
180 final NameMapper lockNaming,
181 final NamedLockFactory namedLockFactory,
182 final long lockWait,
183 final TimeUnit lockWaitUnit) {
184 this.session = session;
185 this.shared = shared;
186 this.lockNaming = lockNaming;
187 this.namedLockFactory = namedLockFactory;
188 this.time = lockWait;
189 this.timeUnit = lockWaitUnit;
190 this.retry = getRetry(session);
191 this.retryWait = getRetryWait(session);
192 this.locks = new ArrayDeque<>();
193 this.closed = new AtomicBoolean(false);
194
195 if (retry < 0L) {
196 throw new IllegalArgumentException(CONFIG_PROP_RETRY + " value cannot be negative");
197 }
198 if (retryWait < 0L) {
199 throw new IllegalArgumentException(CONFIG_PROP_RETRY_WAIT + " value cannot be negative");
200 }
201 }
202
203 private int getRetry(final RepositorySystemSession session) {
204 return ConfigUtils.getInteger(session, DEFAULT_RETRY, CONFIG_PROP_RETRY);
205 }
206
207 private long getRetryWait(final RepositorySystemSession session) {
208 return ConfigUtils.getLong(session, DEFAULT_RETRY_WAIT, CONFIG_PROP_RETRY_WAIT);
209 }
210
211 @Override
212 public void acquire(Collection<? extends Artifact> artifacts, Collection<? extends Metadata> metadatas) {
213 if (closed.get()) {
214 throw new IllegalStateException("sync context is already closed");
215 }
216 Collection<NamedLockKey> keys = lockNaming.nameLocks(session, artifacts, metadatas);
217 if (keys.isEmpty()) {
218 return;
219 }
220
221 final String timeStr = time + " " + timeUnit;
222 final String lockKind = shared ? "shared" : "exclusive";
223 final NamedLock namedLock = namedLockFactory.getLock(keys);
224 if (LOGGER.isTraceEnabled()) {
225 LOGGER.trace(
226 "Need {} lock for {} from {}",
227 lockKind,
228 namedLock.key().resources(),
229 namedLock.key().name());
230 }
231
232 final int attempts = retry + 1;
233 for (int attempt = 1; attempt <= attempts; attempt++) {
234 if (LOGGER.isTraceEnabled()) {
235 LOGGER.trace(
236 "Attempt {}: Acquire {} lock from {}",
237 attempt,
238 lockKind,
239 namedLock.key().name());
240 }
241 try {
242 if (attempt > 1) {
243 Thread.sleep(retryWait);
244 }
245 boolean locked;
246 if (shared) {
247 locked = namedLock.lockShared(time, timeUnit);
248 } else {
249 locked = namedLock.lockExclusively(time, timeUnit);
250 }
251
252 if (locked) {
253
254 locks.push(namedLock);
255 return;
256 }
257
258
259 if (LOGGER.isTraceEnabled()) {
260 LOGGER.trace(
261 "Failed to acquire {} lock for '{}' in {}",
262 lockKind,
263 namedLock.key().name(),
264 timeStr);
265 }
266 } catch (InterruptedException e) {
267
268 try {
269 namedLock.close();
270 } finally {
271 close();
272 }
273 Thread.currentThread().interrupt();
274 throw new RuntimeException(e);
275 }
276 }
277
278 try {
279 namedLock.close();
280 } finally {
281 close();
282 }
283 String message = "Could not acquire " + lockKind + " lock for "
284 + lockSubjects(artifacts, metadatas) + " in " + timeStr
285 + "; consider using '" + CONFIG_PROP_TIME
286 + "' property to increase lock timeout to a value that fits your environment";
287 FailedToAcquireLockException ex = new FailedToAcquireLockException(shared, message);
288 throw namedLockFactory.onFailure(ex);
289 }
290
291 private String lockSubjects(
292 Collection<? extends Artifact> artifacts, Collection<? extends Metadata> metadatas) {
293 StringBuilder builder = new StringBuilder();
294 if (artifacts != null && !artifacts.isEmpty()) {
295 builder.append("artifacts: ")
296 .append(artifacts.stream().map(ArtifactIdUtils::toId).collect(Collectors.joining(", ")));
297 }
298 if (metadatas != null && !metadatas.isEmpty()) {
299 if (builder.length() != 0) {
300 builder.append("; ");
301 }
302 builder.append("metadata: ")
303 .append(metadatas.stream().map(this::metadataSubjects).collect(Collectors.joining(", ")));
304 }
305 return builder.toString();
306 }
307
308 private String metadataSubjects(Metadata metadata) {
309 String name = "";
310 if (!metadata.getGroupId().isEmpty()) {
311 name += metadata.getGroupId();
312 if (!metadata.getArtifactId().isEmpty()) {
313 name += ":" + metadata.getArtifactId();
314 if (!metadata.getVersion().isEmpty()) {
315 name += ":" + metadata.getVersion();
316 }
317 }
318 }
319 if (!metadata.getType().isEmpty()) {
320 name += (name.isEmpty() ? "" : ":") + metadata.getType();
321 }
322 return name;
323 }
324
325 @Override
326 public void close() {
327 if (closed.compareAndSet(false, true)) {
328 while (!locks.isEmpty()) {
329 try (NamedLock namedLock = locks.pop()) {
330 namedLock.unlock();
331 if (LOGGER.isTraceEnabled()) {
332 LOGGER.trace(
333 "Unlocked and closed {} lock of {}",
334 shared ? "shared" : "exclusive",
335 namedLock.key());
336 }
337 }
338 }
339 }
340 }
341 }
342 }