Skip to content

KeyError on changing hash in lru_cache #10

Description

@alex-hofsteede

When an object is used as an argument to a @lru_cache-decorated function, the object becomes part of the internal cache key. This cache key is stored in 2 places, once by reference as the link.key in one of the nodes of the linked list starting at self.__root, and once in the dictionary at self.__map() (where it is keyed based on hash).

If the hash() of this object subsequently changes, we will encounter a bug when we attempt to evict that entry from the cache, as we can still find the object by reference in the linked-list, but when we try and delete it from self.__map, we get a KeyError because its hash has changed and the object in the dictionary is stored under the original hash. The code in question is here

Here is a simple script to demostrate the issue.

import sys

if sys.version_info.major == 3:
    from functools import lru_cache
else:
    from functools32 import lru_cache

@lru_cache(maxsize=1)
def foo(arg):
    return "return value"

class Hashable(object):
    def __init__(self, value):
        self.value = value

    def __hash__(self):
        return self.value

h1 = Hashable(1)
foo(h1) # add h1 to cache
h1.value = 3 # but then change its hash value
h2 = Hashable(2)
foo(h2) # evicts h1

Works in python3/functools, fails with KeyError: (<__main__.Hashable object at 0x10d9daf10>,) line 132, in popitem in python2/functools32

One might argue that having objects change their hash is a terrible thing to do, but unfortunately a lot of objects do have this behavior. Django models for example. Their hash is based off their primary key, so it changes when you save() the model instance.

class MyModel(Model):
    @lru_cache(maxsize=1)
    def get_value(self):
         ...

mm = MyModel()
value = mm.lru_cached_method()
mm.save()
# Later if we try and evict this result of `get_value()` it will error, as the hash of `mm` has changed

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions