python-memcached icon indicating copy to clipboard operation
python-memcached copied to clipboard

Looks like simple CAS setup doesn't work

Open ghost opened this issue 8 years ago • 4 comments

I've tested simple CAS setup, two parallel python processes updating some key on single shared server:

  1. Set the key:
mc.set('k', 0)
  1. Run two processes with that code:
for x in range(10**5):
    while True:
        k = mc.gets('k')
        if mc.cas('k', k+1):
            break
  1. Read the key:
mc.get('k')
# some random number less than 2*10^5 (usually about 1.5*10^5)

The cas() call never failed. Same test, but with cas() replaced by incr() always gives 2*10^5

Python 3.6.3, python-memcached==1.59, everything was inside docker-compose setup

ghost avatar Dec 27 '17 09:12 ghost

You can use ngrep to dump the traffic, and look at STORE and EXISTS responses.

From the protocol "spec":

After sending the command line and the data block the client awaits
the reply, which may be:

- "STORED\r\n", to indicate success.

- "NOT_STORED\r\n" to indicate the data was not stored, but not
because of an error. This normally means that the
condition for an "add" or a "replace" command wasn't met.

- "EXISTS\r\n" to indicate that the item you are trying to store with
a "cas" command has been modified since you last fetched it.

- "NOT_FOUND\r\n" to indicate that the item you are trying to store
with a "cas" command did not exist.

dropwhile avatar Jan 25 '18 20:01 dropwhile

I've tried ngrep and discovered that cas command is actually not used.

Looks like cas_ids are not set for some reason and that causes using set command in place of cas:

In [10]: cl.gets('a')
Out[10]: '2'

In [11]: cl.cas_ids
Out[11]: {}

I can write tests and fixes for cas functionality, if PR's for that subsystem are welcome.

ghost avatar Feb 05 '18 19:02 ghost

Did you ensure that cache_cas=True was set when you initialized the clients? According to this line if that isn't set, the cas_id basically gets ignored.

dropwhile avatar Feb 06 '18 19:02 dropwhile

I've finally got time to debug that

Looks like there are two problems:

  • cas is ignored silently without cache_cas=True
  • cas command checks for 'i' key in cas_ids, but the dictionary has b'i' key, so cas is just ignored silently

After settting cache_cas=True and manually patching cas_ids between gets() and cas() calls I've finally got actual cas command in ngrep output

ghost avatar Mar 05 '18 09:03 ghost