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.named.support; 020 021import java.util.Collection; 022import java.util.Deque; 023import java.util.HashMap; 024import java.util.Map; 025import java.util.concurrent.ConcurrentHashMap; 026import java.util.concurrent.ConcurrentMap; 027import java.util.concurrent.atomic.AtomicBoolean; 028import java.util.concurrent.atomic.AtomicInteger; 029import java.util.function.Supplier; 030import java.util.stream.Collectors; 031 032import org.eclipse.aether.named.NamedLock; 033import org.eclipse.aether.named.NamedLockFactory; 034import org.eclipse.aether.named.NamedLockKey; 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037 038import static java.util.Objects.requireNonNull; 039 040/** 041 * Support class for {@link NamedLockFactory} implementations providing reference counting. 042 */ 043public abstract class NamedLockFactorySupport implements NamedLockFactory { 044 /** 045 * System property key to enable locking diagnostic collection. 046 * 047 * @since 1.9.11 048 * @configurationSource {@link System#getProperty(String, String)} 049 * @configurationType {@link java.lang.Boolean} 050 * @configurationDefaultValue false 051 */ 052 public static final String SYSTEM_PROP_DIAGNOSTIC_ENABLED = "aether.named.diagnostic.enabled"; 053 054 private static final boolean DIAGNOSTIC_ENABLED = Boolean.getBoolean(SYSTEM_PROP_DIAGNOSTIC_ENABLED); 055 056 protected final Logger logger = LoggerFactory.getLogger(getClass()); 057 058 private final ConcurrentMap<NamedLockKey, NamedLockHolder> locks; 059 060 private final AtomicInteger compositeCounter; 061 062 private final boolean diagnosticEnabled; 063 064 private final AtomicBoolean shutdown = new AtomicBoolean(false); 065 066 public NamedLockFactorySupport() { 067 this(DIAGNOSTIC_ENABLED); 068 } 069 070 public NamedLockFactorySupport(boolean diagnosticEnabled) { 071 this.locks = new ConcurrentHashMap<>(); 072 this.compositeCounter = new AtomicInteger(0); 073 this.diagnosticEnabled = diagnosticEnabled; 074 } 075 076 /** 077 * Returns {@code true} if factory diagnostic collection is enabled. 078 * 079 * @since 1.9.11 080 */ 081 public boolean isDiagnosticEnabled() { 082 return diagnosticEnabled; 083 } 084 085 @Override 086 public final NamedLock getLock(final Collection<NamedLockKey> keys) { 087 requireNonNull(keys, "keys"); 088 if (shutdown.get()) { 089 throw new IllegalStateException("factory already shut down"); 090 } 091 if (keys.isEmpty()) { 092 throw new IllegalArgumentException("empty keys"); 093 } else { 094 return doGetLock(keys); 095 } 096 } 097 098 protected NamedLock doGetLock(final Collection<NamedLockKey> keys) { 099 if (keys.size() == 1) { 100 NamedLockKey key = keys.iterator().next(); 101 return getLockAndRefTrack(key, () -> createLock(key)); 102 } else { 103 return new CompositeNamedLock( 104 NamedLockKey.of( 105 "composite-" + compositeCounter.incrementAndGet(), 106 keys.stream() 107 .map(NamedLockKey::resources) 108 .flatMap(Collection::stream) 109 .collect(Collectors.toList())), 110 this, 111 keys.stream() 112 .map(k -> getLockAndRefTrack(k, () -> createLock(k))) 113 .collect(Collectors.toList())); 114 } 115 } 116 117 protected NamedLock getLockAndRefTrack(final NamedLockKey key, Supplier<NamedLockSupport> supplier) { 118 return locks.compute(key, (k, v) -> { 119 if (shutdown.get()) { 120 throw new IllegalStateException("factory already shut down"); 121 } 122 if (v == null) { 123 v = new NamedLockHolder(supplier.get()); 124 } 125 return v.incRef(); 126 }) 127 .namedLock; 128 } 129 130 @Override 131 public void shutdown() { 132 if (shutdown.compareAndSet(false, true)) { 133 doShutdown(); 134 } 135 } 136 137 protected void doShutdown() { 138 // override if needed 139 } 140 141 @Override 142 public <E extends Throwable> E onFailure(E failure) { 143 if (isDiagnosticEnabled()) { 144 Map<NamedLockKey, NamedLockHolder> locks = new HashMap<>(this.locks); // copy 145 int activeLocks = locks.size(); 146 logger.info("Diagnostic dump of lock factory"); 147 logger.info("==============================="); 148 logger.info("Implementation: {}", getClass().getName()); 149 logger.info("Active locks: {}", activeLocks); 150 logger.info(""); 151 if (activeLocks > 0) { 152 for (Map.Entry<NamedLockKey, NamedLockHolder> entry : locks.entrySet()) { 153 NamedLockKey key = entry.getKey(); 154 int refCount = entry.getValue().referenceCount.get(); 155 NamedLockSupport lock = entry.getValue().namedLock; 156 logger.info("Name: {}", key.name()); 157 logger.info("RefCount: {}", refCount); 158 logger.info("Resources:"); 159 key.resources().forEach(r -> logger.info(" - {}", r)); 160 Map<Thread, Deque<String>> diag = lock.diagnosticState(); 161 logger.info("State:"); 162 diag.forEach((k, v) -> logger.info(" {} -> {}", k, v)); 163 } 164 logger.info(""); 165 } 166 } 167 return failure; 168 } 169 170 public void closeLock(final NamedLockKey key) { 171 locks.compute(key, (k, v) -> { 172 if (v != null && v.decRef() == 0) { 173 destroyLock(v.namedLock); 174 return null; 175 } 176 return v; 177 }); 178 } 179 180 /** 181 * Implementations shall create and return {@link NamedLockSupport} for given {@code name}, this method must never 182 * return {@code null}. 183 */ 184 protected abstract NamedLockSupport createLock(NamedLockKey key); 185 186 /** 187 * Implementation may override this (empty) method to perform some sort of implementation specific cleanup for 188 * given lock name. Invoked when reference count for given name drops to zero and named lock was removed. 189 */ 190 protected void destroyLock(final NamedLock namedLock) { 191 // override if needed 192 } 193 194 private static final class NamedLockHolder { 195 private final NamedLockSupport namedLock; 196 197 private final AtomicInteger referenceCount; 198 199 private NamedLockHolder(final NamedLockSupport namedLock) { 200 this.namedLock = requireNonNull(namedLock); 201 this.referenceCount = new AtomicInteger(0); 202 } 203 204 private NamedLockHolder incRef() { 205 referenceCount.incrementAndGet(); 206 return this; 207 } 208 209 private int decRef() { 210 return referenceCount.decrementAndGet(); 211 } 212 213 @Override 214 public String toString() { 215 return "[refCount=" + referenceCount.get() + ", lock=" + namedLock + "]"; 216 } 217 } 218}