View Source Cachex.Services.Locksmith (Cachex v3.6.0)

Locking service in charge of table transactions.

This module acts as a global lock table against all cache. This is due to the fact that ETS tables are fairly expensive to construct if they're only going to store a few keys.

Due to this we have a single global table in charge of locks, and we tag just the key in the table with the name of the cache it's associated with. This keyspace will typically be very small, so there should be almost no impact to operating in this way (except that we only have a single ETS table rather than a potentially large N).

It should be noted that the behaviour in this module could easily live as a GenServer if it weren't for the speedup gained when using ETS. When using an ETS table, checking for a lock is typically 0.3-0.5µs/op whereas a call to a server process is roughly 10x this (due to the process interactions).

Link to this section Summary

Functions

Locks a number of keys for a cache.

Retrieves a list of locked keys for a cache.

Determines if a key is able to be written to by the current process.

Starts the backing services required by the Locksmith.

Flags this process as running in a transaction.

Flags this process as not running in a transaction.

Executes a transaction against a cache table.

Determines if the current process is in transactional context.

Unlocks a number of keys for a cache.

Performs a write against the given key inside the table.

Link to this section Functions

@spec lock(Spec.cache(), [any()]) :: boolean()

Locks a number of keys for a cache.

This function can handle multiple keys to lock together atomically. The returned boolean will signal if the lock was successful. A lock can fail if one of the provided keys is already locked.

@spec locked(Spec.cache()) :: [any()]

Retrieves a list of locked keys for a cache.

This uses some ETS matching voodoo to pull back the locked keys. They won't be returned in any specific order, so don't rely on it.

@spec locked?(Spec.cache(), [any()]) :: true | false

Determines if a key is able to be written to by the current process.

For a key to be writeable, it must either have no lock or be locked by the calling process.

@spec start_link() :: GenServer.on_start()

Starts the backing services required by the Locksmith.

At this point this will start the backing ETS table required by the locking logic inside the Locksmith. This is started with concurrency enabled and logging disabled to avoid spamming log output.

This may become configurable in future, but this table will likelyn never cause issues in the first place (as it only handles very basic operations).

@spec start_transaction() :: no_return()

Flags this process as running in a transaction.

@spec stop_transaction() :: no_return()

Flags this process as not running in a transaction.

Link to this function

transaction(cache, keys, fun)

View Source
@spec transaction(Spec.cache(), [any()], (() -> any())) :: any()

Executes a transaction against a cache table.

If the process is already in a transactional context, the provided function will be executed immediately. Otherwise the required keys will be locked until the provided function has finished executing.

This is mainly shorthand to avoid having to handle row locking explicitly.

@spec transaction?() :: boolean()

Determines if the current process is in transactional context.

@spec unlock(Spec.cache(), [any()]) :: true

Unlocks a number of keys for a cache.

There's currently no way to batch delete items in ETS beyond a select_delete, so we have to simply iterate over the locks and remove them sequentially. This is a little less desirable, but needs must.

@spec write(Spec.cache(), any(), (() -> any())) :: any()

Performs a write against the given key inside the table.

If the key is locked, the write is queued inside the lock server to ensure that we execute consistently.

This is a little hard to explain, but if the cache has not had any transactions executed against it we skip the lock check as any of our ETS writes are atomic and so do not require a lock.