/* * Copyright 2015 Groupon, Inc * Copyright 2015 The Billing Project, LLC * * The Billing Project licenses this file to you under the Apache License, version 2.0 * (the "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package org.killbill.queue.dispatching; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import org.awaitility.Awaitility; import org.joda.time.DateTime; import org.killbill.bus.api.BusEvent; import org.killbill.bus.dao.BusEventModelDao; import org.killbill.queue.api.QueueEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class TestDispatcher { private final int QUEUE_SIZE = 5; private Dispatcher<BusEventModelDao> dispatcher; private TestCallableCallback callback; @BeforeClass(groups = "fast") public void beforeClass() throws Exception { final ThreadFactory testThreadFactory = new ThreadFactory() { @Override public Thread newThread(final Runnable r) { return new Thread(new ThreadGroup("TestGrp"), r, "test-grp--th"); } }; this.callback = new TestCallableCallback(); this.dispatcher = new Dispatcher<BusEventModelDao>(1, 1, 5, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>(QUEUE_SIZE), testThreadFactory, new TestBlockingRejectionExecutionHandler(callback)); this.dispatcher.start(); } @Test(groups = "fast") public void testBlockingRejectionHandler() throws Exception { callback.block(); for (int i = 0; i < QUEUE_SIZE + 2; i++) { dispatch(i, callback); } Awaitility.await().atMost(5, TimeUnit.SECONDS).until(new Callable<Boolean>() { @Override public Boolean call() throws Exception { return callback.getProcessed().size() == QUEUE_SIZE + 2; } }); } private void dispatch(final int i, final TestCallableCallback callback) { final BusEventModelDao e1 = new BusEventModelDao("owner", new DateTime(), String.class.getName(), "e-" + i, UUID.randomUUID(), 1L, 1L); dispatcher.dispatch(e1, callback); } private class TestBlockingRejectionExecutionHandler extends BlockingRejectionExecutionHandler { private final TestCallableCallback callback; public TestBlockingRejectionExecutionHandler(final TestCallableCallback callback) { this.callback = callback; } @Override public void rejectedExecution(final Runnable r, final ThreadPoolExecutor executor) { callback.unblock(); super.rejectedExecution(r, executor); } } private class TestCallableCallback implements CallableCallback<QueueEvent, BusEventModelDao> { private final Logger logger = LoggerFactory.getLogger(TestCallableCallback.class); private volatile boolean isBlocked; private final List<QueueEvent> processed; public void block() { synchronized (this) { this.isBlocked = true; } } public void unblock() { synchronized (this) { this.isBlocked = false; this.notifyAll(); } } public List<QueueEvent> getProcessed() { return processed; } public TestCallableCallback() { this.isBlocked = false; this.processed = new ArrayList<QueueEvent>(); } @Override public QueueEvent deserialize(final BusEventModelDao modelDao) { return new TestEvent(modelDao.getEventJson(), modelDao.getSearchKey1(), modelDao.getSearchKey2(), modelDao.getUserToken()); } @Override public void dispatch(final QueueEvent event, final BusEventModelDao modelDao) throws Exception { synchronized (this) { while (isBlocked) { logger.info("Thread " + Thread.currentThread().getId() + " blocking..."); this.wait(); logger.info("Thread " + Thread.currentThread().getId() + " unblocking..."); } } logger.info("Got entry " + modelDao.getEventJson()); processed.add(event); } @Override public void updateErrorCountOrMoveToHistory(final QueueEvent event, final BusEventModelDao modelDao, final long errorCount, final Throwable lastException) { } } public static class TestEvent implements BusEvent { private final String json; private final Long searchKey1; private final Long searchKey2; private final UUID userToken; @JsonCreator public TestEvent(@JsonProperty("json") final String json, @JsonProperty("searchKey1") final Long searchKey1, @JsonProperty("searchKey2") final Long searchKey2, @JsonProperty("userToken") final UUID userToken) { this.json = json; this.searchKey2 = searchKey2; this.searchKey1 = searchKey1; this.userToken = userToken; } public String getJson() { return json; } @Override public Long getSearchKey1() { return searchKey1; } @Override public Long getSearchKey2() { return searchKey2; } @Override public UUID getUserToken() { return userToken; } } }