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

io.hekate.messaging.internal.MessagingClient Maven / Gradle / Ivy

There is a newer version: 4.1.3
Show newest version
/*
 * Copyright 2020 The Hekate Project
 *
 * The Hekate Project licenses this file to you 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.hekate.messaging.internal;

import io.hekate.cluster.ClusterNode;
import io.hekate.network.NetworkClient;
import io.hekate.network.NetworkConnector;
import io.hekate.network.NetworkFuture;
import io.hekate.util.format.ToString;
import io.hekate.util.format.ToStringIgnore;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static java.util.Collections.singletonList;

class MessagingClient {
    private static final Logger log = LoggerFactory.getLogger(MessagingClient.class);

    private static final boolean DEBUG = log.isDebugEnabled();

    private static final int STATE_DISCONNECTED = 1;

    private static final int STATE_CONNECTED = 2;

    private static final int STATE_IDLE = 3;

    private static final int STATE_CLOSED = 4;

    private final MessagingGatewayContext ctx;

    private final MessagingConnectionOut conn;

    private final ClusterNode remoteNode;

    private final boolean trackIdle;

    @ToStringIgnore
    private final Object mux = new Object();

    private volatile int state = STATE_DISCONNECTED;

    public MessagingClient(
        ClusterNode remoteNode,
        NetworkConnector net,
        MessagingGatewayContext ctx,
        boolean trackIdle
    ) {
        if (DEBUG) {
            log.debug("Creating new connection [channel={}, node={}]", ctx.channel().name(), remoteNode);
        }

        this.ctx = ctx;
        this.remoteNode = remoteNode;
        this.trackIdle = trackIdle;

        NetworkClient client = net.newClient();

        DefaultMessagingEndpoint endpoint = new DefaultMessagingEndpoint<>(remoteNode.address(), ctx.channel());

        this.conn = new MessagingConnectionOut<>(client, ctx, endpoint, mux, () -> {
            // On internal disconnect:
            synchronized (mux) {
                if (state != STATE_CLOSED) {
                    state = STATE_DISCONNECTED;
                }
            }
        });
    }

    public ClusterNode node() {
        return remoteNode;
    }

    public MessagingConnectionOut connection() {
        ensureConnected();

        return conn;
    }

    public List> close() {
        if (DEBUG) {
            log.debug("Closing connection [channel={}, node={}]", ctx.channel().name(), remoteNode);
        }

        synchronized (mux) {
            // Mark as closed.
            state = STATE_CLOSED;

            return singletonList(conn.disconnect());
        }
    }

    public boolean isConnected() {
        synchronized (mux) {
            return state == STATE_CONNECTED || state == STATE_IDLE;
        }
    }

    public void disconnectIfIdle() {
        if (trackIdle) {
            if (state == STATE_CONNECTED) {
                if (conn.state() == NetworkClient.State.CONNECTED && !conn.hasPendingRequests()) {
                    synchronized (mux) {
                        // Double check with lock.
                        if (state == STATE_CONNECTED) {
                            if (conn.state() == NetworkClient.State.CONNECTED && !conn.hasPendingRequests()) {
                                state = STATE_IDLE;
                            }
                        }
                    }
                }
            } else if (state == STATE_IDLE) {
                synchronized (mux) {
                    // Double check with lock.
                    if (state == STATE_IDLE) {
                        if (conn.hasPendingRequests()) {
                            state = STATE_CONNECTED;
                        } else {
                            if (DEBUG) {
                                log.debug("Disconnecting idle connection [chanel={}, node={}]", ctx.channel().name(), remoteNode);
                            }

                            state = STATE_DISCONNECTED;

                            conn.disconnect();
                        }
                    }
                }
            }
        }
    }

    public void touch() {
        if (trackIdle && state == STATE_IDLE) {
            synchronized (mux) {
                if (state == STATE_IDLE) {
                    state = STATE_CONNECTED;
                }
            }
        }
    }

    private void ensureConnected() {
        if (state != STATE_CONNECTED) {
            synchronized (mux) {
                // Double check with lock.
                if (state == STATE_DISCONNECTED) {
                    if (DEBUG) {
                        log.debug("Initializing connection [chanel={}, node={}]", ctx.channel().name(), remoteNode);
                    }

                    // Important to connect before updating the 'state' flag.
                    // Otherwise the double check logic will be broken and concurrent threads
                    // will try to access the client while it is not connected yet.
                    conn.connect();

                    state = STATE_CONNECTED;
                } else if (state == STATE_IDLE) {
                    state = STATE_CONNECTED;
                }
            }
        }
    }

    @Override
    public String toString() {
        return ToString.format(this);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy