Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

python-memcached return wrong value after crash?? #166

Open
samsonle1809 opened this issue Aug 31, 2019 · 2 comments
Open

python-memcached return wrong value after crash?? #166

samsonle1809 opened this issue Aug 31, 2019 · 2 comments

Comments

@samsonle1809
Copy link

samsonle1809 commented Aug 31, 2019

I'm getting the following issue on trying to run this command on IPython. I see python-memcached return wrong value after crash.

Environment

  • MacOS
  • Python 3.7.4
  • IPython 7.7.0
  • python-memcached 1.59

Test scenario:
Run IPython and enter the following commands

bash-3.2$ ipython
Python 3.7.4 (default, Jul  9 2019, 18:13:23)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.7.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from memcache import Client as Memcache
   ...: CACHE = Memcache(['127.0.0.1:11211'], socket_timeout=0.5)

In [2]: exit
bash-3.2$ clear
bash-3.2$ ipython
Python 3.7.4 (default, Jul  9 2019, 18:13:23)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.7.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from memcache import Client as Memcache
In [2]: CACHE = Memcache(['127.0.0.1:11211'], socket_timeout=0.5)
In [3]: CACHE.flush_all()
In [4]: for i in range (1, 11):
   ...:     print(CACHE.set('sam-%s' % i, i))
   ...:
True
True
True
True
True
True
True
True
True
True

In [5]: for i in range (1, 11):
   ...:     print('sam-%s = %s' % (i, CACHE.get('sam-%s' % i)))
   ...:
sam-1 = 1
sam-2 = 2
sam-3 = 3
sam-4 = 4
sam-5 = 5
sam-6 = 6
sam-7 = 7
sam-8 = 8
sam-9 = 9
sam-10 = 10

In [6]: for i in range(1, 1000):
   ...:     # Run for a while, press Ctrl+C to raise crash!
   ...:     print(CACHE.set('interrupt-%s' % i, i))
   ...:
True
True
True
True
True
True
^C---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
<ipython-input-6-b57eaab76114> in <module>
      1 for i in range(1, 1000):
      2     # Run for a while, press Ctrl+C to raise crash!
----> 3     print(CACHE.set('interrupt-%s' % i, i))
      4

