fix: stop kafka consumers without concurrent close
This commit is contained in:
@@ -95,6 +95,9 @@ public final class KafkaEnvelopeConsumerRunner implements SmartLifecycle, AutoCl
|
|||||||
try {
|
try {
|
||||||
worker.pollOnce(pollTimeout);
|
worker.pollOnce(pollTimeout);
|
||||||
} catch (RuntimeException ex) {
|
} catch (RuntimeException ex) {
|
||||||
|
if (!running.get()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
// Kafka/处理器异常不让 Spring 生命周期退出;退避后继续消费,
|
// Kafka/处理器异常不让 Spring 生命周期退出;退避后继续消费,
|
||||||
// 具体坏消息由 EnvelopeConsumerProcessor 写入 DLQ。
|
// 具体坏消息由 EnvelopeConsumerProcessor 写入 DLQ。
|
||||||
log.warn("Kafka envelope consumer poll failed; the worker will retry after backoff", ex);
|
log.warn("Kafka envelope consumer poll failed; the worker will retry after backoff", ex);
|
||||||
@@ -116,17 +119,20 @@ public final class KafkaEnvelopeConsumerRunner implements SmartLifecycle, AutoCl
|
|||||||
if (!running.compareAndSet(true, false)) {
|
if (!running.compareAndSet(true, false)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
wakeupWorkers();
|
||||||
if (executor != null) {
|
if (executor != null) {
|
||||||
executor.shutdownNow();
|
executor.shutdown();
|
||||||
}
|
|
||||||
closeWorkers();
|
|
||||||
if (executor != null) {
|
|
||||||
try {
|
try {
|
||||||
|
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||||
|
executor.shutdownNow();
|
||||||
executor.awaitTermination(5, TimeUnit.SECONDS);
|
executor.awaitTermination(5, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
} catch (InterruptedException ex) {
|
} catch (InterruptedException ex) {
|
||||||
|
executor.shutdownNow();
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
closeWorkers();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -163,4 +169,14 @@ public final class KafkaEnvelopeConsumerRunner implements SmartLifecycle, AutoCl
|
|||||||
worker.close();
|
worker.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void wakeupWorkers() {
|
||||||
|
List<KafkaEnvelopeConsumerWorker> current = workers;
|
||||||
|
if (current == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (KafkaEnvelopeConsumerWorker worker : current) {
|
||||||
|
worker.wakeup();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ public final class KafkaEnvelopeConsumerWorker implements AutoCloseable {
|
|||||||
return processed;
|
return processed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void wakeup() {
|
||||||
|
consumer.wakeup();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
consumer.close();
|
consumer.close();
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.lingniu.ingest.sink.mq;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
class KafkaEnvelopeConsumerRunnerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void stopWakesPollLoopAndClosesWorkerAfterPollThreadExits() throws Exception {
|
||||||
|
KafkaEnvelopeConsumerWorker worker = mock(KafkaEnvelopeConsumerWorker.class);
|
||||||
|
CountDownLatch pollEntered = new CountDownLatch(1);
|
||||||
|
CountDownLatch wakeupCalled = new CountDownLatch(1);
|
||||||
|
CountDownLatch pollReturned = new CountDownLatch(1);
|
||||||
|
CountDownLatch closeCalled = new CountDownLatch(1);
|
||||||
|
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
pollEntered.countDown();
|
||||||
|
assertThat(wakeupCalled.await(1, TimeUnit.SECONDS)).isTrue();
|
||||||
|
pollReturned.countDown();
|
||||||
|
return 0;
|
||||||
|
}).when(worker).pollOnce(any());
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
wakeupCalled.countDown();
|
||||||
|
return null;
|
||||||
|
}).when(worker).wakeup();
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
assertThat(pollReturned.getCount()).isZero();
|
||||||
|
closeCalled.countDown();
|
||||||
|
return null;
|
||||||
|
}).when(worker).close();
|
||||||
|
KafkaEnvelopeConsumerRunner runner = new KafkaEnvelopeConsumerRunner(
|
||||||
|
List.of(worker),
|
||||||
|
Duration.ofSeconds(30),
|
||||||
|
Duration.ofMillis(1),
|
||||||
|
false);
|
||||||
|
|
||||||
|
runner.start();
|
||||||
|
assertThat(pollEntered.await(1, TimeUnit.SECONDS)).isTrue();
|
||||||
|
runner.stop();
|
||||||
|
|
||||||
|
assertThat(wakeupCalled.getCount()).isZero();
|
||||||
|
assertThat(closeCalled.getCount()).isZero();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user