#1336·predis

Use transaction to run multiple commands atomically, but one of the commands has not been executed

Author: oik-jinnCreated Jul 7, 2023Updated Jul 13, 2023
Labelsbug

Describe the bug I use redis list to do a limiter, it works as expected most times, but recently I found that there are some keys without an expiry.Ideally, I "rpush" the value to the list and set expiry as well in one transaction, and I use "watch" as well before the transaction start.

To Reproduce I haven't reproduced the bug in my local environment, even I use jmeter to request the related api in batch, for example 1 seconds 500 requests

Expected behavior Redis transaction is atomic. Atomic means either all of the commands or none are processed. So one key should have a expiry in my case.

Versions (please complete the following information):

  • Predis: v2.1.2
  • PHP 7.4
  • Redis Server 5.0.10

Code sample

    $redisClient->watch($key);
    $current = $redisClient->llen($key);

    // Transaction start
    $tx = $redisClient->transaction();
    if ($current >= $limitNum) {
        $redisClient->unwatch();
        return false;
    } else {
      
        if ($redisClient->exists($key)) {
            $tx->rpush($key, $now);

            try {
                 $replies = $tx->execute();
                 return true;
            } catch (\Exception $e) {
                return false;
            }
        } else {
            // Using transaction to let rpush and expire to be an atomic operation
            $tx->rpush($key, $now);
            $tx->expire($key, $expiryTime);

            try {
                 $replies = $tx->execute();
                 return true;
            } catch (\Exception $e) {
                return false;
            }
        }
    }