diff --git a/src/main/java/io/lettuce/core/AbstractRedisClient.java b/src/main/java/io/lettuce/core/AbstractRedisClient.java index 6d68d037ac..212f99d337 100644 --- a/src/main/java/io/lettuce/core/AbstractRedisClient.java +++ b/src/main/java/io/lettuce/core/AbstractRedisClient.java @@ -597,6 +597,10 @@ private CompletableFuture closeClientResources(long quietPeriod, long time } protected RedisHandshake createHandshake(ConnectionState state) { + return createHandshake(state, clientOptions); + } + + protected RedisHandshake createHandshake(ConnectionState state, ClientOptions clientOptions) { EndpointTypeSource source = null; if (clientOptions.getMaintNotificationsConfig().maintNotificationsEnabled()) { LettuceAssert.notNull(clientOptions.getMaintNotificationsConfig().getEndpointTypeSource(), diff --git a/src/main/java/io/lettuce/core/RedisClient.java b/src/main/java/io/lettuce/core/RedisClient.java index 12d0fc3508..305231cf56 100644 --- a/src/main/java/io/lettuce/core/RedisClient.java +++ b/src/main/java/io/lettuce/core/RedisClient.java @@ -97,6 +97,8 @@ public class RedisClient extends AbstractRedisClient { private static final RedisURI EMPTY_URI = new RedisURI(); + private final ThreadLocal clientOptionsThreadLocal = new ThreadLocal<>(); + private final RedisURI redisURI; protected RedisClient(ClientResources clientResources, RedisURI redisURI) { @@ -276,36 +278,42 @@ private ConnectionFuture> connectStandalone logger.debug("Trying to get a Redis connection for: {}", redisURI); - DefaultEndpoint endpoint = createEndpoint(); - RedisChannelWriter writer = endpoint; + ClientOptions clientOptions = getOptions(); + clientOptionsThreadLocal.set(clientOptions); + try { + DefaultEndpoint endpoint = createEndpoint(); + RedisChannelWriter writer = endpoint; - if (CommandExpiryWriter.isSupported(getOptions())) { - writer = CommandExpiryWriter.buildCommandExpiryWriter(writer, getOptions(), getResources()); - } + if (CommandExpiryWriter.isSupported(clientOptions)) { + writer = CommandExpiryWriter.buildCommandExpiryWriter(writer, clientOptions, getResources()); + } - if (CommandListenerWriter.isSupported(getCommandListeners())) { - writer = new CommandListenerWriter(writer, getCommandListeners()); - } + if (CommandListenerWriter.isSupported(getCommandListeners())) { + writer = new CommandListenerWriter(writer, getCommandListeners()); + } - StatefulRedisConnectionImpl connection = newStatefulRedisConnection(writer, endpoint, codec, timeout); + StatefulRedisConnectionImpl connection = newStatefulRedisConnection(writer, endpoint, codec, timeout, + clientOptions); - ClientOptions clientOptions = getOptions(); - ConnectionFuture> future = connectStatefulAsync(connection, endpoint, redisURI, - () -> new CommandHandler(clientOptions, getResources(), endpoint), false); + ConnectionFuture> future = connectStatefulAsync(connection, endpoint, redisURI, + () -> new CommandHandler(clientOptions, getResources(), endpoint), false, clientOptions); - future.whenComplete((channelHandler, throwable) -> { + future.whenComplete((channelHandler, throwable) -> { - if (throwable != null) { - connection.closeAsync(); - } - }); + if (throwable != null) { + connection.closeAsync(); + } + }); - return future; + return future; + } finally { + clientOptionsThreadLocal.remove(); + } } @SuppressWarnings("unchecked") private ConnectionFuture connectStatefulAsync(StatefulRedisConnectionImpl connection, Endpoint endpoint, - RedisURI redisURI, Supplier commandHandlerSupplier, Boolean isPubSub) { + RedisURI redisURI, Supplier commandHandlerSupplier, Boolean isPubSub, ClientOptions clientOptions) { ConnectionBuilder connectionBuilder; if (redisURI.isSsl()) { @@ -319,15 +327,15 @@ private ConnectionFuture connectStatefulAsync(StatefulRedisConnecti ConnectionState state = connection.getConnectionState(); state.apply(redisURI); state.setDb(redisURI.getDatabase()); - connection - .setAuthenticationHandler(createHandler(connection, redisURI.getCredentialsProvider(), isPubSub, getOptions())); + connection.setAuthenticationHandler( + createHandler(connection, redisURI.getCredentialsProvider(), isPubSub, clientOptions)); connectionBuilder.connection(connection); - connectionBuilder.clientOptions(getOptions()); + connectionBuilder.clientOptions(clientOptions); connectionBuilder.clientResources(getResources()); connectionBuilder.commandHandler(commandHandlerSupplier).endpoint(endpoint); connectionBuilder(getSocketAddressSupplier(redisURI), connectionBuilder, connection.getConnectionEvents(), redisURI); - connectionBuilder.connectionInitializer(createHandshake(state)); + connectionBuilder.connectionInitializer(createHandshake(state, clientOptions)); ConnectionFuture> future = initializeChannelAsync(connectionBuilder); @@ -410,29 +418,35 @@ private ConnectionFuture> connectPubS assertNotNull(codec); checkValidRedisURI(redisURI); - PubSubEndpoint endpoint = createPubSubEndpoint(); - RedisChannelWriter writer = endpoint; + ClientOptions clientOptions = getOptions(); + clientOptionsThreadLocal.set(clientOptions); + try { + PubSubEndpoint endpoint = createPubSubEndpoint(); + RedisChannelWriter writer = endpoint; - if (CommandExpiryWriter.isSupported(getOptions())) { - writer = CommandExpiryWriter.buildCommandExpiryWriter(writer, getOptions(), getResources()); - } + if (CommandExpiryWriter.isSupported(clientOptions)) { + writer = CommandExpiryWriter.buildCommandExpiryWriter(writer, clientOptions, getResources()); + } - if (CommandListenerWriter.isSupported(getCommandListeners())) { - writer = new CommandListenerWriter(writer, getCommandListeners()); - } + if (CommandListenerWriter.isSupported(getCommandListeners())) { + writer = new CommandListenerWriter(writer, getCommandListeners()); + } - StatefulRedisPubSubConnectionImpl connection = newStatefulRedisPubSubConnection(endpoint, writer, codec, timeout); + StatefulRedisPubSubConnectionImpl connection = newStatefulRedisPubSubConnection(endpoint, writer, codec, + timeout, clientOptions); - ClientOptions clientOptions = getOptions(); - ConnectionFuture> future = connectStatefulAsync(connection, endpoint, redisURI, - () -> new PubSubCommandHandler<>(clientOptions, getResources(), codec, endpoint), true); + ConnectionFuture> future = connectStatefulAsync(connection, endpoint, redisURI, + () -> new PubSubCommandHandler<>(clientOptions, getResources(), codec, endpoint), true, clientOptions); - return future.whenComplete((conn, throwable) -> { + return future.whenComplete((conn, throwable) -> { - if (throwable != null) { - conn.close(); - } - }); + if (throwable != null) { + conn.close(); + } + }); + } finally { + clientOptionsThreadLocal.remove(); + } } /** @@ -567,52 +581,59 @@ private CompletableFuture> connectS private ConnectionFuture> doConnectSentinelAsync(RedisCodec codec, RedisURI redisURI, Duration timeout, ConnectionMetadata metadata) { - ConnectionBuilder connectionBuilder; - if (redisURI.isSsl()) { - SslConnectionBuilder sslConnectionBuilder = SslConnectionBuilder.sslConnectionBuilder(); - sslConnectionBuilder.ssl(redisURI); - connectionBuilder = sslConnectionBuilder; - } else { - connectionBuilder = ConnectionBuilder.connectionBuilder(); - } - connectionBuilder.clientOptions(ClientOptions.copyOf(getOptions())); - connectionBuilder.clientResources(getResources()); + ClientOptions clientOptions = getOptions(); + clientOptionsThreadLocal.set(clientOptions); + try { + ConnectionBuilder connectionBuilder; + if (redisURI.isSsl()) { + SslConnectionBuilder sslConnectionBuilder = SslConnectionBuilder.sslConnectionBuilder(); + sslConnectionBuilder.ssl(redisURI); + connectionBuilder = sslConnectionBuilder; + } else { + connectionBuilder = ConnectionBuilder.connectionBuilder(); + } + connectionBuilder.clientOptions(ClientOptions.copyOf(clientOptions)); + connectionBuilder.clientResources(getResources()); - DefaultEndpoint endpoint = createEndpoint(); - RedisChannelWriter writer = endpoint; + DefaultEndpoint endpoint = createEndpoint(); + RedisChannelWriter writer = endpoint; - if (CommandExpiryWriter.isSupported(getOptions())) { - writer = CommandExpiryWriter.buildCommandExpiryWriter(writer, getOptions(), getResources()); - } + if (CommandExpiryWriter.isSupported(clientOptions)) { + writer = CommandExpiryWriter.buildCommandExpiryWriter(writer, clientOptions, getResources()); + } - if (CommandListenerWriter.isSupported(getCommandListeners())) { - writer = new CommandListenerWriter(writer, getCommandListeners()); - } + if (CommandListenerWriter.isSupported(getCommandListeners())) { + writer = new CommandListenerWriter(writer, getCommandListeners()); + } - StatefulRedisSentinelConnectionImpl connection = newStatefulRedisSentinelConnection(writer, codec, timeout); - ConnectionState state = connection.getConnectionState(); + StatefulRedisSentinelConnectionImpl connection = newStatefulRedisSentinelConnection(writer, codec, timeout, + clientOptions); + ConnectionState state = connection.getConnectionState(); - state.apply(redisURI); - state.apply(metadata); + state.apply(redisURI); + state.apply(metadata); - connectionBuilder.connectionInitializer(createHandshake(state)); + connectionBuilder.connectionInitializer(createHandshake(state, clientOptions)); - logger.debug("Connecting to Redis Sentinel, address: " + redisURI); + logger.debug("Connecting to Redis Sentinel, address: " + redisURI); - ClientOptions clientOptions = getOptions(); - connectionBuilder.endpoint(endpoint).commandHandler(() -> new CommandHandler(clientOptions, getResources(), endpoint)) - .connection(connection); - connectionBuilder(getSocketAddressSupplier(redisURI), connectionBuilder, connection.getConnectionEvents(), redisURI); + connectionBuilder.endpoint(endpoint) + .commandHandler(() -> new CommandHandler(clientOptions, getResources(), endpoint)).connection(connection); + connectionBuilder(getSocketAddressSupplier(redisURI), connectionBuilder, connection.getConnectionEvents(), + redisURI); - ConnectionFuture sync = initializeChannelAsync(connectionBuilder); + ConnectionFuture sync = initializeChannelAsync(connectionBuilder); - return sync.thenApply(ignore -> (StatefulRedisSentinelConnection) connection).whenComplete((ignore, e) -> { + return sync.thenApply(ignore -> (StatefulRedisSentinelConnection) connection).whenComplete((ignore, e) -> { - if (e != null) { - logger.warn("Cannot connect Redis Sentinel at " + redisURI + ": " + e); - connection.closeAsync(); - } - }); + if (e != null) { + logger.warn("Cannot connect Redis Sentinel at " + redisURI + ": " + e); + connection.closeAsync(); + } + }); + } finally { + clientOptionsThreadLocal.remove(); + } } /** @@ -645,6 +666,25 @@ public void setOptions(ClientOptions clientOptions) { */ protected StatefulRedisPubSubConnectionImpl newStatefulRedisPubSubConnection(PubSubEndpoint endpoint, RedisChannelWriter channelWriter, RedisCodec codec, Duration timeout) { + return newStatefulRedisPubSubConnection(endpoint, channelWriter, codec, timeout, getOptions()); + } + + /** + * Create a new instance of {@link StatefulRedisPubSubConnectionImpl} or a subclass. + *

+ * Subclasses of {@link RedisClient} may override that method. + * + * @param endpoint the endpoint + * @param channelWriter the channel writer + * @param codec codec + * @param timeout default timeout + * @param clientOptions the client options + * @param Key-Type + * @param Value Type + * @return new instance of StatefulRedisPubSubConnectionImpl + */ + protected StatefulRedisPubSubConnectionImpl newStatefulRedisPubSubConnection(PubSubEndpoint endpoint, + RedisChannelWriter channelWriter, RedisCodec codec, Duration timeout, ClientOptions clientOptions) { return new StatefulRedisPubSubConnectionImpl<>(endpoint, channelWriter, codec, timeout); } @@ -662,7 +702,29 @@ protected StatefulRedisPubSubConnectionImpl newStatefulRedisPubSubC */ protected StatefulRedisSentinelConnectionImpl newStatefulRedisSentinelConnection( RedisChannelWriter channelWriter, RedisCodec codec, Duration timeout) { - return new StatefulRedisSentinelConnectionImpl<>(channelWriter, codec, timeout, getOptions().getJsonParser()); + ClientOptions clientOptions = clientOptionsThreadLocal.get(); + if (clientOptions == null) { + clientOptions = getOptions(); + } + return newStatefulRedisSentinelConnection(channelWriter, codec, timeout, clientOptions); + } + + /** + * Create a new instance of {@link StatefulRedisSentinelConnectionImpl} or a subclass. + *

+ * Subclasses of {@link RedisClient} may override that method. + * + * @param channelWriter the channel writer + * @param codec codec + * @param timeout default timeout + * @param clientOptions the client options + * @param Key-Type + * @param Value Type + * @return new instance of StatefulRedisSentinelConnectionImpl + */ + protected StatefulRedisSentinelConnectionImpl newStatefulRedisSentinelConnection( + RedisChannelWriter channelWriter, RedisCodec codec, Duration timeout, ClientOptions clientOptions) { + return new StatefulRedisSentinelConnectionImpl<>(channelWriter, codec, timeout, clientOptions.getJsonParser()); } /** @@ -680,7 +742,30 @@ protected StatefulRedisSentinelConnectionImpl newStatefulRedisSenti */ protected StatefulRedisConnectionImpl newStatefulRedisConnection(RedisChannelWriter channelWriter, PushHandler pushHandler, RedisCodec codec, Duration timeout) { - return new StatefulRedisConnectionImpl<>(channelWriter, pushHandler, codec, timeout, getOptions().getJsonParser()); + ClientOptions clientOptions = clientOptionsThreadLocal.get(); + if (clientOptions == null) { + clientOptions = getOptions(); + } + return newStatefulRedisConnection(channelWriter, pushHandler, codec, timeout, clientOptions); + } + + /** + * Create a new instance of {@link StatefulRedisConnectionImpl} or a subclass. + *

+ * Subclasses of {@link RedisClient} may override that method. + * + * @param channelWriter the channel writer + * @param pushHandler the handler for push notifications + * @param codec codec + * @param timeout default timeout + * @param clientOptions the client options + * @param Key-Type + * @param Value Type + * @return new instance of StatefulRedisConnectionImpl + */ + protected StatefulRedisConnectionImpl newStatefulRedisConnection(RedisChannelWriter channelWriter, + PushHandler pushHandler, RedisCodec codec, Duration timeout, ClientOptions clientOptions) { + return new StatefulRedisConnectionImpl<>(channelWriter, pushHandler, codec, timeout, clientOptions.getJsonParser()); } /**