|
74 | 74 | import java.util.Set; |
75 | 75 | import java.util.concurrent.ConcurrentHashMap; |
76 | 76 | import java.util.concurrent.ConcurrentMap; |
| 77 | +import java.util.concurrent.CountDownLatch; |
77 | 78 | import java.util.concurrent.ExecutorService; |
| 79 | +import java.util.concurrent.TimeUnit; |
| 80 | +import java.util.concurrent.atomic.AtomicReference; |
| 81 | +import java.util.concurrent.locks.Lock; |
| 82 | +import java.util.concurrent.locks.ReadWriteLock; |
78 | 83 |
|
79 | 84 | import static org.assertj.core.api.Assertions.assertThat; |
80 | 85 | import static org.junit.Assert.assertEquals; |
@@ -397,6 +402,119 @@ public void testResetOffset() throws IllegalAccessException { |
397 | 402 | eq(0L)); |
398 | 403 | } |
399 | 404 |
|
| 405 | + @Test |
| 406 | + public void testResetOffsetOrderly() { |
| 407 | + topicRouteTable.put(topic, createTopicRouteData()); |
| 408 | + brokerAddrTable.put(defaultBroker, createBrokerAddrMap()); |
| 409 | + MessageQueue messageQueue = createMessageQueue(); |
| 410 | + ProcessQueue processQueue = new ProcessQueue(); |
| 411 | + RebalanceImpl rebalanceImpl = mock(RebalanceImpl.class); |
| 412 | + when(rebalanceImpl.removeUnnecessaryMessageQueue(eq(messageQueue), eq(processQueue))) |
| 413 | + .thenReturn(false, false, true); |
| 414 | + consumerTable.put(group, createMQConsumerInner(processQueue, true, rebalanceImpl)); |
| 415 | + Map<MessageQueue, Long> offsetTable = new HashMap<>(); |
| 416 | + offsetTable.put(messageQueue, 0L); |
| 417 | + |
| 418 | + mqClientInstance.resetOffset(topic, group, offsetTable); |
| 419 | + |
| 420 | + verify(rebalanceImpl).removeUnnecessaryMessageQueue(messageQueue, processQueue); |
| 421 | + } |
| 422 | + |
| 423 | + @Test |
| 424 | + public void testResetOffsetOrderlyWhenWaitTimesOut() throws InterruptedException { |
| 425 | + topicRouteTable.put(topic, createTopicRouteData()); |
| 426 | + brokerAddrTable.put(defaultBroker, createBrokerAddrMap()); |
| 427 | + MessageQueue messageQueue = createMessageQueue(); |
| 428 | + ProcessQueue processQueue = mock(ProcessQueue.class); |
| 429 | + ReadWriteLock consumeLock = mock(ReadWriteLock.class); |
| 430 | + Lock writeLock = mock(Lock.class); |
| 431 | + RebalanceImpl rebalanceImpl = mock(RebalanceImpl.class); |
| 432 | + when(processQueue.getConsumeLock()).thenReturn(consumeLock); |
| 433 | + when(consumeLock.writeLock()).thenReturn(writeLock); |
| 434 | + when(writeLock.tryLock(10, TimeUnit.SECONDS)).thenReturn(false); |
| 435 | + DefaultMQPushConsumerImpl consumer = (DefaultMQPushConsumerImpl) createMQConsumerInner(processQueue, true, rebalanceImpl); |
| 436 | + consumerTable.put(group, consumer); |
| 437 | + Map<MessageQueue, Long> offsetTable = new HashMap<>(); |
| 438 | + offsetTable.put(messageQueue, 0L); |
| 439 | + |
| 440 | + mqClientInstance.resetOffset(topic, group, offsetTable); |
| 441 | + |
| 442 | + verify(consumer).updateConsumeOffset(messageQueue, 0L); |
| 443 | + verify(rebalanceImpl).removeUnnecessaryMessageQueue(messageQueue, processQueue); |
| 444 | + verify(writeLock, times(1)).tryLock(10, TimeUnit.SECONDS); |
| 445 | + verify(writeLock, times(0)).unlock(); |
| 446 | + } |
| 447 | + |
| 448 | + @Test |
| 449 | + public void testResetOffsetOrderlyWaitsForInflightConsumptionBeforeUpdatingOffset() throws Exception { |
| 450 | + topicRouteTable.put(topic, createTopicRouteData()); |
| 451 | + brokerAddrTable.put(defaultBroker, createBrokerAddrMap()); |
| 452 | + MessageQueue messageQueue = createMessageQueue(); |
| 453 | + ProcessQueue processQueue = new ProcessQueue(); |
| 454 | + RebalanceImpl rebalanceImpl = mock(RebalanceImpl.class); |
| 455 | + when(rebalanceImpl.removeUnnecessaryMessageQueue(eq(messageQueue), eq(processQueue))).thenReturn(true); |
| 456 | + DefaultMQPushConsumerImpl consumer = (DefaultMQPushConsumerImpl) createMQConsumerInner(processQueue, true, rebalanceImpl); |
| 457 | + consumerTable.put(group, consumer); |
| 458 | + Map<MessageQueue, Long> offsetTable = new HashMap<>(); |
| 459 | + offsetTable.put(messageQueue, 0L); |
| 460 | + |
| 461 | + CountDownLatch consumeLockHeld = new CountDownLatch(1); |
| 462 | + CountDownLatch releaseConsumeLock = new CountDownLatch(1); |
| 463 | + CountDownLatch suspendCalled = new CountDownLatch(1); |
| 464 | + CountDownLatch updateOffsetCalled = new CountDownLatch(1); |
| 465 | + AtomicReference<Throwable> backgroundFailure = new AtomicReference<>(); |
| 466 | + |
| 467 | + doAnswer(invocation -> { |
| 468 | + suspendCalled.countDown(); |
| 469 | + return null; |
| 470 | + }).when(consumer).suspend(); |
| 471 | + doAnswer(invocation -> { |
| 472 | + updateOffsetCalled.countDown(); |
| 473 | + return null; |
| 474 | + }).when(consumer).updateConsumeOffset(messageQueue, 0L); |
| 475 | + |
| 476 | + Thread consumingThread = new Thread(() -> { |
| 477 | + processQueue.getConsumeLock().readLock().lock(); |
| 478 | + try { |
| 479 | + consumeLockHeld.countDown(); |
| 480 | + if (!releaseConsumeLock.await(5, TimeUnit.SECONDS)) { |
| 481 | + backgroundFailure.compareAndSet(null, |
| 482 | + new AssertionError("Timed out while waiting to release orderly consume lock")); |
| 483 | + } |
| 484 | + } catch (InterruptedException e) { |
| 485 | + Thread.currentThread().interrupt(); |
| 486 | + backgroundFailure.compareAndSet(null, e); |
| 487 | + } finally { |
| 488 | + processQueue.getConsumeLock().readLock().unlock(); |
| 489 | + } |
| 490 | + }); |
| 491 | + Thread resetThread = new Thread(() -> { |
| 492 | + try { |
| 493 | + mqClientInstance.resetOffset(topic, group, offsetTable); |
| 494 | + } catch (Throwable t) { |
| 495 | + backgroundFailure.compareAndSet(null, t); |
| 496 | + } |
| 497 | + }); |
| 498 | + |
| 499 | + consumingThread.start(); |
| 500 | + assertTrue(consumeLockHeld.await(5, TimeUnit.SECONDS)); |
| 501 | + |
| 502 | + resetThread.start(); |
| 503 | + assertTrue(suspendCalled.await(5, TimeUnit.SECONDS)); |
| 504 | + assertFalse(updateOffsetCalled.await(200, TimeUnit.MILLISECONDS)); |
| 505 | + |
| 506 | + releaseConsumeLock.countDown(); |
| 507 | + consumingThread.join(5000); |
| 508 | + resetThread.join(5000); |
| 509 | + |
| 510 | + assertNull(backgroundFailure.get()); |
| 511 | + assertFalse(consumingThread.isAlive()); |
| 512 | + assertFalse(resetThread.isAlive()); |
| 513 | + assertTrue(updateOffsetCalled.await(1, TimeUnit.SECONDS)); |
| 514 | + verify(consumer).updateConsumeOffset(messageQueue, 0L); |
| 515 | + verify(rebalanceImpl).removeUnnecessaryMessageQueue(messageQueue, processQueue); |
| 516 | + } |
| 517 | + |
400 | 518 | @Test |
401 | 519 | public void testGetConsumerStatus() { |
402 | 520 | topicRouteTable.put(topic, createTopicRouteData()); |
@@ -475,17 +593,26 @@ private HashMap<Long, String> createBrokerAddrMap() { |
475 | 593 | } |
476 | 594 |
|
477 | 595 | private MQConsumerInner createMQConsumerInner() { |
| 596 | + RebalanceImpl rebalanceImpl = mock(RebalanceImpl.class); |
| 597 | + when(rebalanceImpl.removeUnnecessaryMessageQueue(any(MessageQueue.class), any(ProcessQueue.class))).thenReturn(true); |
| 598 | + return createMQConsumerInner(new ProcessQueue(), false, rebalanceImpl); |
| 599 | + } |
| 600 | + |
| 601 | + private MQConsumerInner createMQConsumerInner(ProcessQueue processQueue, boolean orderly, RebalanceImpl rebalanceImpl) { |
| 602 | + ConcurrentMap<MessageQueue, ProcessQueue> processQueueMap = new ConcurrentHashMap<>(); |
| 603 | + processQueueMap.put(createMessageQueue(), processQueue); |
| 604 | + return createMQConsumerInner(processQueueMap, orderly, rebalanceImpl); |
| 605 | + } |
| 606 | + |
| 607 | + private MQConsumerInner createMQConsumerInner(ConcurrentMap<MessageQueue, ProcessQueue> processQueueMap, boolean orderly, RebalanceImpl rebalanceImpl) { |
478 | 608 | DefaultMQPushConsumerImpl result = mock(DefaultMQPushConsumerImpl.class); |
479 | 609 | Set<SubscriptionData> subscriptionDataSet = new HashSet<>(); |
480 | 610 | SubscriptionData subscriptionData = mock(SubscriptionData.class); |
481 | 611 | subscriptionDataSet.add(subscriptionData); |
482 | 612 | when(result.subscriptions()).thenReturn(subscriptionDataSet); |
483 | | - RebalanceImpl rebalanceImpl = mock(RebalanceImpl.class); |
484 | | - ConcurrentMap<MessageQueue, ProcessQueue> processQueueMap = new ConcurrentHashMap<>(); |
485 | | - ProcessQueue processQueue = new ProcessQueue(); |
486 | | - processQueueMap.put(createMessageQueue(), processQueue); |
487 | 613 | when(rebalanceImpl.getProcessQueueTable()).thenReturn(processQueueMap); |
488 | 614 | when(result.getRebalanceImpl()).thenReturn(rebalanceImpl); |
| 615 | + when(result.isConsumeOrderly()).thenReturn(orderly); |
489 | 616 | OffsetStore offsetStore = mock(OffsetStore.class); |
490 | 617 | when(result.getOffsetStore()).thenReturn(offsetStore); |
491 | 618 | ConsumeMessageService consumeMessageService = mock(ConsumeMessageService.class); |
|
0 commit comments