|
33 | 33 | import org.springframework.kafka.core.ConsumerFactory; |
34 | 34 | import org.springframework.kafka.listener.adapter.FilteringMessageListenerAdapter; |
35 | 35 | import org.springframework.kafka.listener.adapter.RecordFilterStrategy; |
| 36 | +import org.springframework.kafka.support.Acknowledgment; |
36 | 37 |
|
37 | 38 | import static org.assertj.core.api.Assertions.assertThat; |
38 | 39 | import static org.mockito.ArgumentMatchers.any; |
@@ -326,4 +327,77 @@ void testRecordFilteredModeDoesNotBreakNormalProcessing() throws InterruptedExce |
326 | 327 | assertThat(processedValues).containsExactly("value0", "value1", "value2"); |
327 | 328 | verify(consumer, times(3)).commitSync(any(), any(Duration.class)); |
328 | 329 | } |
| 330 | + |
| 331 | + @SuppressWarnings({"unchecked", "deprecation"}) |
| 332 | + @Test |
| 333 | + void recordFilteredModeShouldBeThreadIsolated() throws Exception { |
| 334 | + ConsumerFactory<String, String> cf = mock(ConsumerFactory.class); |
| 335 | + Consumer<String, String> c0 = mock(Consumer.class); |
| 336 | + Consumer<String, String> c1 = mock(Consumer.class); |
| 337 | + given(cf.createConsumer(any(), any(), any(), any())).willReturn(c0, c1); |
| 338 | + |
| 339 | + ContainerProperties props = new ContainerProperties("iso-topic"); |
| 340 | + props.setGroupId("iso-group"); |
| 341 | + props.setAckMode(ContainerProperties.AckMode.RECORD_FILTERED); |
| 342 | + |
| 343 | + CountDownLatch aHasSetState = new CountDownLatch(1); |
| 344 | + CountDownLatch bHasProcessed = new CountDownLatch(1); |
| 345 | + RecordFilterStrategy<String, String> filter = rec -> rec.offset() == 0; |
| 346 | + |
| 347 | + FilteringMessageListenerAdapter<String, String> adapter = |
| 348 | + new FilteringMessageListenerAdapter<>( |
| 349 | + (MessageListener<String, String>) r -> { |
| 350 | + }, |
| 351 | + filter |
| 352 | + ) { |
| 353 | + @Override |
| 354 | + public void onMessage(ConsumerRecord<String, String> rec, |
| 355 | + Acknowledgment ack, |
| 356 | + Consumer<?, ?> consumer) { |
| 357 | + super.onMessage(rec, ack, consumer); |
| 358 | + if (rec.offset() == 0) { |
| 359 | + aHasSetState.countDown(); |
| 360 | + try { |
| 361 | + bHasProcessed.await(500, TimeUnit.MILLISECONDS); |
| 362 | + } catch (InterruptedException e) { |
| 363 | + Thread.currentThread().interrupt(); |
| 364 | + } |
| 365 | + } else if (rec.offset() == 1) { |
| 366 | + try { |
| 367 | + aHasSetState.await(200, TimeUnit.MILLISECONDS); |
| 368 | + } catch (InterruptedException e) { |
| 369 | + Thread.currentThread().interrupt(); |
| 370 | + } |
| 371 | + bHasProcessed.countDown(); |
| 372 | + } |
| 373 | + } |
| 374 | + }; |
| 375 | + |
| 376 | + ConcurrentMessageListenerContainer<String, String> container = |
| 377 | + new ConcurrentMessageListenerContainer<>(cf, props); |
| 378 | + container.setConcurrency(2); |
| 379 | + container.setupMessageListener(adapter); |
| 380 | + |
| 381 | + TopicPartition tp0 = new TopicPartition("iso-topic", 0); |
| 382 | + TopicPartition tp1 = new TopicPartition("iso-topic", 1); |
| 383 | + |
| 384 | + ConsumerRecords<String, String> poll0 = new ConsumerRecords<>(Map.of( |
| 385 | + tp0, List.of(new ConsumerRecord<>("iso-topic", 0, 0, "k0", "v0")) |
| 386 | + )); |
| 387 | + ConsumerRecords<String, String> poll1 = new ConsumerRecords<>(Map.of( |
| 388 | + tp1, List.of(new ConsumerRecord<>("iso-topic", 1, 1, "k1", "v1")) |
| 389 | + )); |
| 390 | + |
| 391 | + given(c0.poll(any(Duration.class))).willReturn(poll0).willReturn(ConsumerRecords.empty()); |
| 392 | + given(c1.poll(any(Duration.class))).willReturn(poll1).willReturn(ConsumerRecords.empty()); |
| 393 | + |
| 394 | + // when: containers process records concurrently (thread-local isolation should apply) |
| 395 | + container.start(); |
| 396 | + Thread.sleep(400); |
| 397 | + container.stop(); |
| 398 | + |
| 399 | + // then: consumer c1 commits only its record, while c0 (filtered) does not |
| 400 | + verify(c1, times(1)).commitSync(any(), any(Duration.class)); |
| 401 | + verify(c0, never()).commitSync(any(), any(Duration.class)); |
| 402 | + } |
329 | 403 | } |
0 commit comments