Coordination
- Start here: Coordination guide
- Common recipes:
Lock,TaskLock,LeaderElection - Backends: backend selection
grelmicro.coordination
Coordination primitives for distributed locking and leader election.
Coordination
Coordination(
source: Provider | type[Provider] | None = None,
*,
lock: Provider
| LockBackend
| type[Provider | LockBackend]
| None = None,
election: Provider
| LeaderElectionBackend
| type[Provider | LeaderElectionBackend]
| None = None,
schedule: Provider
| ScheduleBackend
| type[Provider | ScheduleBackend]
| None = None,
name: str = "default",
)
Coordination component: wraps backends and exposes coordination primitives.
Registered as micro.coordination after Grelmicro.use(Coordination(...)).
Exposes lock(...), tasklock(...), and leaderelection(...) so users do
not need to pass backend= on every primitive.
A single positional Provider resolves every primitive: the component calls
provider.lock() for the lock backend, provider.leaderelection() for the
election backend, and provider.schedule() for the cron schedule backend.
The lock=, election=, and schedule= keywords set each backend
independently, so locks can run on one vendor and leader election on another.
Each accepts a Provider, a backend instance, or a zero-arg class.
Example
from grelmicro import Grelmicro
from grelmicro.coordination import Coordination
from grelmicro.providers.redis import RedisProvider
redis = RedisProvider("redis://localhost:6379/0")
micro = Grelmicro(uses=[redis, Coordination(redis)])
async with micro:
async with micro.coordination.lock("cart"):
...
leader = micro.coordination.leaderelection("worker")
Read more in the Coordination docs.
Initialize the component with the wrapped backends.
| PARAMETER | DESCRIPTION |
|---|---|
source
|
A
TYPE:
|
lock
|
The lock backend. A
TYPE:
|
election
|
The leader election backend. A
TYPE:
|
schedule
|
The cron schedule backend. A
TYPE:
|
name
|
Registration name. Multiple
TYPE:
|
kind
class-attribute
kind: str = 'coordination'
name
property
name: str
Return the registration name.
lock_backend
property
lock_backend: LockBackend
The underlying LockBackend.
| RAISES | DESCRIPTION |
|---|---|
CoordinationBackendError
|
If no lock backend is wired. |
election_backend
property
election_backend: LeaderElectionBackend
The underlying LeaderElectionBackend.
| RAISES | DESCRIPTION |
|---|---|
CoordinationBackendError
|
If no leader election backend is wired. |
schedule_backend
property
schedule_backend: ScheduleBackend
The underlying ScheduleBackend.
| RAISES | DESCRIPTION |
|---|---|
CoordinationBackendError
|
If no schedule backend is wired. |
lock
lock(name: str, **kwargs: Any) -> Lock
Construct a Lock bound to this component's lock backend.
| RAISES | DESCRIPTION |
|---|---|
CoordinationBackendError
|
If no lock backend is wired. |
tasklock
tasklock(name: str, **kwargs: Any) -> TaskLock
Construct a TaskLock bound to this component's lock backend.
| RAISES | DESCRIPTION |
|---|---|
CoordinationBackendError
|
If no lock backend is wired. |
leaderelection
leaderelection(name: str, **kwargs: Any) -> LeaderElection
Construct a LeaderElection bound to this component's election backend.
| RAISES | DESCRIPTION |
|---|---|
CoordinationBackendError
|
If no leader election backend is wired. |
Lock
Lock(
name: str,
*,
backend: LockBackend | str | None = None,
worker: str | UUID | None = None,
lease_duration: Seconds | None = None,
retry_interval: Seconds | None = None,
retry_jitter: float | None = None,
env_prefix: str | None = None,
env_load: bool | None = None,
)
Bases: Reconfigurable[LockConfig], BaseLock
Lock.
This lock is a distributed lock that is used to acquire a resource across multiple workers. The lock is acquired asynchronously and can be extended multiple times manually. The lock is automatically released after a duration if not extended.
Supports live reconfiguration via
reconfigure(new_config).
A swap takes effect on the next call. In-flight calls keep the
config they started with. The worker field cannot change.
Changing it raises ValueError. See
Live reconfiguration.
Initialize the lock.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
The name of the resource to lock. It will be used as the lock name so make sure it is unique on the lock backend.
TYPE:
|
backend
|
The distributed lock backend used to acquire and release the lock. Accepts a backend instance, the name of a registered backend
(e.g.
TYPE:
|
worker
|
The worker identity. By default, a random 16-character hex token is generated.
TYPE:
|
lease_duration
|
The duration in seconds for the lock to be held by default. Default: 60. When unset and env reads are enabled (see
TYPE:
|
retry_interval
|
The duration in seconds between attempts to acquire the lock. Default: 0.1. Must be >= 0.001 to prevent flooding
the lock backend. When unset and env reads are enabled (see
TYPE:
|
retry_jitter
|
Factor for randomized jitter applied to each retry sleep. Default: 0.1. Each sleep becomes
retry_interval * uniform(1 - retry_jitter, 1 + retry_jitter).
Set to 0 to disable jitter. When unset and env reads are
enabled (see
TYPE:
|
env_prefix
|
Override the auto-derived environment variable prefix. Default:
TYPE:
|
env_load
|
Whether to read environment variables. When None (the default), follow the process-wide
TYPE:
|
name
property
name: str
Return the lock identity.
backend
property
backend: LockBackend
Bound lock backend, resolved on each call.
When a backend instance was passed at construction it is
always returned. Otherwise the active Grelmicro app is
consulted via Grelmicro.current() on every access so that
micro.override(Coordination(...)) blocks take effect.
| RAISES | DESCRIPTION |
|---|---|
OutOfContextError
|
No backend resolved in this scope. Pass
|
from_thread
property
from_thread: ThreadLockAdapter
Return the lock adapter for a worker thread.
from_config
classmethod
from_config(
name: str,
config: LockConfig,
*,
backend: LockBackend | str | None = None,
) -> Self
Construct a Lock from a name and a pre-built LockConfig.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
The name of the resource to lock. Acts as the instance identity. Used as the backend
lock key and exposed via the
TYPE:
|
config
|
The pre-built lock configuration. Use this path when the configuration is assembled at
startup from a settings tree (for example YAML, Vault,
or a
TYPE:
|
backend
|
The distributed lock backend used to acquire and release the lock. Accepts a backend instance, the name of a registered backend
(e.g.
TYPE:
|
acquire
async
acquire(*, timeout: Seconds | None = None) -> LockHandle
Acquire the lock.
Returns the LockHandle for this acquisition, carrying the ownership
token and the strictly increasing fencing_token.
| PARAMETER | DESCRIPTION |
|---|---|
timeout
|
Maximum number of seconds to wait for the lock. When None (the default), waits indefinitely. When set to a positive number, retries until the deadline then raises TimeoutError.
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
LockReentrantError
|
If the lock is already acquired (nested usage is not supported). |
LockAcquireError
|
If the lock cannot be acquired due to an error on the backend. |
TimeoutError
|
If |
extend
async
extend() -> LockHandle
Renew the lease for another lease_duration without releasing.
The fencing token is unchanged when the lease is still held. If the
lease has expired or was released by another path, the backend returns
None and this method raises LockNotOwnedError.
| RETURNS | DESCRIPTION |
|---|---|
LockHandle
|
The handle for this lock with the same fencing token.
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
LockNotOwnedError
|
If this task does not hold the lock or the lease was lost. |
LockAcquireError
|
If the backend call fails. |
acquire_nowait
async
acquire_nowait() -> LockHandle
Acquire the lock, without blocking.
Returns the LockHandle for this acquisition, carrying the ownership
token and the strictly increasing fencing_token.
| RAISES | DESCRIPTION |
|---|---|
LockReentrantError
|
If the lock is already acquired (nested usage is not supported). |
WouldBlockError
|
If the lock cannot be acquired without blocking. |
LockAcquireError
|
If the lock cannot be acquired due to an error on the backend. |
release
async
release() -> None
Release the lock.
| RAISES | DESCRIPTION |
|---|---|
LockNotOwnedError
|
If the lock is not owned by the current token. |
LockReleaseError
|
If the lock cannot be released due to an error on the backend. |
locked
async
locked() -> bool
Check if the lock is acquired.
| RAISES | DESCRIPTION |
|---|---|
LockLockedCheckError
|
If the lock cannot be checked due to an error on the backend. |
owned
async
owned() -> bool
Check if the lock is owned by the current token.
| RAISES | DESCRIPTION |
|---|---|
LockBackendError
|
If the lock cannot be checked due to an error on the backend. |
do_acquire
async
do_acquire(token: str, *, duration: Seconds) -> int | None
Acquire the lock.
This method should not be called directly. Use acquire instead.
| PARAMETER | DESCRIPTION |
|---|---|
token
|
The token to register on the backend.
TYPE:
|
duration
|
The lease duration to request, in seconds. The
caller captures this from
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
int | None
|
int | None: The fencing token if the lock was acquired, None if the lock was not acquired. |
| RAISES | DESCRIPTION |
|---|---|
LockAcquireError
|
If the lock cannot be acquired due to an error on the backend. |
do_release
async
do_release(token: str) -> bool
Release the lock.
This method should not be called directly. Use release instead.
| RETURNS | DESCRIPTION |
|---|---|
bool
|
True if the lock was released, False otherwise.
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
LockReleaseError
|
Cannot release the lock due to backend error. |
do_owned
async
do_owned(token: str) -> bool
Check if the lock is owned by the current token.
This method should not be called directly. Use owned instead.
| RETURNS | DESCRIPTION |
|---|---|
bool
|
True if the lock is owned by the current token, False otherwise.
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
LockOwnedCheckError
|
Cannot check if the lock is owned due to backend error. |
do_thread_acquire
async
do_thread_acquire(
thread_id: int, *, timeout: Seconds | None = None
) -> LockHandle
Acquire the lock from a worker thread (blocking).
Runs on the event loop so the reentrant check and backend acquire are atomic with respect to other threads.
| RAISES | DESCRIPTION |
|---|---|
LockReentrantError
|
If the lock is already acquired (nested usage is not supported). |
LockAcquireError
|
If the lock cannot be acquired due to an error on the backend. |
TimeoutError
|
If |
do_thread_extend
async
do_thread_extend(thread_id: int) -> LockHandle
Renew the lease from a worker thread without releasing.
Runs on the event loop so the ownership check and backend acquire are atomic with respect to other threads.
| RAISES | DESCRIPTION |
|---|---|
LockNotOwnedError
|
If this thread does not hold the lock or the lease was lost. |
LockAcquireError
|
If the backend call fails. |
do_thread_acquire_nowait
async
do_thread_acquire_nowait(thread_id: int) -> LockHandle
Acquire the lock from a worker thread (non-blocking).
Runs on the event loop so the reentrant check and backend acquire are atomic with respect to other threads.
| RAISES | DESCRIPTION |
|---|---|
LockReentrantError
|
If the lock is already acquired (nested usage is not supported). |
WouldBlockError
|
If the lock cannot be acquired without blocking. |
LockAcquireError
|
If the lock cannot be acquired due to an error on the backend. |
do_thread_release
async
do_thread_release(thread_id: int) -> None
Release the lock from a worker thread.
Runs on the event loop so the backend release is atomic with respect to other threads.
| RAISES | DESCRIPTION |
|---|---|
LockNotOwnedError
|
If the lock is not owned by the current token. |
LockReleaseError
|
If the lock cannot be released due to an error on the backend. |
TaskLock
TaskLock(
name: str = "default",
*,
backend: LockBackend | str | None = None,
worker: str | UUID | None = None,
min_hold_duration: Seconds | None = None,
lease_duration: Seconds | None = None,
env_prefix: str | None = None,
env_load: bool | None = None,
)
Bases: Reconfigurable[TaskLockConfig], LockPrimitive
Task Lock.
A distributed lock for scheduled tasks. Unlike a regular Lock,
TaskLock does not release immediately on context manager exit. Instead, it keeps
the lock held for at least min_hold_duration seconds to prevent re-execution
on other nodes.
There is no background task that maintains the lock active during execution.
The lock relies entirely on the TTL (lease_duration) set at acquire time.
This lock is designed to be used as the lock parameter of @task.interval.
Supports live reconfiguration via
reconfigure(new_config).
A swap takes effect on the next call. An exit re-acquire uses
the config the call entered with. The worker field cannot
change. Changing it raises ValueError. See
Live reconfiguration.
Initialize the task lock.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
The name of the resource to lock. It will be used as the lock name so make sure it is unique on the lock backend. Defaults to
TYPE:
|
backend
|
The distributed lock backend used to acquire and release the lock. By default, it will use the lock backend registry to get the default lock backend.
TYPE:
|
worker
|
The worker identity. By default, a UUIDv1 is generated.
TYPE:
|
min_hold_duration
|
The minimum duration in seconds to hold the lock after task completion. Default: 1. Prevents re-execution on other nodes
before this duration has elapsed. When unset and env reads
are enabled (see
TYPE:
|
lease_duration
|
The maximum duration in seconds to hold the lock (deadlock protection). Default: 60. Acts as the TTL on acquire. When unset and env reads
are enabled (see
TYPE:
|
env_prefix
|
Override the auto-derived environment variable prefix. Default:
TYPE:
|
env_load
|
Whether to read environment variables. When None (the default), follow the process-wide
TYPE:
|
name
property
name: str
Return the task lock identity.
backend
property
backend: LockBackend
Bound lock backend, resolved on each call.
When a backend instance was passed at construction it is
always returned. Otherwise the active Grelmicro app is
consulted via Grelmicro.current() on every access so that
micro.override(Coordination(...)) blocks take effect.
| RAISES | DESCRIPTION |
|---|---|
OutOfContextError
|
No backend resolved in this scope. Pass
|
from_thread
property
from_thread: ThreadTaskLockAdapter
Return the task lock adapter for worker thread.
from_config
classmethod
from_config(
name: str,
config: TaskLockConfig,
*,
backend: LockBackend | str | None = None,
) -> Self
Construct a TaskLock from a name and a pre-built TaskLockConfig.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
The name of the resource to lock. Acts as the instance identity. Used as the backend
lock key and exposed via the
TYPE:
|
config
|
The pre-built task lock configuration. Use this path when the configuration is assembled at
startup from a settings tree (for example YAML, Vault,
or a
TYPE:
|
backend
|
The distributed lock backend used to acquire and release the lock. By default, it will use the lock backend registry to get the default lock backend.
TYPE:
|
refresh
async
refresh() -> None
Renew the lease for another lease_duration without releasing.
| RAISES | DESCRIPTION |
|---|---|
LockNotOwnedError
|
If this task does not hold the lock or the lease was lost. |
LockAcquireError
|
If the backend call fails. |
locked
async
locked() -> bool
Check if the lock is acquired.
| RAISES | DESCRIPTION |
|---|---|
LockLockedCheckError
|
If the lock cannot be checked due to an error on the backend. |
do_acquire
async
do_acquire(token: str, *, duration: Seconds) -> bool
Acquire the lock.
This method should not be called directly. Use the context manager instead.
| PARAMETER | DESCRIPTION |
|---|---|
token
|
The token to register on the backend.
TYPE:
|
duration
|
The lease duration to request, in seconds. The
caller captures this from
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
bool
|
True if the lock was acquired, False if the lock was not acquired.
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
LockAcquireError
|
If the lock cannot be acquired due to an error on the backend. |
do_release
async
do_release(token: str) -> bool
Release the lock.
This method should not be called directly. Use the context manager instead.
| RETURNS | DESCRIPTION |
|---|---|
bool
|
True if the lock was released, False otherwise.
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
LockReleaseError
|
Cannot release the lock due to backend error. |
do_reacquire
async
do_reacquire(token: str, duration: float) -> bool
Re-acquire the lock with a specific duration.
This method should not be called directly. Use the context manager instead.
| RETURNS | DESCRIPTION |
|---|---|
bool
|
True if the lock was re-acquired, False otherwise.
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
LockReleaseError
|
Cannot re-acquire the lock due to backend error. |
do_thread_enter
async
do_thread_enter() -> None
Acquire the lock from a worker thread.
Runs entirely on the event loop so the reentrant check, token generation, and backend acquire are atomic with respect to other threads.
| RAISES | DESCRIPTION |
|---|---|
WouldBlockError
|
If the lock is already held by another worker. |
LockAcquireError
|
If the lock cannot be acquired due to a backend error. |
LockReentrantError
|
If the lock is already acquired (nested usage is not supported). |
do_thread_exit
async
do_thread_exit() -> None
Release or extend the lock from a worker thread.
Runs entirely on the event loop so the token generation and backend release are atomic with respect to other threads.
| RAISES | DESCRIPTION |
|---|---|
LockReleaseError
|
If the lock cannot be released due to a backend error. |
do_exit
async
do_exit(token: str, *, min_hold_duration: Seconds) -> None
Handle exit logic: release or re-acquire based on elapsed time.
| PARAMETER | DESCRIPTION |
|---|---|
token
|
The token used to release or re-acquire the lock.
TYPE:
|
min_hold_duration
|
The minimum hold duration to enforce, in
seconds. The caller captures this from
TYPE:
|
LeaderElection
LeaderElection(
name: str,
*,
backend: LeaderElectionBackend | str | None = None,
worker: str | UUID | None = None,
lease_duration: Seconds | None = None,
renew_deadline: Seconds | None = None,
retry_interval: Seconds | None = None,
retry_jitter: float | None = None,
backend_timeout: Seconds | None = None,
error_interval: Seconds | None = None,
env_prefix: str | None = None,
env_load: bool | None = None,
metadata: Mapping[str, str] | None = None,
)
Bases: Reconfigurable[LeaderElectionConfig], LockPrimitive, Task
Leader Election.
The leader election is a synchronization primitive with the worker as scope. It runs as a task to acquire or renew the distributed lock.
Supports live reconfiguration via
reconfigure(new_config).
A swap takes effect on the next renew loop iteration. The
worker field cannot change. Changing it raises ValueError.
See Live reconfiguration.
Initialize the leader election.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
The name of the resource representing the leader election. It will be used as the lock name so make sure it is unique on the distributed lock backend.
TYPE:
|
backend
|
The leader election backend used to acquire and renew leadership. By default, it resolves the
TYPE:
|
worker
|
The worker identity. By default, a UUIDv1 will be generated.
TYPE:
|
lease_duration
|
The duration in seconds after the lock will be released if not renewed. Default: 15. If the worker becomes unavailable, the lock
can only be acquired by another worker after it has
expired. When unset and env reads are enabled (see
TYPE:
|
renew_deadline
|
The duration in seconds that the leader worker will try to acquire the lock before giving up. Default: 10. Must be shorter than the lease duration. In case of multiple errors, the leader worker will lose the lead to prevent split-brain scenarios and ensure that only one worker is the leader at any time.
TYPE:
|
retry_interval
|
The duration in seconds between attempts to acquire or renew the lock. Default: 2. Must be shorter than the renew deadline. A shorter schedule enables faster leader elections but may increase load on the distributed lock backend, while a longer schedule reduces load but can delay new leader elections.
TYPE:
|
retry_jitter
|
The jitter factor applied to the retry interval. Default: 0.1. Each sleep becomes
TYPE:
|
backend_timeout
|
The duration in seconds for waiting on backend for acquiring and releasing the lock. Default: 5. This value determines how long the system will wait before giving up the current operation.
TYPE:
|
error_interval
|
The duration in seconds between logging error messages. Default: 30. If shorter than the retry interval, it will log every error. It is used to prevent flooding the logs when the lock backend is unavailable.
TYPE:
|
env_prefix
|
Override the auto-derived environment variable prefix. Default:
TYPE:
|
env_load
|
Whether to read environment variables. When None (the default), follow the process-wide
TYPE:
|
metadata
|
Free-form key/value pairs stored on the lease while this worker
leads, for observability (pod name, version, region). Other
workers read them via
TYPE:
|
name
property
name: str
Return the task name.
record
property
record: LeaderRecord | None
The most recent LeaderRecord seen from the backend.
None until the first acquire/renew attempt completes. Reflects the
current leader (record.holder), when they acquired and renewed the
lease, how many times leadership has changed, and the holder's
metadata. Updated on every renew loop iteration.
backend
property
backend: LeaderElectionBackend
Bound coordination backend, resolved on each call.
When a backend instance was passed at construction it is
always returned. Otherwise the active Grelmicro app is
consulted via Grelmicro.current() on every access so that
micro.override(Coordination(...)) blocks take effect. The
backend comes from the Coordination component, whose election
backend can point at a different vendor than its lock backend.
| RAISES | DESCRIPTION |
|---|---|
OutOfContextError
|
No backend resolved in this scope. Pass
|
from_config
classmethod
from_config(
name: str,
config: LeaderElectionConfig,
*,
backend: LeaderElectionBackend | str | None = None,
metadata: Mapping[str, str] | None = None,
) -> Self
Construct a LeaderElection from a name and a pre-built LeaderElectionConfig.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
The name of the resource representing the leader election. Acts as the instance identity. Used as the backend
lock key and exposed via the
TYPE:
|
config
|
The pre-built leader election configuration. Use this path when the configuration is assembled at
startup from a settings tree (for example YAML, Vault,
or a
TYPE:
|
backend
|
The leader election backend used to acquire and renew leadership. By default, it resolves the
TYPE:
|
metadata
|
Free-form key/value pairs stored on the lease while this worker leads, for observability.
TYPE:
|
is_running
is_running() -> bool
Check if the leader election task is running.
is_leader
is_leader() -> bool
Check if the current worker is the leader.
This is an advisory local view. The result reflects the
last backend response plus the configured renew_deadline.
During a backend partition the answer can remain True
until the renew deadline elapses, even if another worker has
already acquired leadership through a reachable backend.
For work that cannot tolerate stale local leadership, use
is_leader_confirmed_within
with a tighter freshness bound, or fence each backend write
with the lock token.
| RETURNS | DESCRIPTION |
|---|---|
bool
|
True if the current worker is the leader (subject to the |
bool
|
uncertainty window described above), False otherwise. |
last_confirmation_age
last_confirmation_age() -> float | None
Seconds since the last backend response that confirmed leadership.
Returns None until the first acquisition succeeds, and is
reset to None whenever leadership is lost. The value
grows during a backend partition because the underlying
timestamp is only refreshed when the backend responds with
"you still hold the lock".
is_leader_confirmed_within
is_leader_confirmed_within(max_age: float) -> bool
Stricter than is_leader: require a recent backend confirmation.
Returns True only when the local view says leader AND the
last backend response confirming leadership is at most
max_age seconds old. Use this for fan-out work that must
not run on a worker whose leadership is uncertain (for
example, a global migration step or a single-writer task that
is not separately fenced).
| PARAMETER | DESCRIPTION |
|---|---|
max_age
|
Maximum acceptable age in seconds since the
last backend-confirmed renewal. Typical values are
less than
TYPE:
|
wait_for_leader
async
wait_for_leader() -> None
Wait until the current worker is the leader.
wait_lose_leader
async
wait_lose_leader() -> None
Wait until the current worker is no longer the leader.
lead
async
lead(
func: Callable[..., Awaitable[T]],
/,
*args: object,
repeat: bool = False,
) -> T | None
Run func(*args) only while this worker holds leadership.
Waits for leadership, then runs func(*args) in a child task.
The moment leadership is lost the task is cancelled, so no stale
work outlives the lease. This is the difference from a plain
if is_leader(): check, which keeps running until the next poll.
Returns func's result if it finishes while still leader, or
None if leadership was lost first and the body was cancelled.
With repeat=True, waits to re-acquire and runs func again
instead of returning, looping until the surrounding scope is
cancelled.
Requires the LeaderElection service to be running concurrently
(wired through Tasks, or run as a task), since it renews the
lease and drives the leadership changes lead waits on.
Any exception raised by func propagates out of lead.
Cancellation is cooperative: it takes effect at the body's next
await. Pair with is_leader_confirmed_within or a fencing
token for writes that must never overlap a successor.
async def emit_metrics() -> None:
while True:
await push_snapshot()
await asyncio.sleep(10)
await election.lead(emit_metrics, repeat=True)
| PARAMETER | DESCRIPTION |
|---|---|
func
|
Coroutine function to run only while this worker leads.
TYPE:
|
repeat
|
Re-run
TYPE:
|
guard
guard() -> _LeaderGuard
Return a non-blocking synchronization guard.
The guard raises WouldBlock if the current worker is not the leader,
making it suitable for use as the sync parameter of IntervalTask.
Unlike using LeaderElection directly (which blocks until leader),
the guard skips the current tick and retries on the next interval.
LeaderElectionConfig
Bases: BaseLockConfig
Leader Election Config.
Leader election based on a leased reentrant distributed lock.
lease_duration
class-attribute
instance-attribute
lease_duration: Seconds = 15
The lease duration in seconds.
renew_deadline
class-attribute
instance-attribute
renew_deadline: Seconds = 10
The renew deadline in seconds.
retry_interval
class-attribute
instance-attribute
retry_interval: Seconds = 2
The retry interval in seconds.
backend_timeout
class-attribute
instance-attribute
backend_timeout: Seconds = 5
The backend timeout in seconds.
error_interval
class-attribute
instance-attribute
error_interval: Seconds = 30
The error interval in seconds.
retry_jitter
class-attribute
instance-attribute
retry_jitter: float = 0.1
Factor for randomized jitter applied to each retry sleep.
Each sleep becomes retry_interval * uniform(1 - retry_jitter, 1 + retry_jitter). Set to 0 to disable jitter. Must be >= 0 and < 1.
LeaderElectionBackend
Bases: Protocol
Leader Election Backend Protocol.
Optimized for leader election rather than general mutual exclusion: one
renewable lease per election that stores a LeaderRecord, held continuously
by the elected worker and renewed before it expires. A vendor backs it with
whatever native lease it offers (a Redis value, a Postgres row, a Kubernetes
Lease), storing the record alongside.
acquire_or_renew
async
acquire_or_renew(
*,
name: str,
token: str,
duration: float,
metadata: Mapping[str, str] | None = None,
) -> LeaderRecord
Acquire leadership, or renew it when token already holds it.
Returns the resulting LeaderRecord. The caller leads when
record.holder == token, otherwise the record describes the current
leader. Acquiring from a different (or expired) holder bumps
transitions, renewing the same holder only moves renewed_at.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
Identifier of the election to acquire or renew.
TYPE:
|
token
|
Worker token. The same token renews the lease, a different token may take over once the lease expires.
TYPE:
|
duration
|
Seconds the lease is held before it expires. The leader renews before this elapses.
TYPE:
|
metadata
|
Free-form key/value pairs to store on the lease while this worker holds it.
TYPE:
|
release
async
release(*, name: str, token: str) -> bool
Release leadership held by token.
Returns True when leadership was released, False when token did
not hold it.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
Identifier of the election to release.
TYPE:
|
token
|
Worker token. The backend only releases a matching holder.
TYPE:
|
get
async
get(*, name: str) -> LeaderRecord | None
Return the current LeaderRecord, or None when no live lease exists.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
Identifier of the election to inspect.
TYPE:
|
LockBackend
Bases: Protocol
Lock Backend Protocol.
This is the low-level API for the distributed lock backend that is platform agnostic.
Implementations capture the running event loop on __aenter__
in a _loop attribute so lock adapters (Lock.from_thread,
TaskLock.from_thread) can dispatch coroutines back into it.
acquire
async
acquire(
*, name: str, token: str, duration: float
) -> int | None
Acquire the lock.
Returns the fencing token when the lock was granted, None when
another token already holds it.
The fencing token is a strictly increasing integer per lock name. It increments on every free-to-held transition (a fresh acquire by a new holder, or a takeover of an expired lock) and keeps climbing across release and re-acquire cycles. The same holder renewing or extending its lease receives the same token.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
Identifier of the lock to acquire.
TYPE:
|
token
|
Caller-supplied ownership token. The same token must be passed to
TYPE:
|
duration
|
Seconds the lock is held before the backend may release it automatically. The acquirer should renew before this elapses.
TYPE:
|
release
async
release(*, name: str, token: str) -> bool
Release the lock.
Returns True when the lock was released, False when the
token did not own the lock.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
Identifier of the lock to release.
TYPE:
|
token
|
Ownership token previously passed to
TYPE:
|
locked
async
locked(*, name: str) -> bool
Return True when the lock is currently held by any token.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
Identifier of the lock to inspect.
TYPE:
|
owned
async
owned(*, name: str, token: str) -> bool
Return True when the lock is currently held by token.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
Identifier of the lock to inspect.
TYPE:
|
token
|
Ownership token to compare against the current holder.
TYPE:
|
LeaderRecord
dataclass
LeaderRecord(
holder: str,
lease_duration: float,
acquired_at: datetime,
renewed_at: datetime,
transitions: int,
metadata: Mapping[str, str] = dict(),
)
The state of a leader election lease.
Unlike a Lock, a leader election lease carries observable state about who
leads and since when. The shape follows the Kubernetes LeaderElectionRecord
so the same record round-trips through a Redis value, a Postgres row, or a
Kubernetes Lease.
holder
instance-attribute
holder: str
Token of the worker that currently holds the lease.
lease_duration
instance-attribute
lease_duration: float
Seconds the lease is valid from renewed_at before it expires.
acquired_at
instance-attribute
acquired_at: datetime
When the current holder first acquired the lease.
renewed_at
instance-attribute
renewed_at: datetime
When the current holder last renewed the lease.
transitions
instance-attribute
transitions: int
Number of times the lease has changed holder.
metadata
class-attribute
instance-attribute
metadata: Mapping[str, str] = field(default_factory=dict)
Free-form key/value pairs the holder attached, for observability (pod name, version, region). Empty when none were set.
CoordinationError
Bases: GrelmicroError
Coordination Primitive Error.
This is the base class for all coordination errors.