001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.eclipse.aether.internal.impl.synccontext.named; 020 021import java.util.ArrayDeque; 022import java.util.Collection; 023import java.util.Deque; 024import java.util.concurrent.TimeUnit; 025import java.util.concurrent.atomic.AtomicBoolean; 026import java.util.stream.Collectors; 027 028import org.eclipse.aether.ConfigurationProperties; 029import org.eclipse.aether.RepositorySystemSession; 030import org.eclipse.aether.SyncContext; 031import org.eclipse.aether.artifact.Artifact; 032import org.eclipse.aether.internal.impl.named.DefaultNamedLockFactorySelector; 033import org.eclipse.aether.metadata.Metadata; 034import org.eclipse.aether.named.NamedLock; 035import org.eclipse.aether.named.NamedLockFactory; 036import org.eclipse.aether.named.NamedLockKey; 037import org.eclipse.aether.named.providers.FileLockNamedLockFactory; 038import org.eclipse.aether.util.ConfigUtils; 039import org.eclipse.aether.util.artifact.ArtifactIdUtils; 040import org.slf4j.Logger; 041import org.slf4j.LoggerFactory; 042 043import static java.util.Objects.requireNonNull; 044 045/** 046 * Adapter to adapt {@link NamedLockFactory} and {@link NamedLock} to {@link SyncContext}. 047 */ 048public final class NamedLockFactoryAdapter { 049 public static final String CONFIG_PROPS_PREFIX = ConfigurationProperties.PREFIX_SYNC_CONTEXT + "named."; 050 051 /** 052 * The maximum of time amount to be blocked to obtain lock. 053 * <strong>Deprecated: use {@code aether.system.named...} configuration instead.</strong> 054 * 055 * @since 1.7.0 056 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 057 * @configurationType {@link java.lang.Long} 058 * @deprecated 059 */ 060 @Deprecated 061 public static final String CONFIG_PROP_TIME = CONFIG_PROPS_PREFIX + "time"; 062 063 @Deprecated 064 public static final long DEFAULT_TIME = DefaultNamedLockFactorySelector.DEFAULT_LOCK_WAIT_TIME; 065 066 /** 067 * The unit of maximum time amount to be blocked to obtain lock. Use TimeUnit enum names. 068 * <strong>Deprecated: use {@code aether.system.named...} configuration instead.</strong> 069 * 070 * @since 1.7.0 071 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 072 * @configurationType {@link java.lang.String} 073 * @deprecated 074 */ 075 @Deprecated 076 public static final String CONFIG_PROP_TIME_UNIT = CONFIG_PROPS_PREFIX + "time.unit"; 077 078 @Deprecated 079 public static final String DEFAULT_TIME_UNIT = DefaultNamedLockFactorySelector.DEFAULT_LOCK_WAIT_TIME_UNIT; 080 081 /** 082 * The amount of retries on time-out. 083 * 084 * @since 1.7.0 085 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 086 * @configurationType {@link java.lang.Integer} 087 * @configurationDefaultValue {@link #DEFAULT_RETRY} 088 */ 089 public static final String CONFIG_PROP_RETRY = CONFIG_PROPS_PREFIX + "retry"; 090 091 public static final int DEFAULT_RETRY = 1; 092 093 /** 094 * The amount of milliseconds to wait between retries on time-out. 095 * 096 * @since 1.7.0 097 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 098 * @configurationType {@link java.lang.Long} 099 * @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}