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.stream.Collectors; 026 027import org.eclipse.aether.ConfigurationProperties; 028import org.eclipse.aether.RepositorySystemSession; 029import org.eclipse.aether.SyncContext; 030import org.eclipse.aether.artifact.Artifact; 031import org.eclipse.aether.internal.impl.named.DefaultNamedLockFactorySelector; 032import org.eclipse.aether.metadata.Metadata; 033import org.eclipse.aether.named.NamedLock; 034import org.eclipse.aether.named.NamedLockFactory; 035import org.eclipse.aether.named.NamedLockKey; 036import org.eclipse.aether.named.providers.FileLockNamedLockFactory; 037import org.eclipse.aether.util.ConfigUtils; 038import org.eclipse.aether.util.artifact.ArtifactIdUtils; 039import org.slf4j.Logger; 040import org.slf4j.LoggerFactory; 041 042import static java.util.Objects.requireNonNull; 043 044/** 045 * Adapter to adapt {@link NamedLockFactory} and {@link NamedLock} to {@link SyncContext}. 046 */ 047public final class NamedLockFactoryAdapter { 048 public static final String CONFIG_PROPS_PREFIX = ConfigurationProperties.PREFIX_SYNC_CONTEXT + "named."; 049 050 /** 051 * The maximum of time amount to be blocked to obtain lock. 052 * <strong>Deprecated: use {@code aether.system.named...} configuration instead.</strong> 053 * 054 * @since 1.7.0 055 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 056 * @configurationType {@link java.lang.Long} 057 * @deprecated 058 */ 059 @Deprecated 060 public static final String CONFIG_PROP_TIME = CONFIG_PROPS_PREFIX + "time"; 061 062 @Deprecated 063 public static final long DEFAULT_TIME = DefaultNamedLockFactorySelector.DEFAULT_LOCK_WAIT_TIME; 064 065 /** 066 * The unit of maximum time amount to be blocked to obtain lock. Use TimeUnit enum names. 067 * <strong>Deprecated: use {@code aether.system.named...} configuration instead.</strong> 068 * 069 * @since 1.7.0 070 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 071 * @configurationType {@link java.lang.String} 072 * @deprecated 073 */ 074 @Deprecated 075 public static final String CONFIG_PROP_TIME_UNIT = CONFIG_PROPS_PREFIX + "time.unit"; 076 077 @Deprecated 078 public static final String DEFAULT_TIME_UNIT = DefaultNamedLockFactorySelector.DEFAULT_LOCK_WAIT_TIME_UNIT; 079 080 /** 081 * The amount of retries on time-out. 082 * 083 * @since 1.7.0 084 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 085 * @configurationType {@link java.lang.Integer} 086 * @configurationDefaultValue {@link #DEFAULT_RETRY} 087 */ 088 public static final String CONFIG_PROP_RETRY = CONFIG_PROPS_PREFIX + "retry"; 089 090 public static final int DEFAULT_RETRY = 1; 091 092 /** 093 * The amount of milliseconds to wait between retries on time-out. 094 * 095 * @since 1.7.0 096 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 097 * @configurationType {@link java.lang.Long} 098 * @configurationDefaultValue {@link #DEFAULT_RETRY_WAIT} 099 */ 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}