import asyncio
from datetime import datetime, timedelta, timezone
from typing import TYPE_CHECKING, Any
from litestar_queues.backends._notification_wait import PendingNativeRead
from litestar_queues.backends.base import (
STALE_HEARTBEAT_ERROR,
BaseQueueBackend,
attempts_consumed,
interruption_count,
is_external_dispatch_reservation,
record_matches_filters,
retry_schedule,
stale_requeue_error,
stale_requeue_priority,
)
from litestar_queues.backends.memory.event_log import InMemoryQueueEventLog
from litestar_queues.models import (
HeartbeatTouchResult,
QueueBackendCapabilities,
QueuedTaskRecord,
QueueStatistics,
StaleTaskRecoveryResult,
TaskReservation,
)
if TYPE_CHECKING:
from collections.abc import Mapping, Sequence
from uuid import UUID
from litestar_queues.config import QueueConfig
from litestar_queues.events import EventHistoryConfig, QueueEventLog
from litestar_queues.models import HeartbeatTouch, TaskRequest
__all__ = ("InMemoryQueueBackend",)
[docs]
class InMemoryQueueBackend(BaseQueueBackend):
"""In-process queue backend for tests, local development, and examples."""
__slots__ = (
"_control_event",
"_control_pending_read",
"_event_log",
"_keys",
"_lock",
"_maintenances",
"_notification_event",
"_pending_read",
"_records",
"_reservations",
)
[docs]
def __init__(self, config: "QueueConfig | None" = None) -> "None":
super().__init__(config=config)
self._records: "dict[UUID, QueuedTaskRecord]" = {}
self._keys: "dict[str, UUID]" = {}
self._reservations: "dict[str, TaskReservation]" = {}
self._lock = asyncio.Lock()
self._notification_event = asyncio.Event()
self._pending_read = PendingNativeRead()
self._control_event = asyncio.Event()
self._control_pending_read = PendingNativeRead()
self._event_log: "QueueEventLog | None" = None
self._maintenances: "dict[str, tuple[str, datetime]]" = {}
@property
def capabilities(self) -> "QueueBackendCapabilities":
"""Backend behavior capabilities."""
return QueueBackendCapabilities(
supports_worker_wakeups=True,
wakeup_backend="asyncio-event",
wakeups_durable=False,
supports_maintenance=True,
)
[docs]
def get_event_log(self, config: "EventHistoryConfig") -> "QueueEventLog | None":
"""Return bounded, process-local queue event history when enabled."""
if self._event_log is None:
self._event_log = InMemoryQueueEventLog(config)
return self._event_log
[docs]
async def enqueue(
self,
task_name: "str",
*,
args: "tuple[Any, ...]" = (),
kwargs: "dict[str, Any] | None" = None,
queue: "str" = "default",
priority: "int" = 0,
max_retries: "int" = 0,
scheduled_at: "datetime | None" = None,
expires_at: "datetime | None" = None,
key: "str | None" = None,
execution_backend: "str" = "local",
execution_profile: "str | None" = None,
metadata: "dict[str, Any] | None" = None,
id: "UUID | None" = None, # noqa: A002
) -> "QueuedTaskRecord":
async with self._lock:
if key is not None:
existing_id = self._keys.get(key)
if existing_id is not None:
existing = self._records.get(existing_id)
if existing is not None and not existing.is_terminal:
return existing
now = _utc_now()
record = QueuedTaskRecord(
task_name=task_name,
args=args,
kwargs=dict(kwargs or {}),
queue=queue,
execution_backend=execution_backend,
execution_profile=execution_profile,
status="scheduled" if scheduled_at is not None and scheduled_at > _utc_now() else "pending",
priority=priority,
max_retries=max_retries,
scheduled_at=scheduled_at,
expires_at=expires_at,
key=key,
metadata=dict(metadata or {}),
created_at=now,
queued_at=now,
)
if id is not None:
record.id = id
self._records[record.id] = record
if key is not None:
self._keys[key] = record.id
await self.notify_new_task(record)
return record
[docs]
async def enqueue_many(self, requests: "Sequence[TaskRequest]") -> "list[QueuedTaskRecord]":
"""Persist multiple in-memory tasks while signaling waiters once.
Returns:
Queue task records in the same order as ``requests``.
"""
if not requests:
return []
records: "list[QueuedTaskRecord]" = []
now = _utc_now()
async with self._lock:
for request in requests:
if request.key is not None:
existing_id = self._keys.get(request.key)
if existing_id is not None:
existing = self._records.get(existing_id)
if existing is not None and not existing.is_terminal:
records.append(existing)
continue
record_now = _utc_now()
record = QueuedTaskRecord(
task_name=request.task_name,
args=request.args,
kwargs=dict(request.kwargs or {}),
queue=request.queue,
execution_backend=request.execution_backend,
execution_profile=request.execution_profile,
status=(
"scheduled" if request.scheduled_at is not None and request.scheduled_at > now else "pending"
),
priority=request.priority,
max_retries=request.max_retries,
scheduled_at=request.scheduled_at,
expires_at=request.expires_at,
key=request.key,
metadata=dict(request.metadata or {}),
created_at=record_now,
queued_at=record_now,
)
self._records[record.id] = record
if request.key is not None:
self._keys[request.key] = record.id
records.append(record)
await self.notify_new_tasks(records)
self._record_enqueue_batch(len(requests))
return records
[docs]
async def get_task(self, task_id: "UUID") -> "QueuedTaskRecord | None":
return self._records.get(task_id)
[docs]
async def get_task_by_key(self, key: "str") -> "QueuedTaskRecord | None":
task_id = self._keys.get(key)
if task_id is None:
return None
return self._records.get(task_id)
[docs]
async def get_tasks(self, task_ids: "Sequence[UUID]") -> "list[QueuedTaskRecord]":
return [record for task_id in task_ids if (record := self._records.get(task_id)) is not None]
[docs]
async def notify_worker_control(self, worker_id: "str | None") -> "None":
del worker_id
self._control_event.set()
[docs]
async def assign_worker(
self, task_id: "UUID", *, worker_id: "str", expected_retry_count: "int"
) -> "QueuedTaskRecord | None":
async with self._lock:
record = self._records.get(task_id)
if record is None or record.status != "running" or record.retry_count != expected_retry_count:
return None
record.worker_id = worker_id
return record
[docs]
async def list_pending(
self, *, limit: "int" = 1, queue: "str | None" = None, execution_backend: "str | None" = None
) -> "list[QueuedTaskRecord]":
due_records = [
record
for record in self._records.values()
if record.status in {"pending", "scheduled"}
and record.is_due
and not record.is_expired
and not is_external_dispatch_reservation(record.execution_ref)
and (queue is None or record.queue == queue)
and (execution_backend is None or record.execution_backend == execution_backend)
]
due_records.sort(key=lambda record: (-record.priority, record.queued_at, record.created_at, record.id.int))
return due_records[:limit]
[docs]
async def claim_task(
self, task_id: "UUID", *, expected_retry_count: "int | None" = None, expected_execution_ref: "str | None" = None
) -> "QueuedTaskRecord | None":
claimed, _ = await self.claim_task_with_expired(
task_id, expected_retry_count=expected_retry_count, expected_execution_ref=expected_execution_ref
)
return claimed
[docs]
async def claim_task_with_expired(
self, task_id: "UUID", *, expected_retry_count: "int | None" = None, expected_execution_ref: "str | None" = None
) -> "tuple[QueuedTaskRecord | None, QueuedTaskRecord | None]":
"""Claim one task and return an expiry transitioned under the same lock."""
async with self._lock:
record = self._records.get(task_id)
if (
record is None
or record.status not in {"pending", "scheduled"}
or not record.is_due
or is_external_dispatch_reservation(record.execution_ref)
or (expected_retry_count is not None and record.retry_count != expected_retry_count)
or (expected_execution_ref is not None and record.execution_ref != expected_execution_ref)
):
return None, None
now = _utc_now()
if record.execution_ref is None and record.expires_at is not None and record.expires_at <= now:
_expire_record(record, now)
return None, record
record.status = "running"
record.started_at = now
record.heartbeat_at = now
return record, None
[docs]
async def claim_many(
self,
*,
limit: "int",
queues: "tuple[str, ...]" = (),
execution_backend: "str | None" = None,
queue_limits: "Mapping[str, int] | None" = None,
) -> "list[QueuedTaskRecord]":
"""Claim up to ``limit`` due tasks under a single lock acquisition.
Selects eligible records with the same queue/execution/due filter and
priority ordering as :meth:`list_pending`, then transitions them to
``running`` inside one critical section using a single ``now`` snapshot.
The returned records carry the same owner/start/heartbeat fields a
sequential :meth:`claim_next` loop would produce.
Returns:
Claimed task records in claim order.
"""
claimed, _ = await self.claim_many_with_expired(
limit=limit, queues=queues, execution_backend=execution_backend, queue_limits=queue_limits
)
return claimed
[docs]
async def claim_many_with_expired( # noqa: C901
self,
*,
limit: "int",
queues: "tuple[str, ...]" = (),
execution_backend: "str | None" = None,
queue_limits: "Mapping[str, int] | None" = None,
) -> "tuple[list[QueuedTaskRecord], list[QueuedTaskRecord]]":
"""Claim records and return overdue records expired under the same lock."""
if limit <= 0:
return [], []
async with self._lock:
now = _utc_now()
eligible: "list[QueuedTaskRecord]" = []
expired: "list[QueuedTaskRecord]" = []
for record in self._records.values():
if record.status not in {"pending", "scheduled"}:
continue
if queues and record.queue not in queues:
continue
if execution_backend is not None and record.execution_backend != execution_backend:
continue
if is_external_dispatch_reservation(record.execution_ref):
continue
if record.expires_at is not None and record.expires_at <= now:
_expire_record(record, now)
expired.append(record)
continue
if record.scheduled_at is not None and record.scheduled_at > now:
continue
eligible.append(record)
eligible.sort(key=lambda record: (-record.priority, record.queued_at, record.created_at, record.id.int))
claimed: "list[QueuedTaskRecord]" = []
remaining = dict(queue_limits or {})
for record in eligible:
if len(claimed) >= limit:
break
if record.queue in remaining and remaining[record.queue] <= 0:
continue
record.status = "running"
record.started_at = now
record.heartbeat_at = now
claimed.append(record)
if record.queue in remaining:
remaining[record.queue] -= 1
return claimed, expired
[docs]
async def complete_task(
self, task_id: "UUID", *, result: "Any" = None, expected_retry_count: "int | None" = None
) -> "QueuedTaskRecord | None":
async with self._lock:
record = self._records.get(task_id)
if record is None:
return None
if expected_retry_count is not None and (
record.status != "running" or record.retry_count != expected_retry_count
):
return None
now = _utc_now()
record.status = "completed"
record.completed_at = now
record.heartbeat_at = None
record.result = result
record.error = None
return record
[docs]
async def fail_task(
self,
task_id: "UUID",
error: "str",
*,
retry: "bool" = True,
expected_retry_count: "int | None" = None,
retry_at: "datetime | None" = None,
queued_at: "datetime | None" = None,
) -> "QueuedTaskRecord | None":
async with self._lock:
record = self._records.get(task_id)
if record is None:
return None
if expected_retry_count is not None and (
record.status != "running" or record.retry_count != expected_retry_count
):
return None
record.error = error
if retry and attempts_consumed(record) < record.max_retries:
now = queued_at or _utc_now()
record.retry_count += 1
record.queued_at = now
record.scheduled_at = retry_at
record.status = "scheduled" if retry_at is not None and retry_at > now else "pending"
record.started_at = None
record.heartbeat_at = None
return record
now = _utc_now()
record.status = "failed"
record.completed_at = now
record.heartbeat_at = None
return record
[docs]
async def cancel_task(
self, task_id: "UUID", *, include_running: "bool" = False, expected_retry_count: "int | None" = None
) -> "bool":
async with self._lock:
record = self._records.get(task_id)
cancellable_statuses = {"pending", "scheduled", "running"} if include_running else {"pending", "scheduled"}
if (
record is None
or record.status not in cancellable_statuses
or (expected_retry_count is not None and record.retry_count != expected_retry_count)
):
return False
record.status = "cancelled"
record.completed_at = _utc_now()
record.heartbeat_at = None
return True
[docs]
async def interrupt_task(
self, task_id: "UUID", *, expected_retry_count: "int", worker_id: "str", queued_at: "datetime"
) -> "QueuedTaskRecord | None":
async with self._lock:
record = self._records.get(task_id)
if (
record is None
or record.status != "running"
or record.retry_count != expected_retry_count
or record.worker_id != worker_id
):
return None
record.status = "pending"
record.queued_at = queued_at
record.scheduled_at = None
record.started_at = None
record.heartbeat_at = None
record.completed_at = None
record.execution_ref = None
record.worker_id = None
record.metadata["interruptions"] = interruption_count(record) + 1
record.retry_count += 1
return record
[docs]
async def cancel_tasks(
self,
*,
task_name: "str | None" = None,
queue: "str | None" = None,
kwargs: "Mapping[str, Any] | None" = None,
metadata: "Mapping[str, Any] | None" = None,
include_running: "bool" = False,
) -> "int":
cancellable_statuses = {"pending", "scheduled", "running"} if include_running else {"pending", "scheduled"}
cancelled = 0
async with self._lock:
for record in self._records.values():
if record.status not in cancellable_statuses:
continue
if not record_matches_filters(
record, task_name=task_name, queue=queue, kwargs=kwargs, metadata=metadata
):
continue
record.status = "cancelled"
record.completed_at = _utc_now()
record.heartbeat_at = None
cancelled += 1
return cancelled
[docs]
async def touch_heartbeats(self, touches: "Sequence[HeartbeatTouch]") -> "HeartbeatTouchResult":
result = HeartbeatTouchResult()
if not touches:
return result
now = _utc_now()
async with self._lock:
for touch in touches:
record = self._records.get(touch.task_id)
if record is None or record.status != "running":
result.missed_task_ids.add(touch.task_id)
continue
if touch.expected_retry_count is not None and record.retry_count != touch.expected_retry_count:
result.missed_task_ids.add(touch.task_id)
continue
record.heartbeat_at = now
if touch.metadata_patch:
record.metadata.update(touch.metadata_patch)
result.touched_task_ids.add(touch.task_id)
return result
[docs]
async def null_heartbeats(self, task_ids: "list[UUID]", *, expected_retry_count: "int | None" = None) -> "None":
task_id_set = set(task_ids)
async with self._lock:
for task_id, record in self._records.items():
if task_id in task_id_set:
if expected_retry_count is not None and record.retry_count != expected_retry_count:
continue
record.heartbeat_at = None
[docs]
async def requeue_stale_running(
self, *, stale_after: "timedelta", limit: "int | None" = None
) -> "StaleTaskRecoveryResult":
cutoff = _utc_now() - stale_after
result = StaleTaskRecoveryResult()
async with self._lock:
candidates = [
record
for record in self._records.values()
if record.status == "running" and (record.heartbeat_at is None or record.heartbeat_at < cutoff)
]
candidates.sort(key=_stale_sort_key)
if limit is not None:
candidates = candidates[:limit]
for record in candidates:
requeue_on_stale = record.metadata.get("requeue_on_stale", True) is not False
if requeue_on_stale and attempts_consumed(record) < record.max_retries:
queued_at, retry_at = retry_schedule(record)
record.status = "scheduled" if retry_at is not None else "pending"
record.queued_at = queued_at
record.scheduled_at = retry_at
record.priority = stale_requeue_priority(record.priority, self._stale_requeue_priority_policy())
record.started_at = None
record.heartbeat_at = None
record.error = stale_requeue_error(record.error)
record.retry_count += 1
result.requeued += 1
continue
record.status = "failed"
record.completed_at = _utc_now()
record.heartbeat_at = None
record.error = STALE_HEARTBEAT_ERROR
result.failed += 1
result.failed_task_ids.append(record.id)
if not requeue_on_stale:
result.handler_needed += 1
result.handler_needed_task_ids.append(record.id)
return result
[docs]
async def expire_overdue(self, *, limit: "int | None" = None) -> "list[QueuedTaskRecord]":
"""Transition overdue pending or scheduled records to ``expired``.
Returns:
Records transitioned during this call.
"""
now = _utc_now()
expired: "list[QueuedTaskRecord]" = []
async with self._lock:
for record in self._records.values():
if limit is not None and len(expired) >= limit:
break
if record.status not in {"pending", "scheduled"}:
continue
if record.execution_ref is not None:
continue
if record.expires_at is None or record.expires_at > now:
continue
_expire_record(record, now)
expired.append(record)
return expired
[docs]
async def reserve_external_dispatch(
self,
task_id: "UUID",
execution_backend: "str",
reservation_ref: "str",
*,
execution_profile: "str | None" = None,
expected_retry_count: "int | None" = None,
) -> "QueuedTaskRecord | None":
now = _utc_now()
async with self._lock:
record = self._records.get(task_id)
if (
record is None
or record.status not in {"pending", "scheduled"}
or not record.is_due
or record.execution_ref is not None
or (expected_retry_count is not None and record.retry_count != expected_retry_count)
):
return None
if record.expires_at is not None and record.expires_at <= now:
_expire_record(record, now)
return None
record.execution_backend = execution_backend
record.execution_profile = execution_profile
record.execution_ref = reservation_ref
return record
[docs]
async def clear_execution_ref(
self, task_id: "UUID", expected_retry_count: "int", expected_execution_ref: "str"
) -> "QueuedTaskRecord | None":
async with self._lock:
record = self._records.get(task_id)
if (
record is None
or record.status not in {"pending", "scheduled"}
or record.retry_count != expected_retry_count
or record.execution_ref != expected_execution_ref
):
return None
record.execution_ref = None
await self.notify_new_task(record)
return record
[docs]
async def replace_execution_ref(
self, task_id: "UUID", expected_retry_count: "int", expected_execution_ref: "str", execution_ref: "str"
) -> "QueuedTaskRecord | None":
async with self._lock:
record = self._records.get(task_id)
if (
record is None
or record.status not in {"pending", "scheduled"}
or record.retry_count != expected_retry_count
or record.execution_ref != expected_execution_ref
):
return None
record.execution_ref = execution_ref
return record
[docs]
async def release_external_dispatch(
self,
task_id: "UUID",
reservation_ref: "str",
execution_backend: "str",
*,
execution_profile: "str | None" = None,
) -> "QueuedTaskRecord | None":
async with self._lock:
record = self._records.get(task_id)
if record is None or record.execution_ref != reservation_ref:
return None
record.execution_backend = execution_backend
record.execution_profile = execution_profile
record.execution_ref = None
await self.notify_new_task(record)
return record
[docs]
async def finalize_external_dispatch(
self,
task_id: "UUID",
reservation_ref: "str",
execution_backend: "str",
execution_ref: "str",
*,
execution_profile: "str | None" = None,
) -> "QueuedTaskRecord | None":
async with self._lock:
record = self._records.get(task_id)
if (
record is None
or record.execution_ref != reservation_ref
or record.status not in {"pending", "scheduled"}
):
return None
record.execution_backend = execution_backend
record.execution_profile = execution_profile
record.execution_ref = execution_ref
return record
[docs]
async def set_execution_ref(
self, task_id: "UUID", execution_backend: "str", execution_ref: "str", *, execution_profile: "str | None" = None
) -> "QueuedTaskRecord | None":
async with self._lock:
record = self._records.get(task_id)
if record is None:
return None
record.execution_backend = execution_backend
record.execution_profile = execution_profile
record.execution_ref = execution_ref
return record
[docs]
async def set_execution_backend(
self, task_id: "UUID", execution_backend: "str", *, execution_profile: "str | None" = None
) -> "QueuedTaskRecord | None":
async with self._lock:
record = self._records.get(task_id)
if record is None:
return None
record.execution_backend = execution_backend
record.execution_profile = execution_profile
record.execution_ref = None
await self.notify_new_task(record)
return record
[docs]
async def list_running_external(self, *, limit: "int | None" = None) -> "list[QueuedTaskRecord]":
records = [
record for record in self._records.values() if not record.is_terminal and record.execution_ref is not None
]
records.sort(key=lambda record: (record.started_at or record.created_at, str(record.id)))
return records[:limit] if limit is not None else records
[docs]
async def get_statistics(self, *, queue: "str | None" = None) -> "QueueStatistics":
statistics = QueueStatistics()
for record in self._records.values():
if queue is not None and record.queue != queue:
continue
setattr(statistics, record.status, getattr(statistics, record.status) + 1)
return statistics
[docs]
async def list_completed_by_task(
self, task_name: "str", *, since: "datetime | None" = None, limit: "int" = 10
) -> "list[QueuedTaskRecord]":
records = [
record
for record in self._records.values()
if record.task_name == task_name
and record.status == "completed"
and record.completed_at is not None
and (since is None or record.completed_at >= since)
]
records.sort(key=lambda record: record.completed_at or record.created_at, reverse=True)
return records[:limit]
[docs]
async def cleanup_terminal(self, before: "datetime", *, limit: "int | None" = None) -> "int":
removed = 0
async with self._lock:
candidates = [
record
for record in self._records.values()
if record.is_terminal and record.completed_at is not None and record.completed_at < before
]
candidates.sort(key=lambda record: (record.completed_at, str(record.id)))
if limit is not None:
candidates = candidates[:limit]
for record in candidates:
removed += 1
del self._records[record.id]
if record.key is not None and self._keys.get(record.key) == record.id:
del self._keys[record.key]
return removed
[docs]
async def acquire_maintenance(self, name: "str", token: "str", *, ttl: "timedelta") -> "bool":
"""Acquire expiring, token-fenced maintenance ownership under the async lock.
Returns:
True when ownership was granted to ``token``.
"""
async with self._lock:
now = _utc_now()
existing = self._maintenances.get(name)
if existing is not None and existing[1] > now and existing[0] != token:
return False
self._maintenances[name] = (token, now + ttl)
return True
[docs]
async def release_maintenance(self, name: "str", token: "str") -> "bool":
"""Release maintenance ownership only when ``token`` matches the holder.
Returns:
True when ownership held under ``token`` was released.
"""
async with self._lock:
existing = self._maintenances.get(name)
if existing is None or existing[0] != token:
return False
del self._maintenances[name]
return True
[docs]
async def reserve_identity(self, key: "str", *, task_id: "UUID", task_name: "str") -> "TaskReservation | None":
"""Reserve a forever identity under the shared lock beside key ownership.
Returns:
``None`` when this caller won the reservation; otherwise the existing
owner reservation.
"""
async with self._lock:
existing = self._reservations.get(key)
if existing is not None:
return existing
reservation = TaskReservation(key=key, task_id=task_id, task_name=task_name, created_at=_utc_now())
self._reservations[key] = reservation
return None
[docs]
async def has_identity(self, key: "str") -> "TaskReservation | None":
"""Return the reservation owning a reserved forever identity, if any."""
return self._reservations.get(key)
[docs]
async def reset_identity(self, key: "str", *, expected_task_id: "UUID | None" = None) -> "bool":
"""Delete a forever identity reservation under the shared lock.
Args:
key: The exact effective identity key.
expected_task_id: Optional task owner required for deletion.
Returns:
``True`` when a reservation was removed.
"""
async with self._lock:
owner = self._reservations.get(key)
if owner is None or (expected_task_id is not None and owner.task_id != expected_task_id):
return False
del self._reservations[key]
return True
[docs]
async def notify_new_task(self, record: "QueuedTaskRecord") -> "None":
if record.status in {"pending", "scheduled"} and record.is_due:
self._notification_event.set()
self._record_wakeup_emitted()
[docs]
async def wait_for_wakeups(self, timeout: "float | None" = None) -> "bool":
if not self._pending_read.has_pending and self._notification_event.is_set():
self._notification_event.clear()
return True
task = await self._pending_read.race(self._notification_event.wait, timeout)
if task is None:
return False
task.result()
self._notification_event.clear()
return True
[docs]
async def wait_for_worker_control(self, *, worker_id: "str", timeout: "float | None" = None) -> "bool":
del worker_id
if not self._control_pending_read.has_pending and self._control_event.is_set():
self._control_event.clear()
return True
task = await self._control_pending_read.race(self._control_event.wait, timeout)
if task is None:
return False
task.result()
self._control_event.clear()
return True
[docs]
async def time_until_next_due(self, *, queues: "tuple[str, ...]" = ()) -> "float | None":
"""Return seconds until the earliest not-yet-due pending/scheduled record.
Returns:
Seconds until the next due record, or ``None`` when there is no
upcoming scheduled work.
"""
now = _utc_now()
async with self._lock:
upcoming = [
record.scheduled_at
for record in self._records.values()
if record.status in {"pending", "scheduled"}
and record.scheduled_at is not None
and record.scheduled_at > now
and (not queues or record.queue in queues)
]
if not upcoming:
return None
return max((min(upcoming) - _utc_now()).total_seconds(), 0.0)
[docs]
async def close(self) -> "None":
"""Cancel any retained notification and worker-control waits."""
await self._pending_read.aclose()
await self._control_pending_read.aclose()
[docs]
async def clear(self) -> "None":
"""Clear all in-memory records."""
async with self._lock:
self._records.clear()
self._keys.clear()
self._maintenances.clear()
self._reservations.clear()
self._notification_event.clear()
self._control_event.clear()
await self._pending_read.aclose()
await self._control_pending_read.aclose()
if self._event_log is not None:
clear = getattr(self._event_log, "clear", None)
if clear is not None:
await clear()
_MIN_DATETIME = datetime(1, 1, 1, tzinfo=timezone.utc)
def _stale_sort_key(record: "QueuedTaskRecord") -> "tuple[datetime, str]":
"""Order stale candidates oldest-heartbeat-first, then by record id.
Records that never heartbeated sort first (most stale).
Returns:
A sort key of (effective heartbeat, record id).
"""
return (record.heartbeat_at or _MIN_DATETIME, str(record.id))
def _utc_now() -> "datetime":
return datetime.now(timezone.utc)
def _expire_record(record: "QueuedTaskRecord", completed_at: "datetime") -> "None":
record.status = "expired"
record.completed_at = completed_at
record.heartbeat_at = None