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 (v == null) { 120 v = new NamedLockHolder(supplier.get()); 121 } 122 return v.incRef(); 123 }) 124 .namedLock; 125 } 126 127 @Override 128 public void shutdown() { 129 if (shutdown.compareAndSet(false, true)) { 130 doShutdown(); 131 } 132 } 133 134 protected void doShutdown() { 135 // override if needed 136 } 137 138 @Override 139 public <E extends Throwable> E onFailure(E failure) { 140 if (isDiagnosticEnabled()) { 141 Map<NamedLockKey, NamedLockHolder> locks = new HashMap<>(this.locks); // copy 142 int activeLocks = locks.size(); 143 logger.info("Diagnostic dump of lock factory"); 144 logger.info("==============================="); 145 logger.info("Implementation: {}", getClass().getName()); 146 logger.info("Active locks: {}", activeLocks); 147 logger.info(""); 148 if (activeLocks > 0) { 149 for (Map.Entry<NamedLockKey, NamedLockHolder> entry : locks.entrySet()) { 150 NamedLockKey key = entry.getKey(); 151 int refCount = entry.getValue().referenceCount.get(); 152 NamedLockSupport lock = entry.getValue().namedLock; 153 logger.info("Name: {}", key.name()); 154 logger.info("RefCount: {}", refCount); 155 logger.info("Resources:"); 156 key.resources().forEach(r -> logger.info(" - {}", r)); 157 Map<Thread, Deque<String>> diag = lock.diagnosticState(); 158 logger.info("State:"); 159 diag.forEach((k, v) -> logger.info(" {} -> {}", k, v)); 160 } 161 logger.info(""); 162 } 163 } 164 return failure; 165 } 166 167 public void closeLock(final NamedLockKey key) { 168 locks.compute(key, (k, v) -> { 169 if (v != null && v.decRef() == 0) { 170 destroyLock(v.namedLock); 171 return null; 172 } 173 return v; 174 }); 175 } 176 177 /** 178 * Implementations shall create and return {@link NamedLockSupport} for given {@code name}, this method must never 179 * return {@code null}. 180 */ 181 protected abstract NamedLockSupport createLock(NamedLockKey key); 182 183 /** 184 * Implementation may override this (empty) method to perform some sort of implementation specific cleanup for 185 * given lock name. Invoked when reference count for given name drops to zero and named lock was removed. 186 */ 187 protected void destroyLock(final NamedLock namedLock) { 188 // override if needed 189 } 190 191 private static final class NamedLockHolder { 192 private final NamedLockSupport namedLock; 193 194 private final AtomicInteger referenceCount; 195 196 private NamedLockHolder(final NamedLockSupport namedLock) { 197 this.namedLock = requireNonNull(namedLock); 198 this.referenceCount = new AtomicInteger(0); 199 } 200 201 private NamedLockHolder incRef() { 202 referenceCount.incrementAndGet(); 203 return this; 204 } 205 206 private int decRef() { 207 return referenceCount.decrementAndGet(); 208 } 209 210 @Override 211 public String toString() { 212 return "[refCount=" + referenceCount.get() + ", lock=" + namedLock + "]"; 213 } 214 } 215}