3

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.

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Is this a common pattern? And no way to avoid the double checking? I just don't like how it looks, feels weird. But I don't have any better idea.
Yes -this is a common pattern - hits the case of 'making thingsas simple as possible, but not simpler". And it is indeed not redundant, given that self.latest_timestamp can (and will) change during the pause on the async with self.lock:
The alternative would be instead of waiting for the lock to be available, to bail out immediately if it is not: if self.lock.locked(): return \n async with self.lock - (note that this pattern is only suitable with asyncio with the specific await points for concurrent code, and not for multi-threaded concurrency)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.