-
Notifications
You must be signed in to change notification settings - Fork 15.4k
KAFKA-10810: Replace stream threads #9697
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 6 commits
61cdbef
79b743a
d05ef97
0bd4aaf
060a0a4
d211da9
0708ca0
29fa8c0
d5b170c
6e96114
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 |
|---|---|---|
|
|
@@ -444,6 +444,25 @@ private void handleStreamsUncaughtException(final Throwable throwable, | |
| "The old handler will be ignored as long as a new handler is set."); | ||
| } | ||
| switch (action) { | ||
| case REPLACE_THREAD: | ||
| if (globalStreamThread != null && Thread.currentThread().getName().equals(globalStreamThread.getName())) { | ||
| log.warn("The global thread cannot be replaced. Reverting to shutting down the client."); | ||
| log.error("Encountered the following exception during processing " + | ||
| "and the registered exception handler opted to " + action + "." + | ||
| " The streams client is going to shut down now. ", throwable); | ||
| close(Duration.ZERO); | ||
| } | ||
| final StreamThread deadThread = (StreamThread) Thread.currentThread(); | ||
| threads.remove(deadThread); | ||
| addStreamThread(); | ||
| deadThread.shutdown(); | ||
|
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 need to shutdown the dead stream thread?
Contributor
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. I don't think it matters, it just set the thread state earlier, but we can delete it |
||
| if (throwable instanceof RuntimeException) { | ||
|
Contributor
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 need to throw the error or the task gets lost and we drop records |
||
| throw (RuntimeException) throwable; | ||
| } else if (throwable instanceof Error) { | ||
| throw (Error) throwable; | ||
| } else { | ||
| throw new RuntimeException("Unexpected checked exception caught in the uncaught exception handler", throwable); | ||
| } | ||
|
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. I think it would be cleaner to extract this code to a separate method.
Contributor
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 can do that |
||
| case SHUTDOWN_CLIENT: | ||
| log.error("Encountered the following exception during processing " + | ||
| "and the registered exception handler opted to " + action + "." + | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,6 +27,7 @@ public interface StreamsUncaughtExceptionHandler { | |
| * Enumeration that describes the response from the exception handler. | ||
| */ | ||
| enum StreamThreadExceptionResponse { | ||
| REPLACE_THREAD(0, "REPLACE_STREAM_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. I would rename it 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. Oh, good catch. Just a quick question: did we misname this option in the KIP? A StreamThread is a specific kind of thread. What I mean is that a GlobalStreamThread is not a StreamThread. Perhaps
Contributor
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 should be the string is
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. I actually would be in favor of calling the enum value
Contributor
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. What if we add an option to replace the global 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. Ah, now I got it! Sorry! Makes sense! In that case we can reuse |
||
| SHUTDOWN_CLIENT(1, "SHUTDOWN_KAFKA_STREAMS_CLIENT"), | ||
| SHUTDOWN_APPLICATION(2, "SHUTDOWN_KAFKA_STREAMS_APPLICATION"); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -53,6 +53,7 @@ | |
| import static org.apache.kafka.common.utils.Utils.mkEntry; | ||
| import static org.apache.kafka.common.utils.Utils.mkMap; | ||
| import static org.apache.kafka.common.utils.Utils.mkObjectProperties; | ||
| import static org.apache.kafka.streams.errors.StreamsUncaughtExceptionHandler.StreamThreadExceptionResponse.REPLACE_THREAD; | ||
| import static org.apache.kafka.streams.errors.StreamsUncaughtExceptionHandler.StreamThreadExceptionResponse.SHUTDOWN_APPLICATION; | ||
| import static org.apache.kafka.streams.errors.StreamsUncaughtExceptionHandler.StreamThreadExceptionResponse.SHUTDOWN_CLIENT; | ||
| import static org.apache.kafka.streams.integration.utils.IntegrationTestUtils.purgeLocalStreamsState; | ||
|
|
@@ -66,7 +67,7 @@ | |
| @SuppressWarnings("deprecation") //Need to call the old handler, will remove those calls when the old handler is removed | ||
| public class StreamsUncaughtExceptionHandlerIntegrationTest { | ||
| @ClassRule | ||
| public static final EmbeddedKafkaCluster CLUSTER = new EmbeddedKafkaCluster(1); | ||
| public static final EmbeddedKafkaCluster CLUSTER = new EmbeddedKafkaCluster(1, new Properties(), 0L, 0L); | ||
| public static final Duration DEFAULT_DURATION = Duration.ofSeconds(30); | ||
|
|
||
| @Rule | ||
|
|
@@ -145,6 +146,16 @@ public void shouldShutdownClient() throws InterruptedException { | |
| } | ||
| } | ||
|
|
||
| @Test | ||
| public void shouldReplaceThreads() throws InterruptedException { | ||
| testReplaceThreads(2); | ||
| } | ||
|
|
||
| @Test | ||
| public void shouldReplaceSingleThread() throws InterruptedException { | ||
| testReplaceThreads(1); | ||
| } | ||
|
|
||
| @Test | ||
| public void shouldShutdownMultipleThreadApplication() throws InterruptedException { | ||
| testShutdownApplication(2); | ||
|
|
@@ -202,6 +213,29 @@ private void testShutdownApplication(final int numThreads) throws InterruptedExc | |
| assertThat(processorValueCollector.size(), equalTo(1)); | ||
| } | ||
| } | ||
|
|
||
| private void testReplaceThreads(final int numThreads) throws InterruptedException { | ||
| properties.put(StreamsConfig.NUM_STREAM_THREADS_CONFIG, numThreads); | ||
| try (final KafkaStreams kafkaStreams = new KafkaStreams(builder.build(), properties)) { | ||
| kafkaStreams.setUncaughtExceptionHandler((t, e) -> fail("should not hit old handler")); | ||
|
|
||
| final AtomicInteger count = new AtomicInteger(); | ||
| kafkaStreams.setUncaughtExceptionHandler(exception -> { | ||
| count.getAndIncrement(); | ||
| if (count.get() > 2) { | ||
| return SHUTDOWN_CLIENT; | ||
| } | ||
| return 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. I think it would be better to have a test that shows that a new thread that replaced a failed one, actually is able to process records. So, I would let the new thread process some records and then shutdown the client with a normal close. Maybe similar applies to the shutdown tests. First let the client/application process some records and then throw an exception that shuts down the client/application. I guess, this last paragraph is something for a separate PR.
Contributor
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 can change the test so that we verify the replaced threads can process records. I am not sure that is necessary for the shutdown as testing if streams can process some records once started should be tested elsewhere, but in any case I think that the PR is not the place for this discussion |
||
| StreamsTestUtils.startKafkaStreamsAndWaitForRunningState(kafkaStreams); | ||
|
|
||
| produceMessages(0L, inputTopic, "A"); | ||
| waitForApplicationState(Collections.singletonList(kafkaStreams), KafkaStreams.State.NOT_RUNNING, DEFAULT_DURATION); | ||
|
|
||
| assertThat(processorValueCollector.size(), equalTo(3)); | ||
|
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. It's not obvious to me how this verifies that the thread actually got replaced. Maybe an explanatory comment is in order?
Contributor
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 idea |
||
| //because we only have 2 threads at the start and each record kills a thread we must have replaced threads | ||
|
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. Could you please be a bit clearer in the explanatory comment? BTW, we execute this test also once with just one stream thread so the 2 stream threads in the comment are not correct. Also, wouldn't it be better to explain the verification in the call to
Contributor
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. I edited the test to be based on the number of thread instead of hard coding. And gave a reason |
||
| } | ||
| } | ||
| } | ||
|
wcarlson5 marked this conversation as resolved.
|
||
|
|
||
|
|
||
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.
removing from the thread list does 2 things
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.
To ensure #2 holds, do we need to have a mutex on thread creation? Or does it not matter?
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.
It won't matter. Ensures is too strong of a word. IF there has been other threads removed before this it may have a different name. However this ensures that the replacement thread will never have a thread index larger than the number of threads