|
37 | 37 | import java.util.List; |
38 | 38 | import java.util.concurrent.BlockingDeque; |
39 | 39 | import java.util.concurrent.CountDownLatch; |
| 40 | +import java.util.concurrent.CyclicBarrier; |
40 | 41 | import java.util.concurrent.ExecutorService; |
| 42 | +import java.util.concurrent.Future; |
41 | 43 | import java.util.concurrent.Semaphore; |
42 | 44 | import java.util.concurrent.TimeUnit; |
43 | 45 | import java.util.concurrent.atomic.AtomicBoolean; |
|
59 | 61 | import static org.mockito.Mockito.times; |
60 | 62 | import static org.mockito.Mockito.verify; |
61 | 63 | import static org.mockito.Mockito.when; |
62 | | -import static org.testng.Assert.assertEquals; |
63 | | -import static org.testng.Assert.assertNotNull; |
64 | | -import static org.testng.Assert.assertNull; |
65 | | -import static org.testng.Assert.assertTrue; |
66 | | -import static org.testng.Assert.fail; |
| 64 | +import static org.testng.Assert.*; |
67 | 65 |
|
68 | 66 | public class ImportTaskListenerImplTest { |
69 | 67 | private static final String VALID_IMPORT_ID = "valid-id"; |
@@ -286,6 +284,7 @@ public void testStartAsyncImportIfAvailable_WithInvalidStatus() throws Exception |
286 | 284 |
|
287 | 285 | @Test |
288 | 286 | public void testStartImportConsumer_Successful() throws Exception { |
| 287 | + Mockito.doReturn("import123").when(importRequest).getImportId(); |
289 | 288 | when(importRequest.getStatus()).thenReturn(WAITING); |
290 | 289 | when(importRequest.getTopicName()).thenReturn("topic1"); |
291 | 290 |
|
@@ -565,6 +564,158 @@ public void testStartInternalIsNonBlocking() throws InterruptedException { |
565 | 564 | blockStartNextLatch.countDown(); |
566 | 565 | } |
567 | 566 |
|
| 567 | + @Test |
| 568 | + public void testImportNotProcessedWhenPassive() throws Exception { |
| 569 | + Mockito.doReturn("import123").when(importRequest).getImportId(); |
| 570 | + when(importRequest.getStatus()).thenReturn(WAITING); |
| 571 | + when(requestQueue.poll(anyLong(), any(TimeUnit.class))).thenReturn("import123"); |
| 572 | + importTaskListener.instanceIsPassive(); |
| 573 | + importTaskListener.onReceiveImportRequest(importRequest); |
| 574 | + Thread.sleep(200); |
| 575 | + verify(notificationHookConsumer, never()).startAsyncImportConsumer(any(), anyString(), anyString()); |
| 576 | + } |
| 577 | + |
| 578 | + @Test |
| 579 | + public void testExecutorNotRecreatedWhenPassive() throws Exception { |
| 580 | + Mockito.doReturn("import123").when(importRequest).getImportId(); |
| 581 | + when(importRequest.getStatus()).thenReturn(WAITING); |
| 582 | + when(requestQueue.poll(anyLong(), any(TimeUnit.class))).thenReturn("import123"); |
| 583 | + when(importRequest.getStatus()).thenReturn(WAITING); |
| 584 | + when(requestQueue.poll(anyLong(), any(TimeUnit.class))).thenReturn("import123"); |
| 585 | + importTaskListener.instanceIsPassive(); |
| 586 | + Field executorField = ImportTaskListenerImpl.class.getDeclaredField("executorService"); |
| 587 | + executorField.setAccessible(true); |
| 588 | + ExecutorService exec = (ExecutorService) executorField.get(importTaskListener); |
| 589 | + if (exec != null) { |
| 590 | + exec.shutdownNow(); |
| 591 | + } |
| 592 | + importTaskListener.onReceiveImportRequest(importRequest); |
| 593 | + Thread.sleep(200); |
| 594 | + ExecutorService execAfter = (ExecutorService) executorField.get(importTaskListener); |
| 595 | + // Should remain null when passive |
| 596 | + assertTrue(execAfter == null); |
| 597 | + } |
| 598 | + |
| 599 | + @Test |
| 600 | + public void testExecutorRecreatedWhenActive() throws Exception { |
| 601 | + when(importRequest.getStatus()).thenReturn(WAITING); |
| 602 | + when(requestQueue.poll(anyLong(), any(TimeUnit.class))).thenReturn("import123"); |
| 603 | + importTaskListener.instanceIsActive(); |
| 604 | + Field executorField = ImportTaskListenerImpl.class.getDeclaredField("executorService"); |
| 605 | + executorField.setAccessible(true); |
| 606 | + ExecutorService exec = (ExecutorService) executorField.get(importTaskListener); |
| 607 | + if (exec != null) { |
| 608 | + exec.shutdownNow(); |
| 609 | + } |
| 610 | + importTaskListener.onReceiveImportRequest(importRequest); |
| 611 | + Thread.sleep(200); |
| 612 | + ExecutorService execAfter = (ExecutorService) executorField.get(importTaskListener); |
| 613 | + assertNotNull(execAfter); |
| 614 | + assertTrue(!execAfter.isShutdown() && !execAfter.isTerminated()); |
| 615 | + } |
| 616 | + |
| 617 | + @Test |
| 618 | + public void ensureExecutorAliveCreatesSingleInstanceUnderConcurrency() throws Exception { |
| 619 | + // Ensure active mode and a clean executor state |
| 620 | + importTaskListener.instanceIsActive(); |
| 621 | + |
| 622 | + Field execField = ImportTaskListenerImpl.class.getDeclaredField("executorService"); |
| 623 | + execField.setAccessible(true); |
| 624 | + execField.set(importTaskListener, null); |
| 625 | + |
| 626 | + int threads = 64; |
| 627 | + CyclicBarrier start = new CyclicBarrier(threads); |
| 628 | + ExecutorService callers = java.util.concurrent.Executors.newFixedThreadPool(threads); |
| 629 | + |
| 630 | + List<Future<ExecutorService>> futures = new ArrayList<>(); |
| 631 | + for (int i = 0; i < threads; i++) { |
| 632 | + futures.add(callers.submit(() -> { |
| 633 | + start.await(); |
| 634 | + return importTaskListener.ensureExecutorAlive(); |
| 635 | + })); |
| 636 | + } |
| 637 | + |
| 638 | + ExecutorService first = null; |
| 639 | + for (Future<ExecutorService> f : futures) { |
| 640 | + ExecutorService es = f.get(10, TimeUnit.SECONDS); |
| 641 | + assertNotNull(es, "Executor should be created"); |
| 642 | + if (first == null) first = es; else assertSame(first, es, "All callers must see the same instance"); |
| 643 | + } |
| 644 | + |
| 645 | + callers.shutdownNow(); |
| 646 | + first.shutdownNow(); |
| 647 | + } |
| 648 | + |
| 649 | + @Test |
| 650 | + public void ensureExecutorAliveRecreatesOnceIfShutdownUnderConcurrency() throws Exception { |
| 651 | + // Ensure active mode |
| 652 | + importTaskListener.instanceIsActive(); |
| 653 | + |
| 654 | + // First creation |
| 655 | + ExecutorService first = importTaskListener.ensureExecutorAlive(); |
| 656 | + assertNotNull(first); |
| 657 | + |
| 658 | + // Force recreate path: mark current as shutdown and ensure the field holds that value |
| 659 | + first.shutdown(); |
| 660 | + |
| 661 | + Field execField = ImportTaskListenerImpl.class.getDeclaredField("executorService"); |
| 662 | + execField.setAccessible(true); |
| 663 | + execField.set(importTaskListener, first); |
| 664 | + |
| 665 | + int threads = 64; |
| 666 | + CyclicBarrier start = new CyclicBarrier(threads); |
| 667 | + ExecutorService callers = java.util.concurrent.Executors.newFixedThreadPool(threads); |
| 668 | + |
| 669 | + List<Future<ExecutorService>> futures = new ArrayList<>(); |
| 670 | + for (int i = 0; i < threads; i++) { |
| 671 | + futures.add(callers.submit(() -> { |
| 672 | + start.await(); |
| 673 | + return importTaskListener.ensureExecutorAlive(); |
| 674 | + })); |
| 675 | + } |
| 676 | + |
| 677 | + ExecutorService second = null; |
| 678 | + for (Future<ExecutorService> f : futures) { |
| 679 | + ExecutorService es = f.get(10, TimeUnit.SECONDS); |
| 680 | + assertNotNull(es); |
| 681 | + if (second == null) second = es; else assertSame(second, es, "All callers must see the same new instance"); |
| 682 | + } |
| 683 | + |
| 684 | + assertNotSame(first, second, "Executor must be replaced after shutdown"); |
| 685 | + callers.shutdownNow(); |
| 686 | + second.shutdownNow(); |
| 687 | + } |
| 688 | + |
| 689 | + @Test |
| 690 | + public void ensureExecutorAliveReturnsNullWhenPassiveEvenUnderConcurrency() throws Exception { |
| 691 | + // Put into passive mode (ensureExecutorAlive should early-return null) |
| 692 | + importTaskListener.instanceIsPassive(); |
| 693 | + |
| 694 | + Field execField = ImportTaskListenerImpl.class.getDeclaredField("executorService"); |
| 695 | + execField.setAccessible(true); |
| 696 | + execField.set(importTaskListener, null); |
| 697 | + |
| 698 | + int threads = 32; |
| 699 | + CyclicBarrier start = new CyclicBarrier(threads); |
| 700 | + ExecutorService callers = java.util.concurrent.Executors.newFixedThreadPool(threads); |
| 701 | + |
| 702 | + List<Future<ExecutorService>> futures = new ArrayList<>(); |
| 703 | + for (int i = 0; i < threads; i++) { |
| 704 | + futures.add(callers.submit(() -> { |
| 705 | + start.await(); |
| 706 | + return importTaskListener.ensureExecutorAlive(); |
| 707 | + })); |
| 708 | + } |
| 709 | + |
| 710 | + for (Future<ExecutorService> f : futures) { |
| 711 | + assertNull(f.get(5, TimeUnit.SECONDS), "No executor should be created in passive mode"); |
| 712 | + } |
| 713 | + |
| 714 | + // Field should remain null |
| 715 | + assertNull(execField.get(importTaskListener)); |
| 716 | + callers.shutdownNow(); |
| 717 | + } |
| 718 | + |
568 | 719 | private void setExecutorServiceAndSemaphore(ImportTaskListenerImpl importTaskListener, ExecutorService mockExecutor, Semaphore mockSemaphore) { |
569 | 720 | try { |
570 | 721 | Field executorField = ImportTaskListenerImpl.class.getDeclaredField("executorService"); |
|
0 commit comments