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

org.redisson.remote.BaseRemoteProxy 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-2024 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.remote;

import io.netty.util.Timeout;
import org.redisson.RedissonBlockingQueue;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RFuture;
import org.redisson.api.RemoteInvocationOptions;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.remote.ResponseEntry.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;

/**
 * 
 * @author Nikita Koksharov
 *
 */
public abstract class BaseRemoteProxy {

    private final Logger log = LoggerFactory.getLogger(getClass());
    
    final CommandAsyncExecutor commandExecutor;
    private final String name;
    final String responseQueueName;
    private final Map responses;
    final Codec codec;
    final String executorId;
    final BaseRemoteService remoteService;

    BaseRemoteProxy(CommandAsyncExecutor commandExecutor, String name, String responseQueueName,
                    Codec codec, String executorId, BaseRemoteService remoteService) {
        super();
        this.commandExecutor = commandExecutor;
        this.name = name;
        this.responseQueueName = responseQueueName;
        this.responses = commandExecutor.getServiceManager().getResponses();
        this.codec = codec;
        this.executorId = executorId;
        this.remoteService = remoteService;
    }

    private final Map, String> requestQueueNameCache = new ConcurrentHashMap<>();
    
    public String getRequestQueueName(Class remoteInterface) {
        return requestQueueNameCache.computeIfAbsent(remoteInterface, k -> "{" + name + ":" + k.getName() + "}");
    }
    
    protected CompletionStage tryPollAckAgainAsync(RemoteInvocationOptions optionsCopy,
                                                                     String ackName, String requestId) {
        RFuture ackClientsFuture = commandExecutor.evalWriteNoRetryAsync(ackName, LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                    "if redis.call('setnx', KEYS[1], 1) == 1 then " 
                        + "redis.call('pexpire', KEYS[1], ARGV[1]);"
                        + "return 0;" 
                    + "end;" 
                    + "redis.call('del', KEYS[1]);" 
                    + "return 1;",
                Arrays.asList(ackName), optionsCopy.getAckTimeoutInMillis());
        return ackClientsFuture.thenCompose(res -> {
            if (res) {
                return pollResponse(commandExecutor.getServiceManager().getConfig().getTimeout(), requestId, true);
            }
            return CompletableFuture.completedFuture(null);
        });
    }

    protected final  CompletableFuture pollResponse(long timeout,
                                                                                         String requestId, boolean insertFirst) {
        CompletableFuture responseFuture = new CompletableFuture();

        ResponseEntry e = responses.compute(responseQueueName, (key, entry) -> {
            if (entry == null) {
                entry = new ResponseEntry();
            }

            addCancelHandling(requestId, responseFuture);

            Timeout responseTimeoutFuture = createResponseTimeout(timeout, requestId, responseFuture);

            Map> entryResponses = entry.getResponses();
            List list = entryResponses.computeIfAbsent(requestId, k -> new ArrayList<>(3));

            Result res = new Result(responseFuture, responseTimeoutFuture);
            if (insertFirst) {
                list.add(0, res);
            } else {
                list.add(res);
            }
            return entry;
        });

        if (e.getStarted().compareAndSet(false, true)) {
            pollResponse();
        }

        return responseFuture;
    }

    private  Timeout createResponseTimeout(long timeout, String requestId, CompletableFuture responseFuture) {
        return commandExecutor.getServiceManager().newTimeout(t -> {
                    responses.computeIfPresent(responseQueueName, (k, entry) -> {
                        RemoteServiceTimeoutException ex = new RemoteServiceTimeoutException("No response after " + timeout + "ms");
                        if (!responseFuture.completeExceptionally(ex)) {
                            return entry;
                        }

                        List list = entry.getResponses().get(requestId);
                        list.remove(0);
                        if (list.isEmpty()) {
                            entry.getResponses().remove(requestId);
                        }
                        if (entry.getResponses().isEmpty()) {
                            return null;
                        }
                        return entry;
                    });
                }, timeout, TimeUnit.MILLISECONDS);
    }

    private  void addCancelHandling(String requestId, CompletableFuture responseFuture) {
        responseFuture.whenComplete((res, ex) -> {
            if (!responseFuture.isCancelled()) {
                return;
            }

            responses.computeIfPresent(responseQueueName, (key, e) -> {
                List list = e.getResponses().get(requestId);
                if (list == null) {
                    return e;
                }

                for (Iterator iterator = list.iterator(); iterator.hasNext();) {
                    Result result = iterator.next();
                    if (result.getPromise() == responseFuture) {
                        result.cancelResponseTimeout();
                        iterator.remove();
                    }
                }
                if (list.isEmpty()) {
                    e.getResponses().remove(requestId);
                }

                if (e.getResponses().isEmpty()) {
                    return null;
                }
                return e;
            });
        });
    }

    private void pollResponse() {
        RBlockingQueue queue = new RedissonBlockingQueue<>(codec, commandExecutor, responseQueueName);
        RFuture future = queue.pollAsync(60, TimeUnit.SECONDS);
        future.whenComplete(createResponseListener());
    }

    private BiConsumer createResponseListener() {
        return (response, e) -> {
            if (e != null) {
                if (commandExecutor.getServiceManager().isShuttingDown(e)) {
                    return;
                }

                log.error("Can't get response from {}. Try to increase 'retryInterval' and/or 'retryAttempts' settings", responseQueueName, e);
                return;
            }

            if (response == null) {
                pollResponse();
                return;
            }

            AtomicReference> future = new AtomicReference<>();
            responses.computeIfPresent(responseQueueName, (k, entry) -> {
                String key = response.getId();
                List list = entry.getResponses().get(key);
                if (list == null) {
                    pollResponse();
                    return null;
                }

                Result res = list.remove(0);
                if (list.isEmpty()) {
                    entry.getResponses().remove(key);
                }

                CompletableFuture f = res.getPromise();
                res.cancelResponseTimeout();
                future.set(f);

                if (entry.getResponses().isEmpty()) {
                    return null;
                }

                pollResponse();
                return entry;
            });

            if (future.get() != null) {
                future.get().complete(response);
            }
        };
    }
    
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy