3

I am trying to create a SQS queue processor that processes messages from a SQS queue and streams them to a client using RSocket.

The problem is that I don't know how to do this because the list of messages isn't known beforehand.

From what I have read online is that I probably have to use a Sink in order to store the received messages from the queue, but I don't know how to do it cleanly (based on this post)

If someone could help me out, or point me in the right direction, that would be great.

The code below works. When a message is sent to the queue, it gets read by the receiveMessage method, which emits the received message to the sink. If a client wants to receive messages, the stream method gets called, which subscribes on the sink. But what I don't understand is that the code within the subscribe part log.info("receive {} in stream, message) doesn't get executed. But if I remove the subscribe part it doesn't work at all.

    private Many<Message> sink = Sinks.many().multicast().directBestEffort();
    private AmazonSQSAsync amazonSQSAsync;

    @SqsListener(value = "${custom.sqs}", deletionPolicy = SqsMessageDeletionPolicy.ON_SUCCESS)
    public void receiveMessage(Message message) {
        log.info("Received message from sqs: " + message);
        sink.emitNext(message, Sinks.EmitFailureHandler.FAIL_FAST);
    }

    @MessageMapping("messages")
    Flux<Message> stream(final int delay) {
        log.info("Received stream request. With delay of {} seconds between messages", delay);
        
        this.sink.asFlux().subscribe(message -> {
            log.info("receive {} in stream", message);
        }).dispose()

        return this.sink.asFlux();
    }

Based on Yuri's answer, the code below has been made!

private Many<Message> publisher = Sinks.many().multicast().directBestEffort();

@SqsListener(value = "${custom.sqs}", deletionPolicy = SqsMessageDeletionPolicy.ON_SUCCESS)
public void receiveMessage(Message message) {
    log.info("Received message from sqs: " + message);
    this.publisher.emitNext(message, Sinks.EmitFailureHandler.FAIL_FAST);
}

@MessageMapping("messages")
Flux<Message> stream(final int delay) {
    log.info("Received stream request. With delay of {} seconds between messages", delay);

    return Flux.<Message>create(sink -> {
        this.publisher.asFlux().subscribe(message -> {
            sink.next(message);
        });
    });
}

To fix the sink emission exception I used the following code below which I found from this link

    // From: https://stackoverflow.com/questions/65029619/how-to-call-sinks-manyt-tryemitnext-from-multiple-threads/65185325#65185325
    public static EmitFailureHandler retryOnNonSerializedElse(EmitFailureHandler fallback) {
        return (signalType, emitResult) -> {
            if (emitResult == EmitResult.FAIL_NON_SERIALIZED) {
                LockSupport.parkNanos(10);
                return true;
            } else
                return fallback.onEmitFailure(signalType, emitResult);
        };
    }

And I used this failureHandler in the emitNext() method which is called in the receiveMessages() method.

        publisher.emitNext(message, retryOnNonSerializedElse(Sinks.EmitFailureHandler.FAIL_FAST));

This works, although I don't completely understand why

RatManMan
  • 63
  • 6
  • 1
    Without a subscribe, nothing should happen. Rx is an opinionated functional approach, everything until the subscribe is setting up a chain of operations and mutating it without starting it. Removing the subscribe makes it a no-op. – Yuri Schimke May 10 '21 at 00:19
  • 1
    You can either use a Processor like the linked post, or alternatively use Flux.create with the subscription inside the create lambda body. This would also allow you to handle the cancel and any natural completion cases. See https://projectreactor.io/docs/core/release/reference/#producing.create Each message you receive can be sent to sink.next() – Yuri Schimke May 10 '21 at 00:23
  • Thank you for your answers! I chose to use Flux.create() because when I used the `DirectProcessor` it gave a `deprecated` warning. The final code has been added to the post. – RatManMan May 10 '21 at 08:37
  • I misunderstood, I thought Many came from SQS. I think I gave bad advice. If you already have a flux you can hopefully remove the subscribe from your code completely and just end with `return this.publisher.asFlux()`. The framework should call subscribe for you when you return the Flux, but I think you said it wasn't. – Yuri Schimke May 10 '21 at 09:27
  • Also your logging approach in the first example can usually be achieved by chaining a doOnNext to the flux you return. https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Flux.html#doOnNext-java.util.function.Consumer- – Yuri Schimke May 10 '21 at 09:29
  • At first I returned `this.publisher.asFlux()` in the `stream()` method, but it didn't do anything. It only started working when I also subscribed to the `publisher`. But the code works now and looks better than before, so i think i will keep it as it is for now! – RatManMan May 10 '21 at 11:35
  • I just tested the code with a 100 messages (quickly sent) and it sometimes throws the following error: `2021-05-10 14:45:18.870 ERROR 27860 --- [enerContainer-2] o.s.c.a.m.listener.QueueMessageHandler : An exception occurred while invoking the handler method reactor.core.publisher.Sinks$EmissionException: Spec. Rule 1.3 - onSubscribe, onNext, onError and onComplete signaled to a Subscriber MUST be signaled serially.` I tried to look it up online, but couldn't find anything. Also, even though the error is thrown, the messages get correctly sent to the client. Do you know how to fix it? – RatManMan May 10 '21 at 12:49
  • I read [streams 1.3](https://github.com/reactive-streams/reactive-streams-jvm#1.3), [streams signal](https://github.com/reactive-streams/reactive-streams-jvm#term_signal) and [streams serially](https://github.com/reactive-streams/reactive-streams-jvm#term_serially) But I don't understand it :/ – RatManMan May 10 '21 at 12:49
  • 2
    It's because sink.emitNext is called concurrently. See: https://stackoverflow.com/a/65202495/6051176 – Martin Tarjányi May 10 '21 at 12:51
  • So because the `receiveMessage()` method is called concurrently, it throws the error because `sink.emitNext()` should be called sequentially? – RatManMan May 10 '21 at 13:23

0 Answers0