GenServer-based byte-budget semaphore adapter.
Tracks a shared byte budget across concurrent callers. Acquisitions can push the budget negative to allow in-flight work to complete; new acquisitions block while the budget is negative and resume once releases bring it back to a non-negative value.
Example
# Start a semaphore with 5MB budget
{:ok, sem} = GenServer.start_link(max_bytes: 5_242_880)
# Acquire bytes (blocks if budget is negative)
:ok = GenServer.acquire(sem, 1000, 5_000)
# Do work...
# Release bytes back
:ok = GenServer.release(sem, 1000)with_bytes/3
For convenience, with_bytes/3 acquires, executes a function, and guarantees
release even if the function raises:
result = GenServer.with_bytes(sem, 1000, fn ->
# Do work with the allocated bytes
:ok
end)
Summary
Functions
Acquire bytes from the semaphore, blocking while the budget is negative.
Get the number of available bytes.
Returns a specification to start this module under a supervisor.
Release bytes back to the semaphore.
Start a BytesSemaphore with the given byte budget.
Execute fun while holding the requested byte budget.
Types
Functions
@spec acquire(t(), non_neg_integer(), timeout()) :: :ok | {:error, :timeout}
Acquire bytes from the semaphore, blocking while the budget is negative.
Returns :ok when the bytes have been acquired. If the current budget is
negative, the caller blocks until enough releases bring it back to non-negative
or the timeout expires.
Examples
:ok = GenServer.acquire(sem, 1000, 5_000)
{:error, :timeout} = GenServer.acquire(sem, 1000, 100)
@spec available(t()) :: non_neg_integer()
Get the number of available bytes.
Returns 0 if the budget is negative.
Examples
available = GenServer.available(sem)
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec release(t(), non_neg_integer()) :: :ok
Release bytes back to the semaphore.
This is an asynchronous operation. If there are blocked waiters and the budget returns to non-negative, they will be woken up.
Examples
:ok = GenServer.release(sem, 1000)
@spec start_link(keyword()) :: GenServer.on_start()
Start a BytesSemaphore with the given byte budget.
Options
:max_bytes- Maximum byte budget (default: 5MB):name- Optional name for registration
Examples
{:ok, sem} = GenServer.start_link(max_bytes: 1_000_000)
{:ok, sem} = GenServer.start_link(name: MyApp.BytesSemaphore)
@spec with_bytes(t(), non_neg_integer(), (-> result)) :: result when result: any()
Execute fun while holding the requested byte budget.
Acquires the bytes, executes the function, and guarantees release even if the function raises an exception, throws, or exits.
Examples
result = GenServer.with_bytes(sem, 1000, fn ->
# Work with allocated bytes
:done
end)