View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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   * Adapter to adapt {@link NamedLockFactory} and {@link NamedLock} to {@link SyncContext}.
47   */
48  public final class NamedLockFactoryAdapter {
49      public static final String CONFIG_PROPS_PREFIX = ConfigurationProperties.PREFIX_SYNC_CONTEXT + "named.";
50  
51      /**
52       * The maximum of time amount to be blocked to obtain lock.
53       * <strong>Deprecated: use {@code aether.system.named...} configuration instead.</strong>
54       *
55       * @since 1.7.0
56       * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
57       * @configurationType {@link java.lang.Long}
58       * @deprecated
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       * The unit of maximum time amount to be blocked to obtain lock. Use TimeUnit enum names.
68       * <strong>Deprecated: use {@code aether.system.named...} configuration instead.</strong>
69       *
70       * @since 1.7.0
71       * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
72       * @configurationType {@link java.lang.String}
73       * @deprecated
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       * The amount of retries on time-out.
83       *
84       * @since 1.7.0
85       * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
86       * @configurationType {@link java.lang.Integer}
87       * @configurationDefaultValue {@link #DEFAULT_RETRY}
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       * The amount of milliseconds to wait between retries on time-out.
95       *
96       * @since 1.7.0
97       * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
98       * @configurationType {@link java.lang.Long}
99       * @configurationDefaultValue {@link #DEFAULT_RETRY_WAIT}
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         // TODO: this is ad-hoc "validation", experimental and likely to change
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      * @since 1.9.1
135      */
136     public NameMapper getNameMapper() {
137         return nameMapper;
138     }
139 
140     /**
141      * @since 1.9.1
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                         // we are done, get out
254                         locks.push(namedLock);
255                         return;
256                     }
257 
258                     // we failed; retry
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                     // if we are here, means we were interrupted: fail
268                     try {
269                         namedLock.close();
270                     } finally {
271                         close();
272                     }
273                     Thread.currentThread().interrupt();
274                     throw new RuntimeException(e);
275                 }
276             }
277             // if we are here, means all attempts were unsuccessful: fail
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 }