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 [https://neo4j.com]
 *
 * 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 static java.lang.String.format;
import static java.util.Arrays.asList;
import static org.neo4j.driver.internal.util.LockUtil.executeWithLock;

import java.time.Clock;
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;

public class ClusterRoutingTable implements RoutingTable {
    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.isEmpty()
                        || mode == AccessMode.READ && readers.isEmpty()
                        || mode == AccessMode.WRITE && writers.isEmpty());
    }

    @Override
    public boolean hasBeenStaleFor(long extraTime) {
        var 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) {
        return addresses.stream()
                .filter(address -> !address.equals(addressToSkip))
                .toList();
    }

    private List newWithAddressReplacedIfPresent(
            List addresses, BoltServerAddress oldAddress, BoltServerAddress newAddress) {
        return addresses.stream()
                .map(address -> address.equals(oldAddress) ? newAddress : address)
                .toList();
    }

    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