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

io.reactivex.mantis.remote.observable.DynamicConnectionSet Maven / Gradle / Ivy

There is a newer version: 3.1.4
Show newest version
/*
 * Copyright 2019 Netflix, Inc.
 *
 * 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.reactivex.mantis.remote.observable;

import io.mantisrx.common.MantisGroup;
import io.mantisrx.common.metrics.Gauge;
import io.mantisrx.common.metrics.Metrics;
import io.mantisrx.common.network.Endpoint;
import io.reactivex.mantis.remote.observable.reconciliator.ConnectionSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.jctools.queues.SpscArrayQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import rx.Observer;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.functions.Func3;
import rx.observables.GroupedObservable;
import rx.subjects.PublishSubject;


public class DynamicConnectionSet implements ConnectionSet {

    private static final Logger logger = LoggerFactory.getLogger(DynamicConnectionSet.class);
    private static final SpscArrayQueue> inputQueue = new SpscArrayQueue>(1000);
    private static int MIN_TIME_SEC_DEFAULT = 1;
    private static int MAX_TIME_SEC_DEFAULT = 10;
    private EndpointInjector endpointInjector;
    private PublishSubject reconciliatorConnector = PublishSubject.create();
    private Func3, RemoteRxConnection> toObservableFunc;
    private Metrics connectionMetrics;
    private PublishSubject> activeConnectionsSubject = PublishSubject.create();
    private Lock activeConnectionsLock = new ReentrantLock();
    private Map currentActiveConnections = new HashMap<>();
    private int minTimeoutOnUnexpectedTerminateSec;
    private int maxTimeoutOnUnexpectedTerminateSec;
    private Gauge activeConnectionsGauge;
    private Gauge closedConnections;
    private Gauge forceCompletedConnections;
    private Random random = new Random();

    public DynamicConnectionSet(Func3, RemoteRxConnection>
                                        toObservableFunc, int minTimeoutOnUnexpectedTerminateSec, int maxTimeoutOnUnexpectedTerminateSec) {
        this.toObservableFunc = toObservableFunc;
        connectionMetrics = new Metrics.Builder()
                .name("DynamicConnectionSet")
                .addGauge("activeConnections")
                .addGauge("closedConnections")
                .addGauge("forceCompletedConnections")
                .build();

        activeConnectionsGauge = connectionMetrics.getGauge("activeConnections");
        closedConnections = connectionMetrics.getGauge("closedConnections");
        forceCompletedConnections = connectionMetrics.getGauge("forceCompletedConnections");

        this.minTimeoutOnUnexpectedTerminateSec = minTimeoutOnUnexpectedTerminateSec;
        this.maxTimeoutOnUnexpectedTerminateSec = maxTimeoutOnUnexpectedTerminateSec;
    }

    public DynamicConnectionSet(Func3, RemoteRxConnection>
                                        toObservableFunc) {
        this(toObservableFunc, MIN_TIME_SEC_DEFAULT, MAX_TIME_SEC_DEFAULT);
    }

    public static  DynamicConnectionSet> create(
            final ConnectToGroupedObservable.Builder config, int maxTimeBeforeDisconnectSec) {
        Func3, RemoteRxConnection>> toObservableFunc = new
                Func3, RemoteRxConnection>>() {
                    @Override
                    public RemoteRxConnection> call(Endpoint endpoint, Action0 disconnectCallback,
                                                                            PublishSubject closeConnectionTrigger) {
                        // copy config, change host, port and id
                        ConnectToGroupedObservable.Builder configCopy = new ConnectToGroupedObservable.Builder(config);
                        configCopy
                                .host(endpoint.getHost())
                                .port(endpoint.getPort())
                                .closeTrigger(closeConnectionTrigger)
                                .connectionDisconnectCallback(disconnectCallback)
                                .slotId(endpoint.getSlotId());
                        return RemoteObservable.connect(configCopy.build());
                    }
                };
        return new DynamicConnectionSet>(toObservableFunc, MIN_TIME_SEC_DEFAULT, maxTimeBeforeDisconnectSec);
    }

    // NJ
    public static  DynamicConnectionSet> createMGO(
            final ConnectToGroupedObservable.Builder config, int maxTimeBeforeDisconnectSec, final SpscArrayQueue> inputQueue) {
        Func3, RemoteRxConnection>> toObservableFunc
                = new Func3, RemoteRxConnection>>() {
            @Override
            public RemoteRxConnection> call(Endpoint endpoint, Action0 disconnectCallback,
                                                              PublishSubject closeConnectionTrigger) {
                // copy config, change host, port and id
                ConnectToGroupedObservable.Builder configCopy = new ConnectToGroupedObservable.Builder(config);
                configCopy
                        .host(endpoint.getHost())
                        .port(endpoint.getPort())
                        .closeTrigger(closeConnectionTrigger)
                        .connectionDisconnectCallback(disconnectCallback)
                        .slotId(endpoint.getSlotId());
                return RemoteObservable.connectToMGO(configCopy.build(), inputQueue);
            }
        };
        return new DynamicConnectionSet>(toObservableFunc, MIN_TIME_SEC_DEFAULT, maxTimeBeforeDisconnectSec);
    }

    public static  DynamicConnectionSet> create(
            final ConnectToGroupedObservable.Builder config) {
        return create(config, MAX_TIME_SEC_DEFAULT);
    }

    // NJ
    public static  DynamicConnectionSet> createMGO(
            final ConnectToGroupedObservable.Builder config) {
        return createMGO(config, MAX_TIME_SEC_DEFAULT, inputQueue);
    }

    public static  DynamicConnectionSet create(
            final ConnectToObservable.Builder config, int maxTimeBeforeDisconnectSec) {
        Func3, RemoteRxConnection> toObservableFunc = new
                Func3, RemoteRxConnection>() {
                    @Override
                    public RemoteRxConnection call(Endpoint endpoint, Action0 disconnectCallback,
                                                      PublishSubject closeConnectionTrigger) {
                        // copy config, change host, port and id
                        ConnectToObservable.Builder configCopy = new ConnectToObservable.Builder(config);
                        configCopy
                                .host(endpoint.getHost())
                                .port(endpoint.getPort())
                                .closeTrigger(closeConnectionTrigger)
                                .connectionDisconnectCallback(disconnectCallback)
                                .slotId(endpoint.getSlotId());
                        return RemoteObservable.connect(configCopy.build());
                    }
                };
        return new DynamicConnectionSet(toObservableFunc, MIN_TIME_SEC_DEFAULT, maxTimeBeforeDisconnectSec);
    }

    public static  DynamicConnectionSet create(
            final ConnectToObservable.Builder config) {
        return create(config, MAX_TIME_SEC_DEFAULT);
    }

    public void setEndpointInjector(EndpointInjector endpointInjector) {
        this.endpointInjector = endpointInjector;
    }

    public Observer reconciliatorObserver() {
        return reconciliatorConnector;
    }

    public Metrics getConnectionMetrics() {
        return connectionMetrics;
    }

    public Observable> observables() {
        return
                endpointInjector
                        .deltas()
                        .doOnCompleted(() -> logger.info("onComplete on injector deltas"))
                        .doOnError(t -> logger.error("caught unexpected error {}", t.getMessage(), t))
                        .doOnSubscribe(new Action0() {
                            @Override
                            public void call() {
                                logger.info("Subscribing, clearing active connection set");
                                resetActiveConnections();
                            }
                        })
                        .groupBy(t1 -> Endpoint.uniqueHost(t1.getEndpoint().getHost(), t1.getEndpoint().getPort(), t1.getEndpoint().getSlotId()))
                        .flatMap(new Func1, Observable>>() {
                            @Override
                            public Observable> call(
                                    final GroupedObservable group) {

                                final PublishSubject closeConnectionTrigger = PublishSubject.create();
                                return group
                                        .doOnNext(new Action1() {
                                            @Override
                                            public void call(EndpointChange change) {
                                                // side effect to force complete
                                                if (EndpointChange.Type.complete == change.getType() &&
                                                        activeConnectionsContains(group.getKey(), change.getEndpoint())) {
                                                    logger.info("Received complete request, removing connection from active set, " + change.getEndpoint().getHost() +
                                                            " port: " + change.getEndpoint().getPort() + " id: " + change.getEndpoint().getSlotId());
                                                    forceCompletedConnections.increment();
                                                    removeConnection(group.getKey(), change.getEndpoint());
                                                    closeConnectionTrigger.onNext(1);
                                                }
                                            }
                                        })
                                        .filter(new Func1() {
                                            @Override
                                            public Boolean call(EndpointChange change) {
                                                // active connection check is to ensure
                                                // latent adds are not allowed.  This can
                                                // occur if dynamicConnection set
                                                // and reconciliator are chained together.
                                                // Reconciliator will "see" changes
                                                // before dynamic connection set add will
                                                // assume connection is missing from set
                                                boolean contains = activeConnectionsContains(group.getKey(), change.getEndpoint());
                                                if (contains) {
                                                    logger.info("Skipping latent add for endpoint, already in active set: " + change);
                                                }
                                                return EndpointChange.Type.add == change.getType() &&
                                                        !contains;
                                            }
                                        })
                                        .map(new Func1>() {
                                            @Override
                                            public Observable call(final EndpointChange toAdd) {
                                                logger.info("Received add request, adding connection to active set, " + toAdd.getEndpoint().getHost() +
                                                        " port: " + toAdd.getEndpoint().getPort() + ", with client id: " + toAdd.getEndpoint().getSlotId());
                                                addConnection(group.getKey(), toAdd.getEndpoint());
                                                Action0 disconnectCallback = new Action0() {
                                                    @Override
                                                    public void call() {
                                                        int timeToWait = random.nextInt((maxTimeoutOnUnexpectedTerminateSec - minTimeoutOnUnexpectedTerminateSec) + 1)
                                                                + minTimeoutOnUnexpectedTerminateSec;
                                                        logger.info("Connection disconnected, waiting " + timeToWait + " seconds before removing from active set of connections: " + toAdd);
                                                        Observable.timer(timeToWait, TimeUnit.SECONDS)
                                                                .doOnCompleted(new Action0() {
                                                                    @Override
                                                                    public void call() {
                                                                        logger.warn("Removing connection from active set, " + toAdd);
                                                                        closedConnections.increment();
                                                                        removeConnection(group.getKey(), toAdd.getEndpoint());
                                                                    }
                                                                }).subscribe();
                                                    }
                                                };
                                                RemoteRxConnection connection = toObservableFunc.call(toAdd.getEndpoint(), disconnectCallback,
                                                        closeConnectionTrigger);
                                                return connection.getObservable()
                                                        .doOnCompleted(toAdd.getEndpoint().getCompletedCallback())
                                                        .doOnError(toAdd.getEndpoint().getErrorCallback());

                                            }
                                        });
                            }
                        });
    }

    public Observable> activeConnections() {
        return activeConnectionsSubject;
    }

    public boolean activeConnectionsContains(String id, Endpoint endpoint) {
        try {
            activeConnectionsLock.lock();
            return currentActiveConnections.containsKey(id);
        } finally {
            activeConnectionsLock.unlock();
        }
    }

    public void resetActiveConnections() {
        try {
            activeConnectionsLock.lock();
            currentActiveConnections.clear();
            activeConnectionsGauge.set(0);
            activeConnectionsSubject.onNext(new HashSet());
        } finally {
            activeConnectionsLock.unlock();
        }
    }


    public void addConnection(String id, Endpoint toAdd) {
        try {
            activeConnectionsLock.lock();
            if (!currentActiveConnections.containsKey(id)) {
                currentActiveConnections.put(id, new Endpoint(toAdd.getHost(),
                        toAdd.getPort(), toAdd.getSlotId(),
                        toAdd.getCompletedCallback(),
                        toAdd.getErrorCallback()));
                activeConnectionsGauge.increment();
                activeConnectionsSubject.onNext(new HashSet(currentActiveConnections.values()));
            }
        } finally {
            activeConnectionsLock.unlock();
        }
    }

    public void removeConnection(String id, Endpoint toRemove) {
        try {
            activeConnectionsLock.lock();
            if (currentActiveConnections.containsKey(id)) {
                currentActiveConnections.remove(id);
                activeConnectionsGauge.decrement();
                activeConnectionsSubject.onNext(new HashSet(currentActiveConnections.values()));
            }
        } finally {
            activeConnectionsLock.unlock();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy