KAFKA-8620: fix NPE due to race condition during shutdown while rebalancing#7021
Conversation
|
@mjsax @guozhangwang @ableegoldman @cadonna @bbejeck @vvcephei Call for review |
| return null; | ||
| } else if (state == State.PARTITIONS_REVOKED && newState == State.PARTITIONS_REVOKED) { | ||
| log.debug("Invalid transition from PARTITIONS_REVOKED to PARTITIONS_REVOKED: " + | ||
| "self transition is not allowed"); |
There was a problem hiding this comment.
Thinking about this, I am wondering if we should just change the FSM to allow this transition and simplify the code here? \cc @guozhangwang
There was a problem hiding this comment.
I thought about this when tightening the FSM before but the unit tests reminds me of one thing: our current contract is that we only transit to PARTITIONS_REVOKED when calling onPartitionsRevoked, which is called only once at the beginning of the rebalance today, so keeping it strict is better just in case we have incorrect partial rebalance procedure.
With KIP-429 this may be violated so we need to revisit our FSM once Streams adopt cooperative protocols. cc @ableegoldman who's working on this.
| return; | ||
| } | ||
| if (streamThread.assignmentErrorCode.get() == StreamsPartitionAssignor.Error.NONE.code()) { | ||
| log.error("State transition from {} to PARTITIONS_ASSIGNED failed. " + |
There was a problem hiding this comment.
Why is this an ERROR? If we receive a SHUTDOWN signal, it's just an "eager exit" to not create tasks IMHO. We might want to log a DEBUG though.
Would also update the message:
log.debug("Skipping task creation in rebalance because we are already in shutdown phase.");
| setState(State.PENDING_SHUTDOWN); | ||
|
|
||
| log.info("Shutting down"); | ||
| log.info("Shutting down with cleanRun {}", cleanRun); |
There was a problem hiding this comment.
Not sure if we need this? If the shutdown is not clean, we logged an ERROR before in StreamThread#run()
There was a problem hiding this comment.
I would not use variable names in log messages.
There was a problem hiding this comment.
I could avoid using variable name in the log, but still feel needed to log the flag here, as we can't control future changes to set cleanRun flag.
|
|
||
| // assign single partition | ||
| assignedPartitions.add(t1p1); | ||
| assignedPartitions.add(t1p2); |
| beginOffsets.put(t1p2, 0L); | ||
| mockConsumer.updateBeginningOffsets(beginOffsets); | ||
|
|
||
| final Thread callbackThread = new Thread(() -> { |
There was a problem hiding this comment.
We need a separate thread to trigger callbacks, otherwise the state won't proceed. Maybe there is a better way to do that?
There was a problem hiding this comment.
Can you elaborate? What do you mean by
otherwise the state won't proceed
| if (streamThread.assignmentErrorCode.get() == StreamsPartitionAssignor.Error.NONE.code()) { | ||
| log.error("State transition from {} to PARTITIONS_ASSIGNED failed. " + | ||
| "Will skip the task initialization", streamThread.state()); | ||
| } else if (streamThread.assignmentErrorCode.get() != StreamsPartitionAssignor.Error.NONE.code()) { |
There was a problem hiding this comment.
Isn't this a rather brittle fix? How does this code guarantee that after streamThread.assignmentErrorCode.get() != StreamsPartitionAssignor.Error.NONE.code() is false, the result of streamThread.assignmentErrorCode.get() does not change again before the tasks are created?
There was a problem hiding this comment.
in PartitionsAssigned we should have already passed the assignment error placement, so I won't expect another flip for its value during this round rebalance.
| setState(State.PENDING_SHUTDOWN); | ||
|
|
||
| log.info("Shutting down"); | ||
| log.info("Shutting down with cleanRun {}", cleanRun); |
There was a problem hiding this comment.
I would not use variable names in log messages.
vvcephei
left a comment
There was a problem hiding this comment.
LGTM. Just one minor suggestion.
a09a388 to
a99606b
Compare
| if (streamThread.assignmentErrorCode.get() == StreamsPartitionAssignor.Error.NONE.code()) { | ||
| log.error("State transition from {} to PARTITIONS_ASSIGNED failed. " + | ||
| "Will skip the task initialization", streamThread.state()); | ||
| } else if (streamThread.assignmentErrorCode.get() != StreamsPartitionAssignor.Error.NONE.code()) { |
| } | ||
|
|
||
| @Test | ||
| public void shouldNotAccessTaskManagerWhenPendingShutdownInRunOnce() { |
There was a problem hiding this comment.
I would give this test a more meaningful name, e.g. shouldNotThrowWhenPendingShutdownInRunOnce.
| } | ||
|
|
||
| @Test | ||
| public void shouldHaveNoErrorAccessTaskManagerInRunOnceNoShutdown() { |
There was a problem hiding this comment.
Same as above, e.g. shouldNotThrowWithoutPendingShutdownInRunOnce
| return; | ||
| } | ||
| if (streamThread.assignmentErrorCode.get() == StreamsPartitionAssignor.Error.NONE.code()) { | ||
| log.debug("Skipping task creation in rebalance because we are already in {} state.", |
There was a problem hiding this comment.
Shouldn't the indentation be as follows?
log.debug(
"Skipping task creation in rebalance because we are already in {} state.",
streamThread.state()
);
| streamThread.state()); | ||
| } else if (streamThread.assignmentErrorCode.get() != StreamsPartitionAssignor.Error.NONE.code()) { | ||
| log.debug("Encountered assignment error during partition assignment: {}. " + | ||
| "Will skip the task initialization", streamThread.assignmentErrorCode); |
There was a problem hiding this comment.
Shouldn't the indentation be as follows?
log.debug(
"Encountered assignment error during partition assignment: {}. Will skip the task initialization",
streamThread.assignmentErrorCode
);
The first parameter is one or two characters too long, though.
|
Retest this, please |
|
@cadonna Thank you for the review, addressed! |
|
@cadonna The bug is, that if we don't create tasks on the |
|
Retest this please |
|
@mjsax I do not agree. The |
|
Retest this please |
|
@cadonna @mjsax My take is that the purpose of mocking taskManager is to create a conditional behavior path: if you set an active task, you could retrieve it later; if you don't, you will get NPE, while the setup of real taskManger is too much compared with a mock. Actually in our case, the mock examples in other tests doesn't quite fit to implement this mentioned conditional logic, and the task manager setup is not bad either. So we should be fine just creating a new client in this case. |
|
Reopening to trigger Jenkins build. |
|
@abbccdda: What you describe is also a valid use case of a mock, but it only achieves the isolation of the class under test. It does not document well the expected behaviour of productive code. Furthermore, IMO conditional behaviour in a mock should be avoided if possible, because it introduces code that needs maintenance and could be wrong. A mock should be as simple as possible. Admittedly, in this case the code would probably not be that complex, but if it could be avoided, I would avoid it. @mjsax @abbccdda: I do not want to block this fix further. The test seems to be correct. My concern is about clean and maintainable code. Up to you to follow my reasoning or not. |
|
retest this please |
2 similar comments
|
retest this please |
|
retest this please |
|
Checked log and the single test failure was in RebalanceSourceConnectorsIntegrationTest in Connect, not related |
|
retest this please |
2 similar comments
|
retest this please |
|
retest this please |
|
failures unrelated |
|
All flaky tests: |
|
retest this please |
…ancing (#7021) Reviewers: Matthias J. Sax <matthias@confluent.io>, Bruno Cadonna <bruno@confluent.io>, John Roesler <john@confluent.io>
…ancing (#7021) Reviewers: Matthias J. Sax <matthias@confluent.io>, Bruno Cadonna <bruno@confluent.io>, John Roesler <john@confluent.io>
| return null; | ||
| } else if (state == State.PARTITIONS_REVOKED && newState == State.PARTITIONS_REVOKED) { | ||
| log.debug("Invalid transition from PARTITIONS_REVOKED to PARTITIONS_REVOKED: " + | ||
| "self transition is not allowed"); |
There was a problem hiding this comment.
I thought about this when tightening the FSM before but the unit tests reminds me of one thing: our current contract is that we only transit to PARTITIONS_REVOKED when calling onPartitionsRevoked, which is called only once at the beginning of the rebalance today, so keeping it strict is better just in case we have incorrect partial rebalance procedure.
With KIP-429 this may be violated so we need to revisit our FSM once Streams adopt cooperative protocols. cc @ableegoldman who's working on this.
| "will abort the current process and re-throw at the end of rebalance: {}", | ||
| t | ||
| ); | ||
| "will abort the current process and re-throw at the end of rebalance", t); |
…ancing (#7021) Reviewers: Matthias J. Sax <matthias@confluent.io>, Bruno Cadonna <bruno@confluent.io>, John Roesler <john@confluent.io>
|
Merged to |
* apache-github/2.3: MINOR: Update documentation for enabling optimizations (apache#7099) MINOR: Remove stale streams producer retry default docs. (apache#6844) KAFKA-8635; Skip client poll in Sender loop when no request is sent (apache#7085) KAFKA-8615: Change to track partition time breaks TimestampExtractor (apache#7054) KAFKA-8670; Fix exception for kafka-topics.sh --describe without --topic mentioned (apache#7094) KAFKA-8602: Separate PR for 2.3 branch (apache#7092) KAFKA-8530; Check for topic authorization errors in OffsetFetch response (apache#6928) KAFKA-8662; Fix producer metadata error handling and consumer manual assignment (apache#7086) KAFKA-8637: WriteBatch objects leak off-heap memory (apache#7050) KAFKA-8620: fix NPE due to race condition during shutdown while rebalancing (apache#7021) HOT FIX: close RocksDB objects in correct order (apache#7076) KAFKA-7157: Fix handling of nulls in TimestampConverter (apache#7070) KAFKA-6605: Fix NPE in Flatten when optional Struct is null (apache#5705) Fixes apache#8198 KStreams testing docs use non-existent method pipe (apache#6678) KAFKA-5998: fix checkpointableOffsets handling (apache#7030) KAFKA-8653; Default rebalance timeout to session timeout for JoinGroup v0 (apache#7072) KAFKA-8591; WorkerConfigTransformer NPE on connector configuration reloading (apache#6991) MINOR: add upgrade text (apache#7013) Bump version to 2.3.1-SNAPSHOT
…during shutdown while rebalancing (apache#7021) TICKET = KAFKA-8620 LI_DESCRIPTION = EXIT_CRITERIA = HASH [0f0093c] ORIGINAL_DESCRIPTION = Reviewers: Matthias J. Sax <matthias@confluent.io>, Bruno Cadonna <bruno@confluent.io>, John Roesler <john@confluent.io> (cherry picked from commit 0f0093c)
We have detected a race condition under system test failure. The problem was that the task manager internal active tasks should be guarded against state changes on the stream thread. Could definitely consider other fixes but this is currently the make-sense one.
Committer Checklist (excluded from commit message)