/*
* Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved.
*
* Licensed 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 com.hazelcast.topic;
import com.hazelcast.config.Config;
import com.hazelcast.config.ListenerConfig;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.ITopic;
import com.hazelcast.core.Member;
import com.hazelcast.core.Message;
import com.hazelcast.core.MessageListener;
import com.hazelcast.instance.MemberImpl;
import com.hazelcast.monitor.impl.LocalTopicStatsImpl;
import com.hazelcast.nio.ObjectDataInput;
import com.hazelcast.nio.ObjectDataOutput;
import com.hazelcast.nio.serialization.DataSerializable;
import com.hazelcast.test.AssertTask;
import com.hazelcast.test.HazelcastParallelClassRunner;
import com.hazelcast.test.HazelcastTestSupport;
import com.hazelcast.test.TestHazelcastInstanceFactory;
import com.hazelcast.test.annotation.NightlyTest;
import com.hazelcast.test.annotation.ParallelTest;
import com.hazelcast.test.annotation.QuickTest;
import com.hazelcast.test.annotation.Repeat;
import com.hazelcast.topic.impl.TopicService;
import com.hazelcast.util.UuidUtil;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@RunWith(HazelcastParallelClassRunner.class)
@Category({QuickTest.class, ParallelTest.class})
public class TopicTest extends HazelcastTestSupport {
@Test
public void testDestroyTopicRemovesStatistics() {
String randomTopicName = randomString();
HazelcastInstance instance = createHazelcastInstance();
final ITopic<String> topic = instance.getTopic(randomTopicName);
topic.publish("foobar");
// we need to give the message the chance to be processed, else the topic statistics are recreated
// so in theory the destroy for the topic is broken
sleepSeconds(1);
topic.destroy();
final TopicService topicService = getNode(instance).nodeEngine.getService(TopicService.SERVICE_NAME);
assertTrueEventually(new AssertTask() {
@Override
public void run() {
boolean containsStats = topicService.getStatsMap().containsKey(topic.getName());
assertFalse(containsStats);
}
});
}
@Test
public void testTopicPublishingMember() {
final int nodeCount = 3;
final String randomName = "testTopicPublishingMember" + generateRandomString(5);
TestHazelcastInstanceFactory factory = createHazelcastInstanceFactory(nodeCount);
HazelcastInstance[] instances = factory.newInstances();
final AtomicInteger count1 = new AtomicInteger(0);
final AtomicInteger count2 = new AtomicInteger(0);
final AtomicInteger count3 = new AtomicInteger(0);
for (int i = 0; i < nodeCount; i++) {
final HazelcastInstance instance = instances[i];
ITopic<Member> topic = instance.getTopic(randomName);
topic.addMessageListener(new MessageListener<Member>() {
public void onMessage(Message<Member> message) {
Member publishingMember = message.getPublishingMember();
if (publishingMember.equals(instance.getCluster().getLocalMember())) {
count1.incrementAndGet();
}
Member messageObject = message.getMessageObject();
if (publishingMember.equals(messageObject)) {
count2.incrementAndGet();
}
if (publishingMember.localMember()) {
count3.incrementAndGet();
}
}
});
}
for (int i = 0; i < nodeCount; i++) {
HazelcastInstance instance = instances[i];
instance.getTopic(randomName).publish(instance.getCluster().getLocalMember());
}
assertTrueEventually(new AssertTask() {
@Override
public void run() {
assertEquals(nodeCount, count1.get());
assertEquals(nodeCount * nodeCount, count2.get());
assertEquals(nodeCount, count3.get());
}
});
}
@Test
@SuppressWarnings("unchecked")
public void testTopicLocalOrder() throws Exception {
final int nodeCount = 5;
final int count = 1000;
final String randomTopicName = randomString();
Config config = new Config();
config.getTopicConfig(randomTopicName).setGlobalOrderingEnabled(false);
TestHazelcastInstanceFactory factory = createHazelcastInstanceFactory(nodeCount);
final HazelcastInstance[] instances = factory.newInstances(config);
final List<TestMessage>[] messageLists = new List[nodeCount];
for (int i = 0; i < nodeCount; i++) {
messageLists[i] = new CopyOnWriteArrayList<TestMessage>();
}
final CountDownLatch startLatch = new CountDownLatch(nodeCount);
final CountDownLatch messageLatch = new CountDownLatch(nodeCount * nodeCount * count);
final CountDownLatch publishLatch = new CountDownLatch(nodeCount * count);
ExecutorService ex = Executors.newFixedThreadPool(nodeCount);
for (int i = 0; i < nodeCount; i++) {
final int finalI = i;
ex.execute(new Runnable() {
public void run() {
final List<TestMessage> messages = messageLists[finalI];
HazelcastInstance hz = instances[finalI];
ITopic<TestMessage> topic = hz.getTopic(randomTopicName);
topic.addMessageListener(new MessageListener<TestMessage>() {
public void onMessage(Message<TestMessage> message) {
messages.add(message.getMessageObject());
messageLatch.countDown();
}
});
startLatch.countDown();
try {
startLatch.await(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
Member localMember = hz.getCluster().getLocalMember();
for (int j = 0; j < count; j++) {
topic.publish(new TestMessage(localMember, UuidUtil.newUnsecureUuidString()));
publishLatch.countDown();
}
}
});
}
try {
assertTrue(publishLatch.await(2, TimeUnit.MINUTES));
assertTrue(messageLatch.await(5, TimeUnit.MINUTES));
TestMessage[] ref = new TestMessage[messageLists[0].size()];
messageLists[0].toArray(ref);
Comparator<TestMessage> comparator = new Comparator<TestMessage>() {
public int compare(TestMessage m1, TestMessage m2) {
// sort only publisher blocks. if publishers are the same, leave them as they are
return m1.publisher.getUuid().compareTo(m2.publisher.getUuid());
}
};
Arrays.sort(ref, comparator);
for (int i = 1; i < nodeCount; i++) {
TestMessage[] messages = new TestMessage[messageLists[i].size()];
messageLists[i].toArray(messages);
Arrays.sort(messages, comparator);
assertArrayEquals(ref, messages);
}
} finally {
ex.shutdownNow();
}
}
@Test
@SuppressWarnings("unchecked")
public void testTopicGlobalOrder() throws Exception {
final int nodeCount = 5;
final int count = 1000;
final String randomTopicName = randomString();
Config config = new Config();
config.getTopicConfig(randomTopicName).setGlobalOrderingEnabled(true);
TestHazelcastInstanceFactory factory = createHazelcastInstanceFactory(nodeCount);
final HazelcastInstance[] nodes = factory.newInstances(config);
final List<TestMessage>[] messageListPerNode = new List[nodeCount];
for (int i = 0; i < nodeCount; i++) {
messageListPerNode[i] = new CopyOnWriteArrayList<TestMessage>();
}
final CountDownLatch messageLatch = new CountDownLatch(nodeCount * count);
// add message listeners
for (int i = 0; i < nodes.length; i++) {
final int nodeIndex = i;
ITopic<TestMessage> topic = nodes[i].getTopic(randomTopicName);
topic.addMessageListener(new MessageListener<TestMessage>() {
public void onMessage(Message<TestMessage> message) {
messageListPerNode[nodeIndex].add(message.getMessageObject());
messageLatch.countDown();
}
});
}
// publish messages
for (HazelcastInstance node : nodes) {
Member localMember = node.getCluster().getLocalMember();
for (int j = 0; j < count; j++) {
TestMessage message = new TestMessage(localMember, UUID.randomUUID().toString());
ITopic<Object> topic = node.getTopic(randomTopicName);
topic.publish(message);
}
}
// all messages in nodes messageLists should be equal
assertTrueEventually(new AssertTask() {
@Override
public void run() throws Exception {
int i = 0;
do {
assertEquals(messageListPerNode[i], messageListPerNode[i++]);
} while (i < nodeCount);
}
});
}
private static class TestMessage implements DataSerializable {
Member publisher;
String data;
@SuppressWarnings("unused")
TestMessage() {
}
TestMessage(Member publisher, String data) {
this.publisher = publisher;
this.data = data;
}
public void writeData(ObjectDataOutput out) throws IOException {
publisher.writeData(out);
out.writeUTF(data);
}
public void readData(ObjectDataInput in) throws IOException {
publisher = new MemberImpl();
publisher.readData(in);
data = in.readUTF();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TestMessage that = (TestMessage) o;
if (data != null ? !data.equals(that.data) : that.data != null) {
return false;
}
if (publisher != null ? !publisher.equals(that.publisher) : that.publisher != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = publisher != null ? publisher.hashCode() : 0;
result = 31 * result + (data != null ? data.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "TestMessage{" + "publisher=" + publisher + ", data='" + data + "'}";
}
}
@Test
public void testName() {
String randomTopicName = randomString();
HazelcastInstance hClient = createHazelcastInstance();
ITopic<?> topic = hClient.getTopic(randomTopicName);
assertEquals(randomTopicName, topic.getName());
}
@Test
public void addMessageListener() throws InterruptedException {
String randomTopicName = "addMessageListener" + generateRandomString(5);
HazelcastInstance instance = createHazelcastInstance();
ITopic<String> topic = instance.getTopic(randomTopicName);
final CountDownLatch latch = new CountDownLatch(1);
final String message = "Hazelcast Rocks!";
topic.addMessageListener(new MessageListener<String>() {
public void onMessage(Message<String> msg) {
if (msg.getMessageObject().equals(message)) {
latch.countDown();
}
}
});
topic.publish(message);
assertTrue(latch.await(10000, MILLISECONDS));
}
@Test
public void testConfigListenerRegistration() throws InterruptedException {
String topicName = "default";
Config config = new Config();
final CountDownLatch latch = new CountDownLatch(1);
config.getTopicConfig(topicName).addMessageListenerConfig(new ListenerConfig().setImplementation(new MessageListener() {
public void onMessage(Message message) {
latch.countDown();
}
}));
HazelcastInstance instance = createHazelcastInstance(config);
instance.getTopic(topicName).publish(1);
assertTrue(latch.await(10, TimeUnit.SECONDS));
}
@Test
public void addTwoMessageListener() throws InterruptedException {
String topicName = "addTwoMessageListener" + generateRandomString(5);
HazelcastInstance instance = createHazelcastInstance();
ITopic<String> topic = instance.getTopic(topicName);
final CountDownLatch latch = new CountDownLatch(2);
final String message = "Hazelcast Rocks!";
topic.addMessageListener(new MessageListener<String>() {
public void onMessage(Message<String> msg) {
if (msg.getMessageObject().equals(message)) {
latch.countDown();
}
}
});
topic.addMessageListener(new MessageListener<String>() {
public void onMessage(Message<String> msg) {
if (msg.getMessageObject().equals(message)) {
latch.countDown();
}
}
});
topic.publish(message);
assertTrue(latch.await(10000, MILLISECONDS));
}
@Test
@Repeat(10)
public void removeMessageListener() throws InterruptedException {
String topicName = "removeMessageListener" + generateRandomString(5);
try {
HazelcastInstance instance = createHazelcastInstance();
ITopic<String> topic = instance.getTopic(topicName);
final AtomicInteger onMessageCount = new AtomicInteger(0);
final CountDownLatch onMessageInvoked = new CountDownLatch(1);
MessageListener<String> messageListener = new MessageListener<String>() {
public void onMessage(Message<String> msg) {
onMessageCount.incrementAndGet();
onMessageInvoked.countDown();
}
};
final String message = "message_" + messageListener.hashCode() + "_";
final String id = topic.addMessageListener(messageListener);
topic.publish(message + "1");
onMessageInvoked.await();
assertTrue(topic.removeMessageListener(id));
topic.publish(message + "2");
assertTrueEventually(new AssertTask() {
@Override
public void run() {
assertEquals(1, onMessageCount.get());
}
});
} finally {
shutdownNodeFactory();
}
}
@Test
public void testPerformance() throws InterruptedException {
int count = 10000;
String randomTopicName = randomString();
HazelcastInstance instance = createHazelcastInstance();
ExecutorService ex = Executors.newFixedThreadPool(10);
final ITopic<String> topic = instance.getTopic(randomTopicName);
final CountDownLatch latch = new CountDownLatch(count);
for (int i = 0; i < count; i++) {
ex.submit(new Runnable() {
public void run() {
topic.publish("my object");
latch.countDown();
}
});
}
assertTrue(latch.await(20, TimeUnit.SECONDS));
}
@Test
public void addTwoListenerAndRemoveOne() throws InterruptedException {
String topicName = "addTwoListenerAndRemoveOne" + generateRandomString(5);
HazelcastInstance instance = createHazelcastInstance();
ITopic<String> topic = instance.getTopic(topicName);
final CountDownLatch latch = new CountDownLatch(3);
final CountDownLatch cp = new CountDownLatch(2);
final AtomicInteger atomicInteger = new AtomicInteger();
final String message = "Hazelcast Rocks!";
MessageListener<String> messageListener1 = new MessageListener<String>() {
public void onMessage(Message<String> msg) {
atomicInteger.incrementAndGet();
latch.countDown();
cp.countDown();
}
};
MessageListener<String> messageListener2 = new MessageListener<String>() {
public void onMessage(Message<String> msg) {
atomicInteger.incrementAndGet();
latch.countDown();
cp.countDown();
}
};
String messageListenerId = topic.addMessageListener(messageListener1);
topic.addMessageListener(messageListener2);
topic.publish(message);
assertOpenEventually(cp);
topic.removeMessageListener(messageListenerId);
topic.publish(message);
assertOpenEventually(latch);
assertEquals(3, atomicInteger.get());
}
/**
* Testing if topic can properly listen messages and if topic has any issue after a shutdown.
*/
@Test
public void testTopicCluster() throws InterruptedException {
String topicName = "TestMessages" + generateRandomString(5);
Config cfg = new Config();
TestHazelcastInstanceFactory factory = createHazelcastInstanceFactory(2);
HazelcastInstance[] instances = factory.newInstances(cfg);
HazelcastInstance instance1 = instances[0];
HazelcastInstance instance2 = instances[1];
ITopic<String> topic1 = instance1.getTopic(topicName);
final CountDownLatch latch1 = new CountDownLatch(1);
final String message = "Test" + randomString();
topic1.addMessageListener(new MessageListener<String>() {
public void onMessage(Message msg) {
assertEquals(message, msg.getMessageObject());
latch1.countDown();
}
});
ITopic<String> topic2 = instance2.getTopic(topicName);
final CountDownLatch latch2 = new CountDownLatch(2);
topic2.addMessageListener(new MessageListener<String>() {
public void onMessage(Message msg) {
assertEquals(message, msg.getMessageObject());
latch2.countDown();
}
});
topic1.publish(message);
assertOpenEventually(latch1);
instance1.shutdown();
topic2.publish(message);
assertOpenEventually(latch2);
}
@Test
public void testTopicStats() throws InterruptedException {
String topicName = "testTopicStats" + generateRandomString(5);
HazelcastInstance instance = createHazelcastInstance();
ITopic<String> topic = instance.getTopic(topicName);
final CountDownLatch latch1 = new CountDownLatch(1000);
topic.addMessageListener(new MessageListener<String>() {
public void onMessage(Message msg) {
latch1.countDown();
}
});
final CountDownLatch latch2 = new CountDownLatch(1000);
topic.addMessageListener(new MessageListener<String>() {
public void onMessage(Message msg) {
latch2.countDown();
}
});
for (int i = 0; i < 1000; i++) {
topic.publish("sancar");
}
assertTrue(latch1.await(1, TimeUnit.MINUTES));
assertTrue(latch2.await(1, TimeUnit.MINUTES));
LocalTopicStatsImpl stats = (LocalTopicStatsImpl) topic.getLocalTopicStats();
assertEquals(1000, stats.getPublishOperationCount());
assertEquals(2000, stats.getReceiveOperationCount());
}
@Test
@Category(NightlyTest.class)
@SuppressWarnings("unchecked")
public void testTopicMultiThreading() throws Exception {
final int nodeCount = 5;
final int count = 1000;
final String randomTopicName = randomString();
Config config = new Config();
config.getTopicConfig(randomTopicName).setGlobalOrderingEnabled(false);
config.getTopicConfig(randomTopicName).setMultiThreadingEnabled(true);
TestHazelcastInstanceFactory factory = createHazelcastInstanceFactory(nodeCount);
final HazelcastInstance[] instances = factory.newInstances(config);
final Set<String>[] threads = new Set[nodeCount];
for (int i = 0; i < nodeCount; i++) {
threads[i] = new HashSet<String>();
}
final CountDownLatch startLatch = new CountDownLatch(nodeCount);
final CountDownLatch messageLatch = new CountDownLatch(nodeCount * nodeCount * count);
final CountDownLatch publishLatch = new CountDownLatch(nodeCount * count);
ExecutorService ex = Executors.newFixedThreadPool(nodeCount);
for (int i = 0; i < nodeCount; i++) {
final int finalI = i;
ex.execute(new Runnable() {
public void run() {
final Set<String> thNames = threads[finalI];
HazelcastInstance hz = instances[finalI];
ITopic<TestMessage> topic = hz.getTopic(randomTopicName);
topic.addMessageListener(new MessageListener<TestMessage>() {
public void onMessage(Message<TestMessage> message) {
thNames.add(Thread.currentThread().getName());
messageLatch.countDown();
}
});
startLatch.countDown();
try {
startLatch.await(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
Member localMember = hz.getCluster().getLocalMember();
for (int j = 0; j < count; j++) {
topic.publish(new TestMessage(localMember, UuidUtil.newUnsecureUuidString()));
publishLatch.countDown();
}
}
});
}
try {
assertTrue(publishLatch.await(2, TimeUnit.MINUTES));
assertTrue(messageLatch.await(5, TimeUnit.MINUTES));
boolean passed = false;
for (int i = 0; i < nodeCount; i++) {
if (threads[i].size() > 1) {
passed = true;
}
}
assertTrue("All listeners received messages in single thread. Expecting more threads involved", passed);
} finally {
ex.shutdownNow();
}
}
@Test
public void givenTopicHasNoSubscriber_whenMessageIsPublished_thenNoSerialializationIsInvoked() {
final int nodeCount = 2;
TestHazelcastInstanceFactory factory = createHazelcastInstanceFactory(nodeCount);
final HazelcastInstance[] instances = factory.newInstances();
ITopic<SerializationCounting> topic = instances[0].getTopic(randomString());
SerializationCounting message = new SerializationCounting();
topic.publish(message);
assertNoSerializationInvoked(message);
}
private void assertNoSerializationInvoked(SerializationCounting localMessage) {
assertEquals(0, localMessage.getSerializationCount());
}
public static class SerializationCounting implements DataSerializable {
private AtomicInteger counter = new AtomicInteger();
@Override
public void writeData(ObjectDataOutput out) throws IOException {
counter.incrementAndGet();
}
@Override
public void readData(ObjectDataInput in) throws IOException {
}
public int getSerializationCount() {
return counter.get();
}
}
}