Motivation: We can use the diamond operator these days. Modification: Use diamond operator whenever possible. Result: More modern code and less boiler-plate.
173 lines
6.4 KiB
Java
173 lines
6.4 KiB
Java
/*
|
|
* Copyright 2016 The Netty Project
|
|
*
|
|
* The Netty Project licenses this file to you 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:
|
|
*
|
|
* http://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.netty.util;
|
|
|
|
import org.junit.Test;
|
|
|
|
import java.util.ArrayDeque;
|
|
import java.util.Queue;
|
|
import java.util.concurrent.CyclicBarrier;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
|
|
public class ResourceLeakDetectorTest {
|
|
|
|
@Test(timeout = 60000)
|
|
public void testConcurrentUsage() throws Throwable {
|
|
final AtomicBoolean finished = new AtomicBoolean();
|
|
final AtomicReference<Throwable> error = new AtomicReference<>();
|
|
// With 50 threads issue #6087 is reproducible on every run.
|
|
Thread[] threads = new Thread[50];
|
|
final CyclicBarrier barrier = new CyclicBarrier(threads.length);
|
|
for (int i = 0; i < threads.length; i++) {
|
|
Thread t = new Thread(new Runnable() {
|
|
Queue<LeakAwareResource> resources = new ArrayDeque<>(100);
|
|
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
barrier.await();
|
|
|
|
// Run 10000 times or until the test is marked as finished.
|
|
for (int b = 0; b < 1000 && !finished.get(); b++) {
|
|
|
|
// Allocate 100 LeakAwareResource per run and close them after it.
|
|
for (int a = 0; a < 100; a++) {
|
|
DefaultResource resource = new DefaultResource();
|
|
ResourceLeakTracker<Resource> leak = DefaultResource.detector.track(resource);
|
|
LeakAwareResource leakAwareResource = new LeakAwareResource(resource, leak);
|
|
resources.add(leakAwareResource);
|
|
}
|
|
if (closeResources(true)) {
|
|
finished.set(true);
|
|
}
|
|
}
|
|
} catch (InterruptedException e) {
|
|
Thread.currentThread().interrupt();
|
|
} catch (Throwable e) {
|
|
error.compareAndSet(null, e);
|
|
} finally {
|
|
// Just close all resource now without assert it to eliminate more reports.
|
|
closeResources(false);
|
|
}
|
|
}
|
|
|
|
private boolean closeResources(boolean checkClosed) {
|
|
for (;;) {
|
|
LeakAwareResource r = resources.poll();
|
|
if (r == null) {
|
|
return false;
|
|
}
|
|
boolean closed = r.close();
|
|
if (checkClosed && !closed) {
|
|
error.compareAndSet(null,
|
|
new AssertionError("ResourceLeak.close() returned 'false' but expected 'true'"));
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
threads[i] = t;
|
|
t.start();
|
|
}
|
|
|
|
// Just wait until all threads are done.
|
|
for (Thread t: threads) {
|
|
t.join();
|
|
}
|
|
|
|
// Check if we had any leak reports in the ResourceLeakDetector itself
|
|
DefaultResource.detector.assertNoErrors();
|
|
|
|
assertNoErrors(error);
|
|
}
|
|
|
|
// Mimic the way how we implement our classes that should help with leak detection
|
|
private static final class LeakAwareResource implements Resource {
|
|
private final Resource resource;
|
|
private final ResourceLeakTracker<Resource> leak;
|
|
|
|
LeakAwareResource(Resource resource, ResourceLeakTracker<Resource> leak) {
|
|
this.resource = resource;
|
|
this.leak = leak;
|
|
}
|
|
|
|
@Override
|
|
public boolean close() {
|
|
// Using ResourceLeakDetector.close(...) to prove this fixes the leak problem reported
|
|
// in https://github.com/netty/netty/issues/6034 .
|
|
//
|
|
// The following implementation would produce a leak:
|
|
// return leak.close();
|
|
return leak.close(resource);
|
|
}
|
|
}
|
|
|
|
private static final class DefaultResource implements Resource {
|
|
// Sample every allocation
|
|
static final TestResourceLeakDetector<Resource> detector = new TestResourceLeakDetector<>(
|
|
Resource.class, 1, Integer.MAX_VALUE);
|
|
|
|
@Override
|
|
public boolean close() {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private interface Resource {
|
|
boolean close();
|
|
}
|
|
|
|
private static void assertNoErrors(AtomicReference<Throwable> ref) throws Throwable {
|
|
Throwable error = ref.get();
|
|
if (error != null) {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
private static final class TestResourceLeakDetector<T> extends ResourceLeakDetector<T> {
|
|
|
|
private final AtomicReference<Throwable> error = new AtomicReference<>();
|
|
|
|
TestResourceLeakDetector(Class<?> resourceType, int samplingInterval, long maxActive) {
|
|
super(resourceType, samplingInterval, maxActive);
|
|
}
|
|
|
|
@Override
|
|
protected void reportTracedLeak(String resourceType, String records) {
|
|
reportError(new AssertionError("Leak reported for '" + resourceType + "':\n" + records));
|
|
}
|
|
|
|
@Override
|
|
protected void reportUntracedLeak(String resourceType) {
|
|
reportError(new AssertionError("Leak reported for '" + resourceType + '\''));
|
|
}
|
|
|
|
@Override
|
|
protected void reportInstancesLeak(String resourceType) {
|
|
reportError(new AssertionError("Leak reported for '" + resourceType + '\''));
|
|
}
|
|
|
|
private void reportError(AssertionError cause) {
|
|
error.compareAndSet(null, cause);
|
|
}
|
|
|
|
void assertNoErrors() throws Throwable {
|
|
ResourceLeakDetectorTest.assertNoErrors(error);
|
|
}
|
|
}
|
|
}
|