All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.redisson.command.CommandAsyncService Maven / Gradle / Ivy

Go to download

Easy Redis Java client and Real-Time Data Platform. Valkey compatible. Sync/Async/RxJava3/Reactive API. Client side caching. Over 50 Redis based Java objects and services: JCache API, Apache Tomcat, Hibernate, Spring, Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Scheduler, RPC

There is a newer version: 3.40.2
Show newest version
/**
 * Copyright (c) 2013-2020 Nikita Koksharov
 *
 * 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.redisson.command;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufUtil;
import io.netty.util.ReferenceCountUtil;
import org.redisson.RedissonReference;
import org.redisson.SlotCallback;
import org.redisson.api.RFuture;
import org.redisson.api.RedissonClient;
import org.redisson.api.RedissonReactiveClient;
import org.redisson.api.RedissonRxClient;
import org.redisson.cache.LRUCacheMap;
import org.redisson.client.RedisClient;
import org.redisson.client.RedisException;
import org.redisson.client.RedisRedirectException;
import org.redisson.client.RedisTimeoutException;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.StringCodec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.codec.ReferenceCodecProvider;
import org.redisson.config.Config;
import org.redisson.config.MasterSlaveServersConfig;
import org.redisson.connection.ConnectionManager;
import org.redisson.connection.MasterSlaveEntry;
import org.redisson.connection.NodeSource;
import org.redisson.liveobject.core.RedissonObjectBuilder;
import org.redisson.misc.RPromise;
import org.redisson.misc.RedissonPromise;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.security.MessageDigest;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;

/**
 *
 * @author Nikita Koksharov
 *
 */
public class CommandAsyncService implements CommandAsyncExecutor {

    static final Logger log = LoggerFactory.getLogger(CommandAsyncService.class);

    final ConnectionManager connectionManager;
    protected RedissonObjectBuilder objectBuilder;

    public CommandAsyncService(ConnectionManager connectionManager) {
        this.connectionManager = connectionManager;
    }

    @Override
    public ConnectionManager getConnectionManager() {
        return connectionManager;
    }

    @Override
    public CommandAsyncExecutor enableRedissonReferenceSupport(RedissonClient redisson) {
        enableRedissonReferenceSupport(redisson.getConfig(), redisson, null, null);
        return this;
    }

    @Override
    public CommandAsyncExecutor enableRedissonReferenceSupport(RedissonReactiveClient redissonReactive) {
        enableRedissonReferenceSupport(redissonReactive.getConfig(), null, redissonReactive, null);
        return this;
    }
    
    @Override
    public CommandAsyncExecutor enableRedissonReferenceSupport(RedissonRxClient redissonRx) {
        enableRedissonReferenceSupport(redissonRx.getConfig(), null, null, redissonRx);
        return this;
    }

    private void enableRedissonReferenceSupport(Config config, RedissonClient redisson, RedissonReactiveClient redissonReactive, RedissonRxClient redissonRx) {
        Codec codec = config.getCodec();
        objectBuilder = new RedissonObjectBuilder(config, redisson, redissonReactive, redissonRx);
        ReferenceCodecProvider codecProvider = objectBuilder.getReferenceCodecProvider();
        codecProvider.registerCodec((Class) codec.getClass(), codec);
    }

    private boolean isRedissonReferenceSupportEnabled() {
        return objectBuilder != null;
    }

