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

org.neo4j.driver.internal.cluster.Rediscovery Maven / Gradle / Ivy

There is a newer version: 5.27.0
Show newest version
/*
 * Copyright (c) 2002-2018 "Neo4j,"
 * Neo4j Sweden AB [http://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * 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 org.neo4j.driver.internal.cluster;

import io.netty.util.concurrent.EventExecutorGroup;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;

import org.neo4j.driver.internal.BoltServerAddress;
import org.neo4j.driver.internal.spi.Connection;
import org.neo4j.driver.internal.spi.ConnectionPool;
import org.neo4j.driver.internal.util.Futures;
import org.neo4j.driver.v1.Logger;
import org.neo4j.driver.v1.exceptions.SecurityException;
import org.neo4j.driver.v1.exceptions.ServiceUnavailableException;
import org.neo4j.driver.v1.net.ServerAddressResolver;

import static java.lang.String.format;
import static java.util.Collections.emptySet;
import static java.util.Collections.singletonList;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static java.util.stream.Collectors.toList;
import static org.neo4j.driver.internal.util.Futures.completedWithNull;

public class Rediscovery
{
    private static final String NO_ROUTERS_AVAILABLE = "Could not perform discovery. No routing servers available.";

    private final BoltServerAddress initialRouter;
    private final RoutingSettings settings;
    private final Logger logger;
    private final ClusterCompositionProvider provider;
    private final ServerAddressResolver resolver;
    private final EventExecutorGroup eventExecutorGroup;

    private volatile boolean useInitialRouter;

    public Rediscovery( BoltServerAddress initialRouter, RoutingSettings settings, ClusterCompositionProvider provider,
            EventExecutorGroup eventExecutorGroup, ServerAddressResolver resolver, Logger logger )
    {
        this( initialRouter, settings, provider, resolver, eventExecutorGroup, logger, true );
    }

    // Test-only constructor
    Rediscovery( BoltServerAddress initialRouter, RoutingSettings settings, ClusterCompositionProvider provider,
            ServerAddressResolver resolver, EventExecutorGroup eventExecutorGroup, Logger logger, boolean useInitialRouter )
    {
        this.initialRouter = initialRouter;
        this.settings = settings;
        this.logger = logger;
        this.provider = provider;
        this.resolver = resolver;
        this.eventExecutorGroup = eventExecutorGroup;
        this.useInitialRouter = useInitialRouter;
    }

    /**
     * Given the current routing table and connection pool, use the connection composition provider to fetch a new
     * cluster composition, which would be used to update the routing table and connection pool.
     *
     * @param routingTable current routing table.
     * @param connectionPool connection pool.
     * @return new cluster composition.
     */
    public CompletionStage lookupClusterComposition( RoutingTable routingTable,
            ConnectionPool connectionPool )
    {
        CompletableFuture result = new CompletableFuture<>();
        lookupClusterComposition( routingTable, connectionPool, 0, 0, result );
        return result;
    }

    private void lookupClusterComposition( RoutingTable routingTable, ConnectionPool pool,
            int failures, long previousDelay, CompletableFuture result )
    {
        lookup( routingTable, pool ).whenComplete( ( composition, completionError ) ->
        {
            Throwable error = Futures.completionExceptionCause( completionError );
            if ( error != null )
            {
                result.completeExceptionally( error );
            }
            else if ( composition != null )
            {
                result.complete( composition );
            }
            else
            {
                int newFailures = failures + 1;
                if ( newFailures >= settings.maxRoutingFailures() )
                {
                    result.completeExceptionally( new ServiceUnavailableException( NO_ROUTERS_AVAILABLE ) );
                }
                else
                {
                    long nextDelay = Math.max( settings.retryTimeoutDelay(), previousDelay * 2 );
                    logger.info( "Unable to fetch new routing table, will try again in " + nextDelay + "ms" );
                    eventExecutorGroup.next().schedule(
                            () -> lookupClusterComposition( routingTable, pool, newFailures, nextDelay, result ),
                            nextDelay, TimeUnit.MILLISECONDS
                    );
                }
            }
        } );
    }

    private CompletionStage lookup( RoutingTable routingTable, ConnectionPool connectionPool )
    {
        CompletionStage compositionStage;

        if ( useInitialRouter )
        {
            compositionStage = lookupOnInitialRouterThenOnKnownRouters( routingTable, connectionPool );
            useInitialRouter = false;
        }
        else
        {
            compositionStage = lookupOnKnownRoutersThenOnInitialRouter( routingTable, connectionPool );
        }

        return compositionStage.whenComplete( ( composition, error ) ->
        {
            if ( composition != null && !composition.hasWriters() )
            {
                useInitialRouter = true;
            }
        } );
    }

    private CompletionStage lookupOnKnownRoutersThenOnInitialRouter( RoutingTable routingTable,
            ConnectionPool connectionPool )
    {
        Set seenServers = new HashSet<>();
        return lookupOnKnownRouters( routingTable, connectionPool, seenServers ).thenCompose( composition ->
        {
            if ( composition != null )
            {
                return completedFuture( composition );
            }
            return lookupOnInitialRouter( routingTable, connectionPool, seenServers );
        } );
    }

    private CompletionStage lookupOnInitialRouterThenOnKnownRouters( RoutingTable routingTable,
            ConnectionPool connectionPool )
    {
        Set seenServers = emptySet();
        return lookupOnInitialRouter( routingTable, connectionPool, seenServers ).thenCompose( composition ->
        {
            if ( composition != null )
            {
                return completedFuture( composition );
            }
            return lookupOnKnownRouters( routingTable, connectionPool, new HashSet<>() );
        } );
    }

    private CompletionStage lookupOnKnownRouters( RoutingTable routingTable,
            ConnectionPool connectionPool, Set seenServers )
    {
        BoltServerAddress[] addresses = routingTable.routers().toArray();

        CompletableFuture result = completedWithNull();
        for ( BoltServerAddress address : addresses )
        {
            result = result.thenCompose( composition ->
            {
                if ( composition != null )
                {
                    return completedFuture( composition );
                }
                else
                {
                    return lookupOnRouter( address, routingTable, connectionPool )
                            .whenComplete( ( ignore, error ) -> seenServers.add( address ) );
                }
            } );
        }
        return result;
    }

    private CompletionStage lookupOnInitialRouter( RoutingTable routingTable,
            ConnectionPool connectionPool, Set seenServers )
    {
        List addresses = resolve( initialRouter );
        addresses.removeAll( seenServers );

        CompletableFuture result = completedWithNull();
        for ( BoltServerAddress address : addresses )
        {
            result = result.thenCompose( composition ->
            {
                if ( composition != null )
                {
                    return completedFuture( composition );
                }
                return lookupOnRouter( address, routingTable, connectionPool );
            } );
        }
        return result;
    }

    private CompletionStage lookupOnRouter( BoltServerAddress routerAddress,
            RoutingTable routingTable, ConnectionPool connectionPool )
    {
        CompletionStage connectionStage = connectionPool.acquire( routerAddress );

        return provider.getClusterComposition( connectionStage ).handle( ( response, error ) ->
        {
            Throwable cause = Futures.completionExceptionCause( error );
            if ( cause != null )
            {
                return handleRoutingProcedureError( cause, routingTable, routerAddress );
            }
            else
            {
                return response.clusterComposition();
            }
        } );
    }

    private ClusterComposition handleRoutingProcedureError( Throwable error, RoutingTable routingTable,
            BoltServerAddress routerAddress )
    {
        if ( error instanceof SecurityException )
        {
            // auth error happened, terminate the discovery procedure immediately
            throw new CompletionException( error );
        }
        else
        {
            // connection turned out to be broken
            logger.error( format( "Failed to connect to routing server '%s'.", routerAddress ), error );
            routingTable.forget( routerAddress );
            return null;
        }
    }

    private List resolve( BoltServerAddress address )
    {
        try
        {
            return resolver.resolve( address )
                    .stream()
                    .map( BoltServerAddress::from )
                    .collect( toList() ); // collect to list to preserve the order
        }
        catch ( Throwable error )
        {
            logger.error( "Resolver function failed to resolve '" + address + "'. The address will be used as is", error );
            return singletonList( address );
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy