Cachex v3.1.3 Cachex.Services.Locksmith View Source

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

Link to this function lock(arg, keys) View Source
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.

Link to this function locked(arg) View Source
locked(Spec.cache()) :: [any()]

Retrieves a list of locked keys for a cache.

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

Link to this function locked?(arg, keys) View Source
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.

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).

Link to this function start_transaction() View Source
start_transaction() :: no_return()

Flags this process as running in a transaction.

Link to this function stop_transaction() View Source
stop_transaction() :: no_return()

Flags this process as not running in a transaction.

Link to this function transaction(cache, keys, fun) View Source
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.

Link to this function transaction?() View Source
transaction?() :: boolean()

Determines if the current process is in transactional context.

Link to this function unlock(arg, keys) View Source
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.

Link to this function write(cache, keys, fun) View Source
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.