Skip to content

Lettuce is consistently failing because of full stack after few Redis reconnects #915

@semberal

Description

@semberal

Bug Report

Current Behavior

This issue is a reproducible case from #907 (comment). It is a bit tricky to reproduce, but the following code should do.

Input Code

I create a Lettuce client with queueSize=4, timeout=40ms and then I start 10 threads that are sending GET async commands in a loop. Many calls fail because of timeout or full queue, which is expected, but many calls also succeed. When I close-open Redis connection a few times, there are no more success messages (seems like Lettuce does not even reconnect).

I was able to reproduce it either with local Redis running in Docker (reconnect forced by restarting the container) or remote, port-forwarded Redis running in a Kubernetes pod (reconnect forced by restarting port forwarding).

package test;

import io.lettuce.core.ClientOptions;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.TimeoutOptions;
import io.lettuce.core.api.async.RedisAsyncCommands;
import io.lettuce.core.resource.ClientResources;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Duration;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class LettuceReconnectionTest {

    private final Logger log = LoggerFactory.getLogger(this.getClass());

    @Test
    public void test() throws InterruptedException {

        final RedisURI uri = new RedisURI("127.0.0.1", 6379, Duration.ofSeconds(1));
        final ClientResources clientResources = ClientResources.builder().computationThreadPoolSize(3).ioThreadPoolSize(3).build();
        final RedisClient client = RedisClient.create(clientResources, uri);
        final TimeoutOptions timeoutOptions = TimeoutOptions.enabled(Duration.ofMillis(40));
        client.setOptions(ClientOptions.builder().requestQueueSize(4).autoReconnect(true).cancelCommandsOnReconnectFailure(true).timeoutOptions(timeoutOptions).disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS).build());
        final RedisAsyncCommands<String, String> async = client.connect().async();

        final List<Thread> threads = IntStream.range(0, 10).mapToObj(x -> createThread(async)).collect(Collectors.toList());

        threads.forEach(Thread::start);

        for (final Thread t : threads)
            t.join();
    }

    private Thread createThread(final RedisAsyncCommands<String, String> async) {

        return new Thread(() -> {
            while (true) {
                try {
                    async.get("a").get();
                    log.info("Success!");
                } catch (final Exception ignored) {
                }
            }
        });
    }
}

Expected behavior/code

When Redis is up, I would expect Lettuce to print Success message with similar frequency as in the beginning.

Environment

  • Lettuce version(s): 5.1.2.RELEASE
  • Redis version: 4.0.10

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions