PoolMaintenance.java
package redis.clients.jedis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Everything one maintenance family contributes to the {@link ConnectionPool} hosting it, assembled
* where the family is known so the pool itself never asks which family it serves. The pool runs the
* kit in two phases around its own construction: {@link #configure} shapes the connection factory
* before the pool exists (the factory is built inside {@code super(...)}), and {@link #attach}
* installs the pool-side reactions once it does. Both are no-ops here; a family overrides only what
* it needs. The kit owns the controller's lifetime on the pool's behalf: {@link #close()} is the
* pool's last step in {@code destroy()}.
*/
abstract class PoolMaintenance implements AutoCloseable {
private static final Logger log = LoggerFactory.getLogger(PoolMaintenance.class);
/** Maintenance off: no controller, nothing to configure, nothing to attach. */
static final PoolMaintenance OFF = new PoolMaintenance(null) {
};
private final MaintenanceController controller; // null = maintenance off
private PoolMaintenance(MaintenanceController controller) {
this.controller = controller;
}
/**
* The standalone/enterprise family: MOVING remaps new connections to the announced endpoint and
* retires the affected ones, so the pool must recycle retired connections and evict the retired
* idles once a handoff has been processed. {@code null} or disabled config yields {@link #OFF}.
*/
static PoolMaintenance standalone(MaintenanceNotificationsConfig config) {
if (config == null || !config.isEnabledOrAuto()) {
return OFF;
}
return new StandalonePoolMaintenance(MaintenanceEventController.from(config));
}
/**
* The cluster family: SMIGRATING/SMIGRATED are dispatched to the client-wide coordinator; nothing
* is remapped or retired per pool ��� a source node that leaves the cluster has its whole pool
* dropped by {@link JedisClusterInfoCache} instead. {@code null} coordinator yields {@link #OFF}.
*/
static PoolMaintenance cluster(ClusterMaintenanceCoordinator coordinator) {
if (coordinator == null) {
return OFF;
}
return new ClusterPoolMaintenance(new ClusterMaintenanceController(coordinator));
}
/** The connection-facing controller ({@code null} when off); exposed for test clock injection. */
MaintenanceController controller() {
return controller;
}
/** Phase 1, before the pool exists: wire the controller and any family-specific factory knobs. */
ConnectionFactory.Builder configure(ConnectionFactory.Builder factoryBuilder) {
return factoryBuilder;
}
/** Phase 2, once the pool exists: install the family's pool-side reactions. */
void attach(ConnectionPool pool) {
}
@Override
public void close() {
if (controller != null) {
controller.close();
}
}
/**
* Cluster pools need only the controller on the factory: it installs the handshake visitor on
* each new connection, which is where the pool-wide relax gate and the event dispatch are wired.
* Nothing is remapped or retired per pool, so there is no pool-side reaction to attach.
*/
private static final class ClusterPoolMaintenance extends PoolMaintenance {
private final ClusterMaintenanceController controller;
ClusterPoolMaintenance(ClusterMaintenanceController controller) {
super(controller);
this.controller = controller;
}
@Override
ConnectionFactory.Builder configure(ConnectionFactory.Builder factoryBuilder) {
return factoryBuilder.maintenanceController(controller);
}
}
/** Standalone pools remap new connections during MOVING and recycle the retired ones. */
private static final class StandalonePoolMaintenance extends PoolMaintenance {
private final MaintenanceEventController controller;
StandalonePoolMaintenance(MaintenanceEventController controller) {
super(controller);
this.controller = controller;
}
@Override
ConnectionFactory.Builder configure(ConnectionFactory.Builder factoryBuilder) {
return factoryBuilder.maintenanceController(controller).socketAddressMapper(controller);
}
@Override
void attach(ConnectionPool pool) {
// evictor passes destroy retired idles ahead of the delegate policy
pool.setEvictionPolicy(new RebindAwareEvictionPolicy(pool.getEvictionPolicy()));
// handoff processed: evict the retired idles right away
controller.setHandoffHook(() -> evictQuietly(pool));
}
/**
* Evicts retired idles once a handoff has been processed. Runs on the maintenance scheduler
* thread or inline on a notifying thread; must never propagate (a failed pass degrades to lazy
* recycling on return).
*/
private void evictQuietly(ConnectionPool pool) {
if (pool.isClosed()) {
return;
}
try {
pool.evict();
} catch (Exception e) {
log.warn("Maintenance eviction pass failed; retired connections recycle on return", e);
}
}
}
}