All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.nats.client.impl.NatsFetchConsumer Maven / Gradle / Ivy

// Copyright 2020-2023 The NATS Authors
// 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 io.nats.client.impl;

import io.nats.client.*;
import io.nats.client.api.ConsumerConfiguration;
import io.nats.client.api.ConsumerInfo;

import java.io.IOException;

import static io.nats.client.BaseConsumeOptions.MIN_EXPIRES_MILLS;

class NatsFetchConsumer extends NatsMessageConsumerBase implements FetchConsumer, PullManagerObserver {
    private final boolean isNoWait;
    private final boolean isNoWaitNoExpires;
    private final long maxWaitNanos;
    private final String pullSubject;
    private long startNanos;

    NatsFetchConsumer(SimplifiedSubscriptionMaker subscriptionMaker,
                      ConsumerInfo cachedConsumerInfo,
                      FetchConsumeOptions fetchConsumeOptions) throws IOException, JetStreamApiException
    {
        super(cachedConsumerInfo);

        isNoWait = fetchConsumeOptions.isNoWait();
        long expiresInMillis = fetchConsumeOptions.getExpiresInMillis();
        isNoWaitNoExpires = isNoWait && expiresInMillis == ConsumerConfiguration.LONG_UNSET;

        long inactiveThreshold;
        if (expiresInMillis == ConsumerConfiguration.LONG_UNSET) { // can be for noWait
            maxWaitNanos = MIN_EXPIRES_MILLS * 1_000_000;
            inactiveThreshold = MIN_EXPIRES_MILLS; // no need to do the 10% longer
        }
        else {
            maxWaitNanos = expiresInMillis * 1_000_000;
            inactiveThreshold = expiresInMillis * 110 / 100; // 10% longer than the wait
        }

        PullRequestOptions pro = PullRequestOptions.builder(fetchConsumeOptions.getMaxMessages())
            .maxBytes(fetchConsumeOptions.getMaxBytes())
            .expiresIn(expiresInMillis)
            .idleHeartbeat(fetchConsumeOptions.getIdleHeartbeat())
            .noWait(isNoWait)
            .build();
        initSub(subscriptionMaker.subscribe(null, null, null, inactiveThreshold));
        pullSubject = sub._pull(pro, false, this);
        startNanos = -1;
    }

    @Override
    public void pendingUpdated() {}

    @Override
    public void heartbeatError() {
        stopped.set(true);
        finished.set(true);
    }

    @Override
    public Message nextMessage() throws InterruptedException, JetStreamStatusCheckedException {
        try {
            if (finished.get()) {
                return null;
            }

            // if the manager thinks it has received everything in the pull, it means
            // that all the messages are already in the internal queue and there is
            // no waiting necessary
            if (pmm.noMorePending()) {
                Message m = sub._nextUnmanagedNoWait(pullSubject);
                if (m == null) {
                    // if there are no messages in the internal cache AND there are no more pending,
                    // they all have been read and we can go ahead and finish
                    finished.set(true);
                    lenientClose();
                }
                return m;
            }

            // by not starting the timer until the first call, it gives a little buffer around
            // the next message to account for latency of incoming messages
            if (startNanos == -1) {
                startNanos = System.nanoTime();
            }
            long timeLeftMillis = (maxWaitNanos - (System.nanoTime() - startNanos)) / 1_000_000;

            // if the timer has run out, don't allow waiting
            // this might happen once, but it should already be noMorePending
            if (timeLeftMillis < 1) {
                Message m = sub._nextUnmanagedNoWait(pullSubject); // null means don't wait
                if (m == null) {
                    // no message and no time left, go ahead and finish
                    finished.set(true);
                    lenientClose();
                }
                return m;
            }

            Message m = sub._nextUnmanaged(timeLeftMillis, pullSubject);
            if (m == null && isNoWaitNoExpires) {
                // no message and no wait, go ahead and finish
                finished.set(true);
                lenientClose();
            }
            return m;
        }
        catch (JetStreamStatusException e) {
            throw new JetStreamStatusCheckedException(e);
        }
        catch (IllegalStateException i) {
            // this happens if the consumer is stopped, since it is
            // drained/unsubscribed, so don't pass it on if it's expected
            return null;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy