-
Notifications
You must be signed in to change notification settings - Fork 15.4k
KAFKA-10017: fix 2 issues in EosBetaUpgradeIntegrationTest #9733
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 2 commits
d3693f4
ee037dd
89eb7ef
f2f0967
62cbfb6
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 |
|---|---|---|
|
|
@@ -82,7 +82,7 @@ | |
| import static org.apache.kafka.test.TestUtils.waitForCondition; | ||
| import static org.hamcrest.CoreMatchers.equalTo; | ||
| import static org.hamcrest.MatcherAssert.assertThat; | ||
| import static org.junit.Assert.fail; | ||
| import static org.junit.Assert.assertFalse; | ||
|
|
||
| @RunWith(Parameterized.class) | ||
| @Category({IntegrationTest.class}) | ||
|
|
@@ -123,6 +123,8 @@ public static Collection<Boolean[]> data() { | |
| KeyValue.pair(KafkaStreams.State.PENDING_SHUTDOWN, KafkaStreams.State.NOT_RUNNING) | ||
| ) | ||
| ); | ||
| private static final KeyValue<KafkaStreams.State, KafkaStreams.State> REBALANCED_RUNNING = | ||
| KeyValue.pair(State.REBALANCING, State.RUNNING); | ||
|
|
||
| @ClassRule | ||
| public static final EmbeddedKafkaCluster CLUSTER = new EmbeddedKafkaCluster( | ||
|
|
@@ -135,8 +137,13 @@ public static Collection<Boolean[]> data() { | |
| private final static String CONSUMER_GROUP_ID = "readCommitted"; | ||
| private final static String MULTI_PARTITION_INPUT_TOPIC = "multiPartitionInputTopic"; | ||
| private final static String MULTI_PARTITION_OUTPUT_TOPIC = "multiPartitionOutputTopic"; | ||
| private final static String APP_DIR_1 = "appDir1"; | ||
| private final static String APP_DIR_2 = "appDir2"; | ||
| private final static String UNEXPECTED_EXCEPTION_MSG = "Fail the test since we got an unexpected exception or" + | ||
| "there are too many exceptions thrown, please check standard error log for more info."; | ||
| private final String storeName = "store"; | ||
|
|
||
|
|
||
| private final StableAssignmentListener assignmentListener = new StableAssignmentListener(); | ||
|
|
||
| private final AtomicBoolean errorInjectedClient1 = new AtomicBoolean(false); | ||
|
|
@@ -147,9 +154,16 @@ public static Collection<Boolean[]> data() { | |
| private final AtomicInteger commitCounterClient2 = new AtomicInteger(-1); | ||
| private final AtomicInteger commitRequested = new AtomicInteger(0); | ||
|
|
||
| private Throwable uncaughtException; | ||
|
|
||
| private int testNumber = 0; | ||
| private boolean hasUnexpectedError = false; | ||
|
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. This variable should be declare
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. Nice catch! Fixed. |
||
| private Map<String, Integer> exceptionCounts = new HashMap<String, Integer>() { | ||
| { | ||
| put(APP_DIR_1, 0); | ||
| put(APP_DIR_2, 0); | ||
| } | ||
| }; | ||
| private int prevNumAssignments = 0; | ||
| private int expectedNumAssignments = 0; | ||
|
|
||
| @Before | ||
| public void createTopics() throws Exception { | ||
|
|
@@ -235,7 +249,7 @@ public void shouldUpgradeFromEosAlphaToEosBeta() throws Exception { | |
|
|
||
| try { | ||
| // phase 1: start both clients | ||
| streams1Alpha = getKafkaStreams("appDir1", StreamsConfig.EXACTLY_ONCE); | ||
| streams1Alpha = getKafkaStreams(APP_DIR_1, StreamsConfig.EXACTLY_ONCE); | ||
| streams1Alpha.setStateListener( | ||
| (newState, oldState) -> stateTransitions1.add(KeyValue.pair(oldState, newState)) | ||
| ); | ||
|
|
@@ -246,18 +260,19 @@ public void shouldUpgradeFromEosAlphaToEosBeta() throws Exception { | |
| assignmentListener.waitForNextStableAssignment(MAX_WAIT_TIME_MS); | ||
| waitForRunning(stateTransitions1); | ||
|
|
||
| streams2Alpha = getKafkaStreams("appDir2", StreamsConfig.EXACTLY_ONCE); | ||
| streams2Alpha = getKafkaStreams(APP_DIR_2, StreamsConfig.EXACTLY_ONCE); | ||
| streams2Alpha.setStateListener( | ||
| (newState, oldState) -> stateTransitions2.add(KeyValue.pair(oldState, newState)) | ||
| ); | ||
| stateTransitions1.clear(); | ||
|
|
||
| assignmentListener.prepareForRebalance(); | ||
| prevNumAssignments = assignmentListener.prepareForRebalance(); | ||
| streams2Alpha.cleanUp(); | ||
| streams2Alpha.start(); | ||
| assignmentListener.waitForNextStableAssignment(MAX_WAIT_TIME_MS); | ||
|
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. To avoid the issue you point out, we actually use this line. As a matter of fact, there is no guarantee if there would be even more than two rebalances. \cc @ableegoldman might be able to provide more input?
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. @mjsax And, yes, we make sure it completed the rebalance then checking the running state, but as I mentioned, there will be 2 rebalance happened(1 for Adding new member, 1 for leader re-joining group during Stable), and we only wait 1 rebalance completes, so there might be another rebalancing later. The 2 stream state transition log is like this: So, as you can see, we might enter next step when we are in step 2 (
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.
Well, this seem to imply the the run was on an older version that did not contain the retry yet -- note. adding the retry it was part of the latest PR for this test that we merged: https://github.com/apache/kafka/pull/9688/files#diff-86a5136ae170df067137442b5eae05fa5fd9d1e02aca85bac8a251b7d2557b0eR1089 Thus, I would assume the the whole test failure you observes was before the last fix.
Correct, but we actually "cut off" the unstable rebalances (there might actually be more then 2...) via To me, it seems that you observe the test failure before the PR got merged -- otherwise the line numbers for
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. You're right! I didn't notice there is already new added retry in master. I'll revert my change for it. Thank you! |
||
| expectedNumAssignments = assignmentListener.numTotalAssignments() - prevNumAssignments; | ||
| waitForNumRebalancingToRunning(stateTransitions2, expectedNumAssignments); | ||
| waitForRunning(stateTransitions1); | ||
|
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. If you reasoning is right, do we need to use Also, you PR does not update all stages when we wait for state changes. Why? Would we not need to apply to logic each time?
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. Thanks for your questions. Answer them below:
--> Yes, I should do that. What I did now is just focusing on the case: 1 new started stream + 1 already running stream, which will have more failures here. But you're right, I should put the change in all stages.
--> Good question! I have explained in the previous comment (#9733 (comment)) though, I can explain again since I know you didn't understand exactly why I did this change before. I only Basically, The already running stream thread should have the state change: Does that make sense? |
||
| waitForRunning(stateTransitions2); | ||
|
|
||
| // in all phases, we write comments that assume that p-0/p-1 are assigned to the first client | ||
| // and p-2/p-3 are assigned to the second client (in reality the assignment might be different though) | ||
|
|
@@ -389,6 +404,7 @@ public void shouldUpgradeFromEosAlphaToEosBeta() throws Exception { | |
| stateTransitions1.clear(); | ||
| streams1Alpha.close(); | ||
| waitForStateTransition(stateTransitions1, CLOSE_CRASHED); | ||
| assertFalse(UNEXPECTED_EXCEPTION_MSG, hasUnexpectedError); | ||
| } | ||
|
|
||
| // phase 5: (restart first client) | ||
|
|
@@ -411,12 +427,13 @@ public void shouldUpgradeFromEosAlphaToEosBeta() throws Exception { | |
| commitRequested.set(0); | ||
| stateTransitions1.clear(); | ||
| stateTransitions2.clear(); | ||
| streams1Beta = getKafkaStreams("appDir1", StreamsConfig.EXACTLY_ONCE_BETA); | ||
| streams1Beta = getKafkaStreams(APP_DIR_1, StreamsConfig.EXACTLY_ONCE_BETA); | ||
| streams1Beta.setStateListener((newState, oldState) -> stateTransitions1.add(KeyValue.pair(oldState, newState))); | ||
| assignmentListener.prepareForRebalance(); | ||
| prevNumAssignments = assignmentListener.prepareForRebalance(); | ||
| streams1Beta.start(); | ||
| assignmentListener.waitForNextStableAssignment(MAX_WAIT_TIME_MS); | ||
| waitForRunning(stateTransitions1); | ||
| expectedNumAssignments = assignmentListener.numTotalAssignments() - prevNumAssignments; | ||
| waitForNumRebalancingToRunning(stateTransitions1, expectedNumAssignments); | ||
| waitForRunning(stateTransitions2); | ||
|
|
||
| final Set<Long> newlyCommittedKeys; | ||
|
|
@@ -527,6 +544,7 @@ public void shouldUpgradeFromEosAlphaToEosBeta() throws Exception { | |
| stateTransitions2.clear(); | ||
| streams2Alpha.close(); | ||
| waitForStateTransition(stateTransitions2, CLOSE_CRASHED); | ||
| assertFalse(UNEXPECTED_EXCEPTION_MSG, hasUnexpectedError); | ||
|
|
||
| final List<KeyValue<Long, Long>> expectedCommittedResultAfterFailure = | ||
| computeExpectedResult(uncommittedInputDataAfterFirstUpgrade, committedState); | ||
|
|
@@ -555,15 +573,16 @@ public void shouldUpgradeFromEosAlphaToEosBeta() throws Exception { | |
| commitCounterClient2.set(-1); | ||
| stateTransitions1.clear(); | ||
| stateTransitions2.clear(); | ||
| streams2AlphaTwo = getKafkaStreams("appDir2", StreamsConfig.EXACTLY_ONCE); | ||
| streams2AlphaTwo = getKafkaStreams(APP_DIR_2, StreamsConfig.EXACTLY_ONCE); | ||
| streams2AlphaTwo.setStateListener( | ||
| (newState, oldState) -> stateTransitions2.add(KeyValue.pair(oldState, newState)) | ||
| ); | ||
| assignmentListener.prepareForRebalance(); | ||
| prevNumAssignments = assignmentListener.prepareForRebalance(); | ||
| streams2AlphaTwo.start(); | ||
| assignmentListener.waitForNextStableAssignment(MAX_WAIT_TIME_MS); | ||
| expectedNumAssignments = assignmentListener.numTotalAssignments() - prevNumAssignments; | ||
| waitForNumRebalancingToRunning(stateTransitions2, expectedNumAssignments); | ||
| waitForRunning(stateTransitions1); | ||
| waitForRunning(stateTransitions2); | ||
|
|
||
| // 7b. write third batch of input data | ||
| final Set<Long> keysFirstClientBeta = keysFromInstance(streams1Beta); | ||
|
|
@@ -617,6 +636,7 @@ public void shouldUpgradeFromEosAlphaToEosBeta() throws Exception { | |
| stateTransitions1.clear(); | ||
| streams1Beta.close(); | ||
| waitForStateTransition(stateTransitions1, CLOSE_CRASHED); | ||
| assertFalse(UNEXPECTED_EXCEPTION_MSG, hasUnexpectedError); | ||
|
|
||
| final List<KeyValue<Long, Long>> expectedCommittedResultAfterFailure = | ||
| computeExpectedResult(uncommittedInputDataBetweenUpgrade, committedState); | ||
|
|
@@ -626,12 +646,13 @@ public void shouldUpgradeFromEosAlphaToEosBeta() throws Exception { | |
| // 7c. restart the first client in eos-beta mode and wait until rebalance stabilizes | ||
| stateTransitions1.clear(); | ||
| stateTransitions2.clear(); | ||
| streams1BetaTwo = getKafkaStreams("appDir1", StreamsConfig.EXACTLY_ONCE_BETA); | ||
| streams1BetaTwo = getKafkaStreams(APP_DIR_1, StreamsConfig.EXACTLY_ONCE_BETA); | ||
| streams1BetaTwo.setStateListener((newState, oldState) -> stateTransitions1.add(KeyValue.pair(oldState, newState))); | ||
| assignmentListener.prepareForRebalance(); | ||
| prevNumAssignments = assignmentListener.prepareForRebalance(); | ||
| streams1BetaTwo.start(); | ||
| assignmentListener.waitForNextStableAssignment(MAX_WAIT_TIME_MS); | ||
| waitForRunning(stateTransitions1); | ||
| expectedNumAssignments = assignmentListener.numTotalAssignments() - prevNumAssignments; | ||
| waitForNumRebalancingToRunning(stateTransitions1, expectedNumAssignments); | ||
| waitForRunning(stateTransitions2); | ||
| } | ||
|
|
||
|
|
@@ -738,6 +759,7 @@ public void shouldUpgradeFromEosAlphaToEosBeta() throws Exception { | |
| stateTransitions2.clear(); | ||
| streams2AlphaTwo.close(); | ||
| waitForStateTransition(stateTransitions2, CLOSE_CRASHED); | ||
| assertFalse(UNEXPECTED_EXCEPTION_MSG, hasUnexpectedError); | ||
| } | ||
|
|
||
| // phase 10: (restart second client) | ||
|
|
@@ -759,15 +781,16 @@ public void shouldUpgradeFromEosAlphaToEosBeta() throws Exception { | |
| commitRequested.set(0); | ||
| stateTransitions1.clear(); | ||
| stateTransitions2.clear(); | ||
| streams2Beta = getKafkaStreams("appDir1", StreamsConfig.EXACTLY_ONCE_BETA); | ||
| streams2Beta = getKafkaStreams(APP_DIR_1, StreamsConfig.EXACTLY_ONCE_BETA); | ||
| streams2Beta.setStateListener( | ||
| (newState, oldState) -> stateTransitions2.add(KeyValue.pair(oldState, newState)) | ||
| ); | ||
| assignmentListener.prepareForRebalance(); | ||
| prevNumAssignments = assignmentListener.prepareForRebalance(); | ||
| streams2Beta.start(); | ||
| assignmentListener.waitForNextStableAssignment(MAX_WAIT_TIME_MS); | ||
| expectedNumAssignments = assignmentListener.numTotalAssignments() - prevNumAssignments; | ||
| waitForNumRebalancingToRunning(stateTransitions2, expectedNumAssignments); | ||
| waitForRunning(stateTransitions1); | ||
| waitForRunning(stateTransitions2); | ||
|
|
||
| newlyCommittedKeys.clear(); | ||
| if (!injectError) { | ||
|
|
@@ -869,7 +892,7 @@ public void init(final ProcessorContext context) { | |
| this.context = context; | ||
| state = (KeyValueStore<Long, Long>) context.getStateStore(storeName); | ||
| final String clientId = context.appConfigs().get(StreamsConfig.CLIENT_ID_CONFIG).toString(); | ||
| if ("appDir1".equals(clientId)) { | ||
| if (APP_DIR_1.equals(clientId)) { | ||
| crash = errorInjectedClient1; | ||
| sharedCommit = commitCounterClient1; | ||
| } else { | ||
|
|
@@ -938,11 +961,22 @@ public void close() {} | |
|
|
||
| final KafkaStreams streams = new KafkaStreams(builder.build(), config, new TestKafkaClientSupplier()); | ||
| streams.setUncaughtExceptionHandler((t, e) -> { | ||
| if (uncaughtException != null) { | ||
| if (!injectError) { | ||
| // we don't expect any exception thrown in stop case | ||
| e.printStackTrace(System.err); | ||
| fail("Should only get one uncaught exception from Streams."); | ||
| hasUnexpectedError = true; | ||
| } else { | ||
| int exceptionCount = (int) exceptionCounts.get(appDir); | ||
| // should only have our injected exception or commit exception, and 2 exceptions for each stream | ||
| if (++exceptionCount > 2 || !(e instanceof RuntimeException) || | ||
| !(e.getMessage().contains("test exception"))) { | ||
| // The exception won't cause the test fail since we actually "expected" exception thrown and failed the stream. | ||
| // So, log to stderr for debugging when the exception is not what we expected, and fail in the main thread | ||
| e.printStackTrace(System.err); | ||
| hasUnexpectedError = true; | ||
| } | ||
| exceptionCounts.put(appDir, exceptionCount); | ||
| } | ||
| uncaughtException = e; | ||
| }); | ||
|
|
||
| return streams; | ||
|
|
@@ -956,14 +990,25 @@ private void waitForRunning(final List<KeyValue<KafkaStreams.State, KafkaStreams | |
| ); | ||
| } | ||
|
|
||
| // Wait for the numRebalancing of <REBALANCING -> RUNNING> state transition because when new stream joined, | ||
| // we'll do multiple rebalancing. So, if we only wait for Running, it might enter rebalancing soon | ||
| private void waitForNumRebalancingToRunning(final List<KeyValue<KafkaStreams.State, KafkaStreams.State>> observed, | ||
| final int numRebalancing) throws Exception { | ||
| waitForCondition(() -> !observed.isEmpty() && | ||
| observed.stream().filter(kv -> kv.equals(REBALANCED_RUNNING)).count() == numRebalancing, | ||
| MAX_WAIT_TIME_MS, | ||
| () -> "Client did not run " + numRebalancing + " of Rebalancing to Running on time. Observers transitions: " + observed | ||
| ); | ||
| } | ||
|
|
||
| private void waitForStateTransition(final List<KeyValue<KafkaStreams.State, KafkaStreams.State>> observed, | ||
| final List<KeyValue<KafkaStreams.State, KafkaStreams.State>> expected) | ||
| throws Exception { | ||
|
|
||
| waitForCondition( | ||
| () -> observed.equals(expected), | ||
| MAX_WAIT_TIME_MS, | ||
| () -> "Client did not startup on time. Observers transitions: " + observed | ||
| () -> "Client did not have the expected state transition on time. Observers transitions: " + observed | ||
| ); | ||
| } | ||
|
|
||
|
|
@@ -1143,7 +1188,7 @@ private class ErrorInjector extends KafkaProducer<byte[], byte[]> { | |
| public ErrorInjector(final Map<String, Object> configs) { | ||
| super(configs, new ByteArraySerializer(), new ByteArraySerializer()); | ||
| final String clientId = configs.get(ProducerConfig.CLIENT_ID_CONFIG).toString(); | ||
| if (clientId.contains("appDir1")) { | ||
| if (clientId.contains(APP_DIR_1)) { | ||
| crash = commitErrorInjectedClient1; | ||
| } else { | ||
| crash = commitErrorInjectedClient2; | ||
|
|
@@ -1156,7 +1201,7 @@ public void commitTransaction() { | |
| if (!crash.compareAndSet(true, false)) { | ||
| super.commitTransaction(); | ||
| } else { | ||
| throw new RuntimeException("Injected producer commit exception."); | ||
| throw new RuntimeException("Injected producer commit test exception."); | ||
| } | ||
| } | ||
| } | ||
|
|
||
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.
nit: missing space between
or(line above) andthere