@Test public void testWhenFailureDuringInit() throws InterruptedException { when(tokenStore.fetchSegments(anyString())) .thenThrow(new RuntimeException("Faking issue during fetchSegments")) .thenReturn(new int[]{}) .thenReturn(new int[]{0}); doThrow(new RuntimeException("Faking issue during initializeTokenSegments")) // and on further calls .doNothing() .when(tokenStore).initializeTokenSegments(anyString(), anyInt()); testSubject.start(); Thread.sleep(2500); assertEquals(1, testSubject.activeProcessorThreads()); }
@Test public void testPublishedEventsGetPassedToHandler() throws Exception { CountDownLatch countDownLatch = new CountDownLatch(2); doAnswer(invocation -> { countDownLatch.countDown(); return null; }).when(mockHandler).handle(any()); testSubject.start(); // give it a bit of time to start Thread.sleep(200); eventBus.publish(createEvents(2)); assertTrue("Expected Handler to have received 2 published events", countDownLatch.await(5, TimeUnit.SECONDS)); }
@Test(expected = IllegalStateException.class) public void testResetRejectedWhileRunning() { testSubject.start(); testSubject.resetTokens(); }
@Test public void testMultiThreadPublishedEventsGetPassedToHandler() throws Exception { CountDownLatch countDownLatch = new CountDownLatch(2); final AcknowledgeByThread acknowledgeByThread = new AcknowledgeByThread(); doAnswer(invocation -> { acknowledgeByThread.addMessage(Thread.currentThread(), (EventMessage<?>) invocation.getArguments()[0]); countDownLatch.countDown(); return null; }).when(mockHandler).handle(any()); testSubject.start(); eventBus.publish(createEvents(2)); assertTrue("Expected Handler to have received 2 published events", countDownLatch.await(5, SECONDS)); acknowledgeByThread.assertEventsAckedByMultipleThreads(); acknowledgeByThread.assertEventsAddUpTo(2); }
@Test public void testProcessorRetriesOnTransientExceptionWhenLoadingToken() throws Exception { CountDownLatch countDownLatch = new CountDownLatch(1); doAnswer(invocation -> { countDownLatch.countDown(); return null; }).when(mockHandler).handle(any()); when(tokenStore.fetchToken("test", 0)).thenThrow(new RuntimeException("Faking a recoverable issue")) .thenCallRealMethod(); testSubject.start(); // give it a bit of time to start Thread.sleep(200); eventBus.publish(createEvent()); assertTrue("Expected Handler to have received published event", countDownLatch.await(5, TimeUnit.SECONDS)); assertTrue(testSubject.isRunning()); assertFalse(testSubject.isError()); assertEquals(Collections.singletonList(5000L), sleepInstructions); }
@Test public void testProcessorWorkerCount() { testSubject.start(); // give it some time to split segments from the store and submit to executor service. assertWithin(1, SECONDS, () -> assertThat(testSubject.activeProcessorThreads(), is(2))); assertThat(testSubject.processingStatus().size(), is(2)); assertTrue(testSubject.processingStatus().containsKey(0)); assertTrue(testSubject.processingStatus().containsKey(1)); assertWithin(1, SECONDS, () -> assertTrue(testSubject.processingStatus().get(0).isCaughtUp())); assertWithin(1, SECONDS, () -> assertTrue(testSubject.processingStatus().get(1).isCaughtUp())); }
@Test public void testTokenIsStoredWhenEventIsRead() throws Exception { CountDownLatch countDownLatch = new CountDownLatch(1); testSubject.registerHandlerInterceptor(((unitOfWork, interceptorChain) -> { unitOfWork.onCleanup(uow -> countDownLatch.countDown()); return interceptorChain.proceed(); })); testSubject.start(); // give it a bit of time to start Thread.sleep(200); eventBus.publish(createEvent()); assertTrue("Expected Unit of Work to have reached clean up phase", countDownLatch.await(5, TimeUnit.SECONDS)); verify(tokenStore).extendClaim(eq(testSubject.getName()), anyInt()); verify(tokenStore).storeToken(any(), any(), anyInt()); assertNotNull(tokenStore.fetchToken(testSubject.getName(), 0)); }
@Test public void testMultiThreadTokenIsStoredWhenEventIsRead() throws Exception { CountDownLatch countDownLatch = new CountDownLatch(2); testSubject.registerHandlerInterceptor(((unitOfWork, interceptorChain) -> { unitOfWork.onCleanup(uow -> countDownLatch.countDown()); return interceptorChain.proceed(); })); testSubject.start(); eventBus.publish(createEvents(2)); assertTrue("Expected Unit of Work to have reached clean up phase", countDownLatch.await(5, SECONDS)); verify(tokenStore, atLeastOnce()).storeToken(any(), any(), anyInt()); assertThat(tokenStore.fetchToken(testSubject.getName(), 0), notNullValue()); assertThat(tokenStore.fetchToken(testSubject.getName(), 1), notNullValue()); }
@Test public void testMultiThreadSegmentsExceedsWorkerCount() throws Exception { configureProcessor(TrackingEventProcessorConfiguration.forParallelProcessing(2) .andInitialSegmentsCount(3)); CountDownLatch countDownLatch = new CountDownLatch(2); final AcknowledgeByThread acknowledgeByThread = new AcknowledgeByThread(); doAnswer(invocation -> { acknowledgeByThread.addMessage(Thread.currentThread(), (EventMessage<?>) invocation.getArguments()[0]); countDownLatch.countDown(); return null; }).when(mockHandler).handle(any()); testSubject.start(); eventBus.publish(createEvents(3)); assertTrue("Expected Handler to have received (only) 2 out of 3 published events", countDownLatch.await(5, SECONDS)); acknowledgeByThread.assertEventsAddUpTo(2); }
@Test public void testProcessorStopsOnNonTransientExceptionWhenLoadingToken() { when(tokenStore.fetchToken("test", 0)).thenThrow(new SerializationException("Faking a serialization issue")); testSubject.start(); assertWithin( 1, TimeUnit.SECONDS, () -> assertFalse("Expected processor to have stopped", testSubject.isRunning()) ); assertWithin( 1, TimeUnit.SECONDS, () -> assertTrue("Expected processor to set the error flag", testSubject.isError()) ); assertEquals(Collections.emptyList(), sleepInstructions); }
@Test public void testBlacklistingSegmentWillHaveProcessorClaimAnotherOne() { tokenStore.storeToken(new GlobalSequenceTrackingToken(1L), "test", 0); tokenStore.storeToken(new GlobalSequenceTrackingToken(2L), "test", 1); tokenStore.storeToken(new GlobalSequenceTrackingToken(2L), "test", 2); testSubject.start(); assertWithin(1, SECONDS, () -> assertEquals(0, testSubject.availableProcessorThreads())); assertEquals(new HashSet<>(asList(0, 1)), testSubject.processingStatus().keySet()); testSubject.releaseSegment(0); assertWithin(5, SECONDS, () -> assertTrue(testSubject.processingStatus().containsKey(2))); assertEquals(new HashSet<>(asList(2, 1)), testSubject.processingStatus().keySet()); assertWithin(2, SECONDS, () -> assertEquals(0, testSubject.availableProcessorThreads())); }
@Test public void testHasAvailableSegments() { assertEquals(1, testSubject.availableProcessorThreads()); testSubject.start(); assertWithin(1, TimeUnit.SECONDS, () -> assertEquals(0, testSubject.availableProcessorThreads())); testSubject.releaseSegment(0); assertWithin(2, TimeUnit.SECONDS, () -> assertEquals(1, testSubject.availableProcessorThreads())); }
@Test public void testReleaseSegment() { testSubject.start(); assertWithin(1, TimeUnit.SECONDS, () -> assertEquals(1, testSubject.activeProcessorThreads())); testSubject.releaseSegment(0); assertWithin(2, TimeUnit.SECONDS, () -> assertEquals(0, testSubject.activeProcessorThreads())); assertWithin(15, TimeUnit.SECONDS, () -> assertEquals(1, testSubject.activeProcessorThreads())); }
@Test public void testProcessorInitializesMoreTokensThanWorkerCount() throws InterruptedException { configureProcessor(TrackingEventProcessorConfiguration.forParallelProcessing(2) .andInitialSegmentsCount(4)); testSubject.start(); // give it some time to split segments from the store and submit to executor service. Thread.sleep(200); assertThat(testSubject.activeProcessorThreads(), is(2)); int[] actual = tokenStore.fetchSegments(testSubject.getName()); Arrays.sort(actual); assertArrayEquals(new int[]{0, 1, 2, 3}, actual); }
/** * This processor won't be able to handle any segments, as claiming a segment will fail. */ @Test public void testProcessorWorkerCountWithMultipleSegmentsClaimFails() throws InterruptedException { tokenStore.storeToken(new GlobalSequenceTrackingToken(1L), "test", 0); tokenStore.storeToken(new GlobalSequenceTrackingToken(2L), "test", 1); // Will skip segments. doThrow(new UnableToClaimTokenException("Failed")).when(tokenStore).extendClaim("test", 0); doThrow(new UnableToClaimTokenException("Failed")).when(tokenStore).fetchToken("test", 0); doThrow(new UnableToClaimTokenException("Failed")).when(tokenStore).extendClaim("test", 1); doThrow(new UnableToClaimTokenException("Failed")).when(tokenStore).fetchToken("test", 1); testSubject.start(); // give it some time to split segments from the store and submit to executor service. Thread.sleep(200); assertWithin(1, SECONDS, () -> assertThat(testSubject.activeProcessorThreads(), is(0))); }
@Test public void testMultiThreadContinueFromPreviousToken() throws Exception { tokenStore = spy(new InMemoryTokenStore()); eventBus.publish(createEvents(10)); TrackedEventMessage<?> firstEvent = eventBus.openStream(null).nextAvailable(); tokenStore.storeToken(firstEvent.trackingToken(), testSubject.getName(), 0); assertEquals(firstEvent.trackingToken(), tokenStore.fetchToken(testSubject.getName(), 0)); final AcknowledgeByThread acknowledgeByThread = new AcknowledgeByThread(); CountDownLatch countDownLatch = new CountDownLatch(9); doAnswer(invocation -> { acknowledgeByThread.addMessage(Thread.currentThread(), (EventMessage<?>) invocation.getArguments()[0]); countDownLatch.countDown(); return null; }).when(mockHandler).handle(any()); configureProcessor(TrackingEventProcessorConfiguration.forParallelProcessing(2)); testSubject.start(); assertTrue("Expected 9 invocations on Event Handler by now, missing " + countDownLatch.getCount(), countDownLatch.await(60, SECONDS)); acknowledgeByThread.assertEventsAckedByMultipleThreads(); acknowledgeByThread.assertEventsAddUpTo(9); }
@Test public void testProcessorWorkerCountWithMultipleSegmentsWithOneThread() throws InterruptedException { tokenStore.storeToken(new GlobalSequenceTrackingToken(1L), "test", 0); tokenStore.storeToken(new GlobalSequenceTrackingToken(2L), "test", 1); configureProcessor(TrackingEventProcessorConfiguration.forSingleThreadedProcessing()); testSubject.start(); // give it some time to split segments from the store and submit to executor service. Thread.sleep(200); assertThat(testSubject.activeProcessorThreads(), is(1)); }
@Test public void testProcessorInitializesAndUsesSameTokens() { configureProcessor(TrackingEventProcessorConfiguration.forParallelProcessing(6) .andInitialSegmentsCount(6)); testSubject.start(); assertWithin(5, SECONDS, () -> assertThat(testSubject.activeProcessorThreads(), is(6))); int[] actual = tokenStore.fetchSegments(testSubject.getName()); Arrays.sort(actual); assertArrayEquals(new int[]{0, 1, 2, 3, 4, 5}, actual); }
@Test public void testTokenIsNotStoredWhenUnitOfWorkIsRolledBack() throws Exception { CountDownLatch countDownLatch = new CountDownLatch(1); testSubject.registerHandlerInterceptor(((unitOfWork, interceptorChain) -> { unitOfWork.onCommit(uow -> { throw new MockException(); }); return interceptorChain.proceed(); })); testSubject.registerHandlerInterceptor(((unitOfWork, interceptorChain) -> { unitOfWork.onCleanup(uow -> countDownLatch.countDown()); return interceptorChain.proceed(); })); testSubject.start(); // give it a bit of time to start Thread.sleep(200); eventBus.publish(createEvent()); assertTrue("Expected Unit of Work to have reached clean up phase", countDownLatch.await(5, TimeUnit.SECONDS)); assertNull(tokenStore.fetchToken(testSubject.getName(), 0)); }
@Test public void testProcessorExtendsClaimOnSegment() throws InterruptedException { tokenStore.storeToken(new GlobalSequenceTrackingToken(1L), "test", 0); tokenStore.storeToken(new GlobalSequenceTrackingToken(2L), "test", 1); testSubject.start(); // give it some time to split segments from the store and submit to executor service. Thread.sleep(200); eventBus.publish(createEvents(10)); assertWithin(200, MILLISECONDS, () -> verify(tokenStore, atLeast(1)).extendClaim("test", 0)); assertWithin(200, MILLISECONDS, () -> verify(tokenStore, atLeast(1)).extendClaim("test", 1)); assertWithin(1, SECONDS, () -> assertThat(testSubject.activeProcessorThreads(), is(2))); }