Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8dc0f50
remove commit from handleCorruption and handle TaskCorrupted in handl…
ableegoldman Mar 23, 2021
549a5f7
fixin up the tests
ableegoldman Mar 26, 2021
a843f11
skip only corrupted tasks when checkpointing in handleRevocation
ableegoldman Mar 26, 2021
dbec22d
checkstyle
ableegoldman Mar 26, 2021
4ce51e9
comments
ableegoldman Mar 26, 2021
7282884
clean up logic
ableegoldman Mar 26, 2021
5950b39
simplify even further
ableegoldman Mar 26, 2021
d6f8c0b
handle corrupted tasks
ableegoldman Mar 26, 2021
1a579a4
use own catch block
ableegoldman Mar 26, 2021
4d4f41c
add flag to closeAndRevive to conditionally mark changelogs as corru…
ableegoldman Mar 27, 2021
dc91e75
cleanup
ableegoldman Mar 27, 2021
1446ec2
fix test I messed with before
ableegoldman Mar 27, 2021
9c8730a
add unit test
ableegoldman Mar 27, 2021
723520b
backtrack on handleCorrupted, tick the task timer and add another test
ableegoldman Mar 27, 2021
e5cca19
Revert "backtrack on handleCorrupted, tick the task timer and add ano…
ableegoldman Mar 28, 2021
4a9ffff
add second unit test
ableegoldman Mar 28, 2021
669367f
Merge branch 'trunk' of https://github.com/apache/kafka into 12523-im…
ableegoldman Mar 28, 2021
4d23c99
cleanup in handleRevocation
ableegoldman Mar 28, 2021
0d21ff4
javadocs for commit
ableegoldman Mar 28, 2021
ecb7e7c
clear timeout in revive and refactor commit
ableegoldman Mar 28, 2021
9ea3256
update/remove comment
ableegoldman Mar 28, 2021
f6361c0
need to clear commit statuses during close
ableegoldman Mar 28, 2021
4aed51d
clear status in all kinds of close
ableegoldman Mar 28, 2021
9db590b
add timed out tasks to dirty tasks in handleRevocation
ableegoldman Mar 28, 2021
4c78a79
add StreamTaskTest
ableegoldman Mar 28, 2021
55c076b
add KAFKA-12569 jira number to TODOs
ableegoldman Mar 28, 2021
6fcdd56
add unit tests for eos
ableegoldman Mar 29, 2021
fa4cde5
fix flaky test due to strict mock
ableegoldman Mar 29, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -589,11 +589,7 @@ boolean runLoop() {
} catch (final TaskCorruptedException e) {
log.warn("Detected the states of tasks " + e.corruptedTasks() + " are corrupted. " +
"Will close the task as dirty and re-create and bootstrap from scratch.", e);
try {
taskManager.handleCorruption(e.corruptedTasks());
} catch (final TaskMigratedException taskMigrated) {
Comment thread
ableegoldman marked this conversation as resolved.
handleTaskMigrated(taskMigrated);
}
taskManager.handleCorruption(e.corruptedTasks());
} catch (final TaskMigratedException e) {
handleTaskMigrated(e);
} catch (final UnsupportedVersionException e) {
Expand All @@ -611,6 +607,9 @@ boolean runLoop() {
if (processingMode == ProcessingMode.EXACTLY_ONCE_ALPHA || processingMode == ProcessingMode.EXACTLY_ONCE_BETA) {
return false;
}
// TODO: extract this catch block to encompass all of runLoop, in case exceptions slip through one of the
Comment thread
ableegoldman marked this conversation as resolved.
Outdated
// handling methods in the other catch blocks. Note this we'll need to extract the UnsupportedVersionException
// as well since it invokes the exception handler, and the old-style handler relies on rethrowing exceptions
} catch (final Throwable e) {
failedStreamThreadSensor.record();
this.streamsUncaughtExceptionHandler.accept(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,18 +167,7 @@ void handleCorruption(final Set<TaskId> corruptedTasks) {
}
}

// Make sure to clean up any corrupted standby tasks in their entirety before committing
// since TaskMigrated can be thrown and the resulting handleLostAll will only clean up active tasks
closeAndRevive(corruptedStandbyTasks);

commit(tasks()
Comment thread
ableegoldman marked this conversation as resolved.
.values()
.stream()
.filter(t -> t.state() == Task.State.RUNNING || t.state() == Task.State.RESTORING)
.filter(t -> !corruptedTasks.contains(t.id()))
.collect(Collectors.toSet())
);

closeAndRevive(corruptedActiveTasks);
}

Expand Down Expand Up @@ -332,8 +321,8 @@ private void handleCloseAndRecycle(final Set<Task> tasksToRecycle,
// write the checkpoint file.
final Map<TopicPartition, OffsetAndMetadata> offsets = task.prepareCommit();
if (!offsets.isEmpty()) {
log.error("Task {} should has been committed when it was suspended, but it reports non-empty " +
"offsets {} to commit; it means it fails during last commit and hence should be closed dirty",
log.error("Task {} should have been committed when it was suspended, but it reports non-empty " +
"offsets {} to commit; this means it failed during last commit and hence should be closed dirty",
task.id(), offsets);

tasksToCloseDirty.add(task);
Expand Down Expand Up @@ -515,7 +504,43 @@ void handleRevocation(final Collection<TopicPartition> revokedPartitions) {
commitOffsetsOrTransaction(consumedOffsetsPerTask);
} catch (final RuntimeException e) {
log.error("Exception caught while committing those revoked tasks " + revokedActiveTasks, e);
firstException.compareAndSet(null, e);

// if we hit a TaskCorruptedException, we have to peel off any revoked tasks since they will be gone by
// the time the exception is bubbled up through poll()
Comment thread
ableegoldman marked this conversation as resolved.
Outdated
if (e instanceof TaskCorruptedException) {
Comment thread
ableegoldman marked this conversation as resolved.
Outdated
final Set<TaskId> corruptedTasks = ((TaskCorruptedException) e).corruptedTasks();
final Set<TaskId> stillAssignedCorruptedTasks = new HashSet<>();
final Set<TaskId> revokedCorruptedTasks = new HashSet<>();

for (final TaskId taskId : corruptedTasks) {
final Task task = tasks.task(taskId);
if (!revokedActiveTasks.contains(task)) {
stillAssignedCorruptedTasks.add(taskId);
} else {
revokedCorruptedTasks.add(taskId);
}
}

// If any of the corrupted tasks are being revoked, we need to filter them from the
// TaskCorruptedException otherwise we will try to revive tasks we no longer own
// Any revoked tasks will be detected and closed dirty in handleAssignment and wipe their state
if (!stillAssignedCorruptedTasks.isEmpty()) {
log.info("Removing revoked tasks {} from the corrupted list, the remaining tasks will be revived " +
"after the rebalance has completed.", revokedCorruptedTasks);

// TODO: usually we would always save the first seen exception to rethrow, but TaskCorruptedException
// must be bubbled up to the StreamThread to ensure corrupted state is cleared and revived
firstException.set(new TaskCorruptedException(stillAssignedCorruptedTasks));
} else {
// If all corrupted tasks are being revoked, we can just close them dirty in handleAssignment
// and can swallow the exception
log.info("All the corrupted tasks are being revoked so the exception can be swallowed: {}",
revokedCorruptedTasks);
}
} else {
// TODO: KIP-572 need to handle TimeoutException, may be rethrown from committing offsets under ALOS
firstException.compareAndSet(null, e);
}
}

// only try to complete post-commit if committing succeeded;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2333,74 +2333,6 @@ void runOnce() {
verify(taskManager);
}

@Test
@SuppressWarnings("unchecked")
public void shouldCatchTaskMigratedExceptionOnOnTaskCorruptedExceptionPath() {
Comment thread
ableegoldman marked this conversation as resolved.
final TaskManager taskManager = EasyMock.createNiceMock(TaskManager.class);
final Consumer<byte[], byte[]> consumer = mock(Consumer.class);
final ConsumerGroupMetadata consumerGroupMetadata = mock(ConsumerGroupMetadata.class);
expect(consumer.groupMetadata()).andStubReturn(consumerGroupMetadata);
expect(consumerGroupMetadata.groupInstanceId()).andReturn(Optional.empty());
consumer.subscribe((Collection<String>) anyObject(), anyObject());
EasyMock.expectLastCall().anyTimes();
consumer.unsubscribe();
EasyMock.expectLastCall().anyTimes();
EasyMock.replay(consumerGroupMetadata);
final Task task1 = mock(Task.class);
final Task task2 = mock(Task.class);
final TaskId taskId1 = new TaskId(0, 0);
final TaskId taskId2 = new TaskId(0, 2);

final Set<TaskId> corruptedTasks = singleton(taskId1);

expect(task1.state()).andReturn(Task.State.RUNNING).anyTimes();
expect(task1.id()).andReturn(taskId1).anyTimes();
expect(task2.state()).andReturn(Task.State.RUNNING).anyTimes();
expect(task2.id()).andReturn(taskId2).anyTimes();

taskManager.handleCorruption(corruptedTasks);
expectLastCall().andThrow(new TaskMigratedException("Task migrated",
new RuntimeException("non-corrupted task migrated")));

taskManager.handleLostAll();
expectLastCall();

EasyMock.replay(task1, task2, taskManager, consumer);

final StreamsMetricsImpl streamsMetrics =
new StreamsMetricsImpl(metrics, CLIENT_ID, StreamsConfig.METRICS_LATEST, mockTime);
final StreamThread thread = new StreamThread(
mockTime,
config,
null,
consumer,
consumer,
null,
null,
taskManager,
streamsMetrics,
internalTopologyBuilder,
CLIENT_ID,
new LogContext(""),
new AtomicInteger(),
new AtomicLong(Long.MAX_VALUE),
null,
HANDLER,
null
) {
@Override
void runOnce() {
setState(State.PENDING_SHUTDOWN);
throw new TaskCorruptedException(corruptedTasks);
}
}.updateThreadMetadata(getSharedAdminClientId(CLIENT_ID));

thread.setState(StreamThread.State.STARTING);
thread.runLoop();

verify(taskManager);
}

@Test
public void shouldNotCommitNonRunningNonRestoringTasks() {
final TaskManager taskManager = EasyMock.createNiceMock(TaskManager.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -690,7 +690,6 @@ public void suspend() {

task00.setChangelogOffsets(singletonMap(t1p0, 0L));
taskManager.handleCorruption(singleton(taskId00));
assertThat(task00.commitPrepared, is(true));
assertThat(task00.state(), is(Task.State.CREATED));
assertThat(task00.partitionsForOffsetReset, equalTo(taskId00Partitions));
assertThat(taskManager.activeTaskMap(), is(singletonMap(taskId00, task00)));
Expand Down Expand Up @@ -718,7 +717,6 @@ public void shouldCommitNonCorruptedTasksOnTaskCorruptedException() {
topologyBuilder.addSubscribedTopicsFromAssignment(anyObject(), anyString());
expectLastCall().anyTimes();
expectRestoreToBeCompleted(consumer, changeLogReader);
consumer.commitSync(eq(emptyMap()));
expect(consumer.assignment()).andReturn(taskId00Partitions);
replay(activeTaskCreator, topologyBuilder, consumer, changeLogReader);

Expand All @@ -731,7 +729,6 @@ public void shouldCommitNonCorruptedTasksOnTaskCorruptedException() {
corruptedTask.setChangelogOffsets(singletonMap(t1p0, 0L));
taskManager.handleCorruption(singleton(taskId00));

assertTrue(nonCorruptedTask.commitPrepared);
assertThat(nonCorruptedTask.partitionsForOffsetReset, equalTo(Collections.emptySet()));
assertThat(corruptedTask.partitionsForOffsetReset, equalTo(taskId00Partitions));

Expand Down Expand Up @@ -774,48 +771,6 @@ public void shouldNotCommitNonRunningNonCorruptedTasks() {
verify(consumer);
}

@Test
public void shouldCleanAndReviveCorruptedStandbyTasksBeforeCommittingNonCorruptedTasks() {
Comment thread
ableegoldman marked this conversation as resolved.
final ProcessorStateManager stateManager = EasyMock.createStrictMock(ProcessorStateManager.class);
stateManager.markChangelogAsCorrupted(taskId00Partitions);
replay(stateManager);

final StateMachineTask corruptedStandby = new StateMachineTask(taskId00, taskId00Partitions, false, stateManager);
final StateMachineTask runningNonCorruptedActive = new StateMachineTask(taskId01, taskId01Partitions, true, stateManager) {
@Override
public Map<TopicPartition, OffsetAndMetadata> prepareCommit() {
throw new TaskMigratedException("You dropped out of the group!", new RuntimeException());
}
};

// handleAssignment
expect(standbyTaskCreator.createTasks(eq(taskId00Assignment))).andStubReturn(singleton(corruptedStandby));
expect(activeTaskCreator.createTasks(anyObject(), eq(taskId01Assignment))).andStubReturn(singleton(runningNonCorruptedActive));
topologyBuilder.addSubscribedTopicsFromAssignment(anyObject(), anyString());
expectLastCall().anyTimes();

expectRestoreToBeCompleted(consumer, changeLogReader);

replay(activeTaskCreator, standbyTaskCreator, topologyBuilder, consumer, changeLogReader);

taskManager.handleAssignment(taskId01Assignment, taskId00Assignment);
assertThat(taskManager.tryToCompleteRestoration(time.milliseconds(), null), is(true));

// make sure this will be committed and throw
assertThat(runningNonCorruptedActive.state(), is(Task.State.RUNNING));
assertThat(corruptedStandby.state(), is(Task.State.RUNNING));

runningNonCorruptedActive.setCommitNeeded();

corruptedStandby.setChangelogOffsets(singletonMap(t1p0, 0L));
assertThrows(TaskMigratedException.class, () -> taskManager.handleCorruption(singleton(taskId00)));


assertThat(corruptedStandby.commitPrepared, is(true));
assertThat(corruptedStandby.state(), is(Task.State.CREATED));
verify(consumer);
}

@Test
public void shouldCloseStandbyUnassignedTasksWhenCreatingNewTasks() {
final Task task00 = new StateMachineTask(taskId00, taskId00Partitions, false);
Expand Down