Support the combination (delay_on_commit, immediate_atomic, TASK_ALWAYS_EAGER)

As per the comment.

Additionally, the original of _start_transaction_under_autocommit
is stored on the contextprocessor now (which is the place with the
best match of the actual call-stack anyway). (Was needed for nested
invocations)
This commit is contained in:
Klaas van Schelven
2025-06-27 11:09:58 +02:00
parent 4ca15c7159
commit b5ffa154c1

View File

@@ -8,6 +8,8 @@ import threading
from django.db import transaction as django_db_transaction
from django.db import DEFAULT_DB_ALIAS
from snappea.settings import get_settings as get_snappea_settings
performance_logger = logging.getLogger("bugsink.performance.db")
local_storage = threading.local()
@@ -153,7 +155,7 @@ class ImmediateAtomic(SuperDurableAtomic):
connection = django_db_transaction.get_connection(self.using)
if hasattr(connection, "_start_transaction_under_autocommit"):
connection._start_transaction_under_autocommit_original = connection._start_transaction_under_autocommit
self._start_transaction_under_autocommit_original = connection._start_transaction_under_autocommit
connection._start_transaction_under_autocommit = types.MethodType(
_start_transaction_under_autocommit_patched, connection)
@@ -183,9 +185,9 @@ class ImmediateAtomic(SuperDurableAtomic):
performance_logger.info(f"{took * 1000:6.2f}ms IMMEDIATE transaction{using_clause}")
connection = django_db_transaction.get_connection(self.using)
if hasattr(connection, "_start_transaction_under_autocommit"):
connection._start_transaction_under_autocommit = connection._start_transaction_under_autocommit_original
del connection._start_transaction_under_autocommit_original
if hasattr(self, "_start_transaction_under_autocommit_original"):
connection._start_transaction_under_autocommit = self._start_transaction_under_autocommit_original
del self._start_transaction_under_autocommit_original
@contextmanager
@@ -206,10 +208,21 @@ def immediate_atomic(using=None, savepoint=True, durable=True):
else:
immediate_atomic = ImmediateAtomic(using, savepoint, durable)
# https://stackoverflow.com/a/45681273/339144 provides some context on nesting context managers; and how to proceed
# if you want to do this with an arbitrary number of context managers.
with SemaphoreContext(using), immediate_atomic:
yield
if get_snappea_settings().TASK_ALWAYS_EAGER:
# In ALWAYS_EAGER mode we cannot use SemaphoreContext as the outermost context, because any delay_on_commit
# tasks that are triggered on __exit__ of the (in that case, inner) immediate_atomic, when themselves initiating
# a new task-with-transaction, will not be able to acquire the semaphore (it's not been released yet).
# Fundamentally the solution would be to push the "on commit" logic onto the outermost context, but that seems
# fragile (monkeypatching/heavy overriding) and since the whole SemaphoreContext is only needed as an extra
# guard against WAL growth (not something we care about in the non-production setup), we just simplify for that
# case.
with immediate_atomic:
yield
else:
# https://stackoverflow.com/a/45681273/339144 provides some context on nesting context managers; and how to
# proceed if you want to do this with an arbitrary number of context managers.
with SemaphoreContext(using), immediate_atomic:
yield
def delay_on_commit(function, *args, **kwargs):