CustomResourceLeakDetector.java
/*
* Copyright 2017-2024 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.fuzzing.http;
import io.netty.util.ResourceLeakDetector;
import io.netty.util.ResourceLeakDetectorFactory;
import io.netty.util.ResourceLeakHint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.Base64;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
final class CustomResourceLeakDetector<T> extends ResourceLeakDetector<T> {
private static final VarHandle ALL_LEAKS_FIELD;
static {
try {
ALL_LEAKS_FIELD = MethodHandles.privateLookupIn(ResourceLeakDetector.class, MethodHandles.lookup())
.findVarHandle(ResourceLeakDetector.class, "allLeaks", Set.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
private static final List<Leak> LEAKS = new CopyOnWriteArrayList<>();
private static final List<ResourceLeakDetector<?>> DETECTORS = new CopyOnWriteArrayList<>();
private static volatile ResourceLeakHint currentHint = new FixedHint(null);
public CustomResourceLeakDetector(Class<?> resourceType, int samplingInterval) {
super(resourceType, samplingInterval);
DETECTORS.add(this);
}
static void register() {
ResourceLeakDetector.setLevel(Level.PARANOID);
ResourceLeakDetectorFactory.setResourceLeakDetectorFactory(new ResourceLeakDetectorFactory() {
@Override
public <T> ResourceLeakDetector<T> newResourceLeakDetector(Class<T> resource, int samplingInterval, long maxActive) {
// maxActive is ignored by netty anyway, that constructor is deprecated
return new CustomResourceLeakDetector<>(resource, samplingInterval);
}
});
}
public static void setCurrentInput(byte[] input) {
currentHint = new FixedHint(input);
}
@Override
protected boolean needReport() {
return true;
}
@Override
protected void reportTracedLeak(String resourceType, String records) {
LEAKS.add(new Leak(resourceType, records));
super.reportTracedLeak(resourceType, records);
}
@Override
protected void reportUntracedLeak(String resourceType) {
LEAKS.add(new Leak(resourceType, null));
super.reportUntracedLeak(resourceType);
}
@Override
protected Object getInitialHint(String resourceType) {
return currentHint;
}
static void reportLeaks() {
if (!LEAKS.isEmpty()) {
StringBuilder msg = new StringBuilder("Reported leaks! Probably unrelated to this particular run, though.\n");
for (Leak leak : LEAKS) {
msg.append(leak.resourceType).append("\n");
msg.append(leak.records).append("\n");
}
throw new RuntimeException(msg.toString());
}
}
static void reportStillOpen() {
Logger logger = LoggerFactory.getLogger(CustomResourceLeakDetector.class);
String found = null;
for (ResourceLeakDetector<?> detector : DETECTORS) {
Set<?> s = (Set<?>) ALL_LEAKS_FIELD.get(detector);
for (Object o : s) {
String v = o.toString();
if (v.contains("<clinit>")) {
logger.debug("Skipping still-open resource that has a <clinit> stack, so is probably irrelevant.");
} else {
logger.info("Still open: {}", v);
found = v;
}
}
}
if (found != null) {
throw new RuntimeException("Still open: " + found);
}
}
private record Leak(String resourceType, String records) {
}
private static final class FixedHint implements ResourceLeakHint {
private final String msg;
private FixedHint(byte[] associatedInput) {
this.msg = "Associated input: " + (associatedInput == null ? "<none>" : Base64.getEncoder().encodeToString(associatedInput));
}
@Override
public String toHintString() {
return msg;
}
}
}