Big refactory on session management:

- introduced a unique point of access and management of sessions state (SessionRepository)
- separated behaiour for sessions with clean flag to false into DurableSession
- separated behaviour for session with clean flag to true into TransientSession
This commit is contained in:
andsel
2017-10-14 17:37:26 +02:00
parent 6bacce6cec
commit 1b272e5849
43 changed files with 1107 additions and 1710 deletions
@@ -16,14 +16,10 @@
package io.moquette.spi;
import io.moquette.spi.ISubscriptionsStore.ClientTopicCouple;
import io.moquette.spi.impl.subscriptions.Subscription;
import io.moquette.spi.impl.subscriptions.Topic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.Queue;
import java.util.Set;
/**
@@ -40,164 +36,51 @@ import java.util.Set;
* <li>Optionally, QoS 0 messages pending transmission to the Client.</li>
* </ul>
*/
public class ClientSession {
class OutboundFlightZone {
/**
* Save the binding messageID, clientID - message
*
* @param messageID the packet ID used in transmission
* @param msg the message to put in flight zone
*/
void waitingAck(int messageID, IMessagesStore.StoredMessage msg) {
if (LOG.isTraceEnabled()) {
LOG.trace("Adding to inflight {}, guid <{}>", messageID, msg.getGuid());
}
m_sessionsStore.inFlight(ClientSession.this.clientID, messageID, msg);
}
IMessagesStore.StoredMessage acknowledged(int messageID) {
if (LOG.isTraceEnabled())
LOG.trace("Acknowledging inflight, clientID <{}> messageID {}", ClientSession.this.clientID, messageID);
return m_sessionsStore.inFlightAck(ClientSession.this.clientID, messageID);
}
}
class InboundFlightZone {
public IMessagesStore.StoredMessage lookup(int messageID) {
return m_sessionsStore.inboundInflight(clientID, messageID);
}
public void waitingRel(int messageID, IMessagesStore.StoredMessage msg) {
m_sessionsStore.markAsInboundInflight(clientID, messageID, msg);
}
}
private static final Logger LOG = LoggerFactory.getLogger(ClientSession.class);
public abstract class ClientSession {
public final String clientID;
private final ISessionsStore m_sessionsStore;
protected Set<Subscription> subscriptions = new HashSet<>();
private Set<Subscription> subscriptions = new HashSet<>();
private final ISubscriptionsStore subscriptionsStore;
private volatile boolean cleanSession;
private final OutboundFlightZone outboundFlightZone;
private final InboundFlightZone inboundFlightZone;
public ClientSession(String clientID, ISessionsStore sessionsStore, ISubscriptionsStore subscriptionsStore,
boolean cleanSession) {
public ClientSession(String clientID) {
this.clientID = clientID;
this.m_sessionsStore = sessionsStore;
this.subscriptionsStore = subscriptionsStore;
this.cleanSession = cleanSession;
this.outboundFlightZone = new OutboundFlightZone();
this.inboundFlightZone = new InboundFlightZone();
}
/**
* Return the list of persisted publishes for the given clientID. For QoS1 and QoS2 with clean
* session flag, this method return the list of missed publish events while the client was
* disconnected.
*
* @return the list of messages to be delivered for client related to the session.
*/
public Queue<IMessagesStore.StoredMessage> queue() {
LOG.info("Retrieving enqueued messages. CId={}", clientID);
return this.m_sessionsStore.queue(clientID);
}
@Override
public String toString() {
return "ClientSession{clientID='" + clientID + "'}";
}
public boolean subscribe(Subscription newSubscription) {
LOG.info("Adding new subscription. CId={}, topics={}, qos={}", newSubscription.getClientId(),
newSubscription.getTopicFilter(), newSubscription.getRequestedQos());
boolean validTopic = newSubscription.getTopicFilter().isValid();
if (!validTopic) {
LOG.warn("The topic filter is not valid. CId={}, topics={}", newSubscription.getClientId(),
newSubscription.getTopicFilter());
// send SUBACK with 0x80 for this topic filter
return false;
}
ClientTopicCouple matchingCouple = new ClientTopicCouple(this.clientID, newSubscription.getTopicFilter());
Subscription existingSub = subscriptionsStore.getSubscription(matchingCouple);
// update the selected subscriptions if not present or if has a greater qos
if (existingSub == null || existingSub.getRequestedQos().value() < newSubscription.getRequestedQos().value()) {
if (existingSub != null) {
LOG.info("Subscription already existed with a lower QoS value. It will be updated. CId={}, " +
"topics={}, existingQos={}, newQos={}", newSubscription.getClientId(),
newSubscription.getTopicFilter(), existingSub.getRequestedQos(), newSubscription.getRequestedQos());
subscriptions.remove(newSubscription);
}
subscriptions.add(newSubscription);
subscriptionsStore.addNewSubscription(newSubscription);
}
return true;
}
public void unsubscribeFrom(Topic topicFilter) {
LOG.info("Removing subscription. CId={}, topics={}", clientID, topicFilter);
subscriptionsStore.removeSubscription(topicFilter, clientID);
Set<Subscription> subscriptionsToRemove = new HashSet<>();
public Subscription findSubscriptionByTopicFilter(Subscription matchingSub) {
for (Subscription sub : this.subscriptions) {
if (sub.getTopicFilter().equals(topicFilter)) {
subscriptionsToRemove.add(sub);
if (matchingSub.equals(sub)) {
return sub;
}
}
subscriptions.removeAll(subscriptionsToRemove);
return null;
}
public void disconnect() {
if (this.cleanSession) {
LOG.info("Client disconnected. Removing its subscriptions. CId={}", clientID);
// cleanup topic subscriptions
cleanSession();
}
public Set<Subscription> getSubscriptions() {
return subscriptions;
}
public void cleanSession() {
m_sessionsStore.cleanSession(this.clientID);
}
public abstract boolean isCleanSession();
public boolean isCleanSession() {
return this.cleanSession;
}
public abstract boolean subscribe(Subscription newSubscription);
public void cleanSession(boolean cleanSession) {
this.cleanSession = cleanSession;
this.m_sessionsStore.updateCleanStatus(this.clientID, cleanSession);
}
public abstract void unsubscribeFrom(Topic topicFilter);
public int nextPacketId() {
return this.m_sessionsStore.nextPacketID(this.clientID);
}
public abstract void disconnect();
public IMessagesStore.StoredMessage inFlightAcknowledged(int messageID) {
return outboundFlightZone.acknowledged(messageID);
}
public abstract void cleanSession();
protected abstract int nextPacketId();
public abstract IMessagesStore.StoredMessage inFlightAcknowledged(int messageID);
/**
* Mark the message identified by guid as publish in flight.
* Mark the message as publish in flight.
*
* @return the packetID for the message in flight.
* */
public int inFlightAckWaiting(IMessagesStore.StoredMessage msg) {
LOG.debug("Adding message ot inflight zone. CId={}", clientID);
int messageId = ClientSession.this.nextPacketId();
outboundFlightZone.waitingAck(messageId, msg);
return messageId;
}
public abstract int inFlightAckWaiting(IMessagesStore.StoredMessage msg);
public IMessagesStore.StoredMessage secondPhaseAcknowledged(int messageID) {
return m_sessionsStore.secondPhaseAcknowledged(clientID, messageID);
}
public abstract IMessagesStore.StoredMessage completeReleasedPublish(int messageID);
/**
* Enqueue a message to be sent to the client.
@@ -205,37 +88,31 @@ public class ClientSession {
* @param message
* the message to enqueue.
*/
public void enqueue(IMessagesStore.StoredMessage message) {
this.m_sessionsStore.queue(this.clientID).add(message);
}
public abstract void enqueue(IMessagesStore.StoredMessage message);
public IMessagesStore.StoredMessage inboundInflight(int messageID) {
return inboundFlightZone.lookup(messageID);
}
/**
* @return the next message queued to this client or null if empty.
* */
public abstract EnqueuedMessage poll();
public void markAsInboundInflight(int messageID, IMessagesStore.StoredMessage msg) {
inboundFlightZone.waitingRel(messageID, msg);
}
public abstract boolean isEmptyQueue();
public void moveInFlightToSecondPhaseAckWaiting(int messageID, IMessagesStore.StoredMessage msg) {
m_sessionsStore.moveInFlightToSecondPhaseAckWaiting(this.clientID, messageID, msg);
}
public abstract void dropQueue();
public Set<Subscription> getSubscriptions() {
//TODO WARN this doesn'load anything from DB
return subscriptions;
}
public abstract IMessagesStore.StoredMessage inboundInflight(int messageID);
public int getPendingPublishMessagesNo() {
return m_sessionsStore.getPendingPublishMessagesNo(clientID);
}
public abstract void markAsInboundInflight(int messageID, IMessagesStore.StoredMessage msg);
public int getSecondPhaseAckPendingMessages() {
return m_sessionsStore.getSecondPhaseAckPendingMessages(clientID);
}
public abstract void moveInFlightToSecondPhaseAckWaiting(int messageID, IMessagesStore.StoredMessage msg);
public int getInflightMessagesNo() {
return m_sessionsStore.getInflightMessagesNo(clientID);
}
public abstract int getPendingPublishMessagesNo();
public abstract int countPubReleaseWaitingPubComplete();
public abstract int getInflightMessagesNo();
@Override
public String toString() {
return getClass().getSimpleName() + "{clientID='" + clientID + "'}";
}
}