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

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

There is a newer version: 5.27.0
Show newest version
/*
 * Copyright (c) "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 java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.neo4j.driver.AccessMode;
import org.neo4j.driver.internal.BoltServerAddress;
import org.neo4j.driver.internal.DatabaseName;
import org.neo4j.driver.internal.util.Clock;

import static java.lang.String.format;
import static java.util.Arrays.asList;
import static org.neo4j.driver.internal.util.LockUtil.executeWithLock;

public class ClusterRoutingTable implements RoutingTable
{
    private static final int MIN_ROUTERS = 1;

    private final ReadWriteLock tableLock = new ReentrantReadWriteLock();
    private final DatabaseName databaseName;
    private final Clock clock;
    private final Set disused = new HashSet<>();

    private long expirationTimestamp;
    private boolean preferInitialRouter = true;
    private List readers = Collections.emptyList();
    private List writers = Collections.emptyList();
    private List routers = Collections.emptyList();

    public ClusterRoutingTable( DatabaseName ofDatabase, Clock clock, BoltServerAddress... routingAddresses )
    {
        this( ofDatabase, clock );
        routers = Collections.unmodifiableList( asList( routingAddresses ) );
    }

    private ClusterRoutingTable( DatabaseName ofDatabase, Clock clock )
    {
        this.databaseName = ofDatabase;
        this.clock = clock;
        this.expirationTimestamp = clock.millis() - 1;
    }

    @Override
    public boolean isStaleFor( AccessMode mode )
    {
        return executeWithLock( tableLock.readLock(), () ->
                expirationTimestamp < clock.millis() ||
                routers.size() < MIN_ROUTERS ||
                mode == AccessMode.READ && readers.size() == 0 ||
                mode == AccessMode.WRITE && writers.size() == 0 );
    }

    @Override
    public boolean hasBeenStaleFor( long extraTime )
    {
        long totalTime = executeWithLock( tableLock.readLock(), () -> expirationTimestamp ) + extraTime;
        if ( totalTime < 0 )
        {
            totalTime = Long.MAX_VALUE;
        }
        return totalTime < clock.millis();
    }

    @Override
    public void update( ClusterComposition cluster )
    {
        executeWithLock( tableLock.writeLock(), () ->
        {
            expirationTimestamp = cluster.expirationTimestamp();
            readers = newWithReusedAddresses( readers, disused, cluster.readers() );
            writers = newWithReusedAddresses( writers, disused, cluster.writers() );
            routers = newWithReusedAddresses( routers, disused, cluster.routers() );
            disused.clear();
            preferInitialRouter = !cluster.hasWriters();
        } );
    }

    @Override
    public void forget( BoltServerAddress address )
    {
        executeWithLock( tableLock.writeLock(), () ->
        {
            routers = newWithoutAddressIfPresent( routers, address );
            readers = newWithoutAddressIfPresent( readers, address );
            writers = newWithoutAddressIfPresent( writers, address );
            disused.add( address );
        } );
    }

    @Override
    public List readers()
    {
        return executeWithLock( tableLock.readLock(), () -> readers );
    }

    @Override
    public List writers()
    {
        return executeWithLock( tableLock.readLock(), () -> writers );
    }

    @Override
    public List routers()
    {
        return executeWithLock( tableLock.readLock(), () -> routers );
    }

    @Override
    public Set servers()
    {
        return executeWithLock( tableLock.readLock(), () ->
        {
            Set servers = new HashSet<>();
            servers.addAll( readers );
            servers.addAll( writers );
            servers.addAll( routers );
            servers.addAll( disused );
            return servers;
        } );
    }

    @Override
    public DatabaseName database()
    {
        return databaseName;
    }

    @Override
    public void forgetWriter( BoltServerAddress toRemove )
    {
        executeWithLock( tableLock.writeLock(), () ->
        {
            writers = newWithoutAddressIfPresent( writers, toRemove );
            disused.add( toRemove );
        } );
    }

    @Override
    public void replaceRouterIfPresent( BoltServerAddress oldRouter, BoltServerAddress newRouter )
    {
        executeWithLock( tableLock.writeLock(), () -> routers = newWithAddressReplacedIfPresent( routers, oldRouter, newRouter ) );
    }

    @Override
    public boolean preferInitialRouter()
    {
        return executeWithLock( tableLock.readLock(), () -> preferInitialRouter );
    }

    @Override
    public long expirationTimestamp()
    {
        return executeWithLock( tableLock.readLock(), () -> expirationTimestamp );
    }

    @Override
    public String toString()
    {
        return executeWithLock( tableLock.readLock(), () ->
                format( "Ttl %s, currentTime %s, routers %s, writers %s, readers %s, database '%s'",
                        expirationTimestamp, clock.millis(), routers, writers, readers, databaseName.description() ) );
    }

    private List newWithoutAddressIfPresent( List addresses, BoltServerAddress addressToSkip )
    {
        List newList = new ArrayList<>( addresses.size() );
        for ( BoltServerAddress address : addresses )
        {
            if ( !address.equals( addressToSkip ) )
            {
                newList.add( address );
            }
        }
        return Collections.unmodifiableList( newList );
    }

    private List newWithAddressReplacedIfPresent( List addresses, BoltServerAddress oldAddress,
                                                                     BoltServerAddress newAddress )
    {
        List newList = new ArrayList<>( addresses.size() );
        for ( BoltServerAddress address : addresses )
        {
            newList.add( address.equals( oldAddress ) ? newAddress : address );
        }
        return Collections.unmodifiableList( newList );
    }

    private List newWithReusedAddresses( List currentAddresses, Set disusedAddresses,
                                                            Set newAddresses )
    {
        List newList = Stream.concat( currentAddresses.stream(), disusedAddresses.stream() )
                                                .filter( address -> newAddresses.remove( toBoltServerAddress( address ) ) )
                                                .collect( Collectors.toCollection( () -> new ArrayList<>( newAddresses.size() ) ) );
        newList.addAll( newAddresses );
        return Collections.unmodifiableList( newList );
    }

    private BoltServerAddress toBoltServerAddress( BoltServerAddress address )
    {
        return BoltServerAddress.class.equals( address.getClass() ) ? address : new BoltServerAddress( address.host(), address.port() );
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy