go-redis
go-redis copied to clipboard
Watched key must cause an error on expiration
Expected Behavior
There must be an error if key was expired during being watched or another way to understand that transaction was aborted, because documentation on Watch said:
It is a command that will make the EXEC conditional: we are asking Redis to perform the transaction only if none of the WATCHed keys were modified. This includes modifications made by the client, like write commands, and by Redis itself, like expiration or eviction. If keys were modified between when they were WATCHed and when the EXEC was received, the entire transaction will be aborted instead.
Current Behavior
Transactions inside pipeline are executing without error and there are no ways to understand that key was expired
Steps to Reproduce
Test function:
func TestWatchOnExpiredKey(t *testing.T) {
expirationTime := time.Millisecond * 100
ctx := context.Background()
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default DB
})
key := "mykey"
err := client.Set(ctx, key, 100, expirationTime).Err()
require.NoError(t, err)
timeStart := time.Now()
errWatch := client.Watch(ctx, func(tx *redis.Tx) error {
val, err := tx.Get(ctx, key).Result()
if err != nil {
t.Fatalf("tx.Get error: %v", err)
}
t.Logf("initial value: %v", val)
_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
// Do a lot of operation to catch the expiration time during transaction
for i := 0; i < 100000; i++ {
pipe.Set(ctx, key, i, redis.KeepTTL)
}
return nil
})
if err != nil {
// Expecting error here
t.Fatalf("tx.Pipelined error: %v", err)
}
return nil
}, key)
// Or expecting error here
if errWatch != nil {
t.Fatalf("client.Watch error: %v", errWatch)
}
// Printing watch operation duration to know that it lasted longer key expiration time
t.Logf("watch duration: %v, expiration time: %v", time.Since(timeStart), expirationTime)
val, err := client.Get(ctx, key).Result()
if err != nil {
t.Fatalf("client.Get error: %v", err)
}
t.Logf("value: %v", val)
}
The output:
initial value: 100
watch duration: 129.049759ms, expiration time: 100ms
client.Get error: redis: nil
Context (Environment)
Redis is used from Docker repository "redis" tag "latest"
redis_version:7.2.1
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:9392225f50acfb06
redis_mode:standalone
os:Linux 6.2.0-33-generic x86_64
arch_bits:64
monotonic_clock:POSIX clock_gettime
multiplexing_api:epoll
atomicvar_api:c11-builtin
gcc_version:12.2.0
process_id:1
process_supervised:no
run_id:a4d6a86dc5e0cd904eca798a63dfa15f666118f9
tcp_port:6379
server_time_usec:1696237905385634
uptime_in_seconds:245576
uptime_in_days:2
hz:10
configured_hz:10
lru_clock:1739089
executable:/data/redis-server
config_file:
io_threads_active:0
listener0:name=tcp,bind=*,bind=-::*,port=6379
Detailed Description
Actually I am looking for a way to not create key if it was expired, for example:
- Receiving request to my server
- Server starts doing operations with Redis
- Read operation and validating that key exist
- Key expired
- Write operation
- Server complete request
Operation 3 should not lead to creating key in Redis
"watch duration: 129.049759ms, expiration time: 100ms"
The judgment of this time interval may not be accurate. You can change 100000 to 200000 or higher and run the test code again. The log is like this:
initial value: 100
tx.Pipelined error: redis: transaction failed