com.hivemq.client.internal.mqtt.handler.publish.incoming.MqttIncomingPublishService Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hivemq-mqtt-client Show documentation
Show all versions of hivemq-mqtt-client Show documentation
HiveMQ MQTT Client is an MQTT 5.0 and MQTT 3.1.1 compatible and feature-rich high-performance Java client library with different API flavours and backpressure support
/*
* Copyright 2018-present HiveMQ and the HiveMQ Community
*
* 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.hivemq.client.internal.mqtt.handler.publish.incoming;
import com.hivemq.client.internal.annotations.CallByThread;
import com.hivemq.client.internal.checkpoint.Confirmable;
import com.hivemq.client.internal.logging.InternalLogger;
import com.hivemq.client.internal.logging.InternalLoggerFactory;
import com.hivemq.client.internal.mqtt.ioc.ClientScope;
import com.hivemq.client.internal.mqtt.message.publish.MqttPublish;
import com.hivemq.client.internal.util.collections.ChunkedArrayQueue;
import com.hivemq.client.internal.util.collections.HandleList.Handle;
import com.hivemq.client.mqtt.datatypes.MqttQos;
import org.jetbrains.annotations.NotNull;
/**
* @author Silvio Giebl
*/
@ClientScope
class MqttIncomingPublishService {
private static final @NotNull InternalLogger LOGGER =
InternalLoggerFactory.getLogger(MqttIncomingPublishService.class);
private static final boolean QOS_0_DROP_OLDEST = true; // TODO configurable
private final @NotNull MqttIncomingQosHandler incomingQosHandler;
final @NotNull MqttIncomingPublishFlows incomingPublishFlows;
private final @NotNull ChunkedArrayQueue qos0Queue = new ChunkedArrayQueue<>(32);
private final ChunkedArrayQueue.@NotNull Iterator qos0It = qos0Queue.iterator();
private final @NotNull ChunkedArrayQueue qos1Or2Queue = new ChunkedArrayQueue<>(32);
private final ChunkedArrayQueue.@NotNull Iterator qos1Or2It = qos1Or2Queue.iterator();
private long nextQoS1Or2PublishId = 1;
private int referencedFlowCount;
private int runIndex;
private int blockingFlowCount;
MqttIncomingPublishService(
final @NotNull MqttIncomingQosHandler incomingQosHandler,
final @NotNull MqttIncomingPublishFlows incomingPublishFlows) {
this.incomingQosHandler = incomingQosHandler;
this.incomingPublishFlows = incomingPublishFlows;
}
@CallByThread("Netty EventLoop")
void onPublishQos0(final @NotNull MqttStatefulPublishWithFlows publishWithFlows, final int receiveMaximum) {
if (qos0Queue.size() >= receiveMaximum) { // TODO receiveMaximum
LOGGER.warn("QoS 0 publish message dropped.");
if (QOS_0_DROP_OLDEST) {
qos0It.reset();
final MqttStatefulPublishWithFlows flows = qos0It.next();
qos0It.remove();
for (Handle h = flows.getFirst(); h != null; h = h.getNext()) {
if (h.getElement().dereference() == 0) {
referencedFlowCount--;
}
}
} else {
return;
}
}
onPublish(publishWithFlows);
if (!publishWithFlows.isEmpty()) {
qos0Queue.offer(publishWithFlows);
}
}
@CallByThread("Netty EventLoop")
boolean onPublishQos1Or2(final @NotNull MqttStatefulPublishWithFlows publishWithFlows, final int receiveMaximum) {
if (qos1Or2Queue.size() >= receiveMaximum) {
return false; // flow control error
}
publishWithFlows.id = nextQoS1Or2PublishId++;
onPublish(publishWithFlows);
if (qos1Or2Queue.isEmpty() && publishWithFlows.isEmpty() && publishWithFlows.areAcknowledged()) {
incomingQosHandler.ack(publishWithFlows);
} else {
qos1Or2Queue.offer(publishWithFlows);
}
return true;
}
@CallByThread("Netty EventLoop")
private void onPublish(final @NotNull MqttStatefulPublishWithFlows publishWithFlows) {
incomingPublishFlows.findMatching(publishWithFlows);
if (publishWithFlows.isEmpty()) {
LOGGER.warn("No publish flow registered for {}.", publishWithFlows.publish);
}
drain();
for (Handle h = publishWithFlows.getFirst(); h != null; h = h.getNext()) {
if (h.getElement().reference() == 1) {
referencedFlowCount++;
}
}
emit(publishWithFlows);
}
@CallByThread("Netty EventLoop")
void drain() {
runIndex++;
blockingFlowCount = 0;
qos1Or2It.reset();
while (qos1Or2It.hasNext()) {
final MqttStatefulPublishWithFlows publishWithFlows = qos1Or2It.next();
emit(publishWithFlows);
if ((qos1Or2It.getIterated() == 1) && publishWithFlows.isEmpty() && publishWithFlows.areAcknowledged()) {
qos1Or2It.remove();
incomingQosHandler.ack(publishWithFlows);
} else if (blockingFlowCount == referencedFlowCount) {
return;
}
}
qos0It.reset();
while (qos0It.hasNext()) {
final MqttStatefulPublishWithFlows publishWithFlows = qos0It.next();
emit(publishWithFlows);
if ((qos0It.getIterated() == 1) && publishWithFlows.isEmpty()) {
qos0It.remove();
} else if (blockingFlowCount == referencedFlowCount) {
return;
}
}
}
@CallByThread("Netty EventLoop")
private void emit(final @NotNull MqttStatefulPublishWithFlows publishWithFlows) {
for (Handle h = publishWithFlows.getFirst(); h != null; h = h.getNext()) {
final MqttIncomingPublishFlow flow = h.getElement();
if (flow.isCancelled()) {
publishWithFlows.remove(h);
if (flow.dereference() == 0) {
referencedFlowCount--;
}
} else {
final long requested = flow.requested(runIndex);
if (requested > 0) {
MqttPublish publish = publishWithFlows.publish.stateless();
if (flow.manualAcknowledgement) {
final Confirmable confirmable;
if (publish.getQos() == MqttQos.AT_MOST_ONCE) {
confirmable = new MqttIncomingPublishConfirmable.Qos0();
} else {
confirmable = new MqttIncomingPublishConfirmable(flow, publishWithFlows);
}
publish = publish.withConfirmable(confirmable);
}
flow.onNext(publish);
publishWithFlows.remove(h);
if (flow.dereference() == 0) {
referencedFlowCount--;
flow.checkDone();
}
} else if (requested == 0) {
blockingFlowCount++;
if (blockingFlowCount == referencedFlowCount) {
break;
}
}
}
}
}
}