Files
sailing-analytics/java/com.sap.sse.replication/src/com/sap/sse/replication/impl/RabbitInputStreamProvider.java
T
Axel Uhl 48b5c2c8b0 bug6211: use explicit ACKs in RabbitInputStreamProvider and set basicQos(1);
this to avoid any flooding of TCP sockets by the RabbitMQ server when
the client isn't able to consume traffic fast enough
2026-02-03 11:34:37 +01:00

86 lines
4.3 KiB
Java
Executable File

package com.sap.sse.replication.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConsumerCancelledException;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.QueueingConsumer.Delivery;
import com.rabbitmq.client.ShutdownSignalException;
import com.sap.sse.common.impl.NamedImpl;
/**
* Input stream that reads messages from a RabbitMQ queue filled by a {@link RabbitOutputStream} and lets a client read
* this stream from {@link #getInputStream()}.
*
* @author Simon Marcel Pamies
* @author Axel Uhl
*/
public class RabbitInputStreamProvider extends NamedImpl {
private static final Logger logger = Logger.getLogger(RabbitInputStreamProvider.class.getName());
private static final long serialVersionUID = 1342935135386887494L;
private final PipedInputStream clientReadsFromThis;
private final PipedOutputStream messagesAreWrittenToThis;
public RabbitInputStreamProvider(Channel channel, String queueName) throws IOException {
this(channel, queueName, /* name */ UUID.randomUUID().toString());
}
public RabbitInputStreamProvider(Channel channel, String queueName, String name) throws IOException {
super(name);
assert name != null;
messagesAreWrittenToThis = new PipedOutputStream();
clientReadsFromThis = new PipedInputStream(messagesAreWrittenToThis);
final QueueingConsumer messageConsumer = new QueueingConsumer(channel);
channel.basicQos(1); // with "manual" acknowledgement, process one message at a time, avoiding overloading inbound socket
channel.basicConsume(queueName, /* auto-ack */ false, messageConsumer);
new Thread(getClass().getSimpleName()) {
@Override
public void run() {
while (true) {
try {
final Delivery delivery = messageConsumer.nextDelivery();
byte[] bytesFromMessage = delivery.getBody();
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), /* multiple */ false);
if (RabbitOutputStream.startsWithTerminationCommand(bytesFromMessage, bytesFromMessage.length)) {
if (bytesFromMessage.length == RabbitOutputStream.TERMINATION_COMMAND.length) {
// received exactly TERMINATION_COMMAND
messagesAreWrittenToThis.close();
break;
} else {
// more to come; skip one byte which is the "escape" symbol ensuring that the message
// sizes don't match
byte[] newBytesFromMessage = new byte[bytesFromMessage.length - 1];
System.arraycopy(bytesFromMessage, 0, newBytesFromMessage, 0,
RabbitOutputStream.TERMINATION_COMMAND.length);
System.arraycopy(bytesFromMessage, RabbitOutputStream.TERMINATION_COMMAND.length + 1,
newBytesFromMessage, RabbitOutputStream.TERMINATION_COMMAND.length,
bytesFromMessage.length - RabbitOutputStream.TERMINATION_COMMAND.length - 1);
bytesFromMessage = newBytesFromMessage;
}
}
messagesAreWrittenToThis.write(bytesFromMessage);
} catch (ShutdownSignalException | ConsumerCancelledException e) {
logger.log(Level.INFO, "Problem with message queue "+getName(), e);
break;
} catch (InterruptedException e) {
logger.log(Level.WARNING, "Reading of next message in stream "+getName()+" Interrupted; continuing", e);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
}
public InputStream getInputStream() {
return clientReadsFromThis;
}
}