pinterest / pymemcache

A comprehensive, fast, pure-Python memcached client.
https://pymemcache.readthedocs.io/
Apache License 2.0
778 stars 180 forks source link

pymemcache.client.base.Client.stats(*args) raises exception when Client.key_prefix is set, and arg(s) used. #430

Closed gearshot closed 2 years ago

gearshot commented 2 years ago

Root cause is the first lines of Client._fetch_cmd(), which prepends Client.key_prefix on the 'stats' command arguments:

    def _fetch_cmd(
        self, name: bytes, keys: Iterable[Key], expect_cas: bool
    ) -> Dict[Key, Any]:
        prefixed_keys = [self.check_key(k) for k in keys]
        remapped_keys = dict(zip(prefixed_keys, keys))

Example:

python3
Python 3.9.5 (default, Oct  8 2022, 17:10:03) 
[GCC 9.1.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pprint
>>> import time
>>> pp = pprint.PrettyPrinter(indent=4)
>>> from pymemcache.client.base import Client
>>> client = Client('localhost')
>>> client.version()
b'1.6.17'
>>> client.set('foo', 'bar')
True
>>> time.sleep(1)
>>> for k in client.stats('cachedump', '1', '1'):
...     print(f'{k}={client.get(k)}')
... 
b'foo'=b'bar'

>>> del client
>>> client = Client('localhost', key_prefix=b'node01')
>>> client.set('foo', 'bar')
True
>>> time.sleep(1)
>>> client.stats('items')
Traceback (most recent call last):
  File "", line 1, in 
  File "/usr/lib/python3.9/site-packages/pymemcache/client/base.py", line 825, in stats
    result = self._fetch_cmd(b'stats', args, False)
  File "/usr/lib/python3.9/site-packages/pymemcache/client/base.py", line 1031, in _fetch_cmd
    self._raise_errors(line, name)
  File "/usr/lib/python3.9/site-packages/pymemcache/client/base.py", line 932, in _raise_errors
    raise MemcacheUnknownCommandError(name)
pymemcache.exceptions.MemcacheUnknownCommandError: b'stats'

>>> del client
>>> client = Client('localhost')
>>> pp.pprint(client.stats('items'))
{   b'items:1:age': 2,
    b'items:1:age_hot': 0,
    b'items:1:age_warm': 0,
    b'items:1:crawler_items_checked': 0,
    b'items:1:crawler_reclaimed': 0,
    b'items:1:direct_reclaims': 0,
    b'items:1:evicted': 0,
    b'items:1:evicted_active': 0,
    b'items:1:evicted_nonzero': 0,
    b'items:1:evicted_time': 0,
    b'items:1:evicted_unfetched': 0,
    b'items:1:expired_unfetched': 0,
    b'items:1:hits_to_cold': 1,
    b'items:1:hits_to_hot': 0,
    b'items:1:hits_to_temp': 0,
    b'items:1:hits_to_warm': 0,
    b'items:1:lrutail_reflocked': 0,
    b'items:1:mem_requested': 136,
    b'items:1:moves_to_cold': 2,
    b'items:1:moves_to_warm': 0,
    b'items:1:moves_within_lru': 0,
    b'items:1:number': 2,
    b'items:1:number_cold': 2,
    b'items:1:number_hot': 0,
    b'items:1:number_warm': 0,
    b'items:1:outofmemory': 0,
    b'items:1:reclaimed': 0,
    b'items:1:tailrepairs': 0}
>>> for k in client.stats('cachedump', '1', '2'):
...     print(f'{k}={client.get(k)}')
... 
b'node01foo'=b'bar'
b'foo'=b'bar'
jogo commented 2 years ago

@gearshot thanks for the bug report along with all the details to reproduce it, fix incoming