PerUserCache

class gafaelfawr.cache.PerUserCache

Bases: BaseCache

Base class for a cache with per-user locking.

Notes

There is a moderately complex locking structure at play here. When there’s a cache miss for data for a specific user, the goal is to block the expensive lookups or token creation for that user until the first requester either looks up the data or creates a new token, either way adding it to the cache. Hopefully then subsequent requests that were blocked on the lock can be answered from the cache.

There is therefore a dictionary of per-user locks, but since we don’t know the list of users in advance, we have to populate those locks on the fly. It shouldn’t be necessary to protect the dict of per-user locks with another lock because we only need to worry about asyncio concurrency, but since FastAPI does use a thread pool, err on the side of caution and use the same locking strategy that would be used for multithreaded code.

Note that the per-user lock must be acquired before the general lock is released, so the lock method cannot simply return the per-user lock. To see why, imagine that one code path retrieves the per-user lock in preparation for acquiring it, and then another code path calls clear. clear acquires the global lock and then deletes the per-user lock, but the first caller still has a copy of the per-user lock and thinks it’s valid. It may then take that per-user lock, but a third code path could also try to lock the same user and get a new per-user lock from the post-clearing cache. Both the first and third code paths will think they have a lock and may conflict. UserLockManager is used to handle this.

Methods Summary

clear()

Invalidate the cache.

initialize()

Initialize the cache.

lock(username)

Return the per-user lock for locking.

Methods Documentation

async clear()

Invalidate the cache.

Used primarily for testing. Calls the initialize method provided by derivative classes, with proper locking, to reinitialize the cache.

Return type:

None

abstract initialize()

Initialize the cache.

This will be called by clear and should also be called by the derived class’s __init__ method.

Return type:

None

async lock(username)

Return the per-user lock for locking.

The return value should be used with async with to hold a lock around checking for a cached token and, if one is not found, creating and storing a new token.

Parameters:

username (str) – Per-user lock to hold.

Returns:

Async context manager that will take the user lock.

Return type:

UserLockManager