Transactiontask (#268)

* Introduce TransactionAwareTask

As described in
https://blog.hypertrack.io/2016/10/08/dealing-with-database-transactions-in-django-celery/

* Use TransactionAwareTask instead of countdown

… to prevent race conditions when using a newly created object in a
task.
This commit is contained in:
Tobias Kunze
2016-10-10 16:38:06 +02:00
committed by Raphael Michel
parent 6c5cd56af7
commit 9db333bf80
4 changed files with 37 additions and 6 deletions

View File

@@ -0,0 +1,28 @@
"""
This code has been taken from
https://blog.hypertrack.io/2016/10/08/dealing-with-database-transactions-in-django-celery/
Usage:
from pretix.base.services.async import TransactionAwareTask
@task(base=TransactionAwareTask)
def task_…():
"""
from celery import Task
from django.db import transaction
class TransactionAwareTask(Task):
"""
Task class which is aware of django db transactions and only executes tasks
after transaction has been committed
"""
abstract = True
def apply_async(self, *args, **kwargs):
"""
Unlike the default task in celery, this task does not return an async
result
"""
transaction.on_commit(
lambda: super(TransactionAwareTask, self).apply_async(*args, **kwargs)
)

View File

@@ -23,6 +23,7 @@ from reportlab.platypus import (
from pretix.base.i18n import LazyI18nString, language from pretix.base.i18n import LazyI18nString, language
from pretix.base.models import Invoice, InvoiceAddress, InvoiceLine, Order from pretix.base.models import Invoice, InvoiceAddress, InvoiceLine, Order
from pretix.base.services.async import TransactionAwareTask
from pretix.base.signals import register_payment_providers from pretix.base.signals import register_payment_providers
from pretix.celery import app from pretix.celery import app
@@ -375,7 +376,7 @@ def _invoice_generate_german(invoice, f):
return doc return doc
@app.task @app.task(base=TransactionAwareTask)
def invoice_pdf_task(invoice: int): def invoice_pdf_task(invoice: int):
i = Invoice.objects.get(pk=invoice) i = Invoice.objects.get(pk=invoice)
with language(i.locale): with language(i.locale):
@@ -394,7 +395,8 @@ def invoice_qualified(order: Order):
def invoice_pdf(*args, **kwargs): def invoice_pdf(*args, **kwargs):
# We introduce a 2 second delay, because otherwise we run into conditions where # We call this task asynchroneously, because otherwise we run into conditions where
# the task worker tries to generate the PDF even before our database transaction # the task worker tries to generate the PDF even before our database transaction
# was committed and therefore fails to find the invoice object. # was committed and therefore fails to find the invoice object. The invoice_pdf_task
invoice_pdf_task.apply_async(args=args, kwargs=kwargs, countdown=2) # will prevent this kind of race condition.
invoice_pdf_task.apply_async(args=args, kwargs=kwargs)

View File

@@ -9,6 +9,7 @@ from django.utils.translation import ugettext_noop
from pretix.base.i18n import language from pretix.base.i18n import language
from pretix.base.models import Event, Order, Quota from pretix.base.models import Event, Order, Quota
from pretix.base.services.async import TransactionAwareTask
from pretix.base.services.mail import SendMailException from pretix.base.services.mail import SendMailException
from pretix.base.services.orders import mark_order_paid from pretix.base.services.orders import mark_order_paid
from pretix.celery import app from pretix.celery import app
@@ -95,7 +96,7 @@ def _get_unknown_transactions(event: Event, job: BankImportJob, data: list):
return transactions return transactions
@app.task @app.task(base=TransactionAwareTask)
def process_banktransfers(event: int, job: int, data: list) -> None: def process_banktransfers(event: int, job: int, data: list) -> None:
with language("en"): # We'll translate error messages at display time with language("en"): # We'll translate error messages at display time
event = Event.objects.get(pk=event) event = Event.objects.get(pk=event)

View File

@@ -337,7 +337,7 @@ class ImportView(EventPermissionRequiredMixin, ListView):
'event': self.request.event.pk, 'event': self.request.event.pk,
'job': job.pk, 'job': job.pk,
'data': parsed 'data': parsed
}, countdown=1) })
return redirect(reverse('plugins:banktransfer:import.job', kwargs={ return redirect(reverse('plugins:banktransfer:import.job', kwargs={
'event': self.request.event.slug, 'event': self.request.event.slug,
'organizer': self.request.event.organizer.slug, 'organizer': self.request.event.organizer.slug,