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

io.nats.stan.ConnectionImpl Maven / Gradle / Ivy

There is a newer version: 2.2.3
Show newest version
/*******************************************************************************
 * Copyright (c) 2015-2016 Apcera Inc. All rights reserved. This program and the accompanying
 * materials are made available under the terms of the MIT License (MIT) which accompanies this
 * distribution, and is available at http://opensource.org/licenses/MIT
 *******************************************************************************/

package io.nats.stan;

import io.nats.client.Message;
import io.nats.client.MessageHandler;
import io.nats.client.NUID;
import io.nats.stan.protobuf.Ack;
import io.nats.stan.protobuf.CloseRequest;
import io.nats.stan.protobuf.CloseResponse;
import io.nats.stan.protobuf.ConnectRequest;
import io.nats.stan.protobuf.ConnectResponse;
import io.nats.stan.protobuf.MsgProto;
import io.nats.stan.protobuf.PubAck;
import io.nats.stan.protobuf.PubMsg;
import io.nats.stan.protobuf.SubscriptionRequest;
import io.nats.stan.protobuf.SubscriptionResponse;

import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class ConnectionImpl implements Connection, io.nats.client.MessageHandler {

    static final String DEFAULT_NATS_URL = io.nats.client.ConnectionFactory.DEFAULT_URL;
    static final int DEFAULT_CONNECT_WAIT = 2; // Seconds
    static final String DEFAULT_DISCOVER_PREFIX = "_STAN.discover";
    static final String DEFAULT_ACK_PREFIX = "_STAN.acks";
    static final int DEFAULT_MAX_PUB_ACKS_IN_FLIGHT = 2 ^ 14; // 16384

    static final String PFX = "stan: ";
    static final String ERR_BAD_ACK = PFX + "malformed ack";
    static final String ERR_BAD_CONNECTION = PFX + "invalid connection";
    static final String ERR_BAD_SUBSCRIPTION = PFX + "invalid subscription";
    static final String ERR_CLOSE_REQ_TIMEOUT = PFX + "close request timeout";
    static final String ERR_CONNECTION_CLOSED = PFX + "connection closed";
    static final String ERR_CONNECTION_REQ_TIMEOUT = PFX + "connect request timeout";
    static final String ERR_MANUAL_ACK = PFX + "cannot manually ack in auto-ack mode";
    static final String ERR_NULL_MSG = PFX + "null message";
    static final String ERR_TIMEOUT = PFX + "publish ack timeout";

    // Server errors
    static final String SERVER_ERR_BAD_PUB_MSG = "stan: malformed publish message envelope";
    static final String SERVER_ERR_BAD_SUB_REQUEST = "stan: malformed subscription request";
    static final String SERVER_ERR_INVALID_SUBJECT = "stan: invalid subject";
    static final String SERVER_ERR_INVALID_SEQUENCE = "stan: invalid start sequence";
    static final String SERVER_ERR_INVALID_TIME = "stan: invalid start time";
    static final String SERVER_ERR_INVALID_SUB = "stan: invalid subscription";
    static final String SERVER_ERR_INVALID_CONN_REQ = "stan: invalid connection request";
    static final String SERVER_ERR_INVALID_CLIENT = "stan: clientID already registered";
    static final String SERVER_ERR_INVALID_CLOSE_REQ = "stan: invalid close request";
    static final String SERVER_ERR_INVALID_ACK_WAIT =
            "stan: invalid ack wait time, should be >= 1s";
    static final String SERVER_ERR_DUP_DURABLE = "stan: duplicate durable registration";
    static final String SERVER_ERR_INVALID_DURABLE_NAME =
            "stan: durable name of a durable queue subscriber can't contain the character ':'";
    static final String SERVER_ERR_DURABLE_QUEUE = "stan: queue subscribers can't be durable";
    static final String SERVER_ERR_TIMEOUT = "stan: publish ack timeout";

    static final Logger logger = LoggerFactory.getLogger(ConnectionImpl.class);

    final ReadWriteLock mu = new ReentrantReadWriteLock();

    String clientId;
    String clusterId;
    String pubPrefix; // Publish prefix set by stan, append our subject.
    String subRequests; // Subject to send subscription requests.
    String unsubRequests; // Subject to send unsubscribe requests.
    String closeRequests; // Subject to send close requests.
    String ackSubject; // publish acks
    io.nats.client.Subscription ackSubscription;
    String hbInbox;
    io.nats.client.Subscription hbSubscription;
    io.nats.client.MessageHandler hbCallback;

    Map subMap;
    Map pubAckMap;
    BlockingQueue pubAckChan;
    Options opts;
    io.nats.client.Connection nc;

    Timer ackTimer = new Timer(true);

    boolean ncOwned = false;

    ExecutorService exec = Executors.newCachedThreadPool();

    protected ConnectionImpl() {

    }

    ConnectionImpl(String stanClusterId, String clientId) {
        this(stanClusterId, clientId, null);
    }

    ConnectionImpl(String stanClusterId, String clientId, Options opts) {
        this.clusterId = stanClusterId;
        this.clientId = clientId;

        if (opts == null) {
            this.opts = new Options.Builder().create();
        } else {
            this.opts = opts;
            // Check if the user has provided a connection as an option
            if (this.opts.getNatsConn() != null) {
                setNatsConnection(this.opts.getNatsConn());
            }
        }
    }

    // Connect will form a connection to the STAN subsystem.
    void connect() throws IOException, TimeoutException {
        boolean exThrown = false;
        io.nats.client.Connection nc = getNatsConnection();
        // Create a NATS connection if it doesn't exist
        if (nc == null) {
            nc = createNatsConnection();
            setNatsConnection(nc);
            ncOwned = true;
        } else if (!nc.isConnected()) {
            // Bail if the custom NATS connection is disconnected
            throw new IOException(ERR_BAD_CONNECTION);
        }

        try {
            // Create a heartbeat inbox
            hbInbox = nc.newInbox();
            hbCallback = new MessageHandler() {
                @Override
                public void onMessage(Message msg) {
                    processHeartBeat(msg);
                }
            };
            hbSubscription = nc.subscribe(hbInbox, hbCallback);

            // Send Request to discover the cluster
            String discoverSubject = String.format("%s.%s", opts.getDiscoverPrefix(), clusterId);
            ConnectRequest req = ConnectRequest.newBuilder().setClientID(clientId)
                    .setHeartbeatInbox(hbInbox).build();
            // logger.trace("Sending ConnectRequest:\n{}", req.toString().trim());
            byte[] bytes = req.toByteArray();
            Message reply = null;
            reply = nc.request(discoverSubject, bytes, opts.getConnectTimeout().toMillis());

            ConnectResponse cr = ConnectResponse.parseFrom(reply.getData());
            if (!cr.getError().isEmpty()) {
                // This is already a properly formatted stan error message
                // (ConnectionImpl.SERVER_ERR_INVALID_CLIENT)
                throw new IOException(cr.getError());
            }
            logger.trace("Received ConnectResponse:\n{}", cr);

            // Capture cluster configuration endpoints to publish and
            // subscribe/unsubscribe.
            pubPrefix = cr.getPubPrefix();
            subRequests = cr.getSubRequests();
            unsubRequests = cr.getUnsubRequests();
            closeRequests = cr.getCloseRequests();

            // Setup the ACK subscription
            ackSubject = String.format("%s.%s", DEFAULT_ACK_PREFIX, NUID.nextGlobal());
            ackSubscription = nc.subscribe(ackSubject, new MessageHandler() {
                public void onMessage(io.nats.client.Message msg) {
                    processAck(msg);
                }
            });
            ackSubscription.setPendingLimits(1024 ^ 2, 32 * 1024 ^ 2);
            pubAckMap = new HashMap();

            // Create Subscription map
            subMap = new HashMap();

            pubAckChan = new LinkedBlockingQueue(opts.getMaxPubAcksInFlight());
        } catch (IOException e) {
            exThrown = true;
            throw e;
        } catch (TimeoutException e) {
            exThrown = true;
            if (io.nats.client.Constants.ERR_TIMEOUT.equals(e.getMessage())) {
                TimeoutException te = new TimeoutException(ERR_CONNECTION_REQ_TIMEOUT);
                te.initCause(e);
                throw te;
            }
        } finally {
            if (exThrown) {
                try {
                    close();
                } catch (Exception e2) {
                    /* NOOP -- can't do anything if close fails */
                }
            }
        }
    }

    io.nats.client.ConnectionFactory createNatsConnectionFactory() {
        io.nats.client.ConnectionFactory cf = new io.nats.client.ConnectionFactory();
        if (opts.getNatsUrl() != null) {
            cf.setUrl(opts.getNatsUrl());
        }
        return cf;
    }

    io.nats.client.Connection createNatsConnection() throws IOException, TimeoutException {
        // Create a NATS connection if it doesn't exist
        io.nats.client.Connection nc = null;
        if (getNatsConnection() == null) {
            nc = createNatsConnectionFactory().createConnection();
            ncOwned = true;
        }
        return nc;
    }

    @Override
    public void close() throws IOException, TimeoutException {
        logger.trace("In STAN close()");
        io.nats.client.Connection nc = null;
        this.lock();
        try {
            // Capture for NATS calls below
            nc = getNatsConnection();
            if (nc == null) {
                // We are already closed
                logger.debug("stan: NATS connection already closed");
                return;
            }

            // Capture for NATS calls below.
            nc = getNatsConnection();

            // if ncOwned, we close it at the end
            try {
                // Signals we are closed.
                setNatsConnection(null);

                // Now close ourselves.
                if (getAckSubscription() != null) {
                    try {
                        getAckSubscription().unsubscribe();
                    } catch (Exception e) {
                        logger.warn("stan: error unsubscribing from acks during connection close");
                        logger.debug("Full stack trace: ", e);
                    }
                }

                if (getHbSubscription() != null) {
                    try {
                        getHbSubscription().unsubscribe();
                    } catch (Exception e) {
                        logger.warn(
                                "stan: error unsubscribing from heartbeats during connection close");
                        logger.debug("Full stack trace: ", e);
                    }
                }

                CloseRequest req = CloseRequest.newBuilder().setClientID(clientId).build();
                logger.trace("CLOSE request: [{}]", req);
                byte[] bytes = req.toByteArray();
                Message reply = null;
                try {
                    reply = nc.request(closeRequests, bytes, opts.getConnectTimeout().toMillis());
                } catch (Exception e) {
                    if (io.nats.client.Constants.ERR_TIMEOUT.equals(e.getMessage())) {
                        throw new TimeoutException(ERR_CLOSE_REQ_TIMEOUT);
                    }
                    throw e;
                }
                logger.trace("CLOSE response: [{}]", reply);
                if (reply.getData() != null) {
                    CloseResponse cr = CloseResponse.parseFrom(reply.getData());

                    if (!cr.getError().isEmpty()) {
                        throw new IOException(cr.getError());
                    }
                }
            } finally {
                if (ncOwned) {
                    try {
                        nc.close();
                    } catch (Exception ignore) {
                        logger.debug("NATS connection was null in close()");
                    }
                }
            } // first finally
        } finally {
            this.unlock();
        }
    }

    protected AckClosure createAckClosure(AckHandler ah, BlockingQueue ch) {
        return new AckClosure(ah, ch);
    }

    protected SubscriptionImpl createSubscription(String subject, String qgroup,
            io.nats.stan.MessageHandler cb, ConnectionImpl conn, SubscriptionOptions opts) {
        SubscriptionImpl sub = new SubscriptionImpl(subject, qgroup, cb, conn, opts);
        return sub;
    }

    protected void processHeartBeat(Message msg) {
        // No payload assumed, just reply.
        io.nats.client.Connection nc;
        this.rLock();
        nc = this.nc;
        this.rUnlock();
        if (nc != null) {
            try {
                nc.publish(msg.getReplyTo(), null);
                logger.debug("Sent heartbeat response");
            } catch (IOException e) {
                logger.warn("stan: error publishing heartbeat response: {}", e.getMessage());
                logger.debug("Full stack trace:", e);
            }
        }
    }

    BlockingQueue createErrorChannel() {
        return new LinkedBlockingQueue();
    }

    // Publish will publish to the cluster and wait for an ACK.
    @Override
    public void publish(String subject, byte[] data) throws IOException {
        final BlockingQueue ch = createErrorChannel();
        publish(subject, data, null, ch);
        String err = null;
        try {
            err = ch.take();
            if (!err.isEmpty()) {
                throw new IOException(err);
            }
        } catch (InterruptedException e) {
            logger.debug("stan: publish interrupted");
            logger.debug("Full stack trace:", e);
            // Thread.currentThread().interrupt();
        }
    }

    /*
     * PublishAsync will publish to the cluster on pubPrefix+subject and asynchronously process the
     * ACK or error state. It will return the GUID for the message being sent.
     */
    @Override
    public String publish(String subject, byte[] data, AckHandler ah) throws IOException {
        return publish(subject, data, ah, null);
    }

    String publish(String subject, byte[] data, AckHandler ah, BlockingQueue ch)
            throws IOException {
        String subj = null;
        String ackSubject = null;
        Duration ackTimeout;
        BlockingQueue pac;
        final AckClosure a;
        final PubMsg pe;
        String guid;
        byte[] bytes;

        a = createAckClosure(ah, ch);
        this.lock();
        try {
            if (getNatsConnection() == null) {
                throw new IllegalStateException(ERR_CONNECTION_CLOSED);
            }

            subj = pubPrefix + "." + subject;
            guid = NUID.nextGlobal();
            PubMsg.Builder pb =
                    PubMsg.newBuilder().setClientID(clientId).setGuid(guid).setSubject(subject);
            if (data != null) {
                pb = pb.setData(ByteString.copyFrom(data));
            }
            pe = pb.build();
            bytes = pe.toByteArray();

            // Map ack to guid
            pubAckMap.put(guid, a);
            // snapshot
            ackSubject = this.ackSubject;
            ackTimeout = opts.getAckTimeout();
            pac = pubAckChan;
        } finally {
            this.unlock();
        }

        // Use the buffered channel to control the number of outstanding acks.
        try {
            pac.put(PubAck.getDefaultInstance());
        } catch (InterruptedException e) {
            logger.warn("Publish operation interrupted", e);
            // Eat this because you can't really do anything with it
            // Thread.currentThread().interrupt();
        }

        try {
            nc.publish(subj, ackSubject, bytes, true);
        } catch (IOException e) {
            removeAck(guid);
            throw (e);
        }

        // Setup the timer for expiration.
        this.lock();
        try {
            a.ackTask = createAckTimerTask(guid);
            ackTimer.schedule(a.ackTask, ackTimeout.toMillis());
        } catch (Exception e) {
            throw e;
        } finally {
            this.unlock();
        }
        return guid;
    }

    @Override
    public Subscription subscribe(String subject, io.nats.stan.MessageHandler cb)
            throws IOException, TimeoutException {
        return subscribe(subject, cb, null);
    }

    @Override
    public Subscription subscribe(String subject, io.nats.stan.MessageHandler cb,
            SubscriptionOptions opts) throws IOException, TimeoutException {
        return subscribe(subject, null, cb, opts);
    }

    @Override
    public Subscription subscribe(String subject, String queue, io.nats.stan.MessageHandler cb)
            throws IOException, TimeoutException {
        return subscribe(subject, queue, cb, null);
    }

    @Override
    public Subscription subscribe(String subject, String queue, io.nats.stan.MessageHandler cb,
            SubscriptionOptions opts) throws IOException, TimeoutException {
        SubscriptionImpl sub = null;
        io.nats.client.Connection nc = null;
        this.lock();
        try {
            if (getNatsConnection() == null) {
                sub = null;
                throw new IllegalStateException(ERR_CONNECTION_CLOSED);
            }
            sub = createSubscription(subject, queue, cb, this, opts);

            // Register subscription.
            subMap.put(sub.inbox, sub);
            nc = getNatsConnection();
        } finally {
            this.unlock();
        }

        // Hold lock throughout.
        sub.wLock();
        try {
            // Listen for actual messages
            io.nats.client.Subscription nsub = nc.subscribe(sub.inbox, this);
            sub.inboxSub = nsub;

            // Create a subscription request
            // FIXME(dlc) add others.
            SubscriptionRequest sr = createSubscriptionRequest(sub);

            Message reply = null;
            try {
                // logger.trace("Sending SubscriptionRequest:\n{}", sr);
                reply = nc.request(subRequests, sr.toByteArray(), 2L, TimeUnit.SECONDS);
            } catch (TimeoutException e) {
                throw new TimeoutException(ConnectionImpl.ERR_TIMEOUT);
            }

            SubscriptionResponse response = null;
            try {
                response = SubscriptionResponse.parseFrom(reply.getData());
            } catch (InvalidProtocolBufferException e) {
                sub.inboxSub.unsubscribe();
                sub.inboxSub = null;
                throw e;
            }
            // logger.trace("Received SubscriptionResponse:\n{}", response);
            if (!response.getError().isEmpty()) {
                sub.inboxSub.unsubscribe();
                throw new IOException(response.getError());
            }
            sub.setAckInbox(response.getAckInbox());
        } finally {
            sub.wUnlock();
        }
        return sub;
    }

    protected SubscriptionRequest createSubscriptionRequest(SubscriptionImpl sub) {
        SubscriptionOptions subOpts = sub.getOptions();
        SubscriptionRequest.Builder srb = SubscriptionRequest.newBuilder();
        String clientId = sub.getConnection().getClientId();
        String queue = sub.getQueue();
        String subject = sub.getSubject();

        srb.setClientID(clientId).setSubject(subject).setQGroup(queue == null ? "" : queue)
                .setInbox(sub.getInbox()).setMaxInFlight(subOpts.getMaxInFlight())
                .setAckWaitInSecs((int) subOpts.getAckWait().getSeconds());

        switch (subOpts.getStartAt()) {
            case First:
                break;
            case LastReceived:
                break;
            case NewOnly:
                break;
            case SequenceStart:
                srb.setStartSequence(subOpts.getStartSequence());
                break;
            case TimeDeltaStart:
                long delta = ChronoUnit.NANOS.between(subOpts.getStartTime(), Instant.now());
                srb.setStartTimeDelta(delta);
                break;
            case UNRECOGNIZED:
            default:
                break;
        }
        srb.setStartPosition(subOpts.getStartAt());

        if (subOpts.getDurableName() != null) {
            srb.setDurableName(subOpts.getDurableName());
        }

        SubscriptionRequest sr = srb.build();
        return sr;
    }

    // Process an ack from the STAN cluster
    protected void processAck(Message msg) {
        PubAck pa = null;
        Exception ex = null;
        try {
            pa = PubAck.parseFrom(msg.getData());
            // logger.trace("Received PubAck:\n{}", pa);
        } catch (InvalidProtocolBufferException e) {
            logger.error("stan: error unmarshaling PubAck");
            logger.debug("Full stack trace: ", e);
            return;
        }

        // Remove
        AckClosure ackClosure = removeAck(pa.getGuid());
        if (ackClosure != null) {
            // Capture error if it exists.
            String ackError = pa.getError();

            if (ackClosure.ah != null) {
                if (!ackError.isEmpty()) {
                    ex = new IOException(ackError);
                }
                // Perform the ackHandler callback
                ackClosure.ah.onAck(pa.getGuid(), ex);
            } else if (ackClosure.ch != null) {
                try {
                    ackClosure.ch.put(ackError);
                } catch (InterruptedException e) {
                    logger.debug("stan: processAck interrupted");
                }
            }
        }
    }

    TimerTask createAckTimerTask(String guid) {
        TimerTask task = new java.util.TimerTask() {
            public void run() {
                processAckTimeout(guid);
            }
        };
        return task;
    }

    protected void processAckTimeout(String guid) {
        AckClosure ackClosure = removeAck(guid);
        if (ackClosure.ah != null) {
            ackClosure.ah.onAck(guid, new TimeoutException(ERR_TIMEOUT));
        } else if (ackClosure.ch != null) {
            if (!ackClosure.ch.offer(ERR_TIMEOUT)) {
                logger.warn("stan: processAckTimeout unable to write timeout error to ack channel");
            }
        }
    }

    protected AckClosure removeAck(String guid) {
        AckClosure ackClosure = null;
        BlockingQueue pac = null;
        TimerTask timerTask = null;
        this.lock();
        try {
            ackClosure = (AckClosure) pubAckMap.get(guid);
            if (ackClosure != null) {
                timerTask = ackClosure.ackTask;
                pubAckMap.remove(guid);
            }
            pac = pubAckChan;
        } finally {
            this.unlock();
        }

        // Cancel timer if needed
        if (timerTask != null) {
            timerTask.cancel();
        }

        // Remove from channel to unblock async publish
        if (ackClosure != null && pac.size() > 0) {
            try {
                // remove from queue to unblock publish
                pac.take();
            } catch (InterruptedException e) {
                logger.warn("stan: interrupted during removeAck for {}", guid);
                logger.debug("Full stack trace:", e);
                // Thread.currentThread().interrupt();
            }
        }

        return ackClosure;
    }

    @Override
    public void onMessage(io.nats.client.Message msg) {
        // For handling inbound NATS messages
        processMsg(msg);
    }

    protected io.nats.stan.Message createStanMessage(MsgProto msgp) {
        return new io.nats.stan.Message(msgp);
    }

    protected void processMsg(io.nats.client.Message raw) {
        io.nats.stan.Message stanMsg = null;
        boolean isClosed = false;
        SubscriptionImpl sub = null;
        io.nats.client.Connection nc = null;

        try {
            // logger.trace("In processMsg, msg = {}", raw);
            MsgProto msgp = MsgProto.parseFrom(raw.getData());
            // logger.trace("processMsg received MsgProto:\n{}", msgp);
            stanMsg = createStanMessage(msgp);
        } catch (InvalidProtocolBufferException e) {
            logger.error("stan: error unmarshaling msg");
            logger.debug("msg: {}", raw);
            logger.debug("full stack trace:", e);
        }

        // Lookup the subscription
        lock();
        try {
            nc = getNatsConnection();
            isClosed = (nc == null);
            sub = (SubscriptionImpl) subMap.get(raw.getSubject());
        } catch (Exception e) {
            throw e;
        } finally {
            unlock();
        }

        // Check if sub is no longer valid or connection has been closed.
        if (sub == null || isClosed) {
            return;
        }

        // Store in msg for backlink
        stanMsg.setSubscription(sub);

        io.nats.stan.MessageHandler cb = null;
        String ackSubject = null;
        boolean isManualAck = false;
        ConnectionImpl subsc = null;

        sub.rLock();
        try {
            cb = sub.getMessageHandler();
            ackSubject = sub.getAckInbox();
            isManualAck = sub.getOptions().isManualAcks();
            subsc = sub.getConnection(); // Can be nil if sub has been unsubscribed
        } catch (Exception e) {
            throw e;
        } finally {
            sub.rUnlock();
        }

        // Perform the callback
        if (cb != null && subsc != null) {
            cb.onMessage(stanMsg);
        }

        // Process auto-ack
        if (!isManualAck) {
            Ack ack = Ack.newBuilder().setSubject(stanMsg.getSubject())
                    .setSequence(stanMsg.getSequence()).build();
            try {
                // logger.trace("processMsg publishing Ack for sequence: {}",
                // stanMsg.getSequence());
                nc.publish(ackSubject, ack.toByteArray());
                // logger.trace("processMsg published Ack:\n{}", ack);
            } catch (IOException e) {
                // FIXME(dlc) - Async error handler? Retry?
                // This really won't happen since the publish is executing in the NATS thread.
                logger.error("Exception while publishing auto-ack: {}", e.getMessage());
                logger.debug("Stack trace: ", e);
            }
        }
    }

    public String getClientId() {
        return this.clientId;
    }

    @Override
    public io.nats.client.Connection getNatsConnection() {
        return this.nc;
    }

    protected void setNatsConnection(io.nats.client.Connection nc) {
        this.nc = nc;
    }

    public String newInbox() {
        return nc.newInbox();
    }

    protected void lock() {
        mu.writeLock().lock();
    }

    protected void unlock() {
        mu.writeLock().unlock();
    }

    protected void rLock() {
        mu.writeLock().lock();
    }

    protected void rUnlock() {
        mu.writeLock().unlock();
    }

    protected io.nats.client.Subscription getAckSubscription() {
        return this.ackSubscription;
    }

    protected io.nats.client.Subscription getHbSubscription() {
        return this.hbSubscription;
    }

    // test injection setter/getters
    void setPubAckChan(BlockingQueue ch) {
        this.pubAckChan = ch;
    }

    BlockingQueue getPubAckChan() {
        return pubAckChan;
    }

    void setPubAckMap(Map map) {
        this.pubAckMap = map;
    }

    Map getPubAckMap() {
        return pubAckMap;
    }

    void setSubMap(Map map) {
        this.subMap = map;
    }

    Map getSubMap() {
        return subMap;
    }

    class AckClosure {
        protected TimerTask ackTask;
        AckHandler ah;
        BlockingQueue ch;

        AckClosure(final AckHandler ah, final BlockingQueue ch) {
            this.ah = ah;
            this.ch = ch;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy