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
27 import org.eclipse.aether.RepositorySystemSession;
28 import org.eclipse.aether.SyncContext;
29 import org.eclipse.aether.artifact.Artifact;
30 import org.eclipse.aether.metadata.Metadata;
31 import org.eclipse.aether.named.NamedLock;
32 import org.eclipse.aether.named.NamedLockFactory;
33 import org.eclipse.aether.named.providers.FileLockNamedLockFactory;
34 import org.eclipse.aether.util.ConfigUtils;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 import static java.util.Objects.requireNonNull;
39
40
41
42
43 public final class NamedLockFactoryAdapter {
44 public static final String TIME_KEY = "aether.syncContext.named.time";
45
46 public static final long DEFAULT_TIME = 30L;
47
48 public static final String TIME_UNIT_KEY = "aether.syncContext.named.time.unit";
49
50 public static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.SECONDS;
51
52 public static final String RETRY_KEY = "aether.syncContext.named.retry";
53
54 public static final int DEFAULT_RETRY = 1;
55
56 public static final String RETRY_WAIT_KEY = "aether.syncContext.named.retry.wait";
57
58 public static final long DEFAULT_RETRY_WAIT = 200L;
59
60 private final NameMapper nameMapper;
61
62 private final NamedLockFactory namedLockFactory;
63
64 public NamedLockFactoryAdapter(final NameMapper nameMapper, final NamedLockFactory namedLockFactory) {
65 this.nameMapper = requireNonNull(nameMapper);
66 this.namedLockFactory = requireNonNull(namedLockFactory);
67
68 if (this.namedLockFactory instanceof FileLockNamedLockFactory && !this.nameMapper.isFileSystemFriendly()) {
69 throw new IllegalArgumentException(
70 "Misconfiguration: FileLockNamedLockFactory lock factory requires FS friendly NameMapper");
71 }
72 }
73
74 public SyncContext newInstance(final RepositorySystemSession session, final boolean shared) {
75 return new AdaptedLockSyncContext(session, shared, nameMapper, namedLockFactory);
76 }
77
78
79
80
81 public NameMapper getNameMapper() {
82 return nameMapper;
83 }
84
85
86
87
88 public NamedLockFactory getNamedLockFactory() {
89 return namedLockFactory;
90 }
91
92 public String toString() {
93 return getClass().getSimpleName()
94 + "(nameMapper=" + nameMapper
95 + ", namedLockFactory=" + namedLockFactory
96 + ")";
97 }
98
99 private static class AdaptedLockSyncContext implements SyncContext {
100 private static final Logger LOGGER = LoggerFactory.getLogger(AdaptedLockSyncContext.class);
101
102 private final RepositorySystemSession session;
103
104 private final boolean shared;
105
106 private final NameMapper lockNaming;
107
108 private final NamedLockFactory namedLockFactory;
109
110 private final long time;
111
112 private final TimeUnit timeUnit;
113
114 private final int retry;
115
116 private final long retryWait;
117
118 private final Deque<NamedLock> locks;
119
120 private AdaptedLockSyncContext(
121 final RepositorySystemSession session,
122 final boolean shared,
123 final NameMapper lockNaming,
124 final NamedLockFactory namedLockFactory) {
125 this.session = session;
126 this.shared = shared;
127 this.lockNaming = lockNaming;
128 this.namedLockFactory = namedLockFactory;
129 this.time = getTime(session);
130 this.timeUnit = getTimeUnit(session);
131 this.retry = getRetry(session);
132 this.retryWait = getRetryWait(session);
133 this.locks = new ArrayDeque<>();
134
135 if (time < 0L) {
136 throw new IllegalArgumentException(TIME_KEY + " value cannot be negative");
137 }
138 if (retry < 0L) {
139 throw new IllegalArgumentException(RETRY_KEY + " value cannot be negative");
140 }
141 if (retryWait < 0L) {
142 throw new IllegalArgumentException(RETRY_WAIT_KEY + " value cannot be negative");
143 }
144 }
145
146 private long getTime(final RepositorySystemSession session) {
147 return ConfigUtils.getLong(session, DEFAULT_TIME, TIME_KEY);
148 }
149
150 private TimeUnit getTimeUnit(final RepositorySystemSession session) {
151 return TimeUnit.valueOf(ConfigUtils.getString(session, DEFAULT_TIME_UNIT.name(), TIME_UNIT_KEY));
152 }
153
154 private int getRetry(final RepositorySystemSession session) {
155 return ConfigUtils.getInteger(session, DEFAULT_RETRY, RETRY_KEY);
156 }
157
158 private long getRetryWait(final RepositorySystemSession session) {
159 return ConfigUtils.getLong(session, DEFAULT_RETRY_WAIT, RETRY_WAIT_KEY);
160 }
161
162 @Override
163 public void acquire(Collection<? extends Artifact> artifacts, Collection<? extends Metadata> metadatas) {
164 Collection<String> keys = lockNaming.nameLocks(session, artifacts, metadatas);
165 if (keys.isEmpty()) {
166 return;
167 }
168
169 final int attempts = retry + 1;
170 final ArrayList<IllegalStateException> illegalStateExceptions = new ArrayList<>();
171 for (int attempt = 1; attempt <= attempts; attempt++) {
172 LOGGER.trace(
173 "Attempt {}: Need {} {} lock(s) for {}", attempt, keys.size(), shared ? "read" : "write", keys);
174 int acquiredLockCount = 0;
175 try {
176 if (attempt > 1) {
177 Thread.sleep(retryWait);
178 }
179 for (String key : keys) {
180 NamedLock namedLock = namedLockFactory.getLock(key);
181 LOGGER.trace("Acquiring {} lock for '{}'", shared ? "read" : "write", key);
182
183 boolean locked;
184 if (shared) {
185 locked = namedLock.lockShared(time, timeUnit);
186 } else {
187 locked = namedLock.lockExclusively(time, timeUnit);
188 }
189
190 if (!locked) {
191 String timeStr = time + " " + timeUnit;
192 LOGGER.trace(
193 "Failed to acquire {} lock for '{}' in {}",
194 shared ? "read" : "write",
195 key,
196 timeStr);
197
198 namedLock.close();
199 closeAll();
200 illegalStateExceptions.add(new IllegalStateException(
201 "Attempt " + attempt + ": Could not acquire " + (shared ? "read" : "write")
202 + " lock for '" + namedLock.name() + "' in " + timeStr));
203 break;
204 } else {
205 locks.push(namedLock);
206 acquiredLockCount++;
207 }
208 }
209 } catch (InterruptedException e) {
210 Thread.currentThread().interrupt();
211 throw new RuntimeException(e);
212 }
213 LOGGER.trace("Attempt {}: Total locks acquired: {}", attempt, acquiredLockCount);
214 if (acquiredLockCount == keys.size()) {
215 break;
216 }
217 }
218 if (!illegalStateExceptions.isEmpty()) {
219 IllegalStateException ex = new IllegalStateException("Could not acquire lock(s)");
220 illegalStateExceptions.forEach(ex::addSuppressed);
221 throw ex;
222 }
223 }
224
225 private void closeAll() {
226 if (locks.isEmpty()) {
227 return;
228 }
229
230
231 int released = 0;
232 while (!locks.isEmpty()) {
233 try (NamedLock namedLock = locks.pop()) {
234 LOGGER.trace("Releasing {} lock for '{}'", shared ? "read" : "write", namedLock.name());
235 namedLock.unlock();
236 released++;
237 }
238 }
239 LOGGER.trace("Total locks released: {}", released);
240 }
241
242 @Override
243 public void close() {
244 closeAll();
245 }
246 }
247 }