@ -122,7 +122,7 @@ TENANT_APPS = [
|
|||||||
"authentik.stages.user_login",
|
"authentik.stages.user_login",
|
||||||
"authentik.stages.user_logout",
|
"authentik.stages.user_logout",
|
||||||
"authentik.stages.user_write",
|
"authentik.stages.user_write",
|
||||||
# "authentik.tasks",
|
"authentik.tasks",
|
||||||
"authentik.brands",
|
"authentik.brands",
|
||||||
"authentik.blueprints",
|
"authentik.blueprints",
|
||||||
"guardian",
|
"guardian",
|
||||||
|
|||||||
@ -1,30 +1,29 @@
|
|||||||
import logging
|
|
||||||
from psycopg.errors import AdminShutdown
|
|
||||||
import tenacity
|
|
||||||
import functools
|
import functools
|
||||||
from pglock.core import _cast_lock_id
|
import logging
|
||||||
from django.utils import timezone
|
|
||||||
from random import randint
|
|
||||||
import time
|
import time
|
||||||
from django.db.backends.postgresql.base import DatabaseWrapper
|
from collections.abc import Iterable
|
||||||
from typing import Iterable
|
from queue import Empty, Queue
|
||||||
from django.db import DEFAULT_DB_ALIAS, DatabaseError, InterfaceError, OperationalError, connections
|
from random import randint
|
||||||
from queue import Queue, Empty
|
|
||||||
|
|
||||||
from django.db.models import QuerySet
|
|
||||||
from dramatiq.broker import Broker, Consumer, MessageProxy
|
|
||||||
from dramatiq.message import Message
|
|
||||||
from dramatiq.common import compute_backoff, current_millis, dq_name, xq_name
|
|
||||||
from dramatiq.results import Results
|
|
||||||
from dramatiq.errors import QueueJoinTimeout, ConnectionError
|
|
||||||
import orjson
|
import orjson
|
||||||
|
import tenacity
|
||||||
|
from django.db import DEFAULT_DB_ALIAS, DatabaseError, InterfaceError, OperationalError, connections
|
||||||
|
from django.db.backends.postgresql.base import DatabaseWrapper
|
||||||
|
from django.db.models import QuerySet
|
||||||
|
from django.utils import timezone
|
||||||
|
from dramatiq.broker import Broker, Consumer, MessageProxy
|
||||||
|
from dramatiq.common import compute_backoff, current_millis, dq_name, xq_name
|
||||||
|
from dramatiq.errors import ConnectionError, QueueJoinTimeout
|
||||||
|
from dramatiq.message import Message
|
||||||
|
from dramatiq.results import Results
|
||||||
|
from pglock.core import _cast_lock_id
|
||||||
from psycopg import Notify
|
from psycopg import Notify
|
||||||
|
from psycopg.errors import AdminShutdown
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.tasks.models import Queue
|
from authentik.tasks.models import Queue as MQueue
|
||||||
from authentik.tasks.results import PostgresBackend
|
from authentik.tasks.results import PostgresBackend
|
||||||
|
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
@ -61,7 +60,7 @@ class PostgresBroker(Broker):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def query_set(self) -> QuerySet:
|
def query_set(self) -> QuerySet:
|
||||||
return Queue.objects.using(self.db_alias)
|
return MQueue.objects.using(self.db_alias)
|
||||||
|
|
||||||
def consume(self, queue_name: str, prefetch: int = 1, timeout: int = 30000) -> Consumer:
|
def consume(self, queue_name: str, prefetch: int = 1, timeout: int = 30000) -> Consumer:
|
||||||
self.declare_queue(queue_name)
|
self.declare_queue(queue_name)
|
||||||
@ -119,7 +118,7 @@ class PostgresBroker(Broker):
|
|||||||
self.query_set.create(
|
self.query_set.create(
|
||||||
message_id=message.message_id,
|
message_id=message.message_id,
|
||||||
queue_name=message.queue_name,
|
queue_name=message.queue_name,
|
||||||
state=Queue.State.QUEUED,
|
state=MQueue.State.QUEUED,
|
||||||
message=message.encode(),
|
message=message.encode(),
|
||||||
)
|
)
|
||||||
self.emit_after("enqueue", message, delay)
|
self.emit_after("enqueue", message, delay)
|
||||||
@ -154,7 +153,7 @@ class PostgresBroker(Broker):
|
|||||||
if (
|
if (
|
||||||
self.query_set.filter(
|
self.query_set.filter(
|
||||||
queue_name=queue_name,
|
queue_name=queue_name,
|
||||||
state__in=(Queue.State.QUEUED, Queue.State.CONSUMED),
|
state__in=(MQueue.State.QUEUED, MQueue.State.CONSUMED),
|
||||||
)
|
)
|
||||||
== 0
|
== 0
|
||||||
):
|
):
|
||||||
@ -185,7 +184,7 @@ class _PostgresConsumer(Consumer):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def query_set(self) -> QuerySet:
|
def query_set(self) -> QuerySet:
|
||||||
return Queue.objects.using(self.db_alias)
|
return MQueue.objects.using(self.db_alias)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def listen_connection(self) -> DatabaseWrapper:
|
def listen_connection(self) -> DatabaseWrapper:
|
||||||
@ -207,9 +206,9 @@ class _PostgresConsumer(Consumer):
|
|||||||
self.query_set.filter(
|
self.query_set.filter(
|
||||||
message_id=message.message_id,
|
message_id=message.message_id,
|
||||||
queue_name=message.queue_name,
|
queue_name=message.queue_name,
|
||||||
state=Queue.State.CONSUMED,
|
state=MQueue.State.CONSUMED,
|
||||||
).update(
|
).update(
|
||||||
state=Queue.State.DONE,
|
state=MQueue.State.DONE,
|
||||||
message=message.encode(),
|
message=message.encode(),
|
||||||
)
|
)
|
||||||
self.in_processing.remove(message.message_id)
|
self.in_processing.remove(message.message_id)
|
||||||
@ -220,9 +219,9 @@ class _PostgresConsumer(Consumer):
|
|||||||
self.query_set.filter(
|
self.query_set.filter(
|
||||||
message_id=message.message_id,
|
message_id=message.message_id,
|
||||||
queue_name=message.queue_name,
|
queue_name=message.queue_name,
|
||||||
state__ne=Queue.State.REJECTED,
|
state__ne=MQueue.State.REJECTED,
|
||||||
).update(
|
).update(
|
||||||
state=Queue.State.REJECT,
|
state=MQueue.State.REJECT,
|
||||||
message=message.encode(),
|
message=message.encode(),
|
||||||
)
|
)
|
||||||
self.in_processing.remove(message.message_id)
|
self.in_processing.remove(message.message_id)
|
||||||
@ -232,14 +231,14 @@ class _PostgresConsumer(Consumer):
|
|||||||
self.query_set.filter(
|
self.query_set.filter(
|
||||||
message_id__in=[message.message_id for message in messages],
|
message_id__in=[message.message_id for message in messages],
|
||||||
).update(
|
).update(
|
||||||
state=Queue.State.QUEUED,
|
state=MQueue.State.QUEUED,
|
||||||
)
|
)
|
||||||
# We don't care about locks, requeue occurs on worker stop
|
# We don't care about locks, requeue occurs on worker stop
|
||||||
|
|
||||||
def _fetch_pending_notifies(self) -> list[Notify]:
|
def _fetch_pending_notifies(self) -> list[Notify]:
|
||||||
self.logger.debug(f"Polling for lost messages in {self.queue_name}")
|
self.logger.debug(f"Polling for lost messages in {self.queue_name}")
|
||||||
notifies = self.query_set.filter(
|
notifies = self.query_set.filter(
|
||||||
state__in=(Queue.State.QUEUED, Queue.State.CONSUMED), queue_name=self.queue_name
|
state__in=(MQueue.State.QUEUED, MQueue.State.CONSUMED), queue_name=self.queue_name
|
||||||
)
|
)
|
||||||
channel = channel_name(self.connection, self.queue_name, "enqueue")
|
channel = channel_name(self.connection, self.queue_name, "enqueue")
|
||||||
return [Notify(pid=0, channel=channel, payload=item.message) for item in notifies]
|
return [Notify(pid=0, channel=channel, payload=item.message) for item in notifies]
|
||||||
@ -252,7 +251,7 @@ class _PostgresConsumer(Consumer):
|
|||||||
|
|
||||||
def _get_message_lock_id(self, message: Message) -> int:
|
def _get_message_lock_id(self, message: Message) -> int:
|
||||||
return _cast_lock_id(
|
return _cast_lock_id(
|
||||||
f"{channel_name(connections[self.connection], self.queue_name, 'lock')}.{message.message_id}"
|
f"{channel_name(connections[self.connection], self.queue_name, 'lock')}.{message.message_id}" # noqa: E501
|
||||||
)
|
)
|
||||||
|
|
||||||
def _consume_one(self, message: Message) -> bool:
|
def _consume_one(self, message: Message) -> bool:
|
||||||
@ -262,9 +261,10 @@ class _PostgresConsumer(Consumer):
|
|||||||
|
|
||||||
result = (
|
result = (
|
||||||
self.query_set.filter(
|
self.query_set.filter(
|
||||||
message_id=message.message_id, state__in=(Queue.State.QUEUED, Queue.State.CONSUMED)
|
message_id=message.message_id,
|
||||||
|
state__in=(MQueue.State.QUEUED, MQueue.State.CONSUMED),
|
||||||
)
|
)
|
||||||
.update(state=Queue.State.CONSUMED, mtime=timezone.now())
|
.update(state=MQueue.State.CONSUMED, mtime=timezone.now())
|
||||||
.extra(where=["pg_try_advisory_lock(%s)"], params=[self._get_message_lock_id(message)])
|
.extra(where=["pg_try_advisory_lock(%s)"], params=[self._get_message_lock_id(message)])
|
||||||
)
|
)
|
||||||
return result == 1
|
return result == 1
|
||||||
@ -276,7 +276,7 @@ class _PostgresConsumer(Consumer):
|
|||||||
# If we don't have a connection yet, fetch missed notifications from the table directly
|
# If we don't have a connection yet, fetch missed notifications from the table directly
|
||||||
if self._listen_connection is None:
|
if self._listen_connection is None:
|
||||||
# We might miss a notification between the initial query and the first time we wait for
|
# We might miss a notification between the initial query and the first time we wait for
|
||||||
# notitications, it doesn't matter because we re-fetch for missed messages later on.
|
# notifications, it doesn't matter because we re-fetch for missed messages later on.
|
||||||
self.notifies = self._fetch_pending_notifies()
|
self.notifies = self._fetch_pending_notifies()
|
||||||
self.logger.debug(
|
self.logger.debug(
|
||||||
f"Found {len(self.notifies)} pending messages in queue {self.queue_name}"
|
f"Found {len(self.notifies)} pending messages in queue {self.queue_name}"
|
||||||
@ -340,7 +340,7 @@ class _PostgresConsumer(Consumer):
|
|||||||
return
|
return
|
||||||
self.logger.debug("Running garbage collector")
|
self.logger.debug("Running garbage collector")
|
||||||
count = self.query_set.filter(
|
count = self.query_set.filter(
|
||||||
state__in=(Queue.State.DONE, Queue.State.REJECTED),
|
state__in=(MQueue.State.DONE, MQueue.State.REJECTED),
|
||||||
mtime__lte=timezone.now() - timezone.timedelta(days=30),
|
mtime__lte=timezone.now() - timezone.timedelta(days=30),
|
||||||
).delete()
|
).delete()
|
||||||
self.logger.info(f"Purged {count} messages in all queues")
|
self.logger.info(f"Purged {count} messages in all queues")
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 5.0.12 on 2025-03-08 15:35
|
# Generated by Django 5.0.12 on 2025-03-08 22:39
|
||||||
|
|
||||||
import django.utils.timezone
|
import django.utils.timezone
|
||||||
import uuid
|
import uuid
|
||||||
@ -37,7 +37,7 @@ class Migration(migrations.Migration):
|
|||||||
("mtime", models.DateTimeField(default=django.utils.timezone.now)),
|
("mtime", models.DateTimeField(default=django.utils.timezone.now)),
|
||||||
("message", models.JSONField(blank=True, null=True)),
|
("message", models.JSONField(blank=True, null=True)),
|
||||||
("result", models.JSONField(blank=True, null=True)),
|
("result", models.JSONField(blank=True, null=True)),
|
||||||
("result_ttl", models.DateTimeField()),
|
("result_ttl", models.DateTimeField(blank=True, null=True)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
"indexes": [
|
"indexes": [
|
||||||
@ -45,5 +45,7 @@ class Migration(migrations.Migration):
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.RunSQL("ALTER TABLE authentik_tasks_queue SET WITHOUT OIDS;", migrations.RunSQL.noop),
|
migrations.RunSQL(
|
||||||
|
"ALTER TABLE authentik_tasks_queue SET WITHOUT OIDS;", migrations.RunSQL.noop
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
@ -19,7 +20,7 @@ class Queue(models.Model):
|
|||||||
result_ttl = models.DateTimeField(blank=True, null=True)
|
result_ttl = models.DateTimeField(blank=True, null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
indexes = (
|
indexes = (models.Index(fields=("state", "mtime")),)
|
||||||
models.Index(fields=("state", "mtime")),
|
|
||||||
models.Index(fields=("mesage__actor_name",)),
|
def __str__(self):
|
||||||
)
|
return str(self.message_id)
|
||||||
|
|||||||
@ -2,7 +2,7 @@ from django.db import DEFAULT_DB_ALIAS
|
|||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from dramatiq.message import Message, get_encoder
|
from dramatiq.message import Message, get_encoder
|
||||||
from dramatiq.results.backend import MResult, Missing, Result, ResultBackend
|
from dramatiq.results.backend import Missing, MResult, Result, ResultBackend
|
||||||
|
|
||||||
from authentik.tasks.models import Queue
|
from authentik.tasks.models import Queue
|
||||||
|
|
||||||
|
|||||||
@ -26,6 +26,7 @@ skip = [
|
|||||||
"./gen-go-api",
|
"./gen-go-api",
|
||||||
"*.api.mdx",
|
"*.api.mdx",
|
||||||
"./htmlcov",
|
"./htmlcov",
|
||||||
|
"./web/custom-elements.json",
|
||||||
]
|
]
|
||||||
dictionary = ".github/codespell-dictionary.txt,-"
|
dictionary = ".github/codespell-dictionary.txt,-"
|
||||||
ignore-words = ".github/codespell-words.txt"
|
ignore-words = ".github/codespell-words.txt"
|
||||||
|
|||||||
Reference in New Issue
Block a user