Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jedis 3.10.0 Occasional stack overflow and Connection Leakage Error #4029

Open
guxiaoxie opened this issue Nov 27, 2024 · 4 comments
Open

Jedis 3.10.0 Occasional stack overflow and Connection Leakage Error #4029

guxiaoxie opened this issue Nov 27, 2024 · 4 comments

Comments

@guxiaoxie
Copy link

guxiaoxie commented Nov 27, 2024

Expected behavior

When jedis-3.10.0.jar is used normally, a stack overflow error is reported occasionally.
like:

Caused by: java.lang.StackOverflowError
	..............
	at ******.UdsJedisSocketFactory.createSocket(UdsJedisSocketFactory.java:65) ~[
	at redis.clients.jedis.Connection.connect(Connection.java:226)
	at redis.clients.jedis.BinaryClient.connect(BinaryClient.java:144)
	at redis.clients.jedis.Connection.sendCommand(Connection.java:163)
	at redis.clients.jedis.Connection.sendCommand(Connection.java:154)
	at redis.clients.jedis.BinaryClient.auth(BinaryClient.java:833)
	at redis.clients.jedis.BinaryClient.connect(BinaryClient.java:146)
	at redis.clients.jedis.Connection.sendCommand(Connection.java:163)
	at redis.clients.jedis.Connection.sendCommand(Connection.java:154)
	at redis.clients.jedis.BinaryClient.auth(BinaryClient.java:833)
	at redis.clients.jedis.BinaryClient.connect(BinaryClient.java:146)
	at redis.clients.jedis.Connection.sendCommand(Connection.java:163)
	at redis.clients.jedis.Connection.sendCommand(Connection.java:154)
	at redis.clients.jedis.BinaryClient.auth(BinaryClient.java:833)
	at redis.clients.jedis.BinaryClient.connect(BinaryClient.java:146)
	at redis.clients.jedis.Connection.sendCommand(Connection.java:163)
	at redis.clients.jedis.Connection.sendCommand(Connection.java:154)
	at redis.clients.jedis.BinaryClient.auth(BinaryClient.java:833)
	at redis.clients.jedis.BinaryClient.connect(BinaryClient.java:146)
	at redis.clients.jedis.Connection.sendCommand(Connection.java:163)
	at redis.clients.jedis.Connection.sendCommand(Connection.java:154)
	at redis.clients.jedis.BinaryClient.auth(BinaryClient.java:833)
	at redis.clients.jedis.BinaryClient.connect(BinaryClient.java:146)
	at redis.clients.jedis.Connection.sendCommand(Connection.java:163)
	at redis.clients.jedis.Connection.sendCommand(Connection.java:154)
	at redis.clients.jedis.BinaryClient.auth(BinaryClient.java:833)
	at redis.clients.jedis.BinaryClient.connect(BinaryClient.java:146)
	at redis.clients.jedis.Connection.sendCommand(Connection.java:163)
	at redis.clients.jedis.Connection.sendCommand(Connection.java:154)
	at redis.clients.jedis.BinaryClient.auth(BinaryClient.java:833)
	at redis.clients.jedis.BinaryClient.connect(BinaryClient.java:146)
	at redis.clients.jedis.Connection.sendCommand(Connection.java:163)
	at redis.clients.jedis.Connection.sendCommand(Connection.java:154)
	at redis.clients.jedis.BinaryClient.auth(BinaryClient.java:833)
	at redis.clients.jedis.BinaryClient.connect(BinaryClient.java:146)
	at redis.clients.jedis.Connection.sendCommand(Connection.java:163)
	at redis.clients.jedis.Connection.sendCommand(Connection.java:154)
	at redis.clients.jedis.BinaryClient.auth(BinaryClient.java:833)
	at redis.clients.jedis.BinaryClient.connect(BinaryClient.java:146)
	at redis.clients.jedis.Connection.sendCommand(Connection.java:163)
	at redis.clients.jedis.Connection.sendCommand(Connection.java:154)
	at redis.clients.jedis.BinaryClient.auth(BinaryClient.java:833)
	at redis.clients.jedis.BinaryClient.connect(BinaryClient.java:146)
	at redis.clients.jedis.Connection.sendCommand(Connection.java:163)
	at redis.clients.jedis.Connection.sendCommand(Connection.java:154)

Write here what you're expecting ...

Actual behavior

When a stack overflow error occurs, a soekct connection leak occurs.
The socket object generated by the createSocket() method of JedisSocketFactory cannot be commissioned.

Steps to reproduce:

I wrote a mock test code to reproduce the error.
Stack overflow and Connection Leakage can be completely reproduced.

import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisClientConfig;
import redis.clients.jedis.JedisSocketFactory;
import redis.clients.jedis.exceptions.JedisConnectionException;

import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.stubbing.Answer;

import java.io.IOException;
import java.net.Socket;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

public class RedisNoNameTest {
    public static class MockErrorSocketFactory implements JedisSocketFactory {
        public static AtomicInteger closeCount = new AtomicInteger();

        public static AtomicInteger newCount = new AtomicInteger();

        @Override
        public Socket createSocket() throws JedisConnectionException {

            Socket socket = Mockito.mock(Socket.class);

            newCount.incrementAndGet();

            Mockito.when(socket.isConnected()).thenReturn(false);

            try {
                Mockito.doAnswer((Answer<Void>) invocation -> {
                    closeCount.incrementAndGet();
                    return null; // 返回null,因为doSomething方法没有返回值
                }).when(socket).close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            return socket;
        }

        @Override
        public void updateHostAndPort(HostAndPort hostAndPort) {

        }

        @Override
        public String getDescription() {
            return null;
        }

        @Override
        public String getHost() {
            return null;
        }

        @Override
        public void setHost(String host) {

        }

        @Override
        public int getPort() {
            return 0;
        }

        @Override
        public void setPort(int port) {

        }

        @Override
        public int getConnectionTimeout() {
            return 0;
        }

        @Override
        public void setConnectionTimeout(int connectionTimeout) {

        }

        @Override
        public int getSoTimeout() {
            return 0;
        }

        @Override
        public void setSoTimeout(int soTimeout) {

        }
    }

    public static class MockJedisClientConfig implements JedisClientConfig {
        @Override
        public String getUser() {
            return "test1";
        }

        @Override
        public String getPassword() {
            return "test2";
        }
    }

    @Test
    public void jedis_stack_error_test() throws IOException {
        MockErrorSocketFactory mockErrorSocketFactory = new MockErrorSocketFactory();
        try {
            Jedis jedis = new Jedis(mockErrorSocketFactory, new MockJedisClientConfig());
            jedis.set("1", "2");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        Socket socket = mockErrorSocketFactory.createSocket();
        socket.close();
        System.out.println(MockErrorSocketFactory.newCount);
        System.out.println(MockErrorSocketFactory.closeCount);
    }
}

There is a high probability that this error occurs when the network is abnormal.

Redis / Jedis Configuration

Jedis version: 3.10.0

Redis version:6.2.0

Java version: java1.8

@guxiaoxie guxiaoxie changed the title Jedis 3.10.0 Occasional stack overflow Error Jedis 3.10.0 Occasional stack overflow and Connection Leakage Error Nov 27, 2024
@guxiaoxie
Copy link
Author

My JedisSocketFactory implements is Like this:

import org.newsclub.net.unix.AFUNIXSocketFactory;

class UdsJedisSocketFactory implements JedisSocketFactory {
    private final String udsPath;

    UdsJedisSocketFactory(String udsPath) {
        this.udsPath = udsPath;
    }

    public Socket createSocket() throws IOException, JedisConnectionException {
        AFUNIXSocketFactory.FactoryArg factoryArg = new AFUNIXSocketFactory.FactoryArg(this.udsPath);
            Socket socket = factoryArg.createSocket();
            if (this.socketTimeout.get() > 0) {
                socket.setSoTimeout(this.socketTimeout.get());
            }

            socket.connect(new InetSocketAddress("localhost", 0));
            return socket;
    }

@sazzad16
Copy link
Collaborator

Try an updated Jedis version.

@guxiaoxie
Copy link
Author

@sazzad16 Unfortunately, in my project, I can't upgrade the Jedis version at will. Currently, I can only use Jedis 3.10.0.
I wonder why, in Jedis 3.10.0 Connection.java, making a new Socket connection without forcing the old Socket object to close, would cause a connection leak. In addition, when closing the Jedis object, the connection of the Socket object needs to be checked. If the connection is not detected, the Jedis object is not closed.

@sazzad16
Copy link
Collaborator

@guxiaoxie The last proper development in 3.x branch was done in the year 2021. Now we are almost at 2025. Due to current circumstances, it is difficult for us to invest time in 3.x branch. Thank you for you understanding.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants