pythonlockingpython-asyncioprimitive

How to avoid double checks with asyncio.Lock


I have a piece of code, that checks if the redis db has data updates and loads them to the memory. I want only one corouitine to execute this load.


class MyService:
    def __init__(self):
        self.lock = asyncio.Lock()
        self.latest_timestamp = None

    async def _check_latest_timestamp_from_db(self):
        # go to db

    async def ensure_update_loaded():
        latest_timestamp_from_db = await self._check_latest_timestamp_from_db()
        if self.timestamp == latest_timestamp_from_db:
            return
        if self.lock.locked():
            return # the load is in progress, we're okay to use the old data for now
        async with self.lock:
            # do the load
            self.timestamp = latest_timestamp_from_db

From my understanding multiple coroutines can go simultaneously to this line: async with lock:, after all the checks are passed. Yes, they will execute consequently, but the load will happen more than once. I could double check within the lock:

async with self.lock:
    if self.timestamp == latest_timestamp_from_db:
            return
    # do the load
    self.timestamp = latest_timestamp_from_db

But I think there should be a clearer solution to my problem.


Solution

  • You're basically implementing a double-checked locking.

    Pseudocode:

    class MyService:
        def __init__(self):
            self.lock = asyncio.Lock()
            self.latest_timestamp = None
    
        async def _check_latest_timestamp_from_db(self):
            ...
    
    
        async def ensure_update_loaded(self):
            latest_timestamp_from_db = await self._check_latest_timestamp_from_db()
            if self.latest_timestamp == latest_timestamp_from_db:
                return
    
            async with self.lock:
                if self.latest_timestamp == latest_timestamp_from_db:
                    return
    
                ...
    
                self.latest_timestamp = latest_timestamp_from_db
    
    

    Moving the check inside the lock is correct.