
io.lettuce.core.masterreplica.Connections Maven / Gradle / Ivy
Show all versions of lettuce-core Show documentation
package io.lettuce.core.masterreplica;
import java.time.Duration;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import io.lettuce.core.RedisConnectionException;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.AsyncCloseable;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.codec.StringCodec;
import io.lettuce.core.internal.Futures;
import io.lettuce.core.models.role.RedisNodeDescription;
import io.lettuce.core.output.StatusOutput;
import io.lettuce.core.protocol.Command;
import io.lettuce.core.protocol.CommandArgs;
import io.lettuce.core.protocol.CommandKeyword;
import io.lettuce.core.protocol.CommandType;
/**
* Connection collector with non-blocking synchronization. This synchronizer emits itself through a {@link Mono} as soon as it
* gets synchronized via either receiving connects/exceptions from all connections or timing out.
*
* It can be used only once via {@link #getOrTimeout(Duration, ScheduledExecutorService)}.
*
* Synchronizer uses a gate to determine whether it was already emitted or awaiting incoming events (exceptions, successful
* connects). Connections arriving after closing the gate are discarded.
*
* @author Mark Paluch
*/
class Connections extends CompletableEventLatchSupport>, Connections>
implements AsyncCloseable {
private final Lock lock = new ReentrantLock();
private final Map> connections = new TreeMap<>(
ReplicaUtils.RedisURIComparator.INSTANCE);
private final List exceptions = new CopyOnWriteArrayList<>();
private final List nodes;
private volatile boolean closed = false;
public Connections(int expectedConnectionCount, List nodes) {
super(expectedConnectionCount);
this.nodes = nodes;
}
@Override
protected void onAccept(Tuple2> value) {
if (this.closed) {
value.getT2().closeAsync();
return;
}
try {
lock.lock();
this.connections.put(value.getT1(), value.getT2());
} finally {
lock.unlock();
}
}
@Override
protected void onError(Throwable value) {
this.exceptions.add(value);
}
@Override
protected void onDrop(Tuple2> value) {
value.getT2().closeAsync();
}
@Override
protected void onDrop(Throwable value) {
}
@Override
protected void onEmit(Emission emission) {
if (getExpectedCount() != 0 && this.connections.isEmpty() && !this.exceptions.isEmpty()) {
RedisConnectionException collector = new RedisConnectionException(
"Unable to establish a connection to Redis Master/Replica");
this.exceptions.forEach(collector::addSuppressed);
emission.error(collector);
} else {
emission.success(this);
}
}
/**
* @return {@code true} if no connections present.
*/
public boolean isEmpty() {
try {
lock.lock();
return this.connections.isEmpty();
} finally {
lock.unlock();
}
}
/*
* Initiate {@code PING} on all connections and return the {@link Requests}.
* @return the {@link Requests}.
*/
public Requests requestPing() {
Set>> entries = new LinkedHashSet<>(
this.connections.entrySet());
Requests requests = new Requests(entries.size(), this.nodes);
for (Map.Entry> entry : entries) {
CommandArgs args = new CommandArgs<>(StringCodec.ASCII).add(CommandKeyword.NODES);
Command command = new Command<>(CommandType.PING, new StatusOutput<>(StringCodec.ASCII),
args);
TimedAsyncCommand timedCommand = new TimedAsyncCommand<>(command);
entry.getValue().dispatch(timedCommand);
requests.addRequest(entry.getKey(), timedCommand);
}
return requests;
}
/**
* Close all connections.
*/
public CompletableFuture closeAsync() {
List> close = new ArrayList<>(this.connections.size());
List toRemove = new ArrayList<>(this.connections.size());
this.closed = true;
for (Map.Entry> entry : this.connections.entrySet()) {
toRemove.add(entry.getKey());
close.add(entry.getValue().closeAsync());
}
for (RedisURI redisURI : toRemove) {
this.connections.remove(redisURI);
}
return Futures.allOf(close);
}
}