    @Override
    public void syncSubscription(RFuture future) {
        MasterSlaveServersConfig config = connectionManager.getConfig();
        try {
            int timeout = config.getTimeout() + config.getRetryInterval() * config.getRetryAttempts();
            if (!future.await(timeout)) {
                ((RPromise) future).tryFailure(new RedisTimeoutException("Subscribe timeout: (" + timeout + "ms). Increase 'subscriptionsPerConnection' and/or 'subscriptionConnectionPoolSize' parameters."));
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        future.syncUninterruptibly();
    }

    @Override
    public void syncSubscriptionInterrupted(RFuture future) throws InterruptedException {
        MasterSlaveServersConfig config = connectionManager.getConfig();
        int timeout = config.getTimeout() + config.getRetryInterval() * config.getRetryAttempts();
        if (!future.await(timeout)) {
            ((RPromise) future).tryFailure(new RedisTimeoutException("Subscribe timeout: (" + timeout + "ms). Increase 'subscriptionsPerConnection' and/or 'subscriptionConnectionPoolSize' parameters."));
        }
        future.sync();
    }

    @Override
    public  V get(RFuture future) {
        if (Thread.currentThread().getName().startsWith("redisson-netty")) {
            throw new IllegalStateException("Sync methods can't be invoked from async/rx/reactive listeners");
        }

        try {
            future.await();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RedisException(e);
        }
        if (future.isSuccess()) {
            return future.getNow();
        }

        throw convertException(future);
    }
    
    @Override
    public  V getInterrupted(RFuture future) throws InterruptedException {
        try {
            future.await();
        } catch (InterruptedException e) {
            ((RPromise) future).tryFailure(e);
            throw e;
        }

        if (future.isSuccess()) {
            return future.getNow();
        }

        throw convertException(future);
    }

    protected  RPromise createPromise() {
        return new RedissonPromise();
    }

    @Override
    public  RFuture readAsync(RedisClient client, MasterSlaveEntry entry, Codec codec, RedisCommand command, Object... params) {
        RPromise mainPromise = createPromise();
        async(true, new NodeSource(entry, client), codec, command, params, mainPromise, false);
        return mainPromise;
    }
    
    @Override
    public  RFuture readAsync(RedisClient client, String name, Codec codec, RedisCommand command, Object... params) {
        RPromise mainPromise = createPromise();
        int slot = connectionManager.calcSlot(name);
        async(true, new NodeSource(slot, client), codec, command, params, mainPromise, false);
        return mainPromise;
    }
    
    public  RFuture readAsync(RedisClient client, byte[] key, Codec codec, RedisCommand command, Object... params) {
        RPromise mainPromise = createPromise();
        int slot = connectionManager.calcSlot(key);
        async(true, new NodeSource(slot, client), codec, command, params, mainPromise, false);
        return mainPromise;
    }

    @Override
    public  RFuture readAsync(RedisClient client, Codec codec, RedisCommand command, Object... params) {
        RPromise mainPromise = createPromise();
        async(true, new NodeSource(client), codec, command, params, mainPromise, false);
        return mainPromise;
    }

    @Override
    public  RFuture> readAllAsync(Codec codec, RedisCommand command, Object... params) {
        List results = new ArrayList();
        return readAllAsync(results, codec, command, params);
    }
    
    @Override
    public  RFuture> readAllAsync(RedisCommand command, Object... params) {
        List results = new ArrayList();
        return readAllAsync(results, connectionManager.getCodec(), command, params);
    }
    
    @Override
    public  RFuture> readAllAsync(Collection results, Codec codec, RedisCommand command, Object... params) {
        RPromise> mainPromise = createPromise();
        Collection nodes = connectionManager.getEntrySet();
        AtomicInteger counter = new AtomicInteger(nodes.size());
        BiConsumer listener = new BiConsumer() {
            @Override
            public void accept(Object result, Throwable u) {
                if (u != null && !(u instanceof RedisRedirectException)) {
                    mainPromise.tryFailure(u);
                    return;
                }
                
                if (result instanceof Collection) {
                    synchronized (results) {
                        results.addAll((Collection) result);
                    }
                } else {
                    synchronized (results) {
                        results.add((R) result);
                    }
                }
                
                if (counter.decrementAndGet() == 0
                        && !mainPromise.isDone()) {
                    mainPromise.trySuccess(results);
                }
            }
        };

        for (MasterSlaveEntry entry : nodes) {
            RPromise promise = new RedissonPromise();
            promise.onComplete(listener);
            async(true, new NodeSource(entry), codec, command, params, promise, true);
        }
        return mainPromise;
    }
    
    @Override
    public  RFuture readRandomAsync(Codec codec, RedisCommand command, Object... params) {
        RPromise mainPromise = createPromise();
        List nodes = new ArrayList(connectionManager.getEntrySet());
        Collections.shuffle(nodes);

        retryReadRandomAsync(codec, command, mainPromise, nodes, params);
        return mainPromise;
    }

    @Override
    public  RFuture readRandomAsync(MasterSlaveEntry entry, Codec codec, RedisCommand command, Object... params) {
        RPromise mainPromise = createPromise();
        retryReadRandomAsync(codec, command, mainPromise, Collections.singletonList(entry), params);
        return mainPromise;
    }
    
    private  void retryReadRandomAsync(Codec codec, RedisCommand command, RPromise mainPromise,
            List nodes, Object... params) {
        RPromise attemptPromise = new RedissonPromise();
        attemptPromise.onComplete((res, e) -> {
            if (e == null) {
                if (res == null) {
                    if (nodes.isEmpty()) {
                        mainPromise.trySuccess(null);
                    } else {
                        retryReadRandomAsync(codec, command, mainPromise, nodes, params);
                    }
                } else {
                    mainPromise.trySuccess(res);
                }
            } else {
                mainPromise.tryFailure(e);
            }
        });

        MasterSlaveEntry entry = nodes.remove(0);
        async(true, new NodeSource(entry), codec, command, params, attemptPromise, false);
    }

    @Override
    public  RFuture writeAllAsync(RedisCommand command, Object... params) {
        return writeAllAsync(command, null, params);
    }

    @Override
    public  RFuture writeAllAsync(RedisCommand command, SlotCallback callback, Object... params) {
        return allAsync(false, connectionManager.getCodec(), command, callback, params);
    }

    @Override
    public  RFuture writeAllAsync(Codec codec, RedisCommand command, SlotCallback callback, Object... params) {
        return allAsync(false, codec, command, callback, params);
    }
    
    @Override
    public  RFuture readAllAsync(RedisCommand command, SlotCallback callback, Object... params) {
        return allAsync(true, connectionManager.getCodec(), command, callback, params);
    }

    private  RFuture allAsync(boolean readOnlyMode, Codec codec, RedisCommand command, SlotCallback callback, Object... params) {
        RPromise mainPromise = new RedissonPromise();
        Collection nodes = connectionManager.getEntrySet();
        AtomicInteger counter = new AtomicInteger(nodes.size());
        BiConsumer listener = new BiConsumer() {
            @Override
            public void accept(T result, Throwable u) {
                if (u != null && !(u instanceof RedisRedirectException)) {
                    mainPromise.tryFailure(u);
                    return;
                }
                
                if (u instanceof RedisRedirectException) {
                    result = command.getConvertor().convert(result);
                }
                
                if (callback != null) {
                    callback.onSlotResult(result);
                }
                if (counter.decrementAndGet() == 0) {
                    if (callback != null) {
                        mainPromise.trySuccess(callback.onFinish());
                    } else {
                        mainPromise.trySuccess(null);
                    }
                }
            }
        };

        for (MasterSlaveEntry entry : nodes) {
            RPromise promise = new RedissonPromise();
            promise.onComplete(listener);
            async(readOnlyMode, new NodeSource(entry), codec, command, params, promise, true);
        }
        return mainPromise;
    }

    public  RedisException convertException(RFuture future) {
        if (future.cause() instanceof RedisException) {
            return (RedisException) future.cause();
        }
        return new RedisException("Unexpected exception while processing command", future.cause());
    }

    private NodeSource getNodeSource(String key) {
        int slot = connectionManager.calcSlot(key);
        return new NodeSource(slot);
    }

    private NodeSource getNodeSource(byte[] key) {
        int slot = connectionManager.calcSlot(key);
        return new NodeSource(slot);
    }
    
    @Override
    public  RFuture readAsync(String key, Codec codec, RedisCommand command, Object... params) {
        RPromise mainPromise = createPromise();
        NodeSource source = getNodeSource(key);
        async(true, source, codec, command, params, mainPromise, false);
        return mainPromise;
    }
    
    @Override
    public  RFuture readAsync(byte[] key, Codec codec, RedisCommand command, Object... params) {
        RPromise mainPromise = createPromise();
        NodeSource source = getNodeSource(key);
        async(true, source, codec, command, params, mainPromise, false);
        return mainPromise;
    }

    public  RFuture readAsync(MasterSlaveEntry entry, Codec codec, RedisCommand command, Object... params) {
        RPromise mainPromise = createPromise();
        async(true, new NodeSource(entry), codec, command, params, mainPromise, false);
        return mainPromise;
    }

    @Override
    public  RFuture writeAsync(RedisClient client, Codec codec, RedisCommand command, Object... params) {
        MasterSlaveEntry entry = getConnectionManager().getEntry(client);
        return writeAsync(entry, codec, command, params);
    }

    @Override
    public  RFuture writeAsync(MasterSlaveEntry entry, Codec codec, RedisCommand command, Object... params) {
        RPromise mainPromise = createPromise();
        async(false, new NodeSource(entry), codec, command, params, mainPromise, false);
        return mainPromise;
    }

    @Override
    public  RFuture readAsync(String key, RedisCommand command, Object... params) {
        return readAsync(key, connectionManager.getCodec(), command, params);
    }

    @Override
    public  RFuture evalReadAsync(String key, Codec codec, RedisCommand evalCommandType, String script, List keys, Object... params) {
        NodeSource source = getNodeSource(key);
        return evalAsync(source, true, codec, evalCommandType, script, keys, params);
    }

    @Override
    public  RFuture evalReadAsync(MasterSlaveEntry entry, Codec codec, RedisCommand evalCommandType, String script, List keys, Object... params) {
        return evalAsync(new NodeSource(entry), true, codec, evalCommandType, script, keys, params);
    }

    @Override
    public  RFuture evalReadAsync(RedisClient client, String name, Codec codec, RedisCommand evalCommandType, String script, List keys, Object... params) {
        int slot = connectionManager.calcSlot(name);
        return evalAsync(new NodeSource(slot, client), true, codec, evalCommandType, script, keys, params);
    }

    @Override
    public  RFuture evalWriteAsync(String key, Codec codec, RedisCommand evalCommandType, String script, List keys, Object... params) {
        NodeSource source = getNodeSource(key);
        return evalAsync(source, false, codec, evalCommandType, script, keys, params);
    }

    public  RFuture evalWriteAsync(MasterSlaveEntry entry, Codec codec, RedisCommand evalCommandType, String script, List keys, Object... params) {
        return evalAsync(new NodeSource(entry), false, codec, evalCommandType, script, keys, params);
    }

    @Override
    public  RFuture evalWriteAllAsync(RedisCommand command, SlotCallback callback, String script, List keys, Object... params) {
        return evalAllAsync(false, command, callback, script, keys, params);
    }

    public  RFuture evalAllAsync(boolean readOnlyMode, RedisCommand command, SlotCallback callback, String script, List keys, Object... params) {
        RPromise mainPromise = new RedissonPromise();
        Collection entries = connectionManager.getEntrySet();
        AtomicInteger counter = new AtomicInteger(entries.size());
        BiConsumer listener = new BiConsumer() {
            @Override
            public void accept(T t, Throwable u) {
                if (u != null && !(u instanceof RedisRedirectException)) {
                    mainPromise.tryFailure(u);
                    return;
                }
                
                callback.onSlotResult(t);
                if (counter.decrementAndGet() == 0
                        && !mainPromise.isDone()) {
                    mainPromise.trySuccess(callback.onFinish());
                }
            }
        };

        List args = new ArrayList(2 + keys.size() + params.length);
        args.add(script);
        args.add(keys.size());
        args.addAll(keys);
        args.addAll(Arrays.asList(params));
        for (MasterSlaveEntry entry : entries) {
            RPromise promise = new RedissonPromise();
            promise.onComplete(listener);
            async(readOnlyMode, new NodeSource(entry), connectionManager.getCodec(), command, args.toArray(), promise, true);
        }
        return mainPromise;
    }

    private RFuture loadScript(RedisClient client, String script) {
        MasterSlaveEntry entry = getConnectionManager().getEntry(client);
        if (entry.getClient().equals(client)) {
            return writeAsync(entry, StringCodec.INSTANCE, RedisCommands.SCRIPT_LOAD, script);
        }
        return readAsync(client, StringCodec.INSTANCE, RedisCommands.SCRIPT_LOAD, script);
    }
    
    protected boolean isEvalCacheActive() {
        return getConnectionManager().getCfg().isUseScriptCache();
    }
    
    private static final Map SHA_CACHE = new LRUCacheMap(500, 0, 0);
    
    private String calcSHA(String script) {
        String digest = SHA_CACHE.get(script);
        if (digest == null) {
            try {
                MessageDigest mdigest = MessageDigest.getInstance("SHA-1");
                byte[] s = mdigest.digest(script.getBytes());
                digest = ByteBufUtil.hexDump(s);
                SHA_CACHE.put(script, digest);
            } catch (Exception e) {
                throw new IllegalStateException(e);
            }
        }
        return digest;
    }
    
    private Object[] copy(Object[] params) {
        List result = new ArrayList();
        for (Object object : params) {
            if (object instanceof ByteBuf) {
                ByteBuf b = (ByteBuf) object;
                ByteBuf nb = ByteBufAllocator.DEFAULT.buffer(b.readableBytes());
                int ri = b.readerIndex();
                nb.writeBytes(b);
                b.readerIndex(ri);
                result.add(nb);
            } else {
                result.add(object);
            }
        }
        return result.toArray();
    }
    
    private  RFuture evalAsync(NodeSource nodeSource, boolean readOnlyMode, Codec codec, RedisCommand evalCommandType, String script, List keys, Object... params) {
        if (isEvalCacheActive() && evalCommandType.getName().equals("EVAL")) {
            RPromise mainPromise = new RedissonPromise();
            
            Object[] pps = copy(params);
            
            RPromise promise = new RedissonPromise();
            String sha1 = calcSHA(script);
            RedisCommand cmd = new RedisCommand(evalCommandType, "EVALSHA");
            List args = new ArrayList(2 + keys.size() + params.length);
            args.add(sha1);
            args.add(keys.size());
            args.addAll(keys);
            args.addAll(Arrays.asList(params));

            RedisExecutor executor = new RedisExecutor<>(readOnlyMode, nodeSource, codec, cmd,
                                                        args.toArray(), promise, false, connectionManager, objectBuilder);
            executor.execute();

            promise.onComplete((res, e) -> {
                if (e != null) {
                    if (e.getMessage().startsWith("NOSCRIPT")) {
                        RFuture loadFuture = loadScript(executor.getRedisClient(), script);
                        loadFuture.onComplete((r, ex) -> {
                            if (ex != null) {
                                free(pps);
                                mainPromise.tryFailure(ex);
                                return;
                            }

                            RedisCommand command = new RedisCommand(evalCommandType, "EVALSHA");
                            List newargs = new ArrayList(2 + keys.size() + params.length);
                            newargs.add(sha1);
                            newargs.add(keys.size());
                            newargs.addAll(keys);
                            newargs.addAll(Arrays.asList(pps));

                            NodeSource ns = nodeSource;
                            if (ns.getRedisClient() == null) {
                                ns = new NodeSource(nodeSource, executor.getRedisClient());
                            }

                            async(readOnlyMode, ns, codec, command, newargs.toArray(), mainPromise, false);
                        });
                    } else {
                        free(pps);
                        mainPromise.tryFailure(e);
                    }
                    return;
                }
                free(pps);
                mainPromise.trySuccess(res);
            });
            return mainPromise;
        }
        
        RPromise mainPromise = createPromise();
        List args = new ArrayList(2 + keys.size() + params.length);
        args.add(script);
        args.add(keys.size());
        args.addAll(keys);
        args.addAll(Arrays.asList(params));
        async(readOnlyMode, nodeSource, codec, evalCommandType, args.toArray(), mainPromise, false);
        return mainPromise;
    }

    @Override
    public  RFuture writeAsync(String key, RedisCommand command, Object... params) {
        return writeAsync(key, connectionManager.getCodec(), command, params);
    }

    @Override
    public  RFuture writeAsync(String key, Codec codec, RedisCommand command, Object... params) {
        RPromise mainPromise = createPromise();
        NodeSource source = getNodeSource(key);
        async(false, source, codec, command, params, mainPromise, false);
        return mainPromise;
    }

    public  RFuture writeAsync(byte[] key, Codec codec, RedisCommand command, Object... params) {
        RPromise mainPromise = createPromise();
        NodeSource source = getNodeSource(key);
        async(false, source, codec, command, params, mainPromise, false);
        return mainPromise;
    }
    
    public  void async(boolean readOnlyMode, NodeSource source, Codec codec,
            RedisCommand command, Object[] params, RPromise mainPromise, 
            boolean ignoreRedirect) {
        RedisExecutor executor = new RedisExecutor<>(readOnlyMode, source, codec, command, params, mainPromise, ignoreRedirect, connectionManager, objectBuilder);
        executor.execute();
    }

    private void free(Object[] params) {
        for (Object obj : params) {
            ReferenceCountUtil.safeRelease(obj);
        }
    }
    
    @Override
    public  RFuture readBatchedAsync(Codec codec, RedisCommand command, SlotCallback callback, String... keys) {
        return executeBatchedAsync(true, codec, command, callback, keys);
    }
    
    @Override
    public  RFuture writeBatchedAsync(Codec codec, RedisCommand command, SlotCallback callback, String... keys) {
        return executeBatchedAsync(false, codec, command, callback, keys);
    }
    
    private  RFuture executeBatchedAsync(boolean readOnly, Codec codec, RedisCommand command, SlotCallback callback, String... keys) {
        if (!connectionManager.isClusterMode()) {
            if (readOnly) {
                return readAsync((String) null, codec, command, keys);
            }
            return writeAsync((String) null, codec, command, keys);
        }

        Map> range2key = new HashMap<>();
        for (String key : keys) {
            int slot = connectionManager.calcSlot(key);
            MasterSlaveEntry entry = connectionManager.getEntry(slot);
            List list = range2key.computeIfAbsent(entry, k -> new ArrayList<>());
            list.add(key);
        }

        RPromise result = new RedissonPromise<>();
        AtomicLong executed = new AtomicLong(keys.length);
        AtomicReference failed = new AtomicReference<>();
        BiConsumer listener = (res, ex) -> {
            if (ex != null) {
                failed.set(ex);
            } else {
                if (res != null) {
                    callback.onSlotResult(res);
                }
            }

            if (executed.decrementAndGet() == 0) {
                if (failed.get() != null) {
                    result.tryFailure(failed.get());
                } else {
                    result.trySuccess(callback.onFinish());
                }
            }
        };

        for (Entry> entry : range2key.entrySet()) {
            // executes in batch due to CROSSLOT error
            CommandBatchService executorService;
            if (this instanceof CommandBatchService) {
                executorService = (CommandBatchService) this;
            } else {
                executorService = new CommandBatchService(connectionManager);
            }

            for (String key : entry.getValue()) {
                RedisCommand c = command;
                RedisCommand newCommand = callback.createCommand(key);
                if (newCommand != null) {
                    c = newCommand;
                }
                if (readOnly) {
                    RFuture f = executorService.readAsync(entry.getKey(), codec, c, key);
                    f.onComplete(listener);
                } else {
                    RFuture f = executorService.writeAsync(entry.getKey(), codec, c, key);
                    f.onComplete(listener);
                }
            }

            if (!(this instanceof CommandBatchService)) {
                executorService.executeAsync();
            }
        }

        return result;
    }

    
    @Override
    public RedissonObjectBuilder getObjectBuilder() {
        return objectBuilder;
    }

    @Override
    public ByteBuf encode(Codec codec, Object value) {
        if (isRedissonReferenceSupportEnabled()) {
            RedissonReference reference = getObjectBuilder().toReference(value);
            if (reference != null) {
                value = reference;
            }
        }

        try {
            return codec.getValueEncoder().encode(value);
        } catch (IOException e) {
            throw new IllegalArgumentException(e);
        }
    }

    @Override
    public ByteBuf encodeMapKey(Codec codec, Object value) {
        if (isRedissonReferenceSupportEnabled()) {
            RedissonReference reference = getObjectBuilder().toReference(value);
            if (reference != null) {
                value = reference;
            }
        }

        try {
            return codec.getMapKeyEncoder().encode(value);
        } catch (IOException e) {
            throw new IllegalArgumentException(e);
        }
    }

    @Override
    public ByteBuf encodeMapValue(Codec codec, Object value) {
        if (isRedissonReferenceSupportEnabled()) {
            RedissonReference reference = getObjectBuilder().toReference(value);
            if (reference != null) {
                value = reference;
            }
        }

        try {
            return codec.getMapValueEncoder().encode(value);
        } catch (IOException e) {
            throw new IllegalArgumentException(e);
        }
    }

    @Override
    public  RFuture pollFromAnyAsync(String name, Codec codec, RedisCommand command, long secondsTimeout, String... queueNames) {
        if (connectionManager.isClusterMode() && queueNames.length > 0) {
            RPromise result = new RedissonPromise();
            AtomicReference> ref = new AtomicReference>();
            List names = new ArrayList();
            names.add(name);
            names.addAll(Arrays.asList(queueNames));
            ref.set(names.iterator());
            AtomicLong counter = new AtomicLong(secondsTimeout);
            poll(name, codec, result, ref, names, counter, command);
            return result;
        } else {
            List params = new ArrayList(queueNames.length + 1);
            params.add(name);
            params.addAll(Arrays.asList(queueNames));
            params.add(secondsTimeout);
            return writeAsync(name, codec, command, params.toArray());
        }
    }

    private  void poll(String name, Codec codec, RPromise result, AtomicReference> ref, 
            List names, AtomicLong counter, RedisCommand command) {
        if (ref.get().hasNext()) {
            String currentName = ref.get().next().toString();
            RFuture future = writeAsync(currentName, codec, command, currentName, 1);
            future.onComplete((res, e) -> {
                if (e != null) {
                    result.tryFailure(e);
                    return;
                }
                
                if (res != null) {
                    result.trySuccess(res);
                } else {
                    if (counter.decrementAndGet() == 0) {
                        result.trySuccess(null);
                        return;
                    }
                    poll(name, codec, result, ref, names, counter, command);
                }
            });
        } else {
            ref.set(names.iterator());
            poll(name, codec, result, ref, names, counter, command);
        }
    }
    
}