protected void storeEvents(EventMessage<?>... events) { eventStore.publish(events); }
@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 public void testHandlerIsInvokedInTransactionScope() throws Exception { CountDownLatch countDownLatch = new CountDownLatch(1); AtomicInteger counter = new AtomicInteger(); AtomicInteger counterAtHandle = new AtomicInteger(); when(mockTransactionManager.startTransaction()).thenAnswer(i -> { counter.incrementAndGet(); return mockTransaction; }); doAnswer(i -> counter.decrementAndGet()).when(mockTransaction).rollback(); doAnswer(i -> counter.decrementAndGet()).when(mockTransaction).commit(); doAnswer(invocation -> { counterAtHandle.set(counter.get()); 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)); assertEquals(1, counterAtHandle.get()); }
@Test public void testRestartSubscribingEventProcessor() throws Exception { CountDownLatch countDownLatch = new CountDownLatch(2); doAnswer(invocation -> { countDownLatch.countDown(); return null; }).when(mockHandler).handle(any()); testSubject.start(); testSubject.shutDown(); testSubject.start(); eventBus.publish(EventTestUtils.createEvents(2)); assertTrue("Expected Handler to have received 2 published events", countDownLatch.await(5, TimeUnit.SECONDS)); } }
@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 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 testResetToPositionCausesCertainEventsToBeReplayed() throws Exception { when(mockHandler.supportsReset()).thenReturn(true); final List<String> handled = new CopyOnWriteArrayList<>(); final List<String> handledInRedelivery = new CopyOnWriteArrayList<>(); //noinspection Duplicates doAnswer(i -> { EventMessage message = i.getArgument(0); handled.add(message.getIdentifier()); if (ReplayToken.isReplay(message)) { handledInRedelivery.add(message.getIdentifier()); } return null; }).when(mockHandler).handle(any()); eventBus.publish(createEvents(4)); testSubject.start(); assertWithin(1, TimeUnit.SECONDS, () -> assertEquals(4, handled.size())); testSubject.shutDown(); testSubject.resetTokens(source -> new GlobalSequenceTrackingToken(1L)); testSubject.start(); assertWithin(1, TimeUnit.SECONDS, () -> assertEquals(6, handled.size())); assertFalse(handledInRedelivery.contains(handled.get(0))); assertFalse(handledInRedelivery.contains(handled.get(1))); assertEquals(handled.subList(2, 4), handled.subList(4, 6)); assertEquals(handled.subList(4, 6), handledInRedelivery); assertTrue(testSubject.processingStatus().get(0).isReplaying()); eventBus.publish(createEvents(1)); assertWithin(1, TimeUnit.SECONDS, () -> assertFalse(testSubject.processingStatus().get(0).isReplaying())); }
@Test public void testResetBeforeStartingPerformsANormalRun() throws Exception { when(mockHandler.supportsReset()).thenReturn(true); final List<String> handled = new CopyOnWriteArrayList<>(); final List<String> handledInRedelivery = new CopyOnWriteArrayList<>(); //noinspection Duplicates doAnswer(i -> { EventMessage message = i.getArgument(0); handled.add(message.getIdentifier()); if (ReplayToken.isReplay(message)) { handledInRedelivery.add(message.getIdentifier()); } return null; }).when(mockHandler).handle(any()); testSubject.start(); testSubject.shutDown(); testSubject.resetTokens(); testSubject.start(); eventBus.publish(createEvents(4)); assertWithin(1, TimeUnit.SECONDS, () -> assertEquals(4, handled.size())); assertEquals(0, handledInRedelivery.size()); assertFalse(testSubject.processingStatus().get(0).isReplaying()); }
@Test public void testResetCausesEventsToBeReplayed() throws Exception { when(mockHandler.supportsReset()).thenReturn(true); final List<String> handled = new CopyOnWriteArrayList<>(); final List<String> handledInRedelivery = new CopyOnWriteArrayList<>(); //noinspection Duplicates doAnswer(i -> { EventMessage message = i.getArgument(0); handled.add(message.getIdentifier()); if (ReplayToken.isReplay(message)) { handledInRedelivery.add(message.getIdentifier()); } return null; }).when(mockHandler).handle(any()); eventBus.publish(createEvents(4)); testSubject.start(); assertWithin(1, TimeUnit.SECONDS, () -> assertEquals(4, handled.size())); testSubject.shutDown(); testSubject.resetTokens(); // Resetting twice caused problems (see issue #559) testSubject.resetTokens(); testSubject.start(); assertWithin(1, TimeUnit.SECONDS, () -> assertEquals(8, handled.size())); assertEquals(handled.subList(0, 4), handled.subList(4, 8)); assertEquals(handled.subList(4, 8), handledInRedelivery); assertTrue(testSubject.processingStatus().get(0).isReplaying()); eventBus.publish(createEvents(1)); assertWithin(1, TimeUnit.SECONDS, () -> assertFalse(testSubject.processingStatus().get(0).isReplaying())); }
@Test public void testFirstTokenIsStoredWhenUnitOfWorkIsRolledBackOnSecondEvent() throws Exception { List<? extends EventMessage<?>> events = createEvents(2); CountDownLatch countDownLatch = new CountDownLatch(2); testSubject.registerHandlerInterceptor(((unitOfWork, interceptorChain) -> { unitOfWork.onCommit(uow -> { if (uow.getMessage().equals(events.get(1))) { 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(events); assertTrue("Expected Unit of Work to have reached clean up phase", countDownLatch.await(5, TimeUnit.SECONDS)); verify(tokenStore, atLeastOnce()).storeToken(any(), any(), anyInt()); assertNotNull(tokenStore.fetchToken(testSubject.getName(), 0)); }
@Test public void testTokenIsStoredOncePerEventBatch() throws Exception { testSubject = TrackingEventProcessor.builder() .name("test") .eventHandlerInvoker(eventHandlerInvoker) .messageSource(eventBus) .tokenStore(tokenStore) .transactionManager(NoTransactionManager.INSTANCE) .build(); CountDownLatch countDownLatch = new CountDownLatch(2); 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(createEvents(2)); assertTrue("Expected Unit of Work to have reached clean up phase for 2 messages", countDownLatch.await(5, TimeUnit.SECONDS)); InOrder inOrder = inOrder(tokenStore); inOrder.verify(tokenStore, times(1)).extendClaim(eq(testSubject.getName()), anyInt()); inOrder.verify(tokenStore, times(1)).storeToken(any(), any(), anyInt()); assertNotNull(tokenStore.fetchToken(testSubject.getName(), 0)); }
@Test public void testMultiThreadProcessorGoesToRetryModeWhenOpenStreamFails() throws Exception { eventBus = spy(eventBus); tokenStore = new InMemoryTokenStore(); eventBus.publish(createEvents(5)); when(eventBus.openStream(any())).thenThrow(new MockException()).thenCallRealMethod(); final AcknowledgeByThread acknowledgeByThread = new AcknowledgeByThread(); CountDownLatch countDownLatch = new CountDownLatch(5); doAnswer(invocation -> { acknowledgeByThread.addMessage(Thread.currentThread(), (EventMessage<?>) invocation.getArguments()[0]); countDownLatch.countDown(); return null; }).when(mockHandler).handle(any()); testSubject = TrackingEventProcessor.builder() .name("test") .eventHandlerInvoker(eventHandlerInvoker) .messageSource(eventBus) .tokenStore(tokenStore) .transactionManager(NoTransactionManager.INSTANCE) .build(); testSubject.start(); assertTrue("Expected 5 invocations on Event Handler by now", countDownLatch.await(10, SECONDS)); acknowledgeByThread.assertEventsAddUpTo(5); verify(eventBus, times(2)).openStream(any()); }
@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 @DirtiesContext public void testContinueFromPreviousToken() throws Exception { tokenStore = 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)); List<EventMessage<?>> ackedEvents = new ArrayList<>(); CountDownLatch countDownLatch = new CountDownLatch(9); doAnswer(invocation -> { ackedEvents.add((EventMessage<?>) invocation.getArguments()[0]); countDownLatch.countDown(); return null; }).when(mockHandler).handle(any()); testSubject = TrackingEventProcessor.builder() .name("test") .eventHandlerInvoker(eventHandlerInvoker) .messageSource(eventBus) .tokenStore(tokenStore) .transactionManager(NoTransactionManager.INSTANCE) .build(); testSubject.start(); // give it a bit of time to start Thread.sleep(200); assertTrue("Expected 9 invocations on Event Handler by now", countDownLatch.await(5, TimeUnit.SECONDS)); assertEquals(9, ackedEvents.size()); }
@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))); }
@Test public void testMultiThreadTokensAreStoredWhenUnitOfWorkIsRolledBackOnSecondEvent() throws Exception { List<? extends EventMessage<?>> events = createEvents(2); CountDownLatch countDownLatch = new CountDownLatch(2); //noinspection Duplicates testSubject.registerHandlerInterceptor(((unitOfWork, interceptorChain) -> { unitOfWork.onCommit(uow -> { if (uow.getMessage().equals(events.get(1))) { throw new MockException(); } }); return interceptorChain.proceed(); })); testSubject.registerHandlerInterceptor(((unitOfWork, interceptorChain) -> { unitOfWork.onCleanup(uow -> countDownLatch.countDown()); return interceptorChain.proceed(); })); testSubject.start(); eventBus.publish(events); assertTrue("Expected Unit of Work to have reached clean up phase", countDownLatch.await(5, SECONDS)); assertNotNull(tokenStore.fetchToken(testSubject.getName(), 0)); assertNotNull(tokenStore.fetchToken(testSubject.getName(), 1)); }