-
Notifications
You must be signed in to change notification settings - Fork 15.4k
KAFKA-12375: don't reuse thread.id until a thread has fully shut down #10215
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
Changes from 3 commits
4fc17dd
0ee2ef5
1094754
6699084
2409f68
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 |
|---|---|---|
|
|
@@ -92,6 +92,7 @@ | |
| import java.util.Set; | ||
| import java.util.TreeMap; | ||
| import java.util.UUID; | ||
| import java.util.concurrent.atomic.AtomicInteger; | ||
| import java.util.concurrent.ExecutionException; | ||
| import java.util.concurrent.Executors; | ||
| import java.util.concurrent.ScheduledExecutorService; | ||
|
|
@@ -463,9 +464,8 @@ private void replaceStreamThread(final Throwable throwable) { | |
| closeToError(); | ||
| } | ||
| final StreamThread deadThread = (StreamThread) Thread.currentThread(); | ||
| threads.remove(deadThread); | ||
|
Contributor
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. I remember that we had the replace use the same ID for a reason. (maybe it had to do with rebalancing?). I don't think there should be a problem to try to get the same ID by waiting a bit in the replace thread
Member
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. We cannot wait here until the dead thread is shutdown because the shutdown happens after
Contributor
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.
Member
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. Sorry for not being clear enough. I was also referring to
Contributor
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 that makes sense. Another approach is using a temporary name. And then waiting the new thread until the old thread dies and takes the name. This is a bit complicated and I think it should only be done if it is necessary for the new thread to have the same name. And probably not in this PR but it could be an improvement done later
Member
Author
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. Yeah I think swapping the names would make the code unnecessarily complicated, and it would definitely make reading the logs more difficult.
Contributor
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. since the name doesn't matter I wonder why we spent so much effort making sure it had the same name? |
||
| addStreamThread(); | ||
| deadThread.shutdown(); | ||
| addStreamThread(); | ||
| if (throwable instanceof RuntimeException) { | ||
| throw (RuntimeException) throwable; | ||
| } else if (throwable instanceof Error) { | ||
|
|
@@ -1047,9 +1047,15 @@ private Optional<String> removeStreamThread(final long timeoutMs) throws Timeout | |
| if (!streamThread.waitOnThreadState(StreamThread.State.DEAD, timeoutMs - begin)) { | ||
| log.warn("Thread " + streamThread.getName() + " did not shutdown in the allotted time"); | ||
| timeout = true; | ||
| // Don't remove from threads until shutdown is complete. We will trim it from the | ||
| // list once it reaches DEAD, and if for some reason it's hanging indefinitely in the | ||
|
Contributor
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. Where do we trim this list? I don't thing we do. In the begging of
Member
Author
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. We're trimming it in |
||
| // shutdown then we should just consider this thread.id to be burned | ||
| } else { | ||
| threads.remove(streamThread); | ||
|
Contributor
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. If we purge the dead threads before we add new ones and if we remove the assumption that there are no dead threads in the thread list we can just not remove the threads in remove thread. This will make it there should be no concern about the cache size changing when a thread is removing itself. And make the risk we took about memory overflows unnecessary.
Member
Author
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. Yeah, I was mainly trying to keep things simple. There's definitely a tradeoff in when we resize the cache: either we resize it right away and risk an OOM or we resize it whenever we find newly DEAD threads but potentially have to wait to "reclaim" the memory of a thread.
Contributor
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. I think we can leave it for now, if we should see problems this could be a fix, we don't run a single thread soak so we won't see this issue ourselves but there are many single thread applications that could start using this and we should see if they have problems |
||
| } | ||
| } | ||
| threads.remove(streamThread); | ||
| // Don't remove from threads until shutdown is complete since this will let another thread | ||
| // reuse its thread.id. We will trim any DEAD threads from the list later | ||
| final long cacheSizePerThread = getCacheSizePerThread(threads.size()); | ||
|
Member
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. We need to adapt the resizing of the cache per threaad to use only the number of non-DEAD stream threads instead of all stream threads in the list. There are other two locations where we use the size of the thread list to resize the cache per thread.
Contributor
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. Perviously we had relied on the fact there were no dead threads in the list
Member
Author
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. Good catch. I added a method |
||
| resizeThreadCache(cacheSizePerThread); | ||
| if (groupInstanceID.isPresent() && callingThreadIsNotCurrentStreamThread) { | ||
|
|
@@ -1094,16 +1100,32 @@ private Optional<String> removeStreamThread(final long timeoutMs) throws Timeout | |
| } | ||
|
|
||
| private int getNextThreadIndex() { | ||
| final HashSet<String> names = new HashSet<>(); | ||
| processStreamThread(thread -> names.add(thread.getName())); | ||
| final String baseName = clientId + "-StreamThread-"; | ||
| for (int i = 1; i <= threads.size(); i++) { | ||
| final String name = baseName + i; | ||
| if (!names.contains(name)) { | ||
| return i; | ||
| final HashSet<String> allLiveThreadNames = new HashSet<>(); | ||
| final AtomicInteger maxThreadId = new AtomicInteger(1); | ||
|
Member
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. Do we really need an atomic integer here?
Member
Author
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's because of the whole "variables used in a lambda must be final or effectively final" thing |
||
| synchronized (threads) { | ||
| processStreamThread(thread -> { | ||
| // trim any DEAD threads from the list so we can reuse the thread.id | ||
| // this is only safe to do once the thread has fully completed shutdown | ||
| if (thread.state() == StreamThread.State.DEAD) { | ||
| threads.remove(thread); | ||
| } else { | ||
| allLiveThreadNames.add(thread.getName()); | ||
| final int threadId = thread.getName().charAt(thread.getName().length() - 1); | ||
|
ableegoldman marked this conversation as resolved.
Outdated
|
||
| if (threadId > maxThreadId.get()) { | ||
| maxThreadId.set(threadId); | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| final String baseName = clientId + "-StreamThread-"; | ||
| for (int i = 1; i <= maxThreadId.get(); i++) { | ||
| final String name = baseName + i; | ||
| if (!allLiveThreadNames.contains(name)) { | ||
| return i; | ||
| } | ||
| } | ||
| return threads.size() + 1; | ||
| } | ||
| return threads.size() + 1; | ||
| } | ||
|
|
||
| private long getCacheSizePerThread(final int numStreamThreads) { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.