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.ArrayList; 023import java.util.Collection; 024import java.util.Deque; 025import java.util.concurrent.TimeUnit; 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.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<String> keys = lockNaming.nameLocks(session, artifacts, metadatas); 200 if (keys.isEmpty()) { 201 return; 202 } 203 204 final int attempts = retry + 1; 205 final ArrayList<IllegalStateException> illegalStateExceptions = new ArrayList<>(); 206 for (int attempt = 1; attempt <= attempts; attempt++) { 207 LOGGER.trace( 208 "Attempt {}: Need {} {} lock(s) for {}", attempt, keys.size(), shared ? "read" : "write", keys); 209 int acquiredLockCount = 0; 210 try { 211 if (attempt > 1) { 212 Thread.sleep(retryWait); 213 } 214 for (String key : keys) { 215 NamedLock namedLock = namedLockFactory.getLock(key); 216 LOGGER.trace("Acquiring {} lock for '{}'", shared ? "read" : "write", key); 217 218 boolean locked; 219 if (shared) { 220 locked = namedLock.lockShared(time, timeUnit); 221 } else { 222 locked = namedLock.lockExclusively(time, timeUnit); 223 } 224 225 if (!locked) { 226 String timeStr = time + " " + timeUnit; 227 LOGGER.trace( 228 "Failed to acquire {} lock for '{}' in {}", 229 shared ? "read" : "write", 230 key, 231 timeStr); 232 233 namedLock.close(); 234 closeAll(); 235 illegalStateExceptions.add(new IllegalStateException( 236 "Attempt " + attempt + ": Could not acquire " + (shared ? "read" : "write") 237 + " lock for '" + namedLock.name() + "' in " + timeStr)); 238 break; 239 } else { 240 locks.push(namedLock); 241 acquiredLockCount++; 242 } 243 } 244 } catch (InterruptedException e) { 245 Thread.currentThread().interrupt(); 246 throw new RuntimeException(e); 247 } 248 LOGGER.trace("Attempt {}: Total locks acquired: {}", attempt, acquiredLockCount); 249 if (acquiredLockCount == keys.size()) { 250 break; 251 } 252 } 253 if (!illegalStateExceptions.isEmpty()) { 254 IllegalStateException ex = new IllegalStateException("Could not acquire lock(s)"); 255 illegalStateExceptions.forEach(ex::addSuppressed); 256 throw namedLockFactory.onFailure(ex); 257 } 258 } 259 260 private void closeAll() { 261 if (locks.isEmpty()) { 262 return; 263 } 264 265 // Release locks in reverse insertion order 266 int released = 0; 267 while (!locks.isEmpty()) { 268 try (NamedLock namedLock = locks.pop()) { 269 LOGGER.trace("Releasing {} lock for '{}'", shared ? "read" : "write", namedLock.name()); 270 namedLock.unlock(); 271 released++; 272 } 273 } 274 LOGGER.trace("Total locks released: {}", released); 275 } 276 277 @Override 278 public void close() { 279 closeAll(); 280 } 281 } 282}