New issue 140 by larsburgess: calling setnx on existing key with expire
returns 1
http://code.google.com/p/redis/issues/detail?id=140
What steps will reproduce the problem?
example with expire:
$ ./redis-cli flushdb; ./redis-cli setnx 'abc' 1; ./redis-cli expire 'abc'
10; ./redis-cli setnx 'abc' 2
OK
(integer) 1
(integer) 1
(integer) 1
without expire:
$ ./redis-cli flushdb; ./redis-cli setnx 'abc' 1; ./redis-cli setnx 'abc' 2
OK
(integer) 1
(integer) 0
What is the expected output? What do you see instead?
Expected output should be: 1, 1, 0 since the key exists when setnx is
called the second time.
What version of the product are you using? On what operating system?
1.3.2 version from github, this tree:
http://github.com/antirez/redis/tree/20f5b3886761a0ba963fee435bba90dd09bd5bd5
Please provide any additional information below.
We are trying to use expire key along with setnx to implement a simple lock
as described here:
http://code.google.com/p/redis/wiki/SetnxCommand
This works in redis 1.001:
$ ./redis-cli flushdb; ./redis-cli setnx 'abc' 1; ./redis-cli expire 'abc'
10; ./redis-cli setnx 'abc' 2
OK
(integer) 1
(integer) 1
(integer) 0
Here is some ruby code which also demonstrates the use case (we initialized
redis ruby client with :thread_safe => true):
def get_lock(dockey,timeout=0)
lock_key = _lock_key(dockey)
until @@db.setnx(lock_key,1) do
sleep(1)
end
@@db.expire(lock_key,timeout+1)
Time.now.to_i+timeout+1
end
def release_lock(dockey,lock)
if (lock >= Time.now.to_i)
@@db.del(_lock_key(dockey))
end
end
When get_lock is called, it loops until setnx returns true (meaning either
the key expired or the thread holding the lock has called
release_lock).
--
You received this message because you are listed in the owner
or CC fields of this issue, or because you starred this issue.
You may adjust your issue notification preferences at:
http://code.google.com/hosting/settings
I use the version 1.2.1, and I found this setnx/expire/setnx bug.
--
You received this message because you are subscribed to the Google Groups "Redis DB" group.
To post to this group, send email to redi...@googlegroups.com.
To unsubscribe from this group, send email to redis-db+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/redis-db?hl=en.
The funny thing is that this is by design.
There's an explicit test for this behavior in test-redis.tcl.
IMHO, the design is broken.
SETNX should never succeed if the key exists.
OK. here's a patch including new tests.
Attachments:
setnxfix.patch 2.3 KB
There is no way to fix this as it's not broken. Please read the EXPIRE
manual page to
understand why this works this way.
Basically every write operation against expiring keys starts deleting the
key, because
otherwise the resulting dataset is time dependent.
Imagine to use the Append Log File with your code + your patch. What
happens? The
AOF is read in very short time so all the keys will probably exist even
with expires
set: you'll end with a different dataset reloading the AOF.
Cheers,
Salvatore
Yes, INCR will try to access the key in read-write, so Redis will
remove the key if it has an expire set. Basically the INCR after the
EXPIRE is like acting against a non existing key.
Btw they can't read your message as there is no forwarding between the
Redis Group and the issue tracker unfortunately (it could be entirely
possible to implement such a feature for Google, and very useful
indeed).
--
Salvatore 'antirez' Sanfilippo
http://invece.org
"Once you have something that grows faster than education grows,
you’re always going to get a pop culture.", Alan Kay
--
You received this message because you are subscribed to the Google Groups "Redis DB" group.
To post to this group, send email to redi...@googlegroups.com.
To unsubscribe from this group, send email to redis-db+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/redis-db?hl=en.
Ah. OK. I understand.
I think it would be helpful to add a note to the nx commands manual pages
which says
that in case of an existing timeout on a key, setnx is equivalent to set.
And maybe close this issue.
Thanks,
Stefan
Hm. I'm no longer sure the reasoning which leads to the current
implementation is
valid. I think it's more a property of the current implementation. Please
correct me
if I'm wrong.
I assume that currently each command is simply written to the log file for
replay
after a crash/replication to a slave. So a sequence
SET x 10
EXPIREAT x 2232131212
SETNX x 4
could not be replayed without simulating time, which seems impossible.
However, it would be possible to write a different sequence to the log,
depending on
the actual effects of executing the setnx.
If the key existed when the setnx was executed, the log file sequence
should contain
the sequence
SET x 10
EXPIREAT x 2232131212
NOEXPIRE x
(assuming the last command clears the timeout value on x).
If the key did not exist, the sequence to the logfile should be
SET x 10
EXPIREAT x 2232131212
SET x 4
In other words: the logfile should never contain setnx commands at all.
This would make the replay/replication time independent.
What do you think?
Wouldn't it be simpler to treat SETNX commands almost like a server side
atomic
GETSET. If the key exists nothing is written to the AOF since nothing
happens to the
internal data structures. If the key does not exist SETNX is translated
into a SET
command and written as such to the AOF. This would solve the expiring time
knowledge issue as well. Since if the key expires within redis it won't
exist and will
get written to AOF as SET "key" value, resulting in the EXPIREAT command
being
removed.
IE:
case when key already exists:
SET x 10 // appears in log
SETNX x 20 // does not appear in log since key already exists
case when key does not exist:
SETNX x 20 // appears in log as SET x 20
alex,
the current semantics of all write operations to a key is to clear the
timeout value.
Your proposal doesn't respect this semantics.
SETNX is not always a write operation however. From the command docs:
"SETNX works exactly like SET with the only difference that if the key
already exists no
operation is performed"
So in my opinion SETNX should not even be considered a write operation
unless it is in
fact setting the key.
yeah, not doing anything at all is also a possible interpretation. But in
this case,
a get should also not clear the timestamp, I think.
Exactly, and that is the case. GETSET commands are not treated as write
command
initially.
Comment #12 on issue 140 by antirez: calling setnx on existing key with
expire returns 1
http://code.google.com/p/redis/issues/detail?id=140
The new behavior allows writes against volatile keys, starting from Redis
2.2 (Redis master) this issue is definitely solved.
Cheers,
Salvatore
Thanks, it works perfectly now!