Added outbound event queue to be handled by Notifier stage

This commit is contained in:
andrea
2012-08-29 21:20:36 +02:00
parent bb3e8170d5
commit 323811adb2
11 changed files with 249 additions and 47 deletions
@@ -1,5 +1,6 @@
package org.dna.mqtt.moquette.messaging.spi;
import org.apache.mina.core.session.IoSession;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage.QOSType;
/**
@@ -35,4 +36,6 @@ public interface IMessaging {
* doesn't reply any error
*/
void unsubscribe(String topic, String clientID);
void disconnect(IoSession session);
}
@@ -1,5 +1,6 @@
package org.dna.mqtt.moquette.messaging.spi;
import org.apache.mina.core.session.IoSession;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage.QOSType;
/**
@@ -9,5 +10,7 @@ import org.dna.mqtt.moquette.proto.messages.AbstractMessage.QOSType;
public interface INotifier {
public void notify(String clientId, String topic, QOSType qOSType, byte[] payload, boolean retained);
public void disconnect(IoSession session);
}
@@ -1,7 +1,5 @@
package org.dna.mqtt.moquette.messaging.spi.impl;
import org.dna.mqtt.moquette.messaging.spi.impl.events.RemoveAllSubscriptionsEvent;
import org.dna.mqtt.moquette.messaging.spi.impl.events.MessagingEvent;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
@@ -10,12 +8,16 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.apache.mina.core.session.IoSession;
import org.dna.mqtt.moquette.MQTTException;
import org.dna.mqtt.moquette.messaging.spi.IMessaging;
import org.dna.mqtt.moquette.messaging.spi.INotifier;
import org.dna.mqtt.moquette.messaging.spi.impl.SubscriptionsStore.Token;
import org.dna.mqtt.moquette.messaging.spi.impl.events.CloseEvent;
import org.dna.mqtt.moquette.messaging.spi.impl.events.DisconnectEvent;
import org.dna.mqtt.moquette.messaging.spi.impl.events.MessagingEvent;
import org.dna.mqtt.moquette.messaging.spi.impl.events.NotifyEvent;
import org.dna.mqtt.moquette.messaging.spi.impl.events.PublishEvent;
import org.dna.mqtt.moquette.messaging.spi.impl.events.RemoveAllSubscriptionsEvent;
import org.dna.mqtt.moquette.messaging.spi.impl.events.SubscribeEvent;
import org.dna.mqtt.moquette.messaging.spi.impl.events.UnsubscribeEvent;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage.QOSType;
@@ -58,13 +60,13 @@ public class SimpleMessaging implements IMessaging, Runnable {
private SubscriptionsStore subscriptions = new SubscriptionsStore();
private BlockingQueue<MessagingEvent> m_inboundQueue = new LinkedBlockingQueue<MessagingEvent>();
private BlockingQueue<MessagingEvent> m_outboundQueue = new LinkedBlockingQueue<MessagingEvent>();
private INotifier m_notifier;
// private INotifier m_notifier;
private MultiIndexFactory m_multiIndexFactory;
private PageFileFactory pageFactory;
private SortedIndex<String, StoredMessage> m_retainedStore;
public SimpleMessaging() {
String storeFile = Server.STORAGE_FILE_PATH;
@@ -98,9 +100,6 @@ public class SimpleMessaging implements IMessaging, Runnable {
eventLoop();
}
public void setNotifier(INotifier notifier) {
m_notifier = notifier;
}
public void publish(String topic, byte[] message, QOSType qos, boolean retain) {
//TODO for this moment no qos > 0 trivial implementation
@@ -116,6 +115,7 @@ public class SimpleMessaging implements IMessaging, Runnable {
public void subscribe(String clientId, String topic, QOSType qos) {
Subscription newSubscription = new Subscription(clientId, topic, qos);
try {
LOG.debug("subscribe invoked for topic: " + topic);
m_inboundQueue.put(new SubscribeEvent(newSubscription));
} catch (InterruptedException ex) {
LOG.error(null, ex);
@@ -132,6 +132,15 @@ public class SimpleMessaging implements IMessaging, Runnable {
}
public void disconnect(IoSession session) {
try {
m_inboundQueue.put(new DisconnectEvent(session));
} catch (InterruptedException ex) {
LOG.error(null, ex);
}
}
/**
* Verify if the 2 topics matching respecting the rules of MQTT Appendix A
*/
@@ -183,7 +192,11 @@ public class SimpleMessaging implements IMessaging, Runnable {
m_inboundQueue.put(new CloseEvent());
} catch (InterruptedException ex) {
LOG.error(null, ex);
}
}
}
public BlockingQueue<MessagingEvent> getNotifyEventQueue() {
return m_outboundQueue;
}
@@ -203,6 +216,8 @@ public class SimpleMessaging implements IMessaging, Runnable {
processRemoveAllSubscriptions((RemoveAllSubscriptionsEvent) evt);
} else if (evt instanceof CloseEvent) {
processClose();
} else if (evt instanceof DisconnectEvent) {
m_outboundQueue.put(evt);
}
} catch (InterruptedException ex) {
processClose();
@@ -213,13 +228,17 @@ public class SimpleMessaging implements IMessaging, Runnable {
protected void processPublish(PublishEvent evt) {
LOG.debug("processPublish invoked");
String topic = evt.getTopic();
QOSType qos = evt.getQos();
byte[] message = evt.getMessage();
final String topic = evt.getTopic();
final QOSType qos = evt.getQos();
final byte[] message = evt.getMessage();
boolean retain = evt.isRetain();
for (Subscription sub : subscriptions.matches(topic)) {
m_notifier.notify(sub.clientId, topic, qos, message, false);
for (final Subscription sub : subscriptions.matches(topic)) {
try {
m_outboundQueue.put(new NotifyEvent(sub.clientId, topic, qos, message, false));
} catch (InterruptedException ex) {
LOG.error(null, ex);
}
}
if (retain) {
if (message.length == 0) {
@@ -233,7 +252,7 @@ public class SimpleMessaging implements IMessaging, Runnable {
}
protected void processSubscribe(SubscribeEvent evt) {
protected void processSubscribe(SubscribeEvent evt)/* throws InterruptedException*/ {
LOG.debug("processSubscribe invoked");
Subscription newSubscription = evt.getSubscription();
String topic = newSubscription.getTopic();
@@ -241,13 +260,18 @@ public class SimpleMessaging implements IMessaging, Runnable {
subscriptions.add(newSubscription);
//scans retained messages to be published to the new subscription
LOG.debug("Scanning all retained messages...");
LOG.debug("Scanning all retained messages, presents are " + m_retainedStore.size());
for (Map.Entry<String, StoredMessage> entry : m_retainedStore) {
StoredMessage storedMsg = entry.getValue();
if (matchTopics(entry.getKey(), topic)) {
//fire the as retained the message
m_notifier.notify(newSubscription.clientId, topic, storedMsg.getQos(),
storedMsg.getPayload(), true);
try {
//fire the as retained the message
LOG.debug("Inserting NotifyEvent into outbound for topic " + topic + " entry " + entry.getKey());
m_outboundQueue.put(new NotifyEvent(newSubscription.clientId,
topic, storedMsg.getQos(), storedMsg.getPayload(), true));
} catch (InterruptedException ex) {
LOG.error(null, ex);
}
}
}
}
@@ -0,0 +1,22 @@
package org.dna.mqtt.moquette.messaging.spi.impl.events;
import org.apache.mina.core.session.IoSession;
/**
*
* @author andrea
*/
public class DisconnectEvent extends MessagingEvent {
IoSession m_session;
public DisconnectEvent(IoSession session) {
m_session = session;
}
public IoSession getSession() {
return m_session;
}
}
@@ -0,0 +1,46 @@
package org.dna.mqtt.moquette.messaging.spi.impl.events;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage.QOSType;
/**
*
* @author andrea
*/
public class NotifyEvent extends MessagingEvent {
private String m_clientId;
private String m_topic;
private QOSType m_qos;
private byte[] m_message;
private boolean m_retaned;
public NotifyEvent(String clientId, String topic, QOSType qos, byte[] message, boolean retained) {
m_clientId = clientId;
m_topic = topic;
m_qos = qos;
m_message = message;
m_retaned = retained;
}
public String getClientId() {
return m_clientId;
}
public String getTopic() {
return m_topic;
}
public QOSType getQos() {
return m_qos;
}
public byte[] getMessage() {
return m_message;
}
public boolean isRetained() {
return m_retaned;
}
}
@@ -63,7 +63,7 @@ public class MQTTHandler extends IoHandlerAdapter implements INotifier {
break;
case DISCONNECT:
handleDisconnect(session, (DisconnectMessage) msg);
break;
}
} catch (Exception ex) {
LOG.error("Bad error in processing the message", ex);
@@ -152,7 +152,7 @@ public class MQTTHandler extends IoHandlerAdapter implements INotifier {
}
protected void handleSubscribe(IoSession session, SubscribeMessage msg) {
LOG.info("registering the subscriptions");
LOG.debug("handleSubscribe, registering the subscriptions");
for (SubscribeMessage.Couple req : msg.subscriptions()) {
m_messaging.subscribe((String) session.getAttribute(ATTR_CLIENTID),
req.getTopic(), AbstractMessage.QOSType.values()[req.getQos()]);
@@ -191,12 +191,12 @@ public class MQTTHandler extends IoHandlerAdapter implements INotifier {
protected void handleDisconnect(IoSession session, DisconnectMessage disconnectMessage) {
String clientID = (String) session.getAttribute(ATTR_CLIENTID);
//remove from clientIDs
m_clientIDsLock.lock();
try {
m_clientIDs.remove(clientID);
} finally {
m_clientIDsLock.unlock();
}
// m_clientIDsLock.lock();
// try {
// m_clientIDs.remove(clientID);
// } finally {
// m_clientIDsLock.unlock();
// }
boolean cleanSession = (Boolean) session.getAttribute("cleanSession");
if (cleanSession) {
//cleanup topic subscriptions
@@ -204,7 +204,8 @@ public class MQTTHandler extends IoHandlerAdapter implements INotifier {
}
//close the TCP connection
session.close(true);
//session.close(true);
m_messaging.disconnect(session);
}
@Override
@@ -226,9 +227,30 @@ public class MQTTHandler extends IoHandlerAdapter implements INotifier {
pubMessage.setTopicName(topic);
pubMessage.setQos(qOSType);
pubMessage.setPayload(payload);
assert m_clientIDs != null;
assert m_clientIDs.get(clientId) != null;
LOG.debug("Session for clientId " + clientId + " is " + m_clientIDs.get(clientId).getSession());
m_clientIDs.get(clientId).getSession().write(pubMessage);
LOG.debug("notify invoked");
m_clientIDsLock.lock();
try {
assert m_clientIDs != null;
LOG.debug("clientIDs are " + m_clientIDs);
assert m_clientIDs.get(clientId) != null;
LOG.debug("Session for clientId " + clientId + " is " + m_clientIDs.get(clientId).getSession());
m_clientIDs.get(clientId).getSession().write(pubMessage);
}catch(Throwable t) {
LOG.error(null, t);
} finally {
m_clientIDsLock.unlock();
}
}
public void disconnect(IoSession session) {
String clientID = (String) session.getAttribute(ATTR_CLIENTID);
m_clientIDsLock.lock();
try {
m_clientIDs.remove(clientID);
} finally {
m_clientIDsLock.unlock();
}
session.close(true);
}
}
@@ -0,0 +1,48 @@
package org.dna.mqtt.moquette.server;
import java.util.concurrent.BlockingQueue;
import org.dna.mqtt.moquette.messaging.spi.INotifier;
import org.dna.mqtt.moquette.messaging.spi.impl.events.DisconnectEvent;
import org.dna.mqtt.moquette.messaging.spi.impl.events.MessagingEvent;
import org.dna.mqtt.moquette.messaging.spi.impl.events.NotifyEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author andrea
*/
class Notifier implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(Notifier.class);
BlockingQueue<MessagingEvent> m_queue;
INotifier m_notifier;
Notifier(BlockingQueue<MessagingEvent> queue, INotifier notifier) {
m_queue = queue;
m_notifier = notifier;
}
public void run() {
LOG.debug("Notifier started");
boolean interrupted = false;
while (!interrupted) {
try {
MessagingEvent evt = m_queue.take();
LOG.debug("Notifing event " + evt);
if (evt instanceof NotifyEvent) {
NotifyEvent evtN = (NotifyEvent) evt;
m_notifier.notify(evtN.getClientId(), evtN.getTopic(), evtN.getQos(), evtN.getMessage(), evtN.isRetained());
} else if (evt instanceof DisconnectEvent) {
DisconnectEvent evtD = (DisconnectEvent) evt;
m_notifier.disconnect(evtD.getSession());
}
} catch (InterruptedException ex) {
interrupted = true;
}
}
LOG.debug("Notifier stopped");
}
}
@@ -3,7 +3,9 @@ package org.dna.mqtt.moquette.server;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.logging.Level;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
@@ -12,6 +14,7 @@ import org.apache.mina.filter.codec.demux.DemuxingProtocolDecoder;
import org.apache.mina.filter.codec.demux.DemuxingProtocolEncoder;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.dna.mqtt.moquette.messaging.spi.impl.SimpleMessaging;
import org.dna.mqtt.moquette.messaging.spi.impl.events.MessagingEvent;
import org.dna.mqtt.moquette.proto.ConnAckEncoder;
import org.dna.mqtt.moquette.proto.ConnectDecoder;
import org.dna.mqtt.moquette.proto.DisconnectDecoder;
@@ -43,9 +46,13 @@ public class Server {
public static final int DEFAULT_CONNECT_TIMEOUT = 10;
public static final String STORAGE_FILE_PATH = System.getProperty("user.home") +
File.separator + "moquette_store.hawtdb";
//TODO one notifier per outbound queue, else no serialization of message flow is granted!!
private static final int NOTIFIER_POOL_SIZE = 1;
private IoAcceptor m_acceptor;
SimpleMessaging messaging;
Thread messagingEventLoop;
private ExecutorService m_notifierPool/* = Executors.newFixedThreadPool(NOTIFIER_POOL_SIZE)*/;
public static void main(String[] args) throws IOException {
new Server().startServer();
@@ -78,11 +85,18 @@ public class Server {
messaging = new SimpleMessaging();
//TODO fix this hugly wiring
handler.setMessaging(messaging);
messaging.setNotifier(handler);
// messaging.setNotifier(handler);
messagingEventLoop = new Thread(messaging);
messagingEventLoop.setName("Event Loop" + System.currentTimeMillis());
messagingEventLoop.start();
m_notifierPool = Executors.newFixedThreadPool(NOTIFIER_POOL_SIZE);
BlockingQueue<MessagingEvent> notifyQueue = messaging.getNotifyEventQueue();
for (int i = 0; i < NOTIFIER_POOL_SIZE; i++) {
m_notifierPool.submit(new Notifier(notifyQueue, handler));
}
m_acceptor.setHandler(handler);
((NioSocketAcceptor)m_acceptor).setReuseAddress(true);
((NioSocketAcceptor)m_acceptor).getSessionConfig().setReuseAddress(true);
@@ -105,12 +119,15 @@ public class Server {
messaging.close();
messagingEventLoop.interrupt();
LOG.info("shutting down evet loop");
try {
messagingEventLoop.join();
} catch (InterruptedException ex) {
LOG.error(null, ex);
}
m_notifierPool.shutdown();
for(IoSession session: m_acceptor.getManagedSessions().values()) {
if(session.isConnected() && !session.isClosing()){
session.close(false);
+7 -1
View File
@@ -1,5 +1,11 @@
# Set root logger level to DEBUG and its only appender to A1.
log4j.rootLogger=DEBUG, A1
log4j.rootLogger=ERROR, A1
#log4j.rootLogger=DEBUG, A1
#log4j.rootLogger=WARN, A1
#log4j.logger.org.dna.mqtt.moquette.client=WARN, A1
#log4j.logger.org.mina=WARN, A1
#log4j.logger.org.dna.mqtt.moquette.server=DEBUG, A1
#log4j.logger.org.dna.mqtt.moquette.messaging=DEBUG, A1
# A1 is set to be a ConsoleAppender.
log4j.appender.A1=org.apache.log4j.ConsoleAppender
@@ -1,14 +1,14 @@
package org.dna.mqtt.moquette.messaging.spi.impl;
import org.dna.mqtt.moquette.messaging.spi.INotifier;
import java.util.concurrent.BlockingQueue;
import org.dna.mqtt.moquette.messaging.spi.impl.events.MessagingEvent;
import org.dna.mqtt.moquette.messaging.spi.impl.events.NotifyEvent;
import org.dna.mqtt.moquette.messaging.spi.impl.events.PublishEvent;
import org.dna.mqtt.moquette.messaging.spi.impl.events.SubscribeEvent;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage.QOSType;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import static org.mockito.Mockito.*;
/**
*
@@ -50,20 +50,28 @@ public class SimpleMessagingTest {
}
@Test
public void testPublish() {
public void testPublish() throws InterruptedException {
SubscribeEvent evt = new SubscribeEvent(new Subscription(FAKE_CLIENT_ID, FAKE_TOPIC, QOSType.MOST_ONE));
messaging.processSubscribe(evt);
INotifier notifier = mock(INotifier.class);
messaging.setNotifier(notifier);
// INotifier notifier = mock(INotifier.class);
// messaging.setNotifier(notifier);
//Exercise
PublishEvent pubEvt = new PublishEvent(FAKE_TOPIC, QOSType.MOST_ONE, "Hello".getBytes(), false);
messaging.processPublish(pubEvt);
//Verify
ArgumentCaptor<byte[]> argument = ArgumentCaptor.forClass(byte[].class);
verify(notifier).notify(eq(FAKE_CLIENT_ID), eq(FAKE_TOPIC), any(QOSType.class), argument.capture(), eq(false));
assertEquals("Hello", new String(argument.getValue()));
BlockingQueue<MessagingEvent> queue = messaging.getNotifyEventQueue();
MessagingEvent msgEvt = queue.take();
assertTrue(msgEvt instanceof NotifyEvent);
NotifyEvent notifyEvt = (NotifyEvent) msgEvt;
assertEquals(FAKE_CLIENT_ID, notifyEvt.getClientId());
assertEquals(FAKE_TOPIC, notifyEvt.getTopic());
assertFalse(notifyEvt.isRetained());
// ArgumentCaptor<byte[]> argument = ArgumentCaptor.forClass(byte[].class);
// verify(notifier).notify(eq(FAKE_CLIENT_ID), eq(FAKE_TOPIC), any(QOSType.class), argument.capture(), eq(false));
assertEquals("Hello", new String(notifyEvt.getMessage()));
}
@Test
@@ -9,6 +9,7 @@ import org.dna.mqtt.moquette.client.IPublishCallback;
import org.junit.After;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
/**
@@ -203,7 +204,8 @@ public class ServerIntegrationTest {
@Test
public void testRetain_maintainMessage_againstClientDestruction() {
public void testRetain_maintainMessage_againstClientDestruction() throws InterruptedException {
final CountDownLatch barrier = new CountDownLatch(1);
Client client = new Client("localhost", Server.PORT, "CLID_123");
client.connect();
@@ -218,11 +220,13 @@ public class ServerIntegrationTest {
public void published(String topic, byte[] message) {
received = true;
barrier.countDown();
}
});
client.close();
//Verify
barrier.await(1, TimeUnit.SECONDS);
assertTrue(received);
//TearDown
@@ -230,7 +234,7 @@ public class ServerIntegrationTest {
}
@Test
@Ignore
public void testUnsubscribe_do_not_notify_anymore_same_session() throws InterruptedException {
Client client = new Client("localhost", Server.PORT, "CLID_123");
// Client client = new Client("test.mosquitto.org", 1883);
@@ -308,7 +312,6 @@ public class ServerIntegrationTest {
client.close();
client.shutdown();
}
}