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