/usr/local/lib/python3.7/site-packages/python_memcached-1.59-py3.7.egg/memcache.py in set(self, key, val, time, min_compress_len, noreply)
    725         send the reply.
    726         '''
--> 727         return self._set("set", key, val, time, min_compress_len, noreply)
    728
    729     def cas(self, key, val, time=0, min_compress_len=0, noreply=False):

/usr/local/lib/python3.7/site-packages/python_memcached-1.59-py3.7.egg/memcache.py in _set(self, cmd, key, val, time, min_compress_len, noreply)
   1050
   1051         try:
-> 1052             return _unsafe_set()
   1053         except _ConnectionDeadError:
   1054             # retry once

/usr/local/lib/python3.7/site-packages/python_memcached-1.59-py3.7.egg/memcache.py in _unsafe_set()
   1042                 if noreply:
   1043                     return True
-> 1044                 return server.expect(b"STORED", raise_exception=True) == b"STORED"
   1045             except socket.error as msg:
   1046                 if isinstance(msg, tuple):

/usr/local/lib/python3.7/site-packages/python_memcached-1.59-py3.7.egg/memcache.py in expect(self, text, raise_exception)
   1461
   1462     def expect(self, text, raise_exception=False):
-> 1463         line = self.readline(raise_exception)
   1464         if self.debug and line != text:
   1465             if six.PY3:

/usr/local/lib/python3.7/site-packages/python_memcached-1.59-py3.7.egg/memcache.py in readline(self, raise_exception)
   1447             if index >= 0:
   1448                 break
-> 1449             data = recv(4096)
   1450             if not data:
   1451                 # connection close, let's kill it and raise

KeyboardInterrupt:

In [7]: # python-memcached return wrong value!!!
In [8]: for i in range (1, 11):
   ...:     print('sam-%s = %s' % (i, CACHE.get('sam-%s' % i)))
   ...:
sam-1 = None
sam-2 = 1
sam-3 = 2
sam-4 = 3
sam-5 = 4
sam-6 = 5
sam-7 = 6
sam-8 = 7
sam-9 = 8
sam-10 = 9

In [9]: # python-memcached return wrong value!!!
In [10]: for i in range (1, 11):
    ...:     print('sam-%s = %s' % (i, CACHE.get('sam-%s' % i)))
    ...:
sam-1 = 10
sam-2 = 1
sam-3 = 2
sam-4 = 3
sam-5 = 4
sam-6 = 5
sam-7 = 6
sam-8 = 7
sam-9 = 8
sam-10 = 9

I see that after crash python-memcached still keep connection to Memcached but function get() set() have something wrong.

Has anyone run into the same problem? Is this a bug of python-memcached?

@KPassov
Copy link

KPassov commented Nov 1, 2019

I ran into the same problem, and it's a problem with python-memcached. I made a python script based on your ipython commands that recreates the bug on most runs.


import time

from multiprocessing import Process
from memcache import Client as Memcache


CACHE = Memcache(['127.0.0.1:11211'], socket_timeout=0.5)

# Set some values in the cache
print("Setting!")
for i in range (1, 11):
    CACHE.set('sam-%s' % i, i)
    print('set: sam-%s = %s' % (i,i))

# Get's all the set values to ensure they are saved
print("Getting!")
for i in range (1, 11):
    print('get: sam-%s: %s' % (i,CACHE.get('sam-%s' % i)))


def worker():
  print('Function started')
  for i in range(1000):
      CACHE.set('interrupt-%s' % i, i)
  print('Function finished')

# Start a new process and kill it while it's setting values in the cache
p = Process(target=worker)
p.start()
time.sleep(0.1)
p.terminate()
print("process killed")

# Get the values again
print("Getting!")
for i in range (1, 11):
    print('get: sam-%s: %s' % (i,CACHE.get('sam-%s' % i)))

The output should look like this:

Setting!
set: sam-1 = 1
set: sam-2 = 2
set: sam-3 = 3
set: sam-4 = 4
set: sam-5 = 5
set: sam-6 = 6
set: sam-7 = 7
set: sam-8 = 8
set: sam-9 = 9
set: sam-10 = 10
Getting!
get: sam-1: 1
get: sam-2: 2
get: sam-3: 3
get: sam-4: 4
get: sam-5: 5
get: sam-6: 6
get: sam-7: 7
get: sam-8: 8
get: sam-9: 9
get: sam-10: 10
Function started
process killed
Getting!
get: sam-1: None
get: sam-2: 1
get: sam-3: 2
get: sam-4: 3
get: sam-5: 4
get: sam-6: 5
get: sam-7: 6
get: sam-8: 7
get: sam-9: 8
get: sam-10: 9

It looks like the problem arises when there are 2 or more processes that uses the same Memcache client object, and one of them crashes while setting a value into the cache.
After the crash, the other process with the same Memcached object starts returning wrong key/value pairs.

I fixed it by making sure each process initiates it's own Memcached object and made a lookup based on the process id.

_global_caches = {}
def _get_cache():
    # get current process pid
    pid = current_process().pid

    # See if we have a memcache object with this pid
    if pid not in _global_caches:
        _global_caches[pid] = Memcache(['127.0.0.1:11211'])

    return _global_caches[pid]


# Set some values in the cache
print("Setting!")
for i in range (1, 11):
    _get_cache().set('sam-%s' % i, i)
    print('set: sam-%s = %s' % (i,i))

# Get's all the set values to ensure they are saved
print("Getting!")
for i in range (1, 11):
    print('get: sam-%s: %s' % (i,_get_cache().get('sam-%s' % i)))


def worker():
  print('Function started')
  for i in range(1000):
      _get_cache().set('interrupt-%s' % i, i)
  print('Function finished')

# Start a new process that we kill while it's setting values in the 
p = Process(target=worker)
p.start()
time.sleep(0.1)
p.terminate()
print("process killed")

print("Getting!")
for i in range (1, 11):
    print('get: sam-%s: %s' % (i,_get_cache().get('sam-%s' % i)))

This should output the more expected

Setting!
set: sam-1 = 1
set: sam-2 = 2
set: sam-3 = 3
set: sam-4 = 4
set: sam-5 = 5
set: sam-6 = 6
set: sam-7 = 7
set: sam-8 = 8
set: sam-9 = 9
set: sam-10 = 10
Getting!
get: sam-1: 1
get: sam-2: 2
get: sam-3: 3
get: sam-4: 4
get: sam-5: 5
get: sam-6: 6
get: sam-7: 7
get: sam-8: 8
get: sam-9: 9
get: sam-10: 10
Function started
process killed
Getting!
get: sam-1: 1
get: sam-2: 2
get: sam-3: 3
get: sam-4: 4
get: sam-5: 5
get: sam-6: 6
get: sam-7: 7
get: sam-8: 8
get: sam-9: 9
get: sam-10: 10

Just note that this solution uses more sockets for the memcache, as it creates one for each process

@samsonle1809
Copy link
Author

Yes, the problem arises when there are more processes that uses the same Memcache client object. I switched to pymemcache library. It has not the same problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants