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:
@@ -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 + "'}";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user