Merged topic subscription search and publish into single MessagePublisher method
First step on implementing H2 storage Minor, fixed not imported class Cleaned MessageStore interface to avoid duplication of methods used in couple. Switched from guid + message to move messages Separated ISubscriptionsStore from ISessionStore Implemented H2 queue
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2017 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.persistence.h2;
|
||||
|
||||
import io.moquette.spi.IMatchingCondition;
|
||||
import io.moquette.spi.IMessagesStore;
|
||||
import io.moquette.spi.IMessagesStore.StoredMessage;
|
||||
import io.moquette.spi.MessageGUID;
|
||||
import io.moquette.spi.impl.subscriptions.Topic;
|
||||
import org.h2.mvstore.Cursor;
|
||||
import org.h2.mvstore.MVMap;
|
||||
import org.h2.mvstore.MVStore;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sun.plugin2.message.Message;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
class H2MessagesStore implements IMessagesStore {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(H2MessagesStore.class);
|
||||
|
||||
private final MVStore mvStore;
|
||||
|
||||
// maps clientID -> guid
|
||||
private MVMap<Topic, MessageGUID> retainedStore;
|
||||
// maps guid to message, it's message store
|
||||
private MVMap<MessageGUID, StoredMessage> persistentMessageStore;
|
||||
|
||||
private MVMap<Topic, StoredMessage> new_retainedStore;
|
||||
|
||||
public H2MessagesStore(MVStore mvStore) {
|
||||
this.mvStore = mvStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initStore() {
|
||||
retainedStore = mvStore.openMap("retained");
|
||||
persistentMessageStore = mvStore.openMap("oldPersistedMessages");
|
||||
new_retainedStore = mvStore.openMap("persistedMessages");
|
||||
LOG.info("Initialized message H2 store");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeRetained(Topic topic, StoredMessage storedMessage) {
|
||||
LOG.debug("Store retained message for topic={}, CId={}", topic, storedMessage.getClientID());
|
||||
if (storedMessage.getClientID() == null) {
|
||||
throw new IllegalArgumentException( "Message to be persisted must have a not null client ID");
|
||||
}
|
||||
new_retainedStore.put(topic, storedMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<StoredMessage> searchMatching(IMatchingCondition condition) {
|
||||
LOG.debug("Scanning retained messages");
|
||||
List<StoredMessage> results = new ArrayList<>();
|
||||
|
||||
Cursor<Topic, MessageGUID> mapCursor = retainedStore.cursor(null);
|
||||
while (mapCursor.hasNext()) {
|
||||
final MessageGUID guid = mapCursor.getValue();
|
||||
final Topic topic = mapCursor.getKey();
|
||||
StoredMessage storedMsg = persistentMessageStore.get(guid);
|
||||
if (condition.match(topic)) {
|
||||
results.add(storedMsg);
|
||||
}
|
||||
}
|
||||
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("Retained messages have been scanned matchingMessages={}", results);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanRetained(Topic topic) {
|
||||
LOG.debug("Cleaning retained messages. Topic={}", topic);
|
||||
retainedStore.remove(topic);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package io.moquette.persistence.h2;
|
||||
|
||||
import org.h2.mvstore.MVMap;
|
||||
import org.h2.mvstore.MVStore;
|
||||
|
||||
import java.util.AbstractQueue;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
class H2PersistentQueue<T> extends AbstractQueue<T> {
|
||||
|
||||
private final MVMap<Long, T> mvMap;
|
||||
private final MVMap<String, Long> metadataMap;
|
||||
private final AtomicLong head;
|
||||
private final AtomicLong tail;
|
||||
|
||||
H2PersistentQueue(MVStore store, String queueName) {
|
||||
if (queueName == null || queueName.isEmpty()) {
|
||||
throw new IllegalArgumentException("queueName parameter can't be empty or null");
|
||||
}
|
||||
this.mvMap = store.openMap("queue_" + queueName);
|
||||
this.metadataMap = store.openMap("queue_" + queueName + "_meta");
|
||||
|
||||
//setup head index
|
||||
long headIdx = 0L;
|
||||
if (this.metadataMap.containsKey("head")) {
|
||||
headIdx = this.metadataMap.get("head");
|
||||
} else {
|
||||
this.metadataMap.put("head", headIdx);
|
||||
}
|
||||
this.head = new AtomicLong(headIdx);
|
||||
|
||||
//setup tail index
|
||||
long tailIdx = 0L;
|
||||
if (this.metadataMap.containsKey("tail")) {
|
||||
tailIdx = this.metadataMap.get("tail");
|
||||
} else {
|
||||
this.metadataMap.put("tail", tailIdx);
|
||||
}
|
||||
this.tail = new AtomicLong(tailIdx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return this.head.intValue() - this.tail.intValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean offer(T t) {
|
||||
if (t == null) {
|
||||
throw new NullPointerException("Inserted element can't be null");
|
||||
}
|
||||
final long nextHead = head.getAndIncrement();
|
||||
this.mvMap.put(nextHead, t);
|
||||
this.metadataMap.put("head", nextHead + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T poll() {
|
||||
if (head.equals(tail)) {
|
||||
return null;
|
||||
}
|
||||
final long nextTail = tail.getAndIncrement();
|
||||
final T tail = this.mvMap.get(nextTail);
|
||||
this.metadataMap.put("tail", nextTail + 1);
|
||||
return tail;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T peek() {
|
||||
if (head.equals(tail)) {
|
||||
return null;
|
||||
}
|
||||
return this.mvMap.get(tail.get());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2017 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.persistence.h2;
|
||||
|
||||
import io.moquette.BrokerConstants;
|
||||
import io.moquette.server.config.IConfig;
|
||||
import io.moquette.spi.IMessagesStore;
|
||||
import io.moquette.spi.ISessionsStore;
|
||||
import io.moquette.spi.IStore;
|
||||
import org.h2.mvstore.MVStore;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class H2PersistentStore implements IStore {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(H2PersistentStore.class);
|
||||
|
||||
private final String storePath;
|
||||
private final int autosaveInterval; // in seconds
|
||||
protected final ScheduledExecutorService scheduler;
|
||||
private MVStore mvStore;
|
||||
|
||||
private IMessagesStore messageStore;
|
||||
private ISessionsStore sessionsStore;
|
||||
|
||||
public H2PersistentStore(IConfig props, ScheduledExecutorService scheduler) {
|
||||
this.storePath = props.getProperty(BrokerConstants.PERSISTENT_STORE_PROPERTY_NAME, "");
|
||||
this.autosaveInterval = Integer.parseInt(props.getProperty(BrokerConstants.AUTOSAVE_INTERVAL_PROPERTY_NAME, "30"));
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initStore() {
|
||||
LOG.info("Initializing H2 store");
|
||||
if (storePath == null || storePath.isEmpty()) {
|
||||
LOG.warn("H2 store file path is empty, using in-memory store");
|
||||
mvStore = MVStore.open(null);
|
||||
|
||||
} else {
|
||||
mvStore = new MVStore.Builder()
|
||||
.fileName(storePath)
|
||||
.autoCommitDisabled()
|
||||
.open();
|
||||
}
|
||||
|
||||
LOG.info("Scheduling H2 commit task");
|
||||
scheduler.scheduleWithFixedDelay(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
LOG.debug("Committing to H2");
|
||||
mvStore.commit();
|
||||
}
|
||||
}, this.autosaveInterval, this.autosaveInterval, TimeUnit.SECONDS);
|
||||
|
||||
messageStore = new H2MessagesStore(mvStore);
|
||||
messageStore.initStore();
|
||||
|
||||
sessionsStore = new H2SessionsStore(mvStore);
|
||||
sessionsStore.initStore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMessagesStore messagesStore() {
|
||||
return messageStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ISessionsStore sessionsStore() {
|
||||
return sessionsStore;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2017 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
package io.moquette.persistence.h2;
|
||||
|
||||
import io.moquette.persistence.PersistentSession;
|
||||
import io.moquette.spi.ClientSession;
|
||||
import io.moquette.spi.IMessagesStore;
|
||||
import io.moquette.spi.IMessagesStore.StoredMessage;
|
||||
import io.moquette.spi.ISessionsStore;
|
||||
import io.moquette.spi.ISubscriptionsStore;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
import io.moquette.spi.impl.subscriptions.Topic;
|
||||
import org.h2.mvstore.Cursor;
|
||||
import org.h2.mvstore.MVMap;
|
||||
import org.h2.mvstore.MVStore;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
public class H2SessionsStore implements ISessionsStore, ISubscriptionsStore {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(H2SessionsStore.class);
|
||||
|
||||
private final MVStore mvStore;
|
||||
private ConcurrentMap<String, PersistentSession> sessions;
|
||||
// maps clientID->[MessageId -> msg]
|
||||
private ConcurrentMap<String, ConcurrentMap<Integer, StoredMessage>> outboundFlightMessages;
|
||||
// map clientID <-> set of currently in flight packet identifiers
|
||||
private Map<String, Set<Integer>> inFlightIds;
|
||||
// maps clientID->[MessageId -> guid]
|
||||
private ConcurrentMap<String, ConcurrentMap<Integer, StoredMessage>> secondPhaseStore;
|
||||
|
||||
public H2SessionsStore(MVStore mvStore) {
|
||||
this.mvStore = mvStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initStore() {
|
||||
this.sessions = mvStore.openMap("sessions");
|
||||
this.outboundFlightMessages = mvStore.openMap("outboundFlight");
|
||||
this.inFlightIds = mvStore.openMap("inflightPacketIDs");
|
||||
this.secondPhaseStore = mvStore.openMap("secondPhase");
|
||||
LOG.info("Initialized sessions H2 store");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ISubscriptionsStore subscriptionStore() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCleanStatus(String clientID, boolean cleanSession) {
|
||||
LOG.info("Updating cleanSession flag. CId={}, cleanSession={}", clientID, cleanSession);
|
||||
this.sessions.put(clientID, new PersistentSession(cleanSession));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNewSubscription(Subscription newSubscription) {
|
||||
LOG.info("Adding new subscription CId={}, topics={}", newSubscription.getClientId(),
|
||||
newSubscription.getTopicFilter());
|
||||
final String clientID = newSubscription.getClientId();
|
||||
final MVMap<Object, Object> sessionSubscriptions = this.mvStore.openMap("subscriptions_" + clientID);
|
||||
sessionSubscriptions.put(newSubscription.getTopicFilter(), newSubscription);
|
||||
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("Subscription has been added CId={}, topics={}, clientSubscriptions={}",
|
||||
newSubscription.getClientId(), newSubscription.getTopicFilter(), sessionSubscriptions);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSubscription(Topic topicFilter, String clientID) {
|
||||
LOG.info("Removing subscription. CId={}, topics={}", clientID, topicFilter);
|
||||
if (!this.mvStore.hasMap("subscriptions_" + clientID)) {
|
||||
return;
|
||||
}
|
||||
final MVMap<Object, Object> sessionsSubscriptions = this.mvStore.openMap("subscriptions_" + clientID);
|
||||
sessionsSubscriptions.remove(topicFilter);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Subscription has been removed. CId={}, topics={}, clientSubscriptions={}", clientID,
|
||||
topicFilter, sessionsSubscriptions);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void wipeSubscriptions(String sessionID) {
|
||||
LOG.info("Wiping subscriptions. CId={}", sessionID);
|
||||
if (!this.mvStore.hasMap("subscriptions_" + sessionID)) {
|
||||
return;
|
||||
}
|
||||
final MVMap<Object, Object> subscriptions = this.mvStore.openMap("subscriptions_" + sessionID);
|
||||
this.mvStore.removeMap(subscriptions);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Subscriptions have been removed. CId={}, clientSubscriptions={}", sessionID, subscriptions);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ClientTopicCouple> listAllSubscriptions() {
|
||||
LOG.info("Retrieving existing subscriptions");
|
||||
final List<ClientTopicCouple> allSubscriptions = new ArrayList<>();
|
||||
for (String clientID : this.sessions.keySet()) {
|
||||
ConcurrentMap<Topic, Subscription> clientSubscriptions = this.mvStore.openMap("subscriptions_" + clientID);
|
||||
for (Topic topicFilter : clientSubscriptions.keySet()) {
|
||||
allSubscriptions.add(new ClientTopicCouple(clientID, topicFilter));
|
||||
}
|
||||
}
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("The existing subscriptions have been retrieved. Result={}", allSubscriptions);
|
||||
}
|
||||
return allSubscriptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Subscription> getSubscriptions() {
|
||||
LOG.debug("Retrieving existing subscriptions...");
|
||||
List<Subscription> subscriptions = new ArrayList<>();
|
||||
for (String clientID : this.sessions.keySet()) {
|
||||
ConcurrentMap<Topic, Subscription> clientSubscriptions = this.mvStore.openMap("subscriptions_" + clientID);
|
||||
subscriptions.addAll(clientSubscriptions.values());
|
||||
}
|
||||
LOG.debug("Existing subscriptions has been retrieved Result={}", subscriptions);
|
||||
return subscriptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Subscription getSubscription(ClientTopicCouple couple) {
|
||||
ConcurrentMap<Topic, Subscription> clientSubscriptions = this.mvStore.openMap("subscriptions_" + couple.clientID);
|
||||
LOG.debug("Retrieving subscriptions CId={}, subscriptions={}", couple.clientID, clientSubscriptions);
|
||||
return clientSubscriptions.get(couple.topicFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String clientID) {
|
||||
return this.mvStore.hasMap("subscriptions_" + clientID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSession createNewSession(String clientID, boolean cleanSession) {
|
||||
if (sessions.containsKey(clientID)) {
|
||||
LOG.error("Unable to create a new session: the client ID is already in use. CId={}, cleanSession={}",
|
||||
clientID, cleanSession);
|
||||
throw new IllegalArgumentException("Can't create a session with the ID of an already existing" + clientID);
|
||||
}
|
||||
LOG.debug("Creating new session. CId={}, cleanSession={}", clientID, cleanSession);
|
||||
sessions.putIfAbsent(clientID, new PersistentSession(cleanSession));
|
||||
return new ClientSession(clientID, this, this, cleanSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSession sessionForClient(String clientID) {
|
||||
LOG.debug("Retrieving session. CId={}", clientID);
|
||||
if (!this.sessions.containsKey(clientID)) {
|
||||
LOG.warn("Session does not exist. CId={}", clientID);
|
||||
return null;
|
||||
}
|
||||
PersistentSession storedSession = this.sessions.get(clientID);
|
||||
return new ClientSession(clientID, this, this, storedSession.cleanSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ClientSession> getAllSessions() {
|
||||
Collection<ClientSession> result = new ArrayList<>();
|
||||
for (Map.Entry<String, PersistentSession> entry : this.sessions.entrySet()) {
|
||||
result.add(new ClientSession(entry.getKey(), this, this, entry.getValue().cleanSession));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoredMessage inFlightAck(String clientID, int messageID) {
|
||||
LOG.debug("Acknowledging inflight message CId={}, messageId={}", clientID, messageID);
|
||||
ConcurrentMap<Integer, StoredMessage> m = this.outboundFlightMessages.get(clientID);
|
||||
if (m == null) {
|
||||
LOG.error("Can't find the inFlight record for client <{}>", clientID);
|
||||
throw new RuntimeException("Can't find the inFlight record for client <" + clientID + ">");
|
||||
}
|
||||
StoredMessage msg = m.remove(messageID);
|
||||
this.outboundFlightMessages.put(clientID, m);
|
||||
|
||||
// remove from the ids store
|
||||
Set<Integer> inFlightForClient = this.inFlightIds.get(clientID);
|
||||
if (inFlightForClient != null) {
|
||||
inFlightForClient.remove(messageID);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inFlight(String clientID, int messageID, StoredMessage msg) {
|
||||
ConcurrentMap<Integer, IMessagesStore.StoredMessage> messages = this.outboundFlightMessages.get(clientID);
|
||||
if (messages == null) {
|
||||
messages = new ConcurrentHashMap<>();
|
||||
}
|
||||
messages.put(messageID, msg);
|
||||
this.outboundFlightMessages.put(clientID, messages);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextPacketID(String clientID) {
|
||||
LOG.debug("Generating next packet ID CId={}", clientID);
|
||||
Set<Integer> inFlightForClient = this.inFlightIds.get(clientID);
|
||||
if (inFlightForClient == null) {
|
||||
int nextPacketId = 1;
|
||||
inFlightForClient = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());
|
||||
inFlightForClient.add(nextPacketId);
|
||||
this.inFlightIds.put(clientID, inFlightForClient);
|
||||
return nextPacketId;
|
||||
}
|
||||
|
||||
int maxId = inFlightForClient.isEmpty() ? 0 : Collections.max(inFlightForClient);
|
||||
int nextPacketId = (maxId % 0xFFFF) + 1;
|
||||
inFlightForClient.add(nextPacketId);
|
||||
LOG.debug("Next packet ID has been generated CId={}, result={}", clientID, nextPacketId);
|
||||
return nextPacketId;
|
||||
}
|
||||
|
||||
|
||||
class H2BackedQueue<T> extends AbstractQueue<T> {
|
||||
|
||||
private final MVMap<T, ?> map;
|
||||
private final Cursor<T, ?> cursor;
|
||||
|
||||
H2BackedQueue(MVMap<T, ?> map) {
|
||||
this.cursor = map.cursor(null);
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean offer(T t) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T poll() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T peek() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//TODO
|
||||
@Override
|
||||
public BlockingQueue<StoredMessage> queue(String clientID) {
|
||||
throw new RuntimeException("Not yet implemented");
|
||||
}
|
||||
|
||||
//TODO
|
||||
@Override
|
||||
public void dropQueue(String clientID) {
|
||||
throw new RuntimeException("Not yet implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveInFlightToSecondPhaseAckWaiting(String clientID, int messageID, StoredMessage msg) {
|
||||
LOG.debug("Moving inflight message to 2nd phase ack state CId={}, messageID={}", clientID, messageID);
|
||||
ConcurrentMap<Integer, StoredMessage> m = this.secondPhaseStore.get(clientID);
|
||||
if (m == null) {
|
||||
String error = String.format("Can't find the inFlight record for client <%s> during the second phase of " +
|
||||
"QoS2 pub", clientID);
|
||||
LOG.error(error);
|
||||
throw new RuntimeException(error);
|
||||
}
|
||||
m.put(messageID, msg);
|
||||
this.outboundFlightMessages.put(clientID, m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoredMessage secondPhaseAcknowledged(String clientID, int messageID) {
|
||||
LOG.debug("Processing second phase ACK CId={}, messageId={}", clientID, messageID);
|
||||
final ConcurrentMap<Integer, StoredMessage> m = this.secondPhaseStore.get(clientID);
|
||||
if (m == null) {
|
||||
String error = String.format("Can't find the inFlight record for client <%s> during the second phase " +
|
||||
"acking of QoS2 pub", clientID);
|
||||
LOG.error(error);
|
||||
throw new RuntimeException(error);
|
||||
}
|
||||
|
||||
StoredMessage msg = m.remove(messageID);
|
||||
this.secondPhaseStore.put(clientID, m);
|
||||
return msg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInflightMessagesNo(String clientID) {
|
||||
int totalInflight = 0;
|
||||
ConcurrentMap<Integer, StoredMessage> inflightPerClient = this.mvStore.openMap(inboundStoreForClient(clientID));
|
||||
if (inflightPerClient != null) {
|
||||
totalInflight += inflightPerClient.size();
|
||||
}
|
||||
|
||||
Map<Integer, StoredMessage> secondPhaseInFlight = this.secondPhaseStore.get(clientID);
|
||||
if (secondPhaseInFlight != null) {
|
||||
totalInflight += secondPhaseInFlight.size();
|
||||
}
|
||||
|
||||
Map<Integer, StoredMessage> outboundPerClient = outboundFlightMessages.get(clientID);
|
||||
if (outboundPerClient != null) {
|
||||
totalInflight += outboundPerClient.size();
|
||||
}
|
||||
|
||||
return totalInflight;
|
||||
}
|
||||
|
||||
private static String inboundStoreForClient(String clientID) {
|
||||
return "inboundInflight_" + clientID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoredMessage inboundInflight(String clientID, int messageID) {
|
||||
LOG.debug("Mapping inbound message ID to GUID CId={}, messageId={}", clientID, messageID);
|
||||
ConcurrentMap<Integer, StoredMessage> messageIdToGuid = this.mvStore.openMap(inboundStoreForClient(clientID));
|
||||
return messageIdToGuid.get(messageID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markAsInboundInflight(String clientID, int messageID, StoredMessage msg) {
|
||||
ConcurrentMap<Integer, StoredMessage> messageIdToGuid = this.mvStore.openMap(inboundStoreForClient(clientID));
|
||||
messageIdToGuid.put(messageID, msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPendingPublishMessagesNo(String clientID) {
|
||||
return queue(clientID).size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSecondPhaseAckPendingMessages(String clientID) {
|
||||
if (!this.secondPhaseStore.containsKey(clientID))
|
||||
return 0;
|
||||
return this.secondPhaseStore.get(clientID).size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dropInFlightMessagesInSession(String clientID) {
|
||||
this.secondPhaseStore.remove(clientID);
|
||||
this.outboundFlightMessages.remove(clientID);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user