-
Notifications
You must be signed in to change notification settings - Fork 34
Description
I have been observing that occasionally some of my cached + locked functions get stuck calling set_lock repeatedly. This leads to millions of redis calls in a ~30 min span in our application.
As I started to dig into this, I found other unexpected behavior with locking and opened #332. Based on that investigation, we need ttl=None
on the regular cache and positive ttl on the lock to actually achieve proper locking right now.
When I write a script with that, multiprocessing, and a function with a random execution time, I can recreate the issue consistently.
import asyncio
import multiprocessing
import random
from cashews import Command, cache
from cashews.backends.interface import Backend
import redis
r = redis.Redis(
host="localhost",
port=6379,
db=0,
)
r.flushall()
_counter = 0
async def _logging_middleware(call, cmd: Command, backend: Backend, *args, **kwargs):
global _counter
if cmd == Command.SET_LOCK:
_counter += 1
return await call(*args, **kwargs)
_middlewares = (_logging_middleware,)
cache.setup(
"redis://localhost:6379/0",
client_side=True,
retry_on_timeout=True,
middlewares=_middlewares,
)
@cache(ttl=None)
@cache.locked(ttl=5)
async def test():
print("running function")
await asyncio.sleep(random.random() * 0.1)
return 1
async def _main():
await test()
print("set lock called", _counter, "times")
def main():
asyncio.run(_main())
multiprocessing.set_start_method("fork")
for _ in range(2):
multiprocessing.Process(target=main).start()
Outputs:
running function
running function
set lock called 1 times
set lock called 185 times
I think the issue is related to this while loop. If it calls self.set_lock
and it returns False
, then self.set_lock
gets called again almost immediately. The implementation in redis passes nx=True
and from my testing I see that returns None
if the key is already set. So if a function gets into this loop while the key is already set, it will repeatedly hit the cache until the key is removed.