-
-
Notifications
You must be signed in to change notification settings - Fork 15.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Deprecate and ignore ResourceLeakDetector's maxActive parameter #6289
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,9 +29,13 @@ | |
import java.util.concurrent.RejectedExecutionException; | ||
import java.util.concurrent.ThreadFactory; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.concurrent.atomic.AtomicBoolean; | ||
import java.util.concurrent.atomic.AtomicInteger; | ||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; | ||
import java.util.concurrent.atomic.AtomicLong; | ||
|
||
import static io.netty.util.internal.StringUtil.simpleClassName; | ||
|
||
/** | ||
* A {@link Timer} optimized for approximated I/O timeout scheduling. | ||
* | ||
|
@@ -78,8 +82,11 @@ public class HashedWheelTimer implements Timer { | |
static final InternalLogger logger = | ||
InternalLoggerFactory.getInstance(HashedWheelTimer.class); | ||
|
||
private static final AtomicInteger INSTANCE_COUNTER = new AtomicInteger(); | ||
private static final AtomicBoolean WARNED_TOO_MANY_INSTANCES = new AtomicBoolean(); | ||
private static final int INSTANCE_COUNT_LIMIT = 64; | ||
private static final ResourceLeakDetector<HashedWheelTimer> leakDetector = ResourceLeakDetectorFactory.instance() | ||
.newResourceLeakDetector(HashedWheelTimer.class, 1, Runtime.getRuntime().availableProcessors() * 4L); | ||
.newResourceLeakDetector(HashedWheelTimer.class, 1); | ||
|
||
private static final AtomicIntegerFieldUpdater<HashedWheelTimer> WORKER_STATE_UPDATER = | ||
AtomicIntegerFieldUpdater.newUpdater(HashedWheelTimer.class, "workerState"); | ||
|
@@ -266,6 +273,24 @@ public HashedWheelTimer( | |
leak = leakDetection || !workerThread.isDaemon() ? leakDetector.track(this) : null; | ||
|
||
this.maxPendingTimeouts = maxPendingTimeouts; | ||
|
||
if (INSTANCE_COUNTER.incrementAndGet() > INSTANCE_COUNT_LIMIT && | ||
WARNED_TOO_MANY_INSTANCES.compareAndSet(false, true)) { | ||
reportTooManyInstances(); | ||
} | ||
} | ||
|
||
@Override | ||
protected void finalize() throws Throwable { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
try { | ||
super.finalize(); | ||
} finally { | ||
// This object is going to be GCed and it is assumed the ship has sailed to do a proper shutdown. If | ||
// we have not yet shutdown then we want to make sure we decrement the active instance count. | ||
if (WORKER_STATE_UPDATER.getAndSet(this, WORKER_STATE_SHUTDOWN) != WORKER_STATE_SHUTDOWN) { | ||
INSTANCE_COUNTER.decrementAndGet(); | ||
} | ||
} | ||
} | ||
|
||
private static HashedWheelBucket[] createWheel(int ticksPerWheel) { | ||
|
@@ -337,33 +362,37 @@ public Set<Timeout> stop() { | |
|
||
if (!WORKER_STATE_UPDATER.compareAndSet(this, WORKER_STATE_STARTED, WORKER_STATE_SHUTDOWN)) { | ||
// workerState can be 0 or 2 at this moment - let it always be 2. | ||
WORKER_STATE_UPDATER.set(this, WORKER_STATE_SHUTDOWN); | ||
|
||
if (leak != null) { | ||
boolean closed = leak.close(this); | ||
assert closed; | ||
if (WORKER_STATE_UPDATER.getAndSet(this, WORKER_STATE_SHUTDOWN) != WORKER_STATE_SHUTDOWN) { | ||
INSTANCE_COUNTER.decrementAndGet(); | ||
if (leak != null) { | ||
boolean closed = leak.close(this); | ||
assert closed; | ||
} | ||
} | ||
|
||
return Collections.emptySet(); | ||
} | ||
|
||
boolean interrupted = false; | ||
while (workerThread.isAlive()) { | ||
workerThread.interrupt(); | ||
try { | ||
workerThread.join(100); | ||
} catch (InterruptedException ignored) { | ||
interrupted = true; | ||
try { | ||
boolean interrupted = false; | ||
while (workerThread.isAlive()) { | ||
workerThread.interrupt(); | ||
try { | ||
workerThread.join(100); | ||
} catch (InterruptedException ignored) { | ||
interrupted = true; | ||
} | ||
} | ||
} | ||
|
||
if (interrupted) { | ||
Thread.currentThread().interrupt(); | ||
} | ||
|
||
if (leak != null) { | ||
boolean closed = leak.close(this); | ||
assert closed; | ||
if (interrupted) { | ||
Thread.currentThread().interrupt(); | ||
} | ||
} finally { | ||
INSTANCE_COUNTER.decrementAndGet(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe add any assert that this can never be negative ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it could actually go negative (e.g. overflow) as we don't strictly enforce the limit ... we just use it to warn for "too many instances". In practice I wouldn't expect this to happen but I'm not sure we should add an assert for it unless we strictly enforce it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah gotcha... good as it is |
||
if (leak != null) { | ||
boolean closed = leak.close(this); | ||
assert closed; | ||
} | ||
} | ||
return worker.unprocessedTimeouts(); | ||
} | ||
|
@@ -400,6 +429,13 @@ private boolean shouldLimitTimeouts() { | |
return maxPendingTimeouts > 0; | ||
} | ||
|
||
private static void reportTooManyInstances() { | ||
String resourceType = simpleClassName(HashedWheelTimer.class); | ||
logger.error("You are creating too many " + resourceType + " instances. " + | ||
resourceType + " is a shared resource that must be reused across the JVM," + | ||
"so that only a few instances are created."); | ||
} | ||
|
||
private final class Worker implements Runnable { | ||
private final Set<Timeout> unprocessedTimeouts = new HashSet<Timeout>(); | ||
|
||
|
@@ -636,7 +672,7 @@ public String toString() { | |
long remaining = deadline - currentTime + timer.startTime; | ||
|
||
StringBuilder buf = new StringBuilder(192) | ||
.append(StringUtil.simpleClassName(this)) | ||
.append(simpleClassName(this)) | ||
.append('(') | ||
.append("deadline: "); | ||
if (remaining > 0) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,7 +37,7 @@ public abstract class ResourceLeakDetectorFactory { | |
/** | ||
* Get the singleton instance of this factory class. | ||
* | ||
* @return - the current {@link ResourceLeakDetectorFactory} | ||
* @return the current {@link ResourceLeakDetectorFactory} | ||
*/ | ||
public static ResourceLeakDetectorFactory instance() { | ||
return factoryInstance; | ||
|
@@ -48,7 +48,7 @@ public static ResourceLeakDetectorFactory instance() { | |
* {@link ResourceLeakDetector} is called by all the callers of this factory. That is, before initializing a | ||
* Netty Bootstrap. | ||
* | ||
* @param factory - the instance that will become the current {@link ResourceLeakDetectorFactory}'s singleton | ||
* @param factory the instance that will become the current {@link ResourceLeakDetectorFactory}'s singleton | ||
*/ | ||
public static void setResourceLeakDetectorFactory(ResourceLeakDetectorFactory factory) { | ||
factoryInstance = ObjectUtil.checkNotNull(factory, "factory"); | ||
|
@@ -57,30 +57,47 @@ public static void setResourceLeakDetectorFactory(ResourceLeakDetectorFactory fa | |
/** | ||
* Returns a new instance of a {@link ResourceLeakDetector} with the given resource class. | ||
* | ||
* @param resource - the resource class used to initialize the {@link ResourceLeakDetector} | ||
* @param <T> - the type of the resource class | ||
* @return - a new instance of {@link ResourceLeakDetector} | ||
* @param resource the resource class used to initialize the {@link ResourceLeakDetector} | ||
* @param <T> the type of the resource class | ||
* @return a new instance of {@link ResourceLeakDetector} | ||
*/ | ||
public final <T> ResourceLeakDetector<T> newResourceLeakDetector(Class<T> resource) { | ||
return newResourceLeakDetector(resource, ResourceLeakDetector.DEFAULT_SAMPLING_INTERVAL, Long.MAX_VALUE); | ||
return newResourceLeakDetector(resource, ResourceLeakDetector.DEFAULT_SAMPLING_INTERVAL); | ||
} | ||
|
||
/** | ||
* @deprecated Use {@link #newResourceLeakDetector(Class, int)} instead. | ||
* <p> | ||
* Returns a new instance of a {@link ResourceLeakDetector} with the given resource class. | ||
* | ||
* @param resource - the resource class used to initialize the {@link ResourceLeakDetector} | ||
* @param samplingInterval - the interval on which sampling takes place | ||
* @param maxActive - the maximum active instances | ||
* @param <T> - the type of the resource class | ||
* @return - a new instance of {@link ResourceLeakDetector} | ||
* @param resource the resource class used to initialize the {@link ResourceLeakDetector} | ||
* @param samplingInterval the interval on which sampling takes place | ||
* @param maxActive This is deprecated and will be ignored. | ||
* @param <T> the type of the resource class | ||
* @return a new instance of {@link ResourceLeakDetector} | ||
*/ | ||
@Deprecated | ||
public abstract <T> ResourceLeakDetector<T> newResourceLeakDetector( | ||
Class<T> resource, int samplingInterval, long maxActive); | ||
|
||
/** | ||
* Returns a new instance of a {@link ResourceLeakDetector} with the given resource class. | ||
* | ||
* @param resource the resource class used to initialize the {@link ResourceLeakDetector} | ||
* @param samplingInterval the interval on which sampling takes place | ||
* @param <T> the type of the resource class | ||
* @return a new instance of {@link ResourceLeakDetector} | ||
*/ | ||
@SuppressWarnings("deprecation") | ||
public <T> ResourceLeakDetector<T> newResourceLeakDetector(Class<T> resource, int samplingInterval) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
return newResourceLeakDetector(resource, ResourceLeakDetector.DEFAULT_SAMPLING_INTERVAL, Long.MAX_VALUE); | ||
} | ||
|
||
/** | ||
* Default implementation that loads custom leak detector via system property | ||
*/ | ||
private static final class DefaultResourceLeakDetectorFactory extends ResourceLeakDetectorFactory { | ||
private final Constructor<?> obsoleteCustomClassConstructor; | ||
private final Constructor<?> customClassConstructor; | ||
|
||
DefaultResourceLeakDetectorFactory() { | ||
|
@@ -96,10 +113,15 @@ public String run() { | |
logger.error("Could not access System property: io.netty.customResourceLeakDetector", cause); | ||
customLeakDetector = null; | ||
} | ||
customClassConstructor = customLeakDetector == null ? null : customClassConstructor(customLeakDetector); | ||
if (customLeakDetector == null) { | ||
obsoleteCustomClassConstructor = customClassConstructor = null; | ||
} else { | ||
obsoleteCustomClassConstructor = obsoleteCustomClassConstructor(customLeakDetector); | ||
customClassConstructor = customClassConstructor(customLeakDetector); | ||
} | ||
} | ||
|
||
private static Constructor<?> customClassConstructor(String customLeakDetector) { | ||
private static Constructor<?> obsoleteCustomClassConstructor(String customLeakDetector) { | ||
try { | ||
final Class<?> detectorClass = Class.forName(customLeakDetector, true, | ||
PlatformDependent.getSystemClassLoader()); | ||
|
@@ -116,15 +138,56 @@ private static Constructor<?> customClassConstructor(String customLeakDetector) | |
return null; | ||
} | ||
|
||
private static Constructor<?> customClassConstructor(String customLeakDetector) { | ||
try { | ||
final Class<?> detectorClass = Class.forName(customLeakDetector, true, | ||
PlatformDependent.getSystemClassLoader()); | ||
|
||
if (ResourceLeakDetector.class.isAssignableFrom(detectorClass)) { | ||
return detectorClass.getConstructor(Class.class, int.class); | ||
} else { | ||
logger.error("Class {} does not inherit from ResourceLeakDetector.", customLeakDetector); | ||
} | ||
} catch (Throwable t) { | ||
logger.error("Could not load custom resource leak detector class provided: {}", | ||
customLeakDetector, t); | ||
} | ||
return null; | ||
} | ||
|
||
@SuppressWarnings("deprecation") | ||
@Override | ||
public <T> ResourceLeakDetector<T> newResourceLeakDetector( | ||
Class<T> resource, int samplingInterval, long maxActive) { | ||
if (customClassConstructor != null) { | ||
public <T> ResourceLeakDetector<T> newResourceLeakDetector(Class<T> resource, int samplingInterval, | ||
long maxActive) { | ||
if (obsoleteCustomClassConstructor != null) { | ||
try { | ||
@SuppressWarnings("unchecked") | ||
ResourceLeakDetector<T> leakDetector = | ||
(ResourceLeakDetector<T>) customClassConstructor.newInstance( | ||
(ResourceLeakDetector<T>) obsoleteCustomClassConstructor.newInstance( | ||
resource, samplingInterval, maxActive); | ||
logger.debug("Loaded custom ResourceLeakDetector: {}", | ||
obsoleteCustomClassConstructor.getDeclaringClass().getName()); | ||
return leakDetector; | ||
} catch (Throwable t) { | ||
logger.error( | ||
"Could not load custom resource leak detector provided: {} with the given resource: {}", | ||
obsoleteCustomClassConstructor.getDeclaringClass().getName(), resource, t); | ||
} | ||
} | ||
|
||
ResourceLeakDetector<T> resourceLeakDetector = new ResourceLeakDetector<T>(resource, samplingInterval, | ||
maxActive); | ||
logger.debug("Loaded default ResourceLeakDetector: {}", resourceLeakDetector); | ||
return resourceLeakDetector; | ||
} | ||
|
||
@Override | ||
public <T> ResourceLeakDetector<T> newResourceLeakDetector(Class<T> resource, int samplingInterval) { | ||
if (customClassConstructor != null) { | ||
try { | ||
@SuppressWarnings("unchecked") | ||
ResourceLeakDetector<T> leakDetector = | ||
(ResourceLeakDetector<T>) customClassConstructor.newInstance(resource, samplingInterval); | ||
logger.debug("Loaded custom ResourceLeakDetector: {}", | ||
customClassConstructor.getDeclaringClass().getName()); | ||
return leakDetector; | ||
|
@@ -135,8 +198,7 @@ public <T> ResourceLeakDetector<T> newResourceLeakDetector( | |
} | ||
} | ||
|
||
ResourceLeakDetector<T> resourceLeakDetector = new ResourceLeakDetector<T>( | ||
resource, samplingInterval, maxActive); | ||
ResourceLeakDetector<T> resourceLeakDetector = new ResourceLeakDetector<T>(resource, samplingInterval); | ||
logger.debug("Loaded default ResourceLeakDetector: {}", resourceLeakDetector); | ||
return resourceLeakDetector; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Scottmitch finalizers make me 😢
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
me too 😭
I'm open for alternatives.