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