wg / lettuce

Scalable Java Redis client

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Intermittent "ERR EXEC without MULTI" errors in Watch/Multi/Exec scenario

jencompgeek opened this issue · comments

If I run the following test, maybe 4 out of 5 times it fails with "java.lang.RuntimeException: ERR EXEC without MULTI" when running against Redis 2.4.

Against Redis 2.6, I get these failures intermittently also, as well as occasional assertion failures against the result of exec (sometimes it's an empty list and sometimes it contains "OK").

Perhaps I am doing something wrong? Seems to work OK if I eliminate the set done by the 2nd connection.

import java.util.List;
import java.util.concurrent.Future;
import static org.junit.Assert.assertTrue;
import org.junit.Test;

import com.lambdaworks.redis.RedisAsyncConnection;
import com.lambdaworks.redis.RedisClient;
import com.lambdaworks.redis.RedisConnection;
import com.lambdaworks.redis.protocol.Command;

public class LettuceTest {

@Test
public void testWatch() throws Exception {
    RedisClient client = new RedisClient("localhost", 6379);
    RedisAsyncConnection<byte[], byte[]> conn1 = client.connectAsync(LettuceUtils.CODEC);
    RedisAsyncConnection<byte[], byte[]> conn2 = client.connectAsync(LettuceUtils.CODEC);
    RedisConnection<byte[],byte[]> syncConn = new com.lambdaworks.redis.RedisConnection<byte[], byte[]>(conn2);

    // Watch with conn 1
    Future<String> watch = conn1.watch("testitnow".getBytes());

    // Synchronously set the value with conn 2
    syncConn.set("testitnow".getBytes(), "something".getBytes());

    // Start conn 1 tx
    Future<String> mul = conn1.multi();

    // Attempt to change watched variable value
    Future<String> set = conn1.set("testitnow".getBytes(), "somethingelse".getBytes());

    // Exec tx
    Future<List<Object>> f = conn1.exec();
    List<Object> results = f.get();

    // Results should be empty since watched var modified by other connection
    System.out.println(results);
    assertTrue(results.isEmpty());

    Command<?,?,?>[] ppline = new Command<?,?,?>[] {(Command<?,?,?>)watch, (Command<?,?,?>)mul, (Command<?,?,?>)set};
    conn1.awaitAll(ppline);
    for (Command<?, ?, ?> cmd : ppline) {
        if (cmd.getOutput().hasError()) {
            // Processing the "set" future often results in "ERR EXEC without MULTI" here
            throw new RuntimeException(cmd.getOutput().getError());
        }else {
            System.out.println(cmd.getOutput().get());
        }
    }
}
}

Thank you for the issue report and test case! Apologies for the delay, this one was a bit tricky to track down.

Calling get() on the WATCH future prior to sending the first SET is necessary to ensure correct temporal ordering of the commands. Also you should use RedisClient.connect(RedisCodec) to get a syncronous connection rather than instantiating a RedisConnection by hand.

With that done the bug is 100% reproducable and is an internal failure in lettuce that causes a reconnection. I've fixed it and released version 2.3.2 to maven central.