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.metadata.Metadata; 032import org.eclipse.aether.named.NamedLock; 033import org.eclipse.aether.named.NamedLockFactory; 034import org.eclipse.aether.named.NamedLockKey; 035import org.eclipse.aether.named.providers.FileLockNamedLockFactory; 036import org.eclipse.aether.util.ConfigUtils; 037import org.eclipse.aether.util.artifact.ArtifactIdUtils; 038import org.slf4j.Logger; 039import org.slf4j.LoggerFactory; 040 041import static java.util.Objects.requireNonNull; 042 043/** 044 * Adapter to adapt {@link NamedLockFactory} and {@link NamedLock} to {@link SyncContext}. 045 */ 046public final class NamedLockFactoryAdapter { 047 public static final String CONFIG_PROPS_PREFIX = ConfigurationProperties.PREFIX_SYNC_CONTEXT + "named."; 048 049 /** 050 * The maximum of time amount to be blocked to obtain lock. 051 * 052 * @since 1.7.0 053 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 054 * @configurationType {@link java.lang.Long} 055 * @configurationDefaultValue {@link #DEFAULT_TIME} 056 */ 057 public static final String CONFIG_PROP_TIME = CONFIG_PROPS_PREFIX + "time"; 058 059 public static final long DEFAULT_TIME = 900L; 060 061 /** 062 * The unit of maximum time amount to be blocked to obtain lock. Use TimeUnit enum names. 063 * 064 * @since 1.7.0 065 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 066 * @configurationType {@link java.lang.String} 067 * @configurationDefaultValue {@link #DEFAULT_TIME_UNIT} 068 */ 069 public static final String CONFIG_PROP_TIME_UNIT = CONFIG_PROPS_PREFIX + "time.unit"; 070 071 public static final String DEFAULT_TIME_UNIT = "SECONDS"; 072 073 /** 074 * The amount of retries on time-out. 075 * 076 * @since 1.7.0 077 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 078 * @configurationType {@link java.lang.Integer} 079 * @configurationDefaultValue {@link #DEFAULT_RETRY} 080 */ 081 public static final String CONFIG_PROP_RETRY = CONFIG_PROPS_PREFIX + "retry"; 082 083 public static final int DEFAULT_RETRY = 1; 084 085 /** 086 * The amount of milliseconds to wait between retries on time-out. 087 * 088 * @since 1.7.0 089 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 090 * @configurationType {@link java.lang.Long} 091 * @configurationDefaultValue {@link #DEFAULT_RETRY_WAIT} 092 */ 093 public static final String CONFIG_PROP_RETRY_WAIT = CONFIG_PROPS_PREFIX + "retry.wait"; 094 095 public static final long DEFAULT_RETRY_WAIT = 200L; 096 097 private final NameMapper nameMapper; 098 099 private final NamedLockFactory namedLockFactory; 100 101 public NamedLockFactoryAdapter(final NameMapper nameMapper, final NamedLockFactory namedLockFactory) { 102 this.nameMapper = requireNonNull(nameMapper); 103 this.namedLockFactory = requireNonNull(namedLockFactory); 104 // TODO: this is ad-hoc "validation", experimental and likely to change 105 if (this.namedLockFactory instanceof FileLockNamedLockFactory && !this.nameMapper.isFileSystemFriendly()) { 106 throw new IllegalArgumentException( 107 "Misconfiguration: FileLockNamedLockFactory lock factory requires FS friendly NameMapper"); 108 } 109 } 110 111 public SyncContext newInstance(final RepositorySystemSession session, final boolean shared) { 112 return new AdaptedLockSyncContext(session, shared, nameMapper, namedLockFactory); 113 } 114 115 /** 116 * @since 1.9.1 117 */ 118 public NameMapper getNameMapper() { 119 return nameMapper; 120 } 121 122 /** 123 * @since 1.9.1 124 */ 125 public NamedLockFactory getNamedLockFactory() { 126 return namedLockFactory; 127 } 128 129 public String toString() { 130 return getClass().getSimpleName() 131 + "(nameMapper=" + nameMapper 132 + ", namedLockFactory=" + namedLockFactory 133 + ")"; 134 } 135 136 private static class AdaptedLockSyncContext implements SyncContext { 137 private static final Logger LOGGER = LoggerFactory.getLogger(AdaptedLockSyncContext.class); 138 139 private final RepositorySystemSession session; 140 141 private final boolean shared; 142 143 private final NameMapper lockNaming; 144 145 private final NamedLockFactory namedLockFactory; 146 147 private final long time; 148 149 private final TimeUnit timeUnit; 150 151 private final int retry; 152 153 private final long retryWait; 154 155 private final Deque<NamedLock> locks; 156 157 private AdaptedLockSyncContext( 158 final RepositorySystemSession session, 159 final boolean shared, 160 final NameMapper lockNaming, 161 final NamedLockFactory namedLockFactory) { 162 this.session = session; 163 this.shared = shared; 164 this.lockNaming = lockNaming; 165 this.namedLockFactory = namedLockFactory; 166 this.time = getTime(session, DEFAULT_TIME, CONFIG_PROP_TIME); 167 this.timeUnit = getTimeUnit(session); 168 this.retry = getRetry(session); 169 this.retryWait = getRetryWait(session); 170 this.locks = new ArrayDeque<>(); 171 172 if (time < 0L) { 173 throw new IllegalArgumentException(CONFIG_PROP_TIME + " value cannot be negative"); 174 } 175 if (retry < 0L) { 176 throw new IllegalArgumentException(CONFIG_PROP_RETRY + " value cannot be negative"); 177 } 178 if (retryWait < 0L) { 179 throw new IllegalArgumentException(CONFIG_PROP_RETRY_WAIT + " value cannot be negative"); 180 } 181 } 182 183 private long getTime(final RepositorySystemSession session, long defaultValue, String... keys) { 184 return ConfigUtils.getLong(session, defaultValue, keys); 185 } 186 187 private TimeUnit getTimeUnit(final RepositorySystemSession session) { 188 return TimeUnit.valueOf(ConfigUtils.getString(session, DEFAULT_TIME_UNIT, CONFIG_PROP_TIME_UNIT)); 189 } 190 191 private int getRetry(final RepositorySystemSession session) { 192 return ConfigUtils.getInteger(session, DEFAULT_RETRY, CONFIG_PROP_RETRY); 193 } 194 195 private long getRetryWait(final RepositorySystemSession session) { 196 return ConfigUtils.getLong(session, DEFAULT_RETRY_WAIT, CONFIG_PROP_RETRY_WAIT); 197 } 198 199 @Override 200 public void acquire(Collection<? extends Artifact> artifacts, Collection<? extends Metadata> metadatas) { 201 Collection<NamedLockKey> keys = lockNaming.nameLocks(session, artifacts, metadatas); 202 if (keys.isEmpty()) { 203 return; 204 } 205 206 final String timeStr = time + " " + timeUnit; 207 final String lockKind = shared ? "shared" : "exclusive"; 208 final NamedLock namedLock = namedLockFactory.getLock(keys); 209 if (LOGGER.isTraceEnabled()) { 210 LOGGER.trace( 211 "Need {} lock for {} from {}", 212 lockKind, 213 namedLock.key().resources(), 214 namedLock.key().name()); 215 } 216 217 final int attempts = retry + 1; 218 for (int attempt = 1; attempt <= attempts; attempt++) { 219 if (LOGGER.isTraceEnabled()) { 220 LOGGER.trace( 221 "Attempt {}: Acquire {} lock from {}", 222 attempt, 223 lockKind, 224 namedLock.key().name()); 225 } 226 try { 227 if (attempt > 1) { 228 Thread.sleep(retryWait); 229 } 230 boolean locked; 231 if (shared) { 232 locked = namedLock.lockShared(time, timeUnit); 233 } else { 234 locked = namedLock.lockExclusively(time, timeUnit); 235 } 236 237 if (locked) { 238 // we are done, get out 239 locks.push(namedLock); 240 return; 241 } 242 243 // we failed; retry 244 if (LOGGER.isTraceEnabled()) { 245 LOGGER.trace( 246 "Failed to acquire {} lock for '{}' in {}", 247 lockKind, 248 namedLock.key().name(), 249 timeStr); 250 } 251 } catch (InterruptedException e) { 252 // if we are here, means we were interrupted: fail 253 close(); 254 Thread.currentThread().interrupt(); 255 throw new RuntimeException(e); 256 } 257 } 258 // if we are here, means all attempts were unsuccessful: fail 259 close(); 260 String message = "Could not acquire " + lockKind + " lock for " 261 + lockSubjects(artifacts, metadatas) + " in " + timeStr 262 + "; consider using '" + CONFIG_PROP_TIME 263 + "' property to increase lock timeout to a value that fits your environment"; 264 FailedToAcquireLockException ex = new FailedToAcquireLockException(shared, message); 265 throw namedLockFactory.onFailure(ex); 266 } 267 268 private String lockSubjects( 269 Collection<? extends Artifact> artifacts, Collection<? extends Metadata> metadatas) { 270 StringBuilder builder = new StringBuilder(); 271 if (artifacts != null && !artifacts.isEmpty()) { 272 builder.append("artifacts: ") 273 .append(artifacts.stream().map(ArtifactIdUtils::toId).collect(Collectors.joining(", "))); 274 } 275 if (metadatas != null && !metadatas.isEmpty()) { 276 if (builder.length() != 0) { 277 builder.append("; "); 278 } 279 builder.append("metadata: ") 280 .append(metadatas.stream().map(this::metadataSubjects).collect(Collectors.joining(", "))); 281 } 282 return builder.toString(); 283 } 284 285 private String metadataSubjects(Metadata metadata) { 286 String name = ""; 287 if (!metadata.getGroupId().isEmpty()) { 288 name += metadata.getGroupId(); 289 if (!metadata.getArtifactId().isEmpty()) { 290 name += ":" + metadata.getArtifactId(); 291 if (!metadata.getVersion().isEmpty()) { 292 name += ":" + metadata.getVersion(); 293 } 294 } 295 } 296 if (!metadata.getType().isEmpty()) { 297 name += (name.isEmpty() ? "" : ":") + metadata.getType(); 298 } 299 return name; 300 } 301 302 @Override 303 public void close() { 304 while (!locks.isEmpty()) { 305 try (NamedLock namedLock = locks.pop()) { 306 namedLock.unlock(); 307 if (LOGGER.isTraceEnabled()) { 308 LOGGER.trace( 309 "Unlocked and closed {} lock of {}", shared ? "shared" : "exclusive", namedLock.key()); 310 } 311 } 312 } 313 } 314 } 315}