MaintenanceAwareVisitor.java
package redis.clients.jedis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Protocol.Command;
import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisDataException;
import redis.clients.jedis.exceptions.JedisException;
import redis.clients.jedis.util.JedisAsserts;
/**
* Connection-init visitor wiring maintenance notifications, identically for both deployment types:
* performs the {@code CLIENT MAINT_NOTIFICATIONS} handshake, registers the pool's controller as the
* connection's maintenance listener, and installs the controller's relax overlays. What those
* overlays are, whether the connection is tracked, and how events are reacted to is entirely the
* {@link MaintenanceController} subtype's business.
*/
class MaintenanceAwareVisitor implements InitVisitor {
private static final Logger logger = LoggerFactory.getLogger(MaintenanceAwareVisitor.class);
private final Connection.Builder builder;
private final MaintenanceController controller;
MaintenanceAwareVisitor(Connection.Builder builder, MaintenanceController controller) {
JedisAsserts.notNull(builder, "Connection.Builder must not be null");
JedisAsserts.notNull(controller, "MaintenanceController must not be null");
this.builder = builder;
this.controller = controller;
}
/**
* Tracks the connection (before {@code connect()}, so a connect racing a maintenance operation is
* visible to the controller) and installs the controller's relax overlays before the handshake
* runs, so a connection opened while a relax window is open already relaxes its AUTH/HELLO
* handshake reads. The overlays are torn down again in {@link #visitAfterHandshake(Connection)}
* if the feature turns out to be unsupported on this connection.
*/
@Override
public void visitBeforeHandshake(Connection connection) {
MaintenanceNotificationsConfig mConfig = builder.getMaintenanceConfig();
if (!isMaintenanceEnabled(mConfig)) {
return;
}
controller.register(connection);
}
@Override
public void visitAfterHandshake(Connection connection) {
MaintenanceNotificationsConfig mConfig = builder.getMaintenanceConfig();
if (!isMaintenanceEnabled(mConfig)) {
return;
}
boolean strict = mConfig.getMode() == MaintenanceNotificationsConfig.Mode.ENABLED;
boolean maintenanceOn = false;
MaintenanceEventConsumer consumer = null;
try {
// Maintenance push frames require RESP3.
RedisProtocol protocol = connection.getRedisProtocol();
if (protocol != RedisProtocol.RESP3) {
String reason = "RESP3 is required but the established protocol is "
+ (protocol == null ? "RESP2" : protocol);
if (strict) {
throw new JedisConnectionException("Maintenance notifications: " + reason);
}
logger.debug("Maintenance notifications disabled: {}.", reason);
return;
}
// The server must accept CLIENT MAINT_NOTIFICATIONS ON. Pre-register the consumer so a push
// frame the server emits immediately on accepting the subscription cannot race ahead.
consumer = new MaintenanceEventConsumer(connection,
connection.getMaintenanceEventListeners());
connection.addPushConsumer(consumer);
connection.sendCommand(Command.CLIENT, "MAINT_NOTIFICATIONS", "ON", "moving-endpoint-type",
resolveEndpointType(connection, mConfig));
try {
connection.getStatusCodeReply();
maintenanceOn = true;
} catch (JedisDataException e) {
if (strict) {
throw new JedisConnectionException(
"Maintenance notifications: events not supported on server", e);
}
logger.debug(
"Maintenance notifications disabled: server rejected CLIENT MAINT_NOTIFICATIONS ({}).",
e.getMessage());
}
} finally {
if (!maintenanceOn) {
connection.removePushConsumer(consumer);
// Undo the relax overlays installed before the handshake ��� this connection does NOT
// support maintenance notifications.
controller.unregister(connection);
}
}
}
private static boolean isMaintenanceEnabled(MaintenanceNotificationsConfig maintenanceConfig) {
return maintenanceConfig != null
&& maintenanceConfig.getMode() != MaintenanceNotificationsConfig.Mode.DISABLED;
}
/** The {@code moving-endpoint-type} value for this connection. */
private String resolveEndpointType(Connection connection, MaintenanceNotificationsConfig config) {
// TLS as declared by the client config: enabled when either ssl(true) or sslOptions is set,
// mirroring DefaultJedisSocketFactory#createSocket.
JedisClientConfig clientConfig = builder.getClientConfig();
boolean sslEnabled = clientConfig.isSsl() || clientConfig.getSslOptions() != null;
MaintenanceNotificationsConfig.EndpointType endpointType = config.getEndpointTypeResolver()
.getEndpointType(connection.getRemoteSocketAddress(), sslEnabled);
switch (endpointType) {
case INTERNAL_IP:
return "internal-ip";
case INTERNAL_FQDN:
return "internal-fqdn";
case EXTERNAL_IP:
return "external-ip";
case EXTERNAL_FQDN:
return "external-fqdn";
case NONE:
return "none";
default:
throw new JedisException("Unknown endpoint type: " + endpointType);
}
}
}