Introduce ObjectCleaner and use it in FastThreadLocal to ensure FastThreadLocal.onRemoval(...) is called#7463
Introduce ObjectCleaner and use it in FastThreadLocal to ensure FastThreadLocal.onRemoval(...) is called#7463normanmaurer wants to merge 1 commit into4.1from
Conversation
|
There is still some TODO to fix ... Just wanted to show the idea |
9b1ca75 to
15a1c67
Compare
|
@normanmaurer could the same approach be used with Recycler instead of finalize? |
|
@johnou not sure what you refer to with finalize.... any pointers? |
|
io.netty.util.Recycler.WeakOrderQueue#finalize @Override
protected void finalize() throws Throwable {
try {
super.finalize();
} finally {
// We need to reclaim all space that was reserved by this WeakOrderQueue so we not run out of space in
// the stack. This is needed as we not have a good life-time control over the queue as it is used in a
// WeakHashMap which will drop it at any time.
Link link = head;
while (link != null) {
reclaimSpace(LINK_CAPACITY);
link = link.next;
}
}
} |
|
Yes most likely
… Am 01.12.2017 um 17:20 schrieb Johno Crawford ***@***.***>:
@normanmaurer
io.netty.util.Recycler.WeakOrderQueue#finalize
@OverRide
protected void finalize() throws Throwable {
try {
super.finalize();
} finally {
// We need to reclaim all space that was reserved by this WeakOrderQueue so we not run out of space in
// the stack. This is needed as we not have a good life-time control over the queue as it is used in a
// WeakHashMap which will drop it at any time.
Link link = head;
while (link != null) {
reclaimSpace(LINK_CAPACITY);
link = link.next;
}
}
}
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
|
ejona86
left a comment
There was a problem hiding this comment.
I don't see how this would avoid ClassLoader leaks. In fact, it seems strictly worse.
As I mentioned in #7446 (comment), I think there is a fundamental issue with cleaning anything retained from a static via a background-based cleaner.
| Thread cleanupThread = new Thread(new Runnable() { | ||
| @Override | ||
| public void run() { | ||
| for (;;) { |
There was a problem hiding this comment.
So this runs forever? Isn't that guaranteed to be a classloader leak via this Runnable?
15a1c67 to
afc3aa0
Compare
ejona86
left a comment
There was a problem hiding this comment.
This seems fine to me, other than the ThreadCleaner looping forever. And actually, starting a thread from a static initializer also seems a bit worrisome.
| } | ||
| return cache; | ||
| } | ||
| // No caching for non FastThreadLocalThreads. |
There was a problem hiding this comment.
This comment looks out-of-date. Remove?
So, this turned out to be semi-wrong. Or rather, if you leave a Thread running, "you're gonna have a bad time." I realized that even using So I'm not trying to solve that any more :-). And that makes multiple solutions we've considered solvable. The only thing we need to be wary of is clearing the Thread.contextClassLoader. |
fa6d1d7 to
6e971bc
Compare
| @@ -440,14 +441,26 @@ protected synchronized PoolThreadCache initialValue() { | |||
| if (useCacheForAllThreads || current instanceof FastThreadLocalThread) { | |||
| // If our FastThreadLocalThread will call FastThreadLocal.removeAll() we not need to use | |||
| // the ThreadDeathWatcher to release memory from the PoolThreadCache once the Thread dies. | |||
There was a problem hiding this comment.
+1 ... Let me do this
|
|
||
| // We store the Thread in a WeakReference as otherwise we may be the only ones that still hold a strong | ||
| // Reference to the Thread itself after it died. | ||
| final WeakReference<Thread> threadRef; |
There was a problem hiding this comment.
should this be in the same pull request?
There was a problem hiding this comment.
I did it in the same as I noticed it here because the Thread was never GCed. Maybe I should do it in a separate PR tho..
| * Allows a way to register some {@link Runnable} that will executed once there are no references to the {@link Thread} | ||
| * anymore. This typically happens once the {@link Thread} dies / completes. | ||
| */ | ||
| public final class ThreadCleaner { |
There was a problem hiding this comment.
nope... WeakReference was good enough.
|
Out of curiosity: why not spawn the cleaner thread when the original thread goes away, rather than when it is first attached? That would keep the number of active threads down. Also, why not do the cleanup in a |
|
@carl-mastrangelo answers / comments below....
How would you detect that the original Thread goes away ? Also I think its not so bad to have one extra thread that is doing the cleanup for everything. I will most likely use it in other places as well at some point.
I actually had a PR (#7446) with a |
|
@carl-mastrangelo Object.finalize is deprecated, the recommended replacements are PhantomReference and the new Cleaner class. |
|
@johnou @ejona86 @carl-mastrangelo PTAL again as I changed the approach a bit in a followup commit that I pushed. So the "real problem" why we needed all of this is because of the user uses a non Because of this I now moved the usage of WDYT ? |
| } | ||
| } | ||
|
|
||
| private static void removeAll(InternalThreadLocalMap threadLocalMap) { |
There was a problem hiding this comment.
Is there a reason this was split out into its own function?
I had expected the Runnable passed to ThreadCleaner would use it, but it uses remove(...) instead. It's a bit hard to determine what's the difference between the two methods.
There was a problem hiding this comment.
Actually sorry it should use removeAll and I messed up :(
There was a problem hiding this comment.
Sorry what I wanted to say was that we not need to factor out removeAll and can just call remove... will fix it.
It also means that you'll use the ThreadCleaner even if nothing stored in the FastThreadLocal needs cleaning. Normally that'd be rare, but if caching is disabled for non-FastThreadLocalThreads, then it'd be more frequent. Not sure it matters at all. |
|
@ejona86 yeah I agree... That said I think it may be still the preferred way to fix it and ensure |
9a536e4 to
f560ac8
Compare
| */ | ||
| @UnstableApi | ||
| public static boolean willCleanupFastThreadLocals(Thread thread) { | ||
| return thread instanceof FastThreadLocalThread && ((FastThreadLocalThread) thread) |
There was a problem hiding this comment.
might be clearer to break on &&
| // This will hold a reference to the ThreadCleanerReference which will be removed once we called cleanup() | ||
| private static final Set<ThreadCleanerReference> LIVE_SET = new ConcurrentSet<ThreadCleanerReference>(); | ||
| private static final ReferenceQueue<Object> REFERENCE_QUEUE = new ReferenceQueue<Object>(); | ||
| private static final AtomicBoolean CLEANER_RUNNING = new AtomicBoolean(false); |
There was a problem hiding this comment.
nit: caps are usually for constants.
There was a problem hiding this comment.
I think CAPS are for things that are static final so I think this is fine. Sure the content can be changed but this is ok IMHO.
| cleanupThread.setName("ThreadCleanerReaper"); | ||
|
|
||
| // This Thread is not a daemon as it will die once all references to the registered Threads will go away. | ||
| cleanupThread.setDaemon(false); |
There was a problem hiding this comment.
Daemon is for not blocking shutdown. In a shutdown scenario, how important is it to clean up refs?
There was a problem hiding this comment.
actually I think we could also just use a non-daemon thread as the thread will go away once there is no references anymore.
I'm fine with it. It might be unnecessary in some cases, but doesn't seem like it hurts much and does remove a type of bug-prone behavior. |
a232792 to
18eedc6
Compare
|
@carl-mastrangelo PTAL again |
Scottmitch
left a comment
There was a problem hiding this comment.
few questions ... GC sensitive tests make me 😢 but not sure of a better alternative.
| LIVE_SET.add(reference); | ||
|
|
||
| // Check if there is already | ||
| if (CLEANER_RUNNING.compareAndSet(false, true)) { |
There was a problem hiding this comment.
we should able to avoid the CAS operation most of the time by doing a read first because I assume should be true more often than not?
There was a problem hiding this comment.
again I think we should not care as this should not happen very frequently and the CAS is the thing that I would be concerned last about (as the other stuff will be the "expensive")
There was a problem hiding this comment.
fair enough ... I guess even if folks are doing get/remove this may not buy much.
| * This should only be used if there are no other ways to execute some cleanup once the {@link Thread} dies as | ||
| * it is not a cheap way to handle the cleanup. | ||
| */ | ||
| public static void register(Thread thread, Runnable cleanupTask) { |
There was a problem hiding this comment.
this class is internal ... consider:
- exposing a method where the user can directly create a
ThreadCleanerReferenceto avoid allocating aRunnableandThreadCleanerReference - Make the class package private and you can use the same technique as above to reduce object allocation.
There was a problem hiding this comment.
I am not concerned about this as this should be something that is done very frequently and I think this design is cleaner.
There was a problem hiding this comment.
Agreed the current APIs are nicer. However it is internal and doesn't need to be generic right? We could potentially save 1 object allocation per thread local. Since it is internal we can always change it later but maybe nice to keep thread local overhead down if we don't have to be generic.
There was a problem hiding this comment.
@Scottmitch as discussed offline I will merge it as it is for now and we can address this if we find out its an issue later on. The current design is nicer :)
There was a problem hiding this comment.
sgtm ... internal == change later if necessary
| } | ||
| } | ||
| if (interrupted) { | ||
| // As we catched the InterruptedException above we should mark the Thread as interrupted. |
|
@Scottmitch yeah I think how we test here is the best we can do for now... |
4a997ed to
c756364
Compare
Scottmitch
left a comment
There was a problem hiding this comment.
1 suggestion (#7463 (comment)) but lgtm
|
cc: @zpencer |
…hreadLocal.onRemoval(...) is called Motivation: There is no guarantee that FastThreadLocal.onRemoval(...) is called if the FastThreadLocal is used by "non" FastThreacLocalThreads. This can lead to all sort of problems, like for example memory leaks as direct memory is not correctly cleaned up etc. Beside this we use ThreadDeathWatcher to check if we need to release buffers back to the pool when thread local caches are collected. In the past ThreadDeathWatcher was used which will need to "wakeup" every second to check if the registered Threads are still alive. If we can ensure FastThreadLocal.onRemoval(...) is called we do not need this anymore. Modifications: - Introduce ObjectCleaner and use it to ensure FastThreadLocal.onRemoval(...) is always called when a Thread is collected. - Deprecate ThreadDeathWatcher - Add unit tests. Result: Consistent way of cleanup FastThreadLocals when a Thread is collected.
c756364 to
f14cfdc
Compare
|
@mosesn you may be interested as well. |
| Runnable runnable = new Runnable() { | ||
| @Override | ||
| public void run() { | ||
| assertEquals(Thread.currentThread().getName(), threadLocal.get()); |
There was a problem hiding this comment.
A bit late to this PR, but what happens if code sets a FastThreadLocal and the thread exits before it's read? It looks like the cleaner code is only registered on the get path.
Uh oh!
There was an error while loading. Please reload this